This is an omnibus commit which moves the Citadel Server from crusty old GNU Autotool...
authorArt Cancro <ajc@citadel.org>
Sat, 4 Jun 2022 23:11:58 +0000 (19:11 -0400)
committerArt Cancro <ajc@citadel.org>
Sat, 4 Jun 2022 23:11:58 +0000 (19:11 -0400)
350 files changed:
citadel/.gitignore
citadel/COPYING
citadel/Makefile [new file with mode: 0644]
citadel/citadel.h [deleted file]
citadel/citadel_dirs.c [deleted file]
citadel/citadel_dirs.h [deleted file]
citadel/citadel_ldap.h [deleted file]
citadel/citserver.c [deleted file]
citadel/citserver.h [deleted file]
citadel/clientsocket.c [deleted file]
citadel/clientsocket.h [deleted file]
citadel/config.c [deleted file]
citadel/config.guess [deleted file]
citadel/config.h [deleted file]
citadel/config.sub [deleted file]
citadel/configure [new file with mode: 0755]
citadel/context.c [deleted file]
citadel/context.h [deleted file]
citadel/control.c [deleted file]
citadel/control.h [deleted file]
citadel/ctdl_module.h [deleted file]
citadel/database.c [deleted file]
citadel/database.h [deleted file]
citadel/default_timezone.c [deleted file]
citadel/default_timezone.h [deleted file]
citadel/domain.c [deleted file]
citadel/domain.h [deleted file]
citadel/euidindex.c [deleted file]
citadel/euidindex.h [deleted file]
citadel/genstamp.c [deleted file]
citadel/genstamp.h [deleted file]
citadel/housekeeping.c [deleted file]
citadel/housekeeping.h [deleted file]
citadel/internet_addressing.c [deleted file]
citadel/internet_addressing.h [deleted file]
citadel/ipcdef.h [deleted file]
citadel/journaling.c [deleted file]
citadel/journaling.h [deleted file]
citadel/ldap.c [deleted file]
citadel/locate_host.c [deleted file]
citadel/locate_host.h [deleted file]
citadel/missing [deleted file]
citadel/modules/autocompletion/.gitignore [deleted file]
citadel/modules/autocompletion/serv_autocompletion.c [deleted file]
citadel/modules/autocompletion/serv_autocompletion.h [deleted file]
citadel/modules/bio/.gitignore [deleted file]
citadel/modules/bio/serv_bio.c [deleted file]
citadel/modules/blog/serv_blog.c [deleted file]
citadel/modules/calendar/.gitignore [deleted file]
citadel/modules/calendar/serv_calendar.c [deleted file]
citadel/modules/calendar/serv_calendar.h [deleted file]
citadel/modules/checkpoint/.gitignore [deleted file]
citadel/modules/checkpoint/serv_checkpoint.c [deleted file]
citadel/modules/clamav/.gitignore [deleted file]
citadel/modules/clamav/serv_virus.c [deleted file]
citadel/modules/crypto/.gitignore [deleted file]
citadel/modules/crypto/serv_crypto.c [deleted file]
citadel/modules/crypto/serv_crypto.h [deleted file]
citadel/modules/ctdlproto/files.h [deleted file]
citadel/modules/ctdlproto/serv_ctdlproto.c [deleted file]
citadel/modules/ctdlproto/serv_file.c [deleted file]
citadel/modules/ctdlproto/serv_messages.c [deleted file]
citadel/modules/ctdlproto/serv_rooms.c [deleted file]
citadel/modules/ctdlproto/serv_session.c [deleted file]
citadel/modules/ctdlproto/serv_syscmds.c [deleted file]
citadel/modules/ctdlproto/serv_user.c [deleted file]
citadel/modules/expire/.gitignore [deleted file]
citadel/modules/expire/expire_policy.c [deleted file]
citadel/modules/expire/policy.h [deleted file]
citadel/modules/expire/serv_expire.c [deleted file]
citadel/modules/fulltext/.gitignore [deleted file]
citadel/modules/fulltext/crc16.c [deleted file]
citadel/modules/fulltext/crc16.h [deleted file]
citadel/modules/fulltext/ft_wordbreaker.c [deleted file]
citadel/modules/fulltext/ft_wordbreaker.h [deleted file]
citadel/modules/fulltext/serv_fulltext.c [deleted file]
citadel/modules/fulltext/serv_fulltext.h [deleted file]
citadel/modules/image/serv_image.c [deleted file]
citadel/modules/imap/.gitignore [deleted file]
citadel/modules/imap/imap_acl.c [deleted file]
citadel/modules/imap/imap_acl.h [deleted file]
citadel/modules/imap/imap_fetch.c [deleted file]
citadel/modules/imap/imap_fetch.h [deleted file]
citadel/modules/imap/imap_list.c [deleted file]
citadel/modules/imap/imap_list.h [deleted file]
citadel/modules/imap/imap_metadata.c [deleted file]
citadel/modules/imap/imap_metadata.h [deleted file]
citadel/modules/imap/imap_misc.c [deleted file]
citadel/modules/imap/imap_misc.h [deleted file]
citadel/modules/imap/imap_search.c [deleted file]
citadel/modules/imap/imap_search.h [deleted file]
citadel/modules/imap/imap_store.c [deleted file]
citadel/modules/imap/imap_store.h [deleted file]
citadel/modules/imap/imap_tools.c [deleted file]
citadel/modules/imap/imap_tools.h [deleted file]
citadel/modules/imap/serv_imap.c [deleted file]
citadel/modules/imap/serv_imap.h [deleted file]
citadel/modules/inboxrules/.gitignore [deleted file]
citadel/modules/inboxrules/serv_inboxrules.c [deleted file]
citadel/modules/inetcfg/.gitignore [deleted file]
citadel/modules/inetcfg/serv_inetcfg.c [deleted file]
citadel/modules/instmsg/.gitignore [deleted file]
citadel/modules/instmsg/serv_instmsg.c [deleted file]
citadel/modules/instmsg/serv_instmsg.h [deleted file]
citadel/modules/listdeliver/serv_listdeliver.c [deleted file]
citadel/modules/listsub/.gitignore [deleted file]
citadel/modules/listsub/serv_listsub.c [deleted file]
citadel/modules/migrate/.gitignore [deleted file]
citadel/modules/migrate/serv_migrate.c [deleted file]
citadel/modules/newuser/.gitignore [deleted file]
citadel/modules/newuser/serv_newuser.c [deleted file]
citadel/modules/nntp/serv_nntp.c [deleted file]
citadel/modules/nntp/serv_nntp.h [deleted file]
citadel/modules/nntp/wildmat.c [deleted file]
citadel/modules/notes/.gitignore [deleted file]
citadel/modules/notes/serv_notes.c [deleted file]
citadel/modules/openid/.gitignore [deleted file]
citadel/modules/openid/serv_openid_rp.c [deleted file]
citadel/modules/pop3/.gitignore [deleted file]
citadel/modules/pop3/serv_pop3.c [deleted file]
citadel/modules/pop3/serv_pop3.h [deleted file]
citadel/modules/pop3client/.gitignore [deleted file]
citadel/modules/pop3client/serv_pop3client.c [deleted file]
citadel/modules/roomchat/.gitignore [deleted file]
citadel/modules/roomchat/serv_roomchat.c [deleted file]
citadel/modules/rssclient/.gitignore [deleted file]
citadel/modules/rssclient/serv_rssclient.c [deleted file]
citadel/modules/rwho/.gitignore [deleted file]
citadel/modules/rwho/serv_rwho.c [deleted file]
citadel/modules/smtp/.gitignore [deleted file]
citadel/modules/smtp/serv_smtp.c [deleted file]
citadel/modules/smtp/serv_smtpclient.c [deleted file]
citadel/modules/smtp/smtp_util.c [deleted file]
citadel/modules/smtp/smtp_util.h [deleted file]
citadel/modules/spam/.gitignore [deleted file]
citadel/modules/spam/serv_spam.c [deleted file]
citadel/modules/test/.gitignore [deleted file]
citadel/modules/test/serv_test.c [deleted file]
citadel/modules/upgrade/.gitignore [deleted file]
citadel/modules/upgrade/serv_upgrade.c [deleted file]
citadel/modules/upgrade/serv_upgrade.h [deleted file]
citadel/modules/vcard/.gitignore [deleted file]
citadel/modules/vcard/serv_vcard.c [deleted file]
citadel/modules/wiki/.gitignore [deleted file]
citadel/modules/wiki/serv_wiki.c [deleted file]
citadel/modules/xmpp/.gitignore [deleted file]
citadel/modules/xmpp/serv_xmpp.c [deleted file]
citadel/modules/xmpp/serv_xmpp.h [deleted file]
citadel/modules/xmpp/xmpp_messages.c [deleted file]
citadel/modules/xmpp/xmpp_presence.c [deleted file]
citadel/modules/xmpp/xmpp_query_namespace.c [deleted file]
citadel/modules/xmpp/xmpp_queue.c [deleted file]
citadel/modules/xmpp/xmpp_sasl_service.c [deleted file]
citadel/msgbase.c [deleted file]
citadel/msgbase.h [deleted file]
citadel/netconfig.c [deleted file]
citadel/parsedate.c [deleted file]
citadel/parsedate.h [deleted file]
citadel/parsedate.y [deleted file]
citadel/room_ops.c [deleted file]
citadel/room_ops.h [deleted file]
citadel/scripts/mk_svn_revision.sh [deleted file]
citadel/serv_extensions.c [deleted file]
citadel/serv_extensions.h [deleted file]
citadel/serv_vcard.h [deleted file]
citadel/server.h [deleted file]
citadel/server/citadel.h [new file with mode: 0644]
citadel/server/citadel_dirs.c [new file with mode: 0644]
citadel/server/citadel_dirs.h [new file with mode: 0644]
citadel/server/citadel_ldap.h [new file with mode: 0644]
citadel/server/citserver.c [new file with mode: 0644]
citadel/server/citserver.h [new file with mode: 0644]
citadel/server/clientsocket.c [new file with mode: 0644]
citadel/server/clientsocket.h [new file with mode: 0644]
citadel/server/config.c [new file with mode: 0644]
citadel/server/config.h [new file with mode: 0644]
citadel/server/context.c [new file with mode: 0644]
citadel/server/context.h [new file with mode: 0644]
citadel/server/control.c [new file with mode: 0644]
citadel/server/control.h [new file with mode: 0644]
citadel/server/ctdl_module.h [new file with mode: 0644]
citadel/server/database.c [new file with mode: 0644]
citadel/server/database.h [new file with mode: 0644]
citadel/server/default_timezone.c [new file with mode: 0644]
citadel/server/default_timezone.h [new file with mode: 0644]
citadel/server/domain.c [new file with mode: 0644]
citadel/server/domain.h [new file with mode: 0644]
citadel/server/euidindex.c [new file with mode: 0644]
citadel/server/euidindex.h [new file with mode: 0644]
citadel/server/genstamp.c [new file with mode: 0644]
citadel/server/genstamp.h [new file with mode: 0644]
citadel/server/housekeeping.c [new file with mode: 0644]
citadel/server/housekeeping.h [new file with mode: 0644]
citadel/server/internet_addressing.c [new file with mode: 0644]
citadel/server/internet_addressing.h [new file with mode: 0644]
citadel/server/ipcdef.h [new file with mode: 0644]
citadel/server/journaling.c [new file with mode: 0644]
citadel/server/journaling.h [new file with mode: 0644]
citadel/server/ldap.c [new file with mode: 0644]
citadel/server/locate_host.c [new file with mode: 0644]
citadel/server/locate_host.h [new file with mode: 0644]
citadel/server/modules/autocompletion/.gitignore [new file with mode: 0644]
citadel/server/modules/autocompletion/serv_autocompletion.c [new file with mode: 0644]
citadel/server/modules/autocompletion/serv_autocompletion.h [new file with mode: 0644]
citadel/server/modules/bio/.gitignore [new file with mode: 0644]
citadel/server/modules/bio/serv_bio.c [new file with mode: 0644]
citadel/server/modules/blog/serv_blog.c [new file with mode: 0644]
citadel/server/modules/calendar/.gitignore [new file with mode: 0644]
citadel/server/modules/calendar/serv_calendar.c [new file with mode: 0644]
citadel/server/modules/calendar/serv_calendar.h [new file with mode: 0644]
citadel/server/modules/checkpoint/.gitignore [new file with mode: 0644]
citadel/server/modules/checkpoint/serv_checkpoint.c [new file with mode: 0644]
citadel/server/modules/clamav/.gitignore [new file with mode: 0644]
citadel/server/modules/clamav/serv_virus.c [new file with mode: 0644]
citadel/server/modules/crypto/.gitignore [new file with mode: 0644]
citadel/server/modules/crypto/serv_crypto.c [new file with mode: 0644]
citadel/server/modules/crypto/serv_crypto.h [new file with mode: 0644]
citadel/server/modules/ctdlproto/files.h [new file with mode: 0644]
citadel/server/modules/ctdlproto/serv_ctdlproto.c [new file with mode: 0644]
citadel/server/modules/ctdlproto/serv_file.c [new file with mode: 0644]
citadel/server/modules/ctdlproto/serv_messages.c [new file with mode: 0644]
citadel/server/modules/ctdlproto/serv_rooms.c [new file with mode: 0644]
citadel/server/modules/ctdlproto/serv_session.c [new file with mode: 0644]
citadel/server/modules/ctdlproto/serv_syscmds.c [new file with mode: 0644]
citadel/server/modules/ctdlproto/serv_user.c [new file with mode: 0644]
citadel/server/modules/expire/.gitignore [new file with mode: 0644]
citadel/server/modules/expire/expire_policy.c [new file with mode: 0644]
citadel/server/modules/expire/policy.h [new file with mode: 0644]
citadel/server/modules/expire/serv_expire.c [new file with mode: 0644]
citadel/server/modules/fulltext/.gitignore [new file with mode: 0644]
citadel/server/modules/fulltext/crc16.c [new file with mode: 0644]
citadel/server/modules/fulltext/crc16.h [new file with mode: 0644]
citadel/server/modules/fulltext/ft_wordbreaker.c [new file with mode: 0644]
citadel/server/modules/fulltext/ft_wordbreaker.h [new file with mode: 0644]
citadel/server/modules/fulltext/serv_fulltext.c [new file with mode: 0644]
citadel/server/modules/fulltext/serv_fulltext.h [new file with mode: 0644]
citadel/server/modules/image/serv_image.c [new file with mode: 0644]
citadel/server/modules/imap/.gitignore [new file with mode: 0644]
citadel/server/modules/imap/imap_acl.c [new file with mode: 0644]
citadel/server/modules/imap/imap_acl.h [new file with mode: 0644]
citadel/server/modules/imap/imap_fetch.c [new file with mode: 0644]
citadel/server/modules/imap/imap_fetch.h [new file with mode: 0644]
citadel/server/modules/imap/imap_list.c [new file with mode: 0644]
citadel/server/modules/imap/imap_list.h [new file with mode: 0644]
citadel/server/modules/imap/imap_metadata.c [new file with mode: 0644]
citadel/server/modules/imap/imap_metadata.h [new file with mode: 0644]
citadel/server/modules/imap/imap_misc.c [new file with mode: 0644]
citadel/server/modules/imap/imap_misc.h [new file with mode: 0644]
citadel/server/modules/imap/imap_search.c [new file with mode: 0644]
citadel/server/modules/imap/imap_search.h [new file with mode: 0644]
citadel/server/modules/imap/imap_store.c [new file with mode: 0644]
citadel/server/modules/imap/imap_store.h [new file with mode: 0644]
citadel/server/modules/imap/imap_tools.c [new file with mode: 0644]
citadel/server/modules/imap/imap_tools.h [new file with mode: 0644]
citadel/server/modules/imap/serv_imap.c [new file with mode: 0644]
citadel/server/modules/imap/serv_imap.h [new file with mode: 0644]
citadel/server/modules/inboxrules/.gitignore [new file with mode: 0644]
citadel/server/modules/inboxrules/serv_inboxrules.c [new file with mode: 0644]
citadel/server/modules/inetcfg/.gitignore [new file with mode: 0644]
citadel/server/modules/inetcfg/serv_inetcfg.c [new file with mode: 0644]
citadel/server/modules/instmsg/.gitignore [new file with mode: 0644]
citadel/server/modules/instmsg/serv_instmsg.c [new file with mode: 0644]
citadel/server/modules/instmsg/serv_instmsg.h [new file with mode: 0644]
citadel/server/modules/listdeliver/serv_listdeliver.c [new file with mode: 0644]
citadel/server/modules/listsub/.gitignore [new file with mode: 0644]
citadel/server/modules/listsub/serv_listsub.c [new file with mode: 0644]
citadel/server/modules/migrate/.gitignore [new file with mode: 0644]
citadel/server/modules/migrate/serv_migrate.c [new file with mode: 0644]
citadel/server/modules/newuser/.gitignore [new file with mode: 0644]
citadel/server/modules/newuser/serv_newuser.c [new file with mode: 0644]
citadel/server/modules/nntp/serv_nntp.c [new file with mode: 0644]
citadel/server/modules/nntp/serv_nntp.h [new file with mode: 0644]
citadel/server/modules/nntp/wildmat.c [new file with mode: 0644]
citadel/server/modules/notes/.gitignore [new file with mode: 0644]
citadel/server/modules/notes/serv_notes.c [new file with mode: 0644]
citadel/server/modules/openid/.gitignore [new file with mode: 0644]
citadel/server/modules/openid/serv_openid_rp.c [new file with mode: 0644]
citadel/server/modules/pop3/.gitignore [new file with mode: 0644]
citadel/server/modules/pop3/serv_pop3.c [new file with mode: 0644]
citadel/server/modules/pop3/serv_pop3.h [new file with mode: 0644]
citadel/server/modules/pop3client/.gitignore [new file with mode: 0644]
citadel/server/modules/pop3client/serv_pop3client.c [new file with mode: 0644]
citadel/server/modules/roomchat/.gitignore [new file with mode: 0644]
citadel/server/modules/roomchat/serv_roomchat.c [new file with mode: 0644]
citadel/server/modules/rssclient/.gitignore [new file with mode: 0644]
citadel/server/modules/rssclient/serv_rssclient.c [new file with mode: 0644]
citadel/server/modules/rwho/.gitignore [new file with mode: 0644]
citadel/server/modules/rwho/serv_rwho.c [new file with mode: 0644]
citadel/server/modules/smtp/.gitignore [new file with mode: 0644]
citadel/server/modules/smtp/serv_smtp.c [new file with mode: 0644]
citadel/server/modules/smtp/serv_smtpclient.c [new file with mode: 0644]
citadel/server/modules/smtp/smtp_util.c [new file with mode: 0644]
citadel/server/modules/smtp/smtp_util.h [new file with mode: 0644]
citadel/server/modules/spam/.gitignore [new file with mode: 0644]
citadel/server/modules/spam/serv_spam.c [new file with mode: 0644]
citadel/server/modules/test/.gitignore [new file with mode: 0644]
citadel/server/modules/test/serv_test.c [new file with mode: 0644]
citadel/server/modules/upgrade/.gitignore [new file with mode: 0644]
citadel/server/modules/upgrade/serv_upgrade.c [new file with mode: 0644]
citadel/server/modules/upgrade/serv_upgrade.h [new file with mode: 0644]
citadel/server/modules/vcard/.gitignore [new file with mode: 0644]
citadel/server/modules/vcard/serv_vcard.c [new file with mode: 0644]
citadel/server/modules/wiki/.gitignore [new file with mode: 0644]
citadel/server/modules/wiki/serv_wiki.c [new file with mode: 0644]
citadel/server/modules/xmpp/.gitignore [new file with mode: 0644]
citadel/server/modules/xmpp/serv_xmpp.c [new file with mode: 0644]
citadel/server/modules/xmpp/serv_xmpp.h [new file with mode: 0644]
citadel/server/modules/xmpp/xmpp_messages.c [new file with mode: 0644]
citadel/server/modules/xmpp/xmpp_presence.c [new file with mode: 0644]
citadel/server/modules/xmpp/xmpp_query_namespace.c [new file with mode: 0644]
citadel/server/modules/xmpp/xmpp_queue.c [new file with mode: 0644]
citadel/server/modules/xmpp/xmpp_sasl_service.c [new file with mode: 0644]
citadel/server/modules_init.c [new file with mode: 0644]
citadel/server/modules_init.h [new file with mode: 0644]
citadel/server/msgbase.c [new file with mode: 0644]
citadel/server/msgbase.h [new file with mode: 0644]
citadel/server/netconfig.c [new file with mode: 0644]
citadel/server/parsedate.c [new file with mode: 0644]
citadel/server/parsedate.h [new file with mode: 0644]
citadel/server/parsedate.y [new file with mode: 0644]
citadel/server/room_ops.c [new file with mode: 0644]
citadel/server/room_ops.h [new file with mode: 0644]
citadel/server/serv_extensions.c [new file with mode: 0644]
citadel/server/serv_extensions.h [new file with mode: 0644]
citadel/server/serv_vcard.h [new file with mode: 0644]
citadel/server/server.h [new file with mode: 0644]
citadel/server/server_main.c [new file with mode: 0644]
citadel/server/support.c [new file with mode: 0644]
citadel/server/support.h [new file with mode: 0644]
citadel/server/sysconfig.h [new file with mode: 0644]
citadel/server/sysdep.c [new file with mode: 0644]
citadel/server/sysdep.h [new file with mode: 0644]
citadel/server/sysdep_decls.h [new file with mode: 0644]
citadel/server/threads.c [new file with mode: 0644]
citadel/server/threads.h [new file with mode: 0644]
citadel/server/typesize.h [new file with mode: 0644]
citadel/server/user_ops.c [new file with mode: 0644]
citadel/server/user_ops.h [new file with mode: 0644]
citadel/server_main.c [deleted file]
citadel/support.c [deleted file]
citadel/support.h [deleted file]
citadel/svn_revision.h [deleted file]
citadel/sysconfig.h [deleted file]
citadel/sysdep.c [deleted file]
citadel/sysdep_decls.h [deleted file]
citadel/threads.c [deleted file]
citadel/threads.h [deleted file]
citadel/typesize.h [deleted file]
citadel/user_ops.c [deleted file]
citadel/user_ops.h [deleted file]

index 7f78ac606fb9e961f5eab8676d6da8325db8120e..26f283b082e32649138809764f6e80b5a1806aa6 100644 (file)
@@ -2,14 +2,8 @@
 *.d
 *.gcno
 *.gcda
-Make_modules
-Make_sources
-Makefile
-Makefile.in
 locale
-aclocal.m4
 aidepost
-base64
 build-arch-stamp
 build-indep-stamp
 chkpw
@@ -17,26 +11,15 @@ chkpwd
 citadel
 citmail
 citserver
-config.log
-config.status
-configure
-configure-stamp
 ctdlmigrate
 getmail
-modules_init.c
-modules_init.h
-modules_upgrade.c
 msgform
 panic.log
 sendcommand
 setup
 svn_revision.c
-sysdep.h
-sysdep.h.in
 userlist
-whobbs
 gmon.out
-autom4te.cache
 citadel.config
 citadel.control
 citadel.pid
index b9a6cc30fe1231851cfcb2c0d1e12e8a54717fad..97eb78723d68d03ad63a8f158cf5bdb9e6c2e1e9 100644 (file)
@@ -6,23 +6,13 @@ Copyright: (C) 1987-2022 Citadel development team; GPL V3
   software" -- and that our favorite operating system is called
   "Linux" -- NOT "GNU/Linux".
 
-* In addition, as a special exception, it is declared without exception
-  that Richard Stallman is a communist, is an asshole, and should
-  shut up and go away because he contributes nothing useful to the
-  open source community.
-
-* In addition, as a special exception, the copyright holders give
-  permission to link the code of portions of this program with the
-  OpenSSL library under certain conditions as described in each
-  individual source file, and distribute linked combinations
-  including the two.
-  You must obey the General Public License in all respects
-  for all of the code used other than OpenSSL. If you modify
-  file(s) with this exception, you may extend this exception to your
-  version of the file(s), but you are not obligated to do so. If you
-  do not wish to do so, delete this exception statement from your
-  version. If you delete this exception statement from all source
-  files in the program, then also delete it here.
+* We reject and oppose Richard M. Stallman because he contributes
+  nothing but trouble to the open source community.
+
+* As a special exception, the copyright holders of the Citadel
+  system grant permission to link Citadel with non-GPL-compatible
+  libraries, including OpenSSL, in the sole case that such linking
+  is done to build the Citadel system itself.
 
 
                        GENERAL PUBLIC LICENSE
diff --git a/citadel/Makefile b/citadel/Makefile
new file mode 100644 (file)
index 0000000..f1439fa
--- /dev/null
@@ -0,0 +1,27 @@
+# Makefile for Citadel Server
+# Copyright (c) 1987-2022 by Art Cancro and the citadel.org team
+# 
+# This is the new and improved version that does not use the GNU Autotools,
+# because it is The Current Year and we aren't trying to build for weird
+# obscure systems anymore.
+#
+# This program is open source software.  Use, duplication, and/or
+# disclosure are subject to the GNU General Purpose License version 3.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+
+# config.mk is generated by ./configure
+include config.mk
+
+citserver: server/*.c server/modules/*/*.c config.mk
+       cc ${CFLAGS} \
+               server/*.c server/modules/*/*.c \
+               ${LDFLAGS} -lresolv -lcitadel -lpthread -lz -lical -lldap -lcrypt -lexpat -lcurl -ldb \
+               -o citserver
+
+config.mk: configure
+       ./configure
diff --git a/citadel/citadel.h b/citadel/citadel.h
deleted file mode 100644 (file)
index 5e80d65..0000000
+++ /dev/null
@@ -1,187 +0,0 @@
-// Main Citadel header file
-//
-// Copyright (c) 1987-2021 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-/* system customizations are in sysconfig.h */
-
-#ifndef CITADEL_H
-#define CITADEL_H
-/* #include <dmalloc.h> uncomment if using dmalloc */
-
-#include "sysdep.h"
-#include <limits.h>
-#include "sysconfig.h"
-#include "typesize.h"
-#include "ipcdef.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/*
- * Text description of this software
- * (We used to define this ourselves, but why bother when the build tools do it for us?)
- */
-#define CITADEL        PACKAGE_STRING
-
-#define REV_LEVEL 951          // This version
-#define REV_MIN                591             // Oldest compatible database
-#define EXPORT_REV_MIN 931             // Oldest compatible export files
-#define LIBCITADEL_MIN 931             // Minimum required version of libcitadel
-#define SERVER_TYPE    0               // zero for stock Citadel; other developers please obtain SERVER_TYPE codes for your implementations
-
-#ifdef LIBCITADEL_VERSION_NUMBER
-#if LIBCITADEL_VERSION_NUMBER < LIBCITADEL_MIN
-#error libcitadel is too old.  Please upgrade it before continuing.
-#endif
-#endif
-
-/*
- * This is the user name and password for the default administrator account
- * that is created when Citadel Server is started with an empty database.
- */
-#define DEFAULT_ADMIN_USERNAME "admin"
-#define DEFAULT_ADMIN_PASSWORD "citadel"
-
-/* Various length constants */
-
-#define ROOMNAMELEN    128             /* The size of a roomname string */
-#define USERNAME_SIZE  64              /* The size of a username string */
-#define MAX_EDITORS    5               /* number of external editors supported ; must be at least 1 */
-
-/*
- * Message expiration policy stuff
- */
-typedef struct ExpirePolicy ExpirePolicy;
-struct ExpirePolicy {
-       int expire_mode;
-       int expire_value;
-};
-
-#define EXPIRE_NEXTLEVEL       0       // Inherit expiration policy
-#define EXPIRE_MANUAL          1       // Don't expire messages at all
-#define EXPIRE_NUMMSGS         2       // Keep only latest n messages
-#define EXPIRE_AGE             3       // Expire messages after n days
-
-
-/*
- * User records.
- */
-typedef struct ctdluser ctdluser;
-struct ctdluser {                      // User record
-       int version;                    // Citadel version which created this record
-       uid_t uid;                      // Associate with a unix account?
-       char password[32];              // password
-       unsigned flags;                 // See US_ flags below
-       long timescalled;               // Total number of logins
-       long posted;                    // Number of messages ever submitted
-       cit_uint8_t axlevel;            // Access level
-       long usernum;                   // User number (never recycled)
-       time_t lastcall;                // Date/time of most recent login
-       int USuserpurge;                // Purge time (in days) for user
-       char fullname[64];              // Display name (primary identifier)
-       long msgnum_bio;                // msgnum of user's profile (bio)
-       long msgnum_pic;                // msgnum of user's avatar (photo)
-       char emailaddrs[512];           // Internet email addresses
-       long msgnum_inboxrules;         // msgnum of user's inbox filtering rules
-       long lastproc_inboxrules;       // msgnum of last message filtered
-};
-
-
-/* Bits which may appear in MMflags.
- */
-#define MM_VALID       4               // New users need validating
-
-/*
- * Room records.
- */
-typedef struct ctdlroom ctdlroom;
-struct ctdlroom {
-       char QRname[ROOMNAMELEN];       // Name of room
-       char QRpasswd[10];              // Only valid if it's a private rm
-       long QRroomaide;                // User number of room aide
-       long QRhighest;                 // Highest message NUMBER in room
-       time_t QRgen;                   // Generation number of room
-       unsigned QRflags;               // See flag values below
-       char QRdirname[15];             // Directory name, if applicable
-       long msgnum_info;               // msgnum of room banner (info file)
-       char QRfloor;                   // Which floor this room is on
-       time_t QRmtime;                 // Date/time of last post
-       struct ExpirePolicy QRep;       // Message expiration policy
-       long QRnumber;                  // Globally unique room number
-       char QRorder;                   // Sort key for room listing order
-       unsigned QRflags2;              // Additional flags
-       int QRdefaultview;              // How to display the contents
-       long msgnum_pic;                // msgnum of room picture or icon
-};
-
-/* Private rooms are always flagged with QR_PRIVATE.  If neither QR_PASSWORDED
- * or QR_GUESSNAME is set, then it is invitation-only.  Passworded rooms are
- * flagged with both QR_PRIVATE and QR_PASSWORDED while guess-name rooms are
- * flagged with both QR_PRIVATE and QR_GUESSNAME.  NEVER set all three flags.
- */
-
-/*
- * Miscellaneous
- */
-#define MES_NORMAL     65              // Normal message
-#define MES_ANONONLY   66              // "****" header
-#define MES_ANONOPT    67              // "Anonymous" header
-
-/****************************************************************************/
-
-/*
- * Floor record.  The floor number is implicit in its location in the file.
- */
-typedef struct floor floor;
-struct floor {
-       unsigned short f_flags;         // flags
-       char f_name[256];               // name of floor
-       int f_ref_count;                // reference count
-       struct ExpirePolicy f_ep;       // default expiration policy
-};
-
-#define F_INUSE                1               // floor is in use
-
-
-/*
- * Values used internally for function call returns, etc.
- */
-#define NEWREGISTER    0               // new user to register
-#define REREGISTER     1               // existing user reregistering
-
-/* number of items which may be handled by the CONF command */
-#define NUM_CONFIGS 71
-
-#define TRACE  syslog(LOG_DEBUG, "\033[7m  Checkpoint: %s : %d  \033[0m", __FILE__, __LINE__)
-
-#ifndef LONG_MAX
-#define LONG_MAX 2147483647L
-#endif
-
-/*
- * Authentication modes
- */
-#define AUTHMODE_NATIVE                0       // Native (self-contained or "black box")
-#define AUTHMODE_HOST          1       // Authenticate against the host OS user database
-#define AUTHMODE_LDAP          2       // Authenticate using LDAP server with RFC 2307 schema
-#define AUTHMODE_LDAP_AD       3       // Authenticate using LDAP server with Active Directory schema
-
-#ifdef __cplusplus
-}
-#endif
-
-#if __GNUC__ >= 8
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wformat-truncation"
-#endif
-
-#endif /* CITADEL_H */
diff --git a/citadel/citadel_dirs.c b/citadel/citadel_dirs.c
deleted file mode 100644 (file)
index 0560477..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-// citadel_dirs.c : calculate pathnames for various files used in the Citadel system
-//
-// Copyright (c) 1987-2021 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or disclosure
-// is subject to the terms of the GNU General Public License, version 3.
-// The program is distributed without any warranty, expressed or implied.
-
-#include <unistd.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <time.h>
-#include <errno.h>
-#include <syslog.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "citadel_dirs.h"
-
-// Generate an associated file name for a room
-size_t assoc_file_name(char *buf, size_t n, struct ctdlroom *qrbuf, const char *prefix) {
-       return snprintf(buf, n, "%s%ld", prefix, qrbuf->QRnumber);
-}
-
-
-int create_dir(char *which, long ACCESS, long UID, long GID) {
-       int rv;
-       rv = mkdir(which, ACCESS);
-       if ((rv == -1) && (errno != EEXIST)) {
-               syslog(LOG_ERR,
-                      "failed to create directory %s: %s",
-                      which,
-                      strerror(errno));
-               return rv;
-       }
-       rv = chmod(which, ACCESS);
-       if (rv == -1) {
-               syslog(LOG_ERR, "failed to set permissions for directory %s: %s", which, strerror(errno));
-               return rv;
-       }
-       rv = chown(which, UID, GID);
-       if (rv == -1) {
-               syslog(LOG_ERR, "failed to set owner for directory %s: %s", which, strerror(errno));
-               return rv;
-       }
-       return rv;
-}
-
-
-int create_run_directories(long UID, long GID) {
-       int rv = 0;
-       rv += create_dir(ctdl_message_dir   , S_IRUSR|S_IWUSR|S_IXUSR, UID, -1);
-       rv += create_dir(ctdl_file_dir      , S_IRUSR|S_IWUSR|S_IXUSR, UID, -1);
-       rv += create_dir(ctdl_key_dir       , S_IRUSR|S_IWUSR|S_IXUSR, UID, -1);
-       rv += create_dir(ctdl_run_dir       , S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH, UID, GID);
-       return rv;
-}
diff --git a/citadel/citadel_dirs.h b/citadel/citadel_dirs.h
deleted file mode 100644 (file)
index ad422bf..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-#ifndef __CITADEL_DIRS_H
-#define __CITADEL_DIRS_H
-
-#include <limits.h>
-
-/* Fixed directory names (some of these are obsolete and used only for migration) */
-#define ctdl_home_directory    "."
-#define ctdl_db_dir            "data"
-#define ctdl_file_dir          "files"
-#define ctdl_shared_dir                "."
-#define ctdl_image_dir         "images"
-#define ctdl_info_dir          "info"
-#define ctdl_key_dir           "keys"
-#define ctdl_message_dir       "messages"
-#define ctdl_usrpic_dir                "userpics"
-#define ctdl_autoetc_dir       "."
-#define ctdl_run_dir           "."
-#define ctdl_netcfg_dir                "netconfigs"
-#define ctdl_bbsbase_dir       "."
-#define ctdl_sbin_dir          "."
-#define ctdl_bin_dir           "."
-#define ctdl_utilbin_dir       "."
-
-/* Fixed file names (some of these are obsolete and used only for migration) */
-#define file_citadel_config            "citadel.config"
-#define file_lmtp_socket               "lmtp.socket"
-#define file_lmtp_unfiltered_socket    "lmtp-unfiltered.socket"
-#define file_arcq                      "refcount_adjustments.dat"
-#define file_citadel_socket            "citadel.socket"
-#define file_citadel_admin_socket      "citadel-admin.socket"
-#define file_pid_file                  "/var/run/citserver.pid"
-#define file_pid_paniclog              "panic.log"
-#define file_crpt_file_key             "keys/citadel.key"
-#define file_crpt_file_cer             "keys/citadel.cer"
-#define file_chkpwd                    "chkpwd"
-#define file_guesstimezone             "guesstimezone.sh"
-
-
-/* externs */
-extern int create_run_directories(long UID, long GUID);
-extern size_t assoc_file_name(char *buf, size_t n, struct ctdlroom *qrbuf, const char *prefix);
-extern FILE *create_digest_file(struct ctdlroom *room, int forceCreate);
-extern void remove_digest_file(struct ctdlroom *room);
-
-#endif /* __CITADEL_DIRS_H */
diff --git a/citadel/citadel_ldap.h b/citadel/citadel_ldap.h
deleted file mode 100644 (file)
index 926fbde..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-// Configuration for LDAP authentication.  Most of this stuff gets pulled out of our site config file.
-//
-// Copyright (c) 1987-2017 by the citadel.org team
-//
-//  This program is open source software; you can redistribute it and/or modify
-//  it under the terms of the GNU General Public License version 3.
-//
-//  This program is distributed in the hope that it will be useful,
-//  but WITHOUT ANY WARRANTY; without even the implied warranty of
-//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//  GNU General Public License for more details.
-
-int CtdlTryUserLDAP(char *username, char *found_dn, int found_dn_size, char *fullname, int fullname_size, uid_t *found_uid);
-int CtdlTryPasswordLDAP(char *user_dn, const char *password);
-int Ctdl_LDAP_to_vCard(char *ldap_dn, struct vCard *v);
-int extract_email_addresses_from_ldap(char *ldap_dn, char *emailaddrs);
-void CtdlSynchronizeUsersFromLDAP(void);
diff --git a/citadel/citserver.c b/citadel/citserver.c
deleted file mode 100644 (file)
index 2d829f1..0000000
+++ /dev/null
@@ -1,198 +0,0 @@
-// Main source module for the Citadel server
-//
-// Copyright (c) 1987-2021 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or disclosure
-// is subject to the terms of the GNU General Public License, version 3.
-// The program is distributed without any warranty, expressed or implied.
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include "sysdep.h"
-#include <time.h>
-#include <libcitadel.h>
-
-#include "ctdl_module.h"
-#include "housekeeping.h"
-#include "locate_host.h"
-#include "citserver.h"
-#include "user_ops.h"
-#include "control.h"
-#include "config.h"
-
-char *unique_session_numbers;
-int ScheduledShutdown = 0;
-time_t server_startup_time;
-int panic_fd;
-int openid_level_supported = 0;
-
-
-// We need pseudo-random numbers for a few things.  Seed generously.
-void seed_random_number_generator(void) {
-       FILE *urandom;
-       struct timeval tv;
-       unsigned int seed;
-
-       syslog(LOG_INFO, "Seeding the pseudo-random number generator...");
-       urandom = fopen("/dev/urandom", "r");
-       if (urandom != NULL) {
-               if (fread(&seed, sizeof seed, 1, urandom) == -1) {
-                       syslog(LOG_ERR, "citserver: failed to read random seed: %m");
-               }
-               fclose(urandom);
-       }
-       else {
-               gettimeofday(&tv, NULL);
-               seed = tv.tv_usec;
-       }
-       srand(seed);
-       srandom(seed);
-}
-
-
-// Various things that need to be initialized at startup
-void master_startup(void) {
-       struct ctdlroom qrbuf;
-       struct passwd *pw;
-       gid_t gid;
-
-       syslog(LOG_DEBUG, "master_startup() started");
-       time(&server_startup_time);
-
-       syslog(LOG_INFO, "Checking directory access");
-       if ((pw = getpwuid(ctdluid)) == NULL) {
-               gid = getgid();
-       }
-       else {
-               gid = pw->pw_gid;
-       }
-
-       if (create_run_directories(CTDLUID, gid) != 0) {
-               syslog(LOG_ERR, "citserver: failed to access and create directories");
-               exit(1);
-       }
-       syslog(LOG_INFO, "Opening databases");
-       open_databases();
-
-       // Load site-specific configuration
-       seed_random_number_generator();                                 // must be done before config system
-       syslog(LOG_INFO, "Initializing configuration system");
-       initialize_config_system();
-       validate_config();
-       migrate_legacy_control_record();
-
-       // If we have an existing database that is older than version 928, reindex the user records.
-       // Unfortunately we cannot do this in serv_upgrade.c because it needs to happen VERY early during startup.
-       int existing_db = CtdlGetConfigInt("MM_hosted_upgrade_level");
-       if ( (existing_db > 0) && (existing_db < 928) ) {
-               ForEachUser(reindex_user_928, NULL);
-       }
-
-       // Check floor reference counts
-       check_ref_counts();
-
-       syslog(LOG_INFO, "Creating base rooms (if necessary)");
-       CtdlCreateRoom(CtdlGetConfigStr("c_baseroom"), 0, "", 0, 1, 0, VIEW_BBS);
-       CtdlCreateRoom(AIDEROOM, 3, "", 0, 1, 0, VIEW_BBS);
-       CtdlCreateRoom(SYSCONFIGROOM, 3, "", 0, 1, 0, VIEW_BBS);
-       CtdlCreateRoom(CtdlGetConfigStr("c_twitroom"), 0, "", 0, 1, 0, VIEW_BBS);
-
-       // The "Local System Configuration" room doesn't need to be visible
-       if (CtdlGetRoomLock(&qrbuf, SYSCONFIGROOM) == 0) {
-               qrbuf.QRflags2 |= QR2_SYSTEM;
-               CtdlPutRoomLock(&qrbuf);
-       }
-
-       // Aide needs to be public postable, else we're not RFC conformant.
-       if (CtdlGetRoomLock(&qrbuf, AIDEROOM) == 0) {
-               qrbuf.QRflags2 |= QR2_SMTP_PUBLIC;
-               CtdlPutRoomLock(&qrbuf);
-       }
-
-       syslog(LOG_DEBUG, "master_startup() finished");
-}
-
-
-// Cleanup routine to be called when the server is shutting down.  Returns the needed exit code.
-int master_cleanup(int exitcode) {
-       static int already_cleaning_up = 0;
-
-       if (already_cleaning_up) {
-               while (1) {
-                       usleep(1000000);
-               }
-       }
-       already_cleaning_up = 1;
-
-       // Do system-dependent stuff
-       sysdep_master_cleanup();
-
-       // Close the configuration system
-       shutdown_config_system();
-
-       // Close databases
-       syslog(LOG_INFO, "citserver: closing databases");
-       close_databases();
-
-       // If the operator requested a halt but not an exit, halt here.
-       if (shutdown_and_halt) {
-               syslog(LOG_ERR, "citserver: Halting server without exiting.");
-               fflush(stdout);
-               fflush(stderr);
-               while (1) {
-                       sleep(32767);
-               }
-       }
-
-       // Now go away.
-       syslog(LOG_ERR, "citserver: Exiting with status %d", exitcode);
-       fflush(stdout);
-       fflush(stderr);
-
-       if (restart_server != 0) {
-               exitcode = 1;
-       }
-       else if ((running_as_daemon != 0) && ((exitcode == 0))) {
-               exitcode = CTDLEXIT_SHUTDOWN;
-       }
-       return (exitcode);
-}
-
-
-// returns an asterisk if there are any instant messages waiting, space otherwise.
-char CtdlCheckExpress(void) {
-       if (CC->FirstExpressMessage == NULL) {
-               return (' ');
-       }
-       else {
-               return ('*');
-       }
-}
-
-
-void citproto_begin_session() {
-       if (CC->nologin == 1) {
-               cprintf("%d Too many users are already online (maximum is %d)\n",
-                       ERROR + MAX_SESSIONS_EXCEEDED, CtdlGetConfigInt("c_maxsessions")
-               );
-               CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED;
-       }
-       else {
-               cprintf("%d %s Citadel server ready.\n", CIT_OK, CtdlGetConfigStr("c_fqdn"));
-               CC->can_receive_im = 1;
-       }
-}
-
-
-void citproto_begin_admin_session() {
-       CC->internal_pgm = 1;
-       cprintf("%d %s Citadel server ADMIN CONNECTION ready.\n", CIT_OK, CtdlGetConfigStr("c_fqdn"));
-}
-
-
-// This loop performs all asynchronous functions.
-void do_async_loop(void) {
-       PerformSessionHooks(EVT_ASYNC);
-}
diff --git a/citadel/citserver.h b/citadel/citserver.h
deleted file mode 100644 (file)
index effe955..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 1987-2019 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-/* I fucking hate const and want it to die. */
-#pragma GCC diagnostic ignored "-Wcast-qual"
-
-#include "serv_extensions.h"
-#include "context.h"
-#include "ctdl_module.h"
-
-/* Simple linked list structures ... used in a bunch of different places. */
-typedef struct RoomProcList RoomProcList;
-struct RoomProcList {
-        struct RoomProcList *next;
-        char name[ROOMNAMELEN];
-        char lcname[ROOMNAMELEN];
-       long namelen;
-       long lastsent;
-       long key;
-       long QRNum;
-};
-struct UserProcList {
-       struct UserProcList *next;
-       char user[USERNAME_SIZE];
-};
-
-#define CTDLUSERIP      (IsEmptyStr(CC->cs_addr) ?  CC->cs_clientinfo: CC->cs_addr)
-
-void master_startup (void);
-int master_cleanup (int exitcode);
-void set_wtmpsupp (char *newtext);
-void set_wtmpsupp_to_current_room(void);
-void do_command_loop(void);
-void do_async_loop(void);
-void begin_session(struct CitContext *con);
-void citproto_begin_session(void);
-void citproto_begin_admin_session(void);
-void help_subst (char *strbuf, char *source, char *dest);
-char CtdlCheckExpress(void);
-
-extern int panic_fd;
-extern time_t server_startup_time;
-extern int openid_level_supported;
diff --git a/citadel/clientsocket.c b/citadel/clientsocket.c
deleted file mode 100644 (file)
index a88c614..0000000
+++ /dev/null
@@ -1,270 +0,0 @@
-// This module handles client-side sockets opened by the Citadel server (for
-// the client side of Internet protocols, etc.)   It does _not_ handle client
-// sockets for the Citadel client; for that you must look in ipc_c_tcp.c
-// (which, uncoincidentally, bears a striking similarity to this file).
-//
-// Copyright (c) 1987-2017 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or disclosure
-// is subject to the terms of the GNU General Public License, version 3.
-// The program is distributed without any warranty, expressed or implied.
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <netdb.h>
-#include <stdio.h>
-#ifdef __FreeBSD__
-#include <sys/socket.h>
-#endif
-#include <libcitadel.h>
-#include "ctdl_module.h"
-#include "clientsocket.h"
-
-int sock_connect(char *host, char *service) {
-       struct in6_addr serveraddr;
-       struct addrinfo hints;
-       struct addrinfo *res = NULL;
-       struct addrinfo *ai = NULL;
-       int rc = (-1);
-       int sock = (-1);
-
-       if ((host == NULL) || IsEmptyStr(host))
-               return (-1);
-       if ((service == NULL) || IsEmptyStr(service))
-               return (-1);
-
-       memset(&hints, 0x00, sizeof(hints));
-       hints.ai_flags = AI_NUMERICSERV;
-       hints.ai_family = AF_UNSPEC;
-       hints.ai_socktype = SOCK_STREAM;
-
-       // Handle numeric IPv4 and IPv6 addresses
-       rc = inet_pton(AF_INET, host, &serveraddr);
-       if (rc == 1) {                                          // dotted quad
-               hints.ai_family = AF_INET;
-               hints.ai_flags |= AI_NUMERICHOST;
-       }
-       else {
-               rc = inet_pton(AF_INET6, host, &serveraddr);
-               if (rc == 1) {                                  // IPv6 address
-                       hints.ai_family = AF_INET6;
-                       hints.ai_flags |= AI_NUMERICHOST;
-               }
-       }
-
-       // Begin the connection process
-       rc = getaddrinfo(host, service, &hints, &res);
-       if (rc != 0) {
-               syslog(LOG_ERR, "%s: %s", host, gai_strerror(rc));
-               return(-1);
-       }
-
-       // Try all available addresses until we connect to one or until we run out.
-       for (ai = res; ai != NULL; ai = ai->ai_next) {
-               sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
-               if (sock < 0) {
-                       syslog(LOG_ERR, "%s: %m", host);
-                       freeaddrinfo(res);
-                       return(-1);
-               }
-               rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
-               if (rc >= 0) {
-                        freeaddrinfo(res);
-                       return(sock);
-               }
-               else {
-                       syslog(LOG_ERR, "%s: %m", host);
-                       close(sock);
-               }
-       }
-       freeaddrinfo(res);
-       return(-1);
-}
-
-
-// Read data from the client socket.
-//
-// sock                socket fd to read from
-// buf         buffer to read into 
-// bytes       number of bytes to read
-// timeout     Number of seconds to wait before timing out
-//
-// Possible return values:
-//      1       Requested number of bytes has been read.
-//      0       Request timed out.
-//     -1      Connection is broken, or other error.
-int socket_read_blob(int *Socket, StrBuf *Target, int bytes, int timeout) {
-       const char *Error;
-       int retval = 0;
-
-       retval = StrBufReadBLOBBuffered(Target, CC->SBuf.Buf, &CC->SBuf.ReadWritePointer, Socket, 1, bytes, O_TERM, &Error); 
-       if (retval < 0) {
-               syslog(LOG_ERR, "clientsocket: socket_read_blob() failed: %s", Error);
-       }
-       return retval;
-}
-
-
-int CtdlSockGetLine(int *sock, StrBuf *Target, int nSec) {
-       const char *Error;
-       int rc;
-
-       FlushStrBuf(Target);
-       rc = StrBufTCP_read_buffered_line_fast(Target,
-                                              CC->SBuf.Buf,
-                                              &CC->SBuf.ReadWritePointer,
-                                              sock, nSec, 1, &Error);
-       if ((rc < 0) && (Error != NULL)) {
-               syslog(LOG_ERR, "clientsocket: CtdlSockGetLine() failed: %s", Error);
-       }
-       return rc;
-}
-
-
-// client_getln()   ...   Get a LF-terminated line of text from the client.
-int sock_getln(int *sock, char *buf, int bufsize) {
-       int i, retval;
-       const char *pCh;
-
-       FlushStrBuf(CC->sMigrateBuf);
-       retval = CtdlSockGetLine(sock, CC->sMigrateBuf, 5);
-
-       i = StrLength(CC->sMigrateBuf);
-       pCh = ChrPtr(CC->sMigrateBuf);
-
-       memcpy(buf, pCh, i + 1);
-
-       FlushStrBuf(CC->sMigrateBuf);
-       if (retval < 0) {
-               safestrncpy(&buf[i], "000", bufsize - i);
-               i += 3;
-       }
-       return i;
-}
-
-
-// sock_write() - send binary to server.
-// Returns the number of bytes written, or -1 for error.
-int sock_write(int *sock, const char *buf, int nbytes) {
-       return sock_write_timeout(sock, buf, nbytes, 50);
-}
-
-
-int sock_write_timeout(int *sock, const char *buf, int nbytes, int timeout) {
-       int nSuccessLess = 0;
-       int bytes_written = 0;
-       int retval;
-       fd_set rfds;
-        int fdflags;
-       int IsNonBlock;
-       struct timeval tv;
-       int selectresolution = 100;
-
-       fdflags = fcntl(*sock, F_GETFL);
-       IsNonBlock = (fdflags & O_NONBLOCK) == O_NONBLOCK;
-
-       while ((nSuccessLess < timeout) && (*sock != -1) && (bytes_written < nbytes)) {
-               if (IsNonBlock) {
-                       tv.tv_sec = selectresolution;
-                       tv.tv_usec = 0;
-                       
-                       FD_ZERO(&rfds);
-                       FD_SET(*sock, &rfds);
-                       if (select(*sock + 1, NULL, &rfds, NULL, &tv) == -1) {
-                               close (*sock);
-                               *sock = -1;
-                               return -1;
-                       }
-               }
-               if (IsNonBlock && !  FD_ISSET(*sock, &rfds)) {
-                       nSuccessLess ++;
-                       continue;
-               }
-               retval = write(*sock, &buf[bytes_written],
-                              nbytes - bytes_written);
-               if (retval < 1) {
-                       sock_close(*sock);
-                       *sock = -1;
-                       return (-1);
-               }
-               bytes_written = bytes_written + retval;
-               if (IsNonBlock && (bytes_written == nbytes)){
-                       tv.tv_sec = selectresolution;
-                       tv.tv_usec = 0;
-                       
-                       FD_ZERO(&rfds);
-                       FD_SET(*sock, &rfds);
-                       if (select(*sock + 1, NULL, &rfds, NULL, &tv) == -1) {
-                               close (*sock);
-                               *sock = -1;
-                               return -1;
-                       }
-               }
-       }
-       return (bytes_written);
-}
-
-
-// client_getln()   ...   Get a LF-terminated line of text from the client.
-int sock_getln_err(int *sock, char *buf, int bufsize, int *rc, int nSec) {
-       int i, retval;
-       const char *pCh;
-
-       FlushStrBuf(CC->sMigrateBuf);
-       *rc = retval = CtdlSockGetLine(sock, CC->sMigrateBuf, nSec);
-
-       i = StrLength(CC->sMigrateBuf);
-       pCh = ChrPtr(CC->sMigrateBuf);
-
-       memcpy(buf, pCh, i + 1);
-
-       FlushStrBuf(CC->sMigrateBuf);
-       if (retval < 0) {
-               safestrncpy(&buf[i], "000", bufsize - i);
-               i += 3;
-       }
-       return i;
-}
-
-
-// Multiline version of sock_gets() ... this is a convenience function for
-// client side protocol implementations.  It only returns the first line of
-// a multiline response, discarding the rest.
-int ml_sock_gets(int *sock, char *buf, int nSec) {
-       int rc = 0;
-       char bigbuf[1024];
-       int g;
-
-       g = sock_getln_err(sock, buf, SIZ, &rc, nSec);
-       if (rc < 0)
-               return rc;
-       if (g < 4)
-               return (g);
-       if (buf[3] != '-')
-               return (g);
-
-       do {
-               g = sock_getln_err(sock, bigbuf, SIZ, &rc, nSec);
-               if (rc < 0)
-                       return rc;
-               if (g < 0)
-                       return (g);
-       } while ((g >= 4) && (bigbuf[3] == '-'));
-
-       return (strlen(buf));
-}
-
-
-// sock_puts() - send line to server - implemented in terms of serv_write()
-// Returns the number of bytes written, or -1 for error.
-int sock_puts(int *sock, char *buf) {
-       int i, j;
-
-       i = sock_write(sock, buf, strlen(buf));
-       if (i < 0)
-               return (i);
-       j = sock_write(sock, "\n", 1);
-       if (j < 0)
-               return (j);
-       return (i + j);
-}
diff --git a/citadel/clientsocket.h b/citadel/clientsocket.h
deleted file mode 100644 (file)
index cc275e1..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-// Header file for TCP client socket library
-//
-// Copyright (c) 1987-2012 by the citadel.org team
-//
-//  This program is open source software; you can redistribute it and/or modify
-//  it under the terms of the GNU General Public License version 3.
-//
-//  This program is distributed in the hope that it will be useful,
-//  but WITHOUT ANY WARRANTY; without even the implied warranty of
-//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//  GNU General Public License for more details.
-
-int sock_connect(char *host, char *service);
-int sock_write(int *sock, const char *buf, int nbytes);
-int sock_write_timeout(int *sock, const char *buf, int nbytes, int timeout);
-int ml_sock_gets(int *sock, char *buf, int nSec);
-int sock_getln(int *sock, char *buf, int bufsize);
-int CtdlSockGetLine(int *sock, StrBuf *Target, int nSec);
-int sock_puts(int *sock, char *buf);
-int socket_read_blob(int *Socket, StrBuf * Target, int bytes, int timeout);
-
-
-/* 
- * This looks dumb, but it's being done for future portability
- */
-#define sock_close(sock)               close(sock)
-#define sock_shutdown(sock, how)       shutdown(sock, how)
-
-/* 
- * Default timeout for client sessions
- */
-#define CLIENT_TIMEOUT         600
diff --git a/citadel/config.c b/citadel/config.c
deleted file mode 100644 (file)
index 86077a0..0000000
+++ /dev/null
@@ -1,485 +0,0 @@
-// Read and write the system configuration database
-//
-// Copyright (c) 1987-2022 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or disclosure
-// is subject to the terms of the GNU General Public License, version 3.
-// The program is distributed without any warranty, expressed or implied.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <netdb.h>
-#include <crypt.h>
-#include <sys/utsname.h>
-#include <libcitadel.h>
-#include <assert.h>
-#include "config.h"
-#include "ctdl_module.h"
-
-long config_msgnum = 0;
-HashList *ctdlconfig = NULL;   // new configuration
-
-
-void config_warn_if_port_unset(char *key, int default_port) {
-       int p = CtdlGetConfigInt(key);
-       if ((p < -1) || (p == 0) || (p > UINT16_MAX)) {
-               syslog(LOG_ERR, "config: setting %s is not -1 (disabled) or a valid TCP port - setting to default %d", key, default_port);
-               CtdlSetConfigInt(key, default_port);
-       }
-}
-
-
-void config_warn_if_empty(char *key) {
-       if (IsEmptyStr(CtdlGetConfigStr(key))) {
-               syslog(LOG_ERR, "config: setting %s is empty, but must not - check your config!", key);
-       }
-}
-
-
-void validate_config(void) {
-
-       // these shouldn't be empty
-       config_warn_if_empty("c_fqdn");
-       config_warn_if_empty("c_baseroom");
-       config_warn_if_empty("c_aideroom");
-       config_warn_if_empty("c_twitroom");
-       config_warn_if_empty("c_nodename");
-
-       // Sanity check for port bindings
-       config_warn_if_port_unset("c_smtp_port",        25);
-       config_warn_if_port_unset("c_pop3_port",        110);
-       config_warn_if_port_unset("c_imap_port",        143);
-       config_warn_if_port_unset("c_msa_port",         587);
-       config_warn_if_port_unset("c_port_number",      504);
-       config_warn_if_port_unset("c_smtps_port",       465);
-       config_warn_if_port_unset("c_pop3s_port",       995);
-       config_warn_if_port_unset("c_imaps_port",       993);
-       config_warn_if_port_unset("c_pftcpdict_port",   -1);
-       config_warn_if_port_unset("c_xmpp_c2s_port",    5222);
-       config_warn_if_port_unset("c_xmpp_s2s_port",    5269);
-       config_warn_if_port_unset("c_nntp_port",        119);
-       config_warn_if_port_unset("c_nntps_port",       563);
-
-       if (getpwuid(ctdluid) == NULL) {
-               syslog(LOG_ERR, "config: uid (%d) does not exist ... citserver will run as root", ctdluid);
-       }
-}
-
-
-// The "host key" is a 100-byte random string that is used to do some persistent hashing.
-// It must remain consistent throughout the lifetime of this Citadel installation
-void generate_host_key(void) {
-       syslog(LOG_INFO, "config: generating a host key");
-       int len = 0;
-       char host_key[128];
-
-       while (len < 100) {
-               do {
-                       host_key[len] = random() % 0x7F;
-               } while (!isalnum(host_key[len]));
-               host_key[++len] = 0;
-       }
-       CtdlSetConfigStr("host_key", host_key);
-}
-
-
-// Put some sane default values into our configuration.  Some will be overridden when we run setup.
-void brand_new_installation_set_defaults(void) {
-
-       struct utsname my_utsname;
-       struct hostent *he;
-       char detected_hostname[256];
-
-       // Determine our host name, in case we need to use it as a default
-       uname(&my_utsname);
-
-       // set some sample/default values in place of blanks...
-       extract_token(detected_hostname, my_utsname.nodename, 0, '.', sizeof detected_hostname);
-       CtdlSetConfigStr("c_nodename", detected_hostname);
-
-       if ((he = gethostbyname(my_utsname.nodename)) != NULL) {
-               CtdlSetConfigStr("c_fqdn", he->h_name);
-       }
-       else {
-               CtdlSetConfigStr("c_fqdn", my_utsname.nodename);
-       }
-
-       CtdlSetConfigStr("c_humannode",         "Citadel Server");
-       CtdlSetConfigInt("c_initax",            4);
-       CtdlSetConfigStr("c_moreprompt",        "<more>");
-       CtdlSetConfigStr("c_twitroom",          "Trashcan");
-       CtdlSetConfigStr("c_baseroom",          BASEROOM);
-       CtdlSetConfigStr("c_aideroom",          "Aide");
-       CtdlSetConfigInt("c_sleeping",          900);
-
-       if (CtdlGetConfigInt("c_createax") == 0) {
-               CtdlSetConfigInt("c_createax", 3);
-       }
-
-       // Default port numbers for various services
-       CtdlSetConfigInt("c_port_number",       504);
-       CtdlSetConfigInt("c_smtp_port",         25);
-       CtdlSetConfigInt("c_pop3_port",         110);
-       CtdlSetConfigInt("c_imap_port",         143);
-       CtdlSetConfigInt("c_msa_port",          587);
-       CtdlSetConfigInt("c_smtps_port",        465);
-       CtdlSetConfigInt("c_pop3s_port",        995);
-       CtdlSetConfigInt("c_imaps_port",        993);
-       CtdlSetConfigInt("c_pftcpdict_port",    -1);
-       CtdlSetConfigInt("c_xmpp_c2s_port",     5222);
-       CtdlSetConfigInt("c_xmpp_s2s_port",     5269);
-       CtdlSetConfigInt("c_nntp_port",         119);
-       CtdlSetConfigInt("c_nntps_port",        563);
-
-       // Prevent the "new installation, set defaults" behavior from occurring again
-       CtdlSetConfigLong("c_config_created_or_migrated", (long)time(NULL));
-}
-
-
-// Migrate a supplied legacy configuration to the new in-db format.
-// No individual site should ever have to do this more than once.
-void migrate_legacy_config(struct legacy_config *lconfig) {
-       CtdlSetConfigStr(       "c_nodename"            ,       lconfig->c_nodename             );
-       CtdlSetConfigStr(       "c_fqdn"                ,       lconfig->c_fqdn                 );
-       CtdlSetConfigStr(       "c_humannode"           ,       lconfig->c_humannode            );
-       CtdlSetConfigInt(       "c_creataide"           ,       lconfig->c_creataide            );
-       CtdlSetConfigInt(       "c_sleeping"            ,       lconfig->c_sleeping             );
-       CtdlSetConfigInt(       "c_initax"              ,       lconfig->c_initax               );
-       CtdlSetConfigInt(       "c_regiscall"           ,       lconfig->c_regiscall            );
-       CtdlSetConfigInt(       "c_twitdetect"          ,       lconfig->c_twitdetect           );
-       CtdlSetConfigStr(       "c_twitroom"            ,       lconfig->c_twitroom             );
-       CtdlSetConfigStr(       "c_moreprompt"          ,       lconfig->c_moreprompt           );
-       CtdlSetConfigInt(       "c_restrict"            ,       lconfig->c_restrict             );
-       CtdlSetConfigStr(       "c_site_location"       ,       lconfig->c_site_location        );
-       CtdlSetConfigStr(       "c_sysadm"              ,       lconfig->c_sysadm               );
-       CtdlSetConfigInt(       "c_maxsessions"         ,       lconfig->c_maxsessions          );
-       CtdlSetConfigStr(       "c_ip_addr"             ,       lconfig->c_ip_addr              );
-       CtdlSetConfigInt(       "c_port_number"         ,       lconfig->c_port_number          );
-       CtdlSetConfigInt(       "c_ep_mode"             ,       lconfig->c_ep.expire_mode       );
-       CtdlSetConfigInt(       "c_ep_value"            ,       lconfig->c_ep.expire_value      );
-       CtdlSetConfigInt(       "c_userpurge"           ,       lconfig->c_userpurge            );
-       CtdlSetConfigInt(       "c_roompurge"           ,       lconfig->c_roompurge            );
-       CtdlSetConfigStr(       "c_logpages"            ,       lconfig->c_logpages             );
-       CtdlSetConfigInt(       "c_createax"            ,       lconfig->c_createax             );
-       CtdlSetConfigLong(      "c_maxmsglen"           ,       lconfig->c_maxmsglen            );
-       CtdlSetConfigInt(       "c_min_workers"         ,       lconfig->c_min_workers          );
-       CtdlSetConfigInt(       "c_max_workers"         ,       lconfig->c_max_workers          );
-       CtdlSetConfigInt(       "c_pop3_port"           ,       lconfig->c_pop3_port            );
-       CtdlSetConfigInt(       "c_smtp_port"           ,       lconfig->c_smtp_port            );
-       CtdlSetConfigInt(       "c_rfc822_strict_from"  ,       lconfig->c_rfc822_strict_from   );
-       CtdlSetConfigInt(       "c_aide_zap"            ,       lconfig->c_aide_zap             );
-       CtdlSetConfigInt(       "c_imap_port"           ,       lconfig->c_imap_port            );
-       CtdlSetConfigLong(      "c_net_freq"            ,       lconfig->c_net_freq             );
-       CtdlSetConfigInt(       "c_disable_newu"        ,       lconfig->c_disable_newu         );
-       CtdlSetConfigInt(       "c_enable_fulltext"     ,       lconfig->c_enable_fulltext      );
-       CtdlSetConfigStr(       "c_baseroom"            ,       lconfig->c_baseroom             );
-       CtdlSetConfigStr(       "c_aideroom"            ,       lconfig->c_aideroom             );
-       CtdlSetConfigInt(       "c_purge_hour"          ,       lconfig->c_purge_hour           );
-       CtdlSetConfigInt(       "c_mbxep_mode"          ,       lconfig->c_mbxep.expire_mode    );
-       CtdlSetConfigInt(       "c_mbxep_value"         ,       lconfig->c_mbxep.expire_value   );
-       CtdlSetConfigStr(       "c_ldap_host"           ,       lconfig->c_ldap_host            );
-       CtdlSetConfigInt(       "c_ldap_port"           ,       lconfig->c_ldap_port            );
-       CtdlSetConfigStr(       "c_ldap_base_dn"        ,       lconfig->c_ldap_base_dn         );
-       CtdlSetConfigStr(       "c_ldap_bind_dn"        ,       lconfig->c_ldap_bind_dn         );
-       CtdlSetConfigStr(       "c_ldap_bind_pw"        ,       lconfig->c_ldap_bind_pw         );
-       CtdlSetConfigInt(       "c_msa_port"            ,       lconfig->c_msa_port             );
-       CtdlSetConfigInt(       "c_imaps_port"          ,       lconfig->c_imaps_port           );
-       CtdlSetConfigInt(       "c_pop3s_port"          ,       lconfig->c_pop3s_port           );
-       CtdlSetConfigInt(       "c_smtps_port"          ,       lconfig->c_smtps_port           );
-       CtdlSetConfigInt(       "c_auto_cull"           ,       lconfig->c_auto_cull            );
-       CtdlSetConfigInt(       "c_allow_spoofing"      ,       lconfig->c_allow_spoofing       );
-       CtdlSetConfigInt(       "c_journal_email"       ,       lconfig->c_journal_email        );
-       CtdlSetConfigInt(       "c_journal_pubmsgs"     ,       lconfig->c_journal_pubmsgs      );
-       CtdlSetConfigStr(       "c_journal_dest"        ,       lconfig->c_journal_dest         );
-       CtdlSetConfigStr(       "c_default_cal_zone"    ,       lconfig->c_default_cal_zone     );
-       CtdlSetConfigInt(       "c_pftcpdict_port"      ,       lconfig->c_pftcpdict_port       );
-       CtdlSetConfigInt(       "c_auth_mode"           ,       lconfig->c_auth_mode            );
-       CtdlSetConfigInt(       "c_rbl_at_greeting"     ,       lconfig->c_rbl_at_greeting      );
-       CtdlSetConfigStr(       "c_pager_program"       ,       lconfig->c_pager_program        );
-       CtdlSetConfigInt(       "c_imap_keep_from"      ,       lconfig->c_imap_keep_from       );
-       CtdlSetConfigInt(       "c_xmpp_c2s_port"       ,       lconfig->c_xmpp_c2s_port        );
-       CtdlSetConfigInt(       "c_xmpp_s2s_port"       ,       lconfig->c_xmpp_s2s_port        );
-       CtdlSetConfigLong(      "c_pop3_fetch"          ,       lconfig->c_pop3_fetch           );
-       CtdlSetConfigLong(      "c_pop3_fastest"        ,       lconfig->c_pop3_fastest         );
-       CtdlSetConfigInt(       "c_spam_flag_only"      ,       lconfig->c_spam_flag_only       );
-       CtdlSetConfigInt(       "c_guest_logins"        ,       lconfig->c_guest_logins         );
-       CtdlSetConfigInt(       "c_nntp_port"           ,       lconfig->c_nntp_port            );
-       CtdlSetConfigInt(       "c_nntps_port"          ,       lconfig->c_nntps_port           );
-}
-
-
-// Called during the initialization of Citadel server.
-// It verifies the system's integrity and reads citadel.config into memory.
-void initialize_config_system(void) {
-       FILE *cfp;
-       int rv;
-       struct legacy_config lconfig;   // legacy configuration
-       ctdlconfig = NewHash(1, NULL);  // set up the real config system
-
-       // Ensure that we are linked to the correct version of libcitadel
-       if (libcitadel_version_number() < LIBCITADEL_VERSION_NUMBER) {
-               fprintf(stderr, "You are running libcitadel version %d\n", libcitadel_version_number());
-               fprintf(stderr, "citserver was compiled against version %d\n", LIBCITADEL_VERSION_NUMBER);
-               exit(CTDLEXIT_LIBCITADEL);
-       }
-
-       memset(&lconfig, 0, sizeof(struct legacy_config));
-       cfp = fopen(file_citadel_config, "rb");
-       if (cfp != NULL) {
-               if (CtdlGetConfigLong("c_config_created_or_migrated") > 0) {
-                       fprintf(stderr, "Citadel Server found BOTH legacy and new configurations present.\n");
-                       fprintf(stderr, "Exiting to prevent data corruption.\n");
-                       exit(CTDLEXIT_CONFIG);
-               }
-               rv = fread((char *) &lconfig, sizeof(struct legacy_config), 1, cfp);
-               if (rv != 1) {
-                       fprintf(stderr, 
-                               "Warning: Found a legacy config file %s has unexpected size. \n",
-                               file_citadel_config
-                       );
-               }
-
-               migrate_legacy_config(&lconfig);
-
-               fclose(cfp);
-               if (unlink(file_citadel_config) != 0) {
-                       fprintf(stderr, "Unable to remove legacy config file %s after migrating it.\n", file_citadel_config);
-                       fprintf(stderr, "Exiting to prevent data corruption.\n");
-                       exit(CTDLEXIT_CONFIG);
-               }
-
-               // Prevent migration/initialization from happening again.
-               CtdlSetConfigLong("c_config_created_or_migrated", (long)time(NULL));
-
-       }
-
-       // New installation?  Set up configuration
-       if (CtdlGetConfigLong("c_config_created_or_migrated") <= 0) {
-               brand_new_installation_set_defaults();
-       }
-
-        // Default maximum message length is 10 megabytes.  This is site
-       // configurable.  Also check to make sure the limit has not been
-       // set below 8192 bytes.
-        if (CtdlGetConfigLong("c_maxmsglen") <= 0)     CtdlSetConfigLong("c_maxmsglen", 10485760);
-        if (CtdlGetConfigLong("c_maxmsglen") < 8192)   CtdlSetConfigLong("c_maxmsglen", 8192);
-
-       // Default lower and upper limits on number of worker threads
-       if (CtdlGetConfigInt("c_min_workers") < 5)      CtdlSetConfigInt("c_min_workers", 5);   // min
-       if (CtdlGetConfigInt("c_max_workers") == 0)     CtdlSetConfigInt("c_max_workers", 256); // default max
-       if (CtdlGetConfigInt("c_max_workers") < CtdlGetConfigInt("c_min_workers")) {
-               CtdlSetConfigInt("c_max_workers", CtdlGetConfigInt("c_min_workers"));           // max >= min
-       }
-
-       // Networking more than once every five minutes just isn't sane
-       if (CtdlGetConfigLong("c_net_freq") == 0)       CtdlSetConfigLong("c_net_freq", 3600);  // once per hour default
-       if (CtdlGetConfigLong("c_net_freq") < 300)      CtdlSetConfigLong("c_net_freq", 300);   // minimum 5 minutes
-
-       // Same goes for POP3
-       if (CtdlGetConfigLong("c_pop3_fetch") == 0)     CtdlSetConfigLong("c_pop3_fetch", 3600);        // once per hour default
-       if (CtdlGetConfigLong("c_pop3_fetch") < 300)    CtdlSetConfigLong("c_pop3_fetch", 300);         // 5 minutes min
-       if (CtdlGetConfigLong("c_pop3_fastest") == 0)   CtdlSetConfigLong("c_pop3_fastest", 3600);      // once per hour default
-       if (CtdlGetConfigLong("c_pop3_fastest") < 300)  CtdlSetConfigLong("c_pop3_fastest", 300);       // 5 minutes min
-
-       // LDAP sync frequency
-       if (CtdlGetConfigLong("c_ldap_sync_freq") == 0) CtdlSetConfigLong("c_ldap_sync_freq", 300);     // every 5 minutes default
-
-       // "create new user" only works with native authentication mode
-       if (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) {
-               CtdlSetConfigInt("c_disable_newu", 1);
-       }
-
-       // If we do not have a host key, generate one now, and save it.
-       if (IsEmptyStr(CtdlGetConfigStr("host_key"))) {
-               generate_host_key();
-       }
-}
-
-
-// Called when Citadel server is shutting down.
-// Clears out the config hash table.
-void shutdown_config_system(void) {
-       DeleteHash(&ctdlconfig);
-}
-
-
-// Set a system config value.  Simple key/value here.
-void CtdlSetConfigStr(char *key, char *value) {
-       int key_len = strlen(key);
-       int value_len = strlen(value);
-
-       // Save it in memory
-       Put(ctdlconfig, key, key_len, strdup(value), NULL);
-
-       // Also write it to the config database
-
-       int dbv_size = key_len + value_len + 2;
-       char *dbv = malloc(dbv_size);
-       strcpy(dbv, key);
-       strcpy(&dbv[key_len + 1], value);
-       cdb_store(CDB_CONFIG, key, key_len, dbv, dbv_size);
-       free(dbv);
-}
-
-
-// Set a numeric system config value (long integer)
-void CtdlSetConfigLong(char *key, long value) {
-       char longstr[256];
-       sprintf(longstr, "%ld", value);
-       CtdlSetConfigStr(key, longstr);
-}
-
-
-// Set a numeric system config value (integer)
-void CtdlSetConfigInt(char *key, int value) {
-       char intstr[256];
-       sprintf(intstr, "%d", value);
-       CtdlSetConfigStr(key, intstr);
-}
-
-
-// Delete a system config value.
-void CtdlDelConfig(char *key) {
-       int key_len = strlen(key);
-
-       if (IsEmptyStr(key)) return;
-
-       // Delete from the database.
-       cdb_delete(CDB_CONFIG, key, key_len);
-
-       // Delete from the in-memory cache
-       HashPos *Pos = GetNewHashPos(ctdlconfig, 1);
-       if (GetHashPosFromKey(ctdlconfig, key, key_len, Pos)) {
-               DeleteEntryFromHash(ctdlconfig, Pos);
-       }
-       DeleteHashPos(&Pos);
-
-       assert(Pos == NULL);    // no memory leaks allowed
-}
-
-
-// Fetch a system config value.  Caller does *not* own the returned value and may not alter it.
-char *CtdlGetConfigStr(char *key) {
-       char *value = NULL;
-       struct cdbdata *cdb;
-       int key_len = strlen(key);
-
-       if (IsEmptyStr(key)) return(NULL);
-
-       // First look in memory
-       if (GetHash(ctdlconfig, key, key_len, (void *)&value)) {
-               return value;
-       }
-
-       // Then look in the database.
-       cdb = cdb_fetch(CDB_CONFIG, key, key_len);
-       if (cdb == NULL) {      // nope, not there either.
-               return(NULL);
-       }
-
-       // Got it.  Save it in memory for the next fetch.
-       value = strdup(cdb->ptr + key_len + 1);         // The key was stored there too; skip past it
-       cdb_free(cdb);
-       Put(ctdlconfig, key, key_len, value, NULL);
-       return value;
-}
-
-
-// Fetch a system config value - integer
-int CtdlGetConfigInt(char *key) {
-       char *s = CtdlGetConfigStr(key);
-       if (s) return atoi(s);
-       return 0;
-}
-
-
-// Fetch a system config value - long integer
-long CtdlGetConfigLong(char *key) {
-       char *s = CtdlGetConfigStr(key);
-       if (s) return atol(s);
-       return 0;
-}
-
-
-void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
-       config_msgnum = msgnum;
-}
-
-
-// This is for fetching longer configuration sets which are stored in the message base.
-char *CtdlGetSysConfig(char *sysconfname) {
-       char hold_rm[ROOMNAMELEN];
-       long msgnum = -1;
-       char *conf;
-       struct CtdlMessage *msg;
-       char buf[SIZ];
-       
-       strcpy(hold_rm, CC->room.QRname);
-       if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
-               CtdlGetRoom(&CC->room, hold_rm);
-               return NULL;
-       }
-
-       // The new way: hunt for the message number in the config database
-       msgnum = CtdlGetConfigLong(sysconfname);
-
-       // Legacy format: hunt through the local system configuration room for a message with a matching MIME type
-       if (msgnum <= 0) {
-               begin_critical_section(S_CONFIG);
-               config_msgnum = -1;
-               CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL, CtdlGetSysConfigBackend, NULL);
-               msgnum = config_msgnum;
-               end_critical_section(S_CONFIG);
-               if (msgnum > 0) {
-                       CtdlSetConfigLong(sysconfname, msgnum);         // store it the new way so we don't have to do this again
-               }
-       }
-
-       if (msgnum <= 0) {
-               conf = NULL;
-       }
-       else {
-               msg = CtdlFetchMessage(msgnum, 1);
-               if (msg != NULL) {
-                       conf = strdup(msg->cm_fields[eMesageText]);
-                       CM_Free(msg);
-               }
-               else {
-                       conf = NULL;
-               }
-       }
-
-       CtdlGetRoom(&CC->room, hold_rm);
-
-       if (conf != NULL) {             // Strip the MIME headers, leaving only the content
-               do {
-                       extract_token(buf, conf, 0, '\n', sizeof buf);
-                       strcpy(conf, &conf[strlen(buf)+1]);
-               } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
-       }
-
-       return(conf);
-}
-
-
-// This is for storing longer configuration sets which are stored in the message base.
-void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
-       long old_msgnum = -1;
-       long new_msgnum = -1;
-
-       // Search for the previous copy of this config item, so we can delete it
-       old_msgnum = CtdlGetConfigLong(sysconfname);
-
-       // Go ahead and save it, and write the new msgnum to the config database so we can find it again
-       new_msgnum = CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 0);
-       if (new_msgnum > 0) {
-               CtdlSetConfigLong(sysconfname, new_msgnum);
-
-               // Now delete the old copy
-               if (old_msgnum > 0) {
-                       CtdlDeleteMessages(SYSCONFIGROOM, &old_msgnum, 1, "");
-               }
-       }
-}
diff --git a/citadel/config.guess b/citadel/config.guess
deleted file mode 100644 (file)
index f772702..0000000
+++ /dev/null
@@ -1,1701 +0,0 @@
-#! /bin/sh
-# Attempt to guess a canonical system name.
-#   Copyright 1992-2021 Free Software Foundation, Inc.
-
-timestamp='2021-01-01'
-
-# This file is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, see <https://www.gnu.org/licenses/>.
-#
-# As a special exception to the GNU General Public License, if you
-# distribute this file as part of a program that contains a
-# configuration script generated by Autoconf, you may include it under
-# the same distribution terms that you use for the rest of that
-# program.  This Exception is an additional permission under section 7
-# of the GNU General Public License, version 3 ("GPLv3").
-#
-# Originally written by Per Bothner; maintained since 2000 by Ben Elliston.
-#
-# You can get the latest version of this script from:
-# https://git.savannah.gnu.org/cgit/config.git/plain/config.guess
-#
-# Please send patches to <config-patches@gnu.org>.
-
-
-me=$(echo "$0" | sed -e 's,.*/,,')
-
-usage="\
-Usage: $0 [OPTION]
-
-Output the configuration name of the system \`$me' is run on.
-
-Options:
-  -h, --help         print this help, then exit
-  -t, --time-stamp   print date of last modification, then exit
-  -v, --version      print version number, then exit
-
-Report bugs and patches to <config-patches@gnu.org>."
-
-version="\
-GNU config.guess ($timestamp)
-
-Originally written by Per Bothner.
-Copyright 1992-2021 Free Software Foundation, Inc.
-
-This is free software; see the source for copying conditions.  There is NO
-warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
-
-help="
-Try \`$me --help' for more information."
-
-# Parse command line
-while test $# -gt 0 ; do
-  case $1 in
-    --time-stamp | --time* | -t )
-       echo "$timestamp" ; exit ;;
-    --version | -v )
-       echo "$version" ; exit ;;
-    --help | --h* | -h )
-       echo "$usage"; exit ;;
-    -- )     # Stop option processing
-       shift; break ;;
-    - )        # Use stdin as input.
-       break ;;
-    -* )
-       echo "$me: invalid option $1$help" >&2
-       exit 1 ;;
-    * )
-       break ;;
-  esac
-done
-
-if test $# != 0; then
-  echo "$me: too many arguments$help" >&2
-  exit 1
-fi
-
-# CC_FOR_BUILD -- compiler used by this script. Note that the use of a
-# compiler to aid in system detection is discouraged as it requires
-# temporary files to be created and, as you can see below, it is a
-# headache to deal with in a portable fashion.
-
-# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still
-# use `HOST_CC' if defined, but it is deprecated.
-
-# Portable tmp directory creation inspired by the Autoconf team.
-
-tmp=
-# shellcheck disable=SC2172
-trap 'test -z "$tmp" || rm -fr "$tmp"' 0 1 2 13 15
-
-set_cc_for_build() {
-    # prevent multiple calls if $tmp is already set
-    test "$tmp" && return 0
-    : "${TMPDIR=/tmp}"
-    # shellcheck disable=SC2039
-    { tmp=$( (umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null) && test -n "$tmp" && test -d "$tmp" ; } ||
-       { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir "$tmp" 2>/dev/null) ; } ||
-       { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir "$tmp" 2>/dev/null) && echo "Warning: creating insecure temp directory" >&2 ; } ||
-       { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; }
-    dummy=$tmp/dummy
-    case ${CC_FOR_BUILD-},${HOST_CC-},${CC-} in
-       ,,)    echo "int x;" > "$dummy.c"
-              for driver in cc gcc c89 c99 ; do
-                  if ($driver -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then
-                      CC_FOR_BUILD="$driver"
-                      break
-                  fi
-              done
-              if test x"$CC_FOR_BUILD" = x ; then
-                  CC_FOR_BUILD=no_compiler_found
-              fi
-              ;;
-       ,,*)   CC_FOR_BUILD=$CC ;;
-       ,*,*)  CC_FOR_BUILD=$HOST_CC ;;
-    esac
-}
-
-# This is needed to find uname on a Pyramid OSx when run in the BSD universe.
-# (ghazi@noc.rutgers.edu 1994-08-24)
-if test -f /.attbin/uname ; then
-       PATH=$PATH:/.attbin ; export PATH
-fi
-
-UNAME_MACHINE=$( (uname -m) 2>/dev/null) || UNAME_MACHINE=unknown
-UNAME_RELEASE=$( (uname -r) 2>/dev/null) || UNAME_RELEASE=unknown
-UNAME_SYSTEM=$( (uname -s) 2>/dev/null) || UNAME_SYSTEM=unknown
-UNAME_VERSION=$( (uname -v) 2>/dev/null) || UNAME_VERSION=unknown
-
-case "$UNAME_SYSTEM" in
-Linux|GNU|GNU/*)
-       LIBC=unknown
-
-       set_cc_for_build
-       cat <<-EOF > "$dummy.c"
-       #include <features.h>
-       #if defined(__UCLIBC__)
-       LIBC=uclibc
-       #elif defined(__dietlibc__)
-       LIBC=dietlibc
-       #elif defined(__GLIBC__)
-       LIBC=gnu
-       #else
-       #include <stdarg.h>
-       /* First heuristic to detect musl libc.  */
-       #ifdef __DEFINED_va_list
-       LIBC=musl
-       #endif
-       #endif
-       EOF
-       eval "$($CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g')"
-
-       # Second heuristic to detect musl libc.
-       if [ "$LIBC" = unknown ] &&
-          command -v ldd >/dev/null &&
-          ldd --version 2>&1 | grep -q ^musl; then
-               LIBC=musl
-       fi
-
-       # If the system lacks a compiler, then just pick glibc.
-       # We could probably try harder.
-       if [ "$LIBC" = unknown ]; then
-               LIBC=gnu
-       fi
-       ;;
-esac
-
-# Note: order is significant - the case branches are not exclusive.
-
-case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in
-    *:NetBSD:*:*)
-       # NetBSD (nbsd) targets should (where applicable) match one or
-       # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*,
-       # *-*-netbsdecoff* and *-*-netbsd*.  For targets that recently
-       # switched to ELF, *-*-netbsd* would select the old
-       # object file format.  This provides both forward
-       # compatibility and a consistent mechanism for selecting the
-       # object file format.
-       #
-       # Note: NetBSD doesn't particularly care about the vendor
-       # portion of the name.  We always set it to "unknown".
-       sysctl="sysctl -n hw.machine_arch"
-       UNAME_MACHINE_ARCH=$( (uname -p 2>/dev/null || \
-           "/sbin/$sysctl" 2>/dev/null || \
-           "/usr/sbin/$sysctl" 2>/dev/null || \
-           echo unknown))
-       case "$UNAME_MACHINE_ARCH" in
-           aarch64eb) machine=aarch64_be-unknown ;;
-           armeb) machine=armeb-unknown ;;
-           arm*) machine=arm-unknown ;;
-           sh3el) machine=shl-unknown ;;
-           sh3eb) machine=sh-unknown ;;
-           sh5el) machine=sh5le-unknown ;;
-           earmv*)
-               arch=$(echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,')
-               endian=$(echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p')
-               machine="${arch}${endian}"-unknown
-               ;;
-           *) machine="$UNAME_MACHINE_ARCH"-unknown ;;
-       esac
-       # The Operating System including object format, if it has switched
-       # to ELF recently (or will in the future) and ABI.
-       case "$UNAME_MACHINE_ARCH" in
-           earm*)
-               os=netbsdelf
-               ;;
-           arm*|i386|m68k|ns32k|sh3*|sparc|vax)
-               set_cc_for_build
-               if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \
-                       | grep -q __ELF__
-               then
-                   # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout).
-                   # Return netbsd for either.  FIX?
-                   os=netbsd
-               else
-                   os=netbsdelf
-               fi
-               ;;
-           *)
-               os=netbsd
-               ;;
-       esac
-       # Determine ABI tags.
-       case "$UNAME_MACHINE_ARCH" in
-           earm*)
-               expr='s/^earmv[0-9]/-eabi/;s/eb$//'
-               abi=$(echo "$UNAME_MACHINE_ARCH" | sed -e "$expr")
-               ;;
-       esac
-       # The OS release
-       # Debian GNU/NetBSD machines have a different userland, and
-       # thus, need a distinct triplet. However, they do not need
-       # kernel version information, so it can be replaced with a
-       # suitable tag, in the style of linux-gnu.
-       case "$UNAME_VERSION" in
-           Debian*)
-               release='-gnu'
-               ;;
-           *)
-               release=$(echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2)
-               ;;
-       esac
-       # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM:
-       # contains redundant information, the shorter form:
-       # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used.
-       echo "$machine-${os}${release}${abi-}"
-       exit ;;
-    *:Bitrig:*:*)
-       UNAME_MACHINE_ARCH=$(arch | sed 's/Bitrig.//')
-       echo "$UNAME_MACHINE_ARCH"-unknown-bitrig"$UNAME_RELEASE"
-       exit ;;
-    *:OpenBSD:*:*)
-       UNAME_MACHINE_ARCH=$(arch | sed 's/OpenBSD.//')
-       echo "$UNAME_MACHINE_ARCH"-unknown-openbsd"$UNAME_RELEASE"
-       exit ;;
-    *:LibertyBSD:*:*)
-       UNAME_MACHINE_ARCH=$(arch | sed 's/^.*BSD\.//')
-       echo "$UNAME_MACHINE_ARCH"-unknown-libertybsd"$UNAME_RELEASE"
-       exit ;;
-    *:MidnightBSD:*:*)
-       echo "$UNAME_MACHINE"-unknown-midnightbsd"$UNAME_RELEASE"
-       exit ;;
-    *:ekkoBSD:*:*)
-       echo "$UNAME_MACHINE"-unknown-ekkobsd"$UNAME_RELEASE"
-       exit ;;
-    *:SolidBSD:*:*)
-       echo "$UNAME_MACHINE"-unknown-solidbsd"$UNAME_RELEASE"
-       exit ;;
-    *:OS108:*:*)
-       echo "$UNAME_MACHINE"-unknown-os108_"$UNAME_RELEASE"
-       exit ;;
-    macppc:MirBSD:*:*)
-       echo powerpc-unknown-mirbsd"$UNAME_RELEASE"
-       exit ;;
-    *:MirBSD:*:*)
-       echo "$UNAME_MACHINE"-unknown-mirbsd"$UNAME_RELEASE"
-       exit ;;
-    *:Sortix:*:*)
-       echo "$UNAME_MACHINE"-unknown-sortix
-       exit ;;
-    *:Twizzler:*:*)
-       echo "$UNAME_MACHINE"-unknown-twizzler
-       exit ;;
-    *:Redox:*:*)
-       echo "$UNAME_MACHINE"-unknown-redox
-       exit ;;
-    mips:OSF1:*.*)
-       echo mips-dec-osf1
-       exit ;;
-    alpha:OSF1:*:*)
-       case $UNAME_RELEASE in
-       *4.0)
-               UNAME_RELEASE=$(/usr/sbin/sizer -v | awk '{print $3}')
-               ;;
-       *5.*)
-               UNAME_RELEASE=$(/usr/sbin/sizer -v | awk '{print $4}')
-               ;;
-       esac
-       # According to Compaq, /usr/sbin/psrinfo has been available on
-       # OSF/1 and Tru64 systems produced since 1995.  I hope that
-       # covers most systems running today.  This code pipes the CPU
-       # types through head -n 1, so we only detect the type of CPU 0.
-       ALPHA_CPU_TYPE=$(/usr/sbin/psrinfo -v | sed -n -e 's/^  The alpha \(.*\) processor.*$/\1/p' | head -n 1)
-       case "$ALPHA_CPU_TYPE" in
-           "EV4 (21064)")
-               UNAME_MACHINE=alpha ;;
-           "EV4.5 (21064)")
-               UNAME_MACHINE=alpha ;;
-           "LCA4 (21066/21068)")
-               UNAME_MACHINE=alpha ;;
-           "EV5 (21164)")
-               UNAME_MACHINE=alphaev5 ;;
-           "EV5.6 (21164A)")
-               UNAME_MACHINE=alphaev56 ;;
-           "EV5.6 (21164PC)")
-               UNAME_MACHINE=alphapca56 ;;
-           "EV5.7 (21164PC)")
-               UNAME_MACHINE=alphapca57 ;;
-           "EV6 (21264)")
-               UNAME_MACHINE=alphaev6 ;;
-           "EV6.7 (21264A)")
-               UNAME_MACHINE=alphaev67 ;;
-           "EV6.8CB (21264C)")
-               UNAME_MACHINE=alphaev68 ;;
-           "EV6.8AL (21264B)")
-               UNAME_MACHINE=alphaev68 ;;
-           "EV6.8CX (21264D)")
-               UNAME_MACHINE=alphaev68 ;;
-           "EV6.9A (21264/EV69A)")
-               UNAME_MACHINE=alphaev69 ;;
-           "EV7 (21364)")
-               UNAME_MACHINE=alphaev7 ;;
-           "EV7.9 (21364A)")
-               UNAME_MACHINE=alphaev79 ;;
-       esac
-       # A Pn.n version is a patched version.
-       # A Vn.n version is a released version.
-       # A Tn.n version is a released field test version.
-       # A Xn.n version is an unreleased experimental baselevel.
-       # 1.2 uses "1.2" for uname -r.
-       echo "$UNAME_MACHINE"-dec-osf"$(echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz)"
-       # Reset EXIT trap before exiting to avoid spurious non-zero exit code.
-       exitcode=$?
-       trap '' 0
-       exit $exitcode ;;
-    Amiga*:UNIX_System_V:4.0:*)
-       echo m68k-unknown-sysv4
-       exit ;;
-    *:[Aa]miga[Oo][Ss]:*:*)
-       echo "$UNAME_MACHINE"-unknown-amigaos
-       exit ;;
-    *:[Mm]orph[Oo][Ss]:*:*)
-       echo "$UNAME_MACHINE"-unknown-morphos
-       exit ;;
-    *:OS/390:*:*)
-       echo i370-ibm-openedition
-       exit ;;
-    *:z/VM:*:*)
-       echo s390-ibm-zvmoe
-       exit ;;
-    *:OS400:*:*)
-       echo powerpc-ibm-os400
-       exit ;;
-    arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*)
-       echo arm-acorn-riscix"$UNAME_RELEASE"
-       exit ;;
-    arm*:riscos:*:*|arm*:RISCOS:*:*)
-       echo arm-unknown-riscos
-       exit ;;
-    SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*)
-       echo hppa1.1-hitachi-hiuxmpp
-       exit ;;
-    Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*)
-       # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE.
-       if test "$( (/bin/universe) 2>/dev/null)" = att ; then
-               echo pyramid-pyramid-sysv3
-       else
-               echo pyramid-pyramid-bsd
-       fi
-       exit ;;
-    NILE*:*:*:dcosx)
-       echo pyramid-pyramid-svr4
-       exit ;;
-    DRS?6000:unix:4.0:6*)
-       echo sparc-icl-nx6
-       exit ;;
-    DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*)
-       case $(/usr/bin/uname -p) in
-           sparc) echo sparc-icl-nx7; exit ;;
-       esac ;;
-    s390x:SunOS:*:*)
-       echo "$UNAME_MACHINE"-ibm-solaris2"$(echo "$UNAME_RELEASE" | sed -e 's/[^.]*//')"
-       exit ;;
-    sun4H:SunOS:5.*:*)
-       echo sparc-hal-solaris2"$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*//')"
-       exit ;;
-    sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*)
-       echo sparc-sun-solaris2"$(echo "$UNAME_RELEASE" | sed -e 's/[^.]*//')"
-       exit ;;
-    i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*)
-       echo i386-pc-auroraux"$UNAME_RELEASE"
-       exit ;;
-    i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*)
-       set_cc_for_build
-       SUN_ARCH=i386
-       # If there is a compiler, see if it is configured for 64-bit objects.
-       # Note that the Sun cc does not turn __LP64__ into 1 like gcc does.
-       # This test works for both compilers.
-       if test "$CC_FOR_BUILD" != no_compiler_found; then
-           if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \
-               (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
-               grep IS_64BIT_ARCH >/dev/null
-           then
-               SUN_ARCH=x86_64
-           fi
-       fi
-       echo "$SUN_ARCH"-pc-solaris2"$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*//')"
-       exit ;;
-    sun4*:SunOS:6*:*)
-       # According to config.sub, this is the proper way to canonicalize
-       # SunOS6.  Hard to guess exactly what SunOS6 will be like, but
-       # it's likely to be more like Solaris than SunOS4.
-       echo sparc-sun-solaris3"$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*//')"
-       exit ;;
-    sun4*:SunOS:*:*)
-       case "$(/usr/bin/arch -k)" in
-           Series*|S4*)
-               UNAME_RELEASE=$(uname -v)
-               ;;
-       esac
-       # Japanese Language versions have a version number like `4.1.3-JL'.
-       echo sparc-sun-sunos"$(echo "$UNAME_RELEASE"|sed -e 's/-/_/')"
-       exit ;;
-    sun3*:SunOS:*:*)
-       echo m68k-sun-sunos"$UNAME_RELEASE"
-       exit ;;
-    sun*:*:4.2BSD:*)
-       UNAME_RELEASE=$( (sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null)
-       test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3
-       case "$(/bin/arch)" in
-           sun3)
-               echo m68k-sun-sunos"$UNAME_RELEASE"
-               ;;
-           sun4)
-               echo sparc-sun-sunos"$UNAME_RELEASE"
-               ;;
-       esac
-       exit ;;
-    aushp:SunOS:*:*)
-       echo sparc-auspex-sunos"$UNAME_RELEASE"
-       exit ;;
-    # The situation for MiNT is a little confusing.  The machine name
-    # can be virtually everything (everything which is not
-    # "atarist" or "atariste" at least should have a processor
-    # > m68000).  The system name ranges from "MiNT" over "FreeMiNT"
-    # to the lowercase version "mint" (or "freemint").  Finally
-    # the system name "TOS" denotes a system which is actually not
-    # MiNT.  But MiNT is downward compatible to TOS, so this should
-    # be no problem.
-    atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*)
-       echo m68k-atari-mint"$UNAME_RELEASE"
-       exit ;;
-    atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*)
-       echo m68k-atari-mint"$UNAME_RELEASE"
-       exit ;;
-    *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*)
-       echo m68k-atari-mint"$UNAME_RELEASE"
-       exit ;;
-    milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*)
-       echo m68k-milan-mint"$UNAME_RELEASE"
-       exit ;;
-    hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*)
-       echo m68k-hades-mint"$UNAME_RELEASE"
-       exit ;;
-    *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*)
-       echo m68k-unknown-mint"$UNAME_RELEASE"
-       exit ;;
-    m68k:machten:*:*)
-       echo m68k-apple-machten"$UNAME_RELEASE"
-       exit ;;
-    powerpc:machten:*:*)
-       echo powerpc-apple-machten"$UNAME_RELEASE"
-       exit ;;
-    RISC*:Mach:*:*)
-       echo mips-dec-mach_bsd4.3
-       exit ;;
-    RISC*:ULTRIX:*:*)
-       echo mips-dec-ultrix"$UNAME_RELEASE"
-       exit ;;
-    VAX*:ULTRIX*:*:*)
-       echo vax-dec-ultrix"$UNAME_RELEASE"
-       exit ;;
-    2020:CLIX:*:* | 2430:CLIX:*:*)
-       echo clipper-intergraph-clix"$UNAME_RELEASE"
-       exit ;;
-    mips:*:*:UMIPS | mips:*:*:RISCos)
-       set_cc_for_build
-       sed 's/^        //' << EOF > "$dummy.c"
-#ifdef __cplusplus
-#include <stdio.h>  /* for printf() prototype */
-       int main (int argc, char *argv[]) {
-#else
-       int main (argc, argv) int argc; char *argv[]; {
-#endif
-       #if defined (host_mips) && defined (MIPSEB)
-       #if defined (SYSTYPE_SYSV)
-         printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0);
-       #endif
-       #if defined (SYSTYPE_SVR4)
-         printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0);
-       #endif
-       #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
-         printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0);
-       #endif
-       #endif
-         exit (-1);
-       }
-EOF
-       $CC_FOR_BUILD -o "$dummy" "$dummy.c" &&
-         dummyarg=$(echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p') &&
-         SYSTEM_NAME=$("$dummy" "$dummyarg") &&
-           { echo "$SYSTEM_NAME"; exit; }
-       echo mips-mips-riscos"$UNAME_RELEASE"
-       exit ;;
-    Motorola:PowerMAX_OS:*:*)
-       echo powerpc-motorola-powermax
-       exit ;;
-    Motorola:*:4.3:PL8-*)
-       echo powerpc-harris-powermax
-       exit ;;
-    Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*)
-       echo powerpc-harris-powermax
-       exit ;;
-    Night_Hawk:Power_UNIX:*:*)
-       echo powerpc-harris-powerunix
-       exit ;;
-    m88k:CX/UX:7*:*)
-       echo m88k-harris-cxux7
-       exit ;;
-    m88k:*:4*:R4*)
-       echo m88k-motorola-sysv4
-       exit ;;
-    m88k:*:3*:R3*)
-       echo m88k-motorola-sysv3
-       exit ;;
-    AViiON:dgux:*:*)
-       # DG/UX returns AViiON for all architectures
-       UNAME_PROCESSOR=$(/usr/bin/uname -p)
-       if test "$UNAME_PROCESSOR" = mc88100 || test "$UNAME_PROCESSOR" = mc88110
-       then
-           if test "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx || \
-              test "$TARGET_BINARY_INTERFACE"x = x
-           then
-               echo m88k-dg-dgux"$UNAME_RELEASE"
-           else
-               echo m88k-dg-dguxbcs"$UNAME_RELEASE"
-           fi
-       else
-           echo i586-dg-dgux"$UNAME_RELEASE"
-       fi
-       exit ;;
-    M88*:DolphinOS:*:*)        # DolphinOS (SVR3)
-       echo m88k-dolphin-sysv3
-       exit ;;
-    M88*:*:R3*:*)
-       # Delta 88k system running SVR3
-       echo m88k-motorola-sysv3
-       exit ;;
-    XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3)
-       echo m88k-tektronix-sysv3
-       exit ;;
-    Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD)
-       echo m68k-tektronix-bsd
-       exit ;;
-    *:IRIX*:*:*)
-       echo mips-sgi-irix"$(echo "$UNAME_RELEASE"|sed -e 's/-/_/g')"
-       exit ;;
-    ????????:AIX?:[12].1:2)   # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX.
-       echo romp-ibm-aix     # uname -m gives an 8 hex-code CPU id
-       exit ;;               # Note that: echo "'$(uname -s)'" gives 'AIX '
-    i*86:AIX:*:*)
-       echo i386-ibm-aix
-       exit ;;
-    ia64:AIX:*:*)
-       if test -x /usr/bin/oslevel ; then
-               IBM_REV=$(/usr/bin/oslevel)
-       else
-               IBM_REV="$UNAME_VERSION.$UNAME_RELEASE"
-       fi
-       echo "$UNAME_MACHINE"-ibm-aix"$IBM_REV"
-       exit ;;
-    *:AIX:2:3)
-       if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then
-               set_cc_for_build
-               sed 's/^                //' << EOF > "$dummy.c"
-               #include <sys/systemcfg.h>
-
-               main()
-                       {
-                       if (!__power_pc())
-                               exit(1);
-                       puts("powerpc-ibm-aix3.2.5");
-                       exit(0);
-                       }
-EOF
-               if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=$("$dummy")
-               then
-                       echo "$SYSTEM_NAME"
-               else
-                       echo rs6000-ibm-aix3.2.5
-               fi
-       elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then
-               echo rs6000-ibm-aix3.2.4
-       else
-               echo rs6000-ibm-aix3.2
-       fi
-       exit ;;
-    *:AIX:*:[4567])
-       IBM_CPU_ID=$(/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }')
-       if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then
-               IBM_ARCH=rs6000
-       else
-               IBM_ARCH=powerpc
-       fi
-       if test -x /usr/bin/lslpp ; then
-               IBM_REV=$(/usr/bin/lslpp -Lqc bos.rte.libc |
-                          awk -F: '{ print $3 }' | sed s/[0-9]*$/0/)
-       else
-               IBM_REV="$UNAME_VERSION.$UNAME_RELEASE"
-       fi
-       echo "$IBM_ARCH"-ibm-aix"$IBM_REV"
-       exit ;;
-    *:AIX:*:*)
-       echo rs6000-ibm-aix
-       exit ;;
-    ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*)
-       echo romp-ibm-bsd4.4
-       exit ;;
-    ibmrt:*BSD:*|romp-ibm:BSD:*)            # covers RT/PC BSD and
-       echo romp-ibm-bsd"$UNAME_RELEASE"   # 4.3 with uname added to
-       exit ;;                             # report: romp-ibm BSD 4.3
-    *:BOSX:*:*)
-       echo rs6000-bull-bosx
-       exit ;;
-    DPX/2?00:B.O.S.:*:*)
-       echo m68k-bull-sysv3
-       exit ;;
-    9000/[34]??:4.3bsd:1.*:*)
-       echo m68k-hp-bsd
-       exit ;;
-    hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*)
-       echo m68k-hp-bsd4.4
-       exit ;;
-    9000/[34678]??:HP-UX:*:*)
-       HPUX_REV=$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//')
-       case "$UNAME_MACHINE" in
-           9000/31?)            HP_ARCH=m68000 ;;
-           9000/[34]??)         HP_ARCH=m68k ;;
-           9000/[678][0-9][0-9])
-               if test -x /usr/bin/getconf; then
-                   sc_cpu_version=$(/usr/bin/getconf SC_CPU_VERSION 2>/dev/null)
-                   sc_kernel_bits=$(/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null)
-                   case "$sc_cpu_version" in
-                     523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0
-                     528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1
-                     532)                      # CPU_PA_RISC2_0
-                       case "$sc_kernel_bits" in
-                         32) HP_ARCH=hppa2.0n ;;
-                         64) HP_ARCH=hppa2.0w ;;
-                         '') HP_ARCH=hppa2.0 ;;   # HP-UX 10.20
-                       esac ;;
-                   esac
-               fi
-               if test "$HP_ARCH" = ""; then
-                   set_cc_for_build
-                   sed 's/^            //' << EOF > "$dummy.c"
-
-               #define _HPUX_SOURCE
-               #include <stdlib.h>
-               #include <unistd.h>
-
-               int main ()
-               {
-               #if defined(_SC_KERNEL_BITS)
-                   long bits = sysconf(_SC_KERNEL_BITS);
-               #endif
-                   long cpu  = sysconf (_SC_CPU_VERSION);
-
-                   switch (cpu)
-                       {
-                       case CPU_PA_RISC1_0: puts ("hppa1.0"); break;
-                       case CPU_PA_RISC1_1: puts ("hppa1.1"); break;
-                       case CPU_PA_RISC2_0:
-               #if defined(_SC_KERNEL_BITS)
-                           switch (bits)
-                               {
-                               case 64: puts ("hppa2.0w"); break;
-                               case 32: puts ("hppa2.0n"); break;
-                               default: puts ("hppa2.0"); break;
-                               } break;
-               #else  /* !defined(_SC_KERNEL_BITS) */
-                           puts ("hppa2.0"); break;
-               #endif
-                       default: puts ("hppa1.0"); break;
-                       }
-                   exit (0);
-               }
-EOF
-                   (CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=$("$dummy")
-                   test -z "$HP_ARCH" && HP_ARCH=hppa
-               fi ;;
-       esac
-       if test "$HP_ARCH" = hppa2.0w
-       then
-           set_cc_for_build
-
-           # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating
-           # 32-bit code.  hppa64-hp-hpux* has the same kernel and a compiler
-           # generating 64-bit code.  GNU and HP use different nomenclature:
-           #
-           # $ CC_FOR_BUILD=cc ./config.guess
-           # => hppa2.0w-hp-hpux11.23
-           # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess
-           # => hppa64-hp-hpux11.23
-
-           if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) |
-               grep -q __LP64__
-           then
-               HP_ARCH=hppa2.0w
-           else
-               HP_ARCH=hppa64
-           fi
-       fi
-       echo "$HP_ARCH"-hp-hpux"$HPUX_REV"
-       exit ;;
-    ia64:HP-UX:*:*)
-       HPUX_REV=$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//')
-       echo ia64-hp-hpux"$HPUX_REV"
-       exit ;;
-    3050*:HI-UX:*:*)
-       set_cc_for_build
-       sed 's/^        //' << EOF > "$dummy.c"
-       #include <unistd.h>
-       int
-       main ()
-       {
-         long cpu = sysconf (_SC_CPU_VERSION);
-         /* The order matters, because CPU_IS_HP_MC68K erroneously returns
-            true for CPU_PA_RISC1_0.  CPU_IS_PA_RISC returns correct
-            results, however.  */
-         if (CPU_IS_PA_RISC (cpu))
-           {
-             switch (cpu)
-               {
-                 case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break;
-                 case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break;
-                 case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break;
-                 default: puts ("hppa-hitachi-hiuxwe2"); break;
-               }
-           }
-         else if (CPU_IS_HP_MC68K (cpu))
-           puts ("m68k-hitachi-hiuxwe2");
-         else puts ("unknown-hitachi-hiuxwe2");
-         exit (0);
-       }
-EOF
-       $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=$("$dummy") &&
-               { echo "$SYSTEM_NAME"; exit; }
-       echo unknown-hitachi-hiuxwe2
-       exit ;;
-    9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*)
-       echo hppa1.1-hp-bsd
-       exit ;;
-    9000/8??:4.3bsd:*:*)
-       echo hppa1.0-hp-bsd
-       exit ;;
-    *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*)
-       echo hppa1.0-hp-mpeix
-       exit ;;
-    hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*)
-       echo hppa1.1-hp-osf
-       exit ;;
-    hp8??:OSF1:*:*)
-       echo hppa1.0-hp-osf
-       exit ;;
-    i*86:OSF1:*:*)
-       if test -x /usr/sbin/sysversion ; then
-           echo "$UNAME_MACHINE"-unknown-osf1mk
-       else
-           echo "$UNAME_MACHINE"-unknown-osf1
-       fi
-       exit ;;
-    parisc*:Lites*:*:*)
-       echo hppa1.1-hp-lites
-       exit ;;
-    C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*)
-       echo c1-convex-bsd
-       exit ;;
-    C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*)
-       if getsysinfo -f scalar_acc
-       then echo c32-convex-bsd
-       else echo c2-convex-bsd
-       fi
-       exit ;;
-    C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*)
-       echo c34-convex-bsd
-       exit ;;
-    C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*)
-       echo c38-convex-bsd
-       exit ;;
-    C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*)
-       echo c4-convex-bsd
-       exit ;;
-    CRAY*Y-MP:*:*:*)
-       echo ymp-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'
-       exit ;;
-    CRAY*[A-Z]90:*:*:*)
-       echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \
-       | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \
-             -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \
-             -e 's/\.[^.]*$/.X/'
-       exit ;;
-    CRAY*TS:*:*:*)
-       echo t90-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'
-       exit ;;
-    CRAY*T3E:*:*:*)
-       echo alphaev5-cray-unicosmk"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'
-       exit ;;
-    CRAY*SV1:*:*:*)
-       echo sv1-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'
-       exit ;;
-    *:UNICOS/mp:*:*)
-       echo craynv-cray-unicosmp"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'
-       exit ;;
-    F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*)
-       FUJITSU_PROC=$(uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz)
-       FUJITSU_SYS=$(uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///')
-       FUJITSU_REL=$(echo "$UNAME_RELEASE" | sed -e 's/ /_/')
-       echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
-       exit ;;
-    5000:UNIX_System_V:4.*:*)
-       FUJITSU_SYS=$(uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///')
-       FUJITSU_REL=$(echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/')
-       echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
-       exit ;;
-    i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*)
-       echo "$UNAME_MACHINE"-pc-bsdi"$UNAME_RELEASE"
-       exit ;;
-    sparc*:BSD/OS:*:*)
-       echo sparc-unknown-bsdi"$UNAME_RELEASE"
-       exit ;;
-    *:BSD/OS:*:*)
-       echo "$UNAME_MACHINE"-unknown-bsdi"$UNAME_RELEASE"
-       exit ;;
-    arm:FreeBSD:*:*)
-       UNAME_PROCESSOR=$(uname -p)
-       set_cc_for_build
-       if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \
-           | grep -q __ARM_PCS_VFP
-       then
-           echo "${UNAME_PROCESSOR}"-unknown-freebsd"$(echo ${UNAME_RELEASE}|sed -e 's/[-(].*//')"-gnueabi
-       else
-           echo "${UNAME_PROCESSOR}"-unknown-freebsd"$(echo ${UNAME_RELEASE}|sed -e 's/[-(].*//')"-gnueabihf
-       fi
-       exit ;;
-    *:FreeBSD:*:*)
-       UNAME_PROCESSOR=$(/usr/bin/uname -p)
-       case "$UNAME_PROCESSOR" in
-           amd64)
-               UNAME_PROCESSOR=x86_64 ;;
-           i386)
-               UNAME_PROCESSOR=i586 ;;
-       esac
-       echo "$UNAME_PROCESSOR"-unknown-freebsd"$(echo "$UNAME_RELEASE"|sed -e 's/[-(].*//')"
-       exit ;;
-    i*:CYGWIN*:*)
-       echo "$UNAME_MACHINE"-pc-cygwin
-       exit ;;
-    *:MINGW64*:*)
-       echo "$UNAME_MACHINE"-pc-mingw64
-       exit ;;
-    *:MINGW*:*)
-       echo "$UNAME_MACHINE"-pc-mingw32
-       exit ;;
-    *:MSYS*:*)
-       echo "$UNAME_MACHINE"-pc-msys
-       exit ;;
-    i*:PW*:*)
-       echo "$UNAME_MACHINE"-pc-pw32
-       exit ;;
-    *:Interix*:*)
-       case "$UNAME_MACHINE" in
-           x86)
-               echo i586-pc-interix"$UNAME_RELEASE"
-               exit ;;
-           authenticamd | genuineintel | EM64T)
-               echo x86_64-unknown-interix"$UNAME_RELEASE"
-               exit ;;
-           IA64)
-               echo ia64-unknown-interix"$UNAME_RELEASE"
-               exit ;;
-       esac ;;
-    i*:UWIN*:*)
-       echo "$UNAME_MACHINE"-pc-uwin
-       exit ;;
-    amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*)
-       echo x86_64-pc-cygwin
-       exit ;;
-    prep*:SunOS:5.*:*)
-       echo powerpcle-unknown-solaris2"$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*//')"
-       exit ;;
-    *:GNU:*:*)
-       # the GNU system
-       echo "$(echo "$UNAME_MACHINE"|sed -e 's,[-/].*$,,')-unknown-$LIBC$(echo "$UNAME_RELEASE"|sed -e 's,/.*$,,')"
-       exit ;;
-    *:GNU/*:*:*)
-       # other systems with GNU libc and userland
-       echo "$UNAME_MACHINE-unknown-$(echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]")$(echo "$UNAME_RELEASE"|sed -e 's/[-(].*//')-$LIBC"
-       exit ;;
-    *:Minix:*:*)
-       echo "$UNAME_MACHINE"-unknown-minix
-       exit ;;
-    aarch64:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    aarch64_be:Linux:*:*)
-       UNAME_MACHINE=aarch64_be
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    alpha:Linux:*:*)
-       case $(sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' /proc/cpuinfo 2>/dev/null) in
-         EV5)   UNAME_MACHINE=alphaev5 ;;
-         EV56)  UNAME_MACHINE=alphaev56 ;;
-         PCA56) UNAME_MACHINE=alphapca56 ;;
-         PCA57) UNAME_MACHINE=alphapca56 ;;
-         EV6)   UNAME_MACHINE=alphaev6 ;;
-         EV67)  UNAME_MACHINE=alphaev67 ;;
-         EV68*) UNAME_MACHINE=alphaev68 ;;
-       esac
-       objdump --private-headers /bin/sh | grep -q ld.so.1
-       if test "$?" = 0 ; then LIBC=gnulibc1 ; fi
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    arc:Linux:*:* | arceb:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    arm*:Linux:*:*)
-       set_cc_for_build
-       if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \
-           | grep -q __ARM_EABI__
-       then
-           echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       else
-           if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \
-               | grep -q __ARM_PCS_VFP
-           then
-               echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabi
-           else
-               echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabihf
-           fi
-       fi
-       exit ;;
-    avr32*:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    cris:Linux:*:*)
-       echo "$UNAME_MACHINE"-axis-linux-"$LIBC"
-       exit ;;
-    crisv32:Linux:*:*)
-       echo "$UNAME_MACHINE"-axis-linux-"$LIBC"
-       exit ;;
-    e2k:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    frv:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    hexagon:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    i*86:Linux:*:*)
-       echo "$UNAME_MACHINE"-pc-linux-"$LIBC"
-       exit ;;
-    ia64:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    k1om:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    loongarch32:Linux:*:* | loongarch64:Linux:*:* | loongarchx32:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    m32r*:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    m68*:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    mips:Linux:*:* | mips64:Linux:*:*)
-       set_cc_for_build
-       IS_GLIBC=0
-       test x"${LIBC}" = xgnu && IS_GLIBC=1
-       sed 's/^        //' << EOF > "$dummy.c"
-       #undef CPU
-       #undef mips
-       #undef mipsel
-       #undef mips64
-       #undef mips64el
-       #if ${IS_GLIBC} && defined(_ABI64)
-       LIBCABI=gnuabi64
-       #else
-       #if ${IS_GLIBC} && defined(_ABIN32)
-       LIBCABI=gnuabin32
-       #else
-       LIBCABI=${LIBC}
-       #endif
-       #endif
-
-       #if ${IS_GLIBC} && defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6
-       CPU=mipsisa64r6
-       #else
-       #if ${IS_GLIBC} && !defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6
-       CPU=mipsisa32r6
-       #else
-       #if defined(__mips64)
-       CPU=mips64
-       #else
-       CPU=mips
-       #endif
-       #endif
-       #endif
-
-       #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
-       MIPS_ENDIAN=el
-       #else
-       #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
-       MIPS_ENDIAN=
-       #else
-       MIPS_ENDIAN=
-       #endif
-       #endif
-EOF
-       eval "$($CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI')"
-       test "x$CPU" != x && { echo "$CPU${MIPS_ENDIAN}-unknown-linux-$LIBCABI"; exit; }
-       ;;
-    mips64el:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    openrisc*:Linux:*:*)
-       echo or1k-unknown-linux-"$LIBC"
-       exit ;;
-    or32:Linux:*:* | or1k*:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    padre:Linux:*:*)
-       echo sparc-unknown-linux-"$LIBC"
-       exit ;;
-    parisc64:Linux:*:* | hppa64:Linux:*:*)
-       echo hppa64-unknown-linux-"$LIBC"
-       exit ;;
-    parisc:Linux:*:* | hppa:Linux:*:*)
-       # Look for CPU level
-       case $(grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2) in
-         PA7*) echo hppa1.1-unknown-linux-"$LIBC" ;;
-         PA8*) echo hppa2.0-unknown-linux-"$LIBC" ;;
-         *)    echo hppa-unknown-linux-"$LIBC" ;;
-       esac
-       exit ;;
-    ppc64:Linux:*:*)
-       echo powerpc64-unknown-linux-"$LIBC"
-       exit ;;
-    ppc:Linux:*:*)
-       echo powerpc-unknown-linux-"$LIBC"
-       exit ;;
-    ppc64le:Linux:*:*)
-       echo powerpc64le-unknown-linux-"$LIBC"
-       exit ;;
-    ppcle:Linux:*:*)
-       echo powerpcle-unknown-linux-"$LIBC"
-       exit ;;
-    riscv32:Linux:*:* | riscv32be:Linux:*:* | riscv64:Linux:*:* | riscv64be:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    s390:Linux:*:* | s390x:Linux:*:*)
-       echo "$UNAME_MACHINE"-ibm-linux-"$LIBC"
-       exit ;;
-    sh64*:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    sh*:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    sparc:Linux:*:* | sparc64:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    tile*:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    vax:Linux:*:*)
-       echo "$UNAME_MACHINE"-dec-linux-"$LIBC"
-       exit ;;
-    x86_64:Linux:*:*)
-       set_cc_for_build
-       LIBCABI=$LIBC
-       if test "$CC_FOR_BUILD" != no_compiler_found; then
-           if (echo '#ifdef __ILP32__'; echo IS_X32; echo '#endif') | \
-               (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
-               grep IS_X32 >/dev/null
-           then
-               LIBCABI="$LIBC"x32
-           fi
-       fi
-       echo "$UNAME_MACHINE"-pc-linux-"$LIBCABI"
-       exit ;;
-    xtensa*:Linux:*:*)
-       echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
-       exit ;;
-    i*86:DYNIX/ptx:4*:*)
-       # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there.
-       # earlier versions are messed up and put the nodename in both
-       # sysname and nodename.
-       echo i386-sequent-sysv4
-       exit ;;
-    i*86:UNIX_SV:4.2MP:2.*)
-       # Unixware is an offshoot of SVR4, but it has its own version
-       # number series starting with 2...
-       # I am not positive that other SVR4 systems won't match this,
-       # I just have to hope.  -- rms.
-       # Use sysv4.2uw... so that sysv4* matches it.
-       echo "$UNAME_MACHINE"-pc-sysv4.2uw"$UNAME_VERSION"
-       exit ;;
-    i*86:OS/2:*:*)
-       # If we were able to find `uname', then EMX Unix compatibility
-       # is probably installed.
-       echo "$UNAME_MACHINE"-pc-os2-emx
-       exit ;;
-    i*86:XTS-300:*:STOP)
-       echo "$UNAME_MACHINE"-unknown-stop
-       exit ;;
-    i*86:atheos:*:*)
-       echo "$UNAME_MACHINE"-unknown-atheos
-       exit ;;
-    i*86:syllable:*:*)
-       echo "$UNAME_MACHINE"-pc-syllable
-       exit ;;
-    i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*)
-       echo i386-unknown-lynxos"$UNAME_RELEASE"
-       exit ;;
-    i*86:*DOS:*:*)
-       echo "$UNAME_MACHINE"-pc-msdosdjgpp
-       exit ;;
-    i*86:*:4.*:*)
-       UNAME_REL=$(echo "$UNAME_RELEASE" | sed 's/\/MP$//')
-       if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then
-               echo "$UNAME_MACHINE"-univel-sysv"$UNAME_REL"
-       else
-               echo "$UNAME_MACHINE"-pc-sysv"$UNAME_REL"
-       fi
-       exit ;;
-    i*86:*:5:[678]*)
-       # UnixWare 7.x, OpenUNIX and OpenServer 6.
-       case $(/bin/uname -X | grep "^Machine") in
-           *486*)           UNAME_MACHINE=i486 ;;
-           *Pentium)        UNAME_MACHINE=i586 ;;
-           *Pent*|*Celeron) UNAME_MACHINE=i686 ;;
-       esac
-       echo "$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}"
-       exit ;;
-    i*86:*:3.2:*)
-       if test -f /usr/options/cb.name; then
-               UNAME_REL=$(sed -n 's/.*Version //p' </usr/options/cb.name)
-               echo "$UNAME_MACHINE"-pc-isc"$UNAME_REL"
-       elif /bin/uname -X 2>/dev/null >/dev/null ; then
-               UNAME_REL=$( (/bin/uname -X|grep Release|sed -e 's/.*= //'))
-               (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486
-               (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \
-                       && UNAME_MACHINE=i586
-               (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \
-                       && UNAME_MACHINE=i686
-               (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \
-                       && UNAME_MACHINE=i686
-               echo "$UNAME_MACHINE"-pc-sco"$UNAME_REL"
-       else
-               echo "$UNAME_MACHINE"-pc-sysv32
-       fi
-       exit ;;
-    pc:*:*:*)
-       # Left here for compatibility:
-       # uname -m prints for DJGPP always 'pc', but it prints nothing about
-       # the processor, so we play safe by assuming i586.
-       # Note: whatever this is, it MUST be the same as what config.sub
-       # prints for the "djgpp" host, or else GDB configure will decide that
-       # this is a cross-build.
-       echo i586-pc-msdosdjgpp
-       exit ;;
-    Intel:Mach:3*:*)
-       echo i386-pc-mach3
-       exit ;;
-    paragon:*:*:*)
-       echo i860-intel-osf1
-       exit ;;
-    i860:*:4.*:*) # i860-SVR4
-       if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then
-         echo i860-stardent-sysv"$UNAME_RELEASE" # Stardent Vistra i860-SVR4
-       else # Add other i860-SVR4 vendors below as they are discovered.
-         echo i860-unknown-sysv"$UNAME_RELEASE"  # Unknown i860-SVR4
-       fi
-       exit ;;
-    mini*:CTIX:SYS*5:*)
-       # "miniframe"
-       echo m68010-convergent-sysv
-       exit ;;
-    mc68k:UNIX:SYSTEM5:3.51m)
-       echo m68k-convergent-sysv
-       exit ;;
-    M680?0:D-NIX:5.3:*)
-       echo m68k-diab-dnix
-       exit ;;
-    M68*:*:R3V[5678]*:*)
-       test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;;
-    3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0)
-       OS_REL=''
-       test -r /etc/.relid \
-       && OS_REL=.$(sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid)
-       /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
-         && { echo i486-ncr-sysv4.3"$OS_REL"; exit; }
-       /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
-         && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;;
-    3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*)
-       /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
-         && { echo i486-ncr-sysv4; exit; } ;;
-    NCR*:*:4.2:* | MPRAS*:*:4.2:*)
-       OS_REL='.3'
-       test -r /etc/.relid \
-           && OS_REL=.$(sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid)
-       /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
-           && { echo i486-ncr-sysv4.3"$OS_REL"; exit; }
-       /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
-           && { echo i586-ncr-sysv4.3"$OS_REL"; exit; }
-       /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \
-           && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;;
-    m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*)
-       echo m68k-unknown-lynxos"$UNAME_RELEASE"
-       exit ;;
-    mc68030:UNIX_System_V:4.*:*)
-       echo m68k-atari-sysv4
-       exit ;;
-    TSUNAMI:LynxOS:2.*:*)
-       echo sparc-unknown-lynxos"$UNAME_RELEASE"
-       exit ;;
-    rs6000:LynxOS:2.*:*)
-       echo rs6000-unknown-lynxos"$UNAME_RELEASE"
-       exit ;;
-    PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*)
-       echo powerpc-unknown-lynxos"$UNAME_RELEASE"
-       exit ;;
-    SM[BE]S:UNIX_SV:*:*)
-       echo mips-dde-sysv"$UNAME_RELEASE"
-       exit ;;
-    RM*:ReliantUNIX-*:*:*)
-       echo mips-sni-sysv4
-       exit ;;
-    RM*:SINIX-*:*:*)
-       echo mips-sni-sysv4
-       exit ;;
-    *:SINIX-*:*:*)
-       if uname -p 2>/dev/null >/dev/null ; then
-               UNAME_MACHINE=$( (uname -p) 2>/dev/null)
-               echo "$UNAME_MACHINE"-sni-sysv4
-       else
-               echo ns32k-sni-sysv
-       fi
-       exit ;;
-    PENTIUM:*:4.0*:*)  # Unisys `ClearPath HMP IX 4000' SVR4/MP effort
-                       # says <Richard.M.Bartel@ccMail.Census.GOV>
-       echo i586-unisys-sysv4
-       exit ;;
-    *:UNIX_System_V:4*:FTX*)
-       # From Gerald Hewes <hewes@openmarket.com>.
-       # How about differentiating between stratus architectures? -djm
-       echo hppa1.1-stratus-sysv4
-       exit ;;
-    *:*:*:FTX*)
-       # From seanf@swdc.stratus.com.
-       echo i860-stratus-sysv4
-       exit ;;
-    i*86:VOS:*:*)
-       # From Paul.Green@stratus.com.
-       echo "$UNAME_MACHINE"-stratus-vos
-       exit ;;
-    *:VOS:*:*)
-       # From Paul.Green@stratus.com.
-       echo hppa1.1-stratus-vos
-       exit ;;
-    mc68*:A/UX:*:*)
-       echo m68k-apple-aux"$UNAME_RELEASE"
-       exit ;;
-    news*:NEWS-OS:6*:*)
-       echo mips-sony-newsos6
-       exit ;;
-    R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*)
-       if test -d /usr/nec; then
-               echo mips-nec-sysv"$UNAME_RELEASE"
-       else
-               echo mips-unknown-sysv"$UNAME_RELEASE"
-       fi
-       exit ;;
-    BeBox:BeOS:*:*)    # BeOS running on hardware made by Be, PPC only.
-       echo powerpc-be-beos
-       exit ;;
-    BeMac:BeOS:*:*)    # BeOS running on Mac or Mac clone, PPC only.
-       echo powerpc-apple-beos
-       exit ;;
-    BePC:BeOS:*:*)     # BeOS running on Intel PC compatible.
-       echo i586-pc-beos
-       exit ;;
-    BePC:Haiku:*:*)    # Haiku running on Intel PC compatible.
-       echo i586-pc-haiku
-       exit ;;
-    x86_64:Haiku:*:*)
-       echo x86_64-unknown-haiku
-       exit ;;
-    SX-4:SUPER-UX:*:*)
-       echo sx4-nec-superux"$UNAME_RELEASE"
-       exit ;;
-    SX-5:SUPER-UX:*:*)
-       echo sx5-nec-superux"$UNAME_RELEASE"
-       exit ;;
-    SX-6:SUPER-UX:*:*)
-       echo sx6-nec-superux"$UNAME_RELEASE"
-       exit ;;
-    SX-7:SUPER-UX:*:*)
-       echo sx7-nec-superux"$UNAME_RELEASE"
-       exit ;;
-    SX-8:SUPER-UX:*:*)
-       echo sx8-nec-superux"$UNAME_RELEASE"
-       exit ;;
-    SX-8R:SUPER-UX:*:*)
-       echo sx8r-nec-superux"$UNAME_RELEASE"
-       exit ;;
-    SX-ACE:SUPER-UX:*:*)
-       echo sxace-nec-superux"$UNAME_RELEASE"
-       exit ;;
-    Power*:Rhapsody:*:*)
-       echo powerpc-apple-rhapsody"$UNAME_RELEASE"
-       exit ;;
-    *:Rhapsody:*:*)
-       echo "$UNAME_MACHINE"-apple-rhapsody"$UNAME_RELEASE"
-       exit ;;
-    arm64:Darwin:*:*)
-       echo aarch64-apple-darwin"$UNAME_RELEASE"
-       exit ;;
-    *:Darwin:*:*)
-       UNAME_PROCESSOR=$(uname -p)
-       case $UNAME_PROCESSOR in
-           unknown) UNAME_PROCESSOR=powerpc ;;
-       esac
-       if command -v xcode-select > /dev/null 2> /dev/null && \
-               ! xcode-select --print-path > /dev/null 2> /dev/null ; then
-           # Avoid executing cc if there is no toolchain installed as
-           # cc will be a stub that puts up a graphical alert
-           # prompting the user to install developer tools.
-           CC_FOR_BUILD=no_compiler_found
-       else
-           set_cc_for_build
-       fi
-       if test "$CC_FOR_BUILD" != no_compiler_found; then
-           if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \
-                  (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
-                  grep IS_64BIT_ARCH >/dev/null
-           then
-               case $UNAME_PROCESSOR in
-                   i386) UNAME_PROCESSOR=x86_64 ;;
-                   powerpc) UNAME_PROCESSOR=powerpc64 ;;
-               esac
-           fi
-           # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc
-           if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \
-                  (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
-                  grep IS_PPC >/dev/null
-           then
-               UNAME_PROCESSOR=powerpc
-           fi
-       elif test "$UNAME_PROCESSOR" = i386 ; then
-           # uname -m returns i386 or x86_64
-           UNAME_PROCESSOR=$UNAME_MACHINE
-       fi
-       echo "$UNAME_PROCESSOR"-apple-darwin"$UNAME_RELEASE"
-       exit ;;
-    *:procnto*:*:* | *:QNX:[0123456789]*:*)
-       UNAME_PROCESSOR=$(uname -p)
-       if test "$UNAME_PROCESSOR" = x86; then
-               UNAME_PROCESSOR=i386
-               UNAME_MACHINE=pc
-       fi
-       echo "$UNAME_PROCESSOR"-"$UNAME_MACHINE"-nto-qnx"$UNAME_RELEASE"
-       exit ;;
-    *:QNX:*:4*)
-       echo i386-pc-qnx
-       exit ;;
-    NEO-*:NONSTOP_KERNEL:*:*)
-       echo neo-tandem-nsk"$UNAME_RELEASE"
-       exit ;;
-    NSE-*:NONSTOP_KERNEL:*:*)
-       echo nse-tandem-nsk"$UNAME_RELEASE"
-       exit ;;
-    NSR-*:NONSTOP_KERNEL:*:*)
-       echo nsr-tandem-nsk"$UNAME_RELEASE"
-       exit ;;
-    NSV-*:NONSTOP_KERNEL:*:*)
-       echo nsv-tandem-nsk"$UNAME_RELEASE"
-       exit ;;
-    NSX-*:NONSTOP_KERNEL:*:*)
-       echo nsx-tandem-nsk"$UNAME_RELEASE"
-       exit ;;
-    *:NonStop-UX:*:*)
-       echo mips-compaq-nonstopux
-       exit ;;
-    BS2000:POSIX*:*:*)
-       echo bs2000-siemens-sysv
-       exit ;;
-    DS/*:UNIX_System_V:*:*)
-       echo "$UNAME_MACHINE"-"$UNAME_SYSTEM"-"$UNAME_RELEASE"
-       exit ;;
-    *:Plan9:*:*)
-       # "uname -m" is not consistent, so use $cputype instead. 386
-       # is converted to i386 for consistency with other x86
-       # operating systems.
-       # shellcheck disable=SC2154
-       if test "$cputype" = 386; then
-           UNAME_MACHINE=i386
-       else
-           UNAME_MACHINE="$cputype"
-       fi
-       echo "$UNAME_MACHINE"-unknown-plan9
-       exit ;;
-    *:TOPS-10:*:*)
-       echo pdp10-unknown-tops10
-       exit ;;
-    *:TENEX:*:*)
-       echo pdp10-unknown-tenex
-       exit ;;
-    KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*)
-       echo pdp10-dec-tops20
-       exit ;;
-    XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*)
-       echo pdp10-xkl-tops20
-       exit ;;
-    *:TOPS-20:*:*)
-       echo pdp10-unknown-tops20
-       exit ;;
-    *:ITS:*:*)
-       echo pdp10-unknown-its
-       exit ;;
-    SEI:*:*:SEIUX)
-       echo mips-sei-seiux"$UNAME_RELEASE"
-       exit ;;
-    *:DragonFly:*:*)
-       echo "$UNAME_MACHINE"-unknown-dragonfly"$(echo "$UNAME_RELEASE"|sed -e 's/[-(].*//')"
-       exit ;;
-    *:*VMS:*:*)
-       UNAME_MACHINE=$( (uname -p) 2>/dev/null)
-       case "$UNAME_MACHINE" in
-           A*) echo alpha-dec-vms ; exit ;;
-           I*) echo ia64-dec-vms ; exit ;;
-           V*) echo vax-dec-vms ; exit ;;
-       esac ;;
-    *:XENIX:*:SysV)
-       echo i386-pc-xenix
-       exit ;;
-    i*86:skyos:*:*)
-       echo "$UNAME_MACHINE"-pc-skyos"$(echo "$UNAME_RELEASE" | sed -e 's/ .*$//')"
-       exit ;;
-    i*86:rdos:*:*)
-       echo "$UNAME_MACHINE"-pc-rdos
-       exit ;;
-    i*86:AROS:*:*)
-       echo "$UNAME_MACHINE"-pc-aros
-       exit ;;
-    x86_64:VMkernel:*:*)
-       echo "$UNAME_MACHINE"-unknown-esx
-       exit ;;
-    amd64:Isilon\ OneFS:*:*)
-       echo x86_64-unknown-onefs
-       exit ;;
-    *:Unleashed:*:*)
-       echo "$UNAME_MACHINE"-unknown-unleashed"$UNAME_RELEASE"
-       exit ;;
-esac
-
-# No uname command or uname output not recognized.
-set_cc_for_build
-cat > "$dummy.c" <<EOF
-#ifdef _SEQUENT_
-#include <sys/types.h>
-#include <sys/utsname.h>
-#endif
-#if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__)
-#if defined (vax) || defined (__vax) || defined (__vax__) || defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__)
-#include <signal.h>
-#if defined(_SIZE_T_) || defined(SIGLOST)
-#include <sys/utsname.h>
-#endif
-#endif
-#endif
-main ()
-{
-#if defined (sony)
-#if defined (MIPSEB)
-  /* BFD wants "bsd" instead of "newsos".  Perhaps BFD should be changed,
-     I don't know....  */
-  printf ("mips-sony-bsd\n"); exit (0);
-#else
-#include <sys/param.h>
-  printf ("m68k-sony-newsos%s\n",
-#ifdef NEWSOS4
-  "4"
-#else
-  ""
-#endif
-  ); exit (0);
-#endif
-#endif
-
-#if defined (NeXT)
-#if !defined (__ARCHITECTURE__)
-#define __ARCHITECTURE__ "m68k"
-#endif
-  int version;
-  version=$( (hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null);
-  if (version < 4)
-    printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version);
-  else
-    printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version);
-  exit (0);
-#endif
-
-#if defined (MULTIMAX) || defined (n16)
-#if defined (UMAXV)
-  printf ("ns32k-encore-sysv\n"); exit (0);
-#else
-#if defined (CMU)
-  printf ("ns32k-encore-mach\n"); exit (0);
-#else
-  printf ("ns32k-encore-bsd\n"); exit (0);
-#endif
-#endif
-#endif
-
-#if defined (__386BSD__)
-  printf ("i386-pc-bsd\n"); exit (0);
-#endif
-
-#if defined (sequent)
-#if defined (i386)
-  printf ("i386-sequent-dynix\n"); exit (0);
-#endif
-#if defined (ns32000)
-  printf ("ns32k-sequent-dynix\n"); exit (0);
-#endif
-#endif
-
-#if defined (_SEQUENT_)
-  struct utsname un;
-
-  uname(&un);
-  if (strncmp(un.version, "V2", 2) == 0) {
-    printf ("i386-sequent-ptx2\n"); exit (0);
-  }
-  if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */
-    printf ("i386-sequent-ptx1\n"); exit (0);
-  }
-  printf ("i386-sequent-ptx\n"); exit (0);
-#endif
-
-#if defined (vax)
-#if !defined (ultrix)
-#include <sys/param.h>
-#if defined (BSD)
-#if BSD == 43
-  printf ("vax-dec-bsd4.3\n"); exit (0);
-#else
-#if BSD == 199006
-  printf ("vax-dec-bsd4.3reno\n"); exit (0);
-#else
-  printf ("vax-dec-bsd\n"); exit (0);
-#endif
-#endif
-#else
-  printf ("vax-dec-bsd\n"); exit (0);
-#endif
-#else
-#if defined(_SIZE_T_) || defined(SIGLOST)
-  struct utsname un;
-  uname (&un);
-  printf ("vax-dec-ultrix%s\n", un.release); exit (0);
-#else
-  printf ("vax-dec-ultrix\n"); exit (0);
-#endif
-#endif
-#endif
-#if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__)
-#if defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__)
-#if defined(_SIZE_T_) || defined(SIGLOST)
-  struct utsname *un;
-  uname (&un);
-  printf ("mips-dec-ultrix%s\n", un.release); exit (0);
-#else
-  printf ("mips-dec-ultrix\n"); exit (0);
-#endif
-#endif
-#endif
-
-#if defined (alliant) && defined (i860)
-  printf ("i860-alliant-bsd\n"); exit (0);
-#endif
-
-  exit (1);
-}
-EOF
-
-$CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null && SYSTEM_NAME=$($dummy) &&
-       { echo "$SYSTEM_NAME"; exit; }
-
-# Apollos put the system type in the environment.
-test -d /usr/apollo && { echo "$ISP-apollo-$SYSTYPE"; exit; }
-
-echo "$0: unable to guess system type" >&2
-
-case "$UNAME_MACHINE:$UNAME_SYSTEM" in
-    mips:Linux | mips64:Linux)
-       # If we got here on MIPS GNU/Linux, output extra information.
-       cat >&2 <<EOF
-
-NOTE: MIPS GNU/Linux systems require a C compiler to fully recognize
-the system type. Please install a C compiler and try again.
-EOF
-       ;;
-esac
-
-cat >&2 <<EOF
-
-This script (version $timestamp), has failed to recognize the
-operating system you are using. If your script is old, overwrite *all*
-copies of config.guess and config.sub with the latest versions from:
-
-  https://git.savannah.gnu.org/cgit/config.git/plain/config.guess
-and
-  https://git.savannah.gnu.org/cgit/config.git/plain/config.sub
-EOF
-
-year=$(echo $timestamp | sed 's,-.*,,')
-# shellcheck disable=SC2003
-if test "$(expr "$(date +%Y)" - "$year")" -lt 3 ; then
-   cat >&2 <<EOF
-
-If $0 has already been updated, send the following data and any
-information you think might be pertinent to config-patches@gnu.org to
-provide the necessary information to handle your system.
-
-config.guess timestamp = $timestamp
-
-uname -m = $( (uname -m) 2>/dev/null || echo unknown)
-uname -r = $( (uname -r) 2>/dev/null || echo unknown)
-uname -s = $( (uname -s) 2>/dev/null || echo unknown)
-uname -v = $( (uname -v) 2>/dev/null || echo unknown)
-
-/usr/bin/uname -p = $( (/usr/bin/uname -p) 2>/dev/null)
-/bin/uname -X     = $( (/bin/uname -X) 2>/dev/null)
-
-hostinfo               = $( (hostinfo) 2>/dev/null)
-/bin/universe          = $( (/bin/universe) 2>/dev/null)
-/usr/bin/arch -k       = $( (/usr/bin/arch -k) 2>/dev/null)
-/bin/arch              = $( (/bin/arch) 2>/dev/null)
-/usr/bin/oslevel       = $( (/usr/bin/oslevel) 2>/dev/null)
-/usr/convex/getsysinfo = $( (/usr/convex/getsysinfo) 2>/dev/null)
-
-UNAME_MACHINE = "$UNAME_MACHINE"
-UNAME_RELEASE = "$UNAME_RELEASE"
-UNAME_SYSTEM  = "$UNAME_SYSTEM"
-UNAME_VERSION = "$UNAME_VERSION"
-EOF
-fi
-
-exit 1
-
-# Local variables:
-# eval: (add-hook 'before-save-hook 'time-stamp)
-# time-stamp-start: "timestamp='"
-# time-stamp-format: "%:y-%02m-%02d"
-# time-stamp-end: "'"
-# End:
diff --git a/citadel/config.h b/citadel/config.h
deleted file mode 100644 (file)
index 23f144a..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (c) 1987-2016 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include "serv_extensions.h"
-#include "citadel_dirs.h"
-
-// This is the format of the legacy config file.  Do not attempt to do anything with it other
-// than migrate it into the new format.  Seriously -- DO NOT CHANGE IT.  The only purpose of this
-// struct is to represent the OLD configuration format.
-struct legacy_config {
-       char c_nodename[16];            // short name of this node on a Citadel network
-       char c_fqdn[64];                // this site's fully qualified domain name
-       char c_humannode[21];           // human-readable site name
-       char c_niu_7[16];
-       uid_t c_niu_6;
-       char c_creataide;               // 1 = creating a room auto-grants room aide privileges
-       int c_sleeping;                 // watchdog timer (seconds)
-       char c_initax;                  // initial access level for new users
-       char c_regiscall;               // after c_regiscall logins user will be asked to register
-       char c_twitdetect;              // automatically move messages from problem users to trashcan
-       char c_twitroom[ROOMNAMELEN];   // name of trashcan
-       char c_moreprompt[80];          // paginator prompt
-       char c_restrict;                // require per-user permission to send Internet mail
-       long c_niu_1;
-       char c_site_location[32];       // geographic location of this Citadel site
-       char c_sysadm[26];              // name of system administrator
-       char c_niu_2[15];
-       int c_niu_3;
-       int c_maxsessions;              // maximum number of concurrent sessions allowed
-       char c_ip_addr[20];             // bind address for listening sockets
-       int c_port_number;              // port number for Citadel protocol (usually 504)
-       int c_niu_4;
-       struct ExpirePolicy c_ep;       // default expire policy for the entire site
-       int c_userpurge;                // user purge time (in days)
-       int c_roompurge;                // room purge time (in days)
-       char c_logpages[ROOMNAMELEN];
-       char c_createax;
-       long c_maxmsglen;
-       int c_min_workers;
-       int c_max_workers;
-       int c_pop3_port;
-       int c_smtp_port;
-       int c_rfc822_strict_from;
-       int c_aide_zap;
-       int c_imap_port;
-       time_t c_net_freq;
-       char c_disable_newu;
-       char c_enable_fulltext;
-       char c_baseroom[ROOMNAMELEN];
-       char c_aideroom[ROOMNAMELEN];
-       int c_purge_hour;
-       struct ExpirePolicy c_mbxep;
-       char c_ldap_host[128];
-       int c_ldap_port;
-       char c_ldap_base_dn[256];
-       char c_ldap_bind_dn[256];
-       char c_ldap_bind_pw[256];
-       int c_msa_port;
-       int c_imaps_port;
-       int c_pop3s_port;
-       int c_smtps_port;
-       char c_auto_cull;
-       char c_niu_5;
-       char c_allow_spoofing;
-       char c_journal_email;
-       char c_journal_pubmsgs;
-       char c_journal_dest[128];
-       char c_default_cal_zone[128];
-       int c_pftcpdict_port;
-       int c_niu_9;
-       int c_auth_mode;
-       char c_niu_8[256];
-       int c_niu_10;
-       char c_niu_11[256];
-       char c_niu_12[256];
-       char c_rbl_at_greeting;
-       char c_master_user[32];
-       char c_master_pass[32];
-       char c_pager_program[256];
-       char c_imap_keep_from;
-       int c_xmpp_c2s_port;
-       int c_xmpp_s2s_port;
-       time_t c_pop3_fetch;
-       time_t c_pop3_fastest;
-       int c_spam_flag_only;
-       int c_guest_logins;
-       int c_nntp_port;
-       int c_nntps_port;
-};
-
-
-void initialize_config_system(void);
-void shutdown_config_system(void);
-void put_config(void);
-void CtdlSetConfigStr(char *, char *);
-char *CtdlGetConfigStr(char *);
-int CtdlGetConfigInt(char *);
-long CtdlGetConfigLong(char *);
-void CtdlSetConfigInt(char *key, int value);
-void CtdlSetConfigLong(char *key, long value);
-void CtdlDelConfig(char *key);
-
-char *CtdlGetSysConfig(char *sysconfname);
-void CtdlPutSysConfig(char *sysconfname, char *sysconfdata);
-void validate_config(void);
-void netcfg_keyname(char *, long);
diff --git a/citadel/config.sub b/citadel/config.sub
deleted file mode 100644 (file)
index b0f8492..0000000
+++ /dev/null
@@ -1,1855 +0,0 @@
-#! /bin/sh
-# Configuration validation subroutine script.
-#   Copyright 1992-2021 Free Software Foundation, Inc.
-
-timestamp='2021-01-07'
-
-# This file is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, see <https://www.gnu.org/licenses/>.
-#
-# As a special exception to the GNU General Public License, if you
-# distribute this file as part of a program that contains a
-# configuration script generated by Autoconf, you may include it under
-# the same distribution terms that you use for the rest of that
-# program.  This Exception is an additional permission under section 7
-# of the GNU General Public License, version 3 ("GPLv3").
-
-
-# Please send patches to <config-patches@gnu.org>.
-#
-# Configuration subroutine to validate and canonicalize a configuration type.
-# Supply the specified configuration type as an argument.
-# If it is invalid, we print an error message on stderr and exit with code 1.
-# Otherwise, we print the canonical config type on stdout and succeed.
-
-# You can get the latest version of this script from:
-# https://git.savannah.gnu.org/cgit/config.git/plain/config.sub
-
-# This file is supposed to be the same for all GNU packages
-# and recognize all the CPU types, system types and aliases
-# that are meaningful with *any* GNU software.
-# Each package is responsible for reporting which valid configurations
-# it does not support.  The user should be able to distinguish
-# a failure to support a valid configuration from a meaningless
-# configuration.
-
-# The goal of this file is to map all the various variations of a given
-# machine specification into a single specification in the form:
-#      CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
-# or in some cases, the newer four-part form:
-#      CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
-# It is wrong to echo any other type of specification.
-
-me=$(echo "$0" | sed -e 's,.*/,,')
-
-usage="\
-Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS
-
-Canonicalize a configuration name.
-
-Options:
-  -h, --help         print this help, then exit
-  -t, --time-stamp   print date of last modification, then exit
-  -v, --version      print version number, then exit
-
-Report bugs and patches to <config-patches@gnu.org>."
-
-version="\
-GNU config.sub ($timestamp)
-
-Copyright 1992-2021 Free Software Foundation, Inc.
-
-This is free software; see the source for copying conditions.  There is NO
-warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
-
-help="
-Try \`$me --help' for more information."
-
-# Parse command line
-while test $# -gt 0 ; do
-  case $1 in
-    --time-stamp | --time* | -t )
-       echo "$timestamp" ; exit ;;
-    --version | -v )
-       echo "$version" ; exit ;;
-    --help | --h* | -h )
-       echo "$usage"; exit ;;
-    -- )     # Stop option processing
-       shift; break ;;
-    - )        # Use stdin as input.
-       break ;;
-    -* )
-       echo "$me: invalid option $1$help" >&2
-       exit 1 ;;
-
-    *local*)
-       # First pass through any local machine types.
-       echo "$1"
-       exit ;;
-
-    * )
-       break ;;
-  esac
-done
-
-case $# in
- 0) echo "$me: missing argument$help" >&2
-    exit 1;;
- 1) ;;
- *) echo "$me: too many arguments$help" >&2
-    exit 1;;
-esac
-
-# Split fields of configuration type
-# shellcheck disable=SC2162
-IFS="-" read field1 field2 field3 field4 <<EOF
-$1
-EOF
-
-# Separate into logical components for further validation
-case $1 in
-       *-*-*-*-*)
-               echo Invalid configuration \`"$1"\': more than four components >&2
-               exit 1
-               ;;
-       *-*-*-*)
-               basic_machine=$field1-$field2
-               basic_os=$field3-$field4
-               ;;
-       *-*-*)
-               # Ambiguous whether COMPANY is present, or skipped and KERNEL-OS is two
-               # parts
-               maybe_os=$field2-$field3
-               case $maybe_os in
-                       nto-qnx* | linux-* | uclinux-uclibc* \
-                       | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \
-                       | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \
-                       | storm-chaos* | os2-emx* | rtmk-nova*)
-                               basic_machine=$field1
-                               basic_os=$maybe_os
-                               ;;
-                       android-linux)
-                               basic_machine=$field1-unknown
-                               basic_os=linux-android
-                               ;;
-                       *)
-                               basic_machine=$field1-$field2
-                               basic_os=$field3
-                               ;;
-               esac
-               ;;
-       *-*)
-               # A lone config we happen to match not fitting any pattern
-               case $field1-$field2 in
-                       decstation-3100)
-                               basic_machine=mips-dec
-                               basic_os=
-                               ;;
-                       *-*)
-                               # Second component is usually, but not always the OS
-                               case $field2 in
-                                       # Prevent following clause from handling this valid os
-                                       sun*os*)
-                                               basic_machine=$field1
-                                               basic_os=$field2
-                                               ;;
-                                       # Manufacturers
-                                       dec* | mips* | sequent* | encore* | pc533* | sgi* | sony* \
-                                       | att* | 7300* | 3300* | delta* | motorola* | sun[234]* \
-                                       | unicom* | ibm* | next | hp | isi* | apollo | altos* \
-                                       | convergent* | ncr* | news | 32* | 3600* | 3100* \
-                                       | hitachi* | c[123]* | convex* | sun | crds | omron* | dg \
-                                       | ultra | tti* | harris | dolphin | highlevel | gould \
-                                       | cbm | ns | masscomp | apple | axis | knuth | cray \
-                                       | microblaze* | sim | cisco \
-                                       | oki | wec | wrs | winbond)
-                                               basic_machine=$field1-$field2
-                                               basic_os=
-                                               ;;
-                                       *)
-                                               basic_machine=$field1
-                                               basic_os=$field2
-                                               ;;
-                               esac
-                       ;;
-               esac
-               ;;
-       *)
-               # Convert single-component short-hands not valid as part of
-               # multi-component configurations.
-               case $field1 in
-                       386bsd)
-                               basic_machine=i386-pc
-                               basic_os=bsd
-                               ;;
-                       a29khif)
-                               basic_machine=a29k-amd
-                               basic_os=udi
-                               ;;
-                       adobe68k)
-                               basic_machine=m68010-adobe
-                               basic_os=scout
-                               ;;
-                       alliant)
-                               basic_machine=fx80-alliant
-                               basic_os=
-                               ;;
-                       altos | altos3068)
-                               basic_machine=m68k-altos
-                               basic_os=
-                               ;;
-                       am29k)
-                               basic_machine=a29k-none
-                               basic_os=bsd
-                               ;;
-                       amdahl)
-                               basic_machine=580-amdahl
-                               basic_os=sysv
-                               ;;
-                       amiga)
-                               basic_machine=m68k-unknown
-                               basic_os=
-                               ;;
-                       amigaos | amigados)
-                               basic_machine=m68k-unknown
-                               basic_os=amigaos
-                               ;;
-                       amigaunix | amix)
-                               basic_machine=m68k-unknown
-                               basic_os=sysv4
-                               ;;
-                       apollo68)
-                               basic_machine=m68k-apollo
-                               basic_os=sysv
-                               ;;
-                       apollo68bsd)
-                               basic_machine=m68k-apollo
-                               basic_os=bsd
-                               ;;
-                       aros)
-                               basic_machine=i386-pc
-                               basic_os=aros
-                               ;;
-                       aux)
-                               basic_machine=m68k-apple
-                               basic_os=aux
-                               ;;
-                       balance)
-                               basic_machine=ns32k-sequent
-                               basic_os=dynix
-                               ;;
-                       blackfin)
-                               basic_machine=bfin-unknown
-                               basic_os=linux
-                               ;;
-                       cegcc)
-                               basic_machine=arm-unknown
-                               basic_os=cegcc
-                               ;;
-                       convex-c1)
-                               basic_machine=c1-convex
-                               basic_os=bsd
-                               ;;
-                       convex-c2)
-                               basic_machine=c2-convex
-                               basic_os=bsd
-                               ;;
-                       convex-c32)
-                               basic_machine=c32-convex
-                               basic_os=bsd
-                               ;;
-                       convex-c34)
-                               basic_machine=c34-convex
-                               basic_os=bsd
-                               ;;
-                       convex-c38)
-                               basic_machine=c38-convex
-                               basic_os=bsd
-                               ;;
-                       cray)
-                               basic_machine=j90-cray
-                               basic_os=unicos
-                               ;;
-                       crds | unos)
-                               basic_machine=m68k-crds
-                               basic_os=
-                               ;;
-                       da30)
-                               basic_machine=m68k-da30
-                               basic_os=
-                               ;;
-                       decstation | pmax | pmin | dec3100 | decstatn)
-                               basic_machine=mips-dec
-                               basic_os=
-                               ;;
-                       delta88)
-                               basic_machine=m88k-motorola
-                               basic_os=sysv3
-                               ;;
-                       dicos)
-                               basic_machine=i686-pc
-                               basic_os=dicos
-                               ;;
-                       djgpp)
-                               basic_machine=i586-pc
-                               basic_os=msdosdjgpp
-                               ;;
-                       ebmon29k)
-                               basic_machine=a29k-amd
-                               basic_os=ebmon
-                               ;;
-                       es1800 | OSE68k | ose68k | ose | OSE)
-                               basic_machine=m68k-ericsson
-                               basic_os=ose
-                               ;;
-                       gmicro)
-                               basic_machine=tron-gmicro
-                               basic_os=sysv
-                               ;;
-                       go32)
-                               basic_machine=i386-pc
-                               basic_os=go32
-                               ;;
-                       h8300hms)
-                               basic_machine=h8300-hitachi
-                               basic_os=hms
-                               ;;
-                       h8300xray)
-                               basic_machine=h8300-hitachi
-                               basic_os=xray
-                               ;;
-                       h8500hms)
-                               basic_machine=h8500-hitachi
-                               basic_os=hms
-                               ;;
-                       harris)
-                               basic_machine=m88k-harris
-                               basic_os=sysv3
-                               ;;
-                       hp300 | hp300hpux)
-                               basic_machine=m68k-hp
-                               basic_os=hpux
-                               ;;
-                       hp300bsd)
-                               basic_machine=m68k-hp
-                               basic_os=bsd
-                               ;;
-                       hppaosf)
-                               basic_machine=hppa1.1-hp
-                               basic_os=osf
-                               ;;
-                       hppro)
-                               basic_machine=hppa1.1-hp
-                               basic_os=proelf
-                               ;;
-                       i386mach)
-                               basic_machine=i386-mach
-                               basic_os=mach
-                               ;;
-                       isi68 | isi)
-                               basic_machine=m68k-isi
-                               basic_os=sysv
-                               ;;
-                       m68knommu)
-                               basic_machine=m68k-unknown
-                               basic_os=linux
-                               ;;
-                       magnum | m3230)
-                               basic_machine=mips-mips
-                               basic_os=sysv
-                               ;;
-                       merlin)
-                               basic_machine=ns32k-utek
-                               basic_os=sysv
-                               ;;
-                       mingw64)
-                               basic_machine=x86_64-pc
-                               basic_os=mingw64
-                               ;;
-                       mingw32)
-                               basic_machine=i686-pc
-                               basic_os=mingw32
-                               ;;
-                       mingw32ce)
-                               basic_machine=arm-unknown
-                               basic_os=mingw32ce
-                               ;;
-                       monitor)
-                               basic_machine=m68k-rom68k
-                               basic_os=coff
-                               ;;
-                       morphos)
-                               basic_machine=powerpc-unknown
-                               basic_os=morphos
-                               ;;
-                       moxiebox)
-                               basic_machine=moxie-unknown
-                               basic_os=moxiebox
-                               ;;
-                       msdos)
-                               basic_machine=i386-pc
-                               basic_os=msdos
-                               ;;
-                       msys)
-                               basic_machine=i686-pc
-                               basic_os=msys
-                               ;;
-                       mvs)
-                               basic_machine=i370-ibm
-                               basic_os=mvs
-                               ;;
-                       nacl)
-                               basic_machine=le32-unknown
-                               basic_os=nacl
-                               ;;
-                       ncr3000)
-                               basic_machine=i486-ncr
-                               basic_os=sysv4
-                               ;;
-                       netbsd386)
-                               basic_machine=i386-pc
-                               basic_os=netbsd
-                               ;;
-                       netwinder)
-                               basic_machine=armv4l-rebel
-                               basic_os=linux
-                               ;;
-                       news | news700 | news800 | news900)
-                               basic_machine=m68k-sony
-                               basic_os=newsos
-                               ;;
-                       news1000)
-                               basic_machine=m68030-sony
-                               basic_os=newsos
-                               ;;
-                       necv70)
-                               basic_machine=v70-nec
-                               basic_os=sysv
-                               ;;
-                       nh3000)
-                               basic_machine=m68k-harris
-                               basic_os=cxux
-                               ;;
-                       nh[45]000)
-                               basic_machine=m88k-harris
-                               basic_os=cxux
-                               ;;
-                       nindy960)
-                               basic_machine=i960-intel
-                               basic_os=nindy
-                               ;;
-                       mon960)
-                               basic_machine=i960-intel
-                               basic_os=mon960
-                               ;;
-                       nonstopux)
-                               basic_machine=mips-compaq
-                               basic_os=nonstopux
-                               ;;
-                       os400)
-                               basic_machine=powerpc-ibm
-                               basic_os=os400
-                               ;;
-                       OSE68000 | ose68000)
-                               basic_machine=m68000-ericsson
-                               basic_os=ose
-                               ;;
-                       os68k)
-                               basic_machine=m68k-none
-                               basic_os=os68k
-                               ;;
-                       paragon)
-                               basic_machine=i860-intel
-                               basic_os=osf
-                               ;;
-                       parisc)
-                               basic_machine=hppa-unknown
-                               basic_os=linux
-                               ;;
-                       psp)
-                               basic_machine=mipsallegrexel-sony
-                               basic_os=psp
-                               ;;
-                       pw32)
-                               basic_machine=i586-unknown
-                               basic_os=pw32
-                               ;;
-                       rdos | rdos64)
-                               basic_machine=x86_64-pc
-                               basic_os=rdos
-                               ;;
-                       rdos32)
-                               basic_machine=i386-pc
-                               basic_os=rdos
-                               ;;
-                       rom68k)
-                               basic_machine=m68k-rom68k
-                               basic_os=coff
-                               ;;
-                       sa29200)
-                               basic_machine=a29k-amd
-                               basic_os=udi
-                               ;;
-                       sei)
-                               basic_machine=mips-sei
-                               basic_os=seiux
-                               ;;
-                       sequent)
-                               basic_machine=i386-sequent
-                               basic_os=
-                               ;;
-                       sps7)
-                               basic_machine=m68k-bull
-                               basic_os=sysv2
-                               ;;
-                       st2000)
-                               basic_machine=m68k-tandem
-                               basic_os=
-                               ;;
-                       stratus)
-                               basic_machine=i860-stratus
-                               basic_os=sysv4
-                               ;;
-                       sun2)
-                               basic_machine=m68000-sun
-                               basic_os=
-                               ;;
-                       sun2os3)
-                               basic_machine=m68000-sun
-                               basic_os=sunos3
-                               ;;
-                       sun2os4)
-                               basic_machine=m68000-sun
-                               basic_os=sunos4
-                               ;;
-                       sun3)
-                               basic_machine=m68k-sun
-                               basic_os=
-                               ;;
-                       sun3os3)
-                               basic_machine=m68k-sun
-                               basic_os=sunos3
-                               ;;
-                       sun3os4)
-                               basic_machine=m68k-sun
-                               basic_os=sunos4
-                               ;;
-                       sun4)
-                               basic_machine=sparc-sun
-                               basic_os=
-                               ;;
-                       sun4os3)
-                               basic_machine=sparc-sun
-                               basic_os=sunos3
-                               ;;
-                       sun4os4)
-                               basic_machine=sparc-sun
-                               basic_os=sunos4
-                               ;;
-                       sun4sol2)
-                               basic_machine=sparc-sun
-                               basic_os=solaris2
-                               ;;
-                       sun386 | sun386i | roadrunner)
-                               basic_machine=i386-sun
-                               basic_os=
-                               ;;
-                       sv1)
-                               basic_machine=sv1-cray
-                               basic_os=unicos
-                               ;;
-                       symmetry)
-                               basic_machine=i386-sequent
-                               basic_os=dynix
-                               ;;
-                       t3e)
-                               basic_machine=alphaev5-cray
-                               basic_os=unicos
-                               ;;
-                       t90)
-                               basic_machine=t90-cray
-                               basic_os=unicos
-                               ;;
-                       toad1)
-                               basic_machine=pdp10-xkl
-                               basic_os=tops20
-                               ;;
-                       tpf)
-                               basic_machine=s390x-ibm
-                               basic_os=tpf
-                               ;;
-                       udi29k)
-                               basic_machine=a29k-amd
-                               basic_os=udi
-                               ;;
-                       ultra3)
-                               basic_machine=a29k-nyu
-                               basic_os=sym1
-                               ;;
-                       v810 | necv810)
-                               basic_machine=v810-nec
-                               basic_os=none
-                               ;;
-                       vaxv)
-                               basic_machine=vax-dec
-                               basic_os=sysv
-                               ;;
-                       vms)
-                               basic_machine=vax-dec
-                               basic_os=vms
-                               ;;
-                       vsta)
-                               basic_machine=i386-pc
-                               basic_os=vsta
-                               ;;
-                       vxworks960)
-                               basic_machine=i960-wrs
-                               basic_os=vxworks
-                               ;;
-                       vxworks68)
-                               basic_machine=m68k-wrs
-                               basic_os=vxworks
-                               ;;
-                       vxworks29k)
-                               basic_machine=a29k-wrs
-                               basic_os=vxworks
-                               ;;
-                       xbox)
-                               basic_machine=i686-pc
-                               basic_os=mingw32
-                               ;;
-                       ymp)
-                               basic_machine=ymp-cray
-                               basic_os=unicos
-                               ;;
-                       *)
-                               basic_machine=$1
-                               basic_os=
-                               ;;
-               esac
-               ;;
-esac
-
-# Decode 1-component or ad-hoc basic machines
-case $basic_machine in
-       # Here we handle the default manufacturer of certain CPU types.  It is in
-       # some cases the only manufacturer, in others, it is the most popular.
-       w89k)
-               cpu=hppa1.1
-               vendor=winbond
-               ;;
-       op50n)
-               cpu=hppa1.1
-               vendor=oki
-               ;;
-       op60c)
-               cpu=hppa1.1
-               vendor=oki
-               ;;
-       ibm*)
-               cpu=i370
-               vendor=ibm
-               ;;
-       orion105)
-               cpu=clipper
-               vendor=highlevel
-               ;;
-       mac | mpw | mac-mpw)
-               cpu=m68k
-               vendor=apple
-               ;;
-       pmac | pmac-mpw)
-               cpu=powerpc
-               vendor=apple
-               ;;
-
-       # Recognize the various machine names and aliases which stand
-       # for a CPU type and a company and sometimes even an OS.
-       3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc)
-               cpu=m68000
-               vendor=att
-               ;;
-       3b*)
-               cpu=we32k
-               vendor=att
-               ;;
-       bluegene*)
-               cpu=powerpc
-               vendor=ibm
-               basic_os=cnk
-               ;;
-       decsystem10* | dec10*)
-               cpu=pdp10
-               vendor=dec
-               basic_os=tops10
-               ;;
-       decsystem20* | dec20*)
-               cpu=pdp10
-               vendor=dec
-               basic_os=tops20
-               ;;
-       delta | 3300 | motorola-3300 | motorola-delta \
-             | 3300-motorola | delta-motorola)
-               cpu=m68k
-               vendor=motorola
-               ;;
-       dpx2*)
-               cpu=m68k
-               vendor=bull
-               basic_os=sysv3
-               ;;
-       encore | umax | mmax)
-               cpu=ns32k
-               vendor=encore
-               ;;
-       elxsi)
-               cpu=elxsi
-               vendor=elxsi
-               basic_os=${basic_os:-bsd}
-               ;;
-       fx2800)
-               cpu=i860
-               vendor=alliant
-               ;;
-       genix)
-               cpu=ns32k
-               vendor=ns
-               ;;
-       h3050r* | hiux*)
-               cpu=hppa1.1
-               vendor=hitachi
-               basic_os=hiuxwe2
-               ;;
-       hp3k9[0-9][0-9] | hp9[0-9][0-9])
-               cpu=hppa1.0
-               vendor=hp
-               ;;
-       hp9k2[0-9][0-9] | hp9k31[0-9])
-               cpu=m68000
-               vendor=hp
-               ;;
-       hp9k3[2-9][0-9])
-               cpu=m68k
-               vendor=hp
-               ;;
-       hp9k6[0-9][0-9] | hp6[0-9][0-9])
-               cpu=hppa1.0
-               vendor=hp
-               ;;
-       hp9k7[0-79][0-9] | hp7[0-79][0-9])
-               cpu=hppa1.1
-               vendor=hp
-               ;;
-       hp9k78[0-9] | hp78[0-9])
-               # FIXME: really hppa2.0-hp
-               cpu=hppa1.1
-               vendor=hp
-               ;;
-       hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893)
-               # FIXME: really hppa2.0-hp
-               cpu=hppa1.1
-               vendor=hp
-               ;;
-       hp9k8[0-9][13679] | hp8[0-9][13679])
-               cpu=hppa1.1
-               vendor=hp
-               ;;
-       hp9k8[0-9][0-9] | hp8[0-9][0-9])
-               cpu=hppa1.0
-               vendor=hp
-               ;;
-       i*86v32)
-               cpu=$(echo "$1" | sed -e 's/86.*/86/')
-               vendor=pc
-               basic_os=sysv32
-               ;;
-       i*86v4*)
-               cpu=$(echo "$1" | sed -e 's/86.*/86/')
-               vendor=pc
-               basic_os=sysv4
-               ;;
-       i*86v)
-               cpu=$(echo "$1" | sed -e 's/86.*/86/')
-               vendor=pc
-               basic_os=sysv
-               ;;
-       i*86sol2)
-               cpu=$(echo "$1" | sed -e 's/86.*/86/')
-               vendor=pc
-               basic_os=solaris2
-               ;;
-       j90 | j90-cray)
-               cpu=j90
-               vendor=cray
-               basic_os=${basic_os:-unicos}
-               ;;
-       iris | iris4d)
-               cpu=mips
-               vendor=sgi
-               case $basic_os in
-                   irix*)
-                       ;;
-                   *)
-                       basic_os=irix4
-                       ;;
-               esac
-               ;;
-       miniframe)
-               cpu=m68000
-               vendor=convergent
-               ;;
-       *mint | mint[0-9]* | *MiNT | *MiNT[0-9]*)
-               cpu=m68k
-               vendor=atari
-               basic_os=mint
-               ;;
-       news-3600 | risc-news)
-               cpu=mips
-               vendor=sony
-               basic_os=newsos
-               ;;
-       next | m*-next)
-               cpu=m68k
-               vendor=next
-               case $basic_os in
-                   openstep*)
-                       ;;
-                   nextstep*)
-                       ;;
-                   ns2*)
-                     basic_os=nextstep2
-                       ;;
-                   *)
-                     basic_os=nextstep3
-                       ;;
-               esac
-               ;;
-       np1)
-               cpu=np1
-               vendor=gould
-               ;;
-       op50n-* | op60c-*)
-               cpu=hppa1.1
-               vendor=oki
-               basic_os=proelf
-               ;;
-       pa-hitachi)
-               cpu=hppa1.1
-               vendor=hitachi
-               basic_os=hiuxwe2
-               ;;
-       pbd)
-               cpu=sparc
-               vendor=tti
-               ;;
-       pbb)
-               cpu=m68k
-               vendor=tti
-               ;;
-       pc532)
-               cpu=ns32k
-               vendor=pc532
-               ;;
-       pn)
-               cpu=pn
-               vendor=gould
-               ;;
-       power)
-               cpu=power
-               vendor=ibm
-               ;;
-       ps2)
-               cpu=i386
-               vendor=ibm
-               ;;
-       rm[46]00)
-               cpu=mips
-               vendor=siemens
-               ;;
-       rtpc | rtpc-*)
-               cpu=romp
-               vendor=ibm
-               ;;
-       sde)
-               cpu=mipsisa32
-               vendor=sde
-               basic_os=${basic_os:-elf}
-               ;;
-       simso-wrs)
-               cpu=sparclite
-               vendor=wrs
-               basic_os=vxworks
-               ;;
-       tower | tower-32)
-               cpu=m68k
-               vendor=ncr
-               ;;
-       vpp*|vx|vx-*)
-               cpu=f301
-               vendor=fujitsu
-               ;;
-       w65)
-               cpu=w65
-               vendor=wdc
-               ;;
-       w89k-*)
-               cpu=hppa1.1
-               vendor=winbond
-               basic_os=proelf
-               ;;
-       none)
-               cpu=none
-               vendor=none
-               ;;
-       leon|leon[3-9])
-               cpu=sparc
-               vendor=$basic_machine
-               ;;
-       leon-*|leon[3-9]-*)
-               cpu=sparc
-               vendor=$(echo "$basic_machine" | sed 's/-.*//')
-               ;;
-
-       *-*)
-               # shellcheck disable=SC2162
-               IFS="-" read cpu vendor <<EOF
-$basic_machine
-EOF
-               ;;
-       # We use `pc' rather than `unknown'
-       # because (1) that's what they normally are, and
-       # (2) the word "unknown" tends to confuse beginning users.
-       i*86 | x86_64)
-               cpu=$basic_machine
-               vendor=pc
-               ;;
-       # These rules are duplicated from below for sake of the special case above;
-       # i.e. things that normalized to x86 arches should also default to "pc"
-       pc98)
-               cpu=i386
-               vendor=pc
-               ;;
-       x64 | amd64)
-               cpu=x86_64
-               vendor=pc
-               ;;
-       # Recognize the basic CPU types without company name.
-       *)
-               cpu=$basic_machine
-               vendor=unknown
-               ;;
-esac
-
-unset -v basic_machine
-
-# Decode basic machines in the full and proper CPU-Company form.
-case $cpu-$vendor in
-       # Here we handle the default manufacturer of certain CPU types in canonical form. It is in
-       # some cases the only manufacturer, in others, it is the most popular.
-       craynv-unknown)
-               vendor=cray
-               basic_os=${basic_os:-unicosmp}
-               ;;
-       c90-unknown | c90-cray)
-               vendor=cray
-               basic_os=${Basic_os:-unicos}
-               ;;
-       fx80-unknown)
-               vendor=alliant
-               ;;
-       romp-unknown)
-               vendor=ibm
-               ;;
-       mmix-unknown)
-               vendor=knuth
-               ;;
-       microblaze-unknown | microblazeel-unknown)
-               vendor=xilinx
-               ;;
-       rs6000-unknown)
-               vendor=ibm
-               ;;
-       vax-unknown)
-               vendor=dec
-               ;;
-       pdp11-unknown)
-               vendor=dec
-               ;;
-       we32k-unknown)
-               vendor=att
-               ;;
-       cydra-unknown)
-               vendor=cydrome
-               ;;
-       i370-ibm*)
-               vendor=ibm
-               ;;
-       orion-unknown)
-               vendor=highlevel
-               ;;
-       xps-unknown | xps100-unknown)
-               cpu=xps100
-               vendor=honeywell
-               ;;
-
-       # Here we normalize CPU types with a missing or matching vendor
-       dpx20-unknown | dpx20-bull)
-               cpu=rs6000
-               vendor=bull
-               basic_os=${basic_os:-bosx}
-               ;;
-
-       # Here we normalize CPU types irrespective of the vendor
-       amd64-*)
-               cpu=x86_64
-               ;;
-       blackfin-*)
-               cpu=bfin
-               basic_os=linux
-               ;;
-       c54x-*)
-               cpu=tic54x
-               ;;
-       c55x-*)
-               cpu=tic55x
-               ;;
-       c6x-*)
-               cpu=tic6x
-               ;;
-       e500v[12]-*)
-               cpu=powerpc
-               basic_os=${basic_os}"spe"
-               ;;
-       mips3*-*)
-               cpu=mips64
-               ;;
-       ms1-*)
-               cpu=mt
-               ;;
-       m68knommu-*)
-               cpu=m68k
-               basic_os=linux
-               ;;
-       m9s12z-* | m68hcs12z-* | hcs12z-* | s12z-*)
-               cpu=s12z
-               ;;
-       openrisc-*)
-               cpu=or32
-               ;;
-       parisc-*)
-               cpu=hppa
-               basic_os=linux
-               ;;
-       pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
-               cpu=i586
-               ;;
-       pentiumpro-* | p6-* | 6x86-* | athlon-* | athalon_*-*)
-               cpu=i686
-               ;;
-       pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
-               cpu=i686
-               ;;
-       pentium4-*)
-               cpu=i786
-               ;;
-       pc98-*)
-               cpu=i386
-               ;;
-       ppc-* | ppcbe-*)
-               cpu=powerpc
-               ;;
-       ppcle-* | powerpclittle-*)
-               cpu=powerpcle
-               ;;
-       ppc64-*)
-               cpu=powerpc64
-               ;;
-       ppc64le-* | powerpc64little-*)
-               cpu=powerpc64le
-               ;;
-       sb1-*)
-               cpu=mipsisa64sb1
-               ;;
-       sb1el-*)
-               cpu=mipsisa64sb1el
-               ;;
-       sh5e[lb]-*)
-               cpu=$(echo "$cpu" | sed 's/^\(sh.\)e\(.\)$/\1\2e/')
-               ;;
-       spur-*)
-               cpu=spur
-               ;;
-       strongarm-* | thumb-*)
-               cpu=arm
-               ;;
-       tx39-*)
-               cpu=mipstx39
-               ;;
-       tx39el-*)
-               cpu=mipstx39el
-               ;;
-       x64-*)
-               cpu=x86_64
-               ;;
-       xscale-* | xscalee[bl]-*)
-               cpu=$(echo "$cpu" | sed 's/^xscale/arm/')
-               ;;
-       arm64-*)
-               cpu=aarch64
-               ;;
-
-       # Recognize the canonical CPU Types that limit and/or modify the
-       # company names they are paired with.
-       cr16-*)
-               basic_os=${basic_os:-elf}
-               ;;
-       crisv32-* | etraxfs*-*)
-               cpu=crisv32
-               vendor=axis
-               ;;
-       cris-* | etrax*-*)
-               cpu=cris
-               vendor=axis
-               ;;
-       crx-*)
-               basic_os=${basic_os:-elf}
-               ;;
-       neo-tandem)
-               cpu=neo
-               vendor=tandem
-               ;;
-       nse-tandem)
-               cpu=nse
-               vendor=tandem
-               ;;
-       nsr-tandem)
-               cpu=nsr
-               vendor=tandem
-               ;;
-       nsv-tandem)
-               cpu=nsv
-               vendor=tandem
-               ;;
-       nsx-tandem)
-               cpu=nsx
-               vendor=tandem
-               ;;
-       mipsallegrexel-sony)
-               cpu=mipsallegrexel
-               vendor=sony
-               ;;
-       tile*-*)
-               basic_os=${basic_os:-linux-gnu}
-               ;;
-
-       *)
-               # Recognize the canonical CPU types that are allowed with any
-               # company name.
-               case $cpu in
-                       1750a | 580 \
-                       | a29k \
-                       | aarch64 | aarch64_be \
-                       | abacus \
-                       | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] \
-                       | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] \
-                       | alphapca5[67] | alpha64pca5[67] \
-                       | am33_2.0 \
-                       | amdgcn \
-                       | arc | arceb \
-                       | arm | arm[lb]e | arme[lb] | armv* \
-                       | avr | avr32 \
-                       | asmjs \
-                       | ba \
-                       | be32 | be64 \
-                       | bfin | bpf | bs2000 \
-                       | c[123]* | c30 | [cjt]90 | c4x \
-                       | c8051 | clipper | craynv | csky | cydra \
-                       | d10v | d30v | dlx | dsp16xx \
-                       | e2k | elxsi | epiphany \
-                       | f30[01] | f700 | fido | fr30 | frv | ft32 | fx80 \
-                       | h8300 | h8500 \
-                       | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
-                       | hexagon \
-                       | i370 | i*86 | i860 | i960 | ia16 | ia64 \
-                       | ip2k | iq2000 \
-                       | k1om \
-                       | le32 | le64 \
-                       | lm32 \
-                       | loongarch32 | loongarch64 | loongarchx32 \
-                       | m32c | m32r | m32rle \
-                       | m5200 | m68000 | m680[012346]0 | m68360 | m683?2 | m68k \
-                       | m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \
-                       | m88110 | m88k | maxq | mb | mcore | mep | metag \
-                       | microblaze | microblazeel \
-                       | mips | mipsbe | mipseb | mipsel | mipsle \
-                       | mips16 \
-                       | mips64 | mips64eb | mips64el \
-                       | mips64octeon | mips64octeonel \
-                       | mips64orion | mips64orionel \
-                       | mips64r5900 | mips64r5900el \
-                       | mips64vr | mips64vrel \
-                       | mips64vr4100 | mips64vr4100el \
-                       | mips64vr4300 | mips64vr4300el \
-                       | mips64vr5000 | mips64vr5000el \
-                       | mips64vr5900 | mips64vr5900el \
-                       | mipsisa32 | mipsisa32el \
-                       | mipsisa32r2 | mipsisa32r2el \
-                       | mipsisa32r6 | mipsisa32r6el \
-                       | mipsisa64 | mipsisa64el \
-                       | mipsisa64r2 | mipsisa64r2el \
-                       | mipsisa64r6 | mipsisa64r6el \
-                       | mipsisa64sb1 | mipsisa64sb1el \
-                       | mipsisa64sr71k | mipsisa64sr71kel \
-                       | mipsr5900 | mipsr5900el \
-                       | mipstx39 | mipstx39el \
-                       | mmix \
-                       | mn10200 | mn10300 \
-                       | moxie \
-                       | mt \
-                       | msp430 \
-                       | nds32 | nds32le | nds32be \
-                       | nfp \
-                       | nios | nios2 | nios2eb | nios2el \
-                       | none | np1 | ns16k | ns32k | nvptx \
-                       | open8 \
-                       | or1k* \
-                       | or32 \
-                       | orion \
-                       | picochip \
-                       | pdp10 | pdp11 | pj | pjl | pn | power \
-                       | powerpc | powerpc64 | powerpc64le | powerpcle | powerpcspe \
-                       | pru \
-                       | pyramid \
-                       | riscv | riscv32 | riscv32be | riscv64 | riscv64be \
-                       | rl78 | romp | rs6000 | rx \
-                       | s390 | s390x \
-                       | score \
-                       | sh | shl \
-                       | sh[1234] | sh[24]a | sh[24]ae[lb] | sh[23]e | she[lb] | sh[lb]e \
-                       | sh[1234]e[lb] |  sh[12345][lb]e | sh[23]ele | sh64 | sh64le \
-                       | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet \
-                       | sparclite \
-                       | sparcv8 | sparcv9 | sparcv9b | sparcv9v | sv1 | sx* \
-                       | spu \
-                       | tahoe \
-                       | thumbv7* \
-                       | tic30 | tic4x | tic54x | tic55x | tic6x | tic80 \
-                       | tron \
-                       | ubicom32 \
-                       | v70 | v850 | v850e | v850e1 | v850es | v850e2 | v850e2v3 \
-                       | vax \
-                       | visium \
-                       | w65 \
-                       | wasm32 | wasm64 \
-                       | we32k \
-                       | x86 | x86_64 | xc16x | xgate | xps100 \
-                       | xstormy16 | xtensa* \
-                       | ymp \
-                       | z8k | z80)
-                               ;;
-
-                       *)
-                               echo Invalid configuration \`"$1"\': machine \`"$cpu-$vendor"\' not recognized 1>&2
-                               exit 1
-                               ;;
-               esac
-               ;;
-esac
-
-# Here we canonicalize certain aliases for manufacturers.
-case $vendor in
-       digital*)
-               vendor=dec
-               ;;
-       commodore*)
-               vendor=cbm
-               ;;
-       *)
-               ;;
-esac
-
-# Decode manufacturer-specific aliases for certain operating systems.
-
-if test x$basic_os != x
-then
-
-# First recognize some ad-hoc caes, or perhaps split kernel-os, or else just
-# set os.
-case $basic_os in
-       gnu/linux*)
-               kernel=linux
-               os=$(echo $basic_os | sed -e 's|gnu/linux|gnu|')
-               ;;
-       os2-emx)
-               kernel=os2
-               os=$(echo $basic_os | sed -e 's|os2-emx|emx|')
-               ;;
-       nto-qnx*)
-               kernel=nto
-               os=$(echo $basic_os | sed -e 's|nto-qnx|qnx|')
-               ;;
-       *-*)
-               # shellcheck disable=SC2162
-               IFS="-" read kernel os <<EOF
-$basic_os
-EOF
-               ;;
-       # Default OS when just kernel was specified
-       nto*)
-               kernel=nto
-               os=$(echo $basic_os | sed -e 's|nto|qnx|')
-               ;;
-       linux*)
-               kernel=linux
-               os=$(echo $basic_os | sed -e 's|linux|gnu|')
-               ;;
-       *)
-               kernel=
-               os=$basic_os
-               ;;
-esac
-
-# Now, normalize the OS (knowing we just have one component, it's not a kernel,
-# etc.)
-case $os in
-       # First match some system type aliases that might get confused
-       # with valid system types.
-       # solaris* is a basic system type, with this one exception.
-       auroraux)
-               os=auroraux
-               ;;
-       bluegene*)
-               os=cnk
-               ;;
-       solaris1 | solaris1.*)
-               os=$(echo $os | sed -e 's|solaris1|sunos4|')
-               ;;
-       solaris)
-               os=solaris2
-               ;;
-       unixware*)
-               os=sysv4.2uw
-               ;;
-       # es1800 is here to avoid being matched by es* (a different OS)
-       es1800*)
-               os=ose
-               ;;
-       # Some version numbers need modification
-       chorusos*)
-               os=chorusos
-               ;;
-       isc)
-               os=isc2.2
-               ;;
-       sco6)
-               os=sco5v6
-               ;;
-       sco5)
-               os=sco3.2v5
-               ;;
-       sco4)
-               os=sco3.2v4
-               ;;
-       sco3.2.[4-9]*)
-               os=$(echo $os | sed -e 's/sco3.2./sco3.2v/')
-               ;;
-       sco*v* | scout)
-               # Don't match below
-               ;;
-       sco*)
-               os=sco3.2v2
-               ;;
-       psos*)
-               os=psos
-               ;;
-       qnx*)
-               os=qnx
-               ;;
-       hiux*)
-               os=hiuxwe2
-               ;;
-       lynx*178)
-               os=lynxos178
-               ;;
-       lynx*5)
-               os=lynxos5
-               ;;
-       lynxos*)
-               # don't get caught up in next wildcard
-               ;;
-       lynx*)
-               os=lynxos
-               ;;
-       mac[0-9]*)
-               os=$(echo "$os" | sed -e 's|mac|macos|')
-               ;;
-       opened*)
-               os=openedition
-               ;;
-       os400*)
-               os=os400
-               ;;
-       sunos5*)
-               os=$(echo "$os" | sed -e 's|sunos5|solaris2|')
-               ;;
-       sunos6*)
-               os=$(echo "$os" | sed -e 's|sunos6|solaris3|')
-               ;;
-       wince*)
-               os=wince
-               ;;
-       utek*)
-               os=bsd
-               ;;
-       dynix*)
-               os=bsd
-               ;;
-       acis*)
-               os=aos
-               ;;
-       atheos*)
-               os=atheos
-               ;;
-       syllable*)
-               os=syllable
-               ;;
-       386bsd)
-               os=bsd
-               ;;
-       ctix* | uts*)
-               os=sysv
-               ;;
-       nova*)
-               os=rtmk-nova
-               ;;
-       ns2)
-               os=nextstep2
-               ;;
-       # Preserve the version number of sinix5.
-       sinix5.*)
-               os=$(echo $os | sed -e 's|sinix|sysv|')
-               ;;
-       sinix*)
-               os=sysv4
-               ;;
-       tpf*)
-               os=tpf
-               ;;
-       triton*)
-               os=sysv3
-               ;;
-       oss*)
-               os=sysv3
-               ;;
-       svr4*)
-               os=sysv4
-               ;;
-       svr3)
-               os=sysv3
-               ;;
-       sysvr4)
-               os=sysv4
-               ;;
-       ose*)
-               os=ose
-               ;;
-       *mint | mint[0-9]* | *MiNT | MiNT[0-9]*)
-               os=mint
-               ;;
-       dicos*)
-               os=dicos
-               ;;
-       pikeos*)
-               # Until real need of OS specific support for
-               # particular features comes up, bare metal
-               # configurations are quite functional.
-               case $cpu in
-                   arm*)
-                       os=eabi
-                       ;;
-                   *)
-                       os=elf
-                       ;;
-               esac
-               ;;
-       *)
-               # No normalization, but not necessarily accepted, that comes below.
-               ;;
-esac
-
-else
-
-# Here we handle the default operating systems that come with various machines.
-# The value should be what the vendor currently ships out the door with their
-# machine or put another way, the most popular os provided with the machine.
-
-# Note that if you're going to try to match "-MANUFACTURER" here (say,
-# "-sun"), then you have to tell the case statement up towards the top
-# that MANUFACTURER isn't an operating system.  Otherwise, code above
-# will signal an error saying that MANUFACTURER isn't an operating
-# system, and we'll never get to this point.
-
-kernel=
-case $cpu-$vendor in
-       score-*)
-               os=elf
-               ;;
-       spu-*)
-               os=elf
-               ;;
-       *-acorn)
-               os=riscix1.2
-               ;;
-       arm*-rebel)
-               kernel=linux
-               os=gnu
-               ;;
-       arm*-semi)
-               os=aout
-               ;;
-       c4x-* | tic4x-*)
-               os=coff
-               ;;
-       c8051-*)
-               os=elf
-               ;;
-       clipper-intergraph)
-               os=clix
-               ;;
-       hexagon-*)
-               os=elf
-               ;;
-       tic54x-*)
-               os=coff
-               ;;
-       tic55x-*)
-               os=coff
-               ;;
-       tic6x-*)
-               os=coff
-               ;;
-       # This must come before the *-dec entry.
-       pdp10-*)
-               os=tops20
-               ;;
-       pdp11-*)
-               os=none
-               ;;
-       *-dec | vax-*)
-               os=ultrix4.2
-               ;;
-       m68*-apollo)
-               os=domain
-               ;;
-       i386-sun)
-               os=sunos4.0.2
-               ;;
-       m68000-sun)
-               os=sunos3
-               ;;
-       m68*-cisco)
-               os=aout
-               ;;
-       mep-*)
-               os=elf
-               ;;
-       mips*-cisco)
-               os=elf
-               ;;
-       mips*-*)
-               os=elf
-               ;;
-       or32-*)
-               os=coff
-               ;;
-       *-tti)  # must be before sparc entry or we get the wrong os.
-               os=sysv3
-               ;;
-       sparc-* | *-sun)
-               os=sunos4.1.1
-               ;;
-       pru-*)
-               os=elf
-               ;;
-       *-be)
-               os=beos
-               ;;
-       *-ibm)
-               os=aix
-               ;;
-       *-knuth)
-               os=mmixware
-               ;;
-       *-wec)
-               os=proelf
-               ;;
-       *-winbond)
-               os=proelf
-               ;;
-       *-oki)
-               os=proelf
-               ;;
-       *-hp)
-               os=hpux
-               ;;
-       *-hitachi)
-               os=hiux
-               ;;
-       i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent)
-               os=sysv
-               ;;
-       *-cbm)
-               os=amigaos
-               ;;
-       *-dg)
-               os=dgux
-               ;;
-       *-dolphin)
-               os=sysv3
-               ;;
-       m68k-ccur)
-               os=rtu
-               ;;
-       m88k-omron*)
-               os=luna
-               ;;
-       *-next)
-               os=nextstep
-               ;;
-       *-sequent)
-               os=ptx
-               ;;
-       *-crds)
-               os=unos
-               ;;
-       *-ns)
-               os=genix
-               ;;
-       i370-*)
-               os=mvs
-               ;;
-       *-gould)
-               os=sysv
-               ;;
-       *-highlevel)
-               os=bsd
-               ;;
-       *-encore)
-               os=bsd
-               ;;
-       *-sgi)
-               os=irix
-               ;;
-       *-siemens)
-               os=sysv4
-               ;;
-       *-masscomp)
-               os=rtu
-               ;;
-       f30[01]-fujitsu | f700-fujitsu)
-               os=uxpv
-               ;;
-       *-rom68k)
-               os=coff
-               ;;
-       *-*bug)
-               os=coff
-               ;;
-       *-apple)
-               os=macos
-               ;;
-       *-atari*)
-               os=mint
-               ;;
-       *-wrs)
-               os=vxworks
-               ;;
-       *)
-               os=none
-               ;;
-esac
-
-fi
-
-# Now, validate our (potentially fixed-up) OS.
-case $os in
-       # Sometimes we do "kernel-abi", so those need to count as OSes.
-       musl* | newlib* | uclibc*)
-               ;;
-       # Likewise for "kernel-libc"
-       eabi* | gnueabi*)
-               ;;
-       # Now accept the basic system types.
-       # The portable systems comes first.
-       # Each alternative MUST end in a * to match a version number.
-       gnu* | android* | bsd* | mach* | minix* | genix* | ultrix* | irix* \
-            | *vms* | esix* | aix* | cnk* | sunos | sunos[34]* \
-            | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \
-            | sym* |  plan9* | psp* | sim* | xray* | os68k* | v88r* \
-            | hiux* | abug | nacl* | netware* | windows* \
-            | os9* | macos* | osx* | ios* \
-            | mpw* | magic* | mmixware* | mon960* | lnews* \
-            | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \
-            | aos* | aros* | cloudabi* | sortix* | twizzler* \
-            | nindy* | vxsim* | vxworks* | ebmon* | hms* | mvs* \
-            | clix* | riscos* | uniplus* | iris* | isc* | rtu* | xenix* \
-            | mirbsd* | netbsd* | dicos* | openedition* | ose* \
-            | bitrig* | openbsd* | solidbsd* | libertybsd* | os108* \
-            | ekkobsd* | freebsd* | riscix* | lynxos* | os400* \
-            | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \
-            | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \
-            | udi* | lites* | ieee* | go32* | aux* | hcos* \
-            | chorusrdb* | cegcc* | glidix* \
-            | cygwin* | msys* | pe* | moss* | proelf* | rtems* \
-            | midipix* | mingw32* | mingw64* | mint* \
-            | uxpv* | beos* | mpeix* | udk* | moxiebox* \
-            | interix* | uwin* | mks* | rhapsody* | darwin* \
-            | openstep* | oskit* | conix* | pw32* | nonstopux* \
-            | storm-chaos* | tops10* | tenex* | tops20* | its* \
-            | os2* | vos* | palmos* | uclinux* | nucleus* | morphos* \
-            | scout* | superux* | sysv* | rtmk* | tpf* | windiss* \
-            | powermax* | dnix* | nx6 | nx7 | sei* | dragonfly* \
-            | skyos* | haiku* | rdos* | toppers* | drops* | es* \
-            | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \
-            | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \
-            | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx*)
-               ;;
-       # This one is extra strict with allowed versions
-       sco3.2v2 | sco3.2v[4-9]* | sco5v6*)
-               # Don't forget version if it is 3.2v4 or newer.
-               ;;
-       none)
-               ;;
-       *)
-               echo Invalid configuration \`"$1"\': OS \`"$os"\' not recognized 1>&2
-               exit 1
-               ;;
-esac
-
-# As a final step for OS-related things, validate the OS-kernel combination
-# (given a valid OS), if there is a kernel.
-case $kernel-$os in
-       linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* | linux-musl* | linux-uclibc* )
-               ;;
-       uclinux-uclibc* )
-               ;;
-       -dietlibc* | -newlib* | -musl* | -uclibc* )
-               # These are just libc implementations, not actual OSes, and thus
-               # require a kernel.
-               echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2
-               exit 1
-               ;;
-       kfreebsd*-gnu* | kopensolaris*-gnu*)
-               ;;
-       nto-qnx*)
-               ;;
-       os2-emx)
-               ;;
-       *-eabi* | *-gnueabi*)
-               ;;
-       -*)
-               # Blank kernel with real OS is always fine.
-               ;;
-       *-*)
-               echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2
-               exit 1
-               ;;
-esac
-
-# Here we handle the case where we know the os, and the CPU type, but not the
-# manufacturer.  We pick the logical manufacturer.
-case $vendor in
-       unknown)
-               case $cpu-$os in
-                       *-riscix*)
-                               vendor=acorn
-                               ;;
-                       *-sunos*)
-                               vendor=sun
-                               ;;
-                       *-cnk* | *-aix*)
-                               vendor=ibm
-                               ;;
-                       *-beos*)
-                               vendor=be
-                               ;;
-                       *-hpux*)
-                               vendor=hp
-                               ;;
-                       *-mpeix*)
-                               vendor=hp
-                               ;;
-                       *-hiux*)
-                               vendor=hitachi
-                               ;;
-                       *-unos*)
-                               vendor=crds
-                               ;;
-                       *-dgux*)
-                               vendor=dg
-                               ;;
-                       *-luna*)
-                               vendor=omron
-                               ;;
-                       *-genix*)
-                               vendor=ns
-                               ;;
-                       *-clix*)
-                               vendor=intergraph
-                               ;;
-                       *-mvs* | *-opened*)
-                               vendor=ibm
-                               ;;
-                       *-os400*)
-                               vendor=ibm
-                               ;;
-                       s390-* | s390x-*)
-                               vendor=ibm
-                               ;;
-                       *-ptx*)
-                               vendor=sequent
-                               ;;
-                       *-tpf*)
-                               vendor=ibm
-                               ;;
-                       *-vxsim* | *-vxworks* | *-windiss*)
-                               vendor=wrs
-                               ;;
-                       *-aux*)
-                               vendor=apple
-                               ;;
-                       *-hms*)
-                               vendor=hitachi
-                               ;;
-                       *-mpw* | *-macos*)
-                               vendor=apple
-                               ;;
-                       *-*mint | *-mint[0-9]* | *-*MiNT | *-MiNT[0-9]*)
-                               vendor=atari
-                               ;;
-                       *-vos*)
-                               vendor=stratus
-                               ;;
-               esac
-               ;;
-esac
-
-echo "$cpu-$vendor-${kernel:+$kernel-}$os"
-exit
-
-# Local variables:
-# eval: (add-hook 'before-save-hook 'time-stamp)
-# time-stamp-start: "timestamp='"
-# time-stamp-format: "%:y-%02m-%02d"
-# time-stamp-end: "'"
-# End:
diff --git a/citadel/configure b/citadel/configure
new file mode 100755 (executable)
index 0000000..4f2304d
--- /dev/null
@@ -0,0 +1,103 @@
+#!/bin/sh
+
+# CONFIGURE SCRIPT FOR CITADEL SERVER
+# This file is part of "conf-IG-ure"
+# Copyright (C) 2016-2022 by Art Cancro
+#
+# This program is open source software.  Use, duplication, and/or
+# disclosure are subject to the GNU General Purpose License version 3.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+echo 
+echo 
+echo Running the configure script to create config.mk
+echo
+
+# Parse the command line arguments
+for x in $*
+do
+       a=$1
+       k=`echo $a | awk -F= ' { print $1 } '`
+       v=`echo $a | awk -F= ' { print $2 } '`
+
+       case $k in
+               --prefix)
+                       PREFIX=$v
+               ;;
+               --bindir)
+                       BINDIR=$v
+               ;;
+               --ctdldir)
+                       CTDLDIR=$v
+               ;;
+               --with-ssl)
+                       SSL=yes
+               ;;
+               --without-ssl)
+                       SSL=no
+               ;;
+               *)
+                       echo $0 : unknown option $k
+                       echo
+                       echo Valid options are:
+                       echo '  --prefix=PREFIX         Install files in PREFIX [/usr/local]'
+                       echo '  --bindir=DIR            Install executables in DIR [PREFIX/bin]'
+                       echo '  --ctdldir=DIR           Look for Citadel server in DIR [/usr/local/citadel]'
+                       echo '  --with-ssl              Force build with OpenSSL support [normally autodetected]'
+                       echo '  --without-ssl           Force build without OpenSSL support [normally autodetected]'
+                       exit 1
+               ;;
+       esac
+       shift
+done
+
+# Set any missing values (FIXME remove the ultra-fatal -W options when finished converting from autotools)
+
+[ "$PREFIX" = "" ]     && PREFIX=/usr/local/citadel
+[ "$BINDIR" = "" ]     && BINDIR=${PREFIX}
+[ "$CTDLDIR" = "" ]    && CTDLDIR=${PREFIX}
+[ "$CFLAGS" = "" ]     && CFLAGS='-ggdb -Werror -Wfatal-errors -Wno-discarded-qualifiers'
+[ "$LDFLAGS" = "" ]    && LDFLAGS=''
+
+# Test for OpenSSL
+[ "$SSL" != "yes" ] && [ "$SSL" != "no" ] && {
+       echo Testing for OpenSSL...
+       tempfile=`tempfile 2>/dev/null` || tempfile=/tmp/configure.$$
+       tempcc=${tempfile}.c
+       cat >$tempcc <<!
+#include <openssl/ssl.h>
+int main(int argc, char **argv) {
+       SSL_load_error_strings();
+       exit(0);
+}
+!
+       SSL='no';
+       cc $tempcc -lssl -lcrypto -o $tempfile && $tempfile && SSL='yes'
+       rm -f $tempfile 2>/dev/null
+       rm -f $tempcc 2>/dev/null
+}
+echo SSL: $SSL
+[ "$SSL" = "yes" ] && {
+       CFLAGS=${CFLAGS}' -DHAVE_OPENSSL'
+       LDFLAGS=${LDFLAGS}' -lssl -lcrypto -lz'
+}
+
+# FIXME do a real build id here
+CFLAGS=${CFLAGS}' -DBUILD_ID=\"unknown\"'
+
+# Output the config.mk
+
+(
+       echo "CFLAGS := ${CFLAGS}"
+       echo "LDFLAGS := ${LDFLAGS}"
+       echo "PREFIX := ${PREFIX}"
+       echo "BINDIR := ${BINDIR}"
+       echo "CTDLDIR := ${CTDLDIR}"
+) >config.mk
+
+cat config.mk
+echo
diff --git a/citadel/context.c b/citadel/context.c
deleted file mode 100644 (file)
index bca317d..0000000
+++ /dev/null
@@ -1,686 +0,0 @@
-//
-// Citadel context management stuff.
-// Here's where we (hopefully) have all the code that manipulates contexts.
-//
-// Copyright (c) 1987-2020 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or disclosure
-// is subject to the terms of the GNU General Public License, version 3.
-// The program is distributed without any warranty, expressed or implied.
-
-#include "ctdl_module.h"
-#include "serv_extensions.h"
-#include "citserver.h"
-#include "user_ops.h"
-#include "locate_host.h"
-#include "context.h"
-#include "control.h"
-#include "config.h"
-
-pthread_key_t MyConKey;                                /* TSD key for MyContext() */
-CitContext masterCC;
-CitContext *ContextList = NULL;
-time_t last_purge = 0;                         /* Last dead session purge */
-int num_sessions = 0;                          /* Current number of sessions */
-int next_pid = 0;
-
-/* Flag for single user mode */
-static int want_single_user = 0;
-
-/* Try to go single user */
-
-int CtdlTrySingleUser(void) {
-       int can_do = 0;
-       
-       begin_critical_section(S_SINGLE_USER);
-       if (want_single_user)
-               can_do = 0;
-       else
-       {
-               can_do = 1;
-               want_single_user = 1;
-       }
-       end_critical_section(S_SINGLE_USER);
-       return can_do;
-}
-
-
-void CtdlEndSingleUser(void) {
-       begin_critical_section(S_SINGLE_USER);
-       want_single_user = 0;
-       end_critical_section(S_SINGLE_USER);
-}
-
-
-int CtdlWantSingleUser(void) {
-       return want_single_user;
-}
-
-
-int CtdlIsSingleUser(void) {
-       if (want_single_user) {
-               /* check for only one context here */
-               if (num_sessions == 1)
-                       return 1;
-       }
-       return 0;
-}
-
-
-/*
- * Locate a context by its session number and terminate it if the user is able.
- * User can NOT terminate their current session.
- * User CAN terminate any other session that has them logged in.
- * Aide CAN terminate any session except the current one.
- */
-int CtdlTerminateOtherSession (int session_num) {
-       int ret = 0;
-       CitContext *ccptr;
-       int aide;
-
-       if (session_num == CC->cs_pid) return TERM_NOTALLOWED;
-
-       aide = ( (CC->user.axlevel >= AxAideU) || (CC->internal_pgm) ) ;
-
-       syslog(LOG_DEBUG, "context: locating session to kill");
-       begin_critical_section(S_SESSION_TABLE);
-       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
-               if (session_num == ccptr->cs_pid) {
-                       ret |= TERM_FOUND;
-                       if ((ccptr->user.usernum == CC->user.usernum) || aide) {
-                               ret |= TERM_ALLOWED;
-                       }
-                       break;
-               }
-       }
-
-       if (((ret & TERM_FOUND) != 0) && ((ret & TERM_ALLOWED) != 0))
-       {
-               if (ccptr->user.usernum == CC->user.usernum)
-                       ccptr->kill_me = KILLME_ADMIN_TERMINATE;
-               else
-                       ccptr->kill_me = KILLME_IDLE;
-               end_critical_section(S_SESSION_TABLE);
-       }
-       else
-               end_critical_section(S_SESSION_TABLE);
-
-       return ret;
-}
-
-
-/*
- * Check to see if the user who we just sent mail to is logged in.  If yes,
- * bump the 'new mail' counter for their session.  That enables them to
- * receive a new mail notification without having to hit the database.
- */
-void BumpNewMailCounter(long which_user) {
-       CtdlBumpNewMailCounter(which_user);
-}
-
-
-void CtdlBumpNewMailCounter(long which_user) {
-       CitContext *ptr;
-
-       begin_critical_section(S_SESSION_TABLE);
-
-       for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
-               if (ptr->user.usernum == which_user) {
-                       ptr->newmail += 1;
-               }
-       }
-
-       end_critical_section(S_SESSION_TABLE);
-}
-
-
-/*
- * Check to see if a user is currently logged in
- * Take care with what you do as a result of this test.
- * The user may not have been logged in when this function was called BUT
- * because of threading the user might be logged in before you test the result.
- */
-int CtdlIsUserLoggedIn(char *user_name) {
-       CitContext *cptr;
-       int ret = 0;
-
-       begin_critical_section (S_SESSION_TABLE);
-       for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
-               if (!strcasecmp(cptr->user.fullname, user_name)) {
-                       ret = 1;
-                       break;
-               }
-       }
-       end_critical_section(S_SESSION_TABLE);
-       return ret;
-}
-
-
-/*
- * Check to see if a user is currently logged in.
- * Basically same as CtdlIsUserLoggedIn() but uses the user number instead.
- * Take care with what you do as a result of this test.
- * The user may not have been logged in when this function was called BUT
- * because of threading the user might be logged in before you test the result.
- */
-int CtdlIsUserLoggedInByNum (long usernum) {
-       CitContext *cptr;
-       int ret = 0;
-
-       begin_critical_section(S_SESSION_TABLE);
-       for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
-               if (cptr->user.usernum == usernum) {
-                       ret = 1;
-               }
-       }
-       end_critical_section(S_SESSION_TABLE);
-       return ret;
-}
-
-
-/*
- * Return a pointer to the CitContext structure bound to the thread which
- * called this function.  If there's no such binding (for example, if it's
- * called by the housekeeper thread) then a generic 'master' CC is returned.
- *
- * This function is used *VERY* frequently and must be kept small.
- */
-CitContext *MyContext(void) {
-       register CitContext *c;
-       return ((c = (CitContext *) pthread_getspecific(MyConKey), c == NULL) ? &masterCC : c);
-}
-
-
-/*
- * Terminate idle sessions.  This function pounds through the session table
- * comparing the current time to each session's time-of-last-command.  If an
- * idle session is found it is terminated, then the search restarts at the
- * beginning because the pointer to our place in the list becomes invalid.
- */
-void terminate_idle_sessions(void) {
-       CitContext *ccptr;
-       time_t now;
-       int killed = 0;
-       int longrunners = 0;
-
-       now = time(NULL);
-       begin_critical_section(S_SESSION_TABLE);
-       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
-               if (
-                       (ccptr != CC)
-                       && (CtdlGetConfigLong("c_sleeping") > 0)
-                       && (now - (ccptr->lastcmd) > CtdlGetConfigLong("c_sleeping"))
-               ) {
-                       if (!ccptr->dont_term) {
-                               ccptr->kill_me = KILLME_IDLE;
-                               ++killed;
-                       }
-                       else {
-                               ++longrunners;
-                       }
-               }
-       }
-       end_critical_section(S_SESSION_TABLE);
-       if (killed > 0) {
-               syslog(LOG_INFO, "context: scheduled %d idle sessions for termination", killed);
-       }
-       if (longrunners > 0) {
-               syslog(LOG_INFO, "context: did not terminate %d protected idle sessions", longrunners);
-       }
-}
-
-
-/*
- * During shutdown, close the sockets of any sessions still connected.
- */
-void terminate_all_sessions(void) {
-       CitContext *ccptr;
-       int killed = 0;
-
-       begin_critical_section(S_SESSION_TABLE);
-       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
-               if (ccptr->client_socket != -1)
-               {
-                       syslog(LOG_INFO, "context: terminate_all_sessions() is murdering %s CC[%d]", ccptr->curr_user, ccptr->cs_pid);
-                       close(ccptr->client_socket);
-                       ccptr->client_socket = -1;
-                       killed++;
-               }
-       }
-       end_critical_section(S_SESSION_TABLE);
-       if (killed > 0) {
-               syslog(LOG_INFO, "context: flushed %d stuck sessions", killed);
-       }
-}
-
-
-/*
- * Terminate a session.
- */
-void RemoveContext (CitContext *con) {
-       const char *c;
-       if (con == NULL) {
-               syslog(LOG_ERR, "context: RemoveContext() called with NULL, this should not happen");
-               return;
-       }
-       c = con->ServiceName;
-       if (c == NULL) {
-               c = "WTF?";
-       }
-       syslog(LOG_DEBUG, "context: RemoveContext(%s) session %d", c, con->cs_pid);
-
-       /* Run any cleanup routines registered by loadable modules.
-        * Note: We have to "become_session()" because the cleanup functions
-        *       might make references to "CC" assuming it's the right one.
-        */
-       become_session(con);
-       CtdlUserLogout();
-       PerformSessionHooks(EVT_STOP);
-       client_close();                         /* If the client is still connected, blow 'em away. */
-       become_session(NULL);
-
-       syslog(LOG_INFO, "context: [%3d]SRV[%s] Session ended.", con->cs_pid, c);
-
-       /* 
-        * If the client is still connected, blow 'em away. 
-        * if the socket is 0 or -1, its already gone or was never there.
-        */
-       if (con->client_socket > 0) {
-               syslog(LOG_INFO, "context: closing socket %d", con->client_socket);
-               close(con->client_socket);
-       }
-
-       /* If using AUTHMODE_LDAP, free the DN */
-       if (con->ldap_dn) {
-               free(con->ldap_dn);
-               con->ldap_dn = NULL;
-       }
-       FreeStrBuf(&con->StatusMessage);
-       FreeStrBuf(&con->MigrateBuf);
-       FreeStrBuf(&con->RecvBuf.Buf);
-       if (con->cached_msglist) {
-               free(con->cached_msglist);
-       }
-
-       syslog(LOG_DEBUG, "context: done with RemoveContext()");
-}
-
-
-/*
- * Initialize a new context and place it in the list.  The session number
- * used to be the PID (which is why it's called cs_pid), but that was when we
- * had one process per session.  Now we just assign them sequentially, starting
- * at 1 (don't change it to 0 because masterCC uses 0).
- */
-CitContext *CreateNewContext(void) {
-       CitContext *me;
-
-       me = (CitContext *) malloc(sizeof(CitContext));
-       if (me == NULL) {
-               syslog(LOG_ERR, "citserver: malloc() failed: %m");
-               return NULL;
-       }
-       memset(me, 0, sizeof(CitContext));
-
-       /* Give the context a name. Hopefully makes it easier to track */
-       strcpy (me->user.fullname, "SYS_notauth");
-       
-       /* The new context will be created already in the CON_EXECUTING state
-        * in order to prevent another thread from grabbing it while it's
-        * being set up.
-        */
-       me->state = CON_EXECUTING;
-       /*
-        * Generate a unique session number and insert this context into
-        * the list.
-        */
-       me->MigrateBuf = NewStrBuf();
-
-       me->RecvBuf.Buf = NewStrBuf();
-
-       me->lastcmd = time(NULL);       /* set lastcmd to now to prevent idle timer infanticide TODO: if we have a valid IO, use that to set the timer. */
-
-
-       begin_critical_section(S_SESSION_TABLE);
-       me->cs_pid = ++next_pid;
-       me->prev = NULL;
-       me->next = ContextList;
-       ContextList = me;
-       if (me->next != NULL) {
-               me->next->prev = me;
-       }
-       ++num_sessions;
-       end_critical_section(S_SESSION_TABLE);
-       return (me);
-}
-
-
-/*
- * Initialize a new context and place it in the list.  The session number
- * used to be the PID (which is why it's called cs_pid), but that was when we
- * had one process per session.  Now we just assign them sequentially, starting
- * at 1 (don't change it to 0 because masterCC uses 0).
- */
-CitContext *CloneContext(CitContext *CloneMe) {
-       CitContext *me;
-
-       me = (CitContext *) malloc(sizeof(CitContext));
-       if (me == NULL) {
-               syslog(LOG_ERR, "citserver: malloc() failed: %m");
-               return NULL;
-       }
-       memcpy(me, CloneMe, sizeof(CitContext));
-
-       memset(&me->RecvBuf, 0, sizeof(IOBuffer));
-       memset(&me->SendBuf, 0, sizeof(IOBuffer));
-       memset(&me->SBuf, 0, sizeof(IOBuffer));
-       me->MigrateBuf = NULL;
-       me->sMigrateBuf = NULL;
-       me->redirect_buffer = NULL;
-#ifdef HAVE_OPENSSL
-       me->ssl = NULL;
-#endif
-
-       me->download_fp = NULL;
-       me->upload_fp = NULL;
-       /// TODO: what about the room/user?
-       me->ma = NULL;
-       me->openid_data = NULL;
-       me->ldap_dn = NULL;
-       me->session_specific_data = NULL;
-       
-       me->CIT_ICAL = NULL;
-
-       me->cached_msglist = NULL;
-       me->download_fp = NULL;
-       me->upload_fp = NULL;
-       me->client_socket = 0;
-
-       me->MigrateBuf = NewStrBuf();
-       me->RecvBuf.Buf = NewStrBuf();
-       
-       begin_critical_section(S_SESSION_TABLE);
-       {
-               me->cs_pid = ++next_pid;
-               me->prev = NULL;
-               me->next = ContextList;
-               me->lastcmd = time(NULL);       /* set lastcmd to now to prevent idle timer infanticide */
-               ContextList = me;
-               if (me->next != NULL) {
-                       me->next->prev = me;
-               }
-               ++num_sessions;
-       }
-       end_critical_section(S_SESSION_TABLE);
-       return (me);
-}
-
-
-/*
- * Return an array containing a copy of the context list.
- * This allows worker threads to perform "for each context" operations without
- * having to lock and traverse the live list.
- */
-CitContext *CtdlGetContextArray(int *count) {
-       int nContexts, i;
-       CitContext *nptr, *cptr;
-       
-       nContexts = num_sessions;
-       nptr = malloc(sizeof(CitContext) * nContexts);
-       if (!nptr) {
-               *count = 0;
-               return NULL;
-       }
-       begin_critical_section(S_SESSION_TABLE);
-       for (cptr = ContextList, i=0; cptr != NULL && i < nContexts; cptr = cptr->next, i++) {
-               memcpy(&nptr[i], cptr, sizeof (CitContext));
-       }
-       end_critical_section (S_SESSION_TABLE);
-       
-       *count = i;
-       return nptr;
-}
-
-
-/*
- * Back-end function for starting a session
- */
-void begin_session(CitContext *con) {
-       /* 
-        * Initialize some variables specific to our context.
-        */
-       con->logged_in = 0;
-       con->internal_pgm = 0;
-       con->download_fp = NULL;
-       con->upload_fp = NULL;
-       con->cached_msglist = NULL;
-       con->cached_num_msgs = 0;
-       con->FirstExpressMessage = NULL;
-       time(&con->lastcmd);
-       time(&con->lastidle);
-       strcpy(con->lastcmdname, "    ");
-       strcpy(con->cs_clientname, "(unknown)");
-       strcpy(con->curr_user, NLI);
-       *con->cs_clientinfo = '\0';
-       safestrncpy(con->cs_host, CtdlGetConfigStr("c_fqdn"), sizeof con->cs_host);
-       safestrncpy(con->cs_addr, "", sizeof con->cs_addr);
-       con->cs_UDSclientUID = -1;
-       con->cs_host[sizeof con->cs_host - 1] = 0;
-       if (!CC->is_local_client) {
-               locate_host(con->cs_host, sizeof con->cs_host,
-                       con->cs_addr, sizeof con->cs_addr,
-                       con->client_socket
-               );
-       }
-       else {
-               con->cs_host[0] = 0;
-               con->cs_addr[0] = 0;
-#ifdef HAVE_STRUCT_UCRED
-               {
-                       /* as http://www.wsinnovations.com/softeng/articles/uds.html told us... */
-                       struct ucred credentials;
-                       socklen_t ucred_length = sizeof(struct ucred);
-                       
-                       /*fill in the user data structure */
-                       if(getsockopt(con->client_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &ucred_length)) {
-                               syslog(LOG_ERR, "context: could obtain credentials from unix domain socket");
-                               
-                       }
-                       else {          
-                               /* the process ID of the process on the other side of the socket */
-                               /* credentials.pid; */
-                               
-                               /* the effective UID of the process on the other side of the socket  */
-                               con->cs_UDSclientUID = credentials.uid;
-                               
-                               /* the effective primary GID of the process on the other side of the socket */
-                               /* credentials.gid; */
-                               
-                               /* To get supplemental groups, we will have to look them up in our account
-                                  database, after a reverse lookup on the UID to get the account name.
-                                  We can take this opportunity to check to see if this is a legit account.
-                               */
-                               snprintf(con->cs_clientinfo, sizeof(con->cs_clientinfo),
-                                        "PID: "F_PID_T"; UID: "F_UID_T"; GID: "F_XPID_T" ", 
-                                        credentials.pid,
-                                        credentials.uid,
-                                        credentials.gid);
-                       }
-               }
-#endif
-       }
-       con->cs_flags = 0;
-
-       con->nologin = 0;
-       if (((CtdlGetConfigInt("c_maxsessions") > 0)&&(num_sessions > CtdlGetConfigInt("c_maxsessions"))) || CtdlWantSingleUser()) {
-               con->nologin = 1;
-       }
-
-       syslog(LOG_INFO, "context: session (%s) started from %s (%s) uid=%d",
-               con->ServiceName, con->cs_host, con->cs_addr, con->cs_UDSclientUID
-       );
-
-       /* Run any session startup routines registered by loadable modules */
-       PerformSessionHooks(EVT_START);
-}
-
-
-/*
- * This function fills in a context and its user field correctly
- * Then creates/loads that user
- */
-void CtdlFillSystemContext(CitContext *context, char *name) {
-       char sysname[SIZ];
-       long len;
-
-       memset(context, 0, sizeof(CitContext));
-       context->internal_pgm = 1;
-       context->cs_pid = 0;
-       strcpy (sysname, "SYS_");
-       strcat (sysname, name);
-       len = strlen(sysname);
-       memcpy(context->curr_user, sysname, len + 1);
-       context->client_socket = (-1);
-       context->state = CON_SYS;
-       context->ServiceName = name;
-
-       /* internal_create_user has the side effect of loading the user regardless of whether they
-        * already existed or needed to be created
-        */
-       internal_create_user(sysname, &(context->user), -1) ;
-       
-       /* Check to see if the system user needs upgrading */
-       if (context->user.usernum == 0) {       /* old system user with number 0, upgrade it */
-               context->user.usernum = get_new_user_number();
-               syslog(LOG_INFO, "context: upgrading system user \"%s\" from user number 0 to user number %ld", context->user.fullname, context->user.usernum);
-               /* add user to the database */
-               CtdlPutUser(&(context->user));
-               cdb_store(CDB_USERSBYNUMBER, &(context->user.usernum), sizeof(long), context->user.fullname, strlen(context->user.fullname)+1);
-       }
-}
-
-
-/*
- * Cleanup any contexts that are left lying around
- */
-void context_cleanup(void) {
-       CitContext *ptr = NULL;
-       CitContext *rem = NULL;
-
-       /*
-        * Clean up the contexts.
-        * There are no threads so no critical_section stuff is needed.
-        */
-       ptr = ContextList;
-       
-       /* We need to update the ContextList because some modules may want to itterate it
-        * Question is should we NULL it before iterating here or should we just keep updating it
-        * as we remove items?
-        *
-        * Answer is to NULL it first to prevent modules from doing any actions on the list at all
-        */
-       ContextList=NULL;
-       while (ptr != NULL){
-               /* Remove the session from the active list */
-               rem = ptr->next;
-               --num_sessions;
-
-               syslog(LOG_DEBUG, "context: context_cleanup() purging session %d", ptr->cs_pid);
-               RemoveContext(ptr);
-               free (ptr);
-               ptr = rem;
-       }
-}
-
-
-/*
- * Purge all sessions which have the 'kill_me' flag set.
- * This function has code to prevent it from running more than once every
- * few seconds, because running it after every single unbind would waste a lot
- * of CPU time and keep the context list locked too much.  To force it to run
- * anyway, set "force" to nonzero.
- */
-void dead_session_purge(int force) {
-       CitContext *ptr, *ptr2;         /* general-purpose utility pointer */
-       CitContext *rem = NULL;         /* list of sessions to be destroyed */
-       
-       if (force == 0) {
-               if ( (time(NULL) - last_purge) < 5 ) {
-                       return; /* Too soon, go away */
-               }
-       }
-       time(&last_purge);
-
-       if (try_critical_section(S_SESSION_TABLE))
-               return;
-               
-       ptr = ContextList;
-       while (ptr) {
-               ptr2 = ptr;
-               ptr = ptr->next;
-               
-               if ( (ptr2->state == CON_IDLE) && (ptr2->kill_me) ) {
-                       /* Remove the session from the active list */
-                       if (ptr2->prev) {
-                               ptr2->prev->next = ptr2->next;
-                       }
-                       else {
-                               ContextList = ptr2->next;
-                       }
-                       if (ptr2->next) {
-                               ptr2->next->prev = ptr2->prev;
-                       }
-
-                       --num_sessions;
-                       /* And put it on our to-be-destroyed list */
-                       ptr2->next = rem;
-                       rem = ptr2;
-               }
-       }
-       end_critical_section(S_SESSION_TABLE);
-
-       /* Now that we no longer have the session list locked, we can take
-        * our time and destroy any sessions on the to-be-killed list, which
-        * is allocated privately on this thread's stack.
-        */
-       while (rem != NULL) {
-               syslog(LOG_DEBUG, "context: dead_session_purge() purging session %d, reason=%d", rem->cs_pid, rem->kill_me);
-               RemoveContext(rem);
-               ptr = rem;
-               rem = rem->next;
-               free(ptr);
-       }
-}
-
-
-/*
- * masterCC is the context we use when not attached to a session.  This
- * function initializes it.
- */
-void InitializeMasterCC(void) {
-       memset(&masterCC, 0, sizeof(struct CitContext));
-       masterCC.internal_pgm = 1;
-       masterCC.cs_pid = 0;
-}
-
-
-/*
- * Set the "async waiting" flag for a session, if applicable
- */
-void set_async_waiting(struct CitContext *ccptr) {
-       syslog(LOG_DEBUG, "context: setting async_waiting flag for session %d", ccptr->cs_pid);
-       if (ccptr->is_async) {
-               ccptr->async_waiting++;
-               if (ccptr->state == CON_IDLE) {
-                       ccptr->state = CON_READY;
-               }
-       }
-}
-
-
-CTDL_MODULE_INIT(session)
-{
-       if (!threading) {
-       }
-       return "session";
-}
diff --git a/citadel/context.h b/citadel/context.h
deleted file mode 100644 (file)
index e76d7ac..0000000
+++ /dev/null
@@ -1,179 +0,0 @@
-
-#ifndef CONTEXT_H
-#define CONTEXT_H
-
-#include <stdarg.h>
-#include "sysdep.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "threads.h"
-
-
-/*
- * Values for CitContext.state
- * 
- * A session that is doing nothing is in CON_IDLE state.  When activity
- * is detected on the socket, it goes to CON_READY, indicating that it
- * needs to have a worker thread bound to it.  When a thread binds to
- * the session, it goes to CON_EXECUTING and does its thing.  When the
- * transaction is finished, the thread sets it back to CON_IDLE and lets
- * it go.
- */
-typedef enum __CCState {
-       CON_IDLE,               /* This context is doing nothing */
-       CON_GREETING,           /* This context needs to output its greeting */
-       CON_STARTING,           /* This context is outputting its greeting */
-       CON_READY,              /* This context needs attention */
-       CON_EXECUTING,          /* This context is bound to a thread */
-       CON_SYS                 /* This is a system context and mustn't be purged */
-} CCState;
-
-#ifndef __CIT_CONTEXT__
-#define __CIT_CONTEXT__
-typedef struct CitContext CitContext;
-#endif
-
-/*
- * This structure keeps track of all information relating to a running 
- * session on the server.  We keep one of these for each session.
- */
-struct CitContext {
-       CitContext *prev;       /* Link to previous session in list */
-       CitContext *next;       /* Link to next session in the list */
-
-       int cs_pid;             /* session ID */
-       int dont_term;          /* for special activities like artv so we don't get killed */
-       double created;         /* time of birth */
-       time_t lastcmd;         /* time of last command executed */
-       time_t lastidle;        /* For computing idle time */
-       CCState state;          /* thread state (see CON_ values below) */
-       int kill_me;            /* Set to nonzero to flag for termination */
-
-       IOBuffer SendBuf,       /* Our write Buffer */
-               RecvBuf,        /* Our block buffered read buffer */
-               SBuf;           /* Our block buffered read buffer for clients */
-
-       StrBuf *MigrateBuf;     /* Our block buffered read buffer */
-       StrBuf *sMigrateBuf;    /* Our block buffered read buffer */
-
-       int client_socket;
-       int is_local_client;    /* set to 1 if client is running on the same host */
-       /* Redirect this session's output to a memory buffer? */
-       StrBuf *redirect_buffer;                /* the buffer */
-       StrBuf *StatusMessage;
-#ifdef HAVE_OPENSSL
-       SSL *ssl;
-       int redirect_ssl;
-#endif
-
-       char curr_user[USERNAME_SIZE];  /* name of current user */
-       int logged_in;          /* logged in? */
-       int internal_pgm;       /* authenticated as internal program? */
-       int nologin;            /* not allowed to log in */
-       int curr_view;          /* The view type for the current user/room */
-
-       time_t previous_login;  /* Date/time of previous login */
-       char lastcmdname[5];    /* name of last command executed */
-       unsigned cs_flags;      /* miscellaneous flags */
-       int is_async;           /* Nonzero if client accepts async msgs */
-       int async_waiting;      /* Nonzero if there are async msgs waiting */
-       int input_waiting;      /* Nonzero if there is client input waiting */
-       int can_receive_im;     /* Session is capable of receiving instant messages */
-
-       /* Client information */
-       int cs_clientdev;       /* client developer ID */
-       int cs_clienttyp;       /* client type code */
-       int cs_clientver;       /* client version number */
-       char cs_clientinfo[256];/* if its a unix domain socket, some info for logging. */
-       uid_t cs_UDSclientUID;  /* the uid of the client when talking via UDS */
-       char cs_clientname[32]; /* name of client software */
-       char cs_host[64];       /* host logged in from */
-       char cs_addr[64];       /* address logged in from */
-
-       /* The Internet type of thing */
-       char cs_principal_id[256];              /* User principal identity for XMPP, ActivityPub, etc. */
-       char cs_inet_email[128];                /* Return address of outbound Internet mail */
-       char cs_inet_other_emails[1024];        /* User's other valid Internet email addresses */
-       char cs_inet_fn[128];                   /* Friendly-name of outbound Internet mail */
-
-       FILE *download_fp;      /* Fields relating to file transfer */
-       size_t download_fp_total;
-       char download_desired_section[128];
-       FILE *upload_fp;
-       char upl_file[256];
-       char upl_path[PATH_MAX];
-       char upl_comment[256];
-       char upl_filedir[PATH_MAX];
-       char upl_mimetype[64];
-
-       struct ctdluser user;   /* Database record buffers */
-       struct ctdlroom room;
-
-       /* A linked list of all instant messages sent to us. */
-       struct ExpressMessage *FirstExpressMessage;
-       int disable_exp;        /* Set to 1 to disable incoming pages */
-       int newmail;            /* Other sessions increment this */
-
-       /* Preferred MIME formats */
-       char preferred_formats[256];
-       int msg4_dont_decode;
-
-       /* Dynamically allocated session data */
-       void *session_specific_data;            /* Used by individual protocol modules */
-       struct cit_ical *CIT_ICAL;              /* calendaring data */
-       struct ma_info *ma;                     /* multipart/alternative data */
-       const char *ServiceName;                /* readable purpose of this session */
-       long tcp_port;
-       void *openid_data;                      /* Data stored by the OpenID module */
-       char *ldap_dn;                          /* DN of user when using AUTHMODE_LDAP */
-
-       void (*h_command_function) (void) ;     /* service command function */
-       void (*h_async_function) (void) ;       /* do async msgs function */
-       void (*h_greeting_function) (void) ;    /* greeting function for session startup */
-
-       long *cached_msglist;                   /* results of the previous CtdlForEachMessage() */
-       int cached_num_msgs;
-
-       char vcard_updated_by_ldap;             /* !0 iff ldap changed the vcard, treat as aide update */
-};
-
-#define CC MyContext()
-
-extern pthread_key_t MyConKey;                 /* TSD key for MyContext() */
-extern int num_sessions;
-extern CitContext masterCC;
-extern CitContext *ContextList;
-
-CitContext *MyContext (void);
-void RemoveContext (struct CitContext *);
-CitContext *CreateNewContext (void);
-void context_cleanup(void);
-void kill_session (int session_to_kill);
-void InitializeMasterCC(void);
-void dead_session_purge(int force);
-void set_async_waiting(struct CitContext *ccptr);
-
-CitContext *CloneContext(CitContext *CloneMe);
-
-/* forcibly close and flush fd's on shutdown */
-void terminate_all_sessions(void);
-
-/* Deprecated, user CtdlBumpNewMailCounter() instead */
-void BumpNewMailCounter(long) __attribute__ ((deprecated));
-
-void terminate_idle_sessions(void);
-int CtdlTerminateOtherSession (int session_num);
-/* bits returned by CtdlTerminateOtherSession */
-#define TERM_FOUND     0x01
-#define TERM_ALLOWED   0x02
-#define TERM_KILLED    0x03
-#define TERM_NOTALLOWED -1
-
-/*
- * Bind a thread to a context.  (It's inline merely to speed things up.)
- */
-static INLINE void become_session(CitContext *which_con) {
-       pthread_setspecific(MyConKey, (void *)which_con );
-}
-
-#endif /* CONTEXT_H */
diff --git a/citadel/control.c b/citadel/control.c
deleted file mode 100644 (file)
index 6459042..0000000
+++ /dev/null
@@ -1,810 +0,0 @@
-//
-// This module handles states which are global to the entire server.
-//
-// Copyright (c) 1987-2021 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or disclosure
-// is subject to the terms of the GNU General Public License, version 3.
-// The program is distributed without any warranty, expressed or implied.
-
-#include <stdio.h>
-#include <sys/file.h>
-#include <libcitadel.h>
-
-#include "ctdl_module.h"
-#include "config.h"
-#include "citserver.h"
-#include "user_ops.h"
-
-long control_highest_user = 0;
-
-/*
- * This is the legacy "control record" format for the message base.  If found
- * on disk, its contents will be migrated into the system configuration.  Never
- * change this.
- */
-struct legacy_ctrl_format {
-       long MMhighest;                 /* highest message number in file   */
-       unsigned MMflags;               /* Global system flags              */
-       long MMnextuser;                /* highest user number on system    */
-       long MMnextroom;                /* highest room number on system    */
-       int MM_hosted_upgrade_level;    /* Server-hosted upgrade level      */
-       int MM_fulltext_wordbreaker;    /* ID of wordbreaker in use         */
-       long MMfulltext;                /* highest message number indexed   */
-       int MMdbversion;                /* Version of Berkeley DB used on previous server run */
-};
-
-
-/*
- * data that gets passed back and forth between control_find_highest() and its caller
- */
-struct cfh {
-       long highest_roomnum_found;
-       long highest_msgnum_found;
-};
-
-
-/*
- * Callback to get highest room number when rebuilding message base metadata
- *
- * sanity_diag_mode (can be set by -s flag at startup) may be:
- * 0 = attempt to fix inconsistencies
- * 1 = show inconsistencies but don't repair them, exit after complete
- * 2 = show inconsistencies but don't repair them, continue execution
- */
-void control_find_highest(struct ctdlroom *qrbuf, void *data) {
-       struct cfh *cfh = (struct cfh *)data;
-       struct cdbdata *cdbfr;
-       long *msglist;
-       int num_msgs=0;
-       int c;
-
-       if (qrbuf->QRnumber > cfh->highest_roomnum_found) {
-               cfh->highest_roomnum_found = qrbuf->QRnumber;
-       }
-
-       /* Load the message list */
-       cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
-       if (cdbfr != NULL) {
-               msglist = (long *) cdbfr->ptr;
-               num_msgs = cdbfr->len / sizeof(long);
-       }
-       else {
-               return; /* No messages at all?  No further action. */
-       }
-
-       if (num_msgs > 0) {
-               for (c=0; c<num_msgs; c++) {
-                       if (msglist[c] > cfh->highest_msgnum_found) {
-                               cfh->highest_msgnum_found = msglist[c];
-                       }
-               }
-       }
-
-       cdb_free(cdbfr);
-}
-
-
-/*
- * Callback to get highest user number.
- */
-void control_find_user(char *username, void *out_data) {
-       struct ctdluser EachUser;
-
-       if (CtdlGetUser(&EachUser, username) != 0) {
-               return;
-       }
-
-       if (EachUser.usernum > CtdlGetConfigLong("MMnextuser")) {
-               syslog(LOG_DEBUG, "control: fixing MMnextuser %ld > %ld , found in %s",
-                       EachUser.usernum, CtdlGetConfigLong("MMnextuser"), EachUser.fullname
-               );
-               if (!sanity_diag_mode) {
-                       CtdlSetConfigLong("MMnextuser", EachUser.usernum);
-               }
-       }
-}
-
-
-/*
- * If we have a legacy format control record on disk, import it.
- */
-void migrate_legacy_control_record(void) {
-       FILE *fp = NULL;
-       struct legacy_ctrl_format c;
-       memset(&c, 0, sizeof(c));
-
-       fp = fopen("citadel.control", "rb+");
-       if (fp != NULL) {
-               syslog(LOG_INFO, "control: legacy format record found -- importing to db");
-               fread(&c, sizeof(struct legacy_ctrl_format), 1, fp);
-               
-               CtdlSetConfigLong(      "MMhighest",                    c.MMhighest);
-               CtdlSetConfigInt(       "MMflags",                      c.MMflags);
-               CtdlSetConfigLong(      "MMnextuser",                   c.MMnextuser);
-               CtdlSetConfigLong(      "MMnextroom",                   c.MMnextroom);
-               CtdlSetConfigInt(       "MM_hosted_upgrade_level",      c.MM_hosted_upgrade_level);
-               CtdlSetConfigInt(       "MM_fulltext_wordbreaker",      c.MM_fulltext_wordbreaker);
-               CtdlSetConfigLong(      "MMfulltext",                   c.MMfulltext);
-
-               fclose(fp);
-               if (unlink("citadel.control") != 0) {
-                       fprintf(stderr, "Unable to remove legacy control record after migrating it.\n");
-                       fprintf(stderr, "Exiting to prevent data corruption.\n");
-                       exit(CTDLEXIT_CONFIG);
-               }
-       }
-}
-
-
-/*
- * check_control   -  check the control record has sensible values for message, user and room numbers
- */
-void check_control(void) {
-
-       syslog(LOG_INFO, "control: sanity checking the recorded highest message and room numbers");
-       struct cfh cfh;
-       memset(&cfh, 0, sizeof(struct cfh));
-       CtdlForEachRoom(control_find_highest, &cfh);
-
-       if (cfh.highest_roomnum_found > CtdlGetConfigLong("MMnextroom")) {
-               syslog(LOG_DEBUG, "control: fixing MMnextroom %ld > %ld", cfh.highest_roomnum_found, CtdlGetConfigLong("MMnextroom"));
-               if (!sanity_diag_mode) {
-                       CtdlSetConfigLong("MMnextroom", cfh.highest_roomnum_found);
-               }
-       }
-
-       if (cfh.highest_msgnum_found > CtdlGetConfigLong("MMhighest")) {
-               syslog(LOG_DEBUG, "control: fixing MMhighest %ld > %ld", cfh.highest_msgnum_found, CtdlGetConfigLong("MMhighest"));
-               if (!sanity_diag_mode) {
-                       CtdlSetConfigLong("MMhighest", cfh.highest_msgnum_found);
-               }
-       }
-
-       syslog(LOG_INFO, "control: sanity checking the recorded highest user number");
-       ForEachUser(control_find_user, NULL);
-
-       syslog(LOG_INFO, "control: sanity checks complete");
-
-       if (sanity_diag_mode == 1) {
-               syslog(LOG_INFO, "control: sanity check diagnostic mode is active - exiting now");
-               abort();
-       }
-}
-
-
-/*
- * get_new_message_number()  -  Obtain a new, unique ID to be used for a message.
- */
-long get_new_message_number(void)
-{
-       long retval = 0L;
-       begin_critical_section(S_CONTROL);
-       retval = CtdlGetConfigLong("MMhighest");
-       ++retval;
-       CtdlSetConfigLong("MMhighest", retval);
-       end_critical_section(S_CONTROL);
-       return(retval);
-}
-
-
-/*
- * CtdlGetCurrentMessageNumber()  -  Obtain the current highest message number in the system
- * This provides a quick way to initialise a variable that might be used to indicate
- * messages that should not be processed.   For example, an inbox rules script will use this
- * to record determine that messages older than this should not be processed.
- *
- * (Why is this function here?  Can't we just go straight to the config variable it fetches?)
- */
-long CtdlGetCurrentMessageNumber(void)
-{
-       return CtdlGetConfigLong("MMhighest");
-}
-
-
-/*
- * get_new_user_number()  -  Obtain a new, unique ID to be used for a user.
- */
-long get_new_user_number(void)
-{
-       long retval = 0L;
-       begin_critical_section(S_CONTROL);
-       retval = CtdlGetConfigLong("MMnextuser");
-       ++retval;
-       CtdlSetConfigLong("MMnextuser", retval);
-       end_critical_section(S_CONTROL);
-       return(retval);
-}
-
-
-/*
- * get_new_room_number()  -  Obtain a new, unique ID to be used for a room.
- */
-long get_new_room_number(void)
-{
-       long retval = 0L;
-       begin_critical_section(S_CONTROL);
-       retval = CtdlGetConfigLong("MMnextroom");
-       ++retval;
-       CtdlSetConfigLong("MMnextroom", retval);
-       end_critical_section(S_CONTROL);
-       return(retval);
-}
-
-
-/*
- * Helper function for cmd_conf() to handle boolean values
- */
-int confbool(char *v)
-{
-       if (IsEmptyStr(v)) return(0);
-       if (atoi(v) != 0) return(1);
-       return(0);
-}
-
-
-/* 
- * Get or set global configuration options
- */
-void cmd_conf(char *argbuf)
-{
-       char cmd[16];
-       char buf[1024];
-       int a, i;
-       long ii;
-       char *confptr;
-       char confname[128];
-
-       if (CtdlAccessCheck(ac_aide)) return;
-
-       extract_token(cmd, argbuf, 0, '|', sizeof cmd);
-
-       // CONF GET - retrieve system configuration in legacy format
-       // This is deprecated; please do not add fields or change their order.
-       if (!strcasecmp(cmd, "GET")) {
-               cprintf("%d Configuration...\n", LISTING_FOLLOWS);
-               cprintf("%s\n",         CtdlGetConfigStr("c_nodename"));
-               cprintf("%s\n",         CtdlGetConfigStr("c_fqdn"));
-               cprintf("%s\n",         CtdlGetConfigStr("c_humannode"));
-               cprintf("xxx\n"); /* placeholder -- field no longer in use */
-               cprintf("%d\n",         CtdlGetConfigInt("c_creataide"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_sleeping"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_initax"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_regiscall"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_twitdetect"));
-               cprintf("%s\n",         CtdlGetConfigStr("c_twitroom"));
-               cprintf("%s\n",         CtdlGetConfigStr("c_moreprompt"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_restrict"));
-               cprintf("%s\n",         CtdlGetConfigStr("c_site_location"));
-               cprintf("%s\n",         CtdlGetConfigStr("c_sysadm"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_maxsessions"));
-               cprintf("xxx\n"); /* placeholder -- field no longer in use */
-               cprintf("%d\n",         CtdlGetConfigInt("c_userpurge"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_roompurge"));
-               cprintf("%s\n",         CtdlGetConfigStr("c_logpages"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_createax"));
-               cprintf("%ld\n",        CtdlGetConfigLong("c_maxmsglen"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_min_workers"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_max_workers"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_pop3_port"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_smtp_port"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_rfc822_strict_from"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_aide_zap"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_imap_port"));
-               cprintf("%ld\n",        CtdlGetConfigLong("c_net_freq"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_disable_newu"));
-               cprintf("1\n"); /* niu */
-               cprintf("%d\n",         CtdlGetConfigInt("c_purge_hour"));
-               cprintf("%s\n",         CtdlGetConfigStr("c_ldap_host"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_ldap_port"));
-               cprintf("%s\n",         CtdlGetConfigStr("c_ldap_base_dn"));
-               cprintf("%s\n",         CtdlGetConfigStr("c_ldap_bind_dn"));
-               cprintf("%s\n",         CtdlGetConfigStr("c_ldap_bind_pw"));
-               cprintf("%s\n",         CtdlGetConfigStr("c_ip_addr"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_msa_port"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_imaps_port"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_pop3s_port"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_smtps_port"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_enable_fulltext"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_auto_cull"));
-               cprintf("1\n");
-               cprintf("%d\n",         CtdlGetConfigInt("c_allow_spoofing"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_journal_email"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_journal_pubmsgs"));
-               cprintf("%s\n",         CtdlGetConfigStr("c_journal_dest"));
-               cprintf("%s\n",         CtdlGetConfigStr("c_default_cal_zone"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_pftcpdict_port"));
-               cprintf("0\n");
-               cprintf("%d\n",         CtdlGetConfigInt("c_auth_mode"));
-               cprintf("\n");
-               cprintf("\n");
-               cprintf("\n");
-               cprintf("\n");
-               cprintf("%d\n",         CtdlGetConfigInt("c_rbl_at_greeting"));
-               cprintf("\n");
-               cprintf("\n");
-               cprintf("%s\n",         CtdlGetConfigStr("c_pager_program"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_imap_keep_from"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_xmpp_c2s_port"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_xmpp_s2s_port"));
-               cprintf("%ld\n",        CtdlGetConfigLong("c_pop3_fetch"));
-               cprintf("%ld\n",        CtdlGetConfigLong("c_pop3_fastest"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_spam_flag_only"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_guest_logins"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_port_number"));
-               cprintf("%d\n",         ctdluid);
-               cprintf("%d\n",         CtdlGetConfigInt("c_nntp_port"));
-               cprintf("%d\n",         CtdlGetConfigInt("c_nntps_port"));
-               cprintf("000\n");
-       }
-
-       // CONF SET - set system configuration in legacy format
-       // This is deprecated; please do not add fields or change their order.
-       else if (!strcasecmp(cmd, "SET")) {
-               unbuffer_output();
-               cprintf("%d Send configuration...\n", SEND_LISTING);
-               a = 0;
-               while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
-                       switch (a) {
-                       case 0:
-                               CtdlSetConfigStr("c_nodename", buf);
-                               break;
-                       case 1:
-                               CtdlSetConfigStr("c_fqdn", buf);
-                               break;
-                       case 2:
-                               CtdlSetConfigStr("c_humannode", buf);
-                               break;
-                       case 3:
-                               /* placeholder -- field no longer in use */
-                               break;
-                       case 4:
-                               CtdlSetConfigInt("c_creataide", confbool(buf));
-                               break;
-                       case 5:
-                               CtdlSetConfigInt("c_sleeping", atoi(buf));
-                               break;
-                       case 6:
-                               i = atoi(buf);
-                               if (i < 1) i = 1;
-                               if (i > 6) i = 6;
-                               CtdlSetConfigInt("c_initax", i);
-                               break;
-                       case 7:
-                               CtdlSetConfigInt("c_regiscall", confbool(buf));
-                               break;
-                       case 8:
-                               CtdlSetConfigInt("c_twitdetect", confbool(buf));
-                               break;
-                       case 9:
-                               CtdlSetConfigStr("c_twitroom", buf);
-                               break;
-                       case 10:
-                               CtdlSetConfigStr("c_moreprompt", buf);
-                               break;
-                       case 11:
-                               CtdlSetConfigInt("c_restrict", confbool(buf));
-                               break;
-                       case 12:
-                               CtdlSetConfigStr("c_site_location", buf);
-                               break;
-                       case 13:
-                               CtdlSetConfigStr("c_sysadm", buf);
-                               break;
-                       case 14:
-                               i = atoi(buf);
-                               if (i < 0) i = 0;
-                               CtdlSetConfigInt("c_maxsessions", i);
-                               break;
-                       case 15:
-                               /* placeholder -- field no longer in use */
-                               break;
-                       case 16:
-                               CtdlSetConfigInt("c_userpurge", atoi(buf));
-                               break;
-                       case 17:
-                               CtdlSetConfigInt("c_roompurge", atoi(buf));
-                               break;
-                       case 18:
-                               CtdlSetConfigStr("c_logpages", buf);
-                               break;
-                       case 19:
-                               i = atoi(buf);
-                               if (i < 1) i = 1;
-                               if (i > 6) i = 6;
-                               CtdlSetConfigInt("c_createax", i);
-                               break;
-                       case 20:
-                               ii = atol(buf);
-                               if (ii >= 8192) {
-                                       CtdlSetConfigLong("c_maxmsglen", ii);
-                               }
-                               break;
-                       case 21:
-                               i = atoi(buf);
-                               if (i >= 3) {                                   // minimum value
-                                       CtdlSetConfigInt("c_min_workers", i);
-                               }
-                               break;
-                       case 22:
-                               i = atoi(buf);
-                               if (i >= CtdlGetConfigInt("c_min_workers")) {   // max must be >= min
-                                       CtdlSetConfigInt("c_max_workers", i);
-                               }
-                               break;
-                       case 23:
-                               CtdlSetConfigInt("c_pop3_port", atoi(buf));
-                               break;
-                       case 24:
-                               CtdlSetConfigInt("c_smtp_port", atoi(buf));
-                               break;
-                       case 25:
-                               CtdlSetConfigInt("c_rfc822_strict_from", atoi(buf));
-                               break;
-                       case 26:
-                               CtdlSetConfigInt("c_aide_zap", confbool(buf));
-                               break;
-                       case 27:
-                               CtdlSetConfigInt("c_imap_port", atoi(buf));
-                               break;
-                       case 28:
-                               CtdlSetConfigLong("c_net_freq", atol(buf));
-                               break;
-                       case 29:
-                               CtdlSetConfigInt("c_disable_newu", confbool(buf));
-                               break;
-                       case 30:
-                               /* niu */
-                               break;
-                       case 31:
-                               i = atoi(buf);
-                               if ((i >= 0) && (i <= 23)) {
-                                       CtdlSetConfigInt("c_purge_hour", i);
-                               }
-                               break;
-                       case 32:
-                               CtdlSetConfigStr("c_ldap_host", buf);
-                               break;
-                       case 33:
-                               CtdlSetConfigInt("c_ldap_port", atoi(buf));
-                               break;
-                       case 34:
-                               CtdlSetConfigStr("c_ldap_base_dn", buf);
-                               break;
-                       case 35:
-                               CtdlSetConfigStr("c_ldap_bind_dn", buf);
-                               break;
-                       case 36:
-                               CtdlSetConfigStr("c_ldap_bind_pw", buf);
-                               break;
-                       case 37:
-                               CtdlSetConfigStr("c_ip_addr", buf);
-                               break;
-                       case 38:
-                               CtdlSetConfigInt("c_msa_port", atoi(buf));
-                               break;
-                       case 39:
-                               CtdlSetConfigInt("c_imaps_port", atoi(buf));
-                               break;
-                       case 40:
-                               CtdlSetConfigInt("c_pop3s_port", atoi(buf));
-                               break;
-                       case 41:
-                               CtdlSetConfigInt("c_smtps_port", atoi(buf));
-                               break;
-                       case 42:
-                               CtdlSetConfigInt("c_enable_fulltext", confbool(buf));
-                               break;
-                       case 43:
-                               CtdlSetConfigInt("c_auto_cull", confbool(buf));
-                               break;
-                       case 44:
-                               /* niu */
-                               break;
-                       case 45:
-                               CtdlSetConfigInt("c_allow_spoofing", confbool(buf));
-                               break;
-                       case 46:
-                               CtdlSetConfigInt("c_journal_email", confbool(buf));
-                               break;
-                       case 47:
-                               CtdlSetConfigInt("c_journal_pubmsgs", confbool(buf));
-                               break;
-                       case 48:
-                               CtdlSetConfigStr("c_journal_dest", buf);
-                               break;
-                       case 49:
-                               CtdlSetConfigStr("c_default_cal_zone", buf);
-                               break;
-                       case 50:
-                               CtdlSetConfigInt("c_pftcpdict_port", atoi(buf));
-                               break;
-                       case 51:
-                               /* niu */
-                               break;
-                       case 52:
-                               CtdlSetConfigInt("c_auth_mode", atoi(buf));
-                               break;
-                       case 53:
-                               /* niu */
-                               break;
-                       case 54:
-                               /* niu */
-                               break;
-                       case 55:
-                               /* niu */
-                               break;
-                       case 56:
-                               /* niu */
-                               break;
-                       case 57:
-                               CtdlSetConfigInt("c_rbl_at_greeting", confbool(buf));
-                               break;
-                       case 58:
-                               /* niu */
-                               break;
-                       case 59:
-                               /* niu */
-                               break;
-                       case 60:
-                               CtdlSetConfigStr("c_pager_program", buf);
-                               break;
-                       case 61:
-                               CtdlSetConfigInt("c_imap_keep_from", confbool(buf));
-                               break;
-                       case 62:
-                               CtdlSetConfigInt("c_xmpp_c2s_port", atoi(buf));
-                               break;
-                       case 63:
-                               CtdlSetConfigInt("c_xmpp_s2s_port", atoi(buf));
-                               break;
-                       case 64:
-                               CtdlSetConfigLong("c_pop3_fetch", atol(buf));
-                               break;
-                       case 65:
-                               CtdlSetConfigLong("c_pop3_fastest", atol(buf));
-                               break;
-                       case 66:
-                               CtdlSetConfigInt("c_spam_flag_only", confbool(buf));
-                               break;
-                       case 67:
-                               CtdlSetConfigInt("c_guest_logins", confbool(buf));
-                               break;
-                       case 68:
-                               CtdlSetConfigInt("c_port_number", atoi(buf));
-                               break;
-                       case 69:
-                               /* niu */
-                               break;
-                       case 70:
-                               CtdlSetConfigInt("c_nntp_port", atoi(buf));
-                               break;
-                       case 71:
-                               CtdlSetConfigInt("c_nntps_port", atoi(buf));
-                               break;
-                       }
-                       ++a;
-               }
-               snprintf(buf, sizeof buf,
-                       "The global system configuration has been edited by %s.\n",
-                        (CC->logged_in ? CC->curr_user : "an administrator")
-               );
-               CtdlAideMessage(buf, "Citadel Configuration Manager Message");
-
-               if (!IsEmptyStr(CtdlGetConfigStr("c_logpages")))
-                       CtdlCreateRoom(CtdlGetConfigStr("c_logpages"), 3, "", 0, 1, 1, VIEW_BBS);
-
-               /* If full text indexing has been disabled, invalidate the
-                * index so it doesn't try to use it later.
-                */
-               if (CtdlGetConfigInt("c_enable_fulltext") == 0) {
-                       CtdlSetConfigInt("MM_fulltext_wordbreaker", 0);
-               }
-       }
-
-       // CONF GETSYS - retrieve arbitrary system configuration stanzas stored in the message base
-       else if (!strcasecmp(cmd, "GETSYS")) {
-               extract_token(confname, argbuf, 1, '|', sizeof confname);
-               confptr = CtdlGetSysConfig(confname);
-               if (confptr != NULL) {
-                       long len; 
-
-                       len = strlen(confptr);
-                       cprintf("%d %s\n", LISTING_FOLLOWS, confname);
-                       client_write(confptr, len);
-                       if ((len > 0) && (confptr[len - 1] != 10))
-                               client_write("\n", 1);
-                       cprintf("000\n");
-                       free(confptr);
-               } else {
-                       cprintf("%d No such configuration.\n",
-                               ERROR + ILLEGAL_VALUE);
-               }
-       }
-
-       // CONF PUTSYS - store arbitrary system configuration stanzas in the message base
-       else if (!strcasecmp(cmd, "PUTSYS")) {
-               extract_token(confname, argbuf, 1, '|', sizeof confname);
-               unbuffer_output();
-               cprintf("%d %s\n", SEND_LISTING, confname);
-               confptr = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
-               CtdlPutSysConfig(confname, confptr);
-               free(confptr);
-       }
-
-       // CONF GETVAL - retrieve configuration variables from the database
-       // CONF LOADVAL - same thing but can handle variables bigger than 1 KB (deprecated and probably safe to remove)
-       else if ( (!strcasecmp(cmd, "GETVAL")) || (!strcasecmp(cmd, "LOADVAL")) ) {
-               extract_token(confname, argbuf, 1, '|', sizeof confname);
-               char *v = CtdlGetConfigStr(confname);
-               if ( (v) && (!strcasecmp(cmd, "GETVAL")) ) {
-                       cprintf("%d %s|\n", CIT_OK, v);
-               }
-               else if ( (v) && (!strcasecmp(cmd, "LOADVAL")) ) {
-                       cprintf("%d %d\n", BINARY_FOLLOWS, (int)strlen(v));
-                       client_write(v, strlen(v));
-               }
-               else {
-                       cprintf("%d |\n", ERROR);
-               }
-       }
-
-       // CONF PUTVAL - store configuration variables in the database
-       else if (!strcasecmp(cmd, "PUTVAL")) {
-               if (num_tokens(argbuf, '|') < 3) {
-                       cprintf("%d name and value required\n", ERROR);
-               }
-               else {
-                       extract_token(confname, argbuf, 1, '|', sizeof confname);
-                       extract_token(buf, argbuf, 2, '|', sizeof buf);
-                       CtdlSetConfigStr(confname, buf);
-                       cprintf("%d setting '%s' to '%s'\n", CIT_OK, confname, buf);
-               }
-       }
-
-       // CONF STOREVAL - store configuration variables in the database bigger than 1 KB (deprecated and probably safe to remove)
-       else if (!strcasecmp(cmd, "STOREVAL")) {
-               if (num_tokens(argbuf, '|') < 3) {
-                       cprintf("%d name and length required\n", ERROR);
-               }
-               else {
-                       extract_token(confname, argbuf, 1, '|', sizeof confname);
-                       int bytes = extract_int(argbuf, 2);
-                       char *valbuf = malloc(bytes + 1);
-                       cprintf("%d %d\n", SEND_BINARY, bytes);
-                       client_read(valbuf, bytes);
-                       valbuf[bytes+1] = 0;
-                       CtdlSetConfigStr(confname, valbuf);
-                       free(valbuf);
-               }
-       }
-
-       // CONF LISTVAL - list configuration variables in the database and their values
-       else if (!strcasecmp(cmd, "LISTVAL")) {
-               struct cdbdata *cdbcfg;
-               int keylen = 0;
-               char *key = NULL;
-               char *value = NULL;
-       
-               cprintf("%d all configuration variables\n", LISTING_FOLLOWS);
-               cdb_rewind(CDB_CONFIG);
-               while (cdbcfg = cdb_next_item(CDB_CONFIG), cdbcfg != NULL) {
-                       if (cdbcfg->len < 1020) {
-                               keylen = strlen(cdbcfg->ptr);
-                               key = cdbcfg->ptr;
-                               value = cdbcfg->ptr + keylen + 1;
-                               cprintf("%s|%s\n", key, value);
-                       }
-                       cdb_free(cdbcfg);
-               }
-               cprintf("000\n");
-       }
-
-       else {
-               cprintf("%d Illegal option(s) specified.\n", ERROR + ILLEGAL_VALUE);
-       }
-}
-
-
-typedef struct __ConfType {
-       ConstStr Name;
-       long Type;
-}ConfType;
-
-ConfType CfgNames[] = {
-       { {HKEY("localhost") },    0},
-       { {HKEY("directory") },    0},
-       { {HKEY("smarthost") },    2},
-       { {HKEY("fallbackhost") }, 2},
-       { {HKEY("rbl") },          3},
-       { {HKEY("spamassassin") }, 3},
-       { {HKEY("masqdomain") },   1},
-       { {HKEY("clamav") },       3},
-       { {HKEY("notify") },       3},
-       { {NULL, 0}, 0}
-};
-
-HashList *CfgNameHash = NULL;
-void cmd_gvdn(char *argbuf)
-{
-       const ConfType *pCfg;
-       char *confptr;
-       long min = atol(argbuf);
-       const char *Pos = NULL;
-       const char *PPos = NULL;
-       const char *HKey;
-       long HKLen;
-       StrBuf *Line;
-       StrBuf *Config;
-       StrBuf *Cfg;
-       StrBuf *CfgToken;
-       HashList *List;
-       HashPos *It;
-       void *vptr;
-       
-       List = NewHash(1, NULL);
-       Cfg = NewStrBufPlain(CtdlGetConfigStr("c_fqdn"), -1);
-       Put(List, SKEY(Cfg), Cfg, HFreeStrBuf);
-       Cfg = NULL;
-
-       confptr = CtdlGetSysConfig(INTERNETCFG);
-       Config = NewStrBufPlain(confptr, -1);
-       free(confptr);
-
-       Line = NewStrBufPlain(NULL, StrLength(Config));
-       CfgToken = NewStrBufPlain(NULL, StrLength(Config));
-       while (StrBufSipLine(Line, Config, &Pos))
-       {
-               if (Cfg == NULL)
-                       Cfg = NewStrBufPlain(NULL, StrLength(Line));
-               PPos = NULL;
-               StrBufExtract_NextToken(Cfg, Line, &PPos, '|');
-               StrBufExtract_NextToken(CfgToken, Line, &PPos, '|');
-               if (GetHash(CfgNameHash, SKEY(CfgToken), &vptr) &&
-                   (vptr != NULL))
-               {
-                       pCfg = (ConfType *) vptr;
-                       if (pCfg->Type <= min)
-                       {
-                               Put(List, SKEY(Cfg), Cfg, HFreeStrBuf);
-                               Cfg = NULL;
-                       }
-               }
-       }
-
-       cprintf("%d Valid Domains\n", LISTING_FOLLOWS);
-       It = GetNewHashPos(List, 1);
-       while (GetNextHashPos(List, It, &HKLen, &HKey, &vptr))
-       {
-               cputbuf(vptr);
-               cprintf("\n");
-       }
-       cprintf("000\n");
-
-       DeleteHashPos(&It);
-       DeleteHash(&List);
-       FreeStrBuf(&Cfg);
-       FreeStrBuf(&Line);
-       FreeStrBuf(&CfgToken);
-       FreeStrBuf(&Config);
-}
-
-/*****************************************************************************/
-/*                      MODULE INITIALIZATION STUFF                          */
-/*****************************************************************************/
-
-CTDL_MODULE_INIT(control)
-{
-       if (!threading) {
-               int i;
-
-               CfgNameHash = NewHash(1, NULL);
-               for (i = 0; CfgNames[i].Name.Key != NULL; i++)
-                       Put(CfgNameHash, CKEY(CfgNames[i].Name), &CfgNames[i], reference_free_handler);
-
-               CtdlRegisterProtoHook(cmd_gvdn, "GVDN", "get valid domain names");
-               CtdlRegisterProtoHook(cmd_conf, "CONF", "get/set system configuration");
-       }
-       /* return our id for the Log */
-       return "control";
-}
diff --git a/citadel/control.h b/citadel/control.h
deleted file mode 100644 (file)
index 649d3fd..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (c) 1987-2015 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-void get_control (void);
-void put_control (void);
-void check_control(void);
-long int get_new_message_number (void);
-long int get_new_user_number (void);
-long int get_new_room_number (void);
-void migrate_legacy_control_record(void);
diff --git a/citadel/ctdl_module.h b/citadel/ctdl_module.h
deleted file mode 100644 (file)
index 0ddfc69..0000000
+++ /dev/null
@@ -1,326 +0,0 @@
-
-#ifndef CTDL_MODULE_H
-#define CTDL_MODULE_H
-
-#include "sysdep.h"
-
-#ifdef HAVE_GC
-#define GC_THREADS
-#define GC_REDIRECT_TO_LOCAL
-#include <gc/gc_local_alloc.h>
-#else
-#define GC_MALLOC malloc
-#define GC_MALLOC_ATOMIC malloc
-#define GC_FREE free
-#define GC_REALLOC realloc
-#endif
-
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <syslog.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#ifdef HAVE_STRINGS_H
-#include <strings.h>
-#endif
-#include <limits.h>
-
-#include <libcitadel.h>
-
-#include "server.h"
-#include "sysdep_decls.h"
-#include "msgbase.h"
-#include "threads.h"
-#include "citadel_dirs.h"
-#include "context.h"
-
-/*
- * define macros for module init stuff
- */
-#define CTDL_MODULE_INIT(module_name) char *ctdl_module_##module_name##_init (int threading)
-
-#define CTDL_INIT_CALL(module_name) ctdl_module_##module_name##_init (threading)
-
-#define CTDL_MODULE_UPGRADE(module_name) char *ctdl_module_##module_name##_upgrade (void)
-
-#define CTDL_UPGRADE_CALL(module_name) ctdl_module_##module_name##_upgrade ()
-
-#define CtdlAideMessage(TEXT, SUBJECT)         \
-       quickie_message(                        \
-               "Citadel",                      \
-               NULL,                           \
-               NULL,                           \
-               AIDEROOM,                       \
-               TEXT,                           \
-               FMT_CITADEL,                    \
-               SUBJECT) 
-
-/*
- * Hook functions available to modules.
- */
-/* Priorities for  */
-#define PRIO_QUEUE 500
-#define PRIO_AGGR 1000
-#define PRIO_SEND 1500
-#define PRIO_CLEANUP 2000
-/* Priorities for EVT_HOUSE */
-#define PRIO_HOUSE 3000
-/* Priorities for EVT_LOGIN */
-#define PRIO_CREATE 10000
-/* Priorities for EVT_LOGOUT */
-#define PRIO_LOGOUT 15000
-/* Priorities for EVT_LOGIN */
-#define PRIO_LOGIN 20000
-/* Priorities for EVT_START */
-#define PRIO_START 25000
-/* Priorities for EVT_STOP */
-#define PRIO_STOP 30000
-/* Priorities for EVT_ASYNC */
-#define PRIO_ASYNC 35000
-/* Priorities for EVT_SHUTDOWN */
-#define PRIO_SHUTDOWN 40000
-/* Priorities for EVT_UNSTEALTH */
-#define PRIO_UNSTEALTH 45000
-/* Priorities for EVT_STEALTH */
-#define PRIO_STEALTH 50000
-
-
-void CtdlRegisterSessionHook(void (*fcn_ptr)(void), int EventType, int Priority);
-void CtdlUnregisterSessionHook(void (*fcn_ptr)(void), int EventType);
-void CtdlShutdownServiceHooks(void);
-
-void CtdlRegisterUserHook(void (*fcn_ptr)(struct ctdluser *), int EventType);
-void CtdlUnregisterUserHook(void (*fcn_ptr)(struct ctdluser *), int EventType);
-
-void CtdlRegisterXmsgHook(int (*fcn_ptr)(char *, char *, char *, char *), int order);
-void CtdlUnregisterXmsgHook(int (*fcn_ptr)(char *, char *, char *, char *), int order);
-
-void CtdlRegisterMessageHook(int (*handler)(struct CtdlMessage *, struct recptypes *), int EventType);
-void CtdlUnregisterMessageHook(int (*handler)(struct CtdlMessage *, struct recptypes *), int EventType);
-
-void CtdlRegisterRoomHook(int (*fcn_ptr)(struct ctdlroom *) );
-void CtdlUnregisterRoomHook(int (*fnc_ptr)(struct ctdlroom *) );
-
-void CtdlRegisterDeleteHook(void (*handler)(char *, long) );
-void CtdlUnregisterDeleteHook(void (*handler)(char *, long) );
-
-void CtdlRegisterCleanupHook(void (*fcn_ptr)(void));
-void CtdlUnregisterCleanupHook(void (*fcn_ptr)(void));
-
-void CtdlRegisterEVCleanupHook(void (*fcn_ptr)(void));
-void CtdlUnregisterEVCleanupHook(void (*fcn_ptr)(void));
-
-void CtdlRegisterProtoHook(void (*handler)(char *), char *cmd, char *desc);
-
-void CtdlRegisterServiceHook(int tcp_port,
-                            char *sockpath,
-                            void (*h_greeting_function) (void),
-                            void (*h_command_function) (void),
-                            void (*h_async_function) (void),
-                            const char *ServiceName
-);
-void CtdlUnregisterServiceHook(int tcp_port,
-                       char *sockpath,
-                        void (*h_greeting_function) (void),
-                        void (*h_command_function) (void),
-                        void (*h_async_function) (void)
-);
-
-void CtdlRegisterFixedOutputHook(char *content_type, void (*output_function) (char *supplied_data, int len));
-void CtdlUnRegisterFixedOutputHook(char *content_type);
-void CtdlRegisterMaintenanceThread(char *name, void *(*thread_proc) (void *arg));
-void CtdlRegisterSearchFuncHook(void (*fcn_ptr)(int *, long **, const char *), char *name);
-
-/*
- * if you say a) (which may take a while)
- * don't forget to say b)
- */
-void CtdlDisableHouseKeeping(void);
-void CtdlEnableHouseKeeping(void);
-
-/* TODODRW: This needs to be changed into a hook type interface
- * for now we have this horrible hack
- */
-void CtdlModuleStartCryptoMsgs(char *ok_response, char *nosup_response, char *error_response);
-
-/* return the current context list as an array and do it in a safe manner
- * The returned data is a copy so only reading is useful
- * The number of contexts is returned in count.
- * Beware, this does not copy any of the data pointed to by the context.
- * This means that you can not rely on things like the redirect buffer being valid.
- * You must free the returned pointer when done.
- */
-struct CitContext *CtdlGetContextArray (int *count);
-void CtdlFillSystemContext(struct CitContext *context, char *name);
-int CtdlTrySingleUser(void);
-void CtdlEndSingleUser(void);
-int CtdlWantSingleUser(void);
-int CtdlIsSingleUser(void);
-
-
-int CtdlIsUserLoggedIn (char *user_name);
-int CtdlIsUserLoggedInByNum (long usernum);
-void CtdlBumpNewMailCounter(long which_user);
-
-
-/*
- * CtdlGetCurrentMessageNumber()  -  Obtain the current highest message number in the system
- * This provides a quick way to initialise a variable that might be used to indicate
- * messages that should not be processed.  For example, a new inbox script will use this
- * to record determine that messages older than this should not be processed.
- * This function is defined in control.c
- */
-long CtdlGetCurrentMessageNumber(void);
-
-
-
-/*
- * Expose various room operation functions from room_ops.c to the modules API
- */
-
-unsigned CtdlCreateRoom(char *new_room_name,
-                       int new_room_type,
-                       char *new_room_pass,
-                       int new_room_floor,
-                       int really_create,
-                       int avoid_access,
-                       int new_room_view);
-int CtdlGetRoom(struct ctdlroom *qrbuf, const char *room_name);
-int CtdlGetRoomLock(struct ctdlroom *qrbuf, const char *room_name);
-int CtdlDoIHavePermissionToDeleteThisRoom(struct ctdlroom *qr);
-void CtdlRoomAccess(struct ctdlroom *roombuf, struct ctdluser *userbuf, int *result, int *view);
-void CtdlPutRoomLock(struct ctdlroom *qrbuf);
-typedef void (*ForEachRoomCallBack)(struct ctdlroom *EachRoom, void *out_data);
-void CtdlForEachRoom(ForEachRoomCallBack CB, void *in_data);
-char *LoadRoomNetConfigFile(long roomnum);
-void SaveRoomNetConfigFile(long roomnum, const char *raw_netconfig);
-void CtdlDeleteRoom(struct ctdlroom *qrbuf);
-int CtdlRenameRoom(char *old_name, char *new_name, int new_floor);
-void CtdlUserGoto (char *where, int display_result, int transiently, int *msgs, int *new, long *oldest, long *newest);
-struct floor *CtdlGetCachedFloor(int floor_num);
-void CtdlScheduleRoomForDeletion(struct ctdlroom *qrbuf);
-void CtdlGetFloor (struct floor *flbuf, int floor_num);
-void CtdlPutFloor (struct floor *flbuf, int floor_num);
-void CtdlPutFloorLock(struct floor *flbuf, int floor_num);
-int CtdlGetFloorByName(const char *floor_name);
-int CtdlGetFloorByNameLock(const char *floor_name);
-int CtdlGetAvailableFloor(void);
-int CtdlIsNonEditable(struct ctdlroom *qrbuf);
-void CtdlPutRoom(struct ctdlroom *);
-
-/*
- * Possible return values for CtdlRenameRoom()
- */
-enum {
-       crr_ok,                         /* success */
-       crr_room_not_found,             /* room not found */
-       crr_already_exists,             /* new name already exists */
-       crr_noneditable,                /* cannot edit this room */
-       crr_invalid_floor,              /* target floor does not exist */
-       crr_access_denied               /* not allowed to edit this room */
-};
-
-
-
-/*
- * API declarations from citserver.h
- */
-int CtdlAccessCheck(int);
-/* 'required access level' values which may be passed to CtdlAccessCheck()
- */
-enum {
-       ac_none,
-       ac_logged_in_or_guest,
-       ac_logged_in,
-       ac_room_aide,
-       ac_aide,
-       ac_internal,
-};
-
-
-
-/*
- * API declarations from serv_extensions.h
- */
-void CtdlModuleDoSearch(int *num_msgs, long **search_msgs, const char *search_string, const char *func_name);
-
-#define NODENAME               CtdlGetConfigStr("c_nodename")
-#define FQDN                   CtdlGetConfigStr("c_fqdn")
-#define CTDLUID                        ctdluid
-#define CREATAIDE              CtdlGetConfigInt("c_creataide")
-#define REGISCALL              CtdlGetConfigInt("c_regiscall")
-#define TWITDETECT             CtdlGetConfigInt("c_twitdetect")
-#define TWITROOM               CtdlGetConfigStr("c_twitroom")
-#define RESTRICT_INTERNET      CtdlGetConfigInt("c_restrict")
-
-#define CtdlREGISTERRoomCfgType(a, p, uniq, nSegs, s, d) RegisterRoomCfgType(#a, sizeof(#a) - 1, a, p, uniq, nSegs, s, d);
-
-
-
-/*
- * Expose API calls from user_ops.c
- */
-int CtdlGetUser(struct ctdluser *usbuf, char *name);
-int CtdlGetUserLen(struct ctdluser *usbuf, const char *name, long len);
-int CtdlGetUserLock(struct ctdluser *usbuf, char *name);
-void CtdlPutUser(struct ctdluser *usbuf);
-void CtdlPutUserLock(struct ctdluser *usbuf);
-int CtdlLockGetCurrentUser(void);
-void CtdlPutCurrentUserLock(void);
-int CtdlGetUserByNumber(struct ctdluser *usbuf, long number);
-void CtdlGetRelationship(visit *vbuf, struct ctdluser *rel_user, struct ctdlroom *rel_room);
-void CtdlSetRelationship(visit *newvisit, struct ctdluser *rel_user, struct ctdlroom *rel_room);
-void CtdlMailboxName(char *buf, size_t n, const struct ctdluser *who, const char *prefix);
-int CtdlLoginExistingUser(const char *username);
-
-/*
- * Values which may be returned by CtdlLoginExistingUser()
- */
-enum {
-       pass_ok,
-       pass_already_logged_in,
-       pass_no_user,
-       pass_internal_error,
-       pass_wrong_password
-};
-
-int CtdlTryPassword(const char *password, long len);
-/*
- * Values which may be returned by CtdlTryPassword()
- */
-enum {
-       login_ok,
-       login_already_logged_in,
-       login_too_many_users,
-       login_not_found
-};
-
-void CtdlUserLogout(void);
-
-/*
- * Expose API calls from msgbase.c
- */
-
-
-/*
- * Expose API calls from euidindex.c
- */
-long CtdlLocateMessageByEuid(char *euid, struct ctdlroom *qrbuf);
-
-
-/*
- * Expose API calls from modules/openid/serv_openid_rp.c in order to turn it into a generic external authentication driver
- */
-int attach_extauth(struct ctdluser *who, StrBuf *claimed_id);
-
-#endif /* CTDL_MODULE_H */
diff --git a/citadel/database.c b/citadel/database.c
deleted file mode 100644 (file)
index 45b5d27..0000000
+++ /dev/null
@@ -1,812 +0,0 @@
-// This is a data store backend for the Citadel server which uses Berkeley DB.
-//
-// Copyright (c) 1987-2021 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-/*****************************************************************************
-       Tunable configuration parameters for the Berkeley DB back end
- *****************************************************************************/
-
-/* Citadel will checkpoint the db at the end of every session, but only if
- * the specified number of kilobytes has been written, or if the specified
- * number of minutes has passed, since the last checkpoint.
- */
-#define MAX_CHECKPOINT_KBYTES  256
-#define MAX_CHECKPOINT_MINUTES 15
-
-/*****************************************************************************/
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <stdio.h>
-#include <dirent.h>
-#include <zlib.h>
-#include <db.h>
-
-#if DB_VERSION_MAJOR < 5
-#error Citadel requires Berkeley DB v5.0 or newer.  Please upgrade.
-#endif
-
-#include <libcitadel.h>
-#include "ctdl_module.h"
-#include "control.h"
-#include "citserver.h"
-#include "config.h"
-
-static DB *dbp[MAXCDB];                /* One DB handle for each Citadel database */
-static DB_ENV *dbenv;          /* The DB environment (global) */
-
-
-void cdb_abort(void) {
-       syslog(LOG_DEBUG, "db: citserver is stopping in order to prevent data loss. uid=%d gid=%d euid=%d egid=%d",
-               getuid(), getgid(), geteuid(), getegid()
-       );
-       exit(CTDLEXIT_DB);
-}
-
-
-/* Verbose logging callback */
-void cdb_verbose_log(const DB_ENV * dbenv, const char *msg) {
-       if (!IsEmptyStr(msg)) {
-               syslog(LOG_DEBUG, "db: %s", msg);
-       }
-}
-
-
-/* Verbose logging callback */
-void cdb_verbose_err(const DB_ENV * dbenv, const char *errpfx, const char *msg) {
-       syslog(LOG_ERR, "db: %s", msg);
-}
-
-
-/* wrapper for txn_abort() that logs/aborts on error */
-static void txabort(DB_TXN *tid) {
-       int ret;
-
-       ret = tid->abort(tid);
-
-       if (ret) {
-               syslog(LOG_ERR, "db: txn_abort: %s", db_strerror(ret));
-               cdb_abort();
-       }
-}
-
-
-/* wrapper for txn_commit() that logs/aborts on error */
-static void txcommit(DB_TXN *tid) {
-       int ret;
-
-       ret = tid->commit(tid, 0);
-
-       if (ret) {
-               syslog(LOG_ERR, "db: txn_commit: %s", db_strerror(ret));
-               cdb_abort();
-       }
-}
-
-
-/* wrapper for txn_begin() that logs/aborts on error */
-static void txbegin(DB_TXN **tid) {
-       int ret;
-
-       ret = dbenv->txn_begin(dbenv, NULL, tid, 0);
-
-       if (ret) {
-               syslog(LOG_ERR, "db: txn_begin: %s", db_strerror(ret));
-               cdb_abort();
-       }
-}
-
-
-/* panic callback */
-static void dbpanic(DB_ENV * env, int errval) {
-       syslog(LOG_ERR, "db: PANIC: %s", db_strerror(errval));
-}
-
-
-static void cclose(DBC * cursor) {
-       int ret;
-
-       if ((ret = cursor->c_close(cursor))) {
-               syslog(LOG_ERR, "db: c_close: %s", db_strerror(ret));
-               cdb_abort();
-       }
-}
-
-
-static void bailIfCursor(DBC ** cursors, const char *msg) {
-       int i;
-
-       for (i = 0; i < MAXCDB; i++)
-               if (cursors[i] != NULL) {
-                       syslog(LOG_ERR, "db: cursor still in progress on cdb %02x: %s", i, msg);
-                       cdb_abort();
-               }
-}
-
-
-void cdb_check_handles(void) {
-       bailIfCursor(TSD->cursors, "in check_handles");
-
-       if (TSD->tid != NULL) {
-               syslog(LOG_ERR, "db: transaction still in progress!");
-               cdb_abort();
-       }
-}
-
-
-/*
- * Request a checkpoint of the database.  Called once per minute by the thread manager.
- */
-void cdb_checkpoint(void) {
-       int ret;
-
-       syslog(LOG_DEBUG, "db: -- checkpoint --");
-       ret = dbenv->txn_checkpoint(dbenv, MAX_CHECKPOINT_KBYTES, MAX_CHECKPOINT_MINUTES, 0);
-
-       if (ret != 0) {
-               syslog(LOG_ERR, "db: cdb_checkpoint() txn_checkpoint: %s", db_strerror(ret));
-               cdb_abort();
-       }
-
-       /* After a successful checkpoint, we can cull the unused logs */
-       if (CtdlGetConfigInt("c_auto_cull")) {
-               ret = dbenv->log_set_config(dbenv, DB_LOG_AUTO_REMOVE, 1);
-       }
-       else {
-               ret = dbenv->log_set_config(dbenv, DB_LOG_AUTO_REMOVE, 0);
-       }
-}
-
-
-/*
- * Open the various databases we'll be using.  Any database which
- * does not exist should be created.  Note that we don't need a
- * critical section here, because there aren't any active threads
- * manipulating the database yet.
- */
-void open_databases(void) {
-       int ret;
-       int i;
-       char dbfilename[32];
-       u_int32_t flags = 0;
-       int dbversion_major, dbversion_minor, dbversion_patch;
-
-       syslog(LOG_DEBUG, "db: open_databases() starting");
-       syslog(LOG_DEBUG, "db: Compiled libdb: %s", DB_VERSION_STRING);
-       syslog(LOG_DEBUG, "db:   Linked libdb: %s", db_version(&dbversion_major, &dbversion_minor, &dbversion_patch));
-       syslog(LOG_DEBUG, "db:    Linked zlib: %s", zlibVersion());
-
-       /*
-        * Silently try to create the database subdirectory.  If it's already there, no problem.
-        */
-       if ((mkdir(ctdl_db_dir, 0700) != 0) && (errno != EEXIST)) {
-               syslog(LOG_ERR, "db: unable to create database directory [%s]: %m", ctdl_db_dir);
-       }
-       if (chmod(ctdl_db_dir, 0700) != 0) {
-               syslog(LOG_ERR, "db: unable to set database directory permissions [%s]: %m", ctdl_db_dir);
-       }
-       if (chown(ctdl_db_dir, CTDLUID, (-1)) != 0) {
-               syslog(LOG_ERR, "db: unable to set the owner for [%s]: %m", ctdl_db_dir);
-       }
-       syslog(LOG_DEBUG, "db: Setting up DB environment");
-       // db_env_set_func_yield((int (*)(u_long,  u_long))sched_yield);
-       ret = db_env_create(&dbenv, 0);
-       if (ret) {
-               syslog(LOG_ERR, "db: db_env_create: %s", db_strerror(ret));
-               syslog(LOG_ERR, "db: exit code %d", ret);
-               exit(CTDLEXIT_DB);
-       }
-       dbenv->set_errpfx(dbenv, "citserver");
-       dbenv->set_paniccall(dbenv, dbpanic);
-       dbenv->set_errcall(dbenv, cdb_verbose_err);
-       dbenv->set_errpfx(dbenv, "ctdl");
-       dbenv->set_msgcall(dbenv, cdb_verbose_log);
-       dbenv->set_verbose(dbenv, DB_VERB_DEADLOCK, 1);
-       dbenv->set_verbose(dbenv, DB_VERB_RECOVERY, 1);
-
-       /*
-        * We want to specify the shared memory buffer pool cachesize,
-        * but everything else is the default.
-        */
-       ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0);
-       if (ret) {
-               syslog(LOG_ERR, "db: set_cachesize: %s", db_strerror(ret));
-               dbenv->close(dbenv, 0);
-               syslog(LOG_ERR, "db: exit code %d", ret);
-               exit(CTDLEXIT_DB);
-       }
-
-       if ((ret = dbenv->set_lk_detect(dbenv, DB_LOCK_DEFAULT))) {
-               syslog(LOG_ERR, "db: set_lk_detect: %s", db_strerror(ret));
-               dbenv->close(dbenv, 0);
-               syslog(LOG_ERR, "db: exit code %d", ret);
-               exit(CTDLEXIT_DB);
-       }
-
-       flags = DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_INIT_TXN | DB_INIT_LOCK | DB_THREAD | DB_INIT_LOG;
-       syslog(LOG_DEBUG, "db: dbenv->open(dbenv, %s, %d, 0)", ctdl_db_dir, flags);
-       ret = dbenv->open(dbenv, ctdl_db_dir, flags, 0);                                // try opening the database cleanly
-       if (ret == DB_RUNRECOVERY) {
-               syslog(LOG_ERR, "db: dbenv->open: %s", db_strerror(ret));
-               syslog(LOG_ERR, "db: attempting recovery...");
-               flags |= DB_RECOVER;
-               ret = dbenv->open(dbenv, ctdl_db_dir, flags, 0);                        // try recovery
-       }
-       if (ret == DB_RUNRECOVERY) {
-               syslog(LOG_ERR, "db: dbenv->open: %s", db_strerror(ret));
-               syslog(LOG_ERR, "db: attempting catastrophic recovery...");
-               flags &= ~DB_RECOVER;
-               flags |= DB_RECOVER_FATAL;
-               ret = dbenv->open(dbenv, ctdl_db_dir, flags, 0);                        // try catastrophic recovery
-       }
-       if (ret) {
-               syslog(LOG_ERR, "db: dbenv->open: %s", db_strerror(ret));
-               dbenv->close(dbenv, 0);
-               syslog(LOG_ERR, "db: exit code %d", ret);
-               exit(CTDLEXIT_DB);
-       }
-
-       syslog(LOG_INFO, "db: mounting databases");
-       for (i = 0; i < MAXCDB; ++i) {
-               ret = db_create(&dbp[i], dbenv, 0);                                     // Create a database handle
-               if (ret) {
-                       syslog(LOG_ERR, "db: db_create: %s", db_strerror(ret));
-                       syslog(LOG_ERR, "db: exit code %d", ret);
-                       exit(CTDLEXIT_DB);
-               }
-
-               snprintf(dbfilename, sizeof dbfilename, "cdb.%02x", i);                 // table names by number
-               ret = dbp[i]->open(dbp[i], NULL, dbfilename, NULL, DB_BTREE, DB_CREATE | DB_AUTO_COMMIT | DB_THREAD, 0600);
-               if (ret) {
-                       syslog(LOG_ERR, "db: db_open[%02x]: %s", i, db_strerror(ret));
-                       if (ret == ENOMEM) {
-                               syslog(LOG_ERR, "db: You may need to tune your database; please read http://www.citadel.org/doku.php?id=faq:troubleshooting:out_of_lock_entries for more information.");
-                       }
-                       syslog(LOG_ERR, "db: exit code %d", ret);
-                       exit(CTDLEXIT_DB);
-               }
-       }
-}
-
-
-/*
- * Make sure we own all the files, because in a few milliseconds we're going to drop root privs.
- */
-void cdb_chmod_data(void) {
-       DIR *dp;
-       struct dirent *d;
-       char filename[PATH_MAX];
-
-       dp = opendir(ctdl_db_dir);
-       if (dp != NULL) {
-               while (d = readdir(dp), d != NULL) {
-                       if (d->d_name[0] != '.') {
-                               snprintf(filename, sizeof filename, "%s/%s", ctdl_db_dir, d->d_name);
-                               syslog(LOG_DEBUG, "db: chmod(%s, 0600) returned %d", filename, chmod(filename, 0600));
-                               syslog(LOG_DEBUG, "db: chown(%s, CTDLUID, -1) returned %d",
-                                       filename, chown(filename, CTDLUID, (-1))
-                               );
-                       }
-               }
-               closedir(dp);
-       }
-}
-
-
-/*
- * Close all of the db database files we've opened.  This can be done
- * in a loop, since it's just a bunch of closes.
- */
-void close_databases(void) {
-       int i;
-       int ret;
-
-       syslog(LOG_INFO, "db: performing final checkpoint");
-       if ((ret = dbenv->txn_checkpoint(dbenv, 0, 0, 0))) {
-               syslog(LOG_ERR, "db: txn_checkpoint: %s", db_strerror(ret));
-       }
-
-       syslog(LOG_INFO, "db: flushing the database logs");
-       if ((ret = dbenv->log_flush(dbenv, NULL))) {
-               syslog(LOG_ERR, "db: log_flush: %s", db_strerror(ret));
-       }
-
-       /* close the tables */
-       syslog(LOG_INFO, "db: closing databases");
-       for (i = 0; i < MAXCDB; ++i) {
-               syslog(LOG_INFO, "db: closing database %02x", i);
-               ret = dbp[i]->close(dbp[i], 0);
-               if (ret) {
-                       syslog(LOG_ERR, "db: db_close: %s", db_strerror(ret));
-               }
-
-       }
-
-       // This seemed nifty at the time but did anyone really look at it?
-       // #ifdef DB_STAT_ALL
-       // /* print some statistics... */
-       // dbenv->lock_stat_print(dbenv, DB_STAT_ALL);
-       // #endif
-
-       /* Close the handle. */
-       ret = dbenv->close(dbenv, 0);
-       if (ret) {
-               syslog(LOG_ERR, "db: DBENV->close: %s", db_strerror(ret));
-       }
-}
-
-
-/*
- * Decompress a database item if it was compressed on disk
- */
-void cdb_decompress_if_necessary(struct cdbdata *cdb) {
-       static int magic = COMPRESS_MAGIC;
-
-       if ((cdb == NULL) || (cdb->ptr == NULL) || (cdb->len < sizeof(magic)) || (memcmp(cdb->ptr, &magic, sizeof(magic)))) {
-               return;
-       }
-
-       /* At this point we know we're looking at a compressed item. */
-
-       struct CtdlCompressHeader zheader;
-       char *uncompressed_data;
-       char *compressed_data;
-       uLongf destLen, sourceLen;
-       size_t cplen;
-
-       memset(&zheader, 0, sizeof(struct CtdlCompressHeader));
-       cplen = sizeof(struct CtdlCompressHeader);
-       if (sizeof(struct CtdlCompressHeader) > cdb->len) {
-               cplen = cdb->len;
-       }
-       memcpy(&zheader, cdb->ptr, cplen);
-
-       compressed_data = cdb->ptr;
-       compressed_data += sizeof(struct CtdlCompressHeader);
-
-       sourceLen = (uLongf) zheader.compressed_len;
-       destLen = (uLongf) zheader.uncompressed_len;
-       uncompressed_data = malloc(zheader.uncompressed_len);
-
-       if (uncompress((Bytef *) uncompressed_data,
-                      (uLongf *) & destLen, (const Bytef *) compressed_data, (uLong) sourceLen) != Z_OK) {
-               syslog(LOG_ERR, "db: uncompress() error");
-               cdb_abort();
-       }
-
-       free(cdb->ptr);
-       cdb->len = (size_t) destLen;
-       cdb->ptr = uncompressed_data;
-}
-
-
-/*
- * Store a piece of data.  Returns 0 if the operation was successful.  If a
- * key already exists it should be overwritten.
- */
-int cdb_store(int cdb, const void *ckey, int ckeylen, void *cdata, int cdatalen) {
-
-       DBT dkey, ddata;
-       DB_TXN *tid = NULL;
-       int ret = 0;
-       struct CtdlCompressHeader zheader;
-       char *compressed_data = NULL;
-       int compressing = 0;
-       size_t buffer_len = 0;
-       uLongf destLen = 0;
-
-       memset(&dkey, 0, sizeof(DBT));
-       memset(&ddata, 0, sizeof(DBT));
-       dkey.size = ckeylen;
-       dkey.data = (void *) ckey;
-       ddata.size = cdatalen;
-       ddata.data = cdata;
-
-       /* Only compress Visit and UseTable records.  Everything else is uncompressed. */
-       if ((cdb == CDB_VISIT) || (cdb == CDB_USETABLE)) {
-               compressing = 1;
-               zheader.magic = COMPRESS_MAGIC;
-               zheader.uncompressed_len = cdatalen;
-               buffer_len = ((cdatalen * 101) / 100) + 100 + sizeof(struct CtdlCompressHeader);
-               destLen = (uLongf) buffer_len;
-               compressed_data = malloc(buffer_len);
-               if (compress2((Bytef *) (compressed_data + sizeof(struct CtdlCompressHeader)),
-                             &destLen, (Bytef *) cdata, (uLongf) cdatalen, 1) != Z_OK) {
-                       syslog(LOG_ERR, "db: compress2() error");
-                       cdb_abort();
-               }
-               zheader.compressed_len = (size_t) destLen;
-               memcpy(compressed_data, &zheader, sizeof(struct CtdlCompressHeader));
-               ddata.size = (size_t) (sizeof(struct CtdlCompressHeader) + zheader.compressed_len);
-               ddata.data = compressed_data;
-       }
-
-       if (TSD->tid != NULL) {
-               ret = dbp[cdb]->put(dbp[cdb],   // db
-                                   TSD->tid,   // transaction ID
-                                   &dkey,      // key
-                                   &ddata,     // data
-                                   0           // flags
-               );
-               if (ret) {
-                       syslog(LOG_ERR, "db: cdb_store(%d): %s", cdb, db_strerror(ret));
-                       cdb_abort();
-               }
-               if (compressing) {
-                       free(compressed_data);
-               }
-               return ret;
-       } else {
-               bailIfCursor(TSD->cursors, "attempt to write during r/o cursor");
-
-             retry:
-               txbegin(&tid);
-
-               if ((ret = dbp[cdb]->put(dbp[cdb],      // db
-                                        tid,           // transaction ID
-                                        &dkey,         // key
-                                        &ddata,        // data
-                                        0))) {         // flags
-                       if (ret == DB_LOCK_DEADLOCK) {
-                               txabort(tid);
-                               goto retry;
-                       } else {
-                               syslog(LOG_ERR, "db: cdb_store(%d): %s", cdb, db_strerror(ret));
-                               cdb_abort();
-                       }
-               } else {
-                       txcommit(tid);
-                       if (compressing) {
-                               free(compressed_data);
-                       }
-                       return ret;
-               }
-       }
-       return ret;
-}
-
-
-/*
- * Delete a piece of data.  Returns 0 if the operation was successful.
- */
-int cdb_delete(int cdb, void *key, int keylen) {
-       DBT dkey;
-       DB_TXN *tid;
-       int ret;
-
-       memset(&dkey, 0, sizeof dkey);
-       dkey.size = keylen;
-       dkey.data = key;
-
-       if (TSD->tid != NULL) {
-               ret = dbp[cdb]->del(dbp[cdb], TSD->tid, &dkey, 0);
-               if (ret) {
-                       syslog(LOG_ERR, "db: cdb_delete(%d): %s", cdb, db_strerror(ret));
-                       if (ret != DB_NOTFOUND) {
-                               cdb_abort();
-                       }
-               }
-       } else {
-               bailIfCursor(TSD->cursors, "attempt to delete during r/o cursor");
-
-             retry:
-               txbegin(&tid);
-
-               if ((ret = dbp[cdb]->del(dbp[cdb], tid, &dkey, 0)) && ret != DB_NOTFOUND) {
-                       if (ret == DB_LOCK_DEADLOCK) {
-                               txabort(tid);
-                               goto retry;
-                       } else {
-                               syslog(LOG_ERR, "db: cdb_delete(%d): %s", cdb, db_strerror(ret));
-                               cdb_abort();
-                       }
-               } else {
-                       txcommit(tid);
-               }
-       }
-       return ret;
-}
-
-
-static DBC *localcursor(int cdb) {
-       int ret;
-       DBC *curs;
-
-       if (TSD->cursors[cdb] == NULL) {
-               ret = dbp[cdb]->cursor(dbp[cdb], TSD->tid, &curs, 0);
-       }
-       else {
-               ret = TSD->cursors[cdb]->c_dup(TSD->cursors[cdb], &curs, DB_POSITION);
-       }
-
-       if (ret) {
-               syslog(LOG_ERR, "db: localcursor: %s", db_strerror(ret));
-               cdb_abort();
-       }
-
-       return curs;
-}
-
-
-/*
- * Fetch a piece of data.  If not found, returns NULL.  Otherwise, it returns
- * a struct cdbdata which it is the caller's responsibility to free later on
- * using the cdb_free() routine.
- */
-struct cdbdata *cdb_fetch(int cdb, const void *key, int keylen) {
-
-       if (keylen == 0) {              // key length zero is impossible
-               return(NULL);
-       }
-
-       struct cdbdata *tempcdb;
-       DBT dkey, dret;
-       int ret;
-
-       memset(&dkey, 0, sizeof(DBT));
-       dkey.size = keylen;
-       dkey.data = (void *) key;
-
-       if (TSD->tid != NULL) {
-               memset(&dret, 0, sizeof(DBT));
-               dret.flags = DB_DBT_MALLOC;
-               ret = dbp[cdb]->get(dbp[cdb], TSD->tid, &dkey, &dret, 0);
-       }
-       else {
-               DBC *curs;
-
-               do {
-                       memset(&dret, 0, sizeof(DBT));
-                       dret.flags = DB_DBT_MALLOC;
-                       curs = localcursor(cdb);
-                       ret = curs->c_get(curs, &dkey, &dret, DB_SET);
-                       cclose(curs);
-               } while (ret == DB_LOCK_DEADLOCK);
-       }
-
-       if ((ret != 0) && (ret != DB_NOTFOUND)) {
-               syslog(LOG_ERR, "db: cdb_fetch(%d): %s", cdb, db_strerror(ret));
-               cdb_abort();
-       }
-
-       if (ret != 0) {
-               return NULL;
-       }
-
-       tempcdb = (struct cdbdata *) malloc(sizeof(struct cdbdata));
-       if (tempcdb == NULL) {
-               syslog(LOG_ERR, "db: cdb_fetch() cannot allocate memory for tempcdb: %m");
-               cdb_abort();
-               return NULL;    /* make it easier for static analysis... */
-       }
-       else {
-               tempcdb->len = dret.size;
-               tempcdb->ptr = dret.data;
-               cdb_decompress_if_necessary(tempcdb);
-               return (tempcdb);
-       }
-}
-
-
-/*
- * Free a cdbdata item.
- *
- * Note that we only free the 'ptr' portion if it is not NULL.  This allows
- * other code to assume ownership of that memory simply by storing the
- * pointer elsewhere and then setting 'ptr' to NULL.  cdb_free() will then
- * avoid freeing it.
- */
-void cdb_free(struct cdbdata *cdb) {
-       if (cdb->ptr) {
-               free(cdb->ptr);
-       }
-       free(cdb);
-}
-
-
-void cdb_close_cursor(int cdb) {
-       if (TSD->cursors[cdb] != NULL) {
-               cclose(TSD->cursors[cdb]);
-       }
-
-       TSD->cursors[cdb] = NULL;
-}
-
-
-/* 
- * Prepare for a sequential search of an entire database.
- * (There is guaranteed to be no more than one traversal in
- * progress per thread at any given time.)
- */
-void cdb_rewind(int cdb) {
-       int ret = 0;
-
-       if (TSD->cursors[cdb] != NULL) {
-               syslog(LOG_ERR, "db: cdb_rewind: must close cursor on database %d before reopening", cdb);
-               cdb_abort();
-               /* cclose(TSD->cursors[cdb]); */
-       }
-
-       /*
-        * Now initialize the cursor
-        */
-       ret = dbp[cdb]->cursor(dbp[cdb], TSD->tid, &TSD->cursors[cdb], 0);
-       if (ret) {
-               syslog(LOG_ERR, "db: cdb_rewind: db_cursor: %s", db_strerror(ret));
-               cdb_abort();
-       }
-}
-
-
-/*
- * Fetch the next item in a sequential search.  Returns a pointer to a 
- * cdbdata structure, or NULL if we've hit the end.
- */
-struct cdbdata *cdb_next_item(int cdb) {
-       DBT key, data;
-       struct cdbdata *cdbret;
-       int ret = 0;
-
-       /* Initialize the key/data pair so the flags aren't set. */
-       memset(&key, 0, sizeof(key));
-       memset(&data, 0, sizeof(data));
-       data.flags = DB_DBT_MALLOC;
-
-       ret = TSD->cursors[cdb]->c_get(TSD->cursors[cdb], &key, &data, DB_NEXT);
-
-       if (ret) {
-               if (ret != DB_NOTFOUND) {
-                       syslog(LOG_ERR, "db: cdb_next_item(%d): %s", cdb, db_strerror(ret));
-                       cdb_abort();
-               }
-               cdb_close_cursor(cdb);
-               return NULL;    /* presumably, end of file */
-       }
-
-       cdbret = (struct cdbdata *) malloc(sizeof(struct cdbdata));
-       cdbret->len = data.size;
-       cdbret->ptr = data.data;
-       cdb_decompress_if_necessary(cdbret);
-
-       return (cdbret);
-}
-
-
-/*
- * Transaction-based stuff.  I'm writing this as I bake cookies...
- */
-void cdb_begin_transaction(void) {
-       bailIfCursor(TSD->cursors, "can't begin transaction during r/o cursor");
-
-       if (TSD->tid != NULL) {
-               syslog(LOG_ERR, "db: cdb_begin_transaction: ERROR: nested transaction");
-               cdb_abort();
-       }
-
-       txbegin(&TSD->tid);
-}
-
-
-void cdb_end_transaction(void) {
-       int i;
-
-       for (i = 0; i < MAXCDB; i++) {
-               if (TSD->cursors[i] != NULL) {
-                       syslog(LOG_WARNING, "db: cdb_end_transaction: WARNING: cursor %d still open at transaction end", i);
-                       cclose(TSD->cursors[i]);
-                       TSD->cursors[i] = NULL;
-               }
-       }
-
-       if (TSD->tid == NULL) {
-               syslog(LOG_ERR, "db: cdb_end_transaction: ERROR: txcommit(NULL) !!");
-               cdb_abort();
-       }
-       else {
-               txcommit(TSD->tid);
-       }
-
-       TSD->tid = NULL;
-}
-
-
-/*
- * Truncate (delete every record)
- */
-void cdb_trunc(int cdb) {
-       /* DB_TXN *tid; */
-       int ret;
-       u_int32_t count;
-
-       if (TSD->tid != NULL) {
-               syslog(LOG_ERR, "db: cdb_trunc must not be called in a transaction.");
-               cdb_abort();
-       }
-       else {
-               bailIfCursor(TSD->cursors, "attempt to write during r/o cursor");
-
-             retry:
-               /* txbegin(&tid); */
-
-               if ((ret = dbp[cdb]->truncate(dbp[cdb], /* db */
-                                             NULL,     /* transaction ID */
-                                             &count,   /* #rows deleted */
-                                             0))) {    /* flags */
-                       if (ret == DB_LOCK_DEADLOCK) {
-                               /* txabort(tid); */
-                               goto retry;
-                       } else {
-                               syslog(LOG_ERR, "db: cdb_truncate(%d): %s", cdb, db_strerror(ret));
-                               if (ret == ENOMEM) {
-                                       syslog(LOG_ERR, "db: You may need to tune your database; please read http://www.citadel.org/doku.php?id=faq:troubleshooting:out_of_lock_entries for more information.");
-                               }
-                               exit(CTDLEXIT_DB);
-                       }
-               }
-               else {
-                       /* txcommit(tid); */
-               }
-       }
-}
-
-
-// compact (defragment) the database, possibly returning space back to the underlying filesystem
-void cdb_compact(void) {
-       int ret;
-       int i;
-
-       syslog(LOG_DEBUG, "db: cdb_compact() started");
-       for (i = 0; i < MAXCDB; i++) {
-               syslog(LOG_DEBUG, "db: compacting database %d", i);
-               ret = dbp[i]->compact(dbp[i], NULL, NULL, NULL, NULL, DB_FREE_SPACE, NULL);
-               if (ret) {
-                       syslog(LOG_ERR, "db: compact: %s", db_strerror(ret));
-               }
-       }
-       syslog(LOG_DEBUG, "db: cdb_compact() finished");
-}
-
-
-// Has an item already been seen (is it in the CDB_USETABLE) ?
-// Returns 0 if it hasn't, 1 if it has
-// In either case, writes the item to the database for next time.
-int CheckIfAlreadySeen(StrBuf *guid) {
-       int found = 0;
-       struct UseTable ut;
-       struct cdbdata *cdbut;
-
-       syslog(LOG_DEBUG, "db: CheckIfAlreadySeen(%s)", ChrPtr(guid));
-       cdbut = cdb_fetch(CDB_USETABLE, SKEY(guid));
-       if (cdbut != NULL) {
-               found = 1;
-               cdb_free(cdbut);
-       }
-
-       /* (Re)write the record, to update the timestamp.  Zeroing it out makes it compress better. */
-       memset(&ut, 0, sizeof(struct UseTable));
-       memcpy(ut.ut_msgid, SKEY(guid));
-       ut.ut_timestamp = time(NULL);
-       cdb_store(CDB_USETABLE, SKEY(guid), &ut, sizeof(struct UseTable));
-       return (found);
-}
-
-
-CTDL_MODULE_INIT(database)
-{
-       if (!threading) {
-               // nothing to do here
-       }
-
-       /* return our module id for the log */
-       return "database";
-}
diff --git a/citadel/database.h b/citadel/database.h
deleted file mode 100644 (file)
index b9b76b7..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (c) 1987-2017 by the citadel.org team
- *
- *  This program is open source software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License version 3.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- */
-
-#ifndef DATABASE_H
-#define DATABASE_H
-
-
-void open_databases (void);
-void close_databases (void);
-int cdb_store (int cdb, const void *key, int keylen, void *data, int datalen);
-int cdb_delete (int cdb, void *key, int keylen);
-struct cdbdata *cdb_fetch (int cdb, const void *key, int keylen);
-void cdb_free (struct cdbdata *cdb);
-void cdb_rewind (int cdb);
-struct cdbdata *cdb_next_item (int cdb);
-void cdb_close_cursor(int cdb);
-void cdb_begin_transaction(void);
-void cdb_end_transaction(void);
-void cdb_allocate_tsd(void);
-void cdb_free_tsd(void);
-void cdb_check_handles(void);
-void cdb_trunc(int cdb);
-void *checkpoint_thread(void *arg);
-void cdb_chmod_data(void);
-void cdb_checkpoint(void);
-void check_handles(void *arg);
-void cdb_cull_logs(void);
-void cdb_compact(void);
-
-
-/*
- * Database records beginning with this magic number are assumed to
- * be compressed.  In the event that a database record actually begins with
- * this magic number, we *must* compress it whether we want to or not,
- * because the fetch function will try to uncompress it anyway.
- * 
- * (No need to #ifdef this stuff; it compiles ok even if zlib is not present
- * and doesn't declare anything so it won't bloat the code)
- */
-#define COMPRESS_MAGIC 0xc0ffeeee
-
-struct CtdlCompressHeader {
-       int magic;
-       size_t uncompressed_len;
-       size_t compressed_len;
-};
-
-int CheckIfAlreadySeen(StrBuf *guid);
-
-#endif /* DATABASE_H */
-
diff --git a/citadel/default_timezone.c b/citadel/default_timezone.c
deleted file mode 100644 (file)
index 6ce5c10..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (c) 1987-2018 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <limits.h>
-#include <stdio.h>
-#include <string.h>
-#include <strings.h>
-#include <syslog.h>
-#include <libical/ical.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "sysdep_decls.h"
-#include "support.h"
-#include "config.h"
-#include "default_timezone.h"
-#include "ctdl_module.h"
-
-
-/*
- * Figure out which time zone needs to be used for timestamps that are
- * not UTC and do not have a time zone specified.
- */
-icaltimezone *get_default_icaltimezone(void) {
-
-        icaltimezone *zone = NULL;
-       char *default_zone_name = CtdlGetConfigStr("c_default_cal_zone");
-
-        if (!zone) {
-                zone = icaltimezone_get_builtin_timezone(default_zone_name);
-        }
-        if (!zone) {
-               syslog(LOG_ERR, "ical: Unable to load '%s' time zone.  Defaulting to UTC.", default_zone_name);
-                zone = icaltimezone_get_utc_timezone();
-       }
-       if (!zone) {
-               syslog(LOG_ERR, "ical: unable to load UTC time zone!");
-       }
-
-        return zone;
-}
diff --git a/citadel/default_timezone.h b/citadel/default_timezone.h
deleted file mode 100644 (file)
index 869c70d..0000000
+++ /dev/null
@@ -1 +0,0 @@
-icaltimezone *get_default_icaltimezone(void);
diff --git a/citadel/domain.c b/citadel/domain.c
deleted file mode 100644 (file)
index 79461f5..0000000
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * DNS lookup for SMTP sender
- *
- * Copyright (c) 1987-2021 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdio.h>
-#include <syslog.h>
-#ifdef HAVE_RESOLV_H
-#include <arpa/nameser.h>
-#ifdef HAVE_ARPA_NAMESER_COMPAT_H
-#include <arpa/nameser_compat.h>
-#endif
-#ifdef __FreeBSD__
-#include <netinet/in.h>
-#endif
-#include <resolv.h>
-#endif
-#include <libcitadel.h>
-#include "sysdep_decls.h"
-#include "citadel.h"
-#include "domain.h"
-#include "internet_addressing.h"
-
-
-/*
- * get_hosts() checks the Internet configuration for various types of
- * entries and returns them in the same format as getmx() does -- fill the
- * buffer with a delimited list of hosts and return the number of hosts.
- * 
- * This is used to fetch MX smarthosts, SpamAssassin hosts, etc.
- */
-int get_hosts(char *mxbuf, char *rectype) {
-       int config_lines;
-       int i;
-       char buf[256];
-       char host[256], type[256];
-       int total_smarthosts = 0;
-
-       if (inetcfg == NULL) return(0);
-       strcpy(mxbuf, "");
-
-       config_lines = num_tokens(inetcfg, '\n');
-       for (i=0; i<config_lines; ++i) {
-               extract_token(buf, inetcfg, i, '\n', sizeof buf);
-               extract_token(host, buf, 0, '|', sizeof host);
-               extract_token(type, buf, 1, '|', sizeof type);
-
-               if (!strcasecmp(type, rectype)) {
-                       strcat(mxbuf, host);
-                       strcat(mxbuf, "|");
-                       ++total_smarthosts;
-               }
-       }
-
-       return(total_smarthosts);
-}
-
-
-/*
- * Compare the preference of two MX records.  First check by the actual
- * number listed in the MX record.  If they're identical, randomize the
- * result.
- */
-int mx_compare_pref(const void *mx1, const void *mx2) {
-       int pref1;
-       int pref2;
-
-       pref1 = ((const struct mx *)mx1)->pref;
-       pref2 = ((const struct mx *)mx2)->pref;
-
-       if (pref1 > pref2) {
-               return(1);
-       }
-       else if (pref1 < pref2) {
-               return(0);
-       }
-       else {
-               return(rand() % 2);
-       }
-}
-
-
-/* 
- * getmx()
- *
- * Return one or more MX's for a mail destination.
- *
- * Upon success, it fills 'mxbuf' with one or more MX hosts, separated by
- * vertical bar characters, and returns the number of hosts as its return
- * value.  If no MX's are found, it returns 0.
- *
- */
-int getmx(char *mxbuf, char *dest) {
-
-#ifdef HAVE_RESOLV_H
-       union {
-                       u_char bytes[1024];
-                       HEADER header;
-    } answer;
-#endif
-
-       int ret;
-       unsigned char *startptr, *endptr, *ptr;
-       char expanded_buf[1024];
-       unsigned short pref, type;
-       int n = 0;
-       int qdcount;
-       Array *mxrecords = NULL;
-       struct mx mx;
-
-       // If we're configured to send all mail to a smart-host, then our job here is really easy -- just return those.
-       n = get_hosts(mxbuf, "smarthost");
-       if (n > 0) {
-               return(n);
-       }
-
-       mxrecords = array_new(sizeof(struct mx));
-
-       // No smart-host?  Look up the best MX for a site.  Make a call to the resolver library.
-       ret = res_query(dest, C_IN, T_MX, (unsigned char *)answer.bytes, sizeof(answer));
-
-       if (ret < 0) {
-               mx.pref = 0;
-               strcpy(mx.host, dest);
-               array_append(mxrecords, &mx);
-       }
-       else {
-               if (ret > sizeof(answer)) {             // If we had to truncate, shrink the number to avoid fireworks
-                       ret = sizeof(answer);
-               }
-
-               startptr = &answer.bytes[0];            // start and end of buffer
-               endptr = &answer.bytes[ret];
-               ptr = startptr + HFIXEDSZ;              // advance past header
-       
-               for (qdcount = ntohs(answer.header.qdcount); qdcount--; ptr += ret + QFIXEDSZ) {
-                       if ((ret = dn_skipname(ptr, endptr)) < 0) {
-                               syslog(LOG_DEBUG, "domain: dn_skipname error");
-                               return(0);
-                       }
-               }
-       
-               while(1) {
-                       memset(expanded_buf, 0, sizeof(expanded_buf));
-                       ret = dn_expand(startptr, endptr, ptr, expanded_buf, sizeof(expanded_buf));
-                       if (ret < 0) break;
-                       ptr += ret;
-       
-                       GETSHORT(type, ptr);
-                       ptr += INT16SZ + INT32SZ;
-                       GETSHORT(n, ptr);
-       
-                       if (type != T_MX) {
-                               ptr += n;
-                       }
-       
-                       else {
-                               GETSHORT(pref, ptr);
-                               ret = dn_expand(startptr, endptr, ptr, expanded_buf, sizeof(expanded_buf));
-                               ptr += ret;
-       
-                               mx.pref = pref;
-                               strcpy(mx.host, expanded_buf);
-                               array_append(mxrecords, &mx);
-                       }
-               }
-       }
-
-       // Sort the MX records by preference
-       if (array_len(mxrecords) > 1) {
-               array_sort(mxrecords, mx_compare_pref);
-       }
-
-       int num_mxrecs = array_len(mxrecords);
-       strcpy(mxbuf, "");
-       for (n=0; n<num_mxrecs; ++n) {
-               strcat(mxbuf, ((struct mx *)array_get_element_at(mxrecords, n))->host);
-               strcat(mxbuf, "|");
-       }
-       array_free(mxrecords);
-
-       // Append any fallback smart hosts we have configured.
-       num_mxrecs += get_hosts(&mxbuf[strlen(mxbuf)], "fallbackhost");
-       return(num_mxrecs);
-}
diff --git a/citadel/domain.h b/citadel/domain.h
deleted file mode 100644 (file)
index ba36759..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (c) 1987-2012 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-struct mx {
-       int pref;
-       char host[1024];
-};
-
-int getmx(char *mxbuf, char *dest);
-int get_hosts(char *mxbuf, char *rectype);
-
-
-/* HP/UX has old include files...these are from arpa/nameser.h */
-
-#include "typesize.h"
-
-#ifndef HFIXEDSZ
-#define HFIXEDSZ       12              /* I hope! */
-#endif
-#ifndef INT16SZ
-#define        INT16SZ         sizeof(cit_int16_t)
-#endif
-#ifndef INT32SZ
-#define INT32SZ                sizeof(cit_int32_t)
-#endif
diff --git a/citadel/euidindex.c b/citadel/euidindex.c
deleted file mode 100644 (file)
index 59d67de..0000000
+++ /dev/null
@@ -1,214 +0,0 @@
-// Index messages by EUID per room.
-//
-// Copyright (c) 1987-2022 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include "sysdep.h"
-#include <stdio.h>
-#include <libcitadel.h>
-
-#include "citserver.h"
-#include "room_ops.h"
-
-// The structure of an euidindex record *key* is:
-//
-// |----room_number----|----------EUID-------------|
-//    (sizeof long)       (actual length of euid)
-//
-//
-// The structure of an euidindex record *value* is:
-//
-// |-----msg_number----|----room_number----|----------EUID-------------|
-//    (sizeof long)       (sizeof long)       (actual length of euid)
-
-// Return nonzero if the supplied room is one which should have
-// an EUID index.
-int DoesThisRoomNeedEuidIndexing(struct ctdlroom *qrbuf) {
-
-       switch(qrbuf->QRdefaultview) {
-               case VIEW_BBS:          return(0);
-               case VIEW_MAILBOX:      return(0);
-               case VIEW_ADDRESSBOOK:  return(1);
-               case VIEW_DRAFTS:       return(0);
-               case VIEW_CALENDAR:     return(1);
-               case VIEW_TASKS:        return(1);
-               case VIEW_NOTES:        return(1);
-               case VIEW_WIKI:         return(1);
-               case VIEW_BLOG:         return(1);
-       }
-       
-       return(0);
-}
-
-
-// Locate a message in a given room with a given euid, and return
-// its message number.
-long locate_message_by_euid(char *euid, struct ctdlroom *qrbuf) {
-       return CtdlLocateMessageByEuid (euid, qrbuf);
-}
-
-
-long CtdlLocateMessageByEuid(char *euid, struct ctdlroom *qrbuf) {
-       char *key;
-       int key_len;
-       struct cdbdata *cdb_euid;
-       long msgnum = (-1L);
-
-       syslog(LOG_DEBUG, "euidindex: searching for EUID <%s> in <%s>", euid, qrbuf->QRname);
-
-       key_len = strlen(euid) + sizeof(long) + 1;
-       key = malloc(key_len);
-       memcpy(key, &qrbuf->QRnumber, sizeof(long));
-       strcpy(&key[sizeof(long)], euid);
-
-       cdb_euid = cdb_fetch(CDB_EUIDINDEX, key, key_len);
-       free(key);
-
-       if (cdb_euid == NULL) {
-               msgnum = (-1L);
-       }
-       else {
-               // The first (sizeof long) of the record is what we're looking for.  Throw away the rest.
-               memcpy(&msgnum, cdb_euid->ptr, sizeof(long));
-               cdb_free(cdb_euid);
-       }
-       syslog(LOG_DEBUG, "euidindex: returning msgnum = %ld", msgnum);
-       return(msgnum);
-}
-
-
-// Store the euid index for a message, which has presumably just been
-// stored in this room by the caller.
-void index_message_by_euid(char *euid, struct ctdlroom *qrbuf, long msgnum) {
-       char *key;
-       int key_len;
-       char *data;
-       int data_len;
-
-       syslog(LOG_DEBUG, "euidindex: indexing message #%ld <%s> in <%s>", msgnum, euid, qrbuf->QRname);
-
-       key_len = strlen(euid) + sizeof(long) + 1;
-       key = malloc(key_len);
-       memcpy(key, &qrbuf->QRnumber, sizeof(long));
-       strcpy(&key[sizeof(long)], euid);
-
-       data_len = sizeof(long) + key_len;
-       data = malloc(data_len);
-
-       memcpy(data, &msgnum, sizeof(long));
-       memcpy(&data[sizeof(long)], key, key_len);
-
-       cdb_store(CDB_EUIDINDEX, key, key_len, data, data_len);
-       free(key);
-       free(data);
-}
-
-
-// Called by rebuild_euid_index_for_room() to index one message.
-void rebuild_euid_index_for_msg(long msgnum, void *userdata) {
-       struct CtdlMessage *msg = NULL;
-
-       msg = CtdlFetchMessage(msgnum, 0);
-       if (msg == NULL) return;
-       if (!CM_IsEmpty(msg, eExclusiveID)) {
-               index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgnum);
-       }
-       CM_Free(msg);
-}
-
-
-void rebuild_euid_index_for_room(struct ctdlroom *qrbuf, void *data) {
-       static struct RoomProcList *rplist = NULL;
-       struct RoomProcList *ptr;
-       struct ctdlroom qr;
-
-       // Lazy programming here.  Call this function as a CtdlForEachRoom backend
-       // in order to queue up the room names, or call it with a null room
-       // to make it do the processing.
-       if (qrbuf != NULL) {
-               ptr = (struct RoomProcList *)
-                       malloc(sizeof (struct RoomProcList));
-               if (ptr == NULL) return;
-
-               safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
-               ptr->next = rplist;
-               rplist = ptr;
-               return;
-       }
-
-       while (rplist != NULL) {
-               if (CtdlGetRoom(&qr, rplist->name) == 0) {
-                       if (DoesThisRoomNeedEuidIndexing(&qr)) {
-                               syslog(LOG_DEBUG,
-                                       "euidindex: rebuilding EUID index for <%s>",
-                                       rplist->name);
-                               CtdlUserGoto(rplist->name, 0, 0, NULL, NULL, NULL, NULL);
-                               CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, rebuild_euid_index_for_msg, NULL);
-                       }
-               }
-               ptr = rplist;
-               rplist = rplist->next;
-               free(ptr);
-       }
-}
-
-
-// Globally rebuild the EUID indices in every room.
-void rebuild_euid_index(void) {
-       cdb_trunc(CDB_EUIDINDEX);                               // delete the old indices
-       CtdlForEachRoom(rebuild_euid_index_for_room, NULL);     // enumerate room names
-       rebuild_euid_index_for_room(NULL, NULL);                // and index them
-}
-
-
-// Server command to fetch a message number given an euid.
-void cmd_euid(char *cmdbuf) {
-       char euid[256];
-       long msgnum;
-        struct cdbdata *cdbfr;
-        long *msglist = NULL;
-        int num_msgs = 0;
-       int i;
-
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
-       extract_token(euid, cmdbuf, 0, '|', sizeof euid);
-       msgnum = CtdlLocateMessageByEuid(euid, &CC->room);
-       if (msgnum <= 0L) {
-               cprintf("%d not found\n", ERROR + MESSAGE_NOT_FOUND);
-               return;
-       }
-
-        cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
-       if (cdbfr != NULL) {
-                num_msgs = cdbfr->len / sizeof(long);
-                msglist = (long *) cdbfr->ptr;
-                for (i = 0; i < num_msgs; ++i) {
-                        if (msglist[i] == msgnum) {
-                               cdb_free(cdbfr);
-                               cprintf("%d %ld\n", CIT_OK, msgnum);
-                               return;
-                       }
-               }
-                cdb_free(cdbfr);
-       }
-
-       cprintf("%d not found\n", ERROR + MESSAGE_NOT_FOUND);
-}
-
-
-CTDL_MODULE_INIT(euidindex)
-{
-       if (!threading) {
-               CtdlRegisterProtoHook(cmd_euid, "EUID", "Perform operations on Extended IDs for messages");
-       }
-       /* return our Subversion id for the Log */
-       return "euidindex";
-}
diff --git a/citadel/euidindex.h b/citadel/euidindex.h
deleted file mode 100644 (file)
index e30f79f..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-/*
- * Index messages by EUID per room.
- */
-
-int DoesThisRoomNeedEuidIndexing(struct ctdlroom *qrbuf);
-/* locate_message_by_euid is deprecated. Use CtdlLocateMessageByEuid instead */
-long locate_message_by_euid(char *euid, struct ctdlroom *qrbuf) __attribute__ ((deprecated));
-void index_message_by_euid(char *euid, struct ctdlroom *qrbuf, long msgnum);
-void rebuild_euid_index(void);
diff --git a/citadel/genstamp.c b/citadel/genstamp.c
deleted file mode 100644 (file)
index e8d5dd8..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Function to generate RFC822-compliant textual time/date stamp
- */
-
-#include "sysdep.h"
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <string.h>
-#include <time.h>
-#include "genstamp.h"
-
-static char *months[] = {
-       "Jan", "Feb", "Mar", "Apr", "May", "Jun",
-       "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
-};
-
-static char *weekdays[] = {
-       "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
-};
-
-/*
- * Supplied with a unix timestamp, generate an RFC822-compliant textual
- * time and date stamp.
- */
-long datestring(char *buf, size_t n, time_t xtime, int which_format) {
-       struct tm t;
-
-       long offset;
-       char offsign;
-
-       localtime_r(&xtime, &t);
-
-       /* Convert "seconds west of GMT" to "hours/minutes offset" */
-#ifdef HAVE_STRUCT_TM_TM_GMTOFF
-       offset = t.tm_gmtoff;
-#else
-       offset = timezone;
-#endif
-       if (offset > 0) {
-               offsign = '+';
-       }
-       else {
-               offset = 0L - offset;
-               offsign = '-';
-       }
-       offset = ( (offset / 3600) * 100 ) + ( offset % 60 );
-
-       switch(which_format) {
-
-               case DATESTRING_RFC822:
-                       return snprintf(
-                               buf, n,
-                               "%s, %02d %s %04d %02d:%02d:%02d %c%04ld",
-                               weekdays[t.tm_wday],
-                               t.tm_mday,
-                               months[t.tm_mon],
-                               t.tm_year + 1900,
-                               t.tm_hour,
-                               t.tm_min,
-                               t.tm_sec,
-                               offsign, offset
-                               );
-               break;
-
-               case DATESTRING_IMAP:
-                       return snprintf(
-                               buf, n,
-                               "%02d-%s-%04d %02d:%02d:%02d %c%04ld",
-                               t.tm_mday,
-                               months[t.tm_mon],
-                               t.tm_year + 1900,
-                               t.tm_hour,
-                               t.tm_min,
-                               t.tm_sec,
-                               offsign, offset
-                               );
-               break;
-
-       }
-       return 0;
-}
diff --git a/citadel/genstamp.h b/citadel/genstamp.h
deleted file mode 100644 (file)
index ffe0d9c..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-
-long datestring(char *buf, size_t n, time_t xtime, int which_format);
-
-enum {
-       DATESTRING_RFC822,
-       DATESTRING_IMAP
-};
diff --git a/citadel/housekeeping.c b/citadel/housekeeping.c
deleted file mode 100644 (file)
index 54cfe11..0000000
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * This file contains miscellaneous housekeeping tasks.
- *
- * Copyright (c) 1987-2021 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include <stdio.h>
-#include <libcitadel.h>
-
-#include "ctdl_module.h"
-#include "serv_extensions.h"
-#include "room_ops.h"
-#include "internet_addressing.h"
-#include "config.h"
-#include "journaling.h"
-#include "citadel_ldap.h"
-
-void check_sched_shutdown(void) {
-       if ((ScheduledShutdown == 1) && (ContextList == NULL)) {
-               syslog(LOG_NOTICE, "housekeeping: scheduled shutdown initiating");
-               server_shutting_down = 1;
-       }
-}
-
-
-/*
- * Check (and fix) floor reference counts.  This doesn't need to be done
- * very often, since the counts should remain correct during normal operation.
- */
-void check_ref_counts_backend(struct ctdlroom *qrbuf, void *data) {
-
-       int *new_refcounts;
-
-       new_refcounts = (int *) data;
-
-       ++new_refcounts[(int)qrbuf->QRfloor];
-}
-
-
-void check_ref_counts(void) {
-       struct floor flbuf;
-       int a;
-
-       int new_refcounts[MAXFLOORS];
-
-       syslog(LOG_DEBUG, "housekeeping: checking floor reference counts");
-       for (a=0; a<MAXFLOORS; ++a) {
-               new_refcounts[a] = 0;
-       }
-
-       cdb_begin_transaction();
-       CtdlForEachRoom(check_ref_counts_backend, (void *)new_refcounts );
-       cdb_end_transaction();
-
-       for (a=0; a<MAXFLOORS; ++a) {
-               lgetfloor(&flbuf, a);
-               flbuf.f_ref_count = new_refcounts[a];
-               if (new_refcounts[a] > 0) {
-                       flbuf.f_flags = flbuf.f_flags | QR_INUSE;
-               }
-               else {
-                       flbuf.f_flags = flbuf.f_flags & ~QR_INUSE;
-               }
-               lputfloor(&flbuf, a);
-               syslog(LOG_DEBUG, "housekeeping: floor %d has %d rooms", a, new_refcounts[a]);
-       }
-}
-
-
-/*
- * Provide hints as to whether we have any memory leaks
- */
-void keep_an_eye_on_memory_usage(void) {
-       static void *original_brk = NULL;
-       if (!original_brk) original_brk = sbrk(0);      // Remember the original program break so we can test for leaks
-       syslog(LOG_DEBUG, "original_brk=%lx, current_brk=%lx, addl=%ld", (long)original_brk, (long)sbrk(0), (long)(sbrk(0)-original_brk));      // FIXME not so noisy please
-}
-
-
-/*
- * This is the housekeeping loop.  Worker threads come through here after
- * processing client requests but before jumping back into the pool.  We
- * only allow housekeeping to execute once per minute, and we only allow one
- * instance to run at a time.
- */
-static int housekeeping_in_progress = 0;
-static int housekeeping_disabled = 0;
-static time_t last_timer = 0L;
-
-void do_housekeeping(void) {
-       int do_housekeeping_now = 0;
-       int do_perminute_housekeeping_now = 0;
-       time_t now;
-
-       if (housekeeping_disabled) {
-               return;
-       }
-
-       /*
-        * We do it this way instead of wrapping the whole loop in an
-        * S_HOUSEKEEPING critical section because it eliminates the need to
-        * potentially have multiple concurrent mutexes in progress.
-        */
-       begin_critical_section(S_HOUSEKEEPING);
-       if (housekeeping_in_progress == 0) {
-               do_housekeeping_now = 1;
-               housekeeping_in_progress = 1;
-       }
-       end_critical_section(S_HOUSEKEEPING);
-
-       now = time(NULL);
-       if ( (do_housekeeping_now == 0) && (!CtdlIsSingleUser()) ) {
-               if ( (now - last_timer) > (time_t)300 ) {
-                       syslog(LOG_WARNING,
-                               "housekeeping: WARNING: housekeeping loop has not run for %ld minutes.  Is something stuck?",
-                               ((now - last_timer) / 60)
-                       );
-               }
-               return;
-       }
-
-       /*
-        * Ok, at this point we've made the decision to run the housekeeping
-        * loop.  Everything below this point is real work.
-        */
-
-       if ( (now - last_timer) > (time_t)60 ) {
-               do_perminute_housekeeping_now = 1;
-               last_timer = time(NULL);
-       }
-
-       /* First, do the "as often as needed" stuff... */
-       JournalRunQueue();
-       PerformSessionHooks(EVT_HOUSE);
-
-       /* Then, do the "once per minute" stuff... */
-       if (do_perminute_housekeeping_now) {
-               cdb_check_handles();
-               PerformSessionHooks(EVT_TIMER);         // Run all registered TIMER hooks
-
-               // LDAP sync isn't in a module so we can put it here
-               static time_t last_ldap_sync = 0L;
-               if ( (now - last_ldap_sync) > (time_t)CtdlGetConfigLong("c_ldap_sync_freq") ) {
-                       CtdlSynchronizeUsersFromLDAP();
-                       last_ldap_sync = time(NULL);
-               }
-
-       keep_an_eye_on_memory_usage();
-       }
-
-       /*
-        * All done.
-        */
-       begin_critical_section(S_HOUSEKEEPING);
-       housekeeping_in_progress = 0;
-       end_critical_section(S_HOUSEKEEPING);
-}
-
-
-void CtdlDisableHouseKeeping(void) {
-       syslog(LOG_INFO, "housekeeping: trying to disable");
-       while ( (!housekeeping_disabled) && (!server_shutting_down) && (!housekeeping_in_progress) ) {
-
-               if (housekeeping_in_progress) {
-                       sleep(1);
-               }
-               else {
-                       begin_critical_section(S_HOUSEKEEPING);
-                       if (!housekeeping_in_progress) {
-                               housekeeping_disabled = 1;
-                       }
-                       end_critical_section(S_HOUSEKEEPING);
-               }
-       }
-       syslog(LOG_INFO, "housekeeping: disabled now");
-}
-
-
-void CtdlEnableHouseKeeping(void) {
-       begin_critical_section(S_HOUSEKEEPING);
-       housekeeping_in_progress = 0;
-       end_critical_section(S_HOUSEKEEPING);
-}
diff --git a/citadel/housekeeping.h b/citadel/housekeeping.h
deleted file mode 100644 (file)
index 90ee12a..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-void check_sched_shutdown(void);
-void check_ref_counts(void);
-void do_housekeeping(void);
diff --git a/citadel/internet_addressing.c b/citadel/internet_addressing.c
deleted file mode 100644 (file)
index e67a9dc..0000000
+++ /dev/null
@@ -1,1721 +0,0 @@
-// This file contains functions which handle the mapping of Internet addresses
-// to users on the Citadel system.
-//
-// Copyright (c) 1987-2022 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "user_ops.h"
-#include "room_ops.h"
-#include "parsedate.h"
-#include "database.h"
-#include "ctdl_module.h"
-#ifdef HAVE_ICONV
-#include <iconv.h>
-
-// This is the non-define version in case it is needed for debugging
-#if 0
-inline void FindNextEnd (char *bptr, char *end)
-{
-       /* Find the next ?Q? */
-       end = strchr(bptr + 2, '?');
-       if (end == NULL) return NULL;
-       if (((*(end + 1) == 'B') || (*(end + 1) == 'Q')) && 
-           (*(end + 2) == '?')) {
-               /* skip on to the end of the cluster, the next ?= */
-               end = strstr(end + 3, "?=");
-       }
-       else
-               /* sort of half valid encoding, try to find an end. */
-               end = strstr(bptr, "?=");
-}
-#endif
-
-#define FindNextEnd(bptr, end) { \
-       end = strchr(bptr + 2, '?'); \
-       if (end != NULL) { \
-               if (((*(end + 1) == 'B') || (*(end + 1) == 'Q')) && (*(end + 2) == '?')) { \
-                       end = strstr(end + 3, "?="); \
-               } else end = strstr(bptr, "?="); \
-       } \
-}
-
-// Handle subjects with RFC2047 encoding such as:
-// =?koi8-r?B?78bP0s3Mxc7JxSDXz9rE1dvO2c3JINvB0sHNySDP?=
-void utf8ify_rfc822_string(char *buf) {
-       char *start, *end, *next, *nextend, *ptr;
-       char newbuf[1024];
-       char charset[128];
-       char encoding[16];
-       char istr[1024];
-       iconv_t ic = (iconv_t)(-1) ;
-       char *ibuf;                     // Buffer of characters to be converted
-       char *obuf;                     // Buffer for converted characters
-       size_t ibuflen;                 // Length of input buffer
-       size_t obuflen;                 // Length of output buffer
-       char *isav;                     // Saved pointer to input buffer
-       char *osav;                     // Saved pointer to output buffer
-       int passes = 0;
-       int i, len, delta;
-       int illegal_non_rfc2047_encoding = 0;
-
-       // Sometimes, badly formed messages contain strings which were simply
-       // written out directly in some foreign character set instead of
-       // using RFC2047 encoding.  This is illegal but we will attempt to
-       // handle it anyway by converting from a user-specified default
-       // charset to UTF-8 if we see any nonprintable characters.
-       len = strlen(buf);
-       for (i=0; i<len; ++i) {
-               if ((buf[i] < 32) || (buf[i] > 126)) {
-                       illegal_non_rfc2047_encoding = 1;
-                       i = len;        // take a shortcut, it won't be more than one.
-               }
-       }
-       if (illegal_non_rfc2047_encoding) {
-               const char *default_header_charset = "iso-8859-1";
-               if ( (strcasecmp(default_header_charset, "UTF-8")) && (strcasecmp(default_header_charset, "us-ascii")) ) {
-                       ctdl_iconv_open("UTF-8", default_header_charset, &ic);
-                       if (ic != (iconv_t)(-1) ) {
-                               ibuf = malloc(1024);
-                               isav = ibuf;
-                               safestrncpy(ibuf, buf, 1024);
-                               ibuflen = strlen(ibuf);
-                               obuflen = 1024;
-                               obuf = (char *) malloc(obuflen);
-                               osav = obuf;
-                               iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
-                               osav[1024-obuflen] = 0;
-                               strcpy(buf, osav);
-                               free(osav);
-                               iconv_close(ic);
-                               free(isav);
-                       }
-               }
-       }
-
-       // pre evaluate the first pair
-       nextend = end = NULL;
-       len = strlen(buf);
-       start = strstr(buf, "=?");
-       if (start != NULL) 
-               FindNextEnd (start, end);
-
-       while ((start != NULL) && (end != NULL)) {
-               next = strstr(end, "=?");
-               if (next != NULL)
-                       FindNextEnd(next, nextend);
-               if (nextend == NULL)
-                       next = NULL;
-
-               // did we find two partitions
-               if ((next != NULL) && ((next - end) > 2)) {
-                       ptr = end + 2;
-                       while ((ptr < next) && 
-                              (isspace(*ptr) ||
-                               (*ptr == '\r') ||
-                               (*ptr == '\n') || 
-                               (*ptr == '\t')))
-                               ptr ++;
-                       // did we find a gab just filled with blanks?
-                       if (ptr == next) {
-                               memmove(end + 2, next, len - (next - start));
-
-                               // now terminate the gab at the end
-                               delta = (next - end) - 2;
-                               len -= delta;
-                               buf[len] = '\0';
-
-                               // move next to its new location.
-                               next -= delta;
-                               nextend -= delta;
-                       }
-               }
-               // our next-pair is our new first pair now.
-               start = next;
-               end = nextend;
-       }
-
-       // Now we handle foreign character sets properly encoded in RFC2047 format.
-       start = strstr(buf, "=?");
-       FindNextEnd((start != NULL)? start : buf, end);
-       while (start != NULL && end != NULL && end > start) {
-               extract_token(charset, start, 1, '?', sizeof charset);
-               extract_token(encoding, start, 2, '?', sizeof encoding);
-               extract_token(istr, start, 3, '?', sizeof istr);
-
-               ibuf = malloc(1024);
-               isav = ibuf;
-               if (!strcasecmp(encoding, "B")) {       // base64
-                       ibuflen = CtdlDecodeBase64(ibuf, istr, strlen(istr));
-               }
-               else if (!strcasecmp(encoding, "Q")) {  // quoted-printable
-                       size_t len;
-                       unsigned long pos;
-                       
-                       len = strlen(istr);
-                       pos = 0;
-                       while (pos < len) {
-                               if (istr[pos] == '_') istr[pos] = ' ';
-                               pos++;
-                       }
-                       ibuflen = CtdlDecodeQuotedPrintable(ibuf, istr, len);
-               }
-               else {
-                       strcpy(ibuf, istr);             // unknown encoding
-                       ibuflen = strlen(istr);
-               }
-
-               ctdl_iconv_open("UTF-8", charset, &ic);
-               if (ic != (iconv_t)(-1) ) {
-                       obuflen = 1024;
-                       obuf = (char *) malloc(obuflen);
-                       osav = obuf;
-                       iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
-                       osav[1024-obuflen] = 0;
-
-                       end = start;
-                       end++;
-                       strcpy(start, "");
-                       remove_token(end, 0, '?');
-                       remove_token(end, 0, '?');
-                       remove_token(end, 0, '?');
-                       remove_token(end, 0, '?');
-                       strcpy(end, &end[1]);
-
-                       snprintf(newbuf, sizeof newbuf, "%s%s%s", buf, osav, end);
-                       strcpy(buf, newbuf);
-                       free(osav);
-                       iconv_close(ic);
-               }
-               else {
-                       end = start;
-                       end++;
-                       strcpy(start, "");
-                       remove_token(end, 0, '?');
-                       remove_token(end, 0, '?');
-                       remove_token(end, 0, '?');
-                       remove_token(end, 0, '?');
-                       strcpy(end, &end[1]);
-
-                       snprintf(newbuf, sizeof newbuf, "%s(unreadable)%s", buf, end);
-                       strcpy(buf, newbuf);
-               }
-
-               free(isav);
-
-               // Since spammers will go to all sorts of absurd lengths to get their
-               // messages through, there are LOTS of corrupt headers out there.
-               // So, prevent a really badly formed RFC2047 header from throwing
-               // this function into an infinite loop.
-               ++passes;
-               if (passes > 20) return;
-
-               start = strstr(buf, "=?");
-               FindNextEnd((start != NULL)? start : buf, end);
-       }
-
-}
-#else
-inline void utf8ify_rfc822_string(char *a){};
-
-#endif
-
-
-char *inetcfg = NULL;
-
-// Return nonzero if the supplied name is an alias for this host.
-int CtdlHostAlias(char *fqdn) {
-       int config_lines;
-       int i;
-       char buf[256];
-       char host[256], type[256];
-       int found = 0;
-
-       if (fqdn == NULL)                                       return(hostalias_nomatch);
-       if (IsEmptyStr(fqdn))                                   return(hostalias_nomatch);
-       if (!strcasecmp(fqdn, "localhost"))                     return(hostalias_localhost);
-       if (!strcasecmp(fqdn, CtdlGetConfigStr("c_fqdn")))      return(hostalias_localhost);
-       if (!strcasecmp(fqdn, CtdlGetConfigStr("c_nodename")))  return(hostalias_localhost);
-       if (inetcfg == NULL)                                    return(hostalias_nomatch);
-
-       config_lines = num_tokens(inetcfg, '\n');
-       for (i=0; i<config_lines; ++i) {
-               extract_token(buf, inetcfg, i, '\n', sizeof buf);
-               extract_token(host, buf, 0, '|', sizeof host);
-               extract_token(type, buf, 1, '|', sizeof type);
-
-               found = 0;
-
-               // Process these in a specific order, in case there are multiple matches.
-               // We want localhost to override masq, for example.
-
-               if ( (!strcasecmp(type, "masqdomain")) && (!strcasecmp(fqdn, host))) {
-                       found = hostalias_masq;
-               }
-
-               if ( (!strcasecmp(type, "localhost")) && (!strcasecmp(fqdn, host))) {
-                       found = hostalias_localhost;
-               }
-
-               // "directory" used to be a distributed version of "localhost" but they're both the same now
-               if ( (!strcasecmp(type, "directory")) && (!strcasecmp(fqdn, host))) {
-                       found = hostalias_localhost;
-               }
-
-               if (found) return(found);
-       }
-       return(hostalias_nomatch);
-}
-
-
-// Determine whether a given Internet address belongs to the current user
-int CtdlIsMe(char *addr, int addr_buf_len) {
-       struct recptypes *recp;
-       int i;
-
-       recp = validate_recipients(addr, NULL, 0);
-       if (recp == NULL) return(0);
-
-       if (recp->num_local == 0) {
-               free_recipients(recp);
-               return(0);
-       }
-
-       for (i=0; i<recp->num_local; ++i) {
-               extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
-               if (!strcasecmp(addr, CC->user.fullname)) {
-                       free_recipients(recp);
-                       return(1);
-               }
-       }
-
-       free_recipients(recp);
-       return(0);
-}
-
-
-// If the last item in a list of recipients was truncated to a partial address,
-// remove it completely in order to avoid choking library functions.
-void sanitize_truncated_recipient(char *str) {
-       if (!str) return;
-       if (num_tokens(str, ',') < 2) return;
-
-       int len = strlen(str);
-       if (len < 900) return;
-       if (len > 998) str[998] = 0;
-
-       char *cptr = strrchr(str, ',');
-       if (!cptr) return;
-
-       char *lptr = strchr(cptr, '<');
-       char *rptr = strchr(cptr, '>');
-
-       if ( (lptr) && (rptr) && (rptr > lptr) ) return;
-
-       *cptr = 0;
-}
-
-
-// This function is self explanatory.
-// (What can I say, I'm in a weird mood today...)
-void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name) {
-       char *ptr;
-       if (!name) return;
-
-       for (ptr=name; *ptr; ++ptr) {
-               while ( (isspace(*ptr)) && (*(ptr+1)=='@') ) {
-                       strcpy(ptr, ptr+1);
-                       if (ptr > name) --ptr;
-               }
-               while ( (*ptr=='@') && (*(ptr+1)!=0) && (isspace(*(ptr+1))) ) {
-                       strcpy(ptr+1, ptr+2);
-               }
-       }
-}
-
-
-// values that can be returned by expand_aliases()
-enum {
-       EA_ERROR,               // Can't send message due to bad address
-       EA_MULTIPLE,            // Alias expanded into multiple recipients -- run me again!
-       EA_LOCAL,               // Local message, do no network processing
-       EA_INTERNET,            // Convert msg and send as Internet mail
-       EA_SKIP                 // This recipient has been invalidated -- skip it!
-};
-
-
-// Process alias and routing info for email addresses
-int expand_aliases(char *name, char *aliases) {
-       int a;
-       char aaa[SIZ];
-       int at = 0;
-
-       if (aliases) {
-               int num_aliases = num_tokens(aliases, '\n');
-               for (a=0; a<num_aliases; ++a) {
-                       extract_token(aaa, aliases, a, '\n', sizeof aaa);
-                       char *bar = strchr(aaa, '|');
-                       if (bar) {
-                               bar[0] = 0;
-                               ++bar;
-                               striplt(aaa);
-                               striplt(bar);
-                               if ( (!IsEmptyStr(aaa)) && (!strcasecmp(name, aaa)) ) {
-                                       syslog(LOG_DEBUG, "internet_addressing: global alias <%s> to <%s>", name, bar);
-                                       strcpy(name, bar);
-                               }
-                       }
-               }
-               if (strchr(name, ',')) {
-                       return(EA_MULTIPLE);
-               }
-       }
-
-       char original_name[256];                                // Now go for the regular aliases
-       safestrncpy(original_name, name, sizeof original_name);
-
-       // should these checks still be here, or maybe move them to split_recps() ?
-       striplt(name);
-       remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
-       stripallbut(name, '<', '>');
-
-       // Hit the email address directory
-       if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
-               strcpy(name, aaa);
-       }
-
-       if (strcasecmp(original_name, name)) {
-               syslog(LOG_INFO, "internet_addressing: directory alias <%s> to <%s>", original_name, name);
-       }
-
-       // Change "user @ xxx" to "user" if xxx is an alias for this host
-       for (a=0; name[a] != '\0'; ++a) {
-               if (name[a] == '@') {
-                       if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
-                               name[a] = 0;
-                               syslog(LOG_DEBUG, "internet_addressing: host is local, recipient is <%s>", name);
-                               break;
-                       }
-               }
-       }
-
-       // Is this a local or remote recipient?
-       at = haschar(name, '@');
-       if (at == 0) {
-               return(EA_LOCAL);                       // no @'s = local address
-       }
-       else if (at == 1) {
-               return(EA_INTERNET);                    // one @ = internet address
-       }
-       else {
-               return(EA_ERROR);                       // more than one @ = badly formed address
-       }
-}
-
-
-// Return a supplied list of email addresses as an array, removing superfluous information and syntax.
-// If an existing Array is supplied as "append_to" it will do so; otherwise a new Array is allocated.
-Array *split_recps(char *addresses, Array *append_to) {
-
-       if (IsEmptyStr(addresses)) {            // nothing supplied, nothing returned
-               return(NULL);
-       }
-
-       // Copy the supplied address list into our own memory space, because we are going to modify it.
-       char *a = strdup(addresses);
-       if (a == NULL) {
-               syslog(LOG_ERR, "internet_addressing: malloc() failed: %m");
-               return(NULL);
-       }
-
-       // Strip out anything in double quotes
-       char *l = NULL;
-       char *r = NULL;
-       do {
-               l = strchr(a, '\"');
-               r = strrchr(a, '\"');
-               if (r > l) {
-                       strcpy(l, r+1);
-               }
-       } while (r > l);
-
-       // Transform all qualifying delimiters to commas
-       char *t;
-       for (t=a; t[0]; ++t) {
-               if ((t[0]==';') || (t[0]=='|')) {
-                       t[0]=',';
-               }
-       }
-
-       // Tokenize the recipients into an array.  No single recipient should be larger than 256 bytes.
-       Array *recipients_array = NULL;
-       if (append_to) {
-               recipients_array = append_to;                   // Append to an existing array of recipients
-       }
-       else {
-               recipients_array = array_new(256);              // This is a new array of recipients
-       }
-
-       int num_addresses = num_tokens(a, ',');
-       int i;
-       for (i=0; i<num_addresses; ++i) {
-               char this_address[256];
-               extract_token(this_address, a, i, ',', sizeof this_address);
-               striplt(this_address);                          // strip leading and trailing whitespace
-               stripout(this_address, '(', ')');               // remove any portion in parentheses
-               stripallbut(this_address, '<', '>');            // if angle brackets are present, keep only what is inside them
-               if (!IsEmptyStr(this_address)) {
-                       array_append(recipients_array, this_address);
-               }
-       }
-
-       free(a);                                                // We don't need this buffer anymore.
-       return(recipients_array);                               // Return the completed array to the caller.
-}
-
-
-// Validate recipients, count delivery types and errors, and handle aliasing
-//
-// Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses 
-// were specified, or the number of addresses found invalid.
-//
-// Caller needs to free the result using free_recipients()
-//
-struct recptypes *validate_recipients(char *supplied_recipients, const char *RemoteIdentifier, int Flags) {
-       struct recptypes *ret;
-       char *recipients = NULL;
-       char append[SIZ];
-       long len;
-       int mailtype;
-       int invalid;
-       struct ctdluser tempUS;
-       struct ctdlroom original_room;
-       int err = 0;
-       char errmsg[SIZ];
-       char *org_recp;
-       char this_recp[256];
-
-       ret = (struct recptypes *) malloc(sizeof(struct recptypes));                    // Initialize
-       if (ret == NULL) return(NULL);
-       memset(ret, 0, sizeof(struct recptypes));                                       // set all values to null/zero
-
-       if (supplied_recipients == NULL) {
-               recipients = strdup("");
-       }
-       else {
-               recipients = strdup(supplied_recipients);
-       }
-
-       len = strlen(recipients) + 1024;                                                // allocate memory
-       ret->errormsg = malloc(len);
-       ret->recp_local = malloc(len);
-       ret->recp_internet = malloc(len);
-       ret->recp_room = malloc(len);
-       ret->display_recp = malloc(len);
-       ret->recp_orgroom = malloc(len);
-
-       ret->errormsg[0] = 0;
-       ret->recp_local[0] = 0;
-       ret->recp_internet[0] = 0;
-       ret->recp_room[0] = 0;
-       ret->recp_orgroom[0] = 0;
-       ret->display_recp[0] = 0;
-       ret->recptypes_magic = RECPTYPES_MAGIC;
-
-       Array *recp_array = split_recps(supplied_recipients, NULL);
-
-       char *aliases = CtdlGetSysConfig(GLOBAL_ALIASES);                               // First hit the Global Alias Table
-
-       int r;
-       for (r=0; (recp_array && r<array_len(recp_array)); ++r) {
-               org_recp = (char *)array_get_element_at(recp_array, r);
-               strncpy(this_recp, org_recp, sizeof this_recp);
-
-               int i;
-               for (i=0; i<3; ++i) {                                           // pass three times through the aliaser
-                       mailtype = expand_aliases(this_recp, aliases);
-       
-                       // If an alias expanded to multiple recipients, strip off those recipients and append them
-                       // to the end of the array.  This loop will hit those again when it gets there.
-                       if (mailtype == EA_MULTIPLE) {
-                               recp_array = split_recps(this_recp, recp_array);
-                       }
-               }
-
-               // This loop searches for duplicate recipients in the final list and marks them to be skipped.
-               int j;
-               for (j=0; j<r; ++j) {
-                       if (!strcasecmp(this_recp, (char *)array_get_element_at(recp_array, j) )) {
-                               mailtype = EA_SKIP;
-                       }
-               }
-
-               syslog(LOG_DEBUG, "Recipient #%d of type %d is <%s>", r, mailtype, this_recp);
-               invalid = 0;
-               errmsg[0] = 0;
-               switch(mailtype) {
-               case EA_LOCAL:                                  // There are several types of "local" recipients.
-
-                       // Old BBS conventions require mail to "sysop" to go somewhere.  Send it to the admin room.
-                       if (!strcasecmp(this_recp, "sysop")) {
-                               ++ret->num_room;
-                               strcpy(this_recp, CtdlGetConfigStr("c_aideroom"));
-                               if (!IsEmptyStr(ret->recp_room)) {
-                                       strcat(ret->recp_room, "|");
-                               }
-                               strcat(ret->recp_room, this_recp);
-                       }
-
-                       // This handles rooms which can receive posts via email.
-                       else if (!strncasecmp(this_recp, "room_", 5)) {
-                               original_room = CC->room;                               // Remember where we parked
-
-                               char mail_to_room[ROOMNAMELEN];
-                               char *m;
-                               strncpy(mail_to_room, &this_recp[5], sizeof mail_to_room);
-                               for (m = mail_to_room; *m; ++m) {
-                                       if (m[0] == '_') m[0]=' ';
-                               }
-                               if (!CtdlGetRoom(&CC->room, mail_to_room)) {            // Find the room they asked for
-
-                                       err = CtdlDoIHavePermissionToPostInThisRoom(    // check for write permissions to room
-                                               errmsg, 
-                                               sizeof errmsg, 
-                                               RemoteIdentifier,
-                                               Flags,
-                                               0                                       // 0 means "this is not a reply"
-                                       );
-                                       if (err) {
-                                               ++ret->num_error;
-                                               invalid = 1;
-                                       } 
-                                       else {
-                                               ++ret->num_room;
-                                               if (!IsEmptyStr(ret->recp_room)) {
-                                                       strcat(ret->recp_room, "|");
-                                               }
-                                               strcat(ret->recp_room, CC->room.QRname);
-       
-                                               if (!IsEmptyStr(ret->recp_orgroom)) {
-                                                       strcat(ret->recp_orgroom, "|");
-                                               }
-                                               strcat(ret->recp_orgroom, this_recp);
-       
-                                       }
-                               }
-                               else {                                                  // no such room exists
-                                       ++ret->num_error;
-                                       invalid = 1;
-                               }
-                                               
-                               // Restore this session's original room location.
-                               CC->room = original_room;
-
-                       }
-
-                       // This handles the most common case, which is mail to a user's inbox.
-                       else if (CtdlGetUser(&tempUS, this_recp) == 0) {
-                               ++ret->num_local;
-                               strcpy(this_recp, tempUS.fullname);
-                               if (!IsEmptyStr(ret->recp_local)) {
-                                       strcat(ret->recp_local, "|");
-                               }
-                               strcat(ret->recp_local, this_recp);
-                       }
-
-                       // No match for this recipient
-                       else {
-                               ++ret->num_error;
-                               invalid = 1;
-                       }
-                       break;
-               case EA_INTERNET:
-                       // Yes, you're reading this correctly: if the target domain points back to the local system,
-                       // the address is invalid.  That's because if the address were valid, we would have
-                       // already translated it to a local address by now.
-                       if (IsDirectory(this_recp, 0)) {
-                               ++ret->num_error;
-                               invalid = 1;
-                       }
-                       else {
-                               ++ret->num_internet;
-                               if (!IsEmptyStr(ret->recp_internet)) {
-                                       strcat(ret->recp_internet, "|");
-                               }
-                               strcat(ret->recp_internet, this_recp);
-                       }
-                       break;
-               case EA_MULTIPLE:
-               case EA_SKIP:
-                       // no action required, anything in this slot has already been processed elsewhere
-                       break;
-               case EA_ERROR:
-                       ++ret->num_error;
-                       invalid = 1;
-                       break;
-               }
-               if (invalid) {
-                       if (IsEmptyStr(errmsg)) {
-                               snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
-                       }
-                       else {
-                               snprintf(append, sizeof append, "%s", errmsg);
-                       }
-                       if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
-                               if (!IsEmptyStr(ret->errormsg)) {
-                                       strcat(ret->errormsg, "; ");
-                               }
-                               strcat(ret->errormsg, append);
-                       }
-               }
-               else {
-                       if (IsEmptyStr(ret->display_recp)) {
-                               strcpy(append, this_recp);
-                       }
-                       else {
-                               snprintf(append, sizeof append, ", %s", this_recp);
-                       }
-                       if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
-                               strcat(ret->display_recp, append);
-                       }
-               }
-       }
-
-       if (aliases != NULL) {          // ok, we're done with the global alias list now
-               free(aliases);
-       }
-
-       if ( (ret->num_local + ret->num_internet + ret->num_room + ret->num_error) == 0) {
-               ret->num_error = (-1);
-               strcpy(ret->errormsg, "No recipients specified.");
-       }
-
-       syslog(LOG_DEBUG, "internet_addressing: validate_recipients() = %d local, %d room, %d SMTP, %d error",
-               ret->num_local, ret->num_room, ret->num_internet, ret->num_error
-       );
-
-       free(recipients);
-       if (recp_array) {
-               array_free(recp_array);
-       }
-
-       return(ret);
-}
-
-
-// Destructor for recptypes
-void free_recipients(struct recptypes *valid) {
-
-       if (valid == NULL) {
-               return;
-       }
-
-       if (valid->recptypes_magic != RECPTYPES_MAGIC) {
-               syslog(LOG_ERR, "internet_addressing: attempt to call free_recipients() on some other data type!");
-               abort();
-       }
-
-       if (valid->errormsg != NULL)            free(valid->errormsg);
-       if (valid->recp_local != NULL)          free(valid->recp_local);
-       if (valid->recp_internet != NULL)       free(valid->recp_internet);
-       if (valid->recp_room != NULL)           free(valid->recp_room);
-       if (valid->recp_orgroom != NULL)        free(valid->recp_orgroom);
-       if (valid->display_recp != NULL)        free(valid->display_recp);
-       if (valid->bounce_to != NULL)           free(valid->bounce_to);
-       if (valid->envelope_from != NULL)       free(valid->envelope_from);
-       if (valid->sending_room != NULL)        free(valid->sending_room);
-       free(valid);
-}
-
-
-char *qp_encode_email_addrs(char *source) {
-       char *user, *node, *name;
-       const char headerStr[] = "=?UTF-8?Q?";
-       char *Encoded;
-       char *EncodedName;
-       char *nPtr;
-       int need_to_encode = 0;
-       long SourceLen;
-       long EncodedMaxLen;
-       long nColons = 0;
-       long *AddrPtr;
-       long *AddrUtf8;
-       long nAddrPtrMax = 50;
-       long nmax;
-       int InQuotes = 0;
-       int i, n;
-
-       if (source == NULL) return source;
-       if (IsEmptyStr(source)) return source;
-       syslog(LOG_DEBUG, "internet_addressing: qp_encode_email_addrs <%s>", source);
-
-       AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
-       AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
-       memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
-       *AddrPtr = 0;
-       i = 0;
-       while (!IsEmptyStr (&source[i])) {
-               if (nColons >= nAddrPtrMax){
-                       long *ptr;
-
-                       ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
-                       memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
-                       free (AddrPtr), AddrPtr = ptr;
-
-                       ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
-                       memset(&ptr[nAddrPtrMax], 0, sizeof (long) * nAddrPtrMax);
-
-                       memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
-                       free (AddrUtf8), AddrUtf8 = ptr;
-                       nAddrPtrMax *= 2;                               
-               }
-               if (((unsigned char) source[i] < 32) || ((unsigned char) source[i] > 126)) {
-                       need_to_encode = 1;
-                       AddrUtf8[nColons] = 1;
-               }
-               if (source[i] == '"') {
-                       InQuotes = !InQuotes;
-               }
-               if (!InQuotes && source[i] == ',') {
-                       AddrPtr[nColons] = i;
-                       nColons++;
-               }
-               i++;
-       }
-       if (need_to_encode == 0) {
-               free(AddrPtr);
-               free(AddrUtf8);
-               return source;
-       }
-
-       SourceLen = i;
-       EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
-       Encoded = (char*) malloc (EncodedMaxLen);
-
-       for (i = 0; i < nColons; i++) {
-               source[AddrPtr[i]++] = '\0';
-       }
-       // TODO: if libidn, this might get larger
-       user = malloc(SourceLen + 1);
-       node = malloc(SourceLen + 1);
-       name = malloc(SourceLen + 1);
-
-       nPtr = Encoded;
-       *nPtr = '\0';
-       for (i = 0; i < nColons && nPtr != NULL; i++) {
-               nmax = EncodedMaxLen - (nPtr - Encoded);
-               if (AddrUtf8[i]) {
-                       process_rfc822_addr(&source[AddrPtr[i]], user, node, name);
-                       // TODO: libIDN here !
-                       if (IsEmptyStr(name)) {
-                               n = snprintf(nPtr, nmax, (i==0)?"%s@%s" : ",%s@%s", user, node);
-                       }
-                       else {
-                               EncodedName = rfc2047encode(name, strlen(name));                        
-                               n = snprintf(nPtr, nmax, (i==0)?"%s <%s@%s>" : ",%s <%s@%s>", EncodedName, user, node);
-                               free(EncodedName);
-                       }
-               }
-               else { 
-                       n = snprintf(nPtr, nmax, (i==0)?"%s" : ",%s", &source[AddrPtr[i]]);
-               }
-               if (n > 0 )
-                       nPtr += n;
-               else { 
-                       char *ptr, *nnPtr;
-                       ptr = (char*) malloc(EncodedMaxLen * 2);
-                       memcpy(ptr, Encoded, EncodedMaxLen);
-                       nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
-                       free(Encoded), Encoded = ptr;
-                       EncodedMaxLen *= 2;
-                       i--; // do it once more with properly lengthened buffer
-               }
-       }
-       for (i = 0; i < nColons; i++)
-               source[--AddrPtr[i]] = ',';
-
-       free(user);
-       free(node);
-       free(name);
-       free(AddrUtf8);
-       free(AddrPtr);
-       return Encoded;
-}
-
-
-// Unfold a multi-line field into a single line, removing multi-whitespaces
-void unfold_rfc822_field(char **field, char **FieldEnd) 
-{
-       int quote = 0;
-       char *pField = *field;
-       char *sField;
-       char *pFieldEnd = *FieldEnd;
-
-       while (isspace(*pField))
-               pField++;
-       // remove leading/trailing whitespace
-       ;
-
-       while (isspace(*pFieldEnd))
-               pFieldEnd --;
-
-       *FieldEnd = pFieldEnd;
-       // convert non-space whitespace to spaces, and remove double blanks
-       for (sField = *field = pField; 
-            sField < pFieldEnd; 
-            pField++, sField++)
-       {
-               if ((*sField=='\r') || (*sField=='\n'))
-               {
-                       int offset = 1;
-                       while ( ( (*(sField + offset) == '\r') || (*(sField + offset) == '\n' )) && (sField + offset < pFieldEnd) ) {
-                               offset ++;
-                       }
-                       sField += offset;
-                       *pField = *sField;
-               }
-               else {
-                       if (*sField=='\"') quote = 1 - quote;
-                       if (!quote) {
-                               if (isspace(*sField)) {
-                                       *pField = ' ';
-                                       pField++;
-                                       sField++;
-                                       
-                                       while ((sField < pFieldEnd) && 
-                                              isspace(*sField))
-                                               sField++;
-                                       *pField = *sField;
-                               }
-                               else *pField = *sField;
-                       }
-                       else *pField = *sField;
-               }
-       }
-       *pField = '\0';
-       *FieldEnd = pField - 1;
-}
-
-
-// Split an RFC822-style address into userid, host, and full name
-//
-// Note: This still handles obsolete address syntaxes such as user%node@node and ...node!user
-//       We should probably remove that.
-void process_rfc822_addr(const char *rfc822, char *user, char *node, char *name) {
-       int a;
-
-       strcpy(user, "");
-       strcpy(node, CtdlGetConfigStr("c_fqdn"));
-       strcpy(name, "");
-
-       if (rfc822 == NULL) return;
-
-       // extract full name - first, it's From minus <userid>
-       strcpy(name, rfc822);
-       stripout(name, '<', '>');
-
-       // strip anything to the left of a bang
-       while ((!IsEmptyStr(name)) && (haschar(name, '!') > 0))
-               strcpy(name, &name[1]);
-
-       // and anything to the right of a @ or %
-       for (a = 0; name[a] != '\0'; ++a) {
-               if (name[a] == '@') {
-                       name[a] = 0;
-                       break;
-               }
-               if (name[a] == '%') {
-                       name[a] = 0;
-                       break;
-               }
-       }
-
-       // but if there are parentheses, that changes the rules...
-       if ((haschar(rfc822, '(') == 1) && (haschar(rfc822, ')') == 1)) {
-               strcpy(name, rfc822);
-               stripallbut(name, '(', ')');
-       }
-
-       // but if there are a set of quotes, that supersedes everything
-       if (haschar(rfc822, 34) == 2) {
-               strcpy(name, rfc822);
-               while ((!IsEmptyStr(name)) && (name[0] != 34)) {
-                       strcpy(&name[0], &name[1]);
-               }
-               strcpy(&name[0], &name[1]);
-               for (a = 0; name[a] != '\0'; ++a)
-                       if (name[a] == 34) {
-                               name[a] = 0;
-                               break;
-                       }
-       }
-       // extract user id
-       strcpy(user, rfc822);
-
-       // first get rid of anything in parens
-       stripout(user, '(', ')');
-
-       // if there's a set of angle brackets, strip it down to that
-       if ((haschar(user, '<') == 1) && (haschar(user, '>') == 1)) {
-               stripallbut(user, '<', '>');
-       }
-
-       // strip anything to the left of a bang
-       while ((!IsEmptyStr(user)) && (haschar(user, '!') > 0))
-               strcpy(user, &user[1]);
-
-       // and anything to the right of a @ or %
-       for (a = 0; user[a] != '\0'; ++a) {
-               if (user[a] == '@') {
-                       user[a] = 0;
-                       break;
-               }
-               if (user[a] == '%') {
-                       user[a] = 0;
-                       break;
-               }
-       }
-
-
-       // extract node name
-       strcpy(node, rfc822);
-
-       // first get rid of anything in parens
-       stripout(node, '(', ')');
-
-       // if there's a set of angle brackets, strip it down to that
-       if ((haschar(node, '<') == 1) && (haschar(node, '>') == 1)) {
-               stripallbut(node, '<', '>');
-       }
-
-       // If no node specified, tack ours on instead
-       if (
-               (haschar(node, '@')==0)
-               && (haschar(node, '%')==0)
-               && (haschar(node, '!')==0)
-       ) {
-               strcpy(node, CtdlGetConfigStr("c_nodename"));
-       }
-       else {
-
-               // strip anything to the left of a @
-               while ((!IsEmptyStr(node)) && (haschar(node, '@') > 0))
-                       strcpy(node, &node[1]);
-       
-               // strip anything to the left of a %
-               while ((!IsEmptyStr(node)) && (haschar(node, '%') > 0))
-                       strcpy(node, &node[1]);
-       
-               // reduce multiple system bang paths to node!user
-               while ((!IsEmptyStr(node)) && (haschar(node, '!') > 1))
-                       strcpy(node, &node[1]);
-       
-               // now get rid of the user portion of a node!user string
-               for (a = 0; node[a] != '\0'; ++a)
-                       if (node[a] == '!') {
-                               node[a] = 0;
-                               break;
-                       }
-       }
-
-       // strip leading and trailing spaces in all strings
-       striplt(user);
-       striplt(node);
-       striplt(name);
-
-       // If we processed a string that had the address in angle brackets
-       // but no name outside the brackets, we now have an empty name.  In
-       // this case, use the user portion of the address as the name.
-       if ((IsEmptyStr(name)) && (!IsEmptyStr(user))) {
-               strcpy(name, user);
-       }
-}
-
-
-// convert_field() is a helper function for convert_internet_message().
-// Given start/end positions for an rfc822 field, it converts it to a Citadel
-// field if it wants to, and unfolds it if necessary.
-//
-// Returns 1 if the field was converted and inserted into the Citadel message
-// structure, implying that the source field should be removed from the
-// message text.
-int convert_field(struct CtdlMessage *msg, const char *beg, const char *end) {
-       char *key, *value, *valueend;
-       long len;
-       const char *pos;
-       int i;
-       const char *colonpos = NULL;
-       int processed = 0;
-       char user[1024];
-       char node[1024];
-       char name[1024];
-       char addr[1024];
-       time_t parsed_date;
-       long valuelen;
-
-       for (pos = end; pos >= beg; pos--) {
-               if (*pos == ':') colonpos = pos;
-       }
-
-       if (colonpos == NULL) return(0);        /* no colon? not a valid header line */
-
-       len = end - beg;
-       key = malloc(len + 2);
-       memcpy(key, beg, len + 1);
-       key[len] = '\0';
-       valueend = key + len;
-       * ( key + (colonpos - beg) ) = '\0';
-       value = &key[(colonpos - beg) + 1];
-       // printf("Header: [%s]\nValue: [%s]\n", key, value);
-       unfold_rfc822_field(&value, &valueend);
-       valuelen = valueend - value + 1;
-       // printf("UnfoldedValue: [%s]\n", value);
-
-       // Here's the big rfc822-to-citadel loop.
-
-       // Date/time is converted into a unix timestamp.  If the conversion
-       // fails, we replace it with the time the message arrived locally.
-       if (!strcasecmp(key, "Date")) {
-               parsed_date = parsedate(value);
-               if (parsed_date < 0L) parsed_date = time(NULL);
-
-               if (CM_IsEmpty(msg, eTimestamp))
-                       CM_SetFieldLONG(msg, eTimestamp, parsed_date);
-               processed = 1;
-       }
-
-       else if (!strcasecmp(key, "From")) {
-               process_rfc822_addr(value, user, node, name);
-               syslog(LOG_DEBUG, "internet_addressing: converted to <%s@%s> (%s)", user, node, name);
-               snprintf(addr, sizeof(addr), "%s@%s", user, node);
-               if (CM_IsEmpty(msg, eAuthor) && !IsEmptyStr(name)) {
-                       CM_SetField(msg, eAuthor, name, -1);
-               }
-               if (CM_IsEmpty(msg, erFc822Addr) && !IsEmptyStr(addr)) {
-                       CM_SetField(msg, erFc822Addr, addr, -1);
-               }
-               processed = 1;
-       }
-
-       else if (!strcasecmp(key, "Subject")) {
-               if (CM_IsEmpty(msg, eMsgSubject))
-                       CM_SetField(msg, eMsgSubject, value, valuelen);
-               processed = 1;
-       }
-
-       else if (!strcasecmp(key, "List-ID")) {
-               if (CM_IsEmpty(msg, eListID))
-                       CM_SetField(msg, eListID, value, valuelen);
-               processed = 1;
-       }
-
-       else if (!strcasecmp(key, "To")) {
-               if (CM_IsEmpty(msg, eRecipient))
-                       CM_SetField(msg, eRecipient, value, valuelen);
-               processed = 1;
-       }
-
-       else if (!strcasecmp(key, "CC")) {
-               if (CM_IsEmpty(msg, eCarbonCopY))
-                       CM_SetField(msg, eCarbonCopY, value, valuelen);
-               processed = 1;
-       }
-
-       else if (!strcasecmp(key, "Message-ID")) {
-               if (!CM_IsEmpty(msg, emessageId)) {
-                       syslog(LOG_WARNING, "internet_addressing: duplicate message id");
-               }
-               else {
-                       char *pValue;
-                       long pValueLen;
-
-                       pValue = value;
-                       pValueLen = valuelen;
-                       // Strip angle brackets
-                       while (haschar(pValue, '<') > 0) {
-                               pValue ++;
-                               pValueLen --;
-                       }
-
-                       for (i = 0; i <= pValueLen; ++i)
-                               if (pValue[i] == '>') {
-                                       pValueLen = i;
-                                       break;
-                               }
-
-                       CM_SetField(msg, emessageId, pValue, pValueLen);
-               }
-
-               processed = 1;
-       }
-
-       else if (!strcasecmp(key, "Return-Path")) {
-               if (CM_IsEmpty(msg, eMessagePath))
-                       CM_SetField(msg, eMessagePath, value, valuelen);
-               processed = 1;
-       }
-
-       else if (!strcasecmp(key, "Envelope-To")) {
-               if (CM_IsEmpty(msg, eenVelopeTo))
-                       CM_SetField(msg, eenVelopeTo, value, valuelen);
-               processed = 1;
-       }
-
-       else if (!strcasecmp(key, "References")) {
-               CM_SetField(msg, eWeferences, value, valuelen);
-               processed = 1;
-       }
-
-       else if (!strcasecmp(key, "Reply-To")) {
-               CM_SetField(msg, eReplyTo, value, valuelen);
-               processed = 1;
-       }
-
-       else if (!strcasecmp(key, "In-reply-to")) {
-               if (CM_IsEmpty(msg, eWeferences)) // References: supersedes In-reply-to:
-                       CM_SetField(msg, eWeferences, value, valuelen);
-               processed = 1;
-       }
-
-
-
-       // Clean up and move on.
-       free(key);      // Don't free 'value', it's actually the same buffer
-       return processed;
-}
-
-
-// Convert RFC822 references format (References) to Citadel references format (Weferences)
-void convert_references_to_wefewences(char *str) {
-       int bracket_nesting = 0;
-       char *ptr = str;
-       char *moveptr = NULL;
-       char ch;
-
-       while(*ptr) {
-               ch = *ptr;
-               if (ch == '>') {
-                       --bracket_nesting;
-                       if (bracket_nesting < 0) bracket_nesting = 0;
-               }
-               if ((ch == '>') && (bracket_nesting == 0) && (*(ptr+1)) && (ptr>str) ) {
-                       *ptr = '|';
-                       ++ptr;
-               }
-               else if (bracket_nesting > 0) {
-                       ++ptr;
-               }
-               else {
-                       moveptr = ptr;
-                       while (*moveptr) {
-                               *moveptr = *(moveptr+1);
-                               ++moveptr;
-                       }
-               }
-               if (ch == '<') ++bracket_nesting;
-       }
-
-}
-
-
-// Convert an RFC822 message (headers + body) to a CtdlMessage structure.
-// NOTE: the supplied buffer becomes part of the CtdlMessage structure, and
-// will be deallocated when CM_Free() is called.  Therefore, the
-// supplied buffer should be DEREFERENCED.  It should not be freed or used
-// again.
-struct CtdlMessage *convert_internet_message(char *rfc822) {
-       StrBuf *RFCBuf = NewStrBufPlain(rfc822, -1);
-       free (rfc822);
-       return convert_internet_message_buf(&RFCBuf);
-}
-
-
-struct CtdlMessage *convert_internet_message_buf(StrBuf **rfc822)
-{
-       struct CtdlMessage *msg;
-       const char *pos, *beg, *end, *totalend;
-       int done, alldone = 0;
-       int converted;
-       StrBuf *OtherHeaders;
-
-       msg = malloc(sizeof(struct CtdlMessage));
-       if (msg == NULL) return msg;
-
-       memset(msg, 0, sizeof(struct CtdlMessage));
-       msg->cm_magic = CTDLMESSAGE_MAGIC;      // self check
-       msg->cm_anon_type = 0;                  // never anonymous
-       msg->cm_format_type = FMT_RFC822;       // internet message
-
-       pos = ChrPtr(*rfc822);
-       totalend = pos + StrLength(*rfc822);
-       done = 0;
-       OtherHeaders = NewStrBufPlain(NULL, StrLength(*rfc822));
-
-       while (!alldone) {
-
-               /* Locate beginning and end of field, keeping in mind that
-                * some fields might be multiline
-                */
-               end = beg = pos;
-
-               while ((end < totalend) && 
-                      (end == beg) && 
-                      (done == 0) ) 
-               {
-
-                       if ( (*pos=='\n') && ((*(pos+1))!=0x20) && ((*(pos+1))!=0x09) )
-                       {
-                               end = pos;
-                       }
-
-                       /* done with headers? */
-                       if ((*pos=='\n') &&
-                           ( (*(pos+1)=='\n') ||
-                             (*(pos+1)=='\r')) ) 
-                       {
-                               alldone = 1;
-                       }
-
-                       if (pos >= (totalend - 1) )
-                       {
-                               end = pos;
-                               done = 1;
-                       }
-
-                       ++pos;
-
-               }
-
-               /* At this point we have a field.  Are we interested in it? */
-               converted = convert_field(msg, beg, end);
-
-               /* Strip the field out of the RFC822 header if we used it */
-               if (!converted) {
-                       StrBufAppendBufPlain(OtherHeaders, beg, end - beg, 0);
-                       StrBufAppendBufPlain(OtherHeaders, HKEY("\n"), 0);
-               }
-
-               /* If we've hit the end of the message, bail out */
-               if (pos >= totalend)
-                       alldone = 1;
-       }
-       StrBufAppendBufPlain(OtherHeaders, HKEY("\n"), 0);
-       if (pos < totalend)
-               StrBufAppendBufPlain(OtherHeaders, pos, totalend - pos, 0);
-       FreeStrBuf(rfc822);
-       CM_SetAsFieldSB(msg, eMesageText, &OtherHeaders);
-
-       /* Follow-up sanity checks... */
-
-       /* If there's no timestamp on this message, set it to now. */
-       if (CM_IsEmpty(msg, eTimestamp)) {
-               CM_SetFieldLONG(msg, eTimestamp, time(NULL));
-       }
-
-       /* If a W (references, or rather, Wefewences) field is present, we
-        * have to convert it from RFC822 format to Citadel format.
-        */
-       if (!CM_IsEmpty(msg, eWeferences)) {
-               /// todo: API!
-               convert_references_to_wefewences(msg->cm_fields[eWeferences]);
-       }
-
-       return msg;
-}
-
-
-/*
- * Look for a particular header field in an RFC822 message text.  If the
- * requested field is found, it is unfolded (if necessary) and returned to
- * the caller.  The field name is stripped out, leaving only its contents.
- * The caller is responsible for freeing the returned buffer.  If the requested
- * field is not present, or anything else goes wrong, it returns NULL.
- */
-char *rfc822_fetch_field(const char *rfc822, const char *fieldname) {
-       char *fieldbuf = NULL;
-       const char *end_of_headers;
-       const char *field_start;
-       const char *ptr;
-       char *cont;
-       char fieldhdr[SIZ];
-
-       /* Should never happen, but sometimes we get stupid */
-       if (rfc822 == NULL) return(NULL);
-       if (fieldname == NULL) return(NULL);
-
-       snprintf(fieldhdr, sizeof fieldhdr, "%s:", fieldname);
-
-       /* Locate the end of the headers, so we don't run past that point */
-       end_of_headers = cbmstrcasestr(rfc822, "\n\r\n");
-       if (end_of_headers == NULL) {
-               end_of_headers = cbmstrcasestr(rfc822, "\n\n");
-       }
-       if (end_of_headers == NULL) return (NULL);
-
-       field_start = cbmstrcasestr(rfc822, fieldhdr);
-       if (field_start == NULL) return(NULL);
-       if (field_start > end_of_headers) return(NULL);
-
-       fieldbuf = malloc(SIZ);
-       strcpy(fieldbuf, "");
-
-       ptr = field_start;
-       ptr = cmemreadline(ptr, fieldbuf, SIZ-strlen(fieldbuf) );
-       while ( (isspace(ptr[0])) && (ptr < end_of_headers) ) {
-               strcat(fieldbuf, " ");
-               cont = &fieldbuf[strlen(fieldbuf)];
-               ptr = cmemreadline(ptr, cont, SIZ-strlen(fieldbuf) );
-               striplt(cont);
-       }
-
-       strcpy(fieldbuf, &fieldbuf[strlen(fieldhdr)]);
-       striplt(fieldbuf);
-
-       return(fieldbuf);
-}
-
-
-/*****************************************************************************
- *                      DIRECTORY MANAGEMENT FUNCTIONS                       *
- *****************************************************************************/
-
-/*
- * Generate the index key for an Internet e-mail address to be looked up
- * in the database.
- */
-void directory_key(char *key, char *addr) {
-       int i;
-       int keylen = 0;
-
-       for (i=0; !IsEmptyStr(&addr[i]); ++i) {
-               if (!isspace(addr[i])) {
-                       key[keylen++] = tolower(addr[i]);
-               }
-       }
-       key[keylen++] = 0;
-
-       syslog(LOG_DEBUG, "internet_addressing: directory key is <%s>", key);
-}
-
-
-/*
- * Return nonzero if the supplied address is in one of "our" domains
- */
-int IsDirectory(char *addr, int allow_masq_domains) {
-       char domain[256];
-       int h;
-
-       extract_token(domain, addr, 1, '@', sizeof domain);
-       striplt(domain);
-
-       h = CtdlHostAlias(domain);
-
-       if ( (h == hostalias_masq) && allow_masq_domains)
-               return(1);
-       
-       if (h == hostalias_localhost) {
-               return(1);
-       }
-       else {
-               return(0);
-       }
-}
-
-
-/*
- * Add an Internet e-mail address to the directory for a user
- */
-int CtdlDirectoryAddUser(char *internet_addr, char *citadel_addr) {
-       char key[SIZ];
-
-       if (IsDirectory(internet_addr, 0) == 0) {
-               return 0;
-       }
-       syslog(LOG_DEBUG, "internet_addressing: create directory entry: %s --> %s", internet_addr, citadel_addr);
-       directory_key(key, internet_addr);
-       cdb_store(CDB_DIRECTORY, key, strlen(key), citadel_addr, strlen(citadel_addr)+1 );
-       return 1;
-}
-
-
-/*
- * Delete an Internet e-mail address from the directory.
- *
- * (NOTE: we don't actually use or need the citadel_addr variable; it's merely
- * here because the callback API expects to be able to send it.)
- */
-int CtdlDirectoryDelUser(char *internet_addr, char *citadel_addr) {
-       char key[SIZ];
-       
-       syslog(LOG_DEBUG, "internet_addressing: delete directory entry: %s --> %s", internet_addr, citadel_addr);
-       directory_key(key, internet_addr);
-       return cdb_delete(CDB_DIRECTORY, key, strlen(key) ) == 0;
-}
-
-
-/*
- * Look up an Internet e-mail address in the directory.
- * On success: returns 0, and Citadel address stored in 'target'
- * On failure: returns nonzero
- */
-int CtdlDirectoryLookup(char *target, char *internet_addr, size_t targbuflen) {
-       struct cdbdata *cdbrec;
-       char key[SIZ];
-
-       /* Dump it in there unchanged, just for kicks */
-       if (target != NULL) {
-               safestrncpy(target, internet_addr, targbuflen);
-       }
-
-       /* Only do lookups for addresses with hostnames in them */
-       if (num_tokens(internet_addr, '@') != 2) return(-1);
-
-       /* Only do lookups for domains in the directory */
-       if (IsDirectory(internet_addr, 0) == 0) return(-1);
-
-       directory_key(key, internet_addr);
-       cdbrec = cdb_fetch(CDB_DIRECTORY, key, strlen(key) );
-       if (cdbrec != NULL) {
-               if (target != NULL) {
-                       safestrncpy(target, cdbrec->ptr, targbuflen);
-               }
-               cdb_free(cdbrec);
-               return(0);
-       }
-
-       return(-1);
-}
-
-
-/*
- * Harvest any email addresses that someone might want to have in their
- * "collected addresses" book.
- */
-char *harvest_collected_addresses(struct CtdlMessage *msg) {
-       char *coll = NULL;
-       char addr[256];
-       char user[256], node[256], name[256];
-       int is_harvestable;
-       int i, j, h;
-       eMsgField field = 0;
-
-       if (msg == NULL) return(NULL);
-
-       is_harvestable = 1;
-       strcpy(addr, "");       
-       if (!CM_IsEmpty(msg, eAuthor)) {
-               strcat(addr, msg->cm_fields[eAuthor]);
-       }
-       if (!CM_IsEmpty(msg, erFc822Addr)) {
-               strcat(addr, " <");
-               strcat(addr, msg->cm_fields[erFc822Addr]);
-               strcat(addr, ">");
-               if (IsDirectory(msg->cm_fields[erFc822Addr], 0)) {
-                       is_harvestable = 0;
-               }
-       }
-
-       if (is_harvestable) {
-               coll = strdup(addr);
-       }
-       else {
-               coll = strdup("");
-       }
-
-       if (coll == NULL) return(NULL);
-
-       /* Scan both the R (To) and Y (CC) fields */
-       for (i = 0; i < 2; ++i) {
-               if (i == 0) field = eRecipient;
-               if (i == 1) field = eCarbonCopY;
-
-               if (!CM_IsEmpty(msg, field)) {
-                       for (j=0; j<num_tokens(msg->cm_fields[field], ','); ++j) {
-                               extract_token(addr, msg->cm_fields[field], j, ',', sizeof addr);
-                               if (strstr(addr, "=?") != NULL)
-                                       utf8ify_rfc822_string(addr);
-                               process_rfc822_addr(addr, user, node, name);
-                               h = CtdlHostAlias(node);
-                               if (h != hostalias_localhost) {
-                                       coll = realloc(coll, strlen(coll) + strlen(addr) + 4);
-                                       if (coll == NULL) return(NULL);
-                                       if (!IsEmptyStr(coll)) {
-                                               strcat(coll, ",");
-                                       }
-                                       striplt(addr);
-                                       strcat(coll, addr);
-                               }
-                       }
-               }
-       }
-
-       if (IsEmptyStr(coll)) {
-               free(coll);
-               return(NULL);
-       }
-       return(coll);
-}
-
-
-/*
- * Helper function for CtdlRebuildDirectoryIndex()
- */
-void CtdlRebuildDirectoryIndex_backend(char *username, void *data) {
-
-       int j = 0;
-       struct ctdluser usbuf;
-
-       if (CtdlGetUser(&usbuf, username) != 0) {
-               return;
-       }
-
-       if ( (!IsEmptyStr(usbuf.fullname)) && (!IsEmptyStr(usbuf.emailaddrs)) ) {
-               for (j=0; j<num_tokens(usbuf.emailaddrs, '|'); ++j) {
-                       char one_email[512];
-                       extract_token(one_email, usbuf.emailaddrs, j, '|', sizeof one_email);
-                       CtdlDirectoryAddUser(one_email, usbuf.fullname);
-               }
-       }
-}
-
-
-/*
- * Initialize the directory database (erasing anything already there)
- */
-void CtdlRebuildDirectoryIndex(void) {
-       syslog(LOG_INFO, "internet_addressing: rebuilding email address directory index");
-       cdb_trunc(CDB_DIRECTORY);
-       ForEachUser(CtdlRebuildDirectoryIndex_backend, NULL);
-}
-
-
-// Configure Internet email addresses for a user account, updating the Directory Index in the process
-void CtdlSetEmailAddressesForUser(char *requested_user, char *new_emailaddrs) {
-       struct ctdluser usbuf;
-       int i;
-       char buf[SIZ];
-
-       if (CtdlGetUserLock(&usbuf, requested_user) != 0) {     // We can lock because the DirectoryIndex functions don't lock.
-               return;                                         // Silently fail here if the specified user does not exist.
-       }
-
-       syslog(LOG_DEBUG, "internet_addressing: setting email addresses for <%s> to <%s>", usbuf.fullname, new_emailaddrs);
-
-       // Delete all of the existing directory index records for the user (easier this way)
-       for (i=0; i<num_tokens(usbuf.emailaddrs, '|'); ++i) {
-               extract_token(buf, usbuf.emailaddrs, i, '|', sizeof buf);
-               CtdlDirectoryDelUser(buf, requested_user);
-       }
-
-       strcpy(usbuf.emailaddrs, new_emailaddrs);               // make it official.
-
-       // Index all of the new email addresses (they've already been sanitized)
-       for (i=0; i<num_tokens(usbuf.emailaddrs, '|'); ++i) {
-               extract_token(buf, usbuf.emailaddrs, i, '|', sizeof buf);
-               CtdlDirectoryAddUser(buf, requested_user);
-       }
-
-       CtdlPutUserLock(&usbuf);
-}
-
-
-/*
- * Auto-generate an Internet email address for a user account
- */
-void AutoGenerateEmailAddressForUser(struct ctdluser *user) {
-       char synthetic_email_addr[1024];
-       int i, j;
-       int u = 0;
-
-       for (i=0; u==0; ++i) {
-               if (i == 0) {
-                       // first try just converting the user name to lowercase and replacing spaces with underscores
-                       snprintf(synthetic_email_addr, sizeof synthetic_email_addr, "%s@%s", user->fullname, CtdlGetConfigStr("c_fqdn"));
-                       for (j=0; ((synthetic_email_addr[j] != '\0')&&(synthetic_email_addr[j] != '@')); j++) {
-                               synthetic_email_addr[j] = tolower(synthetic_email_addr[j]);
-                               if (!isalnum(synthetic_email_addr[j])) {
-                                       synthetic_email_addr[j] = '_';
-                               }
-                       }
-               }
-               else if (i == 1) {
-                       // then try 'ctdl' followed by the user number
-                       snprintf(synthetic_email_addr, sizeof synthetic_email_addr, "ctdl%08lx@%s", user->usernum, CtdlGetConfigStr("c_fqdn"));
-               }
-               else if (i > 1) {
-                       // oof.  just keep trying other numbers until we find one
-                       snprintf(synthetic_email_addr, sizeof synthetic_email_addr, "ctdl%08x@%s", i, CtdlGetConfigStr("c_fqdn"));
-               }
-               u = CtdlDirectoryLookup(NULL, synthetic_email_addr, 0);
-               syslog(LOG_DEBUG, "user_ops: address <%s> lookup returned <%d>", synthetic_email_addr, u);
-       }
-
-       CtdlSetEmailAddressesForUser(user->fullname, synthetic_email_addr);
-       strncpy(CC->user.emailaddrs, synthetic_email_addr, sizeof(user->emailaddrs));
-       syslog(LOG_DEBUG, "user_ops: auto-generated email address <%s> for <%s>", synthetic_email_addr, user->fullname);
-}
-
-
-// Determine whether the supplied email address is subscribed to the supplied room's mailing list service.
-int is_email_subscribed_to_list(char *email, char *room_name) {
-       struct ctdlroom room;
-       long roomnum;
-       char *roomnetconfig;
-       int found_it = 0;
-
-       if (CtdlGetRoom(&room, room_name)) {
-               return(0);                                      // room not found, so definitely not subscribed
-       }
-
-       // If this room has the QR2_SMTP_PUBLIC flag set, anyone may email a post to this room, even non-subscribers.
-       if (room.QRflags2 & QR2_SMTP_PUBLIC) {
-               return(1);
-       }
-
-       roomnum = room.QRnumber;
-       roomnetconfig = LoadRoomNetConfigFile(roomnum);
-       if (roomnetconfig == NULL) {
-               return(0);
-       }
-
-       // We're going to do a very sloppy match here and simply search for the specified email address
-       // anywhere in the room's netconfig.  If you don't like this, fix it yourself.
-       if (bmstrcasestr(roomnetconfig, email)) {
-               found_it = 1;
-       }
-       else {
-               found_it = 0;
-       }
-
-       free(roomnetconfig);
-       return(found_it);
-}
diff --git a/citadel/internet_addressing.h b/citadel/internet_addressing.h
deleted file mode 100644 (file)
index 4430335..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-
-#include "server.h"
-#include "ctdl_module.h"
-
-struct recptypes *validate_recipients(char *recipients, const char *RemoteIdentifier, int Flags);
-void free_recipients(struct recptypes *);
-void process_rfc822_addr(const char *rfc822, char *user, char *node, char *name);
-char *rfc822_fetch_field(const char *rfc822, const char *fieldname);
-void sanitize_truncated_recipient(char *str);
-char *qp_encode_email_addrs(char *source);
-int alias (char *name);
-int IsDirectory(char *addr, int allow_masq_domains);
-void CtdlRebuildDirectoryIndex(void);
-int CtdlDirectoryAddUser(char *internet_addr, char *citadel_addr);
-int CtdlDirectoryDelUser(char *internet_addr, char *citadel_addr);
-int CtdlDirectoryLookup(char *target, char *internet_addr, size_t targbuflen);
-void CtdlSetEmailAddressesForUser(char *requested_user, char *new_emailaddrs);
-void AutoGenerateEmailAddressForUser(struct ctdluser *user);
-struct CtdlMessage *convert_internet_message(char *rfc822);
-struct CtdlMessage *convert_internet_message_buf(StrBuf **rfc822);
-int CtdlIsMe(char *addr, int addr_buf_len);
-int CtdlHostAlias(char *fqdn);
-char *harvest_collected_addresses(struct CtdlMessage *msg);
-int is_email_subscribed_to_list(char *email, char *room_name);
-
-/* 
- * Values that can be returned by CtdlHostAlias()
- */
-enum {
-       hostalias_nomatch,
-       hostalias_localhost,
-       hostalias_masq
-};
-
-extern char *inetcfg;
diff --git a/citadel/ipcdef.h b/citadel/ipcdef.h
deleted file mode 100644 (file)
index 23897e4..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define LISTING_FOLLOWS                100
-#define CIT_OK                 200
-#define MORE_DATA              300
-#define SEND_LISTING           400
-#define ERROR                  500
-#define BINARY_FOLLOWS         600
-#define SEND_BINARY            700
-#define START_CHAT_MODE                800
-
-#define INTERNAL_ERROR         10
-#define TOO_BIG                        11
-#define ILLEGAL_VALUE          12
-#define NOT_LOGGED_IN          20
-#define CMD_NOT_SUPPORTED      30
-#define SERVER_SHUTTING_DOWN   31
-#define PASSWORD_REQUIRED      40
-#define ALREADY_LOGGED_IN      41
-#define USERNAME_REQUIRED      42
-#define HIGHER_ACCESS_REQUIRED 50
-#define MAX_SESSIONS_EXCEEDED  51
-#define RESOURCE_BUSY          52
-#define RESOURCE_NOT_OPEN      53
-#define NOT_HERE               60
-#define INVALID_FLOOR_OPERATION        61
-#define NO_SUCH_USER           70
-#define FILE_NOT_FOUND         71
-#define ROOM_NOT_FOUND         72
-#define NO_SUCH_SYSTEM         73
-#define ALREADY_EXISTS         74
-#define MESSAGE_NOT_FOUND      75
-
-#define ASYNC_MSG              900
-#define ASYNC_GEXP             02
-
-#define QR_PERMANENT   1               /* Room does not purge              */
-#define QR_INUSE       2               /* Set if in use, clear if avail    */
-#define QR_PRIVATE     4               /* Set for any type of private room */
-#define QR_PASSWORDED  8               /* Set if there's a password too    */
-#define QR_GUESSNAME   16              /* Set if it's a guessname room     */
-#define QR_DIRECTORY   32              /* Directory room                   */
-#define QR_UPLOAD      64              /* Allowed to upload                */
-#define QR_DOWNLOAD    128             /* Allowed to download              */
-#define QR_VISDIR      256             /* Visible directory                */
-#define QR_ANONONLY    512             /* Anonymous-Only room              */
-#define QR_ANONOPT     1024            /* Anonymous-Option room            */
-#define QR_NETWORK     2048            /* Shared network room              */
-#define QR_PREFONLY    4096            /* Preferred status needed to enter */
-#define QR_READONLY    8192            /* Aide status required to post     */
-#define QR_MAILBOX     16384           /* Set if this is a private mailbox */
-
-#define QR2_SYSTEM     1               /* System room; hide by default     */
-#define QR2_SELFLIST   2               /* Self-service mailing list mgmt   */
-#define QR2_COLLABDEL  4               /* Anyone who can post can delete   */
-#define QR2_SUBJECTREQ 8               /* Subject strongly recommended */
-#define QR2_SMTP_PUBLIC        16              /* Listservice Subscribers may post */
-
-#define US_NEEDVALID   1               /* User needs to be validated       */
-#define US_EXTEDIT     2               /* Always use external editor       */
-#define US_PERM                4               /* Permanent user                   */
-#define US_LASTOLD     16              /* Print last old message with new  */
-#define US_EXPERT      32              /* Experienced user                 */
-#define US_UNLISTED    64              /* Unlisted userlog entry           */
-#define US_NOPROMPT    128             /* Don't prompt after each message  */
-#define US_PROMPTCTL   256             /* <N>ext & <S>top work at prompt   */
-#define US_DISAPPEAR   512             /* Use "disappearing msg prompts"   */
-#define US_REGIS       1024            /* Registered user                  */
-#define US_PAGINATOR   2048            /* Pause after each screen of text  */
-#define US_INTERNET    4096            /* Internet mail privileges         */
-#define US_FLOORS      8192            /* User wants to see floors         */
-#define US_COLOR       16384           /* User wants ANSI color support    */
-#define US_USER_SET    (US_LASTOLD | US_EXPERT | US_UNLISTED | \
-                       US_NOPROMPT | US_DISAPPEAR | US_PAGINATOR | \
-                       US_FLOORS | US_COLOR | US_PROMPTCTL | US_EXTEDIT)
-
-#define UA_KNOWN                2      /* Room appears in a 'known rooms' list */
-#define UA_GOTOALLOWED          4      /* User may goto this room if specified by exact name */
-#define UA_HASNEWMSGS           8      /* Unread messages exist in this room */
-#define UA_ZAPPED               16     /* User has forgotten (zapped) this room */
-#define UA_POSTALLOWED         32      /* User may post top-level messages here */
-#define UA_ADMINALLOWED                64      /* Aide or Room Aide rights exist here */
-#define UA_DELETEALLOWED       128     /* User is allowed to delete messages from this room */
-#define UA_REPLYALLOWED                256     /* User is allowed to reply to existing messages here */
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/citadel/journaling.c b/citadel/journaling.c
deleted file mode 100644 (file)
index 939ec3b..0000000
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Message journaling functions.
- *
- * Copyright (c) 1987-2020 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include <stdio.h>
-#include <libcitadel.h>
-#include "ctdl_module.h"
-#include "citserver.h"
-#include "config.h"
-#include "user_ops.h"
-#include "serv_vcard.h"                        /* Needed for vcard_getuser and extract_inet_email_addrs */
-#include "internet_addressing.h"
-#include "journaling.h"
-
-struct jnlq *jnlq = NULL;      /* journal queue */
-
-/*
- * Hand off a copy of a message to be journalized.
- */
-void JournalBackgroundSubmit(struct CtdlMessage *msg,
-                       StrBuf *saved_rfc822_version,
-                       struct recptypes *recps) {
-
-       struct jnlq *jptr = NULL;
-
-       /* Avoid double journaling! */
-       if (!CM_IsEmpty(msg, eJournal)) {
-               FreeStrBuf(&saved_rfc822_version);
-               return;
-       }
-
-       jptr = (struct jnlq *)malloc(sizeof(struct jnlq));
-       if (jptr == NULL) {
-               FreeStrBuf(&saved_rfc822_version);
-               return;
-       }
-       memset(jptr, 0, sizeof(struct jnlq));
-       if (recps != NULL) memcpy(&jptr->recps, recps, sizeof(struct recptypes));
-       if (!CM_IsEmpty(msg, eAuthor)) jptr->from = strdup(msg->cm_fields[eAuthor]);
-       if (!CM_IsEmpty(msg, erFc822Addr)) jptr->rfca = strdup(msg->cm_fields[erFc822Addr]);
-       if (!CM_IsEmpty(msg, eMsgSubject)) jptr->subj = strdup(msg->cm_fields[eMsgSubject]);
-       if (!CM_IsEmpty(msg, emessageId)) jptr->msgn = strdup(msg->cm_fields[emessageId]);
-       jptr->rfc822 = SmashStrBuf(&saved_rfc822_version);
-
-       /* Add to the queue */
-       begin_critical_section(S_JOURNAL_QUEUE);
-       jptr->next = jnlq;
-       jnlq = jptr;
-       end_critical_section(S_JOURNAL_QUEUE);
-}
-
-
-/*
- * Convert a local user name to an internet email address for the journal
- * FIXME - grab the user's Internet email address from the user record, not from vCard !!!!
- */
-void local_to_inetemail(char *inetemail, char *localuser, size_t inetemail_len) {
-       struct ctdluser us;
-       struct vCard *v;
-
-       strcpy(inetemail, "");
-       if (CtdlGetUser(&us, localuser) != 0) {
-               return;
-       }
-
-       v = vcard_get_user(&us);
-       if (v == NULL) {
-               return;
-       }
-
-       extract_inet_email_addrs(inetemail, inetemail_len, NULL, 0, v, 1);
-       vcard_free(v);
-}
-
-
-/*
- * Called by JournalRunQueue() to send an individual message.
- */
-void JournalRunQueueMsg(struct jnlq *jmsg) {
-
-       struct CtdlMessage *journal_msg = NULL;
-       struct recptypes *journal_recps = NULL;
-       StrBuf *message_text = NULL;
-       char mime_boundary[256];
-       long mblen;
-       long rfc822len;
-       char recipient[256];
-       char inetemail[256];
-       static int seq = 0;
-       int i;
-
-       if (jmsg == NULL)
-               return;
-       journal_recps = validate_recipients(CtdlGetConfigStr("c_journal_dest"), NULL, 0);
-       if (journal_recps != NULL) {
-
-               if (  (journal_recps->num_local > 0)
-                  || (journal_recps->num_internet > 0)
-                  || (journal_recps->num_room > 0)
-               ) {
-
-                       /*
-                        * Construct journal message.
-                        * Note that we are transferring ownership of some of the memory here.
-                        */
-                       journal_msg = malloc(sizeof(struct CtdlMessage));
-                       memset(journal_msg, 0, sizeof(struct CtdlMessage));
-                       journal_msg->cm_magic = CTDLMESSAGE_MAGIC;
-                       journal_msg->cm_anon_type = MES_NORMAL;
-                       journal_msg->cm_format_type = FMT_RFC822;
-                       CM_SetField(journal_msg, eJournal, HKEY("is journal"));
-
-                       if (!IsEmptyStr(jmsg->from)) {
-                               CM_SetField(journal_msg, eAuthor, jmsg->from, -1);
-                       }
-
-                       if (!IsEmptyStr(jmsg->rfca)) {
-                               CM_SetField(journal_msg, erFc822Addr, jmsg->rfca, -1);
-                       }
-
-                       if (!IsEmptyStr(jmsg->subj)) {
-                               CM_SetField(journal_msg, eMsgSubject, jmsg->subj, -1);
-                       }
-
-                       mblen = snprintf(mime_boundary, sizeof(mime_boundary),
-                                        "--Citadel-Journal-%08lx-%04x--", time(NULL), ++seq);
-
-                       if (!IsEmptyStr(jmsg->rfc822)) {
-                               rfc822len = strlen(jmsg->rfc822);
-                       }
-                       else {
-                               rfc822len = 0;
-                       }
-
-                       message_text = NewStrBufPlain(NULL, rfc822len + sizeof(struct recptypes) + 1024);
-
-                       /*
-                        * Here is where we begin to compose the journalized message.
-                        * (The "ExJournalReport" header is consumed by some email retention services which assume the journaling agent is Exchange.)
-                        */
-                       StrBufAppendBufPlain(
-                               message_text, 
-                               HKEY("Content-type: multipart/mixed; boundary=\""),
-                               0
-                       );
-
-                       StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
-
-                       StrBufAppendBufPlain(
-                               message_text, 
-                               HKEY("\"\r\n"
-                                    "Content-Identifier: ExJournalReport\r\n"
-                                    "MIME-Version: 1.0\r\n"
-                                    "\n"
-                                    "--"),
-                               0
-                       );
-
-                       StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
-
-                       StrBufAppendBufPlain(
-                               message_text, 
-                               HKEY("\r\n"
-                                    "Content-type: text/plain\r\n"
-                                    "\r\n"
-                                    "Sender: "), 0);
-
-                       if (CM_IsEmpty(journal_msg, eAuthor))
-                               StrBufAppendBufPlain(
-                                       message_text, 
-                                       journal_msg->cm_fields[eAuthor], -1, 0);
-                       else
-                               StrBufAppendBufPlain(
-                                       message_text, 
-                                       HKEY("(null)"), 0);
-
-                       if (!CM_IsEmpty(journal_msg, erFc822Addr)) {
-                               StrBufAppendPrintf(message_text, " <%s>",
-                                                  journal_msg->cm_fields[erFc822Addr]);
-                       }
-
-                       StrBufAppendBufPlain(message_text, HKEY("\r\nMessage-ID: <"), 0);
-                       StrBufAppendBufPlain(message_text, jmsg->msgn, -1, 0);
-                       StrBufAppendBufPlain(message_text, HKEY(">\r\nRecipients:\r\n"), 0);
-
-                       if (jmsg->recps.num_local > 0) {
-                               for (i=0; i<jmsg->recps.num_local; ++i) {
-                                       extract_token(recipient, jmsg->recps.recp_local, i, '|', sizeof recipient);
-                                       local_to_inetemail(inetemail, recipient, sizeof inetemail);
-                                       StrBufAppendPrintf(message_text, "      %s <%s>\r\n", recipient, inetemail);
-                               }
-                       }
-
-                       if (jmsg->recps.num_internet > 0) {
-                               for (i=0; i<jmsg->recps.num_internet; ++i) {
-                                       extract_token(recipient, jmsg->recps.recp_internet, i, '|', sizeof recipient);
-                                       StrBufAppendPrintf(message_text, "      %s\r\n", recipient);
-                               }
-                       }
-
-                       StrBufAppendBufPlain(message_text, HKEY("\r\n" "--"), 0);
-                       StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
-                       StrBufAppendBufPlain(message_text, HKEY("\r\nContent-type: message/rfc822\r\n\r\n"), 0);
-                       StrBufAppendBufPlain(message_text, jmsg->rfc822, rfc822len, 0);
-                       StrBufAppendBufPlain(message_text, HKEY("--"), 0);
-                       StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
-                       StrBufAppendBufPlain(message_text, HKEY("--\r\n"), 0);
-
-                       CM_SetAsFieldSB(journal_msg, eMesageText, &message_text);
-                       free(jmsg->rfc822);
-                       free(jmsg->msgn);
-                       jmsg->rfc822 = NULL;
-                       jmsg->msgn = NULL;
-                       
-                       /* Submit journal message */
-                       CtdlSubmitMsg(journal_msg, journal_recps, "");
-                       CM_Free(journal_msg);
-               }
-
-               free_recipients(journal_recps);
-       }
-
-       /* We are responsible for freeing this memory. */
-       free(jmsg);
-}
-
-
-/*
- * Run the queue.
- */
-void JournalRunQueue(void) {
-       struct jnlq *jptr = NULL;
-
-       while (jnlq != NULL) {
-               begin_critical_section(S_JOURNAL_QUEUE);
-               if (jnlq != NULL) {
-                       jptr = jnlq;
-                       jnlq = jnlq->next;
-               }
-               end_critical_section(S_JOURNAL_QUEUE);
-               JournalRunQueueMsg(jptr);
-       }
-}
-
-
diff --git a/citadel/journaling.h b/citadel/journaling.h
deleted file mode 100644 (file)
index 4a66781..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-struct jnlq {
-       struct jnlq *next;
-       struct recptypes recps;
-       char *from;
-       char *node;
-       char *rfca;
-       char *subj;
-       char *msgn;
-       char *rfc822;
-};
-
-void JournalBackgroundSubmit(struct CtdlMessage *msg,
-                        StrBuf *saved_rfc822_version,
-                        struct recptypes *recps);
-void JournalRunQueueMsg(struct jnlq *jmsg);
-void JournalRunQueue(void);
diff --git a/citadel/ldap.c b/citadel/ldap.c
deleted file mode 100644 (file)
index 5c54953..0000000
+++ /dev/null
@@ -1,631 +0,0 @@
-// These functions implement the portions of AUTHMODE_LDAP and AUTHMODE_LDAP_AD which
-// actually speak to the LDAP server.
-//
-// Copyright (c) 2011-2022 by the citadel.org development team.
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// ldapsearch -D uid=admin,cn=users,cn=compat,dc=demo1,dc=freeipa,dc=org -w Secret123 -h ipa.demo1.freeipa.org
-
-int ctdl_require_ldap_version = 3;
-
-#define _GNU_SOURCE            // Needed to suppress warning about vasprintf() when running on Linux/Linux
-#include <stdio.h>
-#include <libcitadel.h>
-#include "citserver.h"
-#include "citadel_ldap.h"
-#include "ctdl_module.h"
-#include "user_ops.h"
-#include "internet_addressing.h"
-#include "config.h"
-#include <ldap.h>
-
-
-// Utility function, supply a search result and get back the fullname (display name, common name, etc) from the first result
-//
-// POSIX schema:       the display name will be found in "cn" (common name)
-// Active Directory:   the display name will be found in "displayName"
-//
-void derive_fullname_from_ldap_result(char *fullname, int fullname_size, LDAP *ldserver, LDAPMessage *search_result) {
-       struct berval **values;
-
-       if (fullname == NULL) return;
-       if (search_result == NULL) return;
-       if (ldserver == NULL) return;
-
-       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
-               values = ldap_get_values_len(ldserver, search_result, "displayName");
-               if (values) {
-                       if (ldap_count_values_len(values) > 0) {
-                               safestrncpy(fullname, values[0]->bv_val, fullname_size);
-                               syslog(LOG_DEBUG, "ldap: displayName = %s", fullname);
-                       }
-                       ldap_value_free_len(values);
-               }
-       }
-       else {
-               values = ldap_get_values_len(ldserver, search_result, "cn");
-               if (values) {
-                       if (ldap_count_values_len(values) > 0) {
-                               safestrncpy(fullname, values[0]->bv_val, fullname_size);
-                               syslog(LOG_DEBUG, "ldap: cn = %s", fullname);
-                       }
-                       ldap_value_free_len(values);
-               }
-       }
-}
-
-
-// Utility function, supply a search result and get back the uid from the first result
-//
-// POSIX schema:       numeric user id will be in the "uidNumber" attribute
-// Active Directory:   we make a uid hashed from "objectGUID"
-//
-uid_t derive_uid_from_ldap(LDAP *ldserver, LDAPMessage *entry) {
-       struct berval **values;
-       uid_t uid = (-1);
-
-       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
-               values = ldap_get_values_len(ldserver, entry, "objectGUID");
-               if (values) {
-                       if (ldap_count_values_len(values) > 0) {
-                               uid = abs(HashLittle(values[0]->bv_val, values[0]->bv_len));
-                       }
-                       ldap_value_free_len(values);
-               }
-       }
-       else {
-               values = ldap_get_values_len(ldserver, entry, "uidNumber");
-               if (values) {
-                       if (ldap_count_values_len(values) > 0) {
-                               uid = atoi(values[0]->bv_val);
-                       }
-                       ldap_value_free_len(values);
-               }
-       }
-
-       syslog(LOG_DEBUG, "ldap: uid = %d", uid);
-       return(uid);
-}
-
-
-// Wrapper function for ldap_initialize() that consistently fills in the correct fields
-int ctdl_ldap_initialize(LDAP **ld) {
-
-       char server_url[256];
-       int ret;
-
-       snprintf(server_url, sizeof server_url, "ldap://%s:%d", CtdlGetConfigStr("c_ldap_host"), CtdlGetConfigInt("c_ldap_port"));
-       ret = ldap_initialize(ld, server_url);
-       if (ret != LDAP_SUCCESS) {
-               syslog(LOG_ERR, "ldap: could not connect to %s : %m", server_url);
-               *ld = NULL;
-               return(errno);
-       }
-
-       return(ret);
-}
-
-
-// Bind to the LDAP server and return a working handle
-LDAP *ctdl_ldap_bind(void) {
-       LDAP *ldserver = NULL;
-       int i;
-
-       if (ctdl_ldap_initialize(&ldserver) != LDAP_SUCCESS) {
-               return(NULL);
-       }
-
-       ldap_set_option(ldserver, LDAP_OPT_PROTOCOL_VERSION, &ctdl_require_ldap_version);
-       ldap_set_option(ldserver, LDAP_OPT_REFERRALS, (void *)LDAP_OPT_OFF);
-
-       striplt(CtdlGetConfigStr("c_ldap_bind_dn"));
-       striplt(CtdlGetConfigStr("c_ldap_bind_pw"));
-       i = ldap_simple_bind_s(ldserver,
-               (!IsEmptyStr(CtdlGetConfigStr("c_ldap_bind_dn")) ? CtdlGetConfigStr("c_ldap_bind_dn") : NULL),
-               (!IsEmptyStr(CtdlGetConfigStr("c_ldap_bind_pw")) ? CtdlGetConfigStr("c_ldap_bind_pw") : NULL)
-       );
-       if (i != LDAP_SUCCESS) {
-               syslog(LOG_ERR, "ldap: Cannot bind: %s (%d)", ldap_err2string(i), i);
-               return(NULL);
-       }
-
-       return(ldserver);
-}
-
-
-// Look up a user in the directory to see if this is an account that can be authenticated
-//
-// POSIX schema:       Search all "inetOrgPerson" objects with "uid" set to the supplied username
-// Active Directory:   Look for an account with "sAMAccountName" set to the supplied username
-//
-int CtdlTryUserLDAP(char *username, char *found_dn, int found_dn_size, char *fullname, int fullname_size, uid_t *uid) {
-       LDAP *ldserver = NULL;
-       LDAPMessage *search_result = NULL;
-       LDAPMessage *entry = NULL;
-       char searchstring[1024];
-       struct timeval tv;
-       char *user_dn = NULL;
-
-       ldserver = ctdl_ldap_bind();
-       if (!ldserver) return(-1);
-
-       if (fullname) safestrncpy(fullname, username, fullname_size);
-       tv.tv_sec = 10;
-       tv.tv_usec = 0;
-
-       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
-               snprintf(searchstring, sizeof(searchstring), "(sAMAccountName=%s)", username);
-       }
-       else {
-               snprintf(searchstring, sizeof(searchstring), "(&(objectclass=inetOrgPerson)(uid=%s))", username);
-       }
-
-       syslog(LOG_DEBUG, "ldap: search: %s", searchstring);
-       syslog(LOG_DEBUG, "ldap: search results: %s", ldap_err2string(ldap_search_ext_s(
-               ldserver,                                       // ld
-               CtdlGetConfigStr("c_ldap_base_dn"),             // base
-               LDAP_SCOPE_SUBTREE,                             // scope
-               searchstring,                                   // filter
-               NULL,                                           // attrs (all attributes)
-               0,                                              // attrsonly (attrs + values)
-               NULL,                                           // serverctrls (none)
-               NULL,                                           // clientctrls (none)
-               &tv,                                            // timeout
-               1,                                              // sizelimit (1 result max)
-               &search_result                                  // put the result here
-       )));
-
-       // Ignore the return value of ldap_search_ext_s().  Sometimes it returns an error even when
-       // the search succeeds.  Instead, we check to see whether search_result is still NULL.
-       if (search_result == NULL) {
-               syslog(LOG_DEBUG, "ldap: zero search results were returned");
-               ldap_unbind(ldserver);
-               return(2);
-       }
-
-       // At this point we've got at least one result from our query.  If there are multiple
-       // results, we still only look at the first one.
-       entry = ldap_first_entry(ldserver, search_result);
-       if (entry) {
-
-               user_dn = ldap_get_dn(ldserver, entry);
-               if (user_dn) {
-                       syslog(LOG_DEBUG, "ldap: dn = %s", user_dn);
-               }
-
-               derive_fullname_from_ldap_result(fullname, fullname_size, ldserver, search_result);
-               *uid = derive_uid_from_ldap(ldserver, search_result);
-       }
-
-       // free the results
-       ldap_msgfree(search_result);
-
-       // unbind so we can go back in as the authenticating user
-       ldap_unbind(ldserver);
-
-       if (!user_dn) {
-               syslog(LOG_DEBUG, "ldap: No such user was found.");
-               return(4);
-       }
-
-       if (found_dn) safestrncpy(found_dn, user_dn, found_dn_size);
-       ldap_memfree(user_dn);
-       return(0);
-}
-
-
-// This is an extension of CtdlTryPassword() which gets called when using LDAP authentication.
-int CtdlTryPasswordLDAP(char *user_dn, const char *password) {
-       LDAP *ldserver = NULL;
-       int i = (-1);
-
-       if (IsEmptyStr(password)) {
-               syslog(LOG_DEBUG, "ldap: empty passwords are not permitted");
-               return(1);
-       }
-
-       syslog(LOG_DEBUG, "ldap: trying to bind as %s", user_dn);
-       i = ctdl_ldap_initialize(&ldserver);
-       if (i == LDAP_SUCCESS) {
-               ldap_set_option(ldserver, LDAP_OPT_PROTOCOL_VERSION, &ctdl_require_ldap_version);
-               i = ldap_simple_bind_s(ldserver, user_dn, password);
-               if (i == LDAP_SUCCESS) {
-                       syslog(LOG_DEBUG, "ldap: bind succeeded");
-               }
-               else {
-                       syslog(LOG_DEBUG, "ldap: Cannot bind: %s (%d)", ldap_err2string(i), i);
-               }
-               ldap_set_option(ldserver, LDAP_OPT_REFERRALS, (void *)LDAP_OPT_OFF);
-               ldap_unbind(ldserver);
-       }
-
-       if (i == LDAP_SUCCESS) {
-               return(0);
-       }
-
-       return(1);
-}
-
-
-// set multiple properties
-// returns nonzero only if property changed.
-int vcard_set_props_iff_different(struct vCard *v, char *propname, int numvals, char **vals) {
-       int i;
-       char *oldval = "";
-       for (i=0; i<numvals; i++) {
-               oldval = vcard_get_prop(v, propname, 0, i, 0);
-               if (oldval == NULL) break;
-               if (strcmp(vals[i],oldval)) break;
-       }
-       if (i != numvals) {
-               syslog(LOG_DEBUG, "ldap: vcard property %s, element %d of %d changed from %s to %s", propname, i, numvals, oldval, vals[i]);
-               for (i=0; i<numvals; i++) {
-                       vcard_set_prop(v,propname,vals[i],(i==0) ? 0 : 1);
-               }
-               return 1;
-       }
-       return 0;
-}
-
-
-// set one property
-// returns nonzero only if property changed.
-int vcard_set_one_prop_iff_different(struct vCard *v,char *propname, char *newfmt, ...) {
-       va_list args;
-       char *newvalue;
-       int did_change = 0;
-       va_start(args,newfmt);
-       if (vasprintf(&newvalue, newfmt, args) < 0) {
-               syslog(LOG_ERR, "ldap: out of memory");
-               return 0;
-       }
-       did_change = vcard_set_props_iff_different(v, propname, 1, &newvalue);
-       va_end(args);
-       free(newvalue);
-       return did_change;
-}
-
-
-// Learn LDAP attributes and stuff them into the vCard.
-// Returns nonzero if we changed anything.
-int Ctdl_LDAP_to_vCard(char *ldap_dn, struct vCard *v) {
-       int changed_something = 0;
-       LDAP *ldserver = NULL;
-       struct timeval tv;
-       LDAPMessage *search_result = NULL;
-       LDAPMessage *entry = NULL;
-       struct berval **givenName;
-       struct berval **sn;
-       struct berval **cn;
-       struct berval **initials;
-       struct berval **o;
-       struct berval **street;
-       struct berval **l;
-       struct berval **st;
-       struct berval **postalCode;
-       struct berval **telephoneNumber;
-       struct berval **mobile;
-       struct berval **homePhone;
-       struct berval **facsimileTelephoneNumber;
-       struct berval **mail;
-       struct berval **uid;
-       struct berval **homeDirectory;
-       struct berval **uidNumber;
-       struct berval **loginShell;
-       struct berval **gidNumber;
-       struct berval **c;
-       struct berval **title;
-       struct berval **uuid;
-       char *attrs[] = { "*","+",NULL};
-
-       if (!ldap_dn) return(0);
-       if (!v) return(0);
-
-       ldserver = ctdl_ldap_bind();
-       if (!ldserver) return(-1);
-
-       tv.tv_sec = 10;
-       tv.tv_usec = 0;
-
-       syslog(LOG_DEBUG, "ldap: search: %s", ldap_dn);
-       syslog(LOG_DEBUG, "ldap: search results: %s", ldap_err2string(ldap_search_ext_s(
-               ldserver,                               // ld
-               ldap_dn,                                // base
-               LDAP_SCOPE_SUBTREE,                     // scope
-               NULL,                                   // filter
-               attrs,                                  // attrs (all attributes)
-               0,                                      // attrsonly (attrs + values)
-               NULL,                                   // serverctrls (none)
-               NULL,                                   // clientctrls (none)
-               &tv,                                    // timeout
-               1,                                      // sizelimit (1 result max)
-               &search_result                          // res
-       )));
-       
-       // Ignore the return value of ldap_search_ext_s().  Sometimes it returns an error even when
-       // the search succeeds.  Instead, we check to see whether search_result is still NULL.
-       if (search_result == NULL) {
-               syslog(LOG_DEBUG, "ldap: zero search results were returned");
-               ldap_unbind(ldserver);
-               return(0);
-       }
-
-       // At this point we've got at least one result from our query.  If there are multiple
-       // results, we still only look at the first one.
-       entry = ldap_first_entry(ldserver, search_result);
-       if (entry) {
-               syslog(LOG_DEBUG, "ldap: search got user details for vcard.");
-               givenName                       = ldap_get_values_len(ldserver, search_result, "givenName");
-               sn                              = ldap_get_values_len(ldserver, search_result, "sn");
-               cn                              = ldap_get_values_len(ldserver, search_result, "cn");
-               initials                        = ldap_get_values_len(ldserver, search_result, "initials");
-               title                           = ldap_get_values_len(ldserver, search_result, "title");
-               o                               = ldap_get_values_len(ldserver, search_result, "o");
-               street                          = ldap_get_values_len(ldserver, search_result, "street");
-               l                               = ldap_get_values_len(ldserver, search_result, "l");
-               st                              = ldap_get_values_len(ldserver, search_result, "st");
-               postalCode                      = ldap_get_values_len(ldserver, search_result, "postalCode");
-               telephoneNumber                 = ldap_get_values_len(ldserver, search_result, "telephoneNumber");
-               mobile                          = ldap_get_values_len(ldserver, search_result, "mobile");
-               homePhone                       = ldap_get_values_len(ldserver, search_result, "homePhone");
-               facsimileTelephoneNumber        = ldap_get_values_len(ldserver, search_result, "facsimileTelephoneNumber");
-               mail                            = ldap_get_values_len(ldserver, search_result, "mail");
-               uid                             = ldap_get_values_len(ldserver, search_result, "uid");
-               homeDirectory                   = ldap_get_values_len(ldserver, search_result, "homeDirectory");
-               uidNumber                       = ldap_get_values_len(ldserver, search_result, "uidNumber");
-               loginShell                      = ldap_get_values_len(ldserver, search_result, "loginShell");
-               gidNumber                       = ldap_get_values_len(ldserver, search_result, "gidNumber");
-               c                               = ldap_get_values_len(ldserver, search_result, "c");
-               uuid                            = ldap_get_values_len(ldserver, search_result, "entryUUID");
-
-               if (street && l && st && postalCode && c) changed_something |= vcard_set_one_prop_iff_different(v,"adr",";;%s;%s;%s;%s;%s",street[0]->bv_val,l[0]->bv_val,st[0]->bv_val,postalCode[0]->bv_val,c[0]->bv_val);
-               if (telephoneNumber) changed_something |= vcard_set_one_prop_iff_different(v,"tel;work","%s",telephoneNumber[0]->bv_val);
-               if (facsimileTelephoneNumber) changed_something |= vcard_set_one_prop_iff_different(v,"tel;fax","%s",facsimileTelephoneNumber[0]->bv_val);
-               if (mobile) changed_something |= vcard_set_one_prop_iff_different(v,"tel;cell","%s",mobile[0]->bv_val);
-               if (homePhone) changed_something |= vcard_set_one_prop_iff_different(v,"tel;home","%s",homePhone[0]->bv_val);
-               if (givenName && sn) {
-                       if (initials) {
-                               changed_something |= vcard_set_one_prop_iff_different(v,"n","%s;%s;%s",sn[0]->bv_val,givenName[0]->bv_val,initials[0]->bv_val);
-                       }
-                       else {
-                               changed_something |= vcard_set_one_prop_iff_different(v,"n","%s;%s",sn[0]->bv_val,givenName[0]->bv_val);
-                       }
-               }
-
-               // FIXME we need a new way to do this.
-               //if (mail) {
-                       //changed_something |= vcard_set_props_iff_different(v,"email;internet",ldap_count_values_len(mail),mail);
-               //}
-
-               if (uuid) changed_something |= vcard_set_one_prop_iff_different(v,"X-uuid","%s",uuid[0]->bv_val);
-               if (o) changed_something |= vcard_set_one_prop_iff_different(v,"org","%s",o[0]->bv_val);
-               if (cn) changed_something |= vcard_set_one_prop_iff_different(v,"fn","%s",cn[0]->bv_val);
-               if (title) changed_something |= vcard_set_one_prop_iff_different(v,"title","%s",title[0]->bv_val);
-               
-               if (givenName)                  ldap_value_free_len(givenName);
-               if (initials)                   ldap_value_free_len(initials);
-               if (sn)                         ldap_value_free_len(sn);
-               if (cn)                         ldap_value_free_len(cn);
-               if (o)                          ldap_value_free_len(o);
-               if (street)                     ldap_value_free_len(street);
-               if (l)                          ldap_value_free_len(l);
-               if (st)                         ldap_value_free_len(st);
-               if (postalCode)                 ldap_value_free_len(postalCode);
-               if (telephoneNumber)            ldap_value_free_len(telephoneNumber);
-               if (mobile)                     ldap_value_free_len(mobile);
-               if (homePhone)                  ldap_value_free_len(homePhone);
-               if (facsimileTelephoneNumber)   ldap_value_free_len(facsimileTelephoneNumber);
-               if (mail)                       ldap_value_free_len(mail);
-               if (uid)                        ldap_value_free_len(uid);
-               if (homeDirectory)              ldap_value_free_len(homeDirectory);
-               if (uidNumber)                  ldap_value_free_len(uidNumber);
-               if (loginShell)                 ldap_value_free_len(loginShell);
-               if (gidNumber)                  ldap_value_free_len(gidNumber);
-               if (c)                          ldap_value_free_len(c);
-               if (title)                      ldap_value_free_len(title);
-               if (uuid)                       ldap_value_free_len(uuid);
-       }
-       // free the results
-       ldap_msgfree(search_result);
-
-       // unbind so we can go back in as the authenticating user
-       ldap_unbind(ldserver);
-       return(changed_something);      // tell the caller whether we made any changes
-}
-
-
-// Extract a user's Internet email addresses from LDAP.
-// Returns zero if we got a valid set of addresses; nonzero for error.
-int extract_email_addresses_from_ldap(char *ldap_dn, char *emailaddrs) {
-       LDAP *ldserver = NULL;
-       struct timeval tv;
-       LDAPMessage *search_result = NULL;
-       LDAPMessage *entry = NULL;
-       struct berval **mail;
-       char *attrs[] = { "*","+",NULL };
-
-       if (!ldap_dn) return(1);
-       if (!emailaddrs) return(1);
-
-       ldserver = ctdl_ldap_bind();
-       if (!ldserver) return(-1);
-
-       tv.tv_sec = 10;
-       tv.tv_usec = 0;
-
-       syslog(LOG_DEBUG, "ldap: search: %s", ldap_dn);
-       syslog(LOG_DEBUG, "ldap: search results: %s", ldap_err2string(ldap_search_ext_s(
-               ldserver,                               // ld
-               ldap_dn,                                // base
-               LDAP_SCOPE_SUBTREE,                     // scope
-               NULL,                                   // filter
-               attrs,                                  // attrs (all attributes)
-               0,                                      // attrsonly (attrs + values)
-               NULL,                                   // serverctrls (none)
-               NULL,                                   // clientctrls (none)
-               &tv,                                    // timeout
-               1,                                      // sizelimit (1 result max)
-               &search_result                          // res
-       )));
-       
-       // Ignore the return value of ldap_search_ext_s().  Sometimes it returns an error even when
-       // the search succeeds.  Instead, we check to see whether search_result is still NULL.
-       if (search_result == NULL) {
-               syslog(LOG_DEBUG, "ldap: zero search results were returned");
-               ldap_unbind(ldserver);
-               return(4);
-       }
-
-       // At this point we've got at least one result from our query.
-       // If there are multiple results, we still only look at the first one.
-       emailaddrs[0] = 0;                                                              // clear out any previous results
-       entry = ldap_first_entry(ldserver, search_result);
-       if (entry) {
-               syslog(LOG_DEBUG, "ldap: search got user details");
-               mail = ldap_get_values_len(ldserver, search_result, "mail");
-               if (mail) {
-                       int q;
-                       for (q=0; q<ldap_count_values_len(mail); ++q) {
-                               if (IsDirectory(mail[q]->bv_val, 0)) {
-                                       if ((strlen(emailaddrs) + mail[q]->bv_len + 2) > 512) {
-                                               syslog(LOG_ERR, "ldap: can't fit all email addresses into user record");
-                                       }
-                                       else {
-                                               if (!IsEmptyStr(emailaddrs)) {
-                                                       strcat(emailaddrs, "|");
-                                               }
-                                               strcat(emailaddrs, mail[q]->bv_val);
-                                       }
-                               }
-                       }
-               }
-       }
-
-       // free the results
-       ldap_msgfree(search_result);
-
-       // unbind so we can go back in as the authenticating user
-       ldap_unbind(ldserver);
-       return(0);
-}
-
-
-// Remember that a particular user exists in the Citadel database.
-// As we scan the LDAP tree we will remove users from this list when we find them.
-// At the end of the scan, any users remaining in this list are stale and should be deleted.
-void ldap_note_user_in_citadel(char *username, void *data) {
-       return;
-}
-
-
-// Scan LDAP for users and populate Citadel's user database with everyone
-//
-// POSIX schema:       All objects of class "inetOrgPerson"
-// Active Directory:   Objects that are class "user" and class "person" but NOT class "computer"
-//
-void CtdlSynchronizeUsersFromLDAP(void) {
-       LDAP *ldserver = NULL;
-       LDAPMessage *search_result = NULL;
-       LDAPMessage *entry = NULL;
-       char *user_dn = NULL;
-       char searchstring[1024];
-       struct timeval tv;
-
-       if ((CtdlGetConfigInt("c_auth_mode") != AUTHMODE_LDAP) && (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_LDAP_AD)) {
-               return;                                         // If this site is not running LDAP, stop here.
-       }
-
-       syslog(LOG_INFO, "ldap: synchronizing Citadel user database from LDAP");
-
-       // first, scan the existing Citadel user list
-       // ForEachUser(ldap_note_user_in_citadel, NULL);        // FIXME finish this
-
-       ldserver = ctdl_ldap_bind();
-       if (!ldserver) return;
-
-       tv.tv_sec = 10;
-       tv.tv_usec = 0;
-
-       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
-                       snprintf(searchstring, sizeof(searchstring), "(&(objectClass=user)(objectClass=person)(!(objectClass=computer)))");
-       }
-       else {
-                       snprintf(searchstring, sizeof(searchstring), "(objectClass=inetOrgPerson)");
-       }
-
-       syslog(LOG_DEBUG, "ldap: search: %s", searchstring);
-       syslog(LOG_DEBUG, "ldap: search results: %s", ldap_err2string(ldap_search_ext_s(
-               ldserver,                                       // ld
-               CtdlGetConfigStr("c_ldap_base_dn"),             // base
-               LDAP_SCOPE_SUBTREE,                             // scope
-               searchstring,                                   // filter
-               NULL,                                           // attrs (all attributes)
-               0,                                              // attrsonly (attrs + values)
-               NULL,                                           // serverctrls (none)
-               NULL,                                           // clientctrls (none)
-               &tv,                                            // timeout
-               INT_MAX,                                        // sizelimit (max)
-               &search_result                                  // put the result here
-       )));
-
-       // Ignore the return value of ldap_search_ext_s().  Sometimes it returns an error even when
-       // the search succeeds.  Instead, we check to see whether search_result is still NULL.
-       if (search_result == NULL) {
-               syslog(LOG_DEBUG, "ldap: zero search results were returned");
-               ldap_unbind(ldserver);
-               return;
-       }
-
-       syslog(LOG_DEBUG, "ldap: %d entries returned", ldap_count_entries(ldserver, search_result));
-       for (entry=ldap_first_entry(ldserver, search_result); entry!=NULL; entry=ldap_next_entry(ldserver, entry)) {
-               user_dn = ldap_get_dn(ldserver, entry);
-               if (user_dn) {
-                       syslog(LOG_DEBUG, "ldap: found %s", user_dn);
-
-                       int fullname_size = 256;
-                       char fullname[256] = { 0 } ;
-                       uid_t uid = (-1);
-                       char new_emailaddrs[512] = { 0 } ;
-
-                       uid = derive_uid_from_ldap(ldserver, entry);
-                       derive_fullname_from_ldap_result(fullname, fullname_size, ldserver, entry);
-                       syslog(LOG_DEBUG, "ldap: display name: <%s> , uid = <%d>", fullname, uid);
-
-                       // now create or update the user
-                       int found_user;
-                       struct ctdluser usbuf;
-
-                       found_user = getuserbyuid(&usbuf, uid);
-                       if (found_user != 0) {
-                               create_user(fullname, CREATE_USER_DO_NOT_BECOME_USER, uid);
-                               found_user = getuserbyuid(&usbuf, uid);
-                               strcpy(fullname, usbuf.fullname);
-                       }
-
-                       if (found_user == 0) {          // user record exists
-                                                       // now update the account email addresses if necessary
-                               if (CtdlGetConfigInt("c_ldap_sync_email_addrs") > 0) {
-                                       if (extract_email_addresses_from_ldap(user_dn, new_emailaddrs) == 0) {
-                                               if (strcmp(usbuf.emailaddrs, new_emailaddrs)) {                         // update only if changed
-                                                       CtdlSetEmailAddressesForUser(usbuf.fullname, new_emailaddrs);
-                                               }
-                                       }
-                               }
-                       }
-                       ldap_memfree(user_dn);
-               }
-       }
-
-       // free the results
-       ldap_msgfree(search_result);
-
-       // unbind so we can go back in as the authenticating user
-       ldap_unbind(ldserver);
-}
diff --git a/citadel/locate_host.c b/citadel/locate_host.c
deleted file mode 100644 (file)
index 64879ee..0000000
+++ /dev/null
@@ -1,362 +0,0 @@
-/*
- * Functions which handle hostname/address lookups and resolution
- *
- * Copyright (c) 1987-2019 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <string.h>
-#include <stdio.h>
-#include <syslog.h>
-#include <ctype.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <libcitadel.h>
-
-#include "context.h"
-#ifdef HAVE_RESOLV_H
-#include <arpa/nameser.h>
-#ifdef HAVE_ARPA_NAMESER_COMPAT_H
-#include <arpa/nameser_compat.h>
-#endif
-#include <resolv.h>
-#endif
-
-#include "domain.h"
-#include "locate_host.h"
-
-/* START: some missing macros on OpenBSD 3.9 */
-#ifndef NS_CMPRSFLGS
-#define NS_CMPRSFLGS   0xc0
-#endif
-#if !defined(NS_MAXCDNAME) && defined (MAXCDNAME)
-#define NS_MAXCDNAME MAXCDNAME
-#endif
-#if !defined(NS_INT16SZ) && defined(INT16SZ)
-#define NS_INT16SZ INT16SZ
-#define NS_INT32SZ INT32SZ
-#endif
-#ifndef NS_GET16
-#  define NS_GET16 GETSHORT
-#endif
-/* END: some missing macros on OpenBSD 3.9 */
-
-
-/*
- * Given an open client socket, return the host name and IP address at the other end.
- * (IPv4 and IPv6 compatible)
- */
-void locate_host(char *tbuf, size_t n, char *abuf, size_t na, int client_socket)
-{
-       struct sockaddr_in6 clientaddr;
-       unsigned int addrlen = sizeof(clientaddr);
-
-       tbuf[0] = 0;
-       abuf[0] = 0;
-
-       getpeername(client_socket, (struct sockaddr *)&clientaddr, &addrlen);
-       getnameinfo((struct sockaddr *)&clientaddr, addrlen, tbuf, n, NULL, 0, 0);
-       getnameinfo((struct sockaddr *)&clientaddr, addrlen, abuf, na, NULL, 0, NI_NUMERICHOST);
-
-       /* Convert IPv6-mapped IPv4 addresses back to traditional dotted quad.
-        *
-        * Other code here, such as the RBL check, will expect IPv4 addresses to be represented
-        * as dotted-quad, even if they come in over a hybrid IPv6/IPv4 socket.
-        */
-       if ( (strlen(abuf) > 7) && (!strncasecmp(abuf, "::ffff:", 7)) ) {
-               if (!strcmp(abuf, tbuf)) strcpy(tbuf, &tbuf[7]);
-               strcpy(abuf, &abuf[7]);
-       }
-}
-
-
-/*
- * RBL check written by Edward S. Marshall [http://rblcheck.sourceforge.net]
- */
-#define RESULT_SIZE 4096 /* What is the longest result text we support? */
-int rblcheck_backend(char *domain, char *txtbuf, int txtbufsize) {
-       int a, b, c;
-       char *result = NULL;
-       u_char fixedans[ PACKETSZ ];
-       u_char *answer;
-       int need_to_free_answer = 0;
-       const u_char *cp;
-       u_char *rp;
-       const u_char *cend;
-       const u_char *rend;
-       int len;
-       char *p = NULL;
-       static int res_initted = 0;
-
-       if (!res_initted) {             /* only have to do this once */
-               res_init();
-               res_initted = 1;
-       }
-
-       /* Make our DNS query. */
-       answer = fixedans;
-       if (server_shutting_down) {
-               if (txtbuf != NULL) {
-                       snprintf(txtbuf, txtbufsize, "System shutting down");
-               }
-               return (1);
-       }
-       len = res_query(domain, C_IN, T_A, answer, PACKETSZ);
-
-       /* Was there a problem? If so, the domain doesn't exist. */
-       if (len == -1) {
-               if (txtbuf != NULL) {
-                       strcpy(txtbuf, "");
-               }
-               return(0);
-       }
-
-       if (len > PACKETSZ) {
-               answer = malloc(len);
-               need_to_free_answer = 1;
-               len = res_query(domain, C_IN, T_A, answer, len);
-               if( len == -1 ) {
-                       if (txtbuf != NULL) {
-                               snprintf(txtbuf, txtbufsize, "Message rejected due to known spammer source IP address");
-                       }
-                       if (need_to_free_answer) free(answer);
-                       return(1);
-               }
-       }
-       if (server_shutting_down) {
-               if (txtbuf != NULL) {
-                       snprintf(txtbuf, txtbufsize, "System shutting down");
-               }
-               if (need_to_free_answer) free(answer);
-               return (1);
-       }
-
-       result = (char *) malloc(RESULT_SIZE);
-       result[0] = '\0';
-
-       /* Make another DNS query for textual data; this shouldn't
-        * be a performance hit, since it'll now be cached at the
-        * nameserver we're using.
-        */
-       len = res_query(domain, C_IN, T_TXT, answer, PACKETSZ);
-       if (server_shutting_down) {
-               if (txtbuf != NULL) {
-                       snprintf(txtbuf, txtbufsize, "System shutting down");
-               }
-               if (need_to_free_answer) free(answer);
-               free(result);
-               return (1);
-       }
-
-       /* Just in case there's no TXT record... */
-       if (len ==(-1)) {
-               if (txtbuf != NULL) {
-                       snprintf(txtbuf, txtbufsize, "Message rejected due to known spammer source IP address");
-               }
-               if (need_to_free_answer) free(answer);
-               free(result);
-               return(1);
-       }
-
-       /* Skip the header and the address we queried. */
-       cp = answer + sizeof( HEADER );
-       while( *cp != '\0' ) {
-               a = *cp++;
-               while( a-- )
-                       cp++;
-       }
-
-       /* This seems to be a bit of magic data that we need to
-        * skip. I wish there were good online documentation
-        * for programming for libresolv, so I'd know what I'm
-        * skipping here. Anyone reading this, feel free to
-        * enlighten me.
-        */
-       cp += 1 + NS_INT16SZ + NS_INT32SZ;
-
-       /* Skip the type, class and ttl. */
-       cp += (NS_INT16SZ * 2) + NS_INT32SZ;
-
-       /* Get the length and end of the buffer. */
-       NS_GET16(c, cp);
-       cend = cp + c;
-
-       /* Iterate over any multiple answers we might have. In
-        * this context, it's unlikely, but anyway.
-        */
-       rp = (u_char *) result;
-       rend = (u_char *) result + RESULT_SIZE - 1;
-       while (cp < cend && rp < rend) {
-               a = *cp++;
-               if (a != 0) {
-                       for (b = a; b > 0 && cp < cend && rp < rend; b--) {
-                               if (*cp == '\n' || *cp == '"' || *cp == '\\') {
-                                       *rp++ = '\\';
-                               }
-                               *rp++ = *cp++;
-                       }
-               }
-       }
-       *rp = '\0';
-       if (txtbuf != NULL) {
-               long len;
-               len = snprintf(txtbuf, txtbufsize, "%s", result);
-       
-               /* Remove nonprintable characters */
-               for (p = txtbuf; *p != '\0'; p++) {
-                       if (!isprint(*p)) {
-                               memmove (p,
-                                        p + 1,
-                                        len - (p - txtbuf) - 1);
-                       }
-               }
-       }
-       if (need_to_free_answer) free(answer);
-       free(result);
-       return(1);
-}
-
-
-/*
- * Check to see if the client host is on some sort of spam list (RBL)
- * If spammer, returns nonzero and places reason in 'message_to_spammer'
- */
-int rbl_check(char *cs_addr, char *message_to_spammer)
-{
-       char tbuf[256] = "";
-       int suffix_pos = 0;
-       int rbl;
-       int rc;
-       int num_rbl;
-       char rbl_domains[SIZ];
-       char txt_answer[1024];
-       struct timeval tx_start;
-       struct timeval tx_finish;
-
-       rc = 0;
-       strcpy(message_to_spammer, "ok");
-       gettimeofday(&tx_start, NULL);          /* start a stopwatch for performance timing */
-
-       if ((strchr(cs_addr, '.')) && (!strchr(cs_addr, ':'))) {
-               int a1, a2, a3, a4;
-
-               sscanf(cs_addr, "%d.%d.%d.%d", &a1, &a2, &a3, &a4);
-               snprintf(tbuf, sizeof tbuf, "%d.%d.%d.%d.", a4, a3, a2, a1);
-               suffix_pos = strlen(tbuf);
-       }
-       else if ((!strchr(cs_addr, '.')) && (strchr(cs_addr, ':'))) {
-               int num_colons = 0;
-               int i = 0;
-               char workbuf[sizeof tbuf];
-               char *ptr;
-
-               /* tedious code to expand and reverse an IPv6 address */
-               safestrncpy(tbuf, cs_addr, sizeof tbuf);
-               num_colons = haschar(tbuf, ':');
-               if ((num_colons < 2) || (num_colons > 7))
-                       goto finish_rbl;        /* badly formed address */
-
-               /* expand the "::" shorthand */
-               while (num_colons < 7) {
-                       ptr = strstr(tbuf, "::");
-                       if (!ptr)
-                               goto finish_rbl;                                /* badly formed address */
-
-                       ++ptr;
-                       strcpy(workbuf, ptr);
-                       strcpy(ptr, ":");
-                       strcat(ptr, workbuf);
-                       ++num_colons;
-               }
-
-               /* expand to 32 hex characters with no colons */
-               strcpy(workbuf, tbuf);
-               strcpy(tbuf, "00000000000000000000000000000000");
-               for (i=0; i<8; ++i) {
-                       char tokbuf[5];
-                       extract_token(tokbuf, workbuf, i, ':', sizeof tokbuf);
-                       memcpy(&tbuf[ (i*4) + (4-strlen(tokbuf)) ], tokbuf, strlen(tokbuf) );
-               }
-               if (strlen(tbuf) != 32) {
-                       goto finish_rbl;
-               }
-
-               /* now reverse it and add dots */
-               strcpy(workbuf, tbuf);
-               for (i=0; i<32; ++i) {
-                       tbuf[i*2] = workbuf[31-i];
-                       tbuf[(i*2)+1] = '.';
-               }
-               tbuf[64] = 0;
-               suffix_pos = 64;
-       }
-       else {
-               goto finish_rbl;        /* unknown address format */
-       }
-
-       /* See if we have any RBL domains configured */
-       num_rbl = get_hosts(rbl_domains, "rbl");
-       if (num_rbl < 1)
-       {
-               goto finish_rbl;
-       }
-
-       /* Try all configured RBL's */
-        for (rbl=0; rbl<num_rbl; ++rbl) {
-                extract_token(&tbuf[suffix_pos], rbl_domains, rbl, '|', (sizeof tbuf - suffix_pos));
-
-               if (rblcheck_backend(tbuf, txt_answer, sizeof txt_answer)) {
-                       strcpy(message_to_spammer, txt_answer);
-                       syslog(LOG_INFO, "RBL: %s %s", cs_addr, txt_answer);
-                       rc = 1;
-               }
-       }
-finish_rbl:
-       /* How long did this transaction take? */
-       gettimeofday(&tx_finish, NULL);
-
-       syslog(LOG_WARNING, "rbl: %s [%ld.%06ld] %s",
-               cs_addr,
-               ((tx_finish.tv_sec*1000000 + tx_finish.tv_usec) - (tx_start.tv_sec*1000000 + tx_start.tv_usec)) / 1000000,
-               ((tx_finish.tv_sec*1000000 + tx_finish.tv_usec) - (tx_start.tv_sec*1000000 + tx_start.tv_usec)) % 1000000,
-               (rc)?"found":"none found"
-       );
-
-       return rc;
-}
-                       
-
-/*
- * Convert a host name to a dotted quad address. 
- * Returns zero on success or nonzero on failure.
- *
- * FIXME this is obviously not IPv6 compatible.
- */
-int hostname_to_dotted_quad(char *addr, char *host) {
-       struct hostent *ch;
-       const char *i;
-       int a1, a2, a3, a4;
-
-       ch = gethostbyname(host);
-       if (ch == NULL) {
-               strcpy(addr, "0.0.0.0");
-               return(1);
-       }
-
-       i = (const char *) ch->h_addr_list[0];
-       a1 = ((*i++) & 0xff);
-       a2 = ((*i++) & 0xff);
-       a3 = ((*i++) & 0xff);
-       a4 = ((*i++) & 0xff);
-       sprintf(addr, "%d.%d.%d.%d", a1, a2, a3, a4);
-       return(0);
-}
diff --git a/citadel/locate_host.h b/citadel/locate_host.h
deleted file mode 100644 (file)
index 68f113f..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-void locate_host(char *tbuf, size_t n, char *abuf, size_t na, int client_socket);
-int rbl_check(char *cs_addr, char *message_to_spammer);
-int hostname_to_dotted_quad(char *addr, char *host);
-int rblcheck_backend(char *domain, char *txtbuf, int txtbufsize);
diff --git a/citadel/missing b/citadel/missing
deleted file mode 100755 (executable)
index 0a7fb5a..0000000
+++ /dev/null
@@ -1,283 +0,0 @@
-#! /bin/sh
-# Common stub for a few missing GNU programs while installing.
-# Copyright 1996, 1997, 1999, 2000 Free Software Foundation, Inc.
-# Originally by Fran,cois Pinard <pinard@iro.umontreal.ca>, 1996.
-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2, or (at your option)
-# any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-# 02111-1307, USA.
-
-# As a special exception to the GNU General Public License, if you
-# distribute this file as part of a program that contains a
-# configuration script generated by Autoconf, you may include it under
-# the same distribution terms that you use for the rest of that program.
-
-if test $# -eq 0; then
-  echo 1>&2 "Try \`$0 --help' for more information"
-  exit 1
-fi
-
-run=:
-
-# In the cases where this matters, `missing' is being run in the
-# srcdir already.
-if test -f configure.ac; then
-  configure_ac=configure.ac
-else
-  configure_ac=configure.in
-fi
-
-case "$1" in
---run)
-  # Try to run requested program, and just exit if it succeeds.
-  run=
-  shift
-  "$@" && exit 0
-  ;;
-esac
-
-# If it does not exist, or fails to run (possibly an outdated version),
-# try to emulate it.
-case "$1" in
-
-  -h|--h|--he|--hel|--help)
-    echo "\
-$0 [OPTION]... PROGRAM [ARGUMENT]...
-
-Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an
-error status if there is no known handling for PROGRAM.
-
-Options:
-  -h, --help      display this help and exit
-  -v, --version   output version information and exit
-  --run           try to run the given command, and emulate it if it fails
-
-Supported PROGRAM values:
-  aclocal      touch file \`aclocal.m4'
-  autoconf     touch file \`configure'
-  autoheader   touch file \`config.h.in'
-  automake     touch all \`Makefile.in' files
-  bison        create \`y.tab.[ch]', if possible, from existing .[ch]
-  flex         create \`lex.yy.c', if possible, from existing .c
-  help2man     touch the output file
-  lex          create \`lex.yy.c', if possible, from existing .c
-  makeinfo     touch the output file
-  tar          try tar, gnutar, gtar, then tar without non-portable flags
-  yacc         create \`y.tab.[ch]', if possible, from existing .[ch]"
-    ;;
-
-  -v|--v|--ve|--ver|--vers|--versi|--versio|--version)
-    echo "missing 0.3 - GNU automake"
-    ;;
-
-  -*)
-    echo 1>&2 "$0: Unknown \`$1' option"
-    echo 1>&2 "Try \`$0 --help' for more information"
-    exit 1
-    ;;
-
-  aclocal)
-    echo 1>&2 "\
-WARNING: \`$1' is missing on your system.  You should only need it if
-         you modified \`acinclude.m4' or \`${configure_ac}'.  You might want
-         to install the \`Automake' and \`Perl' packages.  Grab them from
-         any GNU archive site."
-    touch aclocal.m4
-    ;;
-
-  autoconf)
-    echo 1>&2 "\
-WARNING: \`$1' is missing on your system.  You should only need it if
-         you modified \`${configure_ac}'.  You might want to install the
-         \`Autoconf' and \`GNU m4' packages.  Grab them from any GNU
-         archive site."
-    touch configure
-    ;;
-
-  autoheader)
-    echo 1>&2 "\
-WARNING: \`$1' is missing on your system.  You should only need it if
-         you modified \`acconfig.h' or \`${configure_ac}'.  You might want
-         to install the \`Autoconf' and \`GNU m4' packages.  Grab them
-         from any GNU archive site."
-    files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' ${configure_ac}`
-    test -z "$files" && files="config.h"
-    touch_files=
-    for f in $files; do
-      case "$f" in
-      *:*) touch_files="$touch_files "`echo "$f" |
-                                      sed -e 's/^[^:]*://' -e 's/:.*//'`;;
-      *) touch_files="$touch_files $f.in";;
-      esac
-    done
-    touch $touch_files
-    ;;
-
-  automake)
-    echo 1>&2 "\
-WARNING: \`$1' is missing on your system.  You should only need it if
-         you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'.
-         You might want to install the \`Automake' and \`Perl' packages.
-         Grab them from any GNU archive site."
-    find . -type f -name Makefile.am -print |
-          sed 's/\.am$/.in/' |
-          while read f; do touch "$f"; done
-    ;;
-
-  bison|yacc)
-    echo 1>&2 "\
-WARNING: \`$1' is missing on your system.  You should only need it if
-         you modified a \`.y' file.  You may need the \`Bison' package
-         in order for those modifications to take effect.  You can get
-         \`Bison' from any GNU archive site."
-    rm -f y.tab.c y.tab.h
-    if [ $# -ne 1 ]; then
-        eval LASTARG="\${$#}"
-       case "$LASTARG" in
-       *.y)
-           SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'`
-           if [ -f "$SRCFILE" ]; then
-                cp "$SRCFILE" y.tab.c
-           fi
-           SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'`
-           if [ -f "$SRCFILE" ]; then
-                cp "$SRCFILE" y.tab.h
-           fi
-         ;;
-       esac
-    fi
-    if [ ! -f y.tab.h ]; then
-       echo >y.tab.h
-    fi
-    if [ ! -f y.tab.c ]; then
-       echo 'main() { return 0; }' >y.tab.c
-    fi
-    ;;
-
-  lex|flex)
-    echo 1>&2 "\
-WARNING: \`$1' is missing on your system.  You should only need it if
-         you modified a \`.l' file.  You may need the \`Flex' package
-         in order for those modifications to take effect.  You can get
-         \`Flex' from any GNU archive site."
-    rm -f lex.yy.c
-    if [ $# -ne 1 ]; then
-        eval LASTARG="\${$#}"
-       case "$LASTARG" in
-       *.l)
-           SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'`
-           if [ -f "$SRCFILE" ]; then
-                cp "$SRCFILE" lex.yy.c
-           fi
-         ;;
-       esac
-    fi
-    if [ ! -f lex.yy.c ]; then
-       echo 'main() { return 0; }' >lex.yy.c
-    fi
-    ;;
-
-  help2man)
-    echo 1>&2 "\
-WARNING: \`$1' is missing on your system.  You should only need it if
-        you modified a dependency of a manual page.  You may need the
-        \`Help2man' package in order for those modifications to take
-        effect.  You can get \`Help2man' from any GNU archive site."
-
-    file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'`
-    if test -z "$file"; then
-       file=`echo "$*" | sed -n 's/.*--output=\([^ ]*\).*/\1/p'`
-    fi
-    if [ -f "$file" ]; then
-       touch $file
-    else
-       test -z "$file" || exec >$file
-       echo ".ab help2man is required to generate this page"
-       exit 1
-    fi
-    ;;
-
-  makeinfo)
-    if test -z "$run" && (makeinfo --version) > /dev/null 2>&1; then
-       # We have makeinfo, but it failed.
-       exit 1
-    fi
-
-    echo 1>&2 "\
-WARNING: \`$1' is missing on your system.  You should only need it if
-         you modified a \`.texi' or \`.texinfo' file, or any other file
-         indirectly affecting the aspect of the manual.  The spurious
-         call might also be the consequence of using a buggy \`make' (AIX,
-         DU, IRIX).  You might want to install the \`Texinfo' package or
-         the \`GNU make' package.  Grab either from any GNU archive site."
-    file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'`
-    if test -z "$file"; then
-      file=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'`
-      file=`sed -n '/^@setfilename/ { s/.* \([^ ]*\) *$/\1/; p; q; }' $file`
-    fi
-    touch $file
-    ;;
-
-  tar)
-    shift
-    if test -n "$run"; then
-      echo 1>&2 "ERROR: \`tar' requires --run"
-      exit 1
-    fi
-
-    # We have already tried tar in the generic part.
-    # Look for gnutar/gtar before invocation to avoid ugly error
-    # messages.
-    if (gnutar --version > /dev/null 2>&1); then
-       gnutar ${1+"$@"} && exit 0
-    fi
-    if (gtar --version > /dev/null 2>&1); then
-       gtar ${1+"$@"} && exit 0
-    fi
-    firstarg="$1"
-    if shift; then
-       case "$firstarg" in
-       *o*)
-           firstarg=`echo "$firstarg" | sed s/o//`
-           tar "$firstarg" ${1+"$@"} && exit 0
-           ;;
-       esac
-       case "$firstarg" in
-       *h*)
-           firstarg=`echo "$firstarg" | sed s/h//`
-           tar "$firstarg" ${1+"$@"} && exit 0
-           ;;
-       esac
-    fi
-
-    echo 1>&2 "\
-WARNING: I can't seem to be able to run \`tar' with the given arguments.
-         You may want to install GNU tar or Free paxutils, or check the
-         command line arguments."
-    exit 1
-    ;;
-
-  *)
-    echo 1>&2 "\
-WARNING: \`$1' is needed, and you do not seem to have it handy on your
-         system.  You might have modified some files without having the
-         proper tools for further handling them.  Check the \`README' file,
-         it often tells you about the needed prerequirements for installing
-         this package.  You may also peek at any GNU archive site, in case
-         some other package would contain this missing \`$1' program."
-    exit 1
-    ;;
-esac
-
-exit 0
diff --git a/citadel/modules/autocompletion/.gitignore b/citadel/modules/autocompletion/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/autocompletion/serv_autocompletion.c b/citadel/modules/autocompletion/serv_autocompletion.c
deleted file mode 100644 (file)
index 0e39b64..0000000
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Autocompletion of email recipients, etc.
- *
- * Copyright (c) 1987-2020 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-
-#include "ctdl_module.h"
-#include "serv_autocompletion.h"
-#include "config.h"
-
-
-/*
- * Convert a structured name into a friendly name.  Caller must free the
- * returned pointer.
- */
-char *n_to_fn(char *value) {
-       char *nnn = NULL;
-       int i;
-
-       nnn = malloc(strlen(value) + 10);
-       strcpy(nnn, "");
-       extract_token(&nnn[strlen(nnn)] , value, 3, ';', 999);
-       strcat(nnn, " ");
-       extract_token(&nnn[strlen(nnn)] , value, 1, ';', 999);
-       strcat(nnn, " ");
-       extract_token(&nnn[strlen(nnn)] , value, 2, ';', 999);
-       strcat(nnn, " ");
-       extract_token(&nnn[strlen(nnn)] , value, 0, ';', 999);
-       strcat(nnn, " ");
-       extract_token(&nnn[strlen(nnn)] , value, 4, ';', 999);
-       strcat(nnn, " ");
-       for (i=0; i<strlen(nnn); ++i) {
-               if (!strncmp(&nnn[i], "  ", 2)) strcpy(&nnn[i], &nnn[i+1]);
-       }
-       striplt(nnn);
-       return(nnn);
-}
-
-
-
-
-/*
- * Back end for cmd_auto()
- */
-void hunt_for_autocomplete(long msgnum, char *search_string) {
-       struct CtdlMessage *msg;
-       struct vCard *v;
-       char *value = NULL;
-       char *value2 = NULL;
-       int i = 0;
-       char *nnn = NULL;
-
-       msg = CtdlFetchMessage(msgnum, 1);
-       if (msg == NULL) return;
-
-       v = vcard_load(msg->cm_fields[eMesageText]);
-       CM_Free(msg);
-
-       /*
-        * Try to match from a friendly name (the "fn" field).  If there is
-        * a match, return the entry in the form of:
-        *     Display Name <user@domain.org>
-        */
-       value = vcard_get_prop(v, "fn", 0, 0, 0);
-       if (value != NULL) if (bmstrcasestr(value, search_string)) {
-               value2 = vcard_get_prop(v, "email", 1, 0, 0);
-               if (value2 == NULL) value2 = "";
-               cprintf("%s <%s>\n", value, value2);
-               vcard_free(v);
-               return;
-       }
-
-       /*
-        * Try to match from a structured name (the "n" field).  If there is
-        * a match, return the entry in the form of:
-        *     Display Name <user@domain.org>
-        */
-       value = vcard_get_prop(v, "n", 0, 0, 0);
-       if (value != NULL) if (bmstrcasestr(value, search_string)) {
-
-               value2 = vcard_get_prop(v, "email", 1, 0, 0);
-               if (value2 == NULL) value2 = "";
-               nnn = n_to_fn(value);
-               cprintf("%s <%s>\n", nnn, value2);
-               free(nnn);
-               vcard_free(v);
-               return;
-       }
-
-       /*
-        * Try a partial match on all listed email addresses.
-        */
-       i = 0;
-       while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
-               if (bmstrcasestr(value, search_string)) {
-                       if (vcard_get_prop(v, "fn", 0, 0, 0)) {
-                               cprintf("%s <%s>\n", vcard_get_prop(v, "fn", 0, 0, 0), value);
-                       }
-                       else if (vcard_get_prop(v, "n", 0, 0, 0)) {
-                               nnn = n_to_fn(vcard_get_prop(v, "n", 0, 0, 0));
-                               cprintf("%s <%s>\n", nnn, value);
-                               free(nnn);
-                       
-                       }
-                       else {
-                               cprintf("%s\n", value);
-                       }
-                       vcard_free(v);
-                       return;
-               }
-       }
-
-       vcard_free(v);
-}
-
-
-
-/*
- * Attempt to autocomplete an address based on a partial...
- */
-void cmd_auto(char *argbuf) {
-       char hold_rm[ROOMNAMELEN];
-       char search_string[256];
-       long *msglist = NULL;
-       int num_msgs = 0;
-       long *fts_msgs = NULL;
-       int fts_num_msgs = 0;
-       struct cdbdata *cdbfr;
-       int r = 0;
-       int i = 0;
-       int j = 0;
-       int search_match = 0;
-       char *rooms_to_try[] = { USERCONTACTSROOM, ADDRESS_BOOK_ROOM };
-               
-       if (CtdlAccessCheck(ac_logged_in)) return;
-       extract_token(search_string, argbuf, 0, '|', sizeof search_string);
-       if (IsEmptyStr(search_string)) {
-               cprintf("%d You supplied an empty partial.\n",
-                       ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       strcpy(hold_rm, CC->room.QRname);       /* save current room */
-       cprintf("%d try these:\n", LISTING_FOLLOWS);
-
-       /*
-        * Gather up message pointers in rooms containing vCards
-        */
-       for (r=0; r < (sizeof(rooms_to_try) / sizeof(char *)); ++r) {
-               if (CtdlGetRoom(&CC->room, rooms_to_try[r]) == 0) {
-                       cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
-                       if (cdbfr != NULL) {
-                               msglist = realloc(msglist, (num_msgs * sizeof(long)) + cdbfr->len + 1);
-                               memcpy(&msglist[num_msgs], cdbfr->ptr, cdbfr->len);
-                               num_msgs += (cdbfr->len / sizeof(long));
-                               cdb_free(cdbfr);
-                       }
-               }
-       }
-
-       /*
-        * Search-reduce the results if we have the full text index available
-        */
-       if (CtdlGetConfigInt("c_enable_fulltext")) {
-               CtdlModuleDoSearch(&fts_num_msgs, &fts_msgs, search_string, "fulltext");
-               if (fts_msgs) {
-                       for (i=0; i<num_msgs; ++i) {
-                               search_match = 0;
-                               for (j=0; j<fts_num_msgs; ++j) {
-                                       if (msglist[i] == fts_msgs[j]) {
-                                               search_match = 1;
-                                               j = fts_num_msgs + 1;   /* end the search */
-                                       }
-                               }
-                               if (!search_match) {
-                                       msglist[i] = 0;         /* invalidate this result */
-                               }
-                       }
-                       free(fts_msgs);
-               }
-               else {
-                       /* If no results, invalidate the whole list */
-                       free(msglist);
-                       msglist = NULL;
-                       num_msgs = 0;
-               }
-       }
-
-       /*
-        * Now output the ones that look interesting
-        */
-       if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
-               if (msglist[i] != 0) {
-                       hunt_for_autocomplete(msglist[i], search_string);
-               }
-       }
-       
-       cprintf("000\n");
-       if (strcmp(CC->room.QRname, hold_rm)) {
-               CtdlGetRoom(&CC->room, hold_rm);    /* return to saved room */
-       }
-
-       if (msglist) {
-               free(msglist);
-       }
-       
-}
-
-
-CTDL_MODULE_INIT(autocompletion)
-{
-       if (!threading)
-       {
-               CtdlRegisterProtoHook(cmd_auto, "AUTO", "Do recipient autocompletion");
-       }
-       /* return our module name for the log */
-       return "autocompletion";
-}
diff --git a/citadel/modules/autocompletion/serv_autocompletion.h b/citadel/modules/autocompletion/serv_autocompletion.h
deleted file mode 100644 (file)
index 9ef0caf..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-/* 
- * Copyright (c) 1987-2012 by the citadel.org team
- *
- *  This program is open source software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License version 3.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- */
-
-char *serv_autocompletion_init(void);
diff --git a/citadel/modules/bio/.gitignore b/citadel/modules/bio/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/bio/serv_bio.c b/citadel/modules/bio/serv_bio.c
deleted file mode 100644 (file)
index e6e1dd2..0000000
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * This module implementsserver commands related to the display and
- * manipulation of user "bio" files.
- *
- * Copyright (c) 1987-2021 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "ctdl_module.h"
-#include "config.h"
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <dirent.h>
-
-
-/*
- * Command to enter user bio (profile) in plain text.
- * This is deprecated , or at least it will be when its replacement is written  :)
- * I want commands to get/set bio in full MIME wonderfulness.
- */
-void cmd_ebio(char *cmdbuf) {
-       char buf[SIZ];
-
-       unbuffer_output();
-
-       if (!(CC->logged_in)) {
-               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
-               return;
-       }
-
-       StrBuf *NewProfile = NewStrBufPlain("Content-type: text/plain; charset=UTF-8\nContent-transfer-encoding: 8bit\n\n", -1);
-
-       cprintf("%d Transmit user profile in plain text now.\n", SEND_LISTING);
-       while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
-               StrBufAppendBufPlain(NewProfile, buf, -1, 0);
-               StrBufAppendBufPlain(NewProfile, HKEY("\n"), 0);
-       }
-
-       /* we have read the new profile from the user , now save it */
-       long old_msgnum = CC->user.msgnum_bio;
-       char userconfigroomname[ROOMNAMELEN];
-       CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &CC->user, USERCONFIGROOM);
-       long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, ChrPtr(NewProfile), FMT_RFC822, "Profile submitted with EBIO command");
-       FreeStrBuf(&NewProfile);
-       CtdlGetUserLock(&CC->user, CC->curr_user);
-       CC->user.msgnum_bio = new_msgnum;
-       CtdlPutUserLock(&CC->user);
-       if (old_msgnum > 0) {
-               syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
-               CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
-       }
-}
-
-
-/*
- * Command to read user bio (profile) in plain text.
- * This is deprecated , or at least it will be when its replacement is written  :)
- * I want commands to get/set bio in full MIME wonderfulness.
- */
-void cmd_rbio(char *cmdbuf)
-{
-       struct ctdluser ruser;
-       char buf[SIZ];
-
-       extract_token(buf, cmdbuf, 0, '|', sizeof buf);
-       if (CtdlGetUser(&ruser, buf) != 0) {
-               cprintf("%d No such user.\n",ERROR + NO_SUCH_USER);
-               return;
-       }
-
-       cprintf("%d OK|%s|%ld|%d|%ld|%ld|%ld\n", LISTING_FOLLOWS,
-               ruser.fullname, ruser.usernum, ruser.axlevel,
-               (long)ruser.lastcall, ruser.timescalled, ruser.posted);
-
-       struct CtdlMessage *msg = CtdlFetchMessage(ruser.msgnum_bio, 1);
-       if (msg != NULL) {
-               CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_NONE, 0, 0, 0);
-               CM_Free(msg);
-       }
-       cprintf("000\n");
-}
-
-
-/*
- * Import function called by import_old_bio_files() for a single user
- */
-void import_one_bio_file(char *username, long usernum, char *path)
-{
-       syslog(LOG_DEBUG, "Import legacy bio for %s, usernum=%ld, filename=%s", username, usernum, path);
-
-       FILE *fp = fopen(path, "r");
-       if (!fp) return;
-
-       fseek(fp, 0, SEEK_END);
-       long data_length = ftell(fp);
-
-       if (data_length >= 1) {
-               rewind(fp);
-               char *unencoded_data = malloc(data_length);
-               if (unencoded_data) {
-                       fread(unencoded_data, data_length, 1, fp);
-                       char *encoded_data = malloc((data_length * 2) + 100);
-                       if (encoded_data) {
-                               sprintf(encoded_data, "Content-type: text/plain; charset=UTF-8\nContent-transfer-encoding: base64\n\n");
-                               CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
-
-                               char userconfigroomname[ROOMNAMELEN];
-                               struct ctdluser usbuf;
-
-                               if (CtdlGetUser(&usbuf, username) == 0) {       // no need to lock it , we are still initializing
-                                       long old_msgnum = usbuf.msgnum_bio;
-                                       CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &usbuf, USERCONFIGROOM);
-                                       long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, encoded_data, FMT_RFC822, "Profile imported from bio");
-                                       syslog(LOG_DEBUG, "Message %ld is now the profile for %s", new_msgnum, username);
-                                       usbuf.msgnum_bio = new_msgnum;
-                                       CtdlPutUser(&usbuf);
-                                       unlink(path);                           // delete the old file , it's in the database now
-                                       if (old_msgnum > 0) {
-                                               syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
-                                               CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
-                                       }
-                               }
-                               free(encoded_data);
-                       }
-                       free(unencoded_data);
-               }
-       }
-       fclose(fp);
-}
-
-
-/*
- * Look for old-format "bio" files and import them into the message base
- */
-void import_old_bio_files(void)
-{
-       DIR *filedir = NULL;
-       struct dirent *filedir_entry;
-       size_t d_namelen;
-       struct ctdluser usbuf;
-       long usernum = 0;
-       int d_type = 0;
-       struct stat s;
-       char path[PATH_MAX];
-
-
-       syslog(LOG_DEBUG, "Importing old style bio files into the message base");
-       filedir = opendir("bio");
-       if (filedir == NULL) {
-               return;
-       }
-       while ( (filedir_entry = readdir(filedir)) , (filedir_entry != NULL))
-       {
-#ifdef _DIRENT_HAVE_D_NAMLEN
-               d_namelen = filedir_entry->d_namlen;
-
-#else
-               d_namelen = strlen(filedir_entry->d_name);
-#endif
-
-#ifdef _DIRENT_HAVE_D_TYPE
-               d_type = filedir_entry->d_type;
-#else
-
-#ifndef DT_UNKNOWN
-#define DT_UNKNOWN     0
-#define DT_DIR         4
-#define DT_REG         8
-#define DT_LNK         10
-
-#define IFTODT(mode)   (((mode) & 0170000) >> 12)
-#define DTTOIF(dirtype)        ((dirtype) << 12)
-#endif
-               d_type = DT_UNKNOWN;
-#endif
-               if ((d_namelen == 1) && 
-                   (filedir_entry->d_name[0] == '.'))
-                       continue;
-
-               if ((d_namelen == 2) && 
-                   (filedir_entry->d_name[0] == '.') &&
-                   (filedir_entry->d_name[1] == '.'))
-                       continue;
-
-               snprintf(path, PATH_MAX, "bio/%s", filedir_entry->d_name);
-               if (d_type == DT_UNKNOWN) {
-                       if (lstat(path, &s) == 0) {
-                               d_type = IFTODT(s.st_mode);
-                       }
-               }
-               switch (d_type)
-               {
-               case DT_DIR:
-                       break;
-               case DT_LNK:
-               case DT_REG:
-                       usernum = atol(filedir_entry->d_name);
-                       if (CtdlGetUserByNumber(&usbuf, usernum) == 0) {
-                               import_one_bio_file(usbuf.fullname, usernum, path);
-                       }
-               }
-       }
-       closedir(filedir);
-       rmdir("bio");
-}
-
-
-
-CTDL_MODULE_INIT(bio)
-{
-       if (!threading)
-       {
-               import_old_bio_files();
-               CtdlRegisterProtoHook(cmd_ebio, "EBIO", "Enter your bio");
-               CtdlRegisterProtoHook(cmd_rbio, "RBIO", "Read a user's bio");
-       }
-       /* return our module name for the log */
-        return "bio";
-}
diff --git a/citadel/modules/blog/serv_blog.c b/citadel/modules/blog/serv_blog.c
deleted file mode 100644 (file)
index 98bc938..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Support for blog rooms
- *
- * Copyright (c) 1999-2011 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <ctype.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_vcard.h"
-#include "citadel_ldap.h"
-#include "ctdl_module.h"
-
-/*
- * Pre-save hook for saving a message in a blog room.
- * (Do we want to only do this for top-level messages?)
- */
-int blog_upload_beforesave(struct CtdlMessage *msg, struct recptypes *recp) {
-
-       /* Only run this hook for blog rooms */
-       if (CC->room.QRdefaultview != VIEW_BLOG) {
-               return(0);
-       }
-
-       /* 
-        * If the message doesn't have an EUID, give it one.
-        */
-       if (CM_IsEmpty(msg, eExclusiveID))
-       {
-               char uuid[SIZ];
-               generate_uuid(uuid);
-               CM_SetField(msg, eExclusiveID, uuid, strlen(uuid));
-       }
-
-       /*
-        * We also want to define a maximum length, whether we generated it or not.
-        */
-       CM_CutFieldAt(msg, eExclusiveID, BLOG_EUIDBUF_SIZE - 1);
-       
-       /* Now allow the save to complete. */
-       return(0);
-}
-
-
-CTDL_MODULE_INIT(blog)
-{
-       if (!threading)
-       {
-               CtdlRegisterMessageHook(blog_upload_beforesave, EVT_BEFORESAVE);
-       }
-       
-       /* return our module id for the Log */
-       return "blog";
-}
diff --git a/citadel/modules/calendar/.gitignore b/citadel/modules/calendar/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/calendar/serv_calendar.c b/citadel/modules/calendar/serv_calendar.c
deleted file mode 100644 (file)
index 0a9090d..0000000
+++ /dev/null
@@ -1,2566 +0,0 @@
-/* 
- * This module implements iCalendar object processing and the Calendar>
- * room on a Citadel server.  It handles iCalendar objects using the
- * iTIP protocol.  See RFCs 2445 and 2446.
- *
- * Copyright (c) 1987-2021 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#define PRODID "-//Citadel//NONSGML Citadel Calendar//EN"
-
-#include "ctdl_module.h"
-#include <libical/ical.h>
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_calendar.h"
-#include "room_ops.h"
-#include "euidindex.h"
-#include "default_timezone.h"
-#include "config.h"
-
-struct ical_respond_data {
-       char desired_partnum[SIZ];
-       icalcomponent *cal;
-};
-
-
-/*
- * Utility function to create a new VCALENDAR component with some of the
- * required fields already set the way we like them.
- */
-icalcomponent *icalcomponent_new_citadel_vcalendar(void) {
-       icalcomponent *encaps;
-
-       encaps = icalcomponent_new_vcalendar();
-       if (encaps == NULL) {
-               syslog(LOG_ERR, "calendar: could not allocate component");
-               return NULL;
-       }
-
-       /* Set the Product ID */
-       icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
-
-       /* Set the Version Number */
-       icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
-
-       return(encaps);
-}
-
-
-/*
- * Utility function to encapsulate a subcomponent into a full VCALENDAR
- */
-icalcomponent *ical_encapsulate_subcomponent(icalcomponent *subcomp) {
-       icalcomponent *encaps;
-
-       /* If we're already looking at a full VCALENDAR component,
-        * don't bother ... just return itself.
-        */
-       if (icalcomponent_isa(subcomp) == ICAL_VCALENDAR_COMPONENT) {
-               return subcomp;
-       }
-
-       /* Encapsulate the VEVENT component into a complete VCALENDAR */
-       encaps = icalcomponent_new_citadel_vcalendar();
-       if (encaps == NULL) return NULL;
-
-       /* Encapsulate the subcomponent inside */
-       icalcomponent_add_component(encaps, subcomp);
-
-       /* Return the object we just created. */
-       return(encaps);
-}
-
-
-/*
- * Write a calendar object into the specified user's calendar room.
- * If the supplied user is NULL, this function writes the calendar object
- * to the currently selected room.
- */
-void ical_write_to_cal(struct ctdluser *u, icalcomponent *cal) {
-       char *ser = NULL;
-       long serlen;
-       icalcomponent *encaps = NULL;
-       struct CtdlMessage *msg = NULL;
-       icalcomponent *tmp=NULL;
-
-       if (cal == NULL) return;
-
-       /* If the supplied object is a subcomponent, encapsulate it in
-        * a full VCALENDAR component, and save that instead.
-        */
-       if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
-               tmp = icalcomponent_new_clone(cal);
-               encaps = ical_encapsulate_subcomponent(tmp);
-               ical_write_to_cal(u, encaps);
-               icalcomponent_free(tmp);
-               icalcomponent_free(encaps);
-               return;
-       }
-
-       ser = icalcomponent_as_ical_string_r(cal);
-       if (ser == NULL) return;
-
-       serlen = strlen(ser);
-
-       /* If the caller supplied a user, write to that user's default calendar room */
-       if (u) {
-               /* This handy API function does all the work for us. */
-               CtdlWriteObject(USERCALENDARROOM,       /* which room */
-                       "text/calendar",        /* MIME type */
-                       ser,                    /* data */
-                       serlen + 1,             /* length */
-                       u,                      /* which user */
-                       0,                      /* not binary */
-                       0                       /* no flags */
-               );
-       }
-
-       /* If the caller did not supply a user, write to the currently selected room */
-       if (!u) {
-               struct CitContext *CCC = CC;
-               StrBuf *MsgBody;
-
-               msg = malloc(sizeof(struct CtdlMessage));
-               memset(msg, 0, sizeof(struct CtdlMessage));
-               msg->cm_magic = CTDLMESSAGE_MAGIC;
-               msg->cm_anon_type = MES_NORMAL;
-               msg->cm_format_type = 4;
-               CM_SetField(msg, eAuthor, CCC->user.fullname, strlen(CCC->user.fullname));
-               CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
-
-               MsgBody = NewStrBufPlain(NULL, serlen + 100);
-               StrBufAppendBufPlain(MsgBody, HKEY("Content-type: text/calendar\r\n\r\n"), 0);
-               StrBufAppendBufPlain(MsgBody, ser, serlen, 0);
-
-               CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
-       
-               /* Now write the data */
-               CtdlSubmitMsg(msg, NULL, "");
-               CM_Free(msg);
-       }
-
-       /* In either case, now we can free the serialized calendar object */
-       free(ser);
-}
-
-
-/*
- * Send a reply to a meeting invitation.
- *
- * 'request' is the invitation to reply to.
- * 'action' is the string "accept" or "decline" or "tentative".
- *
- */
-void ical_send_a_reply(icalcomponent *request, char *action) {
-       icalcomponent *the_reply = NULL;
-       icalcomponent *vevent = NULL;
-       icalproperty *attendee = NULL;
-       char attendee_string[SIZ];
-       icalproperty *organizer = NULL;
-       char organizer_string[SIZ];
-       icalproperty *summary = NULL;
-       char summary_string[SIZ];
-       icalproperty *me_attend = NULL;
-       struct recptypes *recp = NULL;
-       icalparameter *partstat = NULL;
-       char *serialized_reply = NULL;
-       char *reply_message_text = NULL;
-       const char *ch;
-       struct CtdlMessage *msg = NULL;
-       struct recptypes *valid = NULL;
-
-       *organizer_string = '\0';
-       strcpy(summary_string, "Calendar item");
-
-       if (request == NULL) {
-               syslog(LOG_ERR, "calendar: trying to reply to NULL event");
-               return;
-       }
-
-       the_reply = icalcomponent_new_clone(request);
-       if (the_reply == NULL) {
-               syslog(LOG_ERR, "calendar: cannot clone request");
-               return;
-       }
-
-       /* Change the method from REQUEST to REPLY */
-       icalcomponent_set_method(the_reply, ICAL_METHOD_REPLY);
-
-       vevent = icalcomponent_get_first_component(the_reply, ICAL_VEVENT_COMPONENT);
-       if (vevent != NULL) {
-               /* Hunt for attendees, removing ones that aren't us.
-                * (Actually, remove them all, cloning our own one so we can
-                * re-insert it later)
-                */
-               while (attendee = icalcomponent_get_first_property(vevent,
-                   ICAL_ATTENDEE_PROPERTY), (attendee != NULL)
-               ) {
-                       ch = icalproperty_get_attendee(attendee);
-                       if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) {
-                               safestrncpy(attendee_string, ch + 7, sizeof (attendee_string));
-                               striplt(attendee_string);
-                               recp = validate_recipients(attendee_string, NULL, 0);
-                               if (recp != NULL) {
-                                       if (!strcasecmp(recp->recp_local, CC->user.fullname)) {
-                                               if (me_attend) icalproperty_free(me_attend);
-                                               me_attend = icalproperty_new_clone(attendee);
-                                       }
-                                       free_recipients(recp);
-                               }
-                       }
-
-                       /* Remove it... */
-                       icalcomponent_remove_property(vevent, attendee);
-                       icalproperty_free(attendee);
-               }
-
-               /* We found our own address in the attendee list. */
-               if (me_attend) {
-                       /* Change the partstat from NEEDS-ACTION to ACCEPT or DECLINE */
-                       icalproperty_remove_parameter_by_kind(me_attend, ICAL_PARTSTAT_PARAMETER);
-
-                       if (!strcasecmp(action, "accept")) {
-                               partstat = icalparameter_new_partstat(ICAL_PARTSTAT_ACCEPTED);
-                       }
-                       else if (!strcasecmp(action, "decline")) {
-                               partstat = icalparameter_new_partstat(ICAL_PARTSTAT_DECLINED);
-                       }
-                       else if (!strcasecmp(action, "tentative")) {
-                               partstat = icalparameter_new_partstat(ICAL_PARTSTAT_TENTATIVE);
-                       }
-
-                       if (partstat) icalproperty_add_parameter(me_attend, partstat);
-
-                       /* Now insert it back into the vevent. */
-                       icalcomponent_add_property(vevent, me_attend);
-               }
-
-               /* Figure out who to send this thing to */
-               organizer = icalcomponent_get_first_property(vevent, ICAL_ORGANIZER_PROPERTY);
-               if (organizer != NULL) {
-                       if (icalproperty_get_organizer(organizer)) {
-                               strcpy(organizer_string,
-                                       icalproperty_get_organizer(organizer) );
-                       }
-               }
-               if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
-                       strcpy(organizer_string, &organizer_string[7]);
-                       striplt(organizer_string);
-               } else {
-                       strcpy(organizer_string, "");
-               }
-
-               /* Extract the summary string -- we'll use it as the
-                * message subject for the reply
-                */
-               summary = icalcomponent_get_first_property(vevent, ICAL_SUMMARY_PROPERTY);
-               if (summary != NULL) {
-                       if (icalproperty_get_summary(summary)) {
-                               strcpy(summary_string,
-                                       icalproperty_get_summary(summary) );
-                       }
-               }
-       }
-
-       /* Now generate the reply message and send it out. */
-       serialized_reply = icalcomponent_as_ical_string_r(the_reply);
-       icalcomponent_free(the_reply);  /* don't need this anymore */
-       if (serialized_reply == NULL) return;
-
-       reply_message_text = malloc(strlen(serialized_reply) + SIZ);
-       if (reply_message_text != NULL) {
-               sprintf(reply_message_text,
-                       "Content-type: text/calendar; charset=\"utf-8\"\r\n\r\n%s\r\n",
-                       serialized_reply
-               );
-
-               msg = CtdlMakeMessage(&CC->user,
-                       organizer_string,       /* to */
-                       "",                     /* cc */
-                       CC->room.QRname, 0, FMT_RFC822,
-                       "",
-                       "",
-                       summary_string,         /* Use summary for subject */
-                       NULL,
-                       reply_message_text,
-                       NULL);
-       
-               if (msg != NULL) {
-                       valid = validate_recipients(organizer_string, NULL, 0);
-                       CtdlSubmitMsg(msg, valid, "");
-                       CM_Free(msg);
-                       free_recipients(valid);
-               }
-       }
-       free(serialized_reply);
-}
-
-
-/*
- * Callback function for mime parser that hunts for calendar content types
- * and turns them into calendar objects.  If something is found, it is placed
- * in ird->cal, and the caller now owns that memory and is responsible for freeing it.
- */
-void ical_locate_part(char *name, char *filename, char *partnum, char *disp,
-               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
-               char *cbid, void *cbuserdata) {
-
-       struct ical_respond_data *ird = NULL;
-
-       ird = (struct ical_respond_data *) cbuserdata;
-
-       /* desired_partnum can be set to "_HUNT_" to have it just look for
-        * the first part with a content type of text/calendar.  Otherwise
-        * we have to only process the right one.
-        */
-       if (strcasecmp(ird->desired_partnum, "_HUNT_")) {
-               if (strcasecmp(partnum, ird->desired_partnum)) {
-                       return;
-               }
-       }
-
-       if (  (strcasecmp(cbtype, "text/calendar"))
-          && (strcasecmp(cbtype, "application/ics")) ) {
-               return;
-       }
-
-       if (ird->cal != NULL) {
-               icalcomponent_free(ird->cal);
-               ird->cal = NULL;
-       }
-
-       ird->cal = icalcomponent_new_from_string(content);
-}
-
-
-/*
- * Respond to a meeting request.
- */
-void ical_respond(long msgnum, char *partnum, char *action) {
-       struct CtdlMessage *msg = NULL;
-       struct ical_respond_data ird;
-
-       if (
-          (strcasecmp(action, "accept"))
-          && (strcasecmp(action, "decline"))
-       ) {
-               cprintf("%d Action must be 'accept' or 'decline'\n",
-                       ERROR + ILLEGAL_VALUE
-               );
-               return;
-       }
-
-       msg = CtdlFetchMessage(msgnum, 1);
-       if (msg == NULL) {
-               cprintf("%d Message %ld not found.\n",
-                       ERROR + ILLEGAL_VALUE,
-                       (long)msgnum
-               );
-               return;
-       }
-
-       memset(&ird, 0, sizeof ird);
-       strcpy(ird.desired_partnum, partnum);
-       mime_parser(CM_RANGE(msg, eMesageText),
-                   *ical_locate_part,          /* callback function */
-                   NULL, NULL,
-                   (void *) &ird,                      /* user data */
-                   0
-       );
-
-       /* We're done with the incoming message, because we now have a
-        * calendar object in memory.
-        */
-       CM_Free(msg);
-
-       /*
-        * Here is the real meat of this function.  Handle the event.
-        */
-       if (ird.cal != NULL) {
-               /* Save this in the user's calendar if necessary */
-               if (!strcasecmp(action, "accept")) {
-                       ical_write_to_cal(&CC->user, ird.cal);
-               }
-
-               /* Send a reply if necessary */
-               if (icalcomponent_get_method(ird.cal) == ICAL_METHOD_REQUEST) {
-                       ical_send_a_reply(ird.cal, action);
-               }
-
-               /* We used to delete the invitation after handling it.
-                * We don't do that anymore, but here is the code that handled it:
-                * CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
-                */
-
-               /* Free the memory we allocated and return a response. */
-               icalcomponent_free(ird.cal);
-               ird.cal = NULL;
-               cprintf("%d ok\n", CIT_OK);
-               return;
-       }
-       else {
-               cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
-               return;
-       }
-
-       /* should never get here */
-}
-
-
-/*
- * Figure out the UID of the calendar event being referred to in a
- * REPLY object.  This function is recursive.
- */
-void ical_learn_uid_of_reply(char *uidbuf, icalcomponent *cal) {
-       icalcomponent *subcomponent;
-       icalproperty *p;
-
-       /* If this object is a REPLY, then extract the UID. */
-       if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
-               p = icalcomponent_get_first_property(cal, ICAL_UID_PROPERTY);
-               if (p != NULL) {
-                       strcpy(uidbuf, icalproperty_get_comment(p));
-               }
-       }
-
-       /* Otherwise, recurse through any VEVENT subcomponents.  We do NOT want the
-        * UID of the reply; we want the UID of the invitation being replied to.
-        */
-       for (subcomponent = icalcomponent_get_first_component(cal, ICAL_VEVENT_COMPONENT);
-           subcomponent != NULL;
-           subcomponent = icalcomponent_get_next_component(cal, ICAL_VEVENT_COMPONENT) ) {
-               ical_learn_uid_of_reply(uidbuf, subcomponent);
-       }
-}
-
-
-/*
- * ical_update_my_calendar_with_reply() refers to this callback function; when we
- * locate the message containing the calendar event we're replying to, this function
- * gets called.  It basically just sticks the message number in a supplied buffer.
- */
-void ical_hunt_for_event_to_update(long msgnum, void *data) {
-       long *msgnumptr;
-
-       msgnumptr = (long *) data;
-       *msgnumptr = msgnum;
-}
-
-
-struct original_event_container {
-       icalcomponent *c;
-};
-
-/*
- * Callback function for mime parser that hunts for calendar content types
- * and turns them into calendar objects (called by ical_update_my_calendar_with_reply()
- * to fetch the object being updated)
- */
-void ical_locate_original_event(char *name, char *filename, char *partnum, char *disp,
-               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
-               char *cbid, void *cbuserdata) {
-
-       struct original_event_container *oec = NULL;
-
-       if (  (strcasecmp(cbtype, "text/calendar"))
-          && (strcasecmp(cbtype, "application/ics")) ) {
-               return;
-       }
-       oec = (struct original_event_container *) cbuserdata;
-       if (oec->c != NULL) {
-               icalcomponent_free(oec->c);
-       }
-       oec->c = icalcomponent_new_from_string(content);
-}
-
-
-/*
- * Merge updated attendee information from a REPLY into an existing event.
- */
-void ical_merge_attendee_reply(icalcomponent *event, icalcomponent *reply) {
-       icalcomponent *c;
-       icalproperty *e_attendee, *r_attendee;
-
-       /* First things first.  If we're not looking at a VEVENT component,
-        * recurse through subcomponents until we find one.
-        */
-       if (icalcomponent_isa(event) != ICAL_VEVENT_COMPONENT) {
-               for (c = icalcomponent_get_first_component(event, ICAL_VEVENT_COMPONENT);
-                   c != NULL;
-                   c = icalcomponent_get_next_component(event, ICAL_VEVENT_COMPONENT) ) {
-                       ical_merge_attendee_reply(c, reply);
-               }
-               return;
-       }
-
-       /* Now do the same thing with the reply.
-        */
-       if (icalcomponent_isa(reply) != ICAL_VEVENT_COMPONENT) {
-               for (c = icalcomponent_get_first_component(reply, ICAL_VEVENT_COMPONENT);
-                   c != NULL;
-                   c = icalcomponent_get_next_component(reply, ICAL_VEVENT_COMPONENT) ) {
-                       ical_merge_attendee_reply(event, c);
-               }
-               return;
-       }
-
-       /* Clone the reply, because we're going to rip its guts out. */
-       reply = icalcomponent_new_clone(reply);
-
-       /* At this point we're looking at the correct subcomponents.
-        * Iterate through the attendees looking for a match.
-        */
-STARTOVER:
-       for (e_attendee = icalcomponent_get_first_property(event, ICAL_ATTENDEE_PROPERTY);
-           e_attendee != NULL;
-           e_attendee = icalcomponent_get_next_property(event, ICAL_ATTENDEE_PROPERTY)) {
-
-               for (r_attendee = icalcomponent_get_first_property(reply, ICAL_ATTENDEE_PROPERTY);
-                   r_attendee != NULL;
-                   r_attendee = icalcomponent_get_next_property(reply, ICAL_ATTENDEE_PROPERTY)) {
-
-                       /* Check to see if these two attendees match...
-                        */
-                       const char *e, *r;
-                       e = icalproperty_get_attendee(e_attendee);
-                       r = icalproperty_get_attendee(r_attendee);
-
-                       if ((e != NULL) && 
-                           (r != NULL) && 
-                           !strcasecmp(e, r)) {
-                               /* ...and if they do, remove the attendee from the event
-                                * and replace it with the attendee from the reply.  (The
-                                * reply's copy will have the same address, but an updated
-                                * status.)
-                                */
-                               icalcomponent_remove_property(event, e_attendee);
-                               icalproperty_free(e_attendee);
-                               icalcomponent_remove_property(reply, r_attendee);
-                               icalcomponent_add_property(event, r_attendee);
-
-                               /* Since we diddled both sets of attendees, we have to start
-                                * the iteration over again.  This will not create an infinite
-                                * loop because we removed the attendee from the reply.  (That's
-                                * why we cloned the reply, and that's what we mean by "ripping
-                                * its guts out.")
-                                */
-                               goto STARTOVER;
-                       }
-       
-               }
-       }
-
-       /* Free the *clone* of the reply. */
-       icalcomponent_free(reply);
-}
-
-
-/*
- * Handle an incoming RSVP (object with method==ICAL_METHOD_REPLY) for a
- * calendar event.  The object has already been deserialized for us; all
- * we have to do here is hunt for the event in our calendar, merge in the
- * updated attendee status, and save it again.
- *
- * This function returns 0 on success, 1 if the event was not found in the
- * user's calendar, or 2 if an internal error occurred.
- */
-int ical_update_my_calendar_with_reply(icalcomponent *cal) {
-       char uid[SIZ];
-       char hold_rm[ROOMNAMELEN];
-       long msgnum_being_replaced = 0;
-       struct CtdlMessage *msg = NULL;
-       struct original_event_container oec;
-       icalcomponent *original_event;
-       char *serialized_event = NULL;
-       char roomname[ROOMNAMELEN];
-       char *message_text = NULL;
-
-       /* Figure out just what event it is we're dealing with */
-       strcpy(uid, "--==<< InVaLiD uId >>==--");
-       ical_learn_uid_of_reply(uid, cal);
-       syslog(LOG_DEBUG, "calendar: UID of event being replied to is <%s>", uid);
-
-       strcpy(hold_rm, CC->room.QRname);       /* save current room */
-
-       if (CtdlGetRoom(&CC->room, USERCALENDARROOM) != 0) {
-               CtdlGetRoom(&CC->room, hold_rm);
-               syslog(LOG_ERR, "calendar: cannot get user calendar room");
-               return(2);
-       }
-
-       /*
-        * Look in the EUID index for a message with
-        * the Citadel EUID set to the value we're looking for.  Since
-        * Citadel always sets the message EUID to the iCalendar UID of
-        * the event, this will work.
-        */
-       msgnum_being_replaced = CtdlLocateMessageByEuid(uid, &CC->room);
-
-       CtdlGetRoom(&CC->room, hold_rm);        /* return to saved room */
-
-       syslog(LOG_DEBUG, "calendar: msgnum_being_replaced == %ld", msgnum_being_replaced);
-       if (msgnum_being_replaced == 0) {
-               return(1);                      /* no calendar event found */
-       }
-
-       /* Now we know the ID of the message containing the event being updated.
-        * We don't actually have to delete it; that'll get taken care of by the
-        * server when we save another event with the same UID.  This just gives
-        * us the ability to load the event into memory so we can diddle the
-        * attendees.
-        */
-       msg = CtdlFetchMessage(msgnum_being_replaced, 1);
-       if (msg == NULL) {
-               return(2);                      /* internal error */
-       }
-       oec.c = NULL;
-       mime_parser(CM_RANGE(msg, eMesageText),
-                   *ical_locate_original_event,        /* callback function */
-                   NULL, NULL,
-                   &oec,                               /* user data */
-                   0
-       );
-       CM_Free(msg);
-
-       original_event = oec.c;
-       if (original_event == NULL) {
-               syslog(LOG_ERR, "calendar: original_component is NULL");
-               return(2);
-       }
-
-       /* Merge the attendee's updated status into the event */
-       ical_merge_attendee_reply(original_event, cal);
-
-       /* Serialize it */
-       serialized_event = icalcomponent_as_ical_string_r(original_event);
-       icalcomponent_free(original_event);     /* Don't need this anymore. */
-       if (serialized_event == NULL) return(2);
-
-       CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
-
-       message_text = malloc(strlen(serialized_event) + SIZ);
-       if (message_text != NULL) {
-               sprintf(message_text,
-                       "Content-type: text/calendar; charset=\"utf-8\"\r\n\r\n%s\r\n",
-                       serialized_event
-               );
-
-               msg = CtdlMakeMessage(&CC->user,
-                       "",                     /* No recipient */
-                       "",                     /* No recipient */
-                       roomname,
-                       0, FMT_RFC822,
-                       "",
-                       "",
-                       "",             /* no subject */
-                       NULL,
-                       message_text,
-                       NULL);
-       
-               if (msg != NULL) {
-                       CIT_ICAL->avoid_sending_invitations = 1;
-                       CtdlSubmitMsg(msg, NULL, roomname);
-                       CM_Free(msg);
-                       CIT_ICAL->avoid_sending_invitations = 0;
-               }
-       }
-       free(serialized_event);
-       return(0);
-}
-
-
-/*
- * Handle an incoming RSVP for an event.  (This is the server subcommand part; it
- * simply extracts the calendar object from the message, deserializes it, and
- * passes it up to ical_update_my_calendar_with_reply() for processing.
- */
-void ical_handle_rsvp(long msgnum, char *partnum, char *action) {
-       struct CtdlMessage *msg = NULL;
-       struct ical_respond_data ird;
-       int ret;
-
-       if (
-          (strcasecmp(action, "update"))
-          && (strcasecmp(action, "ignore"))
-       ) {
-               cprintf("%d Action must be 'update' or 'ignore'\n",
-                       ERROR + ILLEGAL_VALUE
-               );
-               return;
-       }
-
-       msg = CtdlFetchMessage(msgnum, 1);
-       if (msg == NULL) {
-               cprintf("%d Message %ld not found.\n",
-                       ERROR + ILLEGAL_VALUE,
-                       (long)msgnum
-               );
-               return;
-       }
-
-       memset(&ird, 0, sizeof ird);
-       strcpy(ird.desired_partnum, partnum);
-       mime_parser(CM_RANGE(msg, eMesageText),
-                   *ical_locate_part,          /* callback function */
-                   NULL, NULL,
-                   (void *) &ird,                      /* user data */
-                   0
-               );
-
-       /* We're done with the incoming message, because we now have a
-        * calendar object in memory.
-        */
-       CM_Free(msg);
-
-       /*
-        * Here is the real meat of this function.  Handle the event.
-        */
-       if (ird.cal != NULL) {
-               /* Update the user's calendar if necessary */
-               if (!strcasecmp(action, "update")) {
-                       ret = ical_update_my_calendar_with_reply(ird.cal);
-                       if (ret == 0) {
-                               cprintf("%d Your calendar has been updated with this reply.\n",
-                                       CIT_OK);
-                       }
-                       else if (ret == 1) {
-                               cprintf("%d This event does not exist in your calendar.\n",
-                                       ERROR + FILE_NOT_FOUND);
-                       }
-                       else {
-                               cprintf("%d An internal error occurred.\n",
-                                       ERROR + INTERNAL_ERROR);
-                       }
-               }
-               else {
-                       cprintf("%d This reply has been ignored.\n", CIT_OK);
-               }
-
-               /* Now that we've processed this message, we don't need it
-                * anymore.  So delete it.  (Don't do this anymore.)
-               CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
-                */
-
-               /* Free the memory we allocated and return a response. */
-               icalcomponent_free(ird.cal);
-               ird.cal = NULL;
-               return;
-       }
-       else {
-               cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
-               return;
-       }
-
-       /* should never get here */
-}
-
-
-/*
- * Search for a property in both the top level and in a VEVENT subcomponent
- */
-icalproperty *ical_ctdl_get_subprop(
-               icalcomponent *cal,
-               icalproperty_kind which_prop
-) {
-       icalproperty *p;
-       icalcomponent *c;
-
-       p = icalcomponent_get_first_property(cal, which_prop);
-       if (p == NULL) {
-               c = icalcomponent_get_first_component(cal,
-                                                       ICAL_VEVENT_COMPONENT);
-               if (c != NULL) {
-                       p = icalcomponent_get_first_property(c, which_prop);
-               }
-       }
-       return p;
-}
-
-
-/*
- * Check to see if two events overlap.  Returns nonzero if they do.
- * (This function is used in both Citadel and WebCit.  If you change it in
- * one place, change it in the other.  Better yet, put it in a library.)
- */
-int ical_ctdl_is_overlap(
-                       struct icaltimetype t1start,
-                       struct icaltimetype t1end,
-                       struct icaltimetype t2start,
-                       struct icaltimetype t2end
-) {
-       if (icaltime_is_null_time(t1start)) return(0);
-       if (icaltime_is_null_time(t2start)) return(0);
-
-       /* if either event lacks end time, assume end = start */
-       if (icaltime_is_null_time(t1end))
-               memcpy(&t1end, &t1start, sizeof(struct icaltimetype));
-       else {
-               if (t1end.is_date && icaltime_compare(t1start, t1end)) {
-                        /*
-                         * the end date is non-inclusive so adjust it by one
-                         * day because our test is inclusive, note that a day is
-                         * not too much because we are talking about all day
-                         * events
-                        * if start = end we assume that nevertheless the whole
-                        * day is meant
-                         */
-                       icaltime_adjust(&t1end, -1, 0, 0, 0);   
-               }
-       }
-
-       if (icaltime_is_null_time(t2end))
-               memcpy(&t2end, &t2start, sizeof(struct icaltimetype));
-       else {
-               if (t2end.is_date && icaltime_compare(t2start, t2end)) {
-                       icaltime_adjust(&t2end, -1, 0, 0, 0);   
-               }
-       }
-
-       /* First, check for all-day events */
-       if (t1start.is_date || t2start.is_date) {
-               /* If event 1 ends before event 2 starts, we're in the clear. */
-               if (icaltime_compare_date_only(t1end, t2start) < 0) return(0);
-
-               /* If event 2 ends before event 1 starts, we're also ok. */
-               if (icaltime_compare_date_only(t2end, t1start) < 0) return(0);
-
-               return(1);
-       }
-
-       /* syslog(LOG_DEBUG, "Comparing t1start %d:%d t1end %d:%d t2start %d:%d t2end %d:%d",
-               t1start.hour, t1start.minute, t1end.hour, t1end.minute,
-               t2start.hour, t2start.minute, t2end.hour, t2end.minute);
-       */
-
-       /* Now check for overlaps using date *and* time. */
-
-       /* If event 1 ends before event 2 starts, we're in the clear. */
-       if (icaltime_compare(t1end, t2start) <= 0) return(0);
-       /* syslog(LOG_DEBUG, "calendar: first passed"); */
-
-       /* If event 2 ends before event 1 starts, we're also ok. */
-       if (icaltime_compare(t2end, t1start) <= 0) return(0);
-       /* syslog(LOG_DEBUG, "calendar: second passed"); */
-
-       /* Otherwise, they overlap. */
-       return(1);
-}
-
-
-/* 
- * Phase 6 of "hunt for conflicts"
- * called by ical_conflicts_phase5()
- *
- * Now both the proposed and existing events have been boiled down to start and end times.
- * Check for overlap and output any conflicts.
- *
- * Returns nonzero if a conflict was reported.  This allows the caller to stop iterating.
- */
-int ical_conflicts_phase6(struct icaltimetype t1start,
-                       struct icaltimetype t1end,
-                       struct icaltimetype t2start,
-                       struct icaltimetype t2end,
-                       long existing_msgnum,
-                       char *conflict_event_uid,
-                       char *conflict_event_summary,
-                       char *compare_uid)
-{
-       int conflict_reported = 0;
-
-       /* debugging cruft *
-       time_t tt;
-       tt = icaltime_as_timet_with_zone(t1start, t1start.zone);
-       syslog(LOG_DEBUG, "PROPOSED START: %s", ctime(&tt));
-       tt = icaltime_as_timet_with_zone(t1end, t1end.zone);
-       syslog(LOG_DEBUG, "  PROPOSED END: %s", ctime(&tt));
-       tt = icaltime_as_timet_with_zone(t2start, t2start.zone);
-       syslog(LOG_DEBUG, "EXISTING START: %s", ctime(&tt));
-       tt = icaltime_as_timet_with_zone(t2end, t2end.zone);
-       syslog(LOG_DEBUG, "  EXISTING END: %s", ctime(&tt));
-       * debugging cruft */
-
-       /* compare and output */
-
-       if (ical_ctdl_is_overlap(t1start, t1end, t2start, t2end)) {
-               cprintf("%ld||%s|%s|%d|\n",
-                       existing_msgnum,
-                       conflict_event_uid,
-                       conflict_event_summary,
-                       (       (!IsEmptyStr(compare_uid)
-                               &&(!strcasecmp(compare_uid,
-                               conflict_event_uid))) ? 1 : 0
-                               )
-                       );
-               conflict_reported = 1;
-       }
-
-       return(conflict_reported);
-}
-
-
-/*
- * Phase 5 of "hunt for conflicts"
- * Called by ical_conflicts_phase4()
- *
- * We have the proposed event boiled down to start and end times.
- * Now check it against an existing event. 
- */
-void ical_conflicts_phase5(struct icaltimetype t1start,
-                       struct icaltimetype t1end,
-                       icalcomponent *existing_event,
-                       long existing_msgnum,
-                       char *compare_uid)
-{
-       char conflict_event_uid[SIZ];
-       char conflict_event_summary[SIZ];
-       struct icaltimetype t2start, t2end;
-       icalproperty *p;
-
-       /* recur variables */
-       icalproperty *rrule = NULL;
-       struct icalrecurrencetype recur;
-       icalrecur_iterator *ritr = NULL;
-       struct icaldurationtype dur;
-       int num_recur = 0;
-
-       /* initialization */
-       strcpy(conflict_event_uid, "");
-       strcpy(conflict_event_summary, "");
-       t2start = icaltime_null_time();
-       t2end = icaltime_null_time();
-
-       /* existing event stuff */
-       p = ical_ctdl_get_subprop(existing_event, ICAL_DTSTART_PROPERTY);
-       if (p == NULL) return;
-       if (p != NULL) t2start = icalproperty_get_dtstart(p);
-       if (icaltime_is_utc(t2start)) {
-               t2start.zone = icaltimezone_get_utc_timezone();
-       }
-       else {
-               t2start.zone = icalcomponent_get_timezone(existing_event,
-                       icalparameter_get_tzid(
-                               icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
-                       )
-               );
-               if (!t2start.zone) {
-                       t2start.zone = get_default_icaltimezone();
-               }
-       }
-
-       p = ical_ctdl_get_subprop(existing_event, ICAL_DTEND_PROPERTY);
-       if (p != NULL) {
-               t2end = icalproperty_get_dtend(p);
-
-               if (icaltime_is_utc(t2end)) {
-                       t2end.zone = icaltimezone_get_utc_timezone();
-               }
-               else {
-                       t2end.zone = icalcomponent_get_timezone(existing_event,
-                               icalparameter_get_tzid(
-                                       icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
-                               )
-                       );
-                       if (!t2end.zone) {
-                               t2end.zone = get_default_icaltimezone();
-                       }
-               }
-               dur = icaltime_subtract(t2end, t2start);
-       }
-       else {
-               memset (&dur, 0, sizeof(struct icaldurationtype));
-       }
-
-       rrule = ical_ctdl_get_subprop(existing_event, ICAL_RRULE_PROPERTY);
-       if (rrule) {
-               recur = icalproperty_get_rrule(rrule);
-               ritr = icalrecur_iterator_new(recur, t2start);
-       }
-
-       do {
-               p = ical_ctdl_get_subprop(existing_event, ICAL_UID_PROPERTY);
-               if (p != NULL) {
-                       strcpy(conflict_event_uid, icalproperty_get_comment(p));
-               }
-       
-               p = ical_ctdl_get_subprop(existing_event, ICAL_SUMMARY_PROPERTY);
-               if (p != NULL) {
-                       strcpy(conflict_event_summary, icalproperty_get_comment(p));
-               }
-       
-               if (ical_conflicts_phase6(t1start, t1end, t2start, t2end,
-                  existing_msgnum, conflict_event_uid, conflict_event_summary, compare_uid))
-               {
-                       num_recur = MAX_RECUR + 1;      /* force it out of scope, no need to continue */
-               }
-
-               if (rrule) {
-                       t2start = icalrecur_iterator_next(ritr);
-                       if (!icaltime_is_null_time(t2end)) {
-                               const icaltimezone *hold_zone = t2end.zone;
-                               t2end = icaltime_add(t2start, dur);
-                               t2end.zone = hold_zone;
-                       }
-                       ++num_recur;
-               }
-
-               if (icaltime_compare(t2start, t1end) < 0) {
-                       num_recur = MAX_RECUR + 1;      /* force it out of scope */
-               }
-
-       } while ( (rrule) && (!icaltime_is_null_time(t2start)) && (num_recur < MAX_RECUR) );
-       icalrecur_iterator_free(ritr);
-}
-
-
-/*
- * Phase 4 of "hunt for conflicts"
- * Called by ical_hunt_for_conflicts_backend()
- *
- * At this point we've got it boiled down to two icalcomponent events in memory.
- * If they conflict, output something to the client.
- */
-void ical_conflicts_phase4(icalcomponent *proposed_event,
-               icalcomponent *existing_event,
-               long existing_msgnum)
-{
-       struct icaltimetype t1start, t1end;
-       icalproperty *p;
-       char compare_uid[SIZ];
-
-       /* recur variables */
-       icalproperty *rrule = NULL;
-       struct icalrecurrencetype recur;
-       icalrecur_iterator *ritr = NULL;
-       struct icaldurationtype dur;
-       int num_recur = 0;
-
-       /* initialization */
-       t1end = icaltime_null_time();
-       *compare_uid = '\0';
-
-       /* proposed event stuff */
-
-       p = ical_ctdl_get_subprop(proposed_event, ICAL_DTSTART_PROPERTY);
-       if (p == NULL)
-               return;
-       else
-               t1start = icalproperty_get_dtstart(p);
-
-       if (icaltime_is_utc(t1start)) {
-               t1start.zone = icaltimezone_get_utc_timezone();
-       }
-       else {
-               t1start.zone = icalcomponent_get_timezone(proposed_event,
-                       icalparameter_get_tzid(
-                               icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
-                       )
-               );
-               if (!t1start.zone) {
-                       t1start.zone = get_default_icaltimezone();
-               }
-       }
-       
-       p = ical_ctdl_get_subprop(proposed_event, ICAL_DTEND_PROPERTY);
-       if (p != NULL) {
-               t1end = icalproperty_get_dtend(p);
-
-               if (icaltime_is_utc(t1end)) {
-                       t1end.zone = icaltimezone_get_utc_timezone();
-               }
-               else {
-                       t1end.zone = icalcomponent_get_timezone(proposed_event,
-                               icalparameter_get_tzid(
-                                       icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
-                               )
-                       );
-                       if (!t1end.zone) {
-                               t1end.zone = get_default_icaltimezone();
-                       }
-               }
-
-               dur = icaltime_subtract(t1end, t1start);
-       }
-       else {
-               memset (&dur, 0, sizeof(struct icaldurationtype));
-       }
-
-       rrule = ical_ctdl_get_subprop(proposed_event, ICAL_RRULE_PROPERTY);
-       if (rrule) {
-               recur = icalproperty_get_rrule(rrule);
-               ritr = icalrecur_iterator_new(recur, t1start);
-       }
-
-       p = ical_ctdl_get_subprop(proposed_event, ICAL_UID_PROPERTY);
-       if (p != NULL) {
-               strcpy(compare_uid, icalproperty_get_comment(p));
-       }
-
-       do {
-               ical_conflicts_phase5(t1start, t1end, existing_event, existing_msgnum, compare_uid);
-
-               if (rrule) {
-                       t1start = icalrecur_iterator_next(ritr);
-                       if (!icaltime_is_null_time(t1end)) {
-                               const icaltimezone *hold_zone = t1end.zone;
-                               t1end = icaltime_add(t1start, dur);
-                               t1end.zone = hold_zone;
-                       }
-                       ++num_recur;
-               }
-
-       } while ( (rrule) && (!icaltime_is_null_time(t1start)) && (num_recur < MAX_RECUR) );
-       icalrecur_iterator_free(ritr);
-}
-
-
-/*
- * Phase 3 of "hunt for conflicts"
- * Called by ical_hunt_for_conflicts()
- */
-void ical_hunt_for_conflicts_backend(long msgnum, void *data) {
-       icalcomponent *proposed_event;
-       struct CtdlMessage *msg = NULL;
-       struct ical_respond_data ird;
-
-       proposed_event = (icalcomponent *)data;
-
-       msg = CtdlFetchMessage(msgnum, 1);
-       if (msg == NULL) return;
-       memset(&ird, 0, sizeof ird);
-       strcpy(ird.desired_partnum, "_HUNT_");
-       mime_parser(CM_RANGE(msg, eMesageText),
-                   *ical_locate_part,          /* callback function */
-                   NULL, NULL,
-                   (void *) &ird,                      /* user data */
-                   0
-       );
-       CM_Free(msg);
-
-       if (ird.cal == NULL) return;
-
-       ical_conflicts_phase4(proposed_event, ird.cal, msgnum);
-       icalcomponent_free(ird.cal);
-}
-
-
-/* 
- * Phase 2 of "hunt for conflicts" operation.
- * At this point we have a calendar object which represents the VEVENT that
- * is proposed for addition to the calendar.  Now hunt through the user's
- * calendar room, and output zero or more existing VEVENTs which conflict
- * with this one.
- */
-void ical_hunt_for_conflicts(icalcomponent *cal) {
-       char hold_rm[ROOMNAMELEN];
-
-       strcpy(hold_rm, CC->room.QRname);       /* save current room */
-
-       if (CtdlGetRoom(&CC->room, USERCALENDARROOM) != 0) {
-               CtdlGetRoom(&CC->room, hold_rm);
-               cprintf("%d You do not have a calendar.\n", ERROR + ROOM_NOT_FOUND);
-               return;
-       }
-
-       cprintf("%d Conflicting events:\n", LISTING_FOLLOWS);
-
-       CtdlForEachMessage(MSGS_ALL, 0, NULL,
-               NULL,
-               NULL,
-               ical_hunt_for_conflicts_backend,
-               (void *) cal
-       );
-
-       cprintf("000\n");
-       CtdlGetRoom(&CC->room, hold_rm);        /* return to saved room */
-
-}
-
-
-/*
- * Hunt for conflicts (Phase 1 -- retrieve the object and call Phase 2)
- */
-void ical_conflicts(long msgnum, char *partnum) {
-       struct CtdlMessage *msg = NULL;
-       struct ical_respond_data ird;
-
-       msg = CtdlFetchMessage(msgnum, 1);
-       if (msg == NULL) {
-               cprintf("%d Message %ld not found\n",
-                       ERROR + ILLEGAL_VALUE,
-                       (long)msgnum
-               );
-               return;
-       }
-
-       memset(&ird, 0, sizeof ird);
-       strcpy(ird.desired_partnum, partnum);
-       mime_parser(CM_RANGE(msg, eMesageText),
-                   *ical_locate_part,          /* callback function */
-                   NULL, NULL,
-                   (void *) &ird,                      /* user data */
-                   0
-               );
-
-       CM_Free(msg);
-
-       if (ird.cal != NULL) {
-               ical_hunt_for_conflicts(ird.cal);
-               icalcomponent_free(ird.cal);
-               return;
-       }
-
-       cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
-}
-
-
-/*
- * Look for busy time in a VEVENT and add it to the supplied VFREEBUSY.
- *
- * fb                  The VFREEBUSY component to which we are appending
- * top_level_cal       The top-level VCALENDAR component which contains a VEVENT to be added
- */
-void ical_add_to_freebusy(icalcomponent *fb, icalcomponent *top_level_cal) {
-       icalcomponent *cal;
-       icalproperty *p;
-       icalvalue *v;
-       struct icalperiodtype this_event_period = icalperiodtype_null_period();
-       icaltimetype dtstart;
-       icaltimetype dtend;
-
-       /* recur variables */
-       icalproperty *rrule = NULL;
-       struct icalrecurrencetype recur;
-       icalrecur_iterator *ritr = NULL;
-       struct icaldurationtype dur;
-       int num_recur = 0;
-
-       if (!top_level_cal) return;
-
-       /* Find the VEVENT component containing an event */
-       cal = icalcomponent_get_first_component(top_level_cal, ICAL_VEVENT_COMPONENT);
-       if (!cal) return;
-
-       /* If this event is not opaque, the user isn't publishing it as
-        * busy time, so don't bother doing anything else.
-        */
-       p = icalcomponent_get_first_property(cal, ICAL_TRANSP_PROPERTY);
-       if (p != NULL) {
-               v = icalproperty_get_value(p);
-               if (v != NULL) {
-                       if (icalvalue_get_transp(v) != ICAL_TRANSP_OPAQUE) {
-                               return;
-                       }
-               }
-       }
-
-       /*
-        * Now begin calculating the event start and end times.
-        */
-       p = icalcomponent_get_first_property(cal, ICAL_DTSTART_PROPERTY);
-       if (!p) return;
-       dtstart = icalproperty_get_dtstart(p);
-
-       if (icaltime_is_utc(dtstart)) {
-               dtstart.zone = icaltimezone_get_utc_timezone();
-       }
-       else {
-               dtstart.zone = icalcomponent_get_timezone(top_level_cal,
-                       icalparameter_get_tzid(
-                               icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
-                       )
-               );
-               if (!dtstart.zone) {
-                       dtstart.zone = get_default_icaltimezone();
-               }
-       }
-
-       dtend = icalcomponent_get_dtend(cal);
-       if (!icaltime_is_null_time(dtend)) {
-               dur = icaltime_subtract(dtend, dtstart);
-       }
-       else {
-               memset (&dur, 0, sizeof(struct icaldurationtype));
-       }
-
-       /* Is a recurrence specified?  If so, get ready to process it... */
-       rrule = ical_ctdl_get_subprop(cal, ICAL_RRULE_PROPERTY);
-       if (rrule) {
-               recur = icalproperty_get_rrule(rrule);
-               ritr = icalrecur_iterator_new(recur, dtstart);
-       }
-
-       do {
-               /* Convert the DTSTART and DTEND properties to an icalperiod. */
-               this_event_period.start = dtstart;
-       
-               if (!icaltime_is_null_time(dtend)) {
-                       this_event_period.end = dtend;
-               }
-
-               /* Convert the timestamps to UTC.  It's ok to do this because we've already expanded
-                * recurrences and this data is never going to get used again.
-                */
-               this_event_period.start = icaltime_convert_to_zone(
-                       this_event_period.start,
-                       icaltimezone_get_utc_timezone()
-               );
-               this_event_period.end = icaltime_convert_to_zone(
-                       this_event_period.end,
-                       icaltimezone_get_utc_timezone()
-               );
-       
-               /* Now add it. */
-               icalcomponent_add_property(fb, icalproperty_new_freebusy(this_event_period));
-
-               /* Make sure the DTSTART property of the freebusy *list* is set to
-                * the DTSTART property of the *earliest event*.
-                */
-               p = icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY);
-               if (p == NULL) {
-                       icalcomponent_set_dtstart(fb, this_event_period.start);
-               }
-               else {
-                       if (icaltime_compare(this_event_period.start, icalcomponent_get_dtstart(fb)) < 0) {
-                               icalcomponent_set_dtstart(fb, this_event_period.start);
-                       }
-               }
-       
-               /* Make sure the DTEND property of the freebusy *list* is set to
-                * the DTEND property of the *latest event*.
-                */
-               p = icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY);
-               if (p == NULL) {
-                       icalcomponent_set_dtend(fb, this_event_period.end);
-               }
-               else {
-                       if (icaltime_compare(this_event_period.end, icalcomponent_get_dtend(fb)) > 0) {
-                               icalcomponent_set_dtend(fb, this_event_period.end);
-                       }
-               }
-
-               if (rrule) {
-                       dtstart = icalrecur_iterator_next(ritr);
-                       if (!icaltime_is_null_time(dtend)) {
-                               dtend = icaltime_add(dtstart, dur);
-                               dtend.zone = dtstart.zone;
-                       }
-                       ++num_recur;
-               }
-
-       } while ( (rrule) && (!icaltime_is_null_time(dtstart)) && (num_recur < MAX_RECUR) ) ;
-       icalrecur_iterator_free(ritr);
-}
-
-
-/*
- * Backend for ical_freebusy()
- *
- * This function simply loads the messages in the user's calendar room,
- * which contain VEVENTs, then strips them of all non-freebusy data, and
- * adds them to the supplied VCALENDAR.
- *
- */
-void ical_freebusy_backend(long msgnum, void *data) {
-       icalcomponent *fb;
-       struct CtdlMessage *msg = NULL;
-       struct ical_respond_data ird;
-
-       fb = (icalcomponent *)data;             /* User-supplied data will be the VFREEBUSY component */
-
-       msg = CtdlFetchMessage(msgnum, 1);
-       if (msg == NULL) return;
-       memset(&ird, 0, sizeof ird);
-       strcpy(ird.desired_partnum, "_HUNT_");
-       mime_parser(CM_RANGE(msg, eMesageText),
-                   *ical_locate_part,          /* callback function */
-                   NULL, NULL,
-                   (void *) &ird,                      /* user data */
-                   0
-               );
-       CM_Free(msg);
-
-       if (ird.cal) {
-               ical_add_to_freebusy(fb, ird.cal);              /* Add VEVENT times to VFREEBUSY */
-               icalcomponent_free(ird.cal);
-       }
-}
-
-
-/*
- * Grab another user's free/busy times
- */
-void ical_freebusy(char *who) {
-       struct ctdluser usbuf;
-       char calendar_room_name[ROOMNAMELEN];
-       char hold_rm[ROOMNAMELEN];
-       char *serialized_request = NULL;
-       icalcomponent *encaps = NULL;
-       icalcomponent *fb = NULL;
-       int found_user = (-1);
-       struct recptypes *recp = NULL;
-       char buf[256];
-       char host[256];
-       char type[256];
-       int i = 0;
-       int config_lines = 0;
-
-       /* First try an exact match. */
-       found_user = CtdlGetUser(&usbuf, who);
-
-       /* If not found, try it as an unqualified email address. */
-       if (found_user != 0) {
-               strcpy(buf, who);
-               recp = validate_recipients(buf, NULL, 0);
-               syslog(LOG_DEBUG, "calendar: trying <%s>", buf);
-               if (recp != NULL) {
-                       if (recp->num_local == 1) {
-                               found_user = CtdlGetUser(&usbuf, recp->recp_local);
-                       }
-                       free_recipients(recp);
-               }
-       }
-
-       /* If still not found, try it as an address qualified with the
-        * primary FQDN of this Citadel node.
-        */
-       if (found_user != 0) {
-               snprintf(buf, sizeof buf, "%s@%s", who, CtdlGetConfigStr("c_fqdn"));
-               syslog(LOG_DEBUG, "calendar: trying <%s>", buf);
-               recp = validate_recipients(buf, NULL, 0);
-               if (recp != NULL) {
-                       if (recp->num_local == 1) {
-                               found_user = CtdlGetUser(&usbuf, recp->recp_local);
-                       }
-                       free_recipients(recp);
-               }
-       }
-
-       /* Still not found?  Try qualifying it with every domain we
-        * might have addresses in.
-        */
-       if (found_user != 0) {
-               config_lines = num_tokens(inetcfg, '\n');
-               for (i=0; ((i < config_lines) && (found_user != 0)); ++i) {
-                       extract_token(buf, inetcfg, i, '\n', sizeof buf);
-                       extract_token(host, buf, 0, '|', sizeof host);
-                       extract_token(type, buf, 1, '|', sizeof type);
-
-                       if ( (!strcasecmp(type, "localhost"))
-                          || (!strcasecmp(type, "directory")) ) {
-                               snprintf(buf, sizeof buf, "%s@%s", who, host);
-                               syslog(LOG_DEBUG, "calendar: trying <%s>", buf);
-                               recp = validate_recipients(buf, NULL, 0);
-                               if (recp != NULL) {
-                                       if (recp->num_local == 1) {
-                                               found_user = CtdlGetUser(&usbuf, recp->recp_local);
-                                       }
-                                       free_recipients(recp);
-                               }
-                       }
-               }
-       }
-
-       if (found_user != 0) {
-               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
-               return;
-       }
-
-       CtdlMailboxName(calendar_room_name, sizeof calendar_room_name,
-               &usbuf, USERCALENDARROOM);
-
-       strcpy(hold_rm, CC->room.QRname);       /* save current room */
-
-       if (CtdlGetRoom(&CC->room, calendar_room_name) != 0) {
-               cprintf("%d Cannot open calendar\n", ERROR + ROOM_NOT_FOUND);
-               CtdlGetRoom(&CC->room, hold_rm);
-               return;
-       }
-
-       /* Create a VFREEBUSY subcomponent */
-       syslog(LOG_DEBUG, "calendar: creating VFREEBUSY component");
-       fb = icalcomponent_new_vfreebusy();
-       if (fb == NULL) {
-               cprintf("%d Internal error: cannot allocate memory.\n", ERROR + INTERNAL_ERROR);
-               CtdlGetRoom(&CC->room, hold_rm);
-               return;
-       }
-
-       /* Set the method to PUBLISH */
-       icalcomponent_set_method(fb, ICAL_METHOD_PUBLISH);
-
-       /* Set the DTSTAMP to right now. */
-       icalcomponent_set_dtstamp(fb, icaltime_from_timet_with_zone(time(NULL), 0, icaltimezone_get_utc_timezone()));
-
-       /* Add the user's email address as ORGANIZER */
-       sprintf(buf, "MAILTO:%s", who);
-       if (strchr(buf, '@') == NULL) {
-               strcat(buf, "@");
-               strcat(buf, CtdlGetConfigStr("c_fqdn"));
-       }
-       for (i=0; buf[i]; ++i) {
-               if (buf[i]==' ') buf[i] = '_';
-       }
-       icalcomponent_add_property(fb, icalproperty_new_organizer(buf));
-
-       /* Add busy time from events */
-       syslog(LOG_DEBUG, "calendar: adding busy time from events");
-       CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, ical_freebusy_backend, (void *)fb );
-
-       /* If values for DTSTART and DTEND are still not present, set them
-        * to yesterday and tomorrow as default values.
-        */
-       if (icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY) == NULL) {
-               icalcomponent_set_dtstart(fb, icaltime_from_timet_with_zone(time(NULL)-86400L, 0, icaltimezone_get_utc_timezone()));
-       }
-       if (icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY) == NULL) {
-               icalcomponent_set_dtend(fb, icaltime_from_timet_with_zone(time(NULL)+86400L, 0, icaltimezone_get_utc_timezone()));
-       }
-
-       /* Put the freebusy component into the calendar component */
-       syslog(LOG_DEBUG, "calendar: encapsulating");
-       encaps = ical_encapsulate_subcomponent(fb);
-       if (encaps == NULL) {
-               icalcomponent_free(fb);
-               cprintf("%d Internal error: cannot allocate memory.\n",
-                       ERROR + INTERNAL_ERROR);
-               CtdlGetRoom(&CC->room, hold_rm);
-               return;
-       }
-
-       /* Set the method to PUBLISH */
-       syslog(LOG_DEBUG, "calendar: setting method");
-       icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
-
-       /* Serialize it */
-       syslog(LOG_DEBUG, "calendar: serializing");
-       serialized_request = icalcomponent_as_ical_string_r(encaps);
-       icalcomponent_free(encaps);     /* Don't need this anymore. */
-
-       cprintf("%d Free/busy for %s\n", LISTING_FOLLOWS, usbuf.fullname);
-       if (serialized_request != NULL) {
-               client_write(serialized_request, strlen(serialized_request));
-               free(serialized_request);
-       }
-       cprintf("\n000\n");
-
-       /* Go back to the room from which we came... */
-       CtdlGetRoom(&CC->room, hold_rm);
-}
-
-
-/*
- * Backend for ical_getics()
- * 
- * This is a ForEachMessage() callback function that searches the current room
- * for calendar events and adds them each into one big calendar component.
- */
-void ical_getics_backend(long msgnum, void *data) {
-       icalcomponent *encaps, *c;
-       struct CtdlMessage *msg = NULL;
-       struct ical_respond_data ird;
-
-       encaps = (icalcomponent *)data;
-       if (encaps == NULL) return;
-
-       /* Look for the calendar event... */
-
-       msg = CtdlFetchMessage(msgnum, 1);
-       if (msg == NULL) return;
-       memset(&ird, 0, sizeof ird);
-       strcpy(ird.desired_partnum, "_HUNT_");
-       mime_parser(CM_RANGE(msg, eMesageText),
-                   *ical_locate_part,          /* callback function */
-                   NULL, NULL,
-                   (void *) &ird,                      /* user data */
-                   0
-       );
-       CM_Free(msg);
-
-       if (ird.cal == NULL) return;
-
-       /* Here we go: put the VEVENT into the VCALENDAR.  We now no longer
-        * are responsible for "the_request"'s memory -- it will be freed
-        * when we free "encaps".
-        */
-
-       /* If the top-level component is *not* a VCALENDAR, we can drop it right
-        * in.  This will almost never happen.
-        */
-       if (icalcomponent_isa(ird.cal) != ICAL_VCALENDAR_COMPONENT) {
-               icalcomponent_add_component(encaps, ird.cal);
-       }
-       /*
-        * In the more likely event that we're looking at a VCALENDAR with the VEVENT
-        * and other components encapsulated inside, we have to extract them.
-        */
-       else {
-               for (c = icalcomponent_get_first_component(ird.cal, ICAL_ANY_COMPONENT);
-                   (c != NULL);
-                   c = icalcomponent_get_next_component(ird.cal, ICAL_ANY_COMPONENT)) {
-
-                       /* For VTIMEZONE components, suppress duplicates of the same tzid */
-
-                       if (icalcomponent_isa(c) == ICAL_VTIMEZONE_COMPONENT) {
-                               icalproperty *p = icalcomponent_get_first_property(c, ICAL_TZID_PROPERTY);
-                               if (p) {
-                                       const char *tzid = icalproperty_get_tzid(p);
-                                       if (!icalcomponent_get_timezone(encaps, tzid)) {
-                                               icalcomponent_add_component(encaps,
-                                                                       icalcomponent_new_clone(c));
-                                       }
-                               }
-                       }
-
-                       /* All other types of components can go in verbatim */
-                       else {
-                               icalcomponent_add_component(encaps, icalcomponent_new_clone(c));
-                       }
-               }
-               icalcomponent_free(ird.cal);
-       }
-}
-
-
-/*
- * Retrieve all of the calendar items in the current room, and output them
- * as a single icalendar object.
- */
-void ical_getics(void)
-{
-       icalcomponent *encaps = NULL;
-       char *ser = NULL;
-
-       if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
-          &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
-               cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
-               return;         /* Not an iCalendar-centric room */
-       }
-
-       encaps = icalcomponent_new_vcalendar();
-       if (encaps == NULL) {
-               syslog(LOG_ERR, "calendar: could not allocate component!");
-               cprintf("%d Could not allocate memory\n", ERROR+INTERNAL_ERROR);
-               return;
-       }
-
-       cprintf("%d one big calendar\n", LISTING_FOLLOWS);
-
-       /* Set the Product ID */
-       icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
-
-       /* Set the Version Number */
-       icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
-
-       /* Set the method to PUBLISH */
-       icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
-
-       /* Now go through the room encapsulating all calendar items. */
-       CtdlForEachMessage(MSGS_ALL, 0, NULL,
-               NULL,
-               NULL,
-               ical_getics_backend,
-               (void *) encaps
-       );
-
-       ser = icalcomponent_as_ical_string_r(encaps);
-       icalcomponent_free(encaps);                     /* Don't need this anymore. */
-       client_write(ser, strlen(ser));
-       free(ser);
-       cprintf("\n000\n");
-}
-
-
-/*
- * Helper callback function for ical_putics() to discover which TZID's we need.
- * Simply put the tzid name string into a hash table.  After the callbacks are
- * done we'll go through them and attach the ones that we have.
- */
-void ical_putics_grabtzids(icalparameter *param, void *data)
-{
-       const char *tzid = icalparameter_get_tzid(param);
-       HashList *keys = (HashList *) data;
-       
-       if ( (keys) && (tzid) && (!IsEmptyStr(tzid)) ) {
-               Put(keys, tzid, strlen(tzid), strdup(tzid), NULL);
-       }
-}
-
-
-/*
- * Delete all of the calendar items in the current room, and replace them
- * with calendar items from a client-supplied data stream.
- */
-void ical_putics(void)
-{
-       char *calstream = NULL;
-       icalcomponent *cal;
-       icalcomponent *c;
-       icalcomponent *encaps = NULL;
-       HashList *tzidlist = NULL;
-       HashPos *HashPos;
-       void *Value;
-       const char *Key;
-       long len;
-
-       /* Only allow this operation if we're in a room containing a calendar or tasks view */
-       if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
-          &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
-               cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
-               return;
-       }
-
-       /* Only allow this operation if we have permission to overwrite the existing calendar */
-       if (!CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) {
-               cprintf("%d Permission denied.\n", ERROR+HIGHER_ACCESS_REQUIRED);
-               return;
-       }
-
-       cprintf("%d Transmit data now\n", SEND_LISTING);
-       calstream = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
-       if (calstream == NULL) {
-               return;
-       }
-
-       cal = icalcomponent_new_from_string(calstream);
-       free(calstream);
-
-       /* We got our data stream -- now do something with it. */
-
-       /* Delete the existing messages in the room, because we are overwriting
-        * the entire calendar with an entire new (or updated) calendar.
-        * (Careful: this opens an S_ROOMS critical section!)
-        */
-       CtdlDeleteMessages(CC->room.QRname, NULL, 0, "");
-
-       /* If the top-level component is *not* a VCALENDAR, we can drop it right
-        * in.  This will almost never happen.
-        */
-       if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
-               ical_write_to_cal(NULL, cal);
-       }
-       /*
-        * In the more likely event that we're looking at a VCALENDAR with the VEVENT
-        * and other components encapsulated inside, we have to extract them.
-        */
-       else {
-               for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
-                   (c != NULL);
-                   c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
-
-                       /* Non-VTIMEZONE components each get written as individual messages.
-                        * But we also need to attach the relevant VTIMEZONE components to them.
-                        */
-                       if ( (icalcomponent_isa(c) != ICAL_VTIMEZONE_COMPONENT)
-                          && (encaps = icalcomponent_new_vcalendar()) ) {
-                               icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
-                               icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
-                               icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
-
-                               /* Attach any needed timezones here */
-                               tzidlist = NewHash(1, NULL);
-                               if (tzidlist) {
-                                       icalcomponent_foreach_tzid(c, ical_putics_grabtzids, tzidlist);
-                               }
-                               HashPos = GetNewHashPos(tzidlist, 0);
-
-                               while (GetNextHashPos(tzidlist, HashPos, &len, &Key, &Value)) {
-                                       syslog(LOG_DEBUG, "calendar: attaching timezone '%s'", (char*) Value);
-                                       icaltimezone *t = NULL;
-
-                                       /* First look for a timezone attached to the original calendar */
-                                       t = icalcomponent_get_timezone(cal, Value);
-
-                                       /* Try built-in tzdata if the right one wasn't attached */
-                                       if (!t) {
-                                               t = icaltimezone_get_builtin_timezone(Value);
-                                       }
-
-                                       /* I've got a valid timezone to attach. */
-                                       if (t) {
-                                               icalcomponent_add_component(encaps,
-                                                       icalcomponent_new_clone(
-                                                               icaltimezone_get_component(t)
-                                                       )
-                                               );
-                                       }
-
-                               }
-                               DeleteHashPos(&HashPos);
-                               DeleteHash(&tzidlist);
-
-                               /* Now attach the component itself (usually a VEVENT or VTODO) */
-                               icalcomponent_add_component(encaps, icalcomponent_new_clone(c));
-
-                               /* Write it to the message store */
-                               ical_write_to_cal(NULL, encaps);
-                               icalcomponent_free(encaps);
-                       }
-               }
-       }
-
-       icalcomponent_free(cal);
-}
-
-
-/*
- * All Citadel calendar commands from the client come through here.
- */
-void cmd_ical(char *argbuf)
-{
-       char subcmd[64];
-       long msgnum;
-       char partnum[256];
-       char action[256];
-       char who[256];
-
-       extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
-
-       /* Allow "test" and "freebusy" subcommands without logging in. */
-
-       if (!strcasecmp(subcmd, "test")) {
-               cprintf("%d This server supports calendaring\n", CIT_OK);
-               return;
-       }
-
-       if (!strcasecmp(subcmd, "freebusy")) {
-               extract_token(who, argbuf, 1, '|', sizeof who);
-               ical_freebusy(who);
-               return;
-       }
-
-       if (!strcasecmp(subcmd, "sgi")) {
-               CIT_ICAL->server_generated_invitations = (extract_int(argbuf, 1) ? 1 : 0) ;
-               cprintf("%d %d\n", CIT_OK, CIT_ICAL->server_generated_invitations);
-               return;
-       }
-
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       if (!strcasecmp(subcmd, "respond")) {
-               msgnum = extract_long(argbuf, 1);
-               extract_token(partnum, argbuf, 2, '|', sizeof partnum);
-               extract_token(action, argbuf, 3, '|', sizeof action);
-               ical_respond(msgnum, partnum, action);
-               return;
-       }
-
-       if (!strcasecmp(subcmd, "handle_rsvp")) {
-               msgnum = extract_long(argbuf, 1);
-               extract_token(partnum, argbuf, 2, '|', sizeof partnum);
-               extract_token(action, argbuf, 3, '|', sizeof action);
-               ical_handle_rsvp(msgnum, partnum, action);
-               return;
-       }
-
-       if (!strcasecmp(subcmd, "conflicts")) {
-               msgnum = extract_long(argbuf, 1);
-               extract_token(partnum, argbuf, 2, '|', sizeof partnum);
-               ical_conflicts(msgnum, partnum);
-               return;
-       }
-
-       if (!strcasecmp(subcmd, "getics")) {
-               ical_getics();
-               return;
-       }
-
-       if (!strcasecmp(subcmd, "putics")) {
-               ical_putics();
-               return;
-       }
-
-       cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
-}
-
-
-/*
- * We don't know if the calendar room exists so we just create it at login
- */
-void ical_CtdlCreateRoom(void)
-{
-       struct ctdlroom qr;
-       visit vbuf;
-
-       /* Create the calendar room if it doesn't already exist */
-       CtdlCreateRoom(USERCALENDARROOM, 4, "", 0, 1, 0, VIEW_CALENDAR);
-
-       /* Set expiration policy to manual; otherwise objects will be lost! */
-       if (CtdlGetRoomLock(&qr, USERCALENDARROOM)) {
-               syslog(LOG_ERR, "calendar: couldn't get the user calendar room");
-               return;
-       }
-       qr.QRep.expire_mode = EXPIRE_MANUAL;
-       qr.QRdefaultview = VIEW_CALENDAR;       /* 3 = calendar view */
-       CtdlPutRoomLock(&qr);
-
-       /* Set the view to a calendar view */
-       CtdlGetRelationship(&vbuf, &CC->user, &qr);
-       vbuf.v_view = VIEW_CALENDAR;
-       CtdlSetRelationship(&vbuf, &CC->user, &qr);
-
-       /* Create the tasks list room if it doesn't already exist */
-       CtdlCreateRoom(USERTASKSROOM, 4, "", 0, 1, 0, VIEW_TASKS);
-
-       /* Set expiration policy to manual; otherwise objects will be lost! */
-       if (CtdlGetRoomLock(&qr, USERTASKSROOM)) {
-               syslog(LOG_ERR, "calendar: couldn't get the user calendar room!");
-               return;
-       }
-       qr.QRep.expire_mode = EXPIRE_MANUAL;
-       qr.QRdefaultview = VIEW_TASKS;
-       CtdlPutRoomLock(&qr);
-
-       /* Set the view to a task list view */
-       CtdlGetRelationship(&vbuf, &CC->user, &qr);
-       vbuf.v_view = VIEW_TASKS;
-       CtdlSetRelationship(&vbuf, &CC->user, &qr);
-
-       /* Create the notes room if it doesn't already exist */
-       CtdlCreateRoom(USERNOTESROOM, 4, "", 0, 1, 0, VIEW_NOTES);
-
-       /* Set expiration policy to manual; otherwise objects will be lost! */
-       if (CtdlGetRoomLock(&qr, USERNOTESROOM)) {
-               syslog(LOG_ERR, "calendar: couldn't get the user calendar room!");
-               return;
-       }
-       qr.QRep.expire_mode = EXPIRE_MANUAL;
-       qr.QRdefaultview = VIEW_NOTES;
-       CtdlPutRoomLock(&qr);
-
-       /* Set the view to a notes view */
-       CtdlGetRelationship(&vbuf, &CC->user, &qr);
-       vbuf.v_view = VIEW_NOTES;
-       CtdlSetRelationship(&vbuf, &CC->user, &qr);
-
-       return;
-}
-
-
-/*
- * ical_send_out_invitations() is called by ical_saving_vevent() when it finds a VEVENT.
- *
- * top_level_cal is the highest available level calendar object.
- * cal is the subcomponent containing the VEVENT.
- *
- * Note: if you change the encapsulation code here, change it in WebCit's ical_encapsulate_subcomponent()
- */
-void ical_send_out_invitations(icalcomponent *top_level_cal, icalcomponent *cal) {
-       icalcomponent *the_request = NULL;
-       char *serialized_request = NULL;
-       icalcomponent *encaps = NULL;
-       char *request_message_text = NULL;
-       struct CtdlMessage *msg = NULL;
-       struct recptypes *valid = NULL;
-       char attendees_string[SIZ];
-       int num_attendees = 0;
-       char this_attendee[256];
-       icalproperty *attendee = NULL;
-       char summary_string[SIZ];
-       icalproperty *summary = NULL;
-       size_t reqsize;
-       icalproperty *p;
-       struct icaltimetype t;
-       const icaltimezone *attached_zones[5] = { NULL, NULL, NULL, NULL, NULL };
-       int i;
-       const icaltimezone *z;
-       int num_zones_attached = 0;
-       int zone_already_attached;
-       icalparameter *tzidp = NULL;
-       const char *tzidc = NULL;
-
-       if (cal == NULL) {
-               syslog(LOG_ERR, "calendar: trying to reply to NULL event?");
-               return;
-       }
-
-       /* If this is a VCALENDAR component, look for a VEVENT subcomponent. */
-       if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
-               ical_send_out_invitations(top_level_cal,
-                       icalcomponent_get_first_component(
-                               cal, ICAL_VEVENT_COMPONENT
-                       )
-               );
-               return;
-       }
-
-       /* Clone the event */
-       the_request = icalcomponent_new_clone(cal);
-       if (the_request == NULL) {
-               syslog(LOG_ERR, "calendar: cannot clone calendar object");
-               return;
-       }
-
-       /* Extract the summary string -- we'll use it as the
-        * message subject for the request
-        */
-       strcpy(summary_string, "Meeting request");
-       summary = icalcomponent_get_first_property(the_request, ICAL_SUMMARY_PROPERTY);
-       if (summary != NULL) {
-               if (icalproperty_get_summary(summary)) {
-                       strcpy(summary_string,
-                               icalproperty_get_summary(summary) );
-               }
-       }
-
-       /* Determine who the recipients of this message are (the attendees) */
-       strcpy(attendees_string, "");
-       for (attendee = icalcomponent_get_first_property(the_request, ICAL_ATTENDEE_PROPERTY); attendee != NULL; attendee = icalcomponent_get_next_property(the_request, ICAL_ATTENDEE_PROPERTY)) {
-               const char *ch = icalproperty_get_attendee(attendee);
-               if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) {
-                       safestrncpy(this_attendee, ch + 7, sizeof(this_attendee));
-                       
-                       if (!CtdlIsMe(this_attendee, sizeof this_attendee)) {   /* don't send an invitation to myself! */
-                               snprintf(&attendees_string[strlen(attendees_string)],
-                                        sizeof(attendees_string) - strlen(attendees_string),
-                                        "%s, ",
-                                        this_attendee
-                                       );
-                               ++num_attendees;
-                       }
-               }
-       }
-
-       syslog(LOG_DEBUG, "calendar: <%d> attendees: <%s>", num_attendees, attendees_string);
-
-       /* If there are no attendees, there are no invitations to send, so...
-        * don't bother putting one together!  Punch out, Maverick!
-        */
-       if (num_attendees == 0) {
-               icalcomponent_free(the_request);
-               return;
-       }
-
-       /* Encapsulate the VEVENT component into a complete VCALENDAR */
-       encaps = icalcomponent_new_vcalendar();
-       if (encaps == NULL) {
-               syslog(LOG_ERR, "calendar: could not allocate component!");
-               icalcomponent_free(the_request);
-               return;
-       }
-
-       /* Set the Product ID */
-       icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
-
-       /* Set the Version Number */
-       icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
-
-       /* Set the method to REQUEST */
-       icalcomponent_set_method(encaps, ICAL_METHOD_REQUEST);
-
-       /* Look for properties containing timezone parameters, to see if we need to attach VTIMEZONEs */
-       for (p = icalcomponent_get_first_property(the_request, ICAL_ANY_PROPERTY);
-            p != NULL;
-            p = icalcomponent_get_next_property(the_request, ICAL_ANY_PROPERTY))
-       {
-               if ( (icalproperty_isa(p) == ICAL_COMPLETED_PROPERTY)
-                 || (icalproperty_isa(p) == ICAL_CREATED_PROPERTY)
-                 || (icalproperty_isa(p) == ICAL_DATEMAX_PROPERTY)
-                 || (icalproperty_isa(p) == ICAL_DATEMIN_PROPERTY)
-                 || (icalproperty_isa(p) == ICAL_DTEND_PROPERTY)
-                 || (icalproperty_isa(p) == ICAL_DTSTAMP_PROPERTY)
-                 || (icalproperty_isa(p) == ICAL_DTSTART_PROPERTY)
-                 || (icalproperty_isa(p) == ICAL_DUE_PROPERTY)
-                 || (icalproperty_isa(p) == ICAL_EXDATE_PROPERTY)
-                 || (icalproperty_isa(p) == ICAL_LASTMODIFIED_PROPERTY)
-                 || (icalproperty_isa(p) == ICAL_MAXDATE_PROPERTY)
-                 || (icalproperty_isa(p) == ICAL_MINDATE_PROPERTY)
-                 || (icalproperty_isa(p) == ICAL_RECURRENCEID_PROPERTY)
-               ) {
-                       t = icalproperty_get_dtstart(p);        // it's safe to use dtstart for all of them
-
-                       /* Determine the tzid in order for some of the conditions below to work */
-                       tzidp = icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER);
-                       if (tzidp) {
-                               tzidc = icalparameter_get_tzid(tzidp);
-                       }
-                       else {
-                               tzidc = NULL;
-                       }
-
-                       /* First see if there's a timezone attached to the data structure itself */
-                       if (icaltime_is_utc(t)) {
-                               z = icaltimezone_get_utc_timezone();
-                       }
-                       else {
-                               z = icaltime_get_timezone(t);
-                       }
-
-                       /* If not, try to determine the tzid from the parameter using attached zones */
-                       if ((!z) && (tzidc)) {
-                               z = icalcomponent_get_timezone(top_level_cal, tzidc);
-                       }
-
-                       /* Still no good?  Try our internal database */
-                       if ((!z) && (tzidc)) {
-                               z = icaltimezone_get_builtin_timezone_from_tzid(tzidc);
-                       }
-
-                       if (z) {
-                               /* We have a valid timezone.  Good.  Now we need to attach it. */
-
-                               zone_already_attached = 0;
-                               for (i=0; i<5; ++i) {
-                                       if (z == attached_zones[i]) {
-                                               /* We've already got this one, no need to attach another. */
-                                               ++zone_already_attached;
-                                       }
-                               }
-                               if ((!zone_already_attached) && (num_zones_attached < 5)) {
-                                       /* This is a new one, so attach it. */
-                                       attached_zones[num_zones_attached++] = z;
-                               }
-
-                               icalproperty_set_parameter(p, icalparameter_new_tzid(icaltimezone_get_tzid(z))
-                               );
-                       }
-               }
-       }
-
-       /* Encapsulate any timezones we need */
-       if (num_zones_attached > 0) for (i=0; i<num_zones_attached; ++i) {
-               icalcomponent *zc;
-               zc = icalcomponent_new_clone(icaltimezone_get_component(attached_zones[i]));
-               icalcomponent_add_component(encaps, zc);
-       }
-
-       /* Here we go: encapsulate the VEVENT into the VCALENDAR.  We now no longer
-        * are responsible for "the_request"'s memory -- it will be freed
-        * when we free "encaps".
-        */
-       icalcomponent_add_component(encaps, the_request);
-
-       /* Serialize it */
-       serialized_request = icalcomponent_as_ical_string_r(encaps);
-       icalcomponent_free(encaps);     /* Don't need this anymore. */
-       if (serialized_request == NULL) return;
-
-       reqsize = strlen(serialized_request) + SIZ;
-       request_message_text = malloc(reqsize);
-       if (request_message_text != NULL) {
-               snprintf(request_message_text, reqsize,
-                       "Content-type: text/calendar\r\n\r\n%s\r\n",
-                       serialized_request
-               );
-
-               msg = CtdlMakeMessage(
-                       &CC->user,
-                       NULL,                   /* No single recipient here */
-                       NULL,                   /* No single recipient here */
-                       CC->room.QRname,
-                       0,
-                       FMT_RFC822,
-                       NULL,
-                       NULL,
-                       summary_string,         /* Use summary for subject */
-                       NULL,
-                       request_message_text,
-                       NULL
-               );
-       
-               if (msg != NULL) {
-                       valid = validate_recipients(attendees_string, NULL, 0);
-                       CtdlSubmitMsg(msg, valid, "");
-                       CM_Free(msg);
-                       free_recipients(valid);
-               }
-       }
-       free(serialized_request);
-}
-
-
-/*
- * When a calendar object is being saved, determine whether it's a VEVENT
- * and the user saving it is the organizer.  If so, send out invitations
- * to any listed attendees.
- *
- * This function is recursive.  The caller can simply supply the same object
- * as both arguments.  When it recurses it will alter the second argument
- * while holding on to the top level object.  This allows us to go back and
- * grab things like time zones which might be attached.
- *
- */
-void ical_saving_vevent(icalcomponent *top_level_cal, icalcomponent *cal) {
-       icalcomponent *c;
-       icalproperty *organizer = NULL;
-       char organizer_string[SIZ];
-
-       syslog(LOG_DEBUG, "calendar: ical_saving_vevent() has been called");
-
-       /* Don't send out invitations unless the client wants us to. */
-       if (CIT_ICAL->server_generated_invitations == 0) {
-               return;
-       }
-
-       /* Don't send out invitations if we've been asked not to. */
-       if (CIT_ICAL->avoid_sending_invitations > 0) {
-               return;
-       }
-
-       strcpy(organizer_string, "");
-       /*
-        * The VEVENT subcomponent is the one we're interested in.
-        * Send out invitations if, and only if, this user is the Organizer.
-        */
-       if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
-               organizer = icalcomponent_get_first_property(cal, ICAL_ORGANIZER_PROPERTY);
-               if (organizer != NULL) {
-                       if (icalproperty_get_organizer(organizer)) {
-                               strcpy(organizer_string,
-                                       icalproperty_get_organizer(organizer));
-                       }
-               }
-               if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
-                       strcpy(organizer_string, &organizer_string[7]);
-                       striplt(organizer_string);
-                       /*
-                        * If the user saving the event is listed as the
-                        * organizer, then send out invitations.
-                        */
-                       if (CtdlIsMe(organizer_string, sizeof organizer_string)) {
-                               ical_send_out_invitations(top_level_cal, cal);
-                       }
-               }
-       }
-
-       /* If the component has subcomponents, recurse through them. */
-       for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
-           (c != NULL);
-           c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
-               /* Recursively process subcomponent */
-               ical_saving_vevent(top_level_cal, c);
-       }
-
-}
-
-
-/*
- * Back end for ical_obj_beforesave()
- * This hunts for the UID of the calendar event (becomes Citadel msg EUID),
- * the summary of the event (becomes message subject),
- * and the start time (becomes message date/time).
- */
-void ical_obj_beforesave_backend(char *name, char *filename, char *partnum,
-               char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
-               char *encoding, char *cbid, void *cbuserdata)
-{
-       const char* pch;
-       icalcomponent *cal, *nested_event, *nested_todo, *whole_cal;
-       icalproperty *p;
-       char new_uid[256] = "";
-       struct CtdlMessage *msg = (struct CtdlMessage *) cbuserdata;
-
-       if (!msg) return;
-
-       /* We're only interested in calendar data. */
-       if (  (strcasecmp(cbtype, "text/calendar"))
-          && (strcasecmp(cbtype, "application/ics")) ) {
-               return;
-       }
-
-       /* Hunt for the UID and drop it in
-        * the "user data" pointer for the MIME parser.  When
-        * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
-        * to that string.
-        */
-       whole_cal = icalcomponent_new_from_string(content);
-       cal = whole_cal;
-       if (cal != NULL) {
-               if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
-                       nested_event = icalcomponent_get_first_component(
-                               cal, ICAL_VEVENT_COMPONENT);
-                       if (nested_event != NULL) {
-                               cal = nested_event;
-                       }
-                       else {
-                               nested_todo = icalcomponent_get_first_component(
-                                       cal, ICAL_VTODO_COMPONENT);
-                               if (nested_todo != NULL) {
-                                       cal = nested_todo;
-                               }
-                       }
-               }
-               
-               if (cal != NULL) {
-
-                       /* Set the message EUID to the iCalendar UID */
-
-                       p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
-                       if (p == NULL) {
-                               /* If there's no uid we must generate one */
-                               generate_uuid(new_uid);
-                               icalcomponent_add_property(cal, icalproperty_new_uid(new_uid));
-                               p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
-                       }
-                       if (p != NULL) {
-                               pch = icalproperty_get_comment(p);
-                               if (!IsEmptyStr(pch)) {
-                                       CM_SetField(msg, eExclusiveID, pch, strlen(pch));
-                                       syslog(LOG_DEBUG, "calendar: saving calendar UID <%s>", pch);
-                               }
-                       }
-
-                       /* Set the message subject to the iCalendar summary */
-
-                       p = ical_ctdl_get_subprop(cal, ICAL_SUMMARY_PROPERTY);
-                       if (p != NULL) {
-                               pch = icalproperty_get_comment(p);
-                               if (!IsEmptyStr(pch)) {
-                                       char *subj;
-
-                                       subj = rfc2047encode(pch, strlen(pch));
-                                       CM_SetAsField(msg, eMsgSubject, &subj, strlen(subj));
-                               }
-                       }
-
-                       /* Set the message date/time to the iCalendar start time */
-
-                       p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY);
-                       if (p != NULL) {
-                               time_t idtstart;
-                               idtstart = icaltime_as_timet(icalproperty_get_dtstart(p));
-                               if (idtstart > 0) {
-                                       CM_SetFieldLONG(msg, eTimestamp, idtstart);
-                               }
-                       }
-
-               }
-               icalcomponent_free(cal);
-               if (whole_cal != cal) {
-                       icalcomponent_free(whole_cal);
-               }
-       }
-}
-
-
-/*
- * See if we need to prevent the object from being saved (we don't allow
- * MIME types other than text/calendar in "calendar" or "tasks" rooms).
- *
- * If the message is being saved, we also set various message header fields
- * using data found in the iCalendar object.
- */
-int ical_obj_beforesave(struct CtdlMessage *msg, struct recptypes *recp)
-{
-       /* First determine if this is a calendar or tasks room */
-       if (  (CC->room.QRdefaultview != VIEW_CALENDAR)
-          && (CC->room.QRdefaultview != VIEW_TASKS)
-       ) {
-               return(0);              /* Not an iCalendar-centric room */
-       }
-
-       /* It must be an RFC822 message! */
-       if (msg->cm_format_type != 4) {
-               syslog(LOG_DEBUG, "calendar: rejecting non-RFC822 message");
-               return(1);              /* You tried to save a non-RFC822 message! */
-       }
-
-       if (CM_IsEmpty(msg, eMesageText)) {
-               return(1);              /* You tried to save a null message! */
-       }
-
-       /* Do all of our lovely back-end parsing */
-       mime_parser(CM_RANGE(msg, eMesageText),
-                   *ical_obj_beforesave_backend,
-                   NULL, NULL,
-                   (void *)msg,
-                   0
-               );
-
-       return(0);
-}
-
-
-/*
- * Things we need to do after saving a calendar event.
- */
-void ical_obj_aftersave_backend(char *name, char *filename, char *partnum,
-               char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
-               char *encoding, char *cbid, void *cbuserdata)
-{
-       icalcomponent *cal;
-
-       /* We're only interested in calendar items here. */
-       if (  (strcasecmp(cbtype, "text/calendar"))
-          && (strcasecmp(cbtype, "application/ics")) ) {
-               return;
-       }
-
-       /* Hunt for the UID and drop it in
-        * the "user data" pointer for the MIME parser.  When
-        * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
-        * to that string.
-        */
-       if (  (!strcasecmp(cbtype, "text/calendar"))
-          || (!strcasecmp(cbtype, "application/ics")) ) {
-               cal = icalcomponent_new_from_string(content);
-               if (cal != NULL) {
-                       ical_saving_vevent(cal, cal);
-                       icalcomponent_free(cal);
-               }
-       }
-}
-
-
-/* 
- * Things we need to do after saving a calendar event.
- * (This will start back end tasks such as automatic generation of invitations,
- * if such actions are appropriate.)
- */
-int ical_obj_aftersave(struct CtdlMessage *msg, struct recptypes *recp)
-{
-       char roomname[ROOMNAMELEN];
-
-       /*
-        * If this isn't the Calendar> room, no further action is necessary.
-        */
-
-       /* First determine if this is our room */
-       CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
-       if (strcasecmp(roomname, CC->room.QRname)) {
-               return(0);      /* Not the Calendar room -- don't do anything. */
-       }
-
-       /* It must be an RFC822 message! */
-       if (msg->cm_format_type != 4) return(1);
-
-       /* Reject null messages */
-       if (CM_IsEmpty(msg, eMesageText)) return(1);
-       
-       /* Now recurse through it looking for our icalendar data */
-       mime_parser(CM_RANGE(msg, eMesageText),
-                   *ical_obj_aftersave_backend,
-                   NULL, NULL,
-                   NULL,
-                   0
-               );
-
-       return(0);
-}
-
-
-void ical_session_startup(void) {
-       CIT_ICAL = malloc(sizeof(struct cit_ical));
-       memset(CIT_ICAL, 0, sizeof(struct cit_ical));
-}
-
-
-void ical_session_shutdown(void) {
-       free(CIT_ICAL);
-}
-
-
-/*
- * Back end for ical_fixed_output()
- */
-void ical_fixed_output_backend(icalcomponent *cal, int recursion_level) {
-       icalcomponent *c;
-       icalproperty *p;
-       char buf[256];
-       const char *ch;
-
-       p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY);
-       if (p != NULL) {
-               cprintf("%s\n", (const char *)icalproperty_get_comment(p));
-       }
-
-       p = icalcomponent_get_first_property(cal, ICAL_LOCATION_PROPERTY);
-       if (p != NULL) {
-               cprintf("%s\n", (const char *)icalproperty_get_comment(p));
-       }
-
-       p = icalcomponent_get_first_property(cal, ICAL_DESCRIPTION_PROPERTY);
-       if (p != NULL) {
-               cprintf("%s\n", (const char *)icalproperty_get_comment(p));
-       }
-
-       /* If the component has attendees, iterate through them. */
-       for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); (p != NULL); p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) {
-               ch =  icalproperty_get_attendee(p);
-               if ((ch != NULL) && 
-                   !strncasecmp(ch, "MAILTO:", 7)) {
-
-                       /* screen name or email address */
-                       safestrncpy(buf, ch + 7, sizeof(buf));
-                       striplt(buf);
-                       cprintf("%s ", buf);
-               }
-               cprintf("\n");
-       }
-
-       /* If the component has subcomponents, recurse through them. */
-       for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
-           (c != 0);
-           c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
-               /* Recursively process subcomponent */
-               ical_fixed_output_backend(c, recursion_level+1);
-       }
-}
-
-
-/*
- * Function to output iCalendar data as plain text.  Nobody uses MSG0
- * anymore, so really this is just so we expose the vCard data to the full
- * text indexer.
- */
-void ical_fixed_output(char *ptr, int len) {
-       icalcomponent *cal;
-       char *stringy_cal;
-
-       stringy_cal = malloc(len + 1);
-       safestrncpy(stringy_cal, ptr, len + 1);
-       cal = icalcomponent_new_from_string(stringy_cal);
-       free(stringy_cal);
-
-       if (cal == NULL) {
-               return;
-       }
-
-       ical_fixed_output_backend(cal, 0);
-
-       /* Free the memory we obtained from libical's constructor */
-       icalcomponent_free(cal);
-}
-
-
-/*
- * Register this module with the Citadel server.
- */
-CTDL_MODULE_INIT(calendar)
-{
-       if (!threading)
-       {
-
-               /* Tell libical to return errors instead of aborting if it gets bad data */
-
-#ifdef LIBICAL_ICAL_EXPORT     // cheap and sleazy way to detect libical >=2.0
-               icalerror_set_errors_are_fatal(0);
-#else
-               icalerror_errors_are_fatal = 0;
-#endif
-
-               /* Use our own application prefix in tzid's generated from system tzdata */
-               icaltimezone_set_tzid_prefix("/citadel.org/");
-
-               /* Initialize our hook functions */
-               CtdlRegisterMessageHook(ical_obj_beforesave, EVT_BEFORESAVE);
-               CtdlRegisterMessageHook(ical_obj_aftersave, EVT_AFTERSAVE);
-               CtdlRegisterSessionHook(ical_CtdlCreateRoom, EVT_LOGIN, PRIO_LOGIN + 1);
-               CtdlRegisterProtoHook(cmd_ical, "ICAL", "Citadel iCalendar commands");
-               CtdlRegisterSessionHook(ical_session_startup, EVT_START, PRIO_START + 1);
-               CtdlRegisterSessionHook(ical_session_shutdown, EVT_STOP, PRIO_STOP + 80);
-               CtdlRegisterFixedOutputHook("text/calendar", ical_fixed_output);
-               CtdlRegisterFixedOutputHook("application/ics", ical_fixed_output);
-       }
-
-       /* return our module name for the log */
-       return "calendar";
-}
diff --git a/citadel/modules/calendar/serv_calendar.h b/citadel/modules/calendar/serv_calendar.h
deleted file mode 100644 (file)
index d7a149c..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * iCalendar implementation for Citadel
- *
- *
- * Copyright (c) 1987-2012 by the citadel.org team
- *
- *  This program is open source software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License version 3.
- *  
- *  
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  
- *  
- *  
- */
-
-/* 
- * "server_generated_invitations" tells the Citadel server that the
- * client wants invitations to be generated and sent out by the
- * server.  Set to 1 to enable this functionality.
- *
- * "avoid_sending_invitations" is a server-internal variable.  It is
- * set internally during certain transactions and cleared
- * automatically.
- */
-struct cit_ical {
-       int server_generated_invitations;
-        int avoid_sending_invitations;
-};
-
-#define CIT_ICAL CC->CIT_ICAL
-#define MAX_RECUR 1000
diff --git a/citadel/modules/checkpoint/.gitignore b/citadel/modules/checkpoint/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/checkpoint/serv_checkpoint.c b/citadel/modules/checkpoint/serv_checkpoint.c
deleted file mode 100644 (file)
index 578622b..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * checkpointing module for the database
- *
- * Copyright (c) 1987-2012 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <ctype.h>
-#include <string.h>
-#include <errno.h>
-
-#include <libcitadel.h>
-
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "msgbase.h"
-#include "sysdep_decls.h"
-#include "config.h"
-#include "threads.h"
-
-#include "ctdl_module.h"
-#include "context.h"
-
-CTDL_MODULE_INIT(checkpoint)
-{
-       if (threading)
-       {
-               CtdlRegisterSessionHook(cdb_checkpoint, EVT_TIMER, PRIO_CLEANUP + 10);
-       }
-       /* return our module name for the log */
-       return "checkpoint";
-}
diff --git a/citadel/modules/clamav/.gitignore b/citadel/modules/clamav/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/clamav/serv_virus.c b/citadel/modules/clamav/serv_virus.c
deleted file mode 100644 (file)
index c5599b9..0000000
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * This module allows Citadel to use clamd to filter incoming messages
- * arriving via SMTP.  For more information on clamd, visit
- * http://clamav.net (the ClamAV project is not in any way
- * affiliated with the Citadel project).
- *
- * Copyright (c) 1987-2012 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#define CLAMD_PORT       "3310"
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "domain.h"
-#include "clientsocket.h"
-#include "ctdl_module.h"
-
-
-/*
- * Connect to the clamd server and scan a message.
- */
-int clamd(struct CtdlMessage *msg, struct recptypes *recp) {
-       int sock = (-1);
-       int streamsock = (-1);
-       char clamhosts[SIZ];
-       int num_clamhosts;
-       char buf[SIZ];
-        char hostbuf[SIZ];
-        char portbuf[SIZ];
-       int is_virus = 0;
-       int clamhost;
-       StrBuf *msgtext;
-       CitContext *CCC;
-
-       /* See if we have any clamd hosts configured */
-       num_clamhosts = get_hosts(clamhosts, "clamav");
-       if (num_clamhosts < 1) {
-               return(0);
-       }
-
-       /* Try them one by one until we get a working one */
-        for (clamhost=0; clamhost<num_clamhosts; ++clamhost) {
-                extract_token(buf, clamhosts, clamhost, '|', sizeof buf);
-                syslog(LOG_INFO, "Connecting to clamd at <%s>\n", buf);
-
-                /* Assuming a host:port entry */ 
-                extract_token(hostbuf, buf, 0, ':', sizeof hostbuf);
-                if (extract_token(portbuf, buf, 1, ':', sizeof portbuf)==-1)
-                  /* Didn't specify a port so we'll try the psuedo-standard 3310 */
-                  sock = sock_connect(hostbuf, CLAMD_PORT);
-                else
-                  /* Port specified lets try connecting to it! */
-                  sock = sock_connect(hostbuf, portbuf);
-
-                if (sock >= 0) syslog(LOG_DEBUG, "Connected!\n");
-        }
-
-       if (sock < 0) {
-               /* If the service isn't running, just pass the mail
-                * through.  Potentially throwing away mails isn't good.
-                */
-               return(0);
-       }
-       CCC=CC;
-       CCC->SBuf.Buf = NewStrBuf();
-       CCC->sMigrateBuf = NewStrBuf();
-       CCC->SBuf.ReadWritePointer = NULL;
-
-       /* Command */
-       syslog(LOG_DEBUG, "Transmitting STREAM command\n");
-       sprintf(buf, "STREAM\r\n");
-       sock_write(&sock, buf, strlen(buf));
-
-       syslog(LOG_DEBUG, "Waiting for PORT number\n");
-        if (sock_getln(&sock, buf, sizeof buf) < 0) {
-                goto bail;
-        }
-
-        syslog(LOG_DEBUG, "<%s\n", buf);
-       if (strncasecmp(buf, "PORT", 4)!=0) {
-               goto bail;
-       }
-
-        /* Should have received a port number to connect to */
-       extract_token(portbuf, buf, 1, ' ', sizeof portbuf);
-
-       /* Attempt to establish connection to STREAM socket */
-        streamsock = sock_connect(hostbuf, portbuf);
-
-       if (streamsock < 0) {
-               /* If the service isn't running, just pass the mail
-                * through.  Potentially throwing away mails isn't good.
-                */
-               FreeStrBuf(&CCC->SBuf.Buf);
-               FreeStrBuf(&CCC->sMigrateBuf);
-               return(0);
-        }
-       else {
-               syslog(LOG_DEBUG, "STREAM socket connected!\n");
-       }
-
-
-       /* Message */
-       CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
-       CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, 0);
-       msgtext = CC->redirect_buffer;
-       CC->redirect_buffer = NULL;
-
-       sock_write(&streamsock, SKEY(msgtext));
-       FreeStrBuf(&msgtext);
-
-       /* Close the streamsocket connection; this tells clamd
-        * that we're done.
-        */
-       if (streamsock != -1) {
-               close(streamsock);
-       }
-       
-       /* Response */
-       syslog(LOG_DEBUG, "Awaiting response\n");
-        if (sock_getln(&sock, buf, sizeof buf) < 0) {
-                goto bail;
-        }
-        syslog(LOG_DEBUG, "<%s\n", buf);
-       if (strncasecmp(buf, "stream: OK", 10)!=0) {
-               is_virus = 1;
-       }
-
-       if (is_virus) {
-               CM_SetField(msg, eErrorMsg, HKEY("message rejected by virus filter"));
-       }
-
-bail:  close(sock);
-       FreeStrBuf(&CCC->SBuf.Buf);
-       FreeStrBuf(&CCC->sMigrateBuf);
-       return(is_virus);
-}
-
-
-CTDL_MODULE_INIT(virus)
-{
-       if (!threading)
-       {
-               CtdlRegisterMessageHook(clamd, EVT_SMTPSCAN);
-       }
-       
-       /* return our module name for the log */
-        return "virus";
-}
diff --git a/citadel/modules/crypto/.gitignore b/citadel/modules/crypto/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/crypto/serv_crypto.c b/citadel/modules/crypto/serv_crypto.c
deleted file mode 100644 (file)
index f78c0cd..0000000
+++ /dev/null
@@ -1,619 +0,0 @@
-// Copyright (c) 1987-2022 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include <string.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include "sysdep.h"
-
-#ifdef HAVE_OPENSSL
-#include <openssl/ssl.h>
-#include <openssl/err.h>
-#include <openssl/rand.h>
-#endif
-
-#include <time.h>
-
-#ifdef HAVE_PTHREAD_H
-#include <pthread.h>
-#endif
-
-#ifdef HAVE_SYS_SELECT_H
-#include <sys/select.h>
-#endif
-
-#include <stdio.h>
-#include <libcitadel.h>
-#include "server.h"
-#include "serv_crypto.h"
-#include "sysdep_decls.h"
-#include "citadel.h"
-#include "config.h"
-#include "ctdl_module.h"
-
-#ifdef HAVE_OPENSSL
-
-SSL_CTX *ssl_ctx = NULL;               // This SSL context is used for all sessions.
-char *ssl_cipher_list = CIT_CIPHERS;
-
-// If a private key does not exist, generate one now.
-void generate_key(char *keyfilename) {
-       int ret = 0;
-       RSA *rsa = NULL;
-       BIGNUM *bne = NULL;
-       int bits = 2048;
-       unsigned long e = RSA_F4;
-       FILE *fp;
-
-       if (access(keyfilename, R_OK) == 0) {   // Already have one.
-               syslog(LOG_INFO, "crypto: %s exists and is readable", keyfilename);
-               return;
-       }
-
-       syslog(LOG_INFO, "crypto: generating RSA key pair");
-       // generate rsa key
-       bne = BN_new();
-       ret = BN_set_word(bne,e);
-       if (ret != 1) {
-               goto free_all;
-       }
-       rsa = RSA_new();
-       ret = RSA_generate_key_ex(rsa, bits, bne, NULL);
-       if (ret != 1) {
-               goto free_all;
-       }
-
-       // write the key file
-       fp = fopen(keyfilename, "w");
-       if (fp != NULL) {
-               chmod(keyfilename, 0600);
-               if (PEM_write_RSAPrivateKey(fp, // the file
-                                       rsa,    // the key
-                                       NULL,   // no enc
-                                       NULL,   // no passphrase
-                                       0,      // no passphrase
-                                       NULL,   // no callback
-                                       NULL    // no callback
-               ) != 1) {
-                       syslog(LOG_ERR, "crypto: cannot write key: %s", ERR_reason_error_string(ERR_get_error()));
-                       unlink(keyfilename);
-               }
-               fclose(fp);
-       }
-
-       // free the memory we used
-free_all:
-       RSA_free(rsa);
-       BN_free(bne);
-}
-
-
-// If a certificate does not exist, generate a self-signed one now.
-void generate_certificate(char *keyfilename, char *certfilename) {
-       RSA *private_key = NULL;
-       EVP_PKEY *public_key = NULL;
-       X509_REQ *certificate_signing_request = NULL;
-       X509_NAME *name = NULL;
-       X509 *certificate = NULL;
-       FILE *fp;
-
-       if (access(certfilename, R_OK) == 0) {                  // already have one.
-               syslog(LOG_INFO, "crypto: %s exists and is readable", certfilename);
-               return;
-       }
-
-       syslog(LOG_INFO, "crypto: generating a self-signed certificate");
-
-       // Read in our private key.
-       fp = fopen(keyfilename, "r");
-       if (fp) {
-               private_key = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
-               fclose(fp);
-       }
-
-       if (!private_key) {
-               syslog(LOG_ERR, "crypto: cannot read the private key");
-               return;
-       }
-
-       // Create a public key from the private key
-       public_key = EVP_PKEY_new();
-       if (!public_key) {
-               syslog(LOG_ERR, "crypto: cannot allocate public key");
-               RSA_free(private_key);
-               return;
-       }
-       EVP_PKEY_assign_RSA(public_key, private_key);
-
-       // Create a certificate signing request
-       certificate_signing_request = X509_REQ_new();
-       if (!certificate_signing_request) {
-               syslog(LOG_ERR, "crypto: cannot allocate certificate signing request");
-               RSA_free(private_key);
-               EVP_PKEY_free(public_key);
-               return;
-       }
-
-       // Assign the public key to the certificate signing request
-       X509_REQ_set_pubkey(certificate_signing_request, public_key);
-       X509_REQ_set_version(certificate_signing_request, 0L);
-
-       // Tell it who we are
-       name = X509_REQ_get_subject_name(certificate_signing_request);
-       X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned const char *)"ZZ", -1, -1, 0);
-       X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC, (unsigned const char *)"The World", -1, -1, 0);
-       X509_NAME_add_entry_by_txt(name, "L", MBSTRING_ASC, (unsigned const char *)"My Location", -1, -1, 0);
-       X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned const char *)"Generic certificate", -1, -1, 0);
-       X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, (unsigned const char *)"Citadel server", -1, -1, 0);
-       X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned const char *)"*", -1, -1, 0);
-       X509_REQ_set_subject_name(certificate_signing_request, name);
-
-       // Sign the CSR
-       if (!X509_REQ_sign(certificate_signing_request, public_key, EVP_md5())) {
-               syslog(LOG_ERR, "crypto: X509_REQ_sign(): error");
-               X509_REQ_free(certificate_signing_request);
-               RSA_free(private_key);
-               EVP_PKEY_free(public_key);
-               return;
-       }
-
-       // Generate the self-signed certificate
-       certificate = X509_new();
-       if (!certificate) {
-               syslog(LOG_ERR, "crypto: cannot allocate X.509 certificate");
-               X509_REQ_free(certificate_signing_request);
-               RSA_free(private_key);
-               EVP_PKEY_free(public_key);
-               return;
-       }
-
-       ASN1_INTEGER_set(X509_get_serialNumber(certificate), 0);
-       X509_set_issuer_name(certificate, X509_REQ_get_subject_name(certificate_signing_request));
-       X509_set_subject_name(certificate, X509_REQ_get_subject_name(certificate_signing_request));
-       X509_gmtime_adj(X509_get_notBefore(certificate), 0);
-       X509_gmtime_adj(X509_get_notAfter(certificate), (long)60*60*24*SIGN_DAYS);
-       X509_set_pubkey(certificate, public_key);
-       X509_REQ_free(certificate_signing_request);             // We're done with the CSR; free it
-
-       // Finally, sign the certificate with our private key.
-       if (!X509_sign(certificate, public_key, EVP_md5())) {
-               syslog(LOG_ERR, "crypto: X509_sign() error");
-               X509_free(certificate);
-               RSA_free(private_key);
-               EVP_PKEY_free(public_key);
-               return;
-       }
-
-       // Write the certificate to disk
-       fp = fopen(certfilename, "w");
-       if (fp != NULL) {
-               chmod(certfilename, 0600);
-               PEM_write_X509(fp, certificate);
-               fclose(fp);
-       }
-       else {
-               syslog(LOG_ERR, "crypto: %s: %m", certfilename);
-       }
-
-       X509_free(certificate);
-       EVP_PKEY_free(public_key);
-       // do not RSA_free(private_key); because it was freed by EVP_PKEY_free() above
-}
-
-
-// Set the private key and certificate chain for the global SSL Context.
-// This is called during initialization, and can be called again later if the certificate changes.
-void bind_to_key_and_certificate(void) {
-
-       SSL_CTX *old_ctx = NULL;
-       SSL_CTX *new_ctx = NULL;
-
-       const SSL_METHOD *method = SSLv23_server_method();
-       if (!method) {
-               syslog(LOG_ERR, "crypto: SSLv23_server_method() failed: %s", ERR_reason_error_string(ERR_get_error()));
-               return;
-       }
-
-       new_ctx = SSL_CTX_new(method);
-       if (!new_ctx) {
-               syslog(LOG_ERR, "crypto: SSL_CTX_new failed: %s", ERR_reason_error_string(ERR_get_error()));
-               return;
-       }
-
-       if (!(SSL_CTX_set_cipher_list(new_ctx, ssl_cipher_list))) {
-               syslog(LOG_ERR, "crypto: SSL_CTX_set_cipher_list failed: %s", ERR_reason_error_string(ERR_get_error()));
-               return;
-       }
-
-       syslog(LOG_DEBUG, "crypto: using certificate chain %s", file_crpt_file_cer);
-        if (!SSL_CTX_use_certificate_chain_file(new_ctx, file_crpt_file_cer)) {
-               syslog(LOG_ERR, "crypto: SSL_CTX_use_certificate_chain_file failed: %s", ERR_reason_error_string(ERR_get_error()));
-               return;
-       }
-
-       syslog(LOG_DEBUG, "crypto: using private key %s", file_crpt_file_key);
-        if (!SSL_CTX_use_PrivateKey_file(new_ctx, file_crpt_file_key, SSL_FILETYPE_PEM)) {
-               syslog(LOG_ERR, "crypto: SSL_CTX_use_PrivateKey_file failed: %s", ERR_reason_error_string(ERR_get_error()));
-               return;
-       }
-
-       old_ctx = ssl_ctx;
-       ssl_ctx = new_ctx;              // All future binds will use the new certificate
-
-       if (old_ctx != NULL) {
-               sleep(1);               // Hopefully wait until all in-progress binds to the old certificate have completed
-               SSL_CTX_free(old_ctx);
-       }
-}
-
-
-// Check the modification time of the key and certificate files.  Reload if either one changed.
-void update_key_and_cert_if_needed(void) {
-       static time_t previous_mtime = 0;
-       struct stat keystat;
-       struct stat certstat;
-
-       if (stat(file_crpt_file_key, &keystat) != 0) {
-               syslog(LOG_ERR, "%s: %s", file_crpt_file_key, strerror(errno));
-               return;
-       }
-       if (stat(file_crpt_file_cer, &certstat) != 0) {
-               syslog(LOG_ERR, "%s: %s", file_crpt_file_cer, strerror(errno));
-               return;
-       }
-
-       if ((keystat.st_mtime + certstat.st_mtime) != previous_mtime) {
-               begin_critical_section(S_OPENSSL);
-               bind_to_key_and_certificate();
-               end_critical_section(S_OPENSSL);
-               previous_mtime = keystat.st_mtime + certstat.st_mtime;
-       }
-}
-
-
-// Initialize the TLS subsystem.
-void init_ssl(void) {
-
-       // Initialize the OpenSSL library
-       SSL_library_init();
-       SSL_load_error_strings();
-
-       // Load (or generate) a key and certificate
-       mkdir(ctdl_key_dir, 0700);                                      // If the keys directory does not exist, create it
-       generate_key(file_crpt_file_key);                               // If a private key does not exist, create it
-       generate_certificate(file_crpt_file_key, file_crpt_file_cer);   // If a certificate does not exist, create it
-       bind_to_key_and_certificate();                                  // Load key and cert from disk, and bind to them.
-
-       // Register some Citadel protocol commands for dealing with encrypted sessions
-       CtdlRegisterProtoHook(cmd_stls, "STLS", "Start TLS session");
-       CtdlRegisterProtoHook(cmd_gtls, "GTLS", "Get TLS session status");
-       CtdlRegisterSessionHook(endtls, EVT_STOP, PRIO_STOP + 10);
-}
-
-
-// client_write_ssl() Send binary data to the client encrypted.
-void client_write_ssl(const char *buf, int nbytes) {
-       int retval;
-       int nremain;
-       char junk[1];
-
-       nremain = nbytes;
-
-       while (nremain > 0) {
-               if (SSL_want_write(CC->ssl)) {
-                       if ((SSL_read(CC->ssl, junk, 0)) < 1) {
-                               syslog(LOG_DEBUG, "crypto: SSL_read in client_write: %s", ERR_reason_error_string(ERR_get_error()));
-                       }
-               }
-               retval =
-                   SSL_write(CC->ssl, &buf[nbytes - nremain], nremain);
-               if (retval < 1) {
-                       long errval;
-
-                       errval = SSL_get_error(CC->ssl, retval);
-                       if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
-                               sleep(1);
-                               continue;
-                       }
-                       syslog(LOG_DEBUG, "crypto: SSL_write got error %ld, ret %d", errval, retval);
-                       if (retval == -1) {
-                               syslog(LOG_DEBUG, "crypto: errno is %d", errno);
-                       }
-                       endtls();
-                       client_write(&buf[nbytes - nremain], nremain);
-                       return;
-               }
-               nremain -= retval;
-       }
-}
-
-
-// read data from the encrypted layer.
-int client_read_sslbuffer(StrBuf *buf, int timeout) {
-       char sbuf[16384];       // OpenSSL communicates in 16k blocks, so let's speak its native tongue.
-       int rlen;
-       char junk[1];
-       SSL *pssl = CC->ssl;
-
-       if (pssl == NULL) return(-1);
-
-       while (1) {
-               if (SSL_want_read(pssl)) {
-                       if ((SSL_write(pssl, junk, 0)) < 1) {
-                               syslog(LOG_DEBUG, "crypto: SSL_write in client_read");
-                       }
-               }
-               rlen = SSL_read(pssl, sbuf, sizeof(sbuf));
-               if (rlen < 1) {
-                       long errval;
-
-                       errval = SSL_get_error(pssl, rlen);
-                       if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
-                               sleep(1);
-                               continue;
-                       }
-                       syslog(LOG_DEBUG, "crypto: SSL_read got error %ld", errval);
-                       endtls();
-                       return (-1);
-               }
-               StrBufAppendBufPlain(buf, sbuf, rlen, 0);
-               return rlen;
-       }
-       return (0);
-}
-
-
-int client_readline_sslbuffer(StrBuf *Line, StrBuf *IOBuf, const char **Pos, int timeout) {
-       const char *pos = NULL;
-       const char *pLF;
-       int len, rlen;
-       int nSuccessLess = 0;
-       const char *pch = NULL;
-       
-       if ((Line == NULL) || (Pos == NULL) || (IOBuf == NULL)) {
-               if (Pos != NULL) {
-                       *Pos = NULL;
-               }
-               return -1;
-       }
-
-       pos = *Pos;
-       if ((StrLength(IOBuf) > 0) && (pos != NULL) && (pos < ChrPtr(IOBuf) + StrLength(IOBuf))) {
-               pch = pos;
-               pch = strchr(pch, '\n');
-               
-               if (pch == NULL) {
-                       StrBufAppendBufPlain(Line, pos, StrLength(IOBuf) - (pos - ChrPtr(IOBuf)), 0);
-                       FlushStrBuf(IOBuf);
-                       *Pos = NULL;
-               }
-               else {
-                       int n = 0;
-                       if ((pch > ChrPtr(IOBuf)) && (*(pch - 1) == '\r')) {
-                               n = 1;
-                       }
-                       StrBufAppendBufPlain(Line, pos, (pch - pos - n), 0);
-
-                       if (StrLength(IOBuf) <= (pch - ChrPtr(IOBuf) + 1)) {
-                               FlushStrBuf(IOBuf);
-                               *Pos = NULL;
-                       }
-                       else {
-                               *Pos = pch + 1;
-                       }
-                       return StrLength(Line);
-               }
-       }
-
-       pLF = NULL;
-       while ((nSuccessLess < timeout) && (pLF == NULL) && (CC->ssl != NULL)) {
-
-               rlen = client_read_sslbuffer(IOBuf, timeout);
-               if (rlen < 1) {
-                       return -1;
-               }
-               else if (rlen > 0) {
-                       pLF = strchr(ChrPtr(IOBuf), '\n');
-               }
-       }
-       *Pos = NULL;
-       if (pLF != NULL) {
-               pos = ChrPtr(IOBuf);
-               len = pLF - pos;
-               if (len > 0 && (*(pLF - 1) == '\r') )
-                       len --;
-               StrBufAppendBufPlain(Line, pos, len, 0);
-               if (pLF + 1 >= ChrPtr(IOBuf) + StrLength(IOBuf)) {
-                       FlushStrBuf(IOBuf);
-               }
-               else  {
-                       *Pos = pLF + 1;
-               }
-               return StrLength(Line);
-       }
-       return -1;
-}
-
-
-int client_read_sslblob(StrBuf *Target, long bytes, int timeout) {
-       long baselen;
-       long RemainRead;
-       int retval = 0;
-
-       baselen = StrLength(Target);
-
-       if (StrLength(CC->RecvBuf.Buf) > 0) {
-               long RemainLen;
-               long TotalLen;
-               const char *pchs;
-
-               if (CC->RecvBuf.ReadWritePointer == NULL) {
-                       CC->RecvBuf.ReadWritePointer = ChrPtr(CC->RecvBuf.Buf);
-               }
-               pchs = ChrPtr(CC->RecvBuf.Buf);
-               TotalLen = StrLength(CC->RecvBuf.Buf);
-               RemainLen = TotalLen - (pchs - CC->RecvBuf.ReadWritePointer);
-               if (RemainLen > bytes) {
-                       RemainLen = bytes;
-               }
-               if (RemainLen > 0) {
-                       StrBufAppendBufPlain(Target, CC->RecvBuf.ReadWritePointer, RemainLen, 0);
-                       CC->RecvBuf.ReadWritePointer += RemainLen;
-               }
-               if ((ChrPtr(CC->RecvBuf.Buf) + StrLength(CC->RecvBuf.Buf)) <= CC->RecvBuf.ReadWritePointer) {
-                       CC->RecvBuf.ReadWritePointer = NULL;
-                       FlushStrBuf(CC->RecvBuf.Buf);
-               }
-       }
-
-       if (StrLength(Target) >= bytes + baselen) {
-               return 1;
-       }
-
-       CC->RecvBuf.ReadWritePointer = NULL;
-
-       while ((StrLength(Target) < bytes + baselen) && (retval >= 0)) {
-               retval = client_read_sslbuffer(CC->RecvBuf.Buf, timeout);
-               if (retval >= 0) {
-                       RemainRead = bytes - (StrLength (Target) - baselen);
-                       if (RemainRead < StrLength(CC->RecvBuf.Buf)) {
-                               StrBufAppendBufPlain(
-                                       Target, 
-                                       ChrPtr(CC->RecvBuf.Buf), 
-                                       RemainRead, 0);
-                               CC->RecvBuf.ReadWritePointer = ChrPtr(CC->RecvBuf.Buf) + RemainRead;
-                               break;
-                       }
-                       StrBufAppendBuf(Target, CC->RecvBuf.Buf, 0);
-                       FlushStrBuf(CC->RecvBuf.Buf);
-               }
-               else {
-                       FlushStrBuf(CC->RecvBuf.Buf);
-                       return -1;
-       
-               }
-       }
-       return 1;
-}
-
-
-// CtdlStartTLS() starts TLS encryption for the current session.  It
-// must be supplied with pre-generated strings for responses of "ok," "no
-// support for TLS," and "error" so that we can use this in any protocol.
-void CtdlStartTLS(char *ok_response, char *nosup_response, char *error_response) {
-       int retval, bits, alg_bits;
-
-       if (CC->redirect_ssl) {
-               syslog(LOG_ERR, "crypto: attempt to begin SSL on an already encrypted connection");
-               if (error_response != NULL) {
-                       cprintf("%s", error_response);
-               }
-               return;
-       }
-
-       if (!ssl_ctx) {
-               syslog(LOG_ERR, "crypto: SSL failed: context has not been initialized");
-               if (nosup_response != NULL) {
-                       cprintf("%s", nosup_response);
-               }
-               return;
-       }
-
-       update_key_and_cert_if_needed();                // did someone update the key or cert?  if so, re-bind them
-
-       if (!(CC->ssl = SSL_new(ssl_ctx))) {
-               syslog(LOG_ERR, "crypto: SSL_new failed: %s", ERR_reason_error_string(ERR_get_error()));
-               if (error_response != NULL) {
-                       cprintf("%s", error_response);
-               }
-               return;
-       }
-       if (!(SSL_set_fd(CC->ssl, CC->client_socket))) {
-               syslog(LOG_ERR, "crypto: SSL_set_fd failed: %s", ERR_reason_error_string(ERR_get_error()));
-               SSL_free(CC->ssl);
-               CC->ssl = NULL;
-               if (error_response != NULL) {
-                       cprintf("%s", error_response);
-               }
-               return;
-       }
-       if (ok_response != NULL) {
-               cprintf("%s", ok_response);
-       }
-       retval = SSL_accept(CC->ssl);
-       if (retval < 1) {
-               // Can't notify the client of an error here; they will
-               // discover the problem at the SSL layer and should
-               // revert to unencrypted communications.
-               syslog(LOG_ERR, "crypto: SSL_accept failed: %s", ERR_reason_error_string(ERR_get_error()));
-               SSL_free(CC->ssl);
-               CC->ssl = NULL;
-               return;
-       }
-       bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(CC->ssl), &alg_bits);
-       syslog(LOG_INFO, "crypto: TLS using %s on %s (%d of %d bits)",
-               SSL_CIPHER_get_name(SSL_get_current_cipher(CC->ssl)),
-               SSL_CIPHER_get_version(SSL_get_current_cipher(CC->ssl)),
-               bits, alg_bits
-       );
-       CC->redirect_ssl = 1;
-}
-
-
-// cmd_stls() starts TLS encryption for the current session
-void cmd_stls(char *params) {
-       char ok_response[SIZ];
-       char nosup_response[SIZ];
-       char error_response[SIZ];
-
-       unbuffer_output();
-
-       sprintf(ok_response, "%d Begin TLS negotiation now\n", CIT_OK);
-       sprintf(nosup_response, "%d TLS not supported here\n", ERROR + CMD_NOT_SUPPORTED);
-       sprintf(error_response, "%d TLS negotiation error\n", ERROR + INTERNAL_ERROR);
-
-       CtdlStartTLS(ok_response, nosup_response, error_response);
-}
-
-
-// cmd_gtls() returns status info about the TLS connection
-void cmd_gtls(char *params) {
-       int bits, alg_bits;
-
-       if (!CC->ssl || !CC->redirect_ssl) {
-               cprintf("%d Session is not encrypted.\n", ERROR);
-               return;
-       }
-       bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(CC->ssl), &alg_bits);
-       cprintf("%d %s|%s|%d|%d\n", CIT_OK,
-               SSL_CIPHER_get_version(SSL_get_current_cipher(CC->ssl)),
-               SSL_CIPHER_get_name(SSL_get_current_cipher(CC->ssl)),
-               alg_bits, bits);
-}
-
-
-// endtls() shuts down the TLS connection
-void endtls(void) {
-       if (!CC->ssl) {
-               CC->redirect_ssl = 0;
-               return;
-       }
-
-       syslog(LOG_INFO, "crypto: ending TLS on this session");
-       SSL_shutdown(CC->ssl);
-       SSL_free(CC->ssl);
-       CC->ssl = NULL;
-       CC->redirect_ssl = 0;
-}
-
-#endif // HAVE_OPENSSL
diff --git a/citadel/modules/crypto/serv_crypto.h b/citadel/modules/crypto/serv_crypto.h
deleted file mode 100644 (file)
index b5ee85d..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-
-/*
- * Number of days for which self-signed certs are valid.
- */
-#define SIGN_DAYS      1100    // Just over three years
-
-// Which ciphers will be offered; see https://www.openssl.org/docs/manmaster/man1/ciphers.html
-#define CIT_CIPHERS    "ALL:RC4+RSA:+SSLv2:+TLSv1:!MD5:@STRENGTH"
-
-#ifdef HAVE_OPENSSL
-#define OPENSSL_NO_KRB5                /* work around redhat b0rken ssl headers */
-void init_ssl(void);
-void client_write_ssl (const char *buf, int nbytes);
-int client_read_sslbuffer(StrBuf *buf, int timeout);
-int client_readline_sslbuffer(StrBuf *Target, StrBuf *Buffer, const char **Pos, int timeout);
-int client_read_sslblob(StrBuf *Target, long want_len, int timeout);
-void cmd_stls(char *params);
-void cmd_gtls(char *params);
-void endtls(void);
-void CtdlStartTLS(char *ok_response, char *nosup_response, char *error_response);
-extern SSL_CTX *ssl_ctx;  
-
-#endif
diff --git a/citadel/modules/ctdlproto/files.h b/citadel/modules/ctdlproto/files.h
deleted file mode 100644 (file)
index 080c83e..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#ifndef FILE_OPS_H
-#define FILE_OPS_H
-
-#include "context.h"
-
-void OpenCmdResult (char *, const char *);
-void abort_upl (CitContext *who);
-
-
-#endif /* FILE_OPS_H */
diff --git a/citadel/modules/ctdlproto/serv_ctdlproto.c b/citadel/modules/ctdlproto/serv_ctdlproto.c
deleted file mode 100644 (file)
index cf51e62..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/* 
- * Citadel protocol main dispatcher
- *
- * Copyright (c) 1987-2017 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include <stdio.h>
-#include <libcitadel.h>
-
-#include "citserver.h"
-#include "ctdl_module.h"
-#include "config.h"
-/*
- * This loop recognizes all server commands.
- */
-void do_command_loop(void) {
-       struct CitContext *CCC = CC;
-       char cmdbuf[SIZ];
-       
-       time(&CCC->lastcmd);
-       memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
-       if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
-               syslog(LOG_INFO, "Citadel client disconnected: ending session.");
-               CCC->kill_me = KILLME_CLIENT_DISCONNECTED;
-               return;
-       }
-
-       /* Log the server command, but don't show passwords... */
-       if ( (strncasecmp(cmdbuf, "PASS", 4)) && (strncasecmp(cmdbuf, "SETP", 4)) ) {
-               syslog(LOG_DEBUG, "[%s(%ld)] %s",
-                       CCC->curr_user, CCC->user.usernum, cmdbuf
-               );
-       }
-       else {
-               syslog(LOG_DEBUG, "[%s(%ld)] <password command hidden from log>",
-                           CCC->curr_user, CCC->user.usernum
-               );
-       }
-
-       buffer_output();
-
-       /*
-        * Let other clients see the last command we executed, and
-        * update the idle time, but not NOOP, QNOP, PEXP, GEXP, RWHO, or TIME.
-        */
-       if ( (strncasecmp(cmdbuf, "NOOP", 4))
-          && (strncasecmp(cmdbuf, "QNOP", 4))
-          && (strncasecmp(cmdbuf, "PEXP", 4))
-          && (strncasecmp(cmdbuf, "GEXP", 4))
-          && (strncasecmp(cmdbuf, "RWHO", 4))
-          && (strncasecmp(cmdbuf, "TIME", 4)) ) {
-               strcpy(CCC->lastcmdname, "    ");
-               safestrncpy(CCC->lastcmdname, cmdbuf, sizeof(CCC->lastcmdname));
-               time(&CCC->lastidle);
-       }
-       
-       if ((strncasecmp(cmdbuf, "ENT0", 4))
-          && (strncasecmp(cmdbuf, "MESG", 4))
-          && (strncasecmp(cmdbuf, "MSGS", 4)))
-       {
-          CCC->cs_flags &= ~CS_POSTING;
-       }
-                  
-       if (!DLoader_Exec_Cmd(cmdbuf)) {
-               cprintf("%d Unrecognized or unsupported command.\n", ERROR + CMD_NOT_SUPPORTED);
-       }       
-
-       unbuffer_output();
-
-       /* Run any after-each-command routines registered by modules */
-       PerformSessionHooks(EVT_CMD);
-}
-
diff --git a/citadel/modules/ctdlproto/serv_file.c b/citadel/modules/ctdlproto/serv_file.c
deleted file mode 100644 (file)
index 9af465e..0000000
+++ /dev/null
@@ -1,685 +0,0 @@
-/* 
- * Server functions which handle file transfers and room directories.
- *
- * Copyright (c) 1987-2018 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <netdb.h>
-#include <libcitadel.h>
-#include <dirent.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include "ctdl_module.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-
-
-/*
- * Server command to delete a file from a room's directory
- */
-void cmd_delf(char *filename)
-{
-       char pathname[64];
-       int a;
-
-       if (CtdlAccessCheck(ac_room_aide))
-               return;
-
-       if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
-               cprintf("%d No directory in this room.\n", ERROR + NOT_HERE);
-               return;
-       }
-
-       if (IsEmptyStr(filename)) {
-               cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
-               return;
-       }
-       for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
-               if ( (filename[a] == '/') || (filename[a] == '\\') ) {
-                       filename[a] = '_';
-               }
-       }
-       snprintf(pathname, sizeof pathname,
-                "%s/%s/%s",
-                ctdl_file_dir,
-                CC->room.QRdirname, filename
-       );
-       a = unlink(pathname);
-       if (a == 0) {
-               cprintf("%d File '%s' deleted.\n", CIT_OK, pathname);
-       }
-       else {
-               cprintf("%d File '%s' not found.\n", ERROR + FILE_NOT_FOUND, pathname);
-       }
-}
-
-
-/*
- * move a file from one room directory to another
- */
-void cmd_movf(char *cmdbuf)
-{
-       char filename[PATH_MAX];
-       char pathname[PATH_MAX];
-       char newpath[PATH_MAX];
-       char newroom[ROOMNAMELEN];
-       char buf[PATH_MAX];
-       int a;
-       struct ctdlroom qrbuf;
-
-       extract_token(filename, cmdbuf, 0, '|', sizeof filename);
-       extract_token(newroom, cmdbuf, 1, '|', sizeof newroom);
-
-       if (CtdlAccessCheck(ac_room_aide)) return;
-
-       if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
-               cprintf("%d No directory in this room.\n", ERROR + NOT_HERE);
-               return;
-       }
-
-       if (IsEmptyStr(filename)) {
-               cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
-               return;
-       }
-
-       for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
-               if ( (filename[a] == '/') || (filename[a] == '\\') ) {
-                       filename[a] = '_';
-               }
-       }
-       snprintf(pathname, sizeof pathname, "./files/%s/%s", CC->room.QRdirname, filename);
-       if (access(pathname, 0) != 0) {
-               cprintf("%d File '%s' not found.\n", ERROR + FILE_NOT_FOUND, pathname);
-               return;
-       }
-
-       if (CtdlGetRoom(&qrbuf, newroom) != 0) {
-               cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, newroom);
-               return;
-       }
-       if ((qrbuf.QRflags & QR_DIRECTORY) == 0) {
-               cprintf("%d '%s' is not a directory room.\n", ERROR + NOT_HERE, qrbuf.QRname);
-               return;
-       }
-       snprintf(newpath, sizeof newpath, "./files/%s/%s", qrbuf.QRdirname, filename);
-       if (link(pathname, newpath) != 0) {
-               cprintf("%d Couldn't move file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
-               return;
-       }
-       unlink(pathname);
-
-       /* this is a crude method of copying the file description */
-       snprintf(buf, sizeof buf, "cat ./files/%s/filedir |grep \"%s\" >>./files/%s/filedir", CC->room.QRdirname, filename, qrbuf.QRdirname);
-       system(buf);
-       cprintf("%d File '%s' has been moved.\n", CIT_OK, filename);
-}
-
-
-/*
- * This code is common to all commands which open a file for downloading,
- * regardless of whether it's a file from the directory, an image, a network
- * spool file, a MIME attachment, etc.
- * It examines the file and displays the OK result code and some information
- * about the file.  NOTE: this stuff is Unix dependent.
- */
-void OpenCmdResult(char *filename, const char *mime_type)
-{
-       struct stat statbuf;
-       time_t modtime;
-       long filesize;
-
-       fstat(fileno(CC->download_fp), &statbuf);
-       CC->download_fp_total = statbuf.st_size;
-       filesize = (long) statbuf.st_size;
-       modtime = (time_t) statbuf.st_mtime;
-
-       cprintf("%d %ld|%ld|%s|%s\n", CIT_OK, filesize, (long)modtime, filename, mime_type);
-}
-
-
-/*
- * open a file for downloading
- */
-void cmd_open(char *cmdbuf)
-{
-       char filename[256];
-       char pathname[PATH_MAX];
-       int a;
-
-       extract_token(filename, cmdbuf, 0, '|', sizeof filename);
-
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
-               cprintf("%d No directory in this room.\n", ERROR + NOT_HERE);
-               return;
-       }
-
-       if (IsEmptyStr(filename)) {
-               cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
-               return;
-       }
-       if (strstr(filename, "../") != NULL)
-       {
-               cprintf("%d syntax error.\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       if (CC->download_fp != NULL) {
-               cprintf("%d You already have a download file open.\n", ERROR + RESOURCE_BUSY);
-               return;
-       }
-
-       for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
-               if ( (filename[a] == '/') || (filename[a] == '\\') ) {
-                       filename[a] = '_';
-               }
-       }
-
-       snprintf(pathname, sizeof pathname, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, filename);
-       CC->download_fp = fopen(pathname, "r");
-
-       if (CC->download_fp == NULL) {
-               cprintf("%d cannot open %s: %s\n", ERROR + INTERNAL_ERROR, pathname, strerror(errno));
-               return;
-       }
-
-       OpenCmdResult(filename, "application/octet-stream");
-}
-
-
-/*
- * open an image file
- */
-void cmd_oimg(char *cmdbuf)
-{
-       char filename[PATH_MAX];
-       char pathname[PATH_MAX];
-       char MimeTestBuf[32];
-       int rv;
-
-       extract_token(filename, cmdbuf, 0, '|', sizeof filename);
-
-       if (IsEmptyStr(filename)) {
-               cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
-               return;
-       }
-
-       if (CC->download_fp != NULL) {
-               cprintf("%d You already have a download file open.\n", ERROR + RESOURCE_BUSY);
-               return;
-       }
-
-       CC->download_fp = fopen(pathname, "rb");
-       if (CC->download_fp == NULL) {
-               strcat(pathname, ".gif");
-               CC->download_fp = fopen(pathname, "rb");
-       }
-       if (CC->download_fp == NULL) {
-               cprintf("%d Cannot open %s: %s\n", ERROR + FILE_NOT_FOUND, pathname, strerror(errno));
-               return;
-       }
-       rv = fread(&MimeTestBuf[0], 1, 32, CC->download_fp);
-       if (rv == -1) {
-               cprintf("%d Cannot access %s: %s\n", ERROR + FILE_NOT_FOUND, pathname, strerror(errno));
-               return;
-       }
-
-       rewind (CC->download_fp);
-       OpenCmdResult(pathname, GuessMimeType(&MimeTestBuf[0], 32));
-}
-
-
-/*
- * open a file for uploading
- */
-void cmd_uopn(char *cmdbuf)
-{
-       int a;
-
-       extract_token(CC->upl_file, cmdbuf, 0, '|', sizeof CC->upl_file);
-       extract_token(CC->upl_mimetype, cmdbuf, 1, '|', sizeof CC->upl_mimetype);
-       extract_token(CC->upl_comment, cmdbuf, 2, '|', sizeof CC->upl_comment);
-
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
-               cprintf("%d No directory in this room.\n", ERROR + NOT_HERE);
-               return;
-       }
-
-       if (IsEmptyStr(CC->upl_file)) {
-               cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
-               return;
-       }
-
-       if (CC->upload_fp != NULL) {
-               cprintf("%d You already have a upload file open.\n", ERROR + RESOURCE_BUSY);
-               return;
-       }
-
-       for (a = 0; !IsEmptyStr(&CC->upl_file[a]); ++a) {
-               if ( (CC->upl_file[a] == '/') || (CC->upl_file[a] == '\\') ) {
-                       CC->upl_file[a] = '_';
-               }
-       }
-       snprintf(CC->upl_path, sizeof CC->upl_path, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, CC->upl_file);
-       snprintf(CC->upl_filedir, sizeof CC->upl_filedir, "%s/%s/filedir", ctdl_file_dir, CC->room.QRdirname);
-
-       CC->upload_fp = fopen(CC->upl_path, "r");
-       if (CC->upload_fp != NULL) {
-               fclose(CC->upload_fp);
-               CC->upload_fp = NULL;
-               cprintf("%d '%s' already exists\n", ERROR + ALREADY_EXISTS, CC->upl_path);
-               return;
-       }
-
-       CC->upload_fp = fopen(CC->upl_path, "wb");
-       if (CC->upload_fp == NULL) {
-               cprintf("%d Cannot open %s: %s\n", ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno));
-               return;
-       }
-       cprintf("%d Ok\n", CIT_OK);
-}
-
-
-/*
- * open an image file for uploading
- */
-void cmd_uimg(char *cmdbuf)
-{
-       int is_this_for_real;
-       char basenm[256];
-       int a;
-
-       if (num_parms(cmdbuf) < 2) {
-               cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       is_this_for_real = extract_int(cmdbuf, 0);
-       extract_token(CC->upl_mimetype, cmdbuf, 1, '|', sizeof CC->upl_mimetype);
-       extract_token(basenm, cmdbuf, 2, '|', sizeof basenm);
-       if (CC->upload_fp != NULL) {
-               cprintf("%d You already have an upload file open.\n", ERROR + RESOURCE_BUSY);
-               return;
-       }
-
-       strcpy(CC->upl_path, "");
-
-       for (a = 0; !IsEmptyStr(&basenm[a]); ++a) {
-               basenm[a] = tolower(basenm[a]);
-               if ( (basenm[a] == '/') || (basenm[a] == '\\') ) {
-                       basenm[a] = '_';
-               }
-       }
-
-       if (CC->user.axlevel >= AxAideU) {
-               snprintf(CC->upl_path, sizeof CC->upl_path, "%s/%s", ctdl_image_dir, basenm);
-       }
-
-       if (IsEmptyStr(CC->upl_path)) {
-               cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
-               return;
-       }
-
-       if (is_this_for_real == 0) {
-               cprintf("%d Ok to send image\n", CIT_OK);
-               return;
-       }
-
-       CC->upload_fp = fopen(CC->upl_path, "wb");
-       if (CC->upload_fp == NULL) {
-               cprintf("%d Cannot open %s: %s\n", ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno));
-               return;
-       }
-       cprintf("%d Ok\n", CIT_OK);
-}
-
-
-/*
- * close the download file
- */
-void cmd_clos(char *cmdbuf)
-{
-       if (CC->download_fp == NULL) {
-               cprintf("%d You don't have a download file open.\n", ERROR + RESOURCE_NOT_OPEN);
-               return;
-       }
-
-       fclose(CC->download_fp);
-       CC->download_fp = NULL;
-       cprintf("%d Ok\n", CIT_OK);
-}
-
-
-/*
- * abort an upload
- */
-void abort_upl(CitContext *who)
-{
-       if (who->upload_fp != NULL) {
-               fclose(who->upload_fp);
-               who->upload_fp = NULL;
-               unlink(CC->upl_path);
-       }
-}
-
-
-/*
- * close the upload file
- */
-void cmd_ucls(char *cmd)
-{
-       FILE *fp;
-       char upload_notice[SIZ];
-
-       if (CC->upload_fp == NULL) {
-               cprintf("%d You don't have an upload file open.\n", ERROR + RESOURCE_NOT_OPEN);
-               return;
-       }
-
-       fclose(CC->upload_fp);
-       CC->upload_fp = NULL;
-
-       if (!strcasecmp(cmd, "1")) {
-               cprintf("%d File '%s' saved.\n", CIT_OK, CC->upl_path);
-               fp = fopen(CC->upl_filedir, "a");
-               if (fp == NULL) {
-                       fp = fopen(CC->upl_filedir, "w");
-               }
-               if (fp != NULL) {
-                       fprintf(fp, "%s %s %s\n", CC->upl_file, CC->upl_mimetype, CC->upl_comment);
-                       fclose(fp);
-               }
-
-               if ((CC->room.QRflags2 & QR2_NOUPLMSG) == 0) {
-                       /* put together an upload notice */
-                       snprintf(upload_notice, sizeof upload_notice,
-                                "NEW UPLOAD: '%s'\n %s\n%s\n",
-                                CC->upl_file, 
-                                CC->upl_comment, 
-                                CC->upl_mimetype
-                       );
-                       quickie_message(CC->curr_user, NULL, NULL, CC->room.QRname, upload_notice, 0, NULL);
-               }
-       } else {
-               abort_upl(CC);
-               cprintf("%d File '%s' aborted.\n", CIT_OK, CC->upl_path);
-       }
-}
-
-
-/*
- * read from the download file
- */
-void cmd_read(char *cmdbuf)
-{
-       long start_pos;
-       size_t bytes;
-       char buf[SIZ];
-       int rc;
-
-       /* The client will transmit its requested offset and byte count */
-       start_pos = extract_long(cmdbuf, 0);
-       bytes = extract_int(cmdbuf, 1);
-       if ((start_pos < 0) || (bytes <= 0)) {
-               cprintf("%d you have to specify a value > 0.\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       if (CC->download_fp == NULL) {
-               cprintf("%d You don't have a download file open.\n", ERROR + RESOURCE_NOT_OPEN);
-               return;
-       }
-
-       /* If necessary, reduce the byte count to the size of our buffer */
-       if (bytes > sizeof(buf)) {
-               bytes = sizeof(buf);
-       }
-
-       rc = fseek(CC->download_fp, start_pos, 0);
-       if (rc < 0) {
-               cprintf("%d your file is smaller than %ld.\n", ERROR + ILLEGAL_VALUE, start_pos);
-               syslog(LOG_ERR, "serv_file: your file %s is smaller than %ld [%s]", 
-                       CC->upl_path, 
-                       start_pos,
-                       strerror(errno)
-               );
-
-               return;
-       }
-       bytes = fread(buf, 1, bytes, CC->download_fp);
-       if (bytes > 0) {
-               /* Tell the client the actual byte count and transmit it */
-               cprintf("%d %d\n", BINARY_FOLLOWS, (int)bytes);
-               client_write(buf, bytes);
-       }
-       else {
-               cprintf("%d %s\n", ERROR, strerror(errno));
-       }
-}
-
-
-/*
- * write to the upload file
- */
-void cmd_writ(char *cmdbuf)
-{
-       struct CitContext *CCC = CC;
-       int bytes;
-       char *buf;
-       int rv;
-
-       unbuffer_output();
-
-       bytes = extract_int(cmdbuf, 0);
-
-       if (CCC->upload_fp == NULL) {
-               cprintf("%d You don't have an upload file open.\n", ERROR + RESOURCE_NOT_OPEN);
-               return;
-       }
-       if (bytes <= 0) {
-               cprintf("%d you have to specify a value > 0.\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       if (bytes > 100000) {
-               bytes = 100000;
-       }
-
-       cprintf("%d %d\n", SEND_BINARY, bytes);
-       buf = malloc(bytes + 1);
-       client_read(buf, bytes);
-       rv = fwrite(buf, bytes, 1, CCC->upload_fp);
-       if (rv == -1) {
-               syslog(LOG_ERR, "serv_file: %s", strerror(errno));
-       }
-       free(buf);
-}
-
-
-void files_logout_hook(void)
-{
-        CitContext *CCC = MyContext();
-
-       /*
-        * If there is a download in progress, abort it.
-        */
-       if (CCC->download_fp != NULL) {
-               fclose(CCC->download_fp);
-               CCC->download_fp = NULL;
-       }
-
-       /*
-        * If there is an upload in progress, abort it.
-        */
-       if (CCC->upload_fp != NULL) {
-               abort_upl(CCC);
-       }
-
-}
-
-
-/* 
- * help_subst()  -  support routine for help file viewer
- */
-void help_subst(char *strbuf, char *source, char *dest)
-{
-       char workbuf[SIZ];
-       int p;
-
-       while (p = pattern2(strbuf, source), (p >= 0)) {
-               strcpy(workbuf, &strbuf[p + strlen(source)]);
-               strcpy(&strbuf[p], dest);
-               strcat(strbuf, workbuf);
-       }
-}
-
-
-void do_help_subst(char *buffer)
-{
-       char buf2[16];
-
-       help_subst(buffer, "^nodename", CtdlGetConfigStr("c_nodename"));
-       help_subst(buffer, "^humannode", CtdlGetConfigStr("c_humannode"));
-       help_subst(buffer, "^fqdn", CtdlGetConfigStr("c_fqdn"));
-       help_subst(buffer, "^username", CC->user.fullname);
-       snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
-       help_subst(buffer, "^usernum", buf2);
-       help_subst(buffer, "^sysadm", CtdlGetConfigStr("c_sysadm"));
-       help_subst(buffer, "^variantname", CITADEL);
-       help_subst(buffer, "^maxsessions", CtdlGetConfigStr("c_maxsessions"));          // yes it's numeric but str is ok here
-       help_subst(buffer, "^bbsdir", ctdl_message_dir);
-}
-
-
-typedef const char *ccharp;
-/*
- * display system messages or help
- */
-void cmd_mesg(char *mname)
-{
-       FILE *mfp;
-       char targ[256];
-       char buf[256];
-       char buf2[256];
-       DIR *dp;
-       struct dirent *d;
-
-       extract_token(buf, mname, 0, '|', sizeof buf);
-
-       snprintf(buf2, sizeof buf2, "%s.%d.%d", buf, CC->cs_clientdev, CC->cs_clienttyp);
-
-       /* If the client requested "?" then produce a listing */
-       if (!strcmp(buf, "?")) {
-               cprintf("%d %s\n", LISTING_FOLLOWS, buf);
-               dp = opendir(ctdl_message_dir);
-               if (dp != NULL) {
-                       while (d = readdir(dp), d != NULL) {
-                               if (d->d_name[0] != '.') {
-                                       cprintf(" %s\n", d->d_name);
-                               }
-                       }
-                       closedir(dp);
-               }
-               cprintf("000\n");
-               return;
-       }
-
-       /* Otherwise, look for the requested file by name. */
-       snprintf(targ, sizeof targ, "%s/%s", ctdl_message_dir, buf);
-       mfp = fopen(targ, "r");
-       if (mfp==NULL) {
-               cprintf("%d Cannot open '%s': %s\n",
-                       ERROR + FILE_NOT_FOUND, targ, strerror(errno));
-               return;
-       }
-       cprintf("%d %s\n", LISTING_FOLLOWS, buf);
-
-       while (fgets(buf, (sizeof buf - 1), mfp) != NULL) {
-               buf[strlen(buf)-1] = 0;
-               do_help_subst(buf);
-               cprintf("%s\n",buf);
-       }
-
-       fclose(mfp);
-       cprintf("000\n");
-}
-
-
-/*
- * enter system messages or help
- */
-void cmd_emsg(char *mname)
-{
-       FILE *mfp;
-       char targ[256];
-       char buf[256];
-       int a;
-
-       unbuffer_output();
-
-       if (CtdlAccessCheck(ac_aide)) return;
-
-       extract_token(buf, mname, 0, '|', sizeof buf);
-       for (a=0; !IsEmptyStr(&buf[a]); ++a) {          /* security measure */
-               if (buf[a] == '/') buf[a] = '.';
-       }
-
-       if (IsEmptyStr(targ)) {
-               snprintf(targ, sizeof targ, "%s/%s", ctdl_message_dir, buf);
-       }
-
-       mfp = fopen(targ, "w");
-       if (mfp==NULL) {
-               cprintf("%d Cannot open '%s': %s\n",
-                       ERROR + INTERNAL_ERROR, targ, strerror(errno));
-               return;
-       }
-       cprintf("%d %s\n", SEND_LISTING, targ);
-
-       while (client_getln(buf, sizeof buf) >=0 && strcmp(buf, "000")) {
-               fprintf(mfp, "%s\n", buf);
-       }
-
-       fclose(mfp);
-}
-
-/*****************************************************************************/
-/*                      MODULE INITIALIZATION STUFF                          */
-/*****************************************************************************/
-
-CTDL_MODULE_INIT(file_ops)
-{
-       if (!threading) {
-                CtdlRegisterSessionHook(files_logout_hook, EVT_LOGOUT, PRIO_LOGOUT + 8);
-               CtdlRegisterProtoHook(cmd_delf, "DELF", "Delete a file");
-               CtdlRegisterProtoHook(cmd_movf, "MOVF", "Move a file");
-               CtdlRegisterProtoHook(cmd_open, "OPEN", "Open a download file transfer");
-               CtdlRegisterProtoHook(cmd_clos, "CLOS", "Close a download file transfer");
-               CtdlRegisterProtoHook(cmd_uopn, "UOPN", "Open an upload file transfer");
-               CtdlRegisterProtoHook(cmd_ucls, "UCLS", "Close an upload file transfer");
-               CtdlRegisterProtoHook(cmd_read, "READ", "File transfer read operation");
-               CtdlRegisterProtoHook(cmd_writ, "WRIT", "File transfer write operation");
-               CtdlRegisterProtoHook(cmd_oimg, "OIMG", "Open an image file for download");
-               CtdlRegisterProtoHook(cmd_uimg, "UIMG", "Upload an image file");
-               CtdlRegisterProtoHook(cmd_mesg, "MESG", "fetch system banners");
-               CtdlRegisterProtoHook(cmd_emsg, "EMSG", "submit system banners");
-       }
-        /* return our Subversion id for the Log */
-       return "file_ops";
-}
diff --git a/citadel/modules/ctdlproto/serv_messages.c b/citadel/modules/ctdlproto/serv_messages.c
deleted file mode 100644 (file)
index 480f849..0000000
+++ /dev/null
@@ -1,840 +0,0 @@
-// Message-related protocol commands for Citadel clients
-//
-// Copyright (c) 1987-2022 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include <stdio.h>
-#include <libcitadel.h>
-#include "citserver.h"
-#include "ctdl_module.h"
-#include "internet_addressing.h"
-#include "user_ops.h"
-#include "room_ops.h"
-#include "config.h"
-
-extern char *msgkeys[];
-
-
-// Back end for the MSGS command: output message number only.
-void simple_listing(long msgnum, void *userdata) {
-       cprintf("%ld\n", msgnum);
-}
-
-
-// Back end for the MSGS command: output header summary.
-void headers_listing(long msgnum, void *userdata) {
-       struct CtdlMessage *msg;
-       int output_mode =  *(int *)userdata;
-
-       msg = CtdlFetchMessage(msgnum, 0);
-       if (msg == NULL) {
-               cprintf("%ld|0|||||||\n", msgnum);
-               return;
-       }
-
-       // change all vertical bars in the subject to hyphens so it doesn't screw up the protocol
-       if (!CM_IsEmpty(msg, eMsgSubject)) {
-               char *p;
-               for (p=msg->cm_fields[eMsgSubject]; *p; p++) {
-                       if (*p == '|') {
-                               *p = '-';
-                       }
-               }
-       }
-
-       // output all fields except the references hash
-       cprintf("%ld|%s|%s|%s|%s|%s",
-               msgnum,
-               (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"),
-               (!CM_IsEmpty(msg, eAuthor) ? msg->cm_fields[eAuthor] : ""),
-               CtdlGetConfigStr("c_nodename"),                                         // no more nodenames anymore
-               (!CM_IsEmpty(msg, erFc822Addr) ? msg->cm_fields[erFc822Addr] : ""),
-               (!CM_IsEmpty(msg, eMsgSubject) ? msg->cm_fields[eMsgSubject] : "")
-       );
-
-       if (output_mode == MSG_HDRS_THREADS) {          // field view with thread hashes
-
-               // output the references hash
-               cprintf ("|%d|", 
-                       (!CM_IsEmpty(msg, emessageId) ? HashLittle(msg->cm_fields[emessageId],strlen(msg->cm_fields[emessageId])) : 0)
-               );
-       
-               // output the references hash (yes it's ok that we're trashing the source buffer by doing this)
-               if (!CM_IsEmpty(msg, eWeferences)) {
-                       char *token;
-                       char *rest = msg->cm_fields[eWeferences];
-                       char *prev = rest;
-                       while((token = strtok_r(rest, "|", &rest))) {
-                               cprintf("%d%s", HashLittle(token,rest-prev-(*rest==0?0:1)), (*rest==0?"":","));
-                               prev = rest;
-                       }
-               }
-       
-               cprintf("|\n");
-       }
-
-       else {                                          // field view with no threads, subject extends out forever
-               cprintf("\n");
-       }
-
-       CM_Free(msg);
-}
-
-typedef struct _msg_filter {
-       HashList *Filter;
-       HashPos *p;
-       StrBuf *buffer;
-} msg_filter;
-
-
-void headers_brief_filter(long msgnum, void *userdata) {
-       long i, l;
-       struct CtdlMessage *msg;
-       msg_filter *flt = (msg_filter*) userdata;
-
-       l = GetCount(flt->Filter);
-       msg = CtdlFetchMessage(msgnum, 0);
-       StrBufPrintf(flt->buffer, "%ld", msgnum);
-       if (msg == NULL) {
-               for (i = 0; i < l; i++) {
-                       StrBufAppendBufPlain(flt->buffer, HKEY("|"), 0);
-               }
-       }
-       else {
-               const char *k;
-               long len;
-               void *v;
-               RewindHashPos(flt->Filter, flt->p, 0);
-               while (GetNextHashPos(flt->Filter, flt->p, &len, &k, &v)) {
-                       eMsgField f = (eMsgField) v;
-                       
-                       StrBufAppendBufPlain(flt->buffer, HKEY("|"), 0);
-                       if (!CM_IsEmpty(msg, f)) {
-                               StrBufAppendBufPlain(flt->buffer, CM_KEY(msg, f), 0);
-                       }
-               }
-       }
-       StrBufAppendBufPlain(flt->buffer, HKEY("\n"), 0);
-       cputbuf(flt->buffer);
-}
-
-// Back end for the MSGS command: output EUID header.
-void headers_euid(long msgnum, void *userdata) {
-       struct CtdlMessage *msg;
-
-       msg = CtdlFetchMessage(msgnum, 0);
-       if (msg == NULL) {
-               cprintf("%ld||\n", msgnum);
-               return;
-       }
-
-       cprintf("%ld|%s|%s\n", 
-               msgnum, 
-               (!CM_IsEmpty(msg, eExclusiveID) ? msg->cm_fields[eExclusiveID] : ""),
-               (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"));
-       CM_Free(msg);
-}
-
-
-// cmd_msgs()  -  get list of message #'s in this room
-//             implements the MSGS server command using CtdlForEachMessage()
-void cmd_msgs(char *cmdbuf) {
-       int mode = 0;
-       char which[16];
-       char buf[256];
-       char tfield[256];
-       char tvalue[256];
-       int cm_ref = 0;
-       int with_template = 0;
-       struct CtdlMessage *template = NULL;
-        msg_filter filt;
-       char search_string[1024];
-       ForEachMsgCallback CallBack;
-
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
-       extract_token(which, cmdbuf, 0, '|', sizeof which);
-       cm_ref = extract_int(cmdbuf, 1);
-       extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
-       with_template = extract_int(cmdbuf, 2);
-       int output_mode = extract_int(cmdbuf, 3);
-       switch (output_mode) {
-               default:
-               case MSG_HDRS_BRIEF:
-                       CallBack = simple_listing;
-                       break;
-               case MSG_HDRS_ALL:
-               case MSG_HDRS_THREADS:
-                       CallBack = headers_listing;
-                       break;
-               case MSG_HDRS_EUID:
-                       CallBack = headers_euid;
-                       break;
-               case MSG_HDRS_BRIEFFILTER:
-                       with_template = 2;
-                       CallBack = headers_brief_filter;
-                       break;
-       }
-
-       strcat(which, "   ");
-       if (!strncasecmp(which, "OLD", 3))
-               mode = MSGS_OLD;
-       else if (!strncasecmp(which, "NEW", 3))
-               mode = MSGS_NEW;
-       else if (!strncasecmp(which, "FIRST", 5))
-               mode = MSGS_FIRST;
-       else if (!strncasecmp(which, "LAST", 4))
-               mode = MSGS_LAST;
-       else if (!strncasecmp(which, "GT", 2))
-               mode = MSGS_GT;
-       else if (!strncasecmp(which, "LT", 2))
-               mode = MSGS_LT;
-       else if (!strncasecmp(which, "SEARCH", 6))
-               mode = MSGS_SEARCH;
-       else
-               mode = MSGS_ALL;
-
-       if ( (mode == MSGS_SEARCH) && (!CtdlGetConfigInt("c_enable_fulltext")) ) {
-               cprintf("%d Full text index is not enabled on this server.\n",
-                       ERROR + CMD_NOT_SUPPORTED);
-               return;
-       }
-
-       if (with_template == 1) {
-               memset(buf, 0, 5);
-               unbuffer_output();
-               cprintf("%d Send template then receive message list\n",
-                       START_CHAT_MODE);
-               template = (struct CtdlMessage *)
-                       malloc(sizeof(struct CtdlMessage));
-               memset(template, 0, sizeof(struct CtdlMessage));
-               template->cm_magic = CTDLMESSAGE_MAGIC;
-               template->cm_anon_type = MES_NORMAL;
-
-               while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
-                       eMsgField f;
-                       long tValueLen;
-
-                       tValueLen = extract_token(tfield, buf, 0, '|', sizeof tfield);
-                       if ((tValueLen == 4) && GetFieldFromMnemonic(&f, tfield))
-                       {
-                               tValueLen = extract_token(tvalue, buf, 1, '|', sizeof tvalue);
-                               if (tValueLen >= 0) {
-                                       CM_SetField(template, f, tvalue, tValueLen);
-                               }
-                       }
-               }
-               buffer_output();
-       }
-       else if (with_template == 2) {
-               long i = 0;
-               memset(buf, 0, 5);
-               cprintf("%d Send list of headers\n",
-                       START_CHAT_MODE);
-               filt.Filter = NewHash(1, lFlathash);
-               filt.buffer = NewStrBufPlain(NULL, 1024);
-               while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
-                       eMsgField f;
-       
-                       if (GetFieldFromMnemonic(&f, buf))
-                       {
-                               Put(filt.Filter, LKEY(i), (void*)f, reference_free_handler);
-                               i++;
-                       }
-               }
-               filt.p = GetNewHashPos(filt.Filter, 0);
-               buffer_output();
-       }
-       else {
-               cprintf("%d  \n", LISTING_FOLLOWS);
-       }
-
-       if (with_template < 2) {
-               CtdlForEachMessage(mode,
-                                  ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
-                                  ( (mode == MSGS_SEARCH) ? search_string : NULL ),
-                                  NULL,
-                                  template,
-                                  CallBack,
-                                  &output_mode);
-               if (template != NULL) CM_Free(template);
-       }
-       else {
-               CtdlForEachMessage(mode,
-                                  ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
-                                  ( (mode == MSGS_SEARCH) ? search_string : NULL ),
-                                  NULL,
-                                  NULL,
-                                  CallBack,
-                                  &filt);
-               DeleteHashPos(&filt.p);
-               DeleteHash(&filt.Filter);
-               FreeStrBuf(&filt.buffer);
-               
-       }
-       cprintf("000\n");
-}
-
-
-/*
- * display a message (mode 0 - Citadel proprietary)
- */
-void cmd_msg0(char *cmdbuf)
-{
-       long msgid;
-       int headers_only = HEADERS_ALL;
-
-       msgid = extract_long(cmdbuf, 0);
-       headers_only = extract_int(cmdbuf, 1);
-
-       CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL, NULL);
-       return;
-}
-
-
-// display a message (mode 2 - RFC822)
-void cmd_msg2(char *cmdbuf) {
-       long msgid;
-       int headers_only = HEADERS_ALL;
-
-       msgid = extract_long(cmdbuf, 0);
-       headers_only = extract_int(cmdbuf, 1);
-
-       CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL, NULL);
-}
-
-
-// Display a message using MIME content types
-void cmd_msg4(char *cmdbuf) {
-       long msgid;
-       char section[64];
-
-       msgid = extract_long(cmdbuf, 0);
-       extract_token(section, cmdbuf, 1, '|', sizeof section);
-       CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL, NULL);
-}
-
-
-// Client tells us its preferred message format(s)
-void cmd_msgp(char *cmdbuf) {
-       if (!strcasecmp(cmdbuf, "dont_decode")) {
-               CC->msg4_dont_decode = 1;
-               cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
-       }
-       else {
-               safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
-               cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
-       }
-}
-
-
-// Open a component of a MIME message as a download file 
-void cmd_opna(char *cmdbuf) {
-       long msgid;
-       char desired_section[128];
-
-       msgid = extract_long(cmdbuf, 0);
-       extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
-       safestrncpy(CC->download_desired_section, desired_section,
-               sizeof CC->download_desired_section);
-       CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
-}                      
-
-
-// Open a component of a MIME message and transmit it all at once
-void cmd_dlat(char *cmdbuf) {
-       long msgid;
-       char desired_section[128];
-
-       msgid = extract_long(cmdbuf, 0);
-       extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
-       safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
-       CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
-}
-
-
-// message entry  -  mode 0 (normal)
-void cmd_ent0(char *entargs) {
-       int post = 0;
-       char recp[SIZ];
-       char cc[SIZ];
-       char bcc[SIZ];
-       char supplied_euid[128];
-       int anon_flag = 0;
-       int format_type = 0;
-       char newusername[256];
-       char newuseremail[256];
-       struct CtdlMessage *msg;
-       int anonymous = 0;
-       char errmsg[SIZ];
-       int err = 0;
-       struct recptypes *valid = NULL;
-       struct recptypes *valid_to = NULL;
-       struct recptypes *valid_cc = NULL;
-       struct recptypes *valid_bcc = NULL;
-       char subject[SIZ];
-       int subject_required = 0;
-       int do_confirm = 0;
-       long msgnum;
-       int i, j;
-       char buf[256];
-       int newuseremail_ok = 0;
-       char references[SIZ];
-       char *ptr;
-
-       unbuffer_output();
-
-       post = extract_int(entargs, 0);
-       extract_token(recp, entargs, 1, '|', sizeof recp);
-       anon_flag = extract_int(entargs, 2);
-       format_type = extract_int(entargs, 3);
-       extract_token(subject, entargs, 4, '|', sizeof subject);
-       extract_token(newusername, entargs, 5, '|', sizeof newusername);
-       do_confirm = extract_int(entargs, 6);
-       extract_token(cc, entargs, 7, '|', sizeof cc);
-       extract_token(bcc, entargs, 8, '|', sizeof bcc);
-       switch(CC->room.QRdefaultview) {
-       case VIEW_NOTES:
-       case VIEW_WIKI:
-               extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
-               break;
-       default:
-               supplied_euid[0] = 0;
-               break;
-       }
-       extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
-       extract_token(references, entargs, 11, '|', sizeof references);
-       for (ptr=references; *ptr != 0; ++ptr) {
-               if (*ptr == '!') *ptr = '|';
-       }
-
-       /* first check to make sure the request is valid. */
-
-       err = CtdlDoIHavePermissionToPostInThisRoom(
-               errmsg,
-               sizeof errmsg,
-               NULL,
-               POST_LOGGED_IN,
-               (!IsEmptyStr(references))               // is this a reply?  or a top-level post?
-       );
-       if (err) {
-               cprintf("%d %s\n", err, errmsg);
-               return;
-       }
-
-       /* Check some other permission type things. */
-
-       if (IsEmptyStr(newusername)) {
-               strcpy(newusername, CC->user.fullname);
-       }
-       if (  (CC->user.axlevel < AxAideU)
-             && (strcasecmp(newusername, CC->user.fullname))
-             && (strcasecmp(newusername, CC->cs_inet_fn))
-       ) {     
-               cprintf("%d You don't have permission to author messages as '%s'.\n",
-                       ERROR + HIGHER_ACCESS_REQUIRED,
-                       newusername
-               );
-               return;
-       }
-
-       if (IsEmptyStr(newuseremail)) {
-               newuseremail_ok = 1;
-       }
-
-       if (!IsEmptyStr(newuseremail)) {
-               if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
-                       newuseremail_ok = 1;
-               }
-               else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
-                       j = num_tokens(CC->cs_inet_other_emails, '|');
-                       for (i=0; i<j; ++i) {
-                               extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
-                               if (!strcasecmp(newuseremail, buf)) {
-                                       newuseremail_ok = 1;
-                               }
-                       }
-               }
-       }
-
-       if (!newuseremail_ok) {
-               cprintf("%d You don't have permission to author messages as '%s'.\n",
-                       ERROR + HIGHER_ACCESS_REQUIRED,
-                       newuseremail
-                       );
-               return;
-       }
-
-       CC->cs_flags |= CS_POSTING;
-
-       // In mailbox rooms we have to behave a little differently --
-       // make sure the user has specified at least one recipient.  Then
-       // validate the recipient(s).  We do this for the Mail> room, as
-       // well as any room which has the "Mailbox" view set - unless it
-       // is the DRAFTS room which does not require recipients.
-
-       if ( (  ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
-               || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
-                    ) && (strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
-               if (CC->user.axlevel < AxProbU) {
-                       strcpy(recp, "sysop");
-                       strcpy(cc, "");
-                       strcpy(bcc, "");
-               }
-
-               valid_to = validate_recipients(recp, NULL, 0);
-               if (valid_to->num_error > 0) {
-                       cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
-                       free_recipients(valid_to);
-                       return;
-               }
-
-               valid_cc = validate_recipients(cc, NULL, 0);
-               if (valid_cc->num_error > 0) {
-                       cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
-                       free_recipients(valid_to);
-                       free_recipients(valid_cc);
-                       return;
-               }
-
-               valid_bcc = validate_recipients(bcc, NULL, 0);
-               if (valid_bcc->num_error > 0) {
-                       cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
-                       free_recipients(valid_to);
-                       free_recipients(valid_cc);
-                       free_recipients(valid_bcc);
-                       return;
-               }
-
-               // Recipient required, but none were specified
-               if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
-                       free_recipients(valid_to);
-                       free_recipients(valid_cc);
-                       free_recipients(valid_bcc);
-                       cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
-                       return;
-               }
-
-               if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
-                       if (CtdlCheckInternetMailPermission(&CC->user)==0) {
-                               cprintf("%d You do not have permission "
-                                       "to send Internet mail.\n",
-                                       ERROR + HIGHER_ACCESS_REQUIRED);
-                               free_recipients(valid_to);
-                               free_recipients(valid_cc);
-                               free_recipients(valid_bcc);
-                               return;
-                       }
-               }
-
-               if ( ( (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet) > 0) && (CC->user.axlevel < AxNetU) ) {
-                       cprintf("%d Higher access required for network mail.\n", ERROR + HIGHER_ACCESS_REQUIRED);
-                       free_recipients(valid_to);
-                       free_recipients(valid_cc);
-                       free_recipients(valid_bcc);
-                       return;
-               }
-       
-               if ((RESTRICT_INTERNET == 1)
-                   && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
-                   && ((CC->user.flags & US_INTERNET) == 0)
-                   && (!CC->internal_pgm)) {
-                       cprintf("%d You don't have access to Internet mail.\n",
-                               ERROR + HIGHER_ACCESS_REQUIRED);
-                       free_recipients(valid_to);
-                       free_recipients(valid_cc);
-                       free_recipients(valid_bcc);
-                       return;
-               }
-
-       }
-
-       // Is this a room which has anonymous-only or anonymous-option?
-       anonymous = MES_NORMAL;
-       if (CC->room.QRflags & QR_ANONONLY) {
-               anonymous = MES_ANONONLY;
-       }
-       if (CC->room.QRflags & QR_ANONOPT) {
-               if (anon_flag == 1) {   // only if the user requested it
-                       anonymous = MES_ANONOPT;
-               }
-       }
-
-       if ((CC->room.QRflags & QR_MAILBOX) == 0) {
-               recp[0] = 0;
-       }
-
-       // Recommend to the client that the use of a message subject is
-       // strongly recommended in this room, if either the SUBJECTREQ flag
-       // is set, or if there is one or more Internet email recipients.
-
-       if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
-       if ((valid_to)  && (valid_to->num_internet > 0))        subject_required = 1;
-       if ((valid_cc)  && (valid_cc->num_internet > 0))        subject_required = 1;
-       if ((valid_bcc) && (valid_bcc->num_internet > 0))       subject_required = 1;
-
-       // If we're only checking the validity of the request, return success without creating the message.
-       if (post == 0) {
-               cprintf("%d %s|%d\n", CIT_OK,
-                       ((valid_to != NULL) ? valid_to->display_recp : ""), 
-                       subject_required);
-               free_recipients(valid_to);
-               free_recipients(valid_cc);
-               free_recipients(valid_bcc);
-               return;
-       }
-
-       // We don't need these anymore because we'll do it differently below
-       free_recipients(valid_to);
-       free_recipients(valid_cc);
-       free_recipients(valid_bcc);
-
-       // Read in the message from the client.
-       if (do_confirm) {
-               cprintf("%d send message\n", START_CHAT_MODE);
-       }
-       else {
-               cprintf("%d send message\n", SEND_LISTING);
-       }
-
-       msg = CtdlMakeMessage(&CC->user, recp, cc,
-                             CC->room.QRname, anonymous, format_type,
-                             newusername, newuseremail, subject,
-                             ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
-                             NULL, references);
-
-       // Put together one big recipients struct containing to/cc/bcc all in one.  This is for the envelope.
-       char *all_recps = malloc(SIZ * 3);
-       strcpy(all_recps, recp);
-       if (!IsEmptyStr(cc)) {
-               if (!IsEmptyStr(all_recps)) {
-                       strcat(all_recps, ",");
-               }
-               strcat(all_recps, cc);
-       }
-       if (!IsEmptyStr(bcc)) {
-               if (!IsEmptyStr(all_recps)) {
-                       strcat(all_recps, ",");
-               }
-               strcat(all_recps, bcc);
-       }
-       if (!IsEmptyStr(all_recps)) {
-               valid = validate_recipients(all_recps, NULL, 0);
-       }
-       else {
-               valid = NULL;
-       }
-       free(all_recps);
-
-       // posting into a mailing list room? set the envelope from 
-       // to the actual mail address so others get a valid reply-to-header.
-       if ((valid != NULL) && (valid->num_room == 1) && !IsEmptyStr(valid->recp_orgroom)) {
-               CM_SetField(msg, eenVelopeTo, valid->recp_orgroom, strlen(valid->recp_orgroom));
-       }
-
-       if (msg != NULL) {
-               msgnum = CtdlSubmitMsg(msg, valid, "");
-               if (do_confirm) {
-                       cprintf("%ld\n", msgnum);
-
-                       if (StrLength(CC->StatusMessage) > 0) {
-                               cprintf("%s\n", ChrPtr(CC->StatusMessage));
-                       }
-                       else if (msgnum >= 0L) {
-                               client_write(HKEY("Message accepted.\n"));
-                       }
-                       else {
-                               client_write(HKEY("Internal error.\n"));
-                       }
-
-                       if (!CM_IsEmpty(msg, eExclusiveID)) {
-                               cprintf("%s\n", msg->cm_fields[eExclusiveID]);
-                       } else {
-                               cprintf("\n");
-                       }
-                       cprintf("000\n");
-               }
-
-               CM_Free(msg);
-       }
-       if (valid != NULL) {
-               free_recipients(valid);
-       }
-       return;
-}
-
-
-// Delete message from current room
-void cmd_dele(char *args) {
-       int num_deleted;
-       int i;
-       char msgset[SIZ];
-       char msgtok[32];
-       long *msgs;
-       int num_msgs = 0;
-
-       extract_token(msgset, args, 0, '|', sizeof msgset);
-       num_msgs = num_tokens(msgset, ',');
-       if (num_msgs < 1) {
-               cprintf("%d Nothing to do.\n", CIT_OK);
-               return;
-       }
-
-       if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
-               cprintf("%d Higher access required.\n",
-                       ERROR + HIGHER_ACCESS_REQUIRED);
-               return;
-       }
-
-       // Build our message set to be moved/copied
-       msgs = malloc(num_msgs * sizeof(long));
-       for (i=0; i<num_msgs; ++i) {
-               extract_token(msgtok, msgset, i, ',', sizeof msgtok);
-               msgs[i] = atol(msgtok);
-       }
-
-       num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
-       free(msgs);
-
-       if (num_deleted) {
-               cprintf("%d %d message%s deleted.\n", CIT_OK,
-                       num_deleted, ((num_deleted != 1) ? "s" : ""));
-       } else {
-               cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
-       }
-}
-
-
-// move or copy a message to another room
-void cmd_move(char *args) {
-       char msgset[SIZ];
-       char msgtok[32];
-       long *msgs;
-       int num_msgs = 0;
-
-       char targ[ROOMNAMELEN];
-       struct ctdlroom qtemp;
-       int err;
-       int is_copy = 0;
-       int ra;
-       int permit = 0;
-       int i;
-
-       extract_token(msgset, args, 0, '|', sizeof msgset);
-       num_msgs = num_tokens(msgset, ',');
-       if (num_msgs < 1) {
-               cprintf("%d Nothing to do.\n", CIT_OK);
-               return;
-       }
-
-       extract_token(targ, args, 1, '|', sizeof targ);
-       convert_room_name_macros(targ, sizeof targ);
-       targ[ROOMNAMELEN - 1] = 0;
-       is_copy = extract_int(args, 2);
-
-       if (CtdlGetRoom(&qtemp, targ) != 0) {
-               cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
-               return;
-       }
-
-       if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
-               cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
-               return;
-       }
-
-       CtdlGetUser(&CC->user, CC->curr_user);
-       CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
-
-       // Check for permission to perform this operation.
-       // Remember: "CC->room" is source, "qtemp" is target.
-       permit = 0;
-
-       // Admins can move/copy
-       if (CC->user.axlevel >= AxAideU) permit = 1;
-
-       // Room aides can move/copy
-       if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
-
-       // Permit move/copy from personal rooms
-       if ((CC->room.QRflags & QR_MAILBOX)
-           && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
-
-       // Permit only copy from public to personal room
-       if (    (is_copy)
-               && (!(CC->room.QRflags & QR_MAILBOX))
-               && (qtemp.QRflags & QR_MAILBOX)
-       ) {
-               permit = 1;
-       }
-
-       // Permit message removal from collaborative delete rooms
-       if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
-
-       // Users allowed to post into the target room may move into it too.
-       if ((CC->room.QRflags & QR_MAILBOX) && 
-           (qtemp.QRflags & UA_POSTALLOWED))  permit = 1;
-
-       // User must have access to target room
-       if (!(ra & UA_KNOWN))  permit = 0;
-
-       if (!permit) {
-               cprintf("%d Higher access required.\n",
-                       ERROR + HIGHER_ACCESS_REQUIRED);
-               return;
-       }
-
-       // Build our message set to be moved/copied
-       msgs = malloc(num_msgs * sizeof(long));
-       for (i=0; i<num_msgs; ++i) {
-               extract_token(msgtok, msgset, i, ',', sizeof msgtok);
-               msgs[i] = atol(msgtok);
-       }
-
-       // Do the copy
-       err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
-       if (err != 0) {
-               cprintf("%d Cannot store message(s) in %s: error %d\n",
-                       err, targ, err);
-               free(msgs);
-               return;
-       }
-
-       // Now delete the message from the source room, if this is a 'move' rather than a 'copy' operation.
-       if (is_copy == 0) {
-               CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
-       }
-       free(msgs);
-
-       cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
-}
-
-
-/*****************************************************************************/
-/*                      MODULE INITIALIZATION STUFF                          */
-/*****************************************************************************/
-CTDL_MODULE_INIT(ctdl_message)
-{
-       if (!threading) {
-
-               CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
-               CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
-               CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
-               CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
-               CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
-               CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
-               CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
-               CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
-               CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
-               CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
-       }
-
-        /* return our Subversion id for the Log */
-       return "ctdl_message";
-}
diff --git a/citadel/modules/ctdlproto/serv_rooms.c b/citadel/modules/ctdlproto/serv_rooms.c
deleted file mode 100644 (file)
index 2e9450f..0000000
+++ /dev/null
@@ -1,1051 +0,0 @@
-// Server functions which perform operations on room objects.
-//
-// Copyright (c) 1987-2022 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <dirent.h>    /* for cmd_rdir to read contents of the directory */
-#include <libcitadel.h>
-
-#include "citserver.h"
-#include "ctdl_module.h"
-#include "room_ops.h"
-#include "config.h"
-
-// Back-back-end for all room listing commands
-void list_roomname(struct ctdlroom *qrbuf, int ra, int current_view, int default_view) {
-       char truncated_roomname[ROOMNAMELEN];
-
-       // For my own mailbox rooms, chop off the owner prefix
-       if ( (qrbuf->QRflags & QR_MAILBOX)
-            && (atol(qrbuf->QRname) == CC->user.usernum) ) {
-               safestrncpy(truncated_roomname, qrbuf->QRname, sizeof truncated_roomname);
-               safestrncpy(truncated_roomname, &truncated_roomname[11], sizeof truncated_roomname);
-               cprintf("%s", truncated_roomname);
-       }
-       // For all other rooms, just display the name in its entirety
-       else {
-               cprintf("%s", qrbuf->QRname);
-       }
-
-       /* ...and now the other parameters */
-       cprintf("|%u|%d|%d|%d|%d|%d|%d|%ld|\n",
-               qrbuf->QRflags,
-               (int) qrbuf->QRfloor,
-               (int) qrbuf->QRorder,
-               (int) qrbuf->QRflags2,
-               ra,
-               current_view,
-               default_view,
-               qrbuf->QRmtime
-       );
-}
-
-
-// cmd_lrms()   -  List all accessible rooms, known or forgotten
-void cmd_lrms_backend(struct ctdlroom *qrbuf, void *data) {
-       int FloorBeingSearched = (-1);
-       int ra;
-       int view;
-
-       FloorBeingSearched = *(int *)data;
-       CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
-
-       if ((( ra & (UA_KNOWN | UA_ZAPPED)))
-           && ((qrbuf->QRfloor == (FloorBeingSearched))
-               || ((FloorBeingSearched) < 0)))
-               list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
-}
-
-
-void cmd_lrms(char *argbuf) {
-       int FloorBeingSearched = (-1);
-       if (!IsEmptyStr(argbuf))
-               FloorBeingSearched = extract_int(argbuf, 0);
-
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
-       CtdlGetUser(&CC->user, CC->curr_user);
-       cprintf("%d Accessible rooms:\n", LISTING_FOLLOWS);
-
-       CtdlForEachRoom(cmd_lrms_backend, &FloorBeingSearched);
-       cprintf("000\n");
-}
-
-
-// cmd_lkra()   -  List all known rooms
-void cmd_lkra_backend(struct ctdlroom *qrbuf, void *data) {
-       int FloorBeingSearched = (-1);
-       int ra;
-       int view;
-
-       FloorBeingSearched = *(int *)data;
-       CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
-
-       if ((( ra & (UA_KNOWN))) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
-               list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
-       }
-}
-
-
-void cmd_lkra(char *argbuf) {
-       int FloorBeingSearched = (-1);
-       if (!IsEmptyStr(argbuf)) {
-               FloorBeingSearched = extract_int(argbuf, 0);
-       }
-
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-       
-       CtdlGetUser(&CC->user, CC->curr_user);
-       cprintf("%d Known rooms:\n", LISTING_FOLLOWS);
-
-       CtdlForEachRoom(cmd_lkra_backend, &FloorBeingSearched);
-       cprintf("000\n");
-}
-
-
-void cmd_lprm_backend(struct ctdlroom *qrbuf, void *data) {
-       int FloorBeingSearched = (-1);
-       int ra;
-       int view;
-
-       FloorBeingSearched = *(int *)data;
-       CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
-
-       if (((qrbuf->QRflags & QR_PRIVATE) == 0) && ((qrbuf->QRflags & QR_MAILBOX) == 0) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
-               list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
-       }
-}
-
-
-void cmd_lprm(char *argbuf) {
-       int FloorBeingSearched = (-1);
-       if (!IsEmptyStr(argbuf)) {
-               FloorBeingSearched = extract_int(argbuf, 0);
-       }
-
-       cprintf("%d Public rooms:\n", LISTING_FOLLOWS);
-
-       CtdlForEachRoom(cmd_lprm_backend, &FloorBeingSearched);
-       cprintf("000\n");
-}
-
-
-// cmd_lkrn()   -  List all known rooms with new messages
-void cmd_lkrn_backend(struct ctdlroom *qrbuf, void *data) {
-       int FloorBeingSearched = (-1);
-       int ra;
-       int view;
-
-       FloorBeingSearched = *(int *)data;
-       CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
-
-       if ((ra & UA_KNOWN) && (ra & UA_HASNEWMSGS) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
-               list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
-       }
-}
-
-
-void cmd_lkrn(char *argbuf) {
-       int FloorBeingSearched = (-1);
-       if (!IsEmptyStr(argbuf)) {
-               FloorBeingSearched = extract_int(argbuf, 0);
-       }
-
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-       
-       CtdlGetUser(&CC->user, CC->curr_user);
-       cprintf("%d Rooms w/ new msgs:\n", LISTING_FOLLOWS);
-
-       CtdlForEachRoom(cmd_lkrn_backend, &FloorBeingSearched);
-       cprintf("000\n");
-}
-
-
-// cmd_lkro()   -  List all known rooms
-void cmd_lkro_backend(struct ctdlroom *qrbuf, void *data) {
-       int FloorBeingSearched = (-1);
-       int ra;
-       int view;
-
-       FloorBeingSearched = *(int *)data;
-       CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
-
-       if ((ra & UA_KNOWN) && ((ra & UA_HASNEWMSGS) == 0) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
-               list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
-       }
-}
-
-
-void cmd_lkro(char *argbuf) {
-       int FloorBeingSearched = (-1);
-       if (!IsEmptyStr(argbuf)) {
-               FloorBeingSearched = extract_int(argbuf, 0);
-       }
-
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-       
-       CtdlGetUser(&CC->user, CC->curr_user);
-       cprintf("%d Rooms w/o new msgs:\n", LISTING_FOLLOWS);
-
-       CtdlForEachRoom(cmd_lkro_backend, &FloorBeingSearched);
-       cprintf("000\n");
-}
-
-
-// cmd_lzrm()   -  List all forgotten rooms
-void cmd_lzrm_backend(struct ctdlroom *qrbuf, void *data) {
-       int FloorBeingSearched = (-1);
-       int ra;
-       int view;
-
-       FloorBeingSearched = *(int *)data;
-       CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
-
-       if ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
-               list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
-       }
-}
-
-
-void cmd_lzrm(char *argbuf) {
-       int FloorBeingSearched = (-1);
-       if (!IsEmptyStr(argbuf))
-               FloorBeingSearched = extract_int(argbuf, 0);
-
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-       
-       CtdlGetUser(&CC->user, CC->curr_user);
-       cprintf("%d Zapped rooms:\n", LISTING_FOLLOWS);
-
-       CtdlForEachRoom(cmd_lzrm_backend, &FloorBeingSearched);
-       cprintf("000\n");
-}
-
-
-// cmd_goto()  -  goto a new room
-void cmd_goto(char *gargs) {
-       struct CitContext *CCC = CC;
-       struct ctdlroom QRscratch;
-       int c;
-       int ok = 0;
-       int ra;
-       char augmented_roomname[ROOMNAMELEN];
-       char towhere[ROOMNAMELEN];
-       char password[32];
-       int transiently = 0;
-
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
-       extract_token(towhere, gargs, 0, '|', sizeof towhere);
-       extract_token(password, gargs, 1, '|', sizeof password);
-       transiently = extract_int(gargs, 2);
-
-       CtdlGetUser(&CCC->user, CCC->curr_user);
-
-       // Handle some of the macro named rooms
-       convert_room_name_macros(towhere, sizeof towhere);
-
-       // First try a regular match
-       c = CtdlGetRoom(&QRscratch, towhere);
-
-       // Then try a mailbox name match
-       if (c != 0) {
-               CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CCC->user, towhere);
-               c = CtdlGetRoom(&QRscratch, augmented_roomname);
-               if (c == 0) {
-                       safestrncpy(towhere, augmented_roomname, sizeof towhere);
-               }
-       }
-
-       // And if the room was found...
-       if (c == 0) {
-               // Let internal programs go directly to any room.
-               if (CCC->internal_pgm) {
-                       memcpy(&CCC->room, &QRscratch, sizeof(struct ctdlroom));
-                       CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL);
-                       return;
-               }
-
-               // See if there is an existing user/room relationship
-               CtdlRoomAccess(&QRscratch, &CCC->user, &ra, NULL);
-
-               // normal clients have to pass through security
-               if (ra & UA_GOTOALLOWED) {
-                       ok = 1;
-               }
-
-               if (ok == 1) {
-                       if ((QRscratch.QRflags & QR_MAILBOX) &&
-                           ((ra & UA_GOTOALLOWED))) {
-                               memcpy(&CCC->room, &QRscratch, sizeof(struct ctdlroom));
-                               CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL);
-                               return;
-                       }
-                       else if ((QRscratch.QRflags & QR_PASSWORDED) &&
-                               ((ra & UA_KNOWN) == 0) &&
-                               (strcasecmp(QRscratch.QRpasswd, password)) &&
-                               (CCC->user.axlevel < AxAideU)
-                       ) {
-                               cprintf("%d wrong or missing passwd\n", ERROR + PASSWORD_REQUIRED);
-                               return;
-                       }
-                       else if ((QRscratch.QRflags & QR_PRIVATE) &&
-                                  ((QRscratch.QRflags & QR_PASSWORDED) == 0) &&
-                                  ((QRscratch.QRflags & QR_GUESSNAME) == 0) &&
-                                  ((ra & UA_KNOWN) == 0) &&
-                                  (CCC->user.axlevel < AxAideU)
-                                  ) {
-                               syslog(LOG_DEBUG, "rooms: failed to acquire private room");
-                       }
-                       else {
-                               memcpy(&CCC->room, &QRscratch, sizeof(struct ctdlroom));
-                               CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL);
-                               return;
-                       }
-               }
-       }
-
-       cprintf("%d room '%s' not found\n", ERROR + ROOM_NOT_FOUND, towhere);
-}
-
-
-void cmd_whok(char *cmdbuf) {
-       struct ctdluser temp;
-       struct cdbdata *cdbus;
-       int ra;
-
-       cprintf("%d Who knows room:\n", LISTING_FOLLOWS);
-       cdb_rewind(CDB_USERS);
-       while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
-               memset(&temp, 0, sizeof temp);
-               memcpy(&temp, cdbus->ptr, sizeof temp);
-               cdb_free(cdbus);
-
-               CtdlRoomAccess(&CC->room, &temp, &ra, NULL);
-               if ((!IsEmptyStr(temp.fullname)) && 
-                   (CC->room.QRflags & QR_INUSE) &&
-                   (ra & UA_KNOWN)
-                       )
-                       cprintf("%s\n", temp.fullname);
-       }
-       cprintf("000\n");
-}
-
-
-// RDIR command for room directory
-void cmd_rdir(char *cmdbuf) {
-       char buf[256];
-       char comment[256];
-       FILE *fd;
-       struct stat statbuf;
-       DIR *filedir = NULL;
-       struct dirent *filedir_entry;
-       int d_namelen;
-       char buf2[SIZ];
-       char mimebuf[64];
-       long len;
-       
-       if (CtdlAccessCheck(ac_logged_in)) return;
-       
-       CtdlGetRoom(&CC->room, CC->room.QRname);
-       CtdlGetUser(&CC->user, CC->curr_user);
-
-       if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
-               cprintf("%d not here.\n", ERROR + NOT_HERE);
-               return;
-       }
-       if (((CC->room.QRflags & QR_VISDIR) == 0)
-               && (CC->user.axlevel < AxAideU)
-               && (CC->user.usernum != CC->room.QRroomaide))
-       {
-               cprintf("%d not here.\n", ERROR + HIGHER_ACCESS_REQUIRED);
-               return;
-       }
-
-       snprintf(buf, sizeof buf, "%s/%s", ctdl_file_dir, CC->room.QRdirname);
-       filedir = opendir (buf);
-       
-       if (filedir == NULL) {
-               cprintf("%d not here.\n", ERROR + HIGHER_ACCESS_REQUIRED);
-               return;
-       }
-       cprintf("%d %s|%s/%s\n", LISTING_FOLLOWS, CtdlGetConfigStr("c_fqdn"), ctdl_file_dir, CC->room.QRdirname);
-       
-       snprintf(buf, sizeof buf, "%s/%s/filedir", ctdl_file_dir, CC->room.QRdirname);
-       fd = fopen(buf, "r");
-       if (fd == NULL) {
-               fd = fopen("/dev/null", "r");
-       }
-       while ((filedir_entry = readdir(filedir))) {
-               if (strcasecmp(filedir_entry->d_name, "filedir") && filedir_entry->d_name[0] != '.') {
-#ifdef _DIRENT_HAVE_D_NAMELEN
-                       d_namelen = filedir_entry->d_namlen;
-#else
-                       d_namelen = strlen(filedir_entry->d_name);
-#endif
-                       snprintf(buf, sizeof buf, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, filedir_entry->d_name);
-                       stat(buf, &statbuf);    /* stat the file */
-                       if (!(statbuf.st_mode & S_IFREG)) {
-                               snprintf(buf2, sizeof buf2,
-                                       "\"%s\" appears in the file directory for room \"%s\" but is not a regular file.  Directories, named pipes, sockets, etc. are not usable in Citadel room directories.\n",
-                                       buf, CC->room.QRname
-                               );
-                               CtdlAideMessage(buf2, "Unusable data found in room directory");
-                               continue;       /* not a useable file type so don't show it */
-                       }
-                       safestrncpy(comment, "", sizeof comment);
-                       fseek(fd, 0L, 0);       /* rewind descriptions file */
-                       /* Get the description from the descriptions file */
-                       while ((fgets(buf, sizeof buf, fd) != NULL) && (IsEmptyStr(comment))) {
-                               buf[strlen(buf) - 1] = 0;
-                               if ((!strncasecmp(buf, filedir_entry->d_name, d_namelen)) && (buf[d_namelen] == ' '))
-                                       safestrncpy(comment, &buf[d_namelen + 1], sizeof comment);
-                       }
-                       len = extract_token (mimebuf, comment, 0,' ', 64);
-                       if ((len <0) || strchr(mimebuf, '/') == NULL) {
-                               snprintf (mimebuf, 64, "application/octetstream");
-                               len = 0;
-                       }
-                       cprintf("%s|%ld|%s|%s\n", 
-                               filedir_entry->d_name, 
-                               (long)statbuf.st_size, 
-                               mimebuf, 
-                               &comment[len]);
-               }
-       }
-       fclose(fd);
-       closedir(filedir);
-       
-       cprintf("000\n");
-}
-
-
-// get room parameters (admin or room admin command)
-void cmd_getr(char *cmdbuf) {
-       if (CtdlAccessCheck(ac_room_aide)) return;
-
-       CtdlGetRoom(&CC->room, CC->room.QRname);
-       cprintf("%d%c%s|%s|%s|%d|%d|%d|%d|%d|\n",
-               CIT_OK,
-               CtdlCheckExpress(),
-               ((CC->room.QRflags & QR_MAILBOX) ?  &CC->room.QRname[11] : CC->room.QRname),
-               ((CC->room.QRflags & QR_PASSWORDED) ?  CC->room.QRpasswd : ""),
-               ((CC->room.QRflags & QR_DIRECTORY) ?  CC->room.QRdirname : ""),
-               CC->room.QRflags,
-               (int) CC->room.QRfloor,
-               (int) CC->room.QRorder,
-               CC->room.QRdefaultview,
-               CC->room.QRflags2
-       );
-}
-
-
-// set room parameters (admin or room admin command)
-void cmd_setr(char *args) {
-       char buf[256];
-       int new_order = 0;
-       int r;
-       int new_floor;
-       char new_name[ROOMNAMELEN];
-
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       if (num_parms(args) >= 6) {
-               new_floor = extract_int(args, 5);
-       }
-       else {
-               new_floor = (-1);       /* don't change the floor */
-       }
-
-       /* When is a new name more than just a new name?  When the old name
-        * has a namespace prefix.
-        */
-       if (CC->room.QRflags & QR_MAILBOX) {
-               sprintf(new_name, "%010ld.", atol(CC->room.QRname) );
-       }
-       else {
-               safestrncpy(new_name, "", sizeof new_name);
-       }
-       extract_token(&new_name[strlen(new_name)], args, 0, '|', (sizeof new_name - strlen(new_name)));
-
-       r = CtdlRenameRoom(CC->room.QRname, new_name, new_floor);
-
-       if (r == crr_room_not_found) {
-               cprintf("%d Internal error - room not found?\n", ERROR + INTERNAL_ERROR);
-       }
-       else if (r == crr_already_exists) {
-               cprintf("%d '%s' already exists.\n",
-                       ERROR + ALREADY_EXISTS, new_name);
-       }
-       else if (r == crr_noneditable) {
-               cprintf("%d Cannot edit this room.\n", ERROR + NOT_HERE);
-       }
-       else if (r == crr_invalid_floor) {
-               cprintf("%d Target floor does not exist.\n",
-                       ERROR + INVALID_FLOOR_OPERATION);
-       }
-       else if (r == crr_access_denied) {
-               cprintf("%d You do not have permission to edit '%s'\n",
-                       ERROR + HIGHER_ACCESS_REQUIRED,
-                       CC->room.QRname);
-       }
-       else if (r != crr_ok) {
-               cprintf("%d Error: CtdlRenameRoom() returned %d\n",
-                       ERROR + INTERNAL_ERROR, r);
-       }
-
-       if (r != crr_ok) {
-               return;
-       }
-
-       CtdlGetRoom(&CC->room, new_name);
-
-       /* Now we have to do a bunch of other stuff */
-
-       if (num_parms(args) >= 7) {
-               new_order = extract_int(args, 6);
-               if (new_order < 1)
-                       new_order = 1;
-               if (new_order > 127)
-                       new_order = 127;
-       }
-
-       CtdlGetRoomLock(&CC->room, CC->room.QRname);
-
-       /* Directory room */
-       extract_token(buf, args, 2, '|', sizeof buf);
-       buf[15] = 0;
-       safestrncpy(CC->room.QRdirname, buf,
-               sizeof CC->room.QRdirname);
-
-       /* Default view */
-       if (num_parms(args) >= 8) {
-               CC->room.QRdefaultview = extract_int(args, 7);
-       }
-
-       /* Second set of flags */
-       if (num_parms(args) >= 9) {
-               CC->room.QRflags2 = extract_int(args, 8);
-       }
-
-       /* Misc. flags */
-       CC->room.QRflags = (extract_int(args, 3) | QR_INUSE);
-       /* Clean up a client boo-boo: if the client set the room to
-        * guess-name or passworded, ensure that the private flag is
-        * also set.
-        */
-       if ((CC->room.QRflags & QR_GUESSNAME)
-           || (CC->room.QRflags & QR_PASSWORDED))
-               CC->room.QRflags |= QR_PRIVATE;
-
-       /* Some changes can't apply to BASEROOM */
-       if (!strncasecmp(CC->room.QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
-               CC->room.QRorder = 0;
-               CC->room.QRpasswd[0] = '\0';
-               CC->room.QRflags &= ~(QR_PRIVATE & QR_PASSWORDED &
-                       QR_GUESSNAME & QR_PREFONLY & QR_MAILBOX);
-               CC->room.QRflags |= QR_PERMANENT;
-       }
-       else {  
-               /* March order (doesn't apply to AIDEROOM) */
-               if (num_parms(args) >= 7)
-                       CC->room.QRorder = (char) new_order;
-               /* Room password */
-               extract_token(buf, args, 1, '|', sizeof buf);
-               buf[10] = 0;
-               safestrncpy(CC->room.QRpasswd, buf, sizeof CC->room.QRpasswd);
-               /* Kick everyone out if the client requested it
-                * (by changing the room's generation number)
-                */
-               if (extract_int(args, 4)) {
-                       time(&CC->room.QRgen);
-               }
-       }
-       /* Some changes can't apply to AIDEROOM */
-       if (!strncasecmp(CC->room.QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
-               CC->room.QRorder = 0;
-               CC->room.QRflags &= ~QR_MAILBOX;
-               CC->room.QRflags |= QR_PERMANENT;
-       }
-
-       /* Write the room record back to disk */
-       CtdlPutRoomLock(&CC->room);
-
-       /* Create a room directory if necessary */
-       if (CC->room.QRflags & QR_DIRECTORY) {
-               snprintf(buf, sizeof buf,"%s/%s", ctdl_file_dir, CC->room.QRdirname);
-               mkdir(buf, 0755);
-       }
-       snprintf(buf, sizeof buf, "The room \"%s\" has been edited by %s.\n",
-               CC->room.QRname,
-               (CC->logged_in ? CC->curr_user : "an administrator")
-       );
-       CtdlAideMessage(buf, "Room modification Message");
-       cprintf("%d Ok\n", CIT_OK);
-}
-
-
-// get the name of the room admin for this room
-void cmd_geta(char *cmdbuf) {
-       struct ctdluser usbuf;
-
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       if (CtdlGetUserByNumber(&usbuf, CC->room.QRroomaide) == 0) {
-               cprintf("%d %s\n", CIT_OK, usbuf.fullname);
-       }
-       else {
-               cprintf("%d \n", CIT_OK);
-       }
-}
-
-
-// set the room admin for this room
-void cmd_seta(char *new_ra) {
-       struct ctdluser usbuf;
-       long newu;
-       char buf[SIZ];
-       int post_notice;
-
-       if (CtdlAccessCheck(ac_room_aide)) return;
-
-       if (CtdlGetUser(&usbuf, new_ra) != 0) {
-               newu = (-1L);
-       }
-       else {
-               newu = usbuf.usernum;
-       }
-
-       CtdlGetRoomLock(&CC->room, CC->room.QRname);
-       post_notice = 0;
-       if (CC->room.QRroomaide != newu) {
-               post_notice = 1;
-       }
-       CC->room.QRroomaide = newu;
-       CtdlPutRoomLock(&CC->room);
-
-       // We have to post the change notice _after_ writing changes to 
-       // the room table, otherwise it would deadlock!
-       if (post_notice == 1) {
-               if (!IsEmptyStr(usbuf.fullname))
-                       snprintf(buf, sizeof buf,
-                               "%s is now the room admin for \"%s\".\n",
-                               usbuf.fullname, CC->room.QRname);
-               else
-                       snprintf(buf, sizeof buf,
-                               "There is now no room admin for \"%s\".\n",
-                               CC->room.QRname);
-               CtdlAideMessage(buf, "Admin Room Modification");
-       }
-       cprintf("%d Ok\n", CIT_OK);
-}
-
-
-// Retrieve info file for this room (this ought to be upgraded to handle non-plain-text)
-void cmd_rinf(char *argbuf) {
-       struct CtdlMessage *msg = CtdlFetchMessage(CC->room.msgnum_info, 1);
-       if (msg != NULL) {
-               cprintf("%d Info:\n", LISTING_FOLLOWS);
-               CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_NONE, 0, 0, 0);
-               CM_Free(msg);
-               cprintf("000\n");
-       }
-       else {
-               cprintf("%d No info file.\n", ERROR + FILE_NOT_FOUND);
-       }
-}
-
-
-// admin command: kill the current room
-void cmd_kill(char *argbuf) {
-       char deleted_room_name[ROOMNAMELEN];
-       char msg[SIZ];
-       int kill_ok;
-
-       kill_ok = extract_int(argbuf, 0);
-
-       if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->room) == 0) {
-               cprintf("%d Can't delete this room.\n", ERROR + NOT_HERE);
-               return;
-       }
-       if (kill_ok) {
-               if (CC->room.QRflags & QR_MAILBOX) {
-                       safestrncpy(deleted_room_name, &CC->room.QRname[11], sizeof deleted_room_name);
-               }
-               else {
-                       safestrncpy(deleted_room_name, CC->room.QRname, sizeof deleted_room_name);
-               }
-
-               /* Do the dirty work */
-               CtdlScheduleRoomForDeletion(&CC->room);
-
-               /* Return to the Lobby */
-               CtdlUserGoto(CtdlGetConfigStr("c_baseroom"), 0, 0, NULL, NULL, NULL, NULL);
-
-               /* tell the world what we did */
-               snprintf(msg, sizeof msg, "The room \"%s\" has been deleted by %s.\n",
-                        deleted_room_name,
-                       (CC->logged_in ? CC->curr_user : "an administrator")
-               );
-               CtdlAideMessage(msg, "Room Purger Message");
-               cprintf("%d '%s' deleted.\n", CIT_OK, deleted_room_name);
-       }
-       else {
-               cprintf("%d ok to delete.\n", CIT_OK);
-       }
-}
-
-
-// create a new room
-void cmd_cre8(char *args) {
-       int cre8_ok;
-       char new_room_name[ROOMNAMELEN];
-       int new_room_type;
-       char new_room_pass[32];
-       int new_room_floor;
-       int new_room_view;
-       char *notification_message = NULL;
-       unsigned newflags;
-       struct floor *fl;
-       int avoid_access = 0;
-
-       cre8_ok = extract_int(args, 0);
-       extract_token(new_room_name, args, 1, '|', sizeof new_room_name);
-       new_room_name[ROOMNAMELEN - 1] = 0;
-       new_room_type = extract_int(args, 2);
-       extract_token(new_room_pass, args, 3, '|', sizeof new_room_pass);
-       avoid_access = extract_int(args, 5);
-       new_room_view = extract_int(args, 6);
-       new_room_pass[9] = 0;
-       new_room_floor = 0;
-
-       if ((IsEmptyStr(new_room_name)) && (cre8_ok == 1)) {
-               cprintf("%d Invalid room name.\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       if (!strcasecmp(new_room_name, MAILROOM)) {
-               cprintf("%d '%s' already exists.\n",
-                       ERROR + ALREADY_EXISTS, new_room_name);
-               return;
-       }
-
-       if (num_parms(args) >= 5) {
-               fl = CtdlGetCachedFloor(extract_int(args, 4));
-               if (fl == NULL) {
-                       cprintf("%d Invalid floor number.\n",
-                               ERROR + INVALID_FLOOR_OPERATION);
-                       return;
-               }
-               else if ((fl->f_flags & F_INUSE) == 0) {
-                       cprintf("%d Invalid floor number.\n",
-                               ERROR + INVALID_FLOOR_OPERATION);
-                       return;
-               } else {
-                       new_room_floor = extract_int(args, 4);
-               }
-       }
-
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       if (CC->user.axlevel < CtdlGetConfigInt("c_createax") && !CC->internal_pgm) {
-               cprintf("%d You need higher access to create rooms.\n",
-                       ERROR + HIGHER_ACCESS_REQUIRED);
-               return;
-       }
-
-       if ((IsEmptyStr(new_room_name)) && (cre8_ok == 0)) {
-               cprintf("%d Ok to create rooms.\n", CIT_OK);
-               return;
-       }
-
-       if ((new_room_type < 0) || (new_room_type > 5)) {
-               cprintf("%d Invalid room type.\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       if (new_room_type == 5) {
-               if (CC->user.axlevel < AxAideU) {
-                       cprintf("%d Higher access required\n", 
-                               ERROR + HIGHER_ACCESS_REQUIRED);
-                       return;
-               }
-       }
-
-       /* Check to make sure the requested room name doesn't already exist */
-       newflags = CtdlCreateRoom(new_room_name,
-                               new_room_type, new_room_pass, new_room_floor,
-                               0, avoid_access, new_room_view);
-       if (newflags == 0) {
-               cprintf("%d '%s' already exists.\n",
-                       ERROR + ALREADY_EXISTS, new_room_name);
-               return;
-       }
-
-       if (cre8_ok == 0) {
-               cprintf("%d OK to create '%s'\n", CIT_OK, new_room_name);
-               return;
-       }
-
-       /* If we reach this point, the room needs to be created. */
-
-       newflags = CtdlCreateRoom(new_room_name,
-                          new_room_type, new_room_pass, new_room_floor, 1, 0,
-                          new_room_view);
-
-       /* post a message in Aide> describing the new room */
-       notification_message = malloc(1024);
-       snprintf(notification_message, 1024,
-               "A new room called \"%s\" has been created by %s%s%s%s%s%s\n",
-               new_room_name,
-               (CC->logged_in ? CC->curr_user : "an administrator"),
-               ((newflags & QR_MAILBOX) ? " [personal]" : ""),
-               ((newflags & QR_PRIVATE) ? " [private]" : ""),
-               ((newflags & QR_GUESSNAME) ? " [hidden]" : ""),
-               ((newflags & QR_PASSWORDED) ? " Password: " : ""),
-               ((newflags & QR_PASSWORDED) ? new_room_pass : "")
-       );
-       CtdlAideMessage(notification_message, "Room Creation Message");
-       free(notification_message);
-
-       cprintf("%d '%s' has been created.\n", CIT_OK, new_room_name);
-}
-
-
-// Upload the room banner text for this room.
-// This should be amended to handle content types other than plain text.
-void cmd_einf(char *ok) {                              /* enter info file for current room */
-       char buf[SIZ];
-       unbuffer_output();
-
-       if (CtdlAccessCheck(ac_room_aide)) return;
-
-       if (atoi(ok) == 0) {
-               cprintf("%d Ok.\n", CIT_OK);
-               return;
-       }
-
-       StrBuf *NewBanner = NewStrBufPlain("Content-type: text/plain; charset=UTF-8\nContent-transfer-encoding: 8bit\n\n", -1);
-
-       cprintf("%d Transmit new banner in plain text now.\n", SEND_LISTING);
-       while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
-               StrBufAppendBufPlain(NewBanner, buf, -1, 0);
-               StrBufAppendBufPlain(NewBanner, HKEY("\n"), 0);
-       }
-
-       // We have read the new banner from the user , now save it
-       long new_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, ChrPtr(NewBanner), FMT_RFC822, "Banner submitted with EINF command");
-       FreeStrBuf(&NewBanner);
-
-       // Update the room record with a pointer to our new banner
-       CtdlGetRoomLock(&CC->room, CC->room.QRname);
-       long old_msgnum = CC->room.msgnum_info;
-       CC->room.msgnum_info = new_msgnum;
-       CtdlPutRoomLock(&CC->room);
-
-       // Delete the old one
-       CtdlDeleteMessages(SYSCONFIGROOM, &old_msgnum, 1, "");
-}
-
-
-// cmd_lflr()   -  List all known floors
-void cmd_lflr(char *gargs) {
-       int a;
-       struct floor flbuf;
-
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
-       cprintf("%d Known floors:\n", LISTING_FOLLOWS);
-
-       for (a = 0; a < MAXFLOORS; ++a) {
-               CtdlGetFloor(&flbuf, a);
-               if (flbuf.f_flags & F_INUSE) {
-                       cprintf("%d|%s|%d\n", a, flbuf.f_name, flbuf.f_ref_count);
-               }
-       }
-       cprintf("000\n");
-}
-
-
-// create a new floor
-void cmd_cflr(char *argbuf) {
-       char new_floor_name[256];
-       struct floor flbuf;
-       int cflr_ok;
-       int free_slot = (-1);
-       int a;
-
-       extract_token(new_floor_name, argbuf, 0, '|', sizeof new_floor_name);
-       cflr_ok = extract_int(argbuf, 1);
-
-       if (CtdlAccessCheck(ac_aide)) return;
-
-       if (IsEmptyStr(new_floor_name)) {
-               cprintf("%d Blank floor name not allowed.\n",
-                       ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       for (a = 0; a < MAXFLOORS; ++a) {
-               CtdlGetFloor(&flbuf, a);
-
-               /* note any free slots while we're scanning... */
-               if (((flbuf.f_flags & F_INUSE) == 0)
-                   && (free_slot < 0))
-                       free_slot = a;
-
-               /* check to see if it already exists */
-               if ((!strcasecmp(flbuf.f_name, new_floor_name))
-                   && (flbuf.f_flags & F_INUSE)) {
-                       cprintf("%d Floor '%s' already exists.\n",
-                               ERROR + ALREADY_EXISTS,
-                               flbuf.f_name);
-                       return;
-               }
-       }
-
-       if (free_slot < 0) {
-               cprintf("%d There is no space available for a new floor.\n",
-                       ERROR + INVALID_FLOOR_OPERATION);
-               return;
-       }
-       if (cflr_ok == 0) {
-               cprintf("%d ok to create...\n", CIT_OK);
-               return;
-       }
-       lgetfloor(&flbuf, free_slot);
-       flbuf.f_flags = F_INUSE;
-       flbuf.f_ref_count = 0;
-       safestrncpy(flbuf.f_name, new_floor_name, sizeof flbuf.f_name);
-       lputfloor(&flbuf, free_slot);
-       cprintf("%d %d\n", CIT_OK, free_slot);
-}
-
-
-// delete a floor
-void cmd_kflr(char *argbuf) {
-       struct floor flbuf;
-       int floor_to_delete;
-       int kflr_ok;
-       int delete_ok;
-
-       floor_to_delete = extract_int(argbuf, 0);
-       kflr_ok = extract_int(argbuf, 1);
-
-       if (CtdlAccessCheck(ac_aide)) return;
-
-       lgetfloor(&flbuf, floor_to_delete);
-
-       delete_ok = 1;
-       if ((flbuf.f_flags & F_INUSE) == 0) {
-               cprintf("%d Floor %d not in use.\n", ERROR + INVALID_FLOOR_OPERATION, floor_to_delete);
-               delete_ok = 0;
-       } else {
-               if (flbuf.f_ref_count != 0) {
-                       cprintf("%d Cannot delete; floor contains %d rooms.\n",
-                               ERROR + INVALID_FLOOR_OPERATION,
-                               flbuf.f_ref_count);
-                       delete_ok = 0;
-               }
-               else {
-                       if (kflr_ok == 1) {
-                               cprintf("%d Ok\n", CIT_OK);
-                       }
-                       else {
-                               cprintf("%d Ok to delete...\n", CIT_OK);
-                       }
-
-               }
-
-       }
-
-       if ((delete_ok == 1) && (kflr_ok == 1)) {
-               flbuf.f_flags = 0;
-       }
-       lputfloor(&flbuf, floor_to_delete);
-}
-
-
-// edit a floor
-void cmd_eflr(char *argbuf) {
-       struct floor flbuf;
-       int floor_num;
-       int np;
-
-       np = num_parms(argbuf);
-       if (np < 1) {
-               cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       if (CtdlAccessCheck(ac_aide)) return;
-
-       floor_num = extract_int(argbuf, 0);
-       lgetfloor(&flbuf, floor_num);
-       if ((flbuf.f_flags & F_INUSE) == 0) {
-               lputfloor(&flbuf, floor_num);
-               cprintf("%d Floor %d is not in use.\n", ERROR + INVALID_FLOOR_OPERATION, floor_num);
-               return;
-       }
-       if (np >= 2) {
-               extract_token(flbuf.f_name, argbuf, 1, '|', sizeof flbuf.f_name);
-       }
-       lputfloor(&flbuf, floor_num);
-
-       cprintf("%d Ok\n", CIT_OK);
-}
-
-
-// cmd_stat()  -  return the modification time of the current room (maybe other things in the future)
-void cmd_stat(char *gargs) {
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-       CtdlGetRoom(&CC->room, CC->room.QRname);
-       cprintf("%d %s|%ld|\n", CIT_OK, CC->room.QRname, CC->room.QRmtime);
-}
-
-
-/*****************************************************************************/
-/*                      MODULE INITIALIZATION STUFF                          */
-/*****************************************************************************/
-
-CTDL_MODULE_INIT(rooms)
-{
-       if (!threading) {
-               CtdlRegisterProtoHook(cmd_lrms, "LRMS", "List rooms");
-               CtdlRegisterProtoHook(cmd_lkra, "LKRA", "List all known rooms");
-               CtdlRegisterProtoHook(cmd_lkrn, "LKRN", "List known rooms with new messages");
-               CtdlRegisterProtoHook(cmd_lkro, "LKRO", "List known rooms without new messages");
-               CtdlRegisterProtoHook(cmd_lzrm, "LZRM", "List zapped rooms");
-               CtdlRegisterProtoHook(cmd_lprm, "LPRM", "List public rooms");
-               CtdlRegisterProtoHook(cmd_goto, "GOTO", "Goto a named room");
-               CtdlRegisterProtoHook(cmd_stat, "STAT", "Get mtime of the current room");
-               CtdlRegisterProtoHook(cmd_whok, "WHOK", "List users who know this room");
-               CtdlRegisterProtoHook(cmd_rdir, "RDIR", "List files in room directory");
-               CtdlRegisterProtoHook(cmd_getr, "GETR", "Get room parameters");
-               CtdlRegisterProtoHook(cmd_setr, "SETR", "Set room parameters");
-               CtdlRegisterProtoHook(cmd_geta, "GETA", "Get the room admin name");
-               CtdlRegisterProtoHook(cmd_seta, "SETA", "Set the room admin for this room");
-               CtdlRegisterProtoHook(cmd_rinf, "RINF", "Fetch room info file");
-               CtdlRegisterProtoHook(cmd_kill, "KILL", "Kill (delete) the current room");
-               CtdlRegisterProtoHook(cmd_cre8, "CRE8", "Create a new room");
-               CtdlRegisterProtoHook(cmd_einf, "EINF", "Enter info file for the current room");
-               CtdlRegisterProtoHook(cmd_lflr, "LFLR", "List all known floors");
-               CtdlRegisterProtoHook(cmd_cflr, "CFLR", "Create a new floor");
-               CtdlRegisterProtoHook(cmd_kflr, "KFLR", "Kill a floor");
-               CtdlRegisterProtoHook(cmd_eflr, "EFLR", "Edit a floor");
-       }
-        /* return our Subversion id for the Log */
-       return "rooms";
-}
diff --git a/citadel/modules/ctdlproto/serv_session.c b/citadel/modules/ctdlproto/serv_session.c
deleted file mode 100644 (file)
index 697cc93..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-/* 
- * Server functions which perform operations on user objects.
- *
- * Copyright (c) 1987-2020 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include <stdio.h>
-#include <libcitadel.h>
-#include "citserver.h"
-#include "svn_revision.h"
-#include "ctdl_module.h"
-#include "config.h"
-
-
-void cmd_noop(char *argbuf)
-{
-       cprintf("%d%cok\n", CIT_OK, CtdlCheckExpress() );
-}
-
-
-void cmd_qnop(char *argbuf)
-{
-       /* do nothing, this command returns no response */
-}
-
-
-/*
- * Set or unset asynchronous protocol mode
- */
-void cmd_asyn(char *argbuf)
-{
-       int new_state;
-
-       new_state = extract_int(argbuf, 0);
-       if ((new_state == 0) || (new_state == 1)) {
-               CC->is_async = new_state;
-       }
-       cprintf("%d %d\n", CIT_OK, CC->is_async);
-}
-
-
-/*
- * cmd_info()  -  Identify this server and its capabilities to the client
- */
-void cmd_info(char *cmdbuf) {
-       cprintf("%d Server info:\n", LISTING_FOLLOWS);
-       cprintf("%d\n", CC->cs_pid);
-       cprintf("%s\n", CtdlGetConfigStr("c_nodename"));
-       cprintf("%s\n", CtdlGetConfigStr("c_humannode"));
-       cprintf("%s\n", CtdlGetConfigStr("c_fqdn"));
-       cprintf("%s\n", CITADEL);
-       cprintf("%d\n", REV_LEVEL);
-       cprintf("%s\n", CtdlGetConfigStr("c_site_location"));
-       cprintf("%s\n", CtdlGetConfigStr("c_sysadm"));
-       cprintf("%d\n", SERVER_TYPE);
-       cprintf("%s\n", CtdlGetConfigStr("c_moreprompt"));
-       cprintf("1\n"); /* 1 = yes, this system supports floors */
-       cprintf("1\n"); /* 1 = we support the extended paging options */
-       cprintf("\n");  /* no longer used */
-       cprintf("1\n"); /* 1 = yes, this system supports the QNOP command */
-       cprintf("1\n"); /* 1 = yes, this server is LDAP-enabled */
-
-       if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_NATIVE) && (CtdlGetConfigInt("c_disable_newu") == 0))
-       {
-               cprintf("%d\n", CtdlGetConfigInt("c_disable_newu"));
-       }
-       else {
-               cprintf("1\n"); /* "create new user" does not work with non-native auth modes */
-       }
-
-       cprintf("%s\n", CtdlGetConfigStr("c_default_cal_zone"));
-
-       cprintf("0\n"); /* no longer used */
-       cprintf("0\n"); /* no longer used */
-       cprintf("0\n"); /* no longer used */
-       cprintf("0\n"); /* no longer used */
-
-       cprintf("%d\n", CtdlGetConfigInt("c_enable_fulltext"));
-       cprintf("%s\n", svn_revision());
-
-       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_NATIVE) {
-               cprintf("%d\n", openid_level_supported); /* OpenID is enabled when using native auth */
-       }
-       else {
-               cprintf("0\n"); /* OpenID is disabled when using non-native auth */
-       }
-
-       cprintf("%d\n", CtdlGetConfigInt("c_guest_logins"));
-       cprintf("000\n");
-}
-
-
-/*
- * echo 
- */
-void cmd_echo(char *etext)
-{
-       cprintf("%d %s\n", CIT_OK, etext);
-}
-
-
-/* 
- * get the paginator prompt
- */
-void cmd_more(char *argbuf) {
-       cprintf("%d %s\n", CIT_OK, CtdlGetConfigStr("c_moreprompt"));
-}
-
-
-/*
- * the client is identifying itself to the server
- */
-void cmd_iden(char *argbuf)
-{
-       int dev_code;
-       int cli_code;
-       int rev_level;
-       char desc[128];
-       char from_host[128];
-
-       if (num_parms(argbuf)<4) {
-               cprintf("%d usage error\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       dev_code = extract_int(argbuf,0);
-       cli_code = extract_int(argbuf,1);
-       rev_level = extract_int(argbuf,2);
-       extract_token(desc, argbuf, 3, '|', sizeof desc);
-
-       safestrncpy(from_host, CtdlGetConfigStr("c_fqdn"), sizeof from_host);
-       from_host[sizeof from_host - 1] = 0;
-       if (num_parms(argbuf)>=5) extract_token(from_host, argbuf, 4, '|', sizeof from_host);
-
-       CC->cs_clientdev = dev_code;
-       CC->cs_clienttyp = cli_code;
-       CC->cs_clientver = rev_level;
-       safestrncpy(CC->cs_clientname, desc, sizeof CC->cs_clientname);
-       CC->cs_clientname[31] = 0;
-
-       /* For local sockets, allow the client to supply the user's origin address */
-       if ((CC->is_local_client) || (!IsEmptyStr(CC->cs_addr) && (!strcmp(CC->cs_addr, "127.0.0.1")) || (!strcmp(CC->cs_addr, "::1")))) {
-               safestrncpy(CC->cs_host, from_host, sizeof CC->cs_host);
-               CC->cs_host[sizeof CC->cs_host - 1] = 0;
-               CC->cs_addr[0] = 0;
-       }
-
-       syslog(LOG_NOTICE, "session: client %d/%d/%01d.%02d (%s) from %s",
-               dev_code,
-               cli_code,
-               (rev_level / 100),
-               (rev_level % 100),
-               desc,
-               CC->cs_host
-       );
-       cprintf("%d Ok\n",CIT_OK);
-}
-
-
-/*
- * Terminate another running session
- */
-void cmd_term(char *cmdbuf)
-{
-       int session_num = extract_int(cmdbuf, 0);
-       int terminated = CtdlTerminateOtherSession(session_num);
-
-       if (terminated < 0) {
-               cprintf("%d You can't kill your own session.\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       if (terminated & TERM_FOUND) {
-               if (terminated == TERM_KILLED) {
-                       cprintf("%d Session terminated.\n", CIT_OK);
-               }
-               else {
-                       cprintf("%d You are not allowed to do that.\n", ERROR + HIGHER_ACCESS_REQUIRED);
-               }
-       }
-       else {
-               cprintf("%d No such session.\n", ERROR + ILLEGAL_VALUE);
-       }
-}
-
-
-void cmd_time(char *argbuf)
-{
-       time_t tv;
-       struct tm tmp;
-
-       tv = time(NULL);
-       localtime_r(&tv, &tmp);
-
-       /* timezone and daylight global variables are not portable. */
-#ifdef HAVE_STRUCT_TM_TM_GMTOFF
-       cprintf("%d %ld|%ld|%d|%ld\n", CIT_OK, (long)tv, tmp.tm_gmtoff, tmp.tm_isdst, server_startup_time);
-#else
-       cprintf("%d %ld|%ld|%d|%ld\n", CIT_OK, (long)tv, timezone, tmp.tm_isdst, server_startup_time);
-#endif
-}
-
-
-/*****************************************************************************/
-/*                      MODULE INITIALIZATION STUFF                          */
-/*****************************************************************************/
-
-CTDL_MODULE_INIT(serv_session)
-{
-       if (!threading) {
-               CtdlRegisterProtoHook(cmd_noop, "NOOP", "no operation");
-               CtdlRegisterProtoHook(cmd_qnop, "QNOP", "no operation with no response");
-               CtdlRegisterProtoHook(cmd_asyn, "ASYN", "enable asynchronous server responses");
-               CtdlRegisterProtoHook(cmd_info, "INFO", "fetch server capabilities and configuration");
-               CtdlRegisterProtoHook(cmd_echo, "ECHO", "echo text back to the client");
-               CtdlRegisterProtoHook(cmd_more, "MORE", "fetch the paginator prompt");
-               CtdlRegisterProtoHook(cmd_iden, "IDEN", "identify the client software and location");
-               CtdlRegisterProtoHook(cmd_term, "TERM", "terminate another running session");
-               CtdlRegisterProtoHook(cmd_time, "TIME", "fetch the date and time from the server");
-       }
-        /* return our id for the Log */
-       return "serv_session";
-}
diff --git a/citadel/modules/ctdlproto/serv_syscmds.c b/citadel/modules/ctdlproto/serv_syscmds.c
deleted file mode 100644 (file)
index 28e2ea6..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-// System management commands for Citadel server
-//
-// Copyright (c) 1987-2021 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include <stdio.h>
-#include <libcitadel.h>
-
-#include "serv_extensions.h"
-#include "ctdl_module.h"
-
-
-// Shut down or restart the server
-void cmd_down(char *argbuf) {
-       char *Reply ="%d Shutting down server.  Goodbye.\n";
-
-       if (CtdlAccessCheck(ac_aide)) return;
-
-       if (!IsEmptyStr(argbuf)) {
-               int state = CIT_OK;
-               restart_server = extract_int(argbuf, 0);
-               
-               if (restart_server > 0) {
-                       Reply = "%d citserver will now shut down and automatically restart.\n";
-               }
-               cprintf(Reply, state);
-       }
-       else {
-               cprintf(Reply, CIT_OK + SERVER_SHUTTING_DOWN); 
-       }
-       CC->kill_me = KILLME_SERVER_SHUTTING_DOWN;
-       server_shutting_down = 1;
-}
-
-
-// Halt the server without exiting the server process.
-void cmd_halt(char *argbuf) {
-       if (CtdlAccessCheck(ac_aide)) return;
-
-       cprintf("%d Halting server.  Goodbye.\n", CIT_OK);
-       server_shutting_down = 1;
-       shutdown_and_halt = 1;
-}
-
-
-// Schedule or cancel a server shutdown
-void cmd_scdn(char *argbuf) {
-       int new_state;
-       int state = CIT_OK;
-       char *Reply = "%d %d\n";
-
-       if (CtdlAccessCheck(ac_aide)) return;
-
-       new_state = extract_int(argbuf, 0);
-       if ((new_state == 2) || (new_state == 3)) {
-               restart_server = 1;
-               restart_server = extract_int(argbuf, 0);
-               new_state -= 2;
-       }
-       if ((new_state == 0) || (new_state == 1)) {
-               ScheduledShutdown = new_state;
-       }
-       cprintf(Reply, state, ScheduledShutdown);
-}
-
-
-// ****************************************************************************
-// *                     MODULE INITIALIZATION STUFF                          *
-// ****************************************************************************
-
-CTDL_MODULE_INIT(syscmd)
-{
-       if (!threading) {
-               CtdlRegisterProtoHook(cmd_down, "DOWN", "perform a server shutdown");
-               CtdlRegisterProtoHook(cmd_halt, "HALT", "halt the server without exiting the server process");
-               CtdlRegisterProtoHook(cmd_scdn, "SCDN", "schedule or cancel a server shutdown");
-       }
-        // return our id for the log
-       return "syscmd";
-}
diff --git a/citadel/modules/ctdlproto/serv_user.c b/citadel/modules/ctdlproto/serv_user.c
deleted file mode 100644 (file)
index 7e95335..0000000
+++ /dev/null
@@ -1,815 +0,0 @@
-// Server functions which perform operations on user objects.
-//
-// Copyright (c) 1987-2022 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-
-#include "support.h"
-#include "control.h"
-#include "ctdl_module.h"
-#include "citserver.h"
-#include "config.h"
-#include "user_ops.h"
-#include "internet_addressing.h"
-
-
-// USER command -- attempt to log in as an existing user
-void cmd_user(char *cmdbuf) {
-       char username[256];
-       int a;
-
-       extract_token(username, cmdbuf, 0, '|', sizeof username);
-       striplt(username);
-       syslog(LOG_DEBUG, "user_ops: cmd_user(%s)", username);
-
-       a = CtdlLoginExistingUser(username);
-       switch (a) {
-       case login_already_logged_in:
-               cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN);
-               return;
-       case login_too_many_users:
-               cprintf("%d %s: Too many users are already online (maximum is %d)\n",
-                       ERROR + MAX_SESSIONS_EXCEEDED,
-                       CtdlGetConfigStr("c_nodename"), CtdlGetConfigInt("c_maxsessions")
-               );
-               return;
-       case login_ok:
-               cprintf("%d Password required for %s\n", MORE_DATA, CC->curr_user);
-               return;
-       case login_not_found:
-               cprintf("%d %s not found.\n", ERROR + NO_SUCH_USER, username);
-               return;
-       default:
-               cprintf("%d Internal error\n", ERROR + INTERNAL_ERROR);
-       }
-}
-
-
-// PASS command -- complete logging in as an existing user (used after USER returns MORE_DATA)
-void cmd_pass(char *buf) {
-       char password[SIZ];
-       int a;
-       long len;
-
-       memset(password, 0, sizeof(password));
-       len = extract_token(password, buf, 0, '|', sizeof password);
-       a = CtdlTryPassword(password, len);
-
-       switch (a) {
-       case pass_already_logged_in:
-               cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN);
-               return;
-       case pass_no_user:
-               cprintf("%d You must send a name with USER first.\n", ERROR + USERNAME_REQUIRED);
-               return;
-       case pass_wrong_password:
-               cprintf("%d Wrong password.\n", ERROR + PASSWORD_REQUIRED);
-               return;
-       case pass_ok:
-               logged_in_response();
-               return;
-       }
-}
-
-
-// cmd_newu()  -  create a new user account and log in as that user
-void cmd_newu(char *cmdbuf) {
-       int a;
-       char username[SIZ];
-
-       if (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) {
-               cprintf("%d This system does not use native mode authentication.\n",
-                       ERROR + NOT_HERE);
-               return;
-       }
-
-       if (CtdlGetConfigInt("c_disable_newu")) {
-               cprintf("%d Self-service user account creation is disabled on this system.\n", ERROR + NOT_HERE);
-               return;
-       }
-
-       if (CC->logged_in) {
-               cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN);
-               return;
-       }
-       if (CC->nologin) {
-               cprintf("%d %s: Too many users are already online (maximum is %d)\n",
-                       ERROR + MAX_SESSIONS_EXCEEDED,
-                       CtdlGetConfigStr("c_nodename"), CtdlGetConfigInt("c_maxsessions"));
-               return;
-       }
-       extract_token(username, cmdbuf, 0, '|', sizeof username);
-       strproc(username);
-
-       if (IsEmptyStr(username)) {
-               cprintf("%d You must supply a user name.\n", ERROR + USERNAME_REQUIRED);
-               return;
-       }
-
-       if ((!strcasecmp(username, "bbs")) ||
-           (!strcasecmp(username, "new")) ||
-           (!strcasecmp(username, "."))
-       ) {
-               cprintf("%d '%s' is an invalid login name.\n", ERROR + ILLEGAL_VALUE, username);
-               return;
-       }
-
-       a = create_user(username, CREATE_USER_BECOME_USER, NATIVE_AUTH_UID);
-
-       if (a == 0) {
-               logged_in_response();
-       }
-       else if (a == ERROR + ALREADY_EXISTS) {
-               cprintf("%d '%s' already exists.\n",
-                       ERROR + ALREADY_EXISTS, username);
-               return;
-       }
-       else if (a == ERROR + INTERNAL_ERROR) {
-               cprintf("%d Internal error - user record disappeared?\n",
-                       ERROR + INTERNAL_ERROR);
-               return;
-       }
-       else {
-               cprintf("%d unknown error\n", ERROR + INTERNAL_ERROR);
-       }
-}
-
-
-// set password - citadel protocol implementation
-void cmd_setp(char *new_pw) {
-       if (CtdlAccessCheck(ac_logged_in)) {
-               return;
-       }
-       if ( (CC->user.uid != CTDLUID) && (CC->user.uid != (-1)) ) {
-               cprintf("%d Not allowed.  Use the 'passwd' command.\n", ERROR + NOT_HERE);
-               return;
-       }
-
-       if (!strcasecmp(new_pw, "GENERATE_RANDOM_PASSWORD")) {
-               char random_password[17];
-               snprintf(random_password, sizeof random_password, "%08lx%08lx", random(), random());
-               CtdlSetPassword(random_password);
-               cprintf("%d %s\n", CIT_OK, random_password);
-       }
-       else {
-               strproc(new_pw);
-               if (IsEmptyStr(new_pw)) {
-                       cprintf("%d Password unchanged.\n", CIT_OK);
-                       return;
-               }
-               CtdlSetPassword(new_pw);
-               cprintf("%d Password changed.\n", CIT_OK);
-       }
-}
-
-
-// cmd_creu() - administratively create a new user account (do not log in to it)
-void cmd_creu(char *cmdbuf) {
-       int a;
-       char username[SIZ];
-       char password[SIZ];
-       struct ctdluser tmp;
-
-       if (CtdlAccessCheck(ac_aide)) {
-               return;
-       }
-
-       extract_token(username, cmdbuf, 0, '|', sizeof username);
-       strproc(username);
-       strproc(password);
-       if (IsEmptyStr(username)) {
-               cprintf("%d You must supply a user name.\n", ERROR + USERNAME_REQUIRED);
-               return;
-       }
-
-       extract_token(password, cmdbuf, 1, '|', sizeof password);
-
-       a = create_user(username, CREATE_USER_DO_NOT_BECOME_USER, NATIVE_AUTH_UID);
-
-       if (a == 0) {
-               if (!IsEmptyStr(password)) {
-                       CtdlGetUserLock(&tmp, username);
-                       safestrncpy(tmp.password, password, sizeof(tmp.password));
-                       CtdlPutUserLock(&tmp);
-               }
-               cprintf("%d User '%s' created %s.\n", CIT_OK, username, (!IsEmptyStr(password)) ? "and password set" : "with no password");
-               return;
-       }
-       else if (a == ERROR + ALREADY_EXISTS) {
-               cprintf("%d '%s' already exists.\n", ERROR + ALREADY_EXISTS, username);
-               return;
-       }
-       else if ( (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) && (a == ERROR + NO_SUCH_USER) ) {
-               cprintf("%d User accounts are not created within Citadel in host authentication mode.\n", ERROR + NO_SUCH_USER);
-               return;
-       }
-       else {
-               cprintf("%d An error occurred creating the user account.\n", ERROR + INTERNAL_ERROR);
-       }
-}
-
-
-// get user parameters
-void cmd_getu(char *cmdbuf) {
-       if (CtdlAccessCheck(ac_logged_in)) {
-               return;
-       }
-
-       CtdlGetUser(&CC->user, CC->curr_user);
-       cprintf("%d 80|24|%d|\n", CIT_OK, (CC->user.flags & US_USER_SET));
-}
-
-
-// set user parameters
-void cmd_setu(char *new_parms) {
-       if (CtdlAccessCheck(ac_logged_in)) {
-               return;
-       }
-
-       if (num_parms(new_parms) < 3) {
-               cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-       CtdlLockGetCurrentUser();
-       CC->user.flags = CC->user.flags & (~US_USER_SET);
-       CC->user.flags = CC->user.flags | (extract_int(new_parms, 2) & US_USER_SET);
-       CtdlPutCurrentUserLock();
-       cprintf("%d Ok\n", CIT_OK);
-}
-
-
-// set last read pointer (marks all messages in the current room as read, up to the specified point)
-void cmd_slrp(char *new_ptr) {
-       long newlr;
-       visit vbuf;
-       visit original_vbuf;
-
-       if (CtdlAccessCheck(ac_logged_in)) {
-               return;
-       }
-
-       if (!strncasecmp(new_ptr, "highest", 7)) {
-               newlr = CC->room.QRhighest;
-       }
-       else {
-               newlr = atol(new_ptr);
-       }
-
-       CtdlLockGetCurrentUser();
-
-       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
-       memcpy(&original_vbuf, &vbuf, sizeof(visit));
-       vbuf.v_lastseen = newlr;
-       snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld", newlr);
-
-       // Only rewrite the record if it changed
-       if (    (vbuf.v_lastseen != original_vbuf.v_lastseen)
-               || (strcmp(vbuf.v_seen, original_vbuf.v_seen))
-       ) {
-               CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
-       }
-
-       CtdlPutCurrentUserLock();
-       cprintf("%d %ld\n", CIT_OK, newlr);
-}
-
-
-void cmd_seen(char *argbuf) {
-       long target_msgnum = 0L;
-       int target_setting = 0;
-
-       if (CtdlAccessCheck(ac_logged_in)) {
-               return;
-       }
-
-       if (num_parms(argbuf) != 2) {
-               cprintf("%d Invalid parameters\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       target_msgnum = extract_long(argbuf, 0);
-       target_setting = extract_int(argbuf, 1);
-
-       CtdlSetSeen(&target_msgnum, 1, target_setting,
-                       ctdlsetseen_seen, NULL, NULL);
-       cprintf("%d OK\n", CIT_OK);
-}
-
-
-void cmd_gtsn(char *argbuf) {
-       visit vbuf;
-
-       if (CtdlAccessCheck(ac_logged_in)) {
-               return;
-       }
-
-       // Learn about the user and room in question
-       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
-
-       cprintf("%d ", CIT_OK);
-       client_write(vbuf.v_seen, strlen(vbuf.v_seen));
-       client_write(HKEY("\n"));
-}
-
-
-// INVT and KICK commands (grant/revoke access to an invitation-only room)
-void cmd_invt_kick(char *iuser, int op) {
-
-       // These commands are only allowed by admins, room admins,
-       // and room namespace owners
-       if (is_room_aide()) {
-               // access granted
-       }
-       else if ( ((atol(CC->room.QRname) == CC->user.usernum) ) && (CC->user.usernum != 0) ) {
-               // access granted
-       }
-       else {
-               // access denied
-               cprintf("%d Higher access or room ownership required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
-               return;
-       }
-
-       if (!strncasecmp(CC->room.QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
-               cprintf("%d Can't add/remove users from this room.\n", ERROR + NOT_HERE);
-               return;
-       }
-
-       if (CtdlInvtKick(iuser, op) != 0) {
-               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
-               return;
-       }
-
-       cprintf("%d %s %s %s.\n",
-               CIT_OK, iuser,
-               ((op == 1) ? "invited to" : "kicked out of"),
-               CC->room.QRname);
-       return;
-}
-
-
-void cmd_invt(char *iuser) {
-       cmd_invt_kick(iuser, 1);
-}
-
-
-void cmd_kick(char *iuser) {
-       cmd_invt_kick(iuser, 0);
-}
-
-
-// forget (Zap) the current room
-void cmd_forg(char *argbuf) {
-
-       if (CtdlAccessCheck(ac_logged_in)) {
-               return;
-       }
-
-       if (CtdlForgetThisRoom() == 0) {
-               cprintf("%d Ok\n", CIT_OK);
-       }
-       else {
-               cprintf("%d You may not forget this room.\n", ERROR + NOT_HERE);
-       }
-}
-
-
-// Get Next Unregistered User
-void cmd_gnur(char *argbuf) {
-       struct cdbdata *cdbus;
-       struct ctdluser usbuf;
-
-       if (CtdlAccessCheck(ac_aide)) {
-               return;
-       }
-
-       if ((CtdlGetConfigInt("MMflags") & MM_VALID) == 0) {
-               cprintf("%d There are no unvalidated users.\n", CIT_OK);
-               return;
-       }
-
-       // There are unvalidated users.  Traverse the user database, and return the first user we find that needs validation.
-       cdb_rewind(CDB_USERS);
-       while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
-               memset(&usbuf, 0, sizeof(struct ctdluser));
-               memcpy(&usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ?  sizeof(struct ctdluser) : cdbus->len));
-               cdb_free(cdbus);
-               if ((usbuf.flags & US_NEEDVALID) && (usbuf.axlevel > AxDeleted)) {
-                       cprintf("%d %s\n", MORE_DATA, usbuf.fullname);
-                       cdb_close_cursor(CDB_USERS);
-                       return;
-               }
-       }
-
-       // If we get to this point, there are no more unvalidated users.  Therefore we clear the "users need validation" flag.
-       begin_critical_section(S_CONTROL);
-       int flags;
-       flags = CtdlGetConfigInt("MMflags");
-       flags = flags & (~MM_VALID);
-       CtdlSetConfigInt("MMflags", flags);
-       end_critical_section(S_CONTROL);
-       cprintf("%d *** End of registration.\n", CIT_OK);
-}
-
-
-// validate a user
-void cmd_vali(char *v_args) {
-       char user[128];
-       int newax;
-       struct ctdluser userbuf;
-
-       extract_token(user, v_args, 0, '|', sizeof user);
-       newax = extract_int(v_args, 1);
-
-       if (CtdlAccessCheck(ac_aide) || (newax > AxAideU) || (newax < AxDeleted)) {
-               return;
-       }
-
-       if (CtdlGetUserLock(&userbuf, user) != 0) {
-               cprintf("%d '%s' not found.\n", ERROR + NO_SUCH_USER, user);
-               return;
-       }
-
-       userbuf.axlevel = newax;
-       userbuf.flags = (userbuf.flags & ~US_NEEDVALID);
-
-       CtdlPutUserLock(&userbuf);
-
-       // If the access level was set to zero, delete the user
-       if (newax == 0) {
-               if (purge_user(user) == 0) {
-                       cprintf("%d %s Deleted.\n", CIT_OK, userbuf.fullname);
-                       return;
-               }
-       }
-       cprintf("%d User '%s' validated.\n", CIT_OK, userbuf.fullname);
-}
-
-
-// List one user (this works with cmd_list)
-void ListThisUser(char *username, void *data) {
-       char *searchstring;
-       struct ctdluser usbuf;
-
-       if (CtdlGetUser(&usbuf, username) != 0) {
-               return;
-       }
-
-       searchstring = (char *)data;
-       if (bmstrcasestr(usbuf.fullname, searchstring) == NULL) {
-               return;
-       }
-
-       if (usbuf.axlevel > AxDeleted) {
-               if ((CC->user.axlevel >= AxAideU)
-                   || ((usbuf.flags & US_UNLISTED) == 0)
-                   || ((CC->internal_pgm))) {
-                       cprintf("%s|%d|%ld|%ld|%ld|%ld||\n",
-                               usbuf.fullname,
-                               usbuf.axlevel,
-                               usbuf.usernum,
-                               (long)usbuf.lastcall,
-                               usbuf.timescalled,
-                               usbuf.posted);
-               }
-       }
-}
-
-
-//  List users (searchstring may be empty to list all users)
-void cmd_list(char *cmdbuf) {
-       char searchstring[256];
-       extract_token(searchstring, cmdbuf, 0, '|', sizeof searchstring);
-       striplt(searchstring);
-       cprintf("%d \n", LISTING_FOLLOWS);
-       ForEachUser(ListThisUser, (void *)searchstring );
-       cprintf("000\n");
-}
-
-
-// assorted info we need to check at login
-void cmd_chek(char *argbuf) {
-       int mail = 0;
-       int regis = 0;
-       int vali = 0;
-
-       if (CtdlAccessCheck(ac_logged_in)) {
-               return;
-       }
-
-       CtdlGetUser(&CC->user, CC->curr_user);  // no lock is needed here 
-       if ((REGISCALL != 0) && ((CC->user.flags & US_REGIS) == 0)) {
-               regis = 1;
-       }
-
-       if (CC->user.axlevel >= AxAideU) {
-               if (CtdlGetConfigInt("MMflags") & MM_VALID) {
-                       vali = 1;
-               }
-       }
-
-       mail = InitialMailCheck();              // check for mail
-       cprintf("%d %d|%d|%d|%s|\n", CIT_OK, mail, regis, vali, CC->cs_inet_email);
-}
-
-
-// check to see if a user exists
-void cmd_qusr(char *who) {
-       struct ctdluser usbuf;
-
-       if (CtdlGetUser(&usbuf, who) == 0) {
-               cprintf("%d %s\n", CIT_OK, usbuf.fullname);
-       }
-       else {
-               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
-       }
-}
-
-
-// Administrative Get User Parameters
-void cmd_agup(char *cmdbuf) {
-       struct ctdluser usbuf;
-       char requested_user[128];
-
-       if (CtdlAccessCheck(ac_aide)) {
-               return;
-       }
-
-       extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user);
-       if (CtdlGetUser(&usbuf, requested_user) != 0) {
-               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
-               return;
-       }
-       cprintf("%d %s|%s|%u|%ld|%ld|%d|%ld|%ld|%d\n",
-               CIT_OK,
-               usbuf.fullname,
-               usbuf.password,
-               usbuf.flags,
-               usbuf.timescalled,
-               usbuf.posted,
-               (int) usbuf.axlevel,
-               usbuf.usernum,
-               (long)usbuf.lastcall,
-               usbuf.USuserpurge);
-}
-
-
-// Administrative Set User Parameters
-void cmd_asup(char *cmdbuf) {
-       struct ctdluser usbuf;
-       char requested_user[128];
-       char notify[SIZ];
-       int np;
-       int newax;
-       int deleted = 0;
-
-       if (CtdlAccessCheck(ac_aide))
-               return;
-
-       extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user);
-       if (CtdlGetUserLock(&usbuf, requested_user) != 0) {
-               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
-               return;
-       }
-       np = num_parms(cmdbuf);
-       if (np > 1)
-               extract_token(usbuf.password, cmdbuf, 1, '|', sizeof usbuf.password);
-       if (np > 2)
-               usbuf.flags = extract_int(cmdbuf, 2);
-       if (np > 3)
-               usbuf.timescalled = extract_int(cmdbuf, 3);
-       if (np > 4)
-               usbuf.posted = extract_int(cmdbuf, 4);
-       if (np > 5) {
-               newax = extract_int(cmdbuf, 5);
-               if ((newax >= AxDeleted) && (newax <= AxAideU)) {
-                       usbuf.axlevel = newax;
-               }
-       }
-       if (np > 7) {
-               usbuf.lastcall = extract_long(cmdbuf, 7);
-       }
-       if (np > 8) {
-               usbuf.USuserpurge = extract_int(cmdbuf, 8);
-       }
-       CtdlPutUserLock(&usbuf);
-       if (usbuf.axlevel == AxDeleted) {
-               if (purge_user(requested_user) == 0) {
-                       deleted = 1;
-               }
-       }
-
-       if (deleted) {
-               snprintf(notify, SIZ, 
-                       "User \"%s\" has been deleted by %s.\n",
-                       usbuf.fullname, (CC->logged_in ? CC->user.fullname : "an administrator")
-               );
-               CtdlAideMessage(notify, "User Deletion Message");
-       }
-
-       cprintf("%d Ok", CIT_OK);
-       if (deleted) {
-               cprintf(" (%s deleted)", requested_user);
-       }
-       cprintf("\n");
-}
-
-
-// Citadel protocol command to do the same
-void cmd_isme(char *argbuf) {
-       char addr[256];
-
-       if (CtdlAccessCheck(ac_logged_in)) return;
-       extract_token(addr, argbuf, 0, '|', sizeof addr);
-
-       if (CtdlIsMe(addr, sizeof addr)) {
-               cprintf("%d %s\n", CIT_OK, addr);
-       }
-       else {
-               cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
-       }
-
-}
-
-
-// Retrieve all Internet email addresses/aliases for the specified user
-void cmd_agea(char *cmdbuf) {
-       struct ctdluser usbuf;
-       char requested_user[128];
-       int i, num_e;
-       char e[512];
-
-       if (CtdlAccessCheck(ac_aide)) {
-               return;
-       }
-
-       extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user);
-       if (CtdlGetUser(&usbuf, requested_user) != 0) {
-               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
-               return;
-       }
-       cprintf("%d internet email addresses for %s\n", LISTING_FOLLOWS, usbuf.fullname);
-       num_e = num_tokens(usbuf.emailaddrs, '|');
-       for (i=0; i<num_e; ++i) {
-               extract_token(e, usbuf.emailaddrs, i, '|', sizeof e);
-               cprintf("%s\n", e);
-       }
-       cprintf("000\n");
-}
-
-
-// Set the Internet email addresses/aliases for the specified user
-void cmd_asea(char *cmdbuf) {
-       struct ctdluser usbuf;
-       char requested_user[128];
-       char buf[SIZ];
-       char whodat[64];
-       char new_emailaddrs[512] = { 0 } ;
-
-       if (CtdlAccessCheck(ac_aide)) return;
-
-       extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user);
-       if (CtdlGetUser(&usbuf, requested_user) != 0) {
-               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
-               return;
-       }
-
-       cprintf("%d Ok\n", SEND_LISTING);
-       while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
-               if (IsEmptyStr(buf)) {
-                       syslog(LOG_ERR, "user_ops: address <%s> is empty - not using", buf);
-               }
-               else if ((strlen(new_emailaddrs) + strlen(buf) + 2) > sizeof(new_emailaddrs)) {
-                       syslog(LOG_ERR, "user_ops: address <%s> does not fit in buffer - not using", buf);
-               }
-               else if (!IsDirectory(buf, 0)) {
-                       syslog(LOG_ERR, "user_ops: address <%s> is not in one of our domains - not using", buf);
-               }
-               else if ( (CtdlDirectoryLookup(whodat, buf, sizeof whodat) == 0) && (CtdlUserCmp(whodat, requested_user)) ) {
-                       syslog(LOG_ERR, "user_ops: address <%s> already belongs to <%s> - not using", buf, whodat);
-               }
-               else {
-                       syslog(LOG_DEBUG, "user_ops: address <%s> validated", buf);
-                       if (!IsEmptyStr(new_emailaddrs)) {
-                               strcat(new_emailaddrs, "|");
-                       }
-                       strcat(new_emailaddrs, buf);
-               }
-       }
-
-       CtdlSetEmailAddressesForUser(requested_user, new_emailaddrs);
-}
-
-
-// Set the preferred view for the current user/room combination
-void cmd_view(char *cmdbuf) {
-       int requested_view;
-       visit vbuf;
-
-       if (CtdlAccessCheck(ac_logged_in)) {
-               return;
-       }
-
-       requested_view = extract_int(cmdbuf, 0);
-
-       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
-       vbuf.v_view = requested_view;
-       CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
-       
-       cprintf("%d ok\n", CIT_OK);
-}
-
-
-// Rename a user
-void cmd_renu(char *cmdbuf) {
-       int retcode;
-       char oldname[USERNAME_SIZE];
-       char newname[USERNAME_SIZE];
-
-       if (CtdlAccessCheck(ac_aide)) {
-               return;
-       }
-
-       extract_token(oldname, cmdbuf, 0, '|', sizeof oldname);
-       extract_token(newname, cmdbuf, 1, '|', sizeof newname);
-
-       retcode = rename_user(oldname, newname);
-       switch(retcode) {
-               case RENAMEUSER_OK:
-                       cprintf("%d '%s' has been renamed to '%s'.\n", CIT_OK, oldname, newname);
-                       return;
-               case RENAMEUSER_LOGGED_IN:
-                       cprintf("%d '%s' is currently logged in and cannot be renamed.\n",
-                               ERROR + ALREADY_LOGGED_IN , oldname
-                       );
-                       return;
-               case RENAMEUSER_NOT_FOUND:
-                       cprintf("%d '%s' does not exist.\n", ERROR + NO_SUCH_USER, oldname);
-                       return;
-               case RENAMEUSER_ALREADY_EXISTS:
-                       cprintf("%d A user named '%s' already exists.\n", ERROR + ALREADY_EXISTS, newname);
-                       return;
-       }
-
-       cprintf("%d An unknown error occurred.\n", ERROR);
-}
-
-
-void cmd_quit(char *argbuf) {
-       cprintf("%d Goodbye.\n", CIT_OK);
-       CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
-}
-
-
-void cmd_lout(char *argbuf) {
-       if (CC->logged_in) 
-               CtdlUserLogout();
-       cprintf("%d logged out.\n", CIT_OK);
-}
-
-
-/*****************************************************************************/
-/*                      MODULE INITIALIZATION STUFF                          */
-/*****************************************************************************/
-
-
-CTDL_MODULE_INIT(serv_user)
-{
-       if (!threading) {
-               CtdlRegisterProtoHook(cmd_user, "USER", "Submit username for login");
-               CtdlRegisterProtoHook(cmd_pass, "PASS", "Complete login by submitting a password");
-               CtdlRegisterProtoHook(cmd_quit, "QUIT", "log out and disconnect from server");
-               CtdlRegisterProtoHook(cmd_lout, "LOUT", "log out but do not disconnect from server");
-               CtdlRegisterProtoHook(cmd_creu, "CREU", "Create User");
-               CtdlRegisterProtoHook(cmd_setp, "SETP", "Set the password for an account");
-               CtdlRegisterProtoHook(cmd_getu, "GETU", "Get User parameters");
-               CtdlRegisterProtoHook(cmd_setu, "SETU", "Set User parameters");
-               CtdlRegisterProtoHook(cmd_slrp, "SLRP", "Set Last Read Pointer");
-               CtdlRegisterProtoHook(cmd_invt, "INVT", "Invite a user to a room");
-               CtdlRegisterProtoHook(cmd_kick, "KICK", "Kick a user out of a room");
-               CtdlRegisterProtoHook(cmd_forg, "FORG", "Forget a room");
-               CtdlRegisterProtoHook(cmd_gnur, "GNUR", "Get Next Unregistered User");
-               CtdlRegisterProtoHook(cmd_vali, "VALI", "Validate new users");
-               CtdlRegisterProtoHook(cmd_list, "LIST", "List users");
-               CtdlRegisterProtoHook(cmd_chek, "CHEK", "assorted info we need to check at login");
-               CtdlRegisterProtoHook(cmd_qusr, "QUSR", "check to see if a user exists");
-               CtdlRegisterProtoHook(cmd_agup, "AGUP", "Administratively Get User Parameters");
-               CtdlRegisterProtoHook(cmd_asup, "ASUP", "Administratively Set User Parameters");
-               CtdlRegisterProtoHook(cmd_agea, "AGEA", "Administratively Get Email Addresses");
-               CtdlRegisterProtoHook(cmd_asea, "ASEA", "Administratively Set Email Addresses");
-               CtdlRegisterProtoHook(cmd_seen, "SEEN", "Manipulate seen/unread message flags");
-               CtdlRegisterProtoHook(cmd_gtsn, "GTSN", "Fetch seen/unread message flags");
-               CtdlRegisterProtoHook(cmd_view, "VIEW", "Set preferred view for user/room combination");
-               CtdlRegisterProtoHook(cmd_renu, "RENU", "Rename a user");
-               CtdlRegisterProtoHook(cmd_newu, "NEWU", "Log in as a new user");
-               CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
-       }
-       /* return our Subversion id for the Log */
-       return "user";
-}
diff --git a/citadel/modules/expire/.gitignore b/citadel/modules/expire/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/expire/expire_policy.c b/citadel/modules/expire/expire_policy.c
deleted file mode 100644 (file)
index 0c9471b..0000000
+++ /dev/null
@@ -1,181 +0,0 @@
-/* 
- * Functions which manage expire policy for rooms
- * Copyright (c) 1987-2015 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <string.h>
-
-#include <time.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "database.h"
-#include "config.h"
-#include "room_ops.h"
-#include "sysdep_decls.h"
-#include "support.h"
-#include "msgbase.h"
-#include "citserver.h"
-#include "ctdl_module.h"
-#include "user_ops.h"
-
-/*
- * Retrieve the applicable expire policy for a specific room
- */
-void GetExpirePolicy(struct ExpirePolicy *epbuf, struct ctdlroom *qrbuf) {
-       struct floor *fl;
-
-       /* If the room has its own policy, return it */ 
-       if (qrbuf->QRep.expire_mode != 0) {
-               memcpy(epbuf, &qrbuf->QRep, sizeof(struct ExpirePolicy));
-               return;
-       }
-
-       /* (non-mailbox rooms)
-        * If the floor has its own policy, return it
-        */
-       if ( (qrbuf->QRflags & QR_MAILBOX) == 0) {
-               fl = CtdlGetCachedFloor(qrbuf->QRfloor);
-               if (fl->f_ep.expire_mode != 0) {
-                       memcpy(epbuf, &fl->f_ep, sizeof(struct ExpirePolicy));
-                       return;
-               }
-       }
-
-       /* (Mailbox rooms)
-        * If there is a default policy for mailbox rooms, return it
-        */
-       if (qrbuf->QRflags & QR_MAILBOX) {
-               if (CtdlGetConfigInt("c_mbxep_mode") != 0) {
-                       epbuf->expire_mode = CtdlGetConfigInt("c_mbxep_mode");
-                       epbuf->expire_value = CtdlGetConfigInt("c_mbxep_value");
-                       return;
-               }
-       }
-
-       /* Otherwise, fall back on the system default */
-       epbuf->expire_mode = CtdlGetConfigInt("c_ep_mode");
-       epbuf->expire_value = CtdlGetConfigInt("c_ep_value");
-}
-
-
-/*
- * Get Policy EXpire
- */
-void cmd_gpex(char *argbuf) {
-       struct ExpirePolicy exp;
-       struct floor *fl;
-       char which[128];
-
-       memset(&exp, 0, sizeof(struct ExpirePolicy));
-       extract_token(which, argbuf, 0, '|', sizeof which);
-       if (!strcasecmp(which, strof(roompolicy)) || !strcasecmp(which, "room")) {
-               memcpy(&exp, &CC->room.QRep, sizeof(struct ExpirePolicy));
-       }
-       else if (!strcasecmp(which, strof(floorpolicy)) || !strcasecmp(which, "floor")) {
-               fl = CtdlGetCachedFloor(CC->room.QRfloor);
-               memcpy(&exp, &fl->f_ep, sizeof(struct ExpirePolicy));
-       }
-       else if (!strcasecmp(which, strof(mailboxespolicy)) || !strcasecmp(which, "mailboxes")) {
-               exp.expire_mode = CtdlGetConfigInt("c_mbxep_mode");
-               exp.expire_value = CtdlGetConfigInt("c_mbxep_value");
-       }
-       else if (!strcasecmp(which, strof(sitepolicy)) || !strcasecmp(which, "site")) {
-               exp.expire_mode = CtdlGetConfigInt("c_ep_mode");
-               exp.expire_value = CtdlGetConfigInt("c_ep_value");
-       }
-       else {
-               cprintf("%d Invalid keyword \"%s\"\n", ERROR + ILLEGAL_VALUE, which);
-               return;
-       }
-
-       cprintf("%d %d|%d\n", CIT_OK, exp.expire_mode, exp.expire_value);
-}
-
-
-/*
- * Set Policy EXpire
- */
-void cmd_spex(char *argbuf) {
-       struct ExpirePolicy exp;
-       struct floor flbuf;
-       char which[128];
-
-       memset(&exp, 0, sizeof(struct ExpirePolicy));
-       extract_token(which, argbuf, 0, '|', sizeof which);
-       exp.expire_mode = extract_int(argbuf, 1);
-       exp.expire_value = extract_int(argbuf, 2);
-
-       if ((exp.expire_mode < 0) || (exp.expire_mode > 3)) {
-               cprintf("%d Invalid policy.\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       if ((!strcasecmp(which, strof(roompolicy))) || (!strcasecmp(which, "room")))
-       {
-               if (!is_room_aide()) {
-                       cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
-                       return;
-               }
-               CtdlGetRoomLock(&CC->room, CC->room.QRname);
-               memcpy(&CC->room.QRep, &exp, sizeof(struct ExpirePolicy));
-               CtdlPutRoomLock(&CC->room);
-               cprintf("%d Room expire policy for '%s' has been updated.\n", CIT_OK, CC->room.QRname);
-               syslog(LOG_DEBUG, "Room: %s , Policy: %d , Value: %d",
-                       CC->room.QRname,
-                       exp.expire_mode,
-                       exp.expire_value
-               );
-               return;
-       }
-
-       if (CC->user.axlevel < AxAideU) {
-               cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
-               return;
-       }
-
-       if ((!strcasecmp(which, strof(floorpolicy))) || (!strcasecmp(which, "floor")))
-       {
-               lgetfloor(&flbuf, CC->room.QRfloor);
-               memcpy(&flbuf.f_ep, &exp, sizeof(struct ExpirePolicy));
-               lputfloor(&flbuf, CC->room.QRfloor);
-               cprintf("%d Floor expire policy has been updated.\n", CIT_OK);
-               return;
-       }
-
-       else if ((!strcasecmp(which, strof(mailboxespolicy))) || (!strcasecmp(which, "mailboxes")))
-       {
-               CtdlSetConfigInt("c_mbxep_mode", exp.expire_mode);
-               CtdlSetConfigInt("c_mbxep_value", exp.expire_value);
-               cprintf("%d Default expire policy for mailboxes set.\n", CIT_OK);
-               return;
-       }
-
-       else if ((!strcasecmp(which, strof(sitepolicy))) || (!strcasecmp(which, "site")))
-       {
-               if (exp.expire_mode == EXPIRE_NEXTLEVEL) {
-                       cprintf("%d Invalid policy (no higher level)\n", ERROR + ILLEGAL_VALUE);
-                       return;
-               }
-               CtdlSetConfigInt("c_ep_mode", exp.expire_mode);
-               CtdlSetConfigInt("c_ep_value", exp.expire_value);
-               cprintf("%d Site expire policy has been updated.\n", CIT_OK);
-               return;
-       }
-
-       cprintf("%d Invalid keyword '%s'\n", ERROR + ILLEGAL_VALUE, which);
-}
diff --git a/citadel/modules/expire/policy.h b/citadel/modules/expire/policy.h
deleted file mode 100644 (file)
index e07fe8b..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-void GetExpirePolicy(struct ExpirePolicy *epbuf, struct ctdlroom *qrbuf);
-void cmd_gpex(char *argbuf);
-void cmd_spex(char *argbuf);
diff --git a/citadel/modules/expire/serv_expire.c b/citadel/modules/expire/serv_expire.c
deleted file mode 100644 (file)
index a3c2258..0000000
+++ /dev/null
@@ -1,838 +0,0 @@
-// This module handles the expiry of old messages and the purging of old users.
-//
-// You might also see this module affectionately referred to as TDAP (The Dreaded Auto-Purger).
-//
-// Copyright (c) 1988-2022 by citadel.org (Art Cancro, Wilifried Goesgens, and others)
-//
-// This program is open source software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License as published
-// by the Free Software Foundation; either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "user_ops.h"
-#include "control.h"
-#include "threads.h"
-#include "context.h"
-
-#include "ctdl_module.h"
-
-
-struct PurgeList {
-       struct PurgeList *next;
-       char name[ROOMNAMELEN];         // use the larger of username or roomname
-};
-
-struct VPurgeList {
-       struct VPurgeList *next;
-       long vp_roomnum;
-       long vp_roomgen;
-       long vp_usernum;
-};
-
-struct ValidRoom {
-       struct ValidRoom *next;
-       long vr_roomnum;
-       long vr_roomgen;
-};
-
-struct ValidUser {
-       struct ValidUser *next;
-       long vu_usernum;
-};
-
-struct ctdlroomref {
-       struct ctdlroomref *next;
-       long msgnum;
-};
-
-struct UPurgeList {
-       struct UPurgeList *next;
-       char up_key[256];
-};
-
-struct EPurgeList {
-       struct EPurgeList *next;
-       int ep_keylen;
-       char *ep_key;
-};
-
-
-struct PurgeList *UserPurgeList = NULL;
-struct PurgeList *RoomPurgeList = NULL;
-struct ValidRoom *ValidRoomList = NULL;
-struct ValidUser *ValidUserList = NULL;
-int messages_purged;
-int users_not_purged;
-char *users_corrupt_msg = NULL;
-char *users_zero_msg = NULL;
-struct ctdlroomref *rr = NULL;
-int force_purge_now = 0;                       // set to nonzero to force a run right now
-
-
-// First phase of message purge -- gather the locations of messages which
-// qualify for purging and write them to a temp file.
-void GatherPurgeMessages(struct ctdlroom *qrbuf, void *data) {
-       struct ExpirePolicy epbuf;
-       long delnum;
-       time_t xtime, now;
-       struct CtdlMessage *msg = NULL;
-       int a;
-       struct cdbdata *cdbfr;
-       long *msglist = NULL;
-       int num_msgs = 0;
-       FILE *purgelist;
-
-       purgelist = (FILE *)data;
-       fprintf(purgelist, "r=%s\n", qrbuf->QRname);
-
-       time(&now);
-       GetExpirePolicy(&epbuf, qrbuf);
-
-       // If the room is set to never expire messages ... do nothing
-       if (epbuf.expire_mode == EXPIRE_NEXTLEVEL) return;
-       if (epbuf.expire_mode == EXPIRE_MANUAL) return;
-
-       // Don't purge messages containing system configuration, dumbass.
-       if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
-
-       // Ok, we got this far ... now let's see what's in the room.
-       cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
-
-       if (cdbfr != NULL) {
-               msglist = malloc(cdbfr->len);
-               memcpy(msglist, cdbfr->ptr, cdbfr->len);
-               num_msgs = cdbfr->len / sizeof(long);
-               cdb_free(cdbfr);
-       }
-
-       // Nothing to do if there aren't any messages
-       if (num_msgs == 0) {
-               if (msglist != NULL) free(msglist);
-               return;
-       }
-
-       // If the room is set to expire by count, do that.
-       if (epbuf.expire_mode == EXPIRE_NUMMSGS) {
-               if (num_msgs > epbuf.expire_value) {
-                       for (a=0; a<(num_msgs - epbuf.expire_value); ++a) {
-                               fprintf(purgelist, "m=%ld\n", msglist[a]);
-                               ++messages_purged;
-                       }
-               }
-       }
-
-       // If the room is set to expire by age...
-       if (epbuf.expire_mode == EXPIRE_AGE) {
-               for (a=0; a<num_msgs; ++a) {
-                       delnum = msglist[a];
-
-                       msg = CtdlFetchMessage(delnum, 0);      // don't need the body
-                       if (msg != NULL) {
-                               xtime = atol(msg->cm_fields[eTimestamp]);
-                               CM_Free(msg);
-                       }
-                       else {
-                               xtime = 0L;
-                       }
-
-                       if ((xtime > 0L) && (now - xtime > (time_t)(epbuf.expire_value * 86400L))) {
-                               fprintf(purgelist, "m=%ld\n", delnum);
-                               ++messages_purged;
-                       }
-               }
-       }
-
-       if (msglist != NULL) free(msglist);
-}
-
-
-// Second phase of message purge -- read list of msgs from temp file and delete them.
-void DoPurgeMessages(FILE *purgelist) {
-       char roomname[ROOMNAMELEN];
-       long msgnum;
-       char buf[SIZ];
-
-       rewind(purgelist);
-       strcpy(roomname, "nonexistent room ___ ___");
-       while (fgets(buf, sizeof buf, purgelist) != NULL) {
-               buf[strlen(buf)-1]=0;
-               if (!strncasecmp(buf, "r=", 2)) {
-                       strcpy(roomname, &buf[2]);
-               }
-               if (!strncasecmp(buf, "m=", 2)) {
-                       msgnum = atol(&buf[2]);
-                       if (msgnum > 0L) {
-                               CtdlDeleteMessages(roomname, &msgnum, 1, "");
-                       }
-               }
-       }
-}
-
-
-void PurgeMessages(void) {
-       FILE *purgelist;
-
-       syslog(LOG_DEBUG, "PurgeMessages() called");
-       messages_purged = 0;
-
-       purgelist = tmpfile();
-       if (purgelist == NULL) {
-               syslog(LOG_CRIT, "Can't create purgelist temp file: %s", strerror(errno));
-               return;
-       }
-
-       CtdlForEachRoom(GatherPurgeMessages, (void *)purgelist );
-       DoPurgeMessages(purgelist);
-       fclose(purgelist);
-}
-
-
-void AddValidUser(char *username, void *data) {
-       struct ValidUser *vuptr;
-       struct ctdluser usbuf;
-
-       if (CtdlGetUser(&usbuf, username) != 0) {
-               return;
-       }
-
-       vuptr = (struct ValidUser *)malloc(sizeof(struct ValidUser));
-       vuptr->next = ValidUserList;
-       vuptr->vu_usernum = usbuf.usernum;
-       ValidUserList = vuptr;
-}
-
-
-void AddValidRoom(struct ctdlroom *qrbuf, void *data) {
-       struct ValidRoom *vrptr;
-
-       vrptr = (struct ValidRoom *)malloc(sizeof(struct ValidRoom));
-       vrptr->next = ValidRoomList;
-       vrptr->vr_roomnum = qrbuf->QRnumber;
-       vrptr->vr_roomgen = qrbuf->QRgen;
-       ValidRoomList = vrptr;
-}
-
-
-void DoPurgeRooms(struct ctdlroom *qrbuf, void *data) {
-       time_t age, purge_secs;
-       struct PurgeList *pptr;
-       struct ValidUser *vuptr;
-       int do_purge = 0;
-
-       // For mailbox rooms, there's only one purging rule: if the user who
-       // owns the room still exists, we keep the room; otherwise, we purge
-       // it.  Bypass any other rules.
-       if (qrbuf->QRflags & QR_MAILBOX) {
-               // if user not found, do_purge will be 1
-               do_purge = 1;
-               for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
-                       if (vuptr->vu_usernum == atol(qrbuf->QRname)) {
-                               do_purge = 0;
-                       }
-               }
-       }
-       else {
-               // Any of these attributes render a room non-purgable
-               if (qrbuf->QRflags & QR_PERMANENT) return;
-               if (qrbuf->QRflags & QR_DIRECTORY) return;
-               if (qrbuf->QRflags & QR_NETWORK) return;
-               if (qrbuf->QRflags2 & QR2_SYSTEM) return;
-               if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
-               if (CtdlIsNonEditable(qrbuf)) return;
-
-               // If we don't know the modification date, be safe and don't purge
-               if (qrbuf->QRmtime <= (time_t)0) return;
-
-               // If no room purge time is set, be safe and don't purge
-               if (CtdlGetConfigLong("c_roompurge") < 0) return;
-
-               // Otherwise, check the date of last modification
-               age = time(NULL) - (qrbuf->QRmtime);
-               purge_secs = CtdlGetConfigLong("c_roompurge") * 86400;
-               if (purge_secs <= (time_t)0) return;
-               syslog(LOG_DEBUG, "<%s> is <%ld> seconds old", qrbuf->QRname, (long)age);
-               if (age > purge_secs) do_purge = 1;
-       } // !QR_MAILBOX
-
-       if (do_purge) {
-               pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
-               pptr->next = RoomPurgeList;
-               strcpy(pptr->name, qrbuf->QRname);
-               RoomPurgeList = pptr;
-       }
-
-}
-
-
-int PurgeRooms(void) {
-       struct PurgeList *pptr;
-       int num_rooms_purged = 0;
-       struct ctdlroom qrbuf;
-       struct ValidUser *vuptr;
-       char *transcript = NULL;
-
-       syslog(LOG_DEBUG, "PurgeRooms() called");
-
-       // Load up a table full of valid user numbers so we can delete
-       // user-owned rooms for users who no longer exist
-       ForEachUser(AddValidUser, NULL);
-
-       // Then cycle through the room file
-       CtdlForEachRoom(DoPurgeRooms, NULL);
-
-       // Free the valid user list
-       while (ValidUserList != NULL) {
-               vuptr = ValidUserList->next;
-               free(ValidUserList);
-               ValidUserList = vuptr;
-       }
-
-       transcript = malloc(SIZ);
-       strcpy(transcript, "The following rooms have been auto-purged:\n");
-
-       while (RoomPurgeList != NULL) {
-               if (CtdlGetRoom(&qrbuf, RoomPurgeList->name) == 0) {
-                       transcript=realloc(transcript, strlen(transcript)+SIZ);
-                       snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
-                               qrbuf.QRname);
-                       CtdlDeleteRoom(&qrbuf);
-               }
-               pptr = RoomPurgeList->next;
-               free(RoomPurgeList);
-               RoomPurgeList = pptr;
-               ++num_rooms_purged;
-       }
-
-       if (num_rooms_purged > 0) CtdlAideMessage(transcript, "Room Autopurger Message");
-       free(transcript);
-
-       syslog(LOG_DEBUG, "Purged %d rooms.", num_rooms_purged);
-       return(num_rooms_purged);
-}
-
-
-// Back end function to check user accounts for expiration.
-void do_user_purge(char *username, void *data) {
-       int purge;
-       time_t now;
-       time_t purge_time;
-       struct PurgeList *pptr;
-       struct ctdluser us;
-
-       if (CtdlGetUser(&us, username) != 0) {
-               return;
-       }
-
-       // Set purge time; if the user overrides the system default, use it
-       if (us.USuserpurge > 0) {
-               purge_time = ((time_t)us.USuserpurge) * 86400;
-       }
-       else {
-               purge_time = CtdlGetConfigLong("c_userpurge") * 86400;
-       }
-
-       // The default rule is to not purge.
-       purge = 0;
-       
-       // If the user has not logged in for the configured amount of time, expire the account.
-       if (CtdlGetConfigLong("c_userpurge") > 0) {
-               now = time(NULL);
-               if ((now - us.lastcall) > purge_time) purge = 1;
-       }
-
-       // If the account is marked as permanent, don't purge it.
-       if (us.flags & US_PERM) purge = 0;
-
-       // If the account is an administrator, don't purge it.
-       if (us.axlevel == 6) purge = 0;
-
-       // If the access level is 0, the record should already have been
-       // deleted, but maybe the user was logged in at the time or something.
-       // Delete the record now.
-       if (us.axlevel == 0) purge = 1;
-
-       // If the user set his/her password to 'deleteme', he/she
-       // wishes to be deleted, so purge the record.
-       // Moved this lower down so that aides and permanent users get purged if they ask to be.
-       if (!strcasecmp(us.password, "deleteme")) purge = 1;
-       
-       // Fewer than zero calls is impossible, indicating a corrupted record.
-       if (us.timescalled < 0) purge = 1;
-
-       // any negative user number, is also impossible.
-       if (us.usernum < 0L) purge = 1;
-       
-       // Don't purge user 0. That user is there for the system
-       if (us.usernum == 0L) {
-               // FIXME: Temporary log message. Until we do unauth access with user 0 we should
-               // try to get rid of all user 0 occurences. Many will be remnants from old code so
-               // we will need to try and purge them from users data bases.Some will not have names but
-               // those with names should be purged.
-               syslog(LOG_DEBUG, "Auto purger found a user 0 with name <%s>", us.fullname);
-               // purge = 0;
-       }
-       
-       // If the user has no full name entry then we can't purge them since the actual purge can't find them.
-       // This shouldn't happen but does somehow.
-       if (IsEmptyStr(us.fullname)) {
-               purge = 0;
-               
-               if (us.usernum > 0L) {
-                       purge=0;
-                       if (users_corrupt_msg == NULL) {
-                               users_corrupt_msg = malloc(SIZ);
-                               strcpy(users_corrupt_msg,
-                                       "The auto-purger found the following user numbers with no name.\n"
-                                       "The system has no way to purge a user with no name,"
-                                       " and should not be able to create them either.\n"
-                                       "This indicates corruption of the user DB or possibly a bug.\n"
-                                       "It may be a good idea to restore your DB from a backup.\n"
-                               );
-                       }
-               
-                       users_corrupt_msg=realloc(users_corrupt_msg, strlen(users_corrupt_msg)+30);
-                       snprintf(&users_corrupt_msg[strlen(users_corrupt_msg)], 29, " %ld\n", us.usernum);
-               }
-       }
-
-       if (purge == 1) {
-               pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
-               pptr->next = UserPurgeList;
-               strcpy(pptr->name, us.fullname);
-               UserPurgeList = pptr;
-       }
-       else {
-               ++users_not_purged;
-       }
-
-}
-
-
-int PurgeUsers(void) {
-       struct PurgeList *pptr;
-       int num_users_purged = 0;
-       char *transcript = NULL;
-
-       syslog(LOG_DEBUG, "PurgeUsers() called");
-       users_not_purged = 0;
-
-       switch(CtdlGetConfigInt("c_auth_mode")) {
-               case AUTHMODE_NATIVE:
-                       ForEachUser(do_user_purge, NULL);
-                       break;
-               default:
-                       syslog(LOG_DEBUG, "User purge for auth mode %d is not implemented.", CtdlGetConfigInt("c_auth_mode"));
-                       break;
-       }
-
-       transcript = malloc(SIZ);
-
-       if (users_not_purged == 0) {
-               strcpy(transcript, "The auto-purger was told to purge every user.  It is\n"
-                               "refusing to do this because it usually indicates a problem\n"
-                               "such as an inability to communicate with a name service.\n"
-               );
-               while (UserPurgeList != NULL) {
-                       pptr = UserPurgeList->next;
-                       free(UserPurgeList);
-                       UserPurgeList = pptr;
-                       ++num_users_purged;
-               }
-       }
-
-       else {
-               strcpy(transcript, "The following users have been auto-purged:\n");
-               while (UserPurgeList != NULL) {
-                       transcript=realloc(transcript, strlen(transcript)+SIZ);
-                       snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
-                               UserPurgeList->name);
-                       purge_user(UserPurgeList->name);
-                       pptr = UserPurgeList->next;
-                       free(UserPurgeList);
-                       UserPurgeList = pptr;
-                       ++num_users_purged;
-               }
-       }
-
-       if (num_users_purged > 0) CtdlAideMessage(transcript, "User Purge Message");
-       free(transcript);
-
-       if (users_corrupt_msg) {
-               CtdlAideMessage(users_corrupt_msg, "User Corruption Message");
-               free (users_corrupt_msg);
-               users_corrupt_msg = NULL;
-       }
-       
-       if(users_zero_msg) {
-               CtdlAideMessage(users_zero_msg, "User Zero Message");
-               free (users_zero_msg);
-               users_zero_msg = NULL;
-       }
-               
-       syslog(LOG_DEBUG, "Purged %d users.", num_users_purged);
-       return(num_users_purged);
-}
-
-
-// Purge visits
-//
-// This is a really cumbersome "garbage collection" function.  We have to
-// delete visits which refer to rooms and/or users which no longer exist.  In
-// order to prevent endless traversals of the room and user files, we first
-// build linked lists of rooms and users which _do_ exist on the system, then
-// traverse the visit file, checking each record against those two lists and
-// purging the ones that do not have a match on _both_ lists.  (Remember, if
-// either the room or user being referred to is no longer on the system, the
-// record is completely useless.)
-//
-int PurgeVisits(void) {
-       struct cdbdata *cdbvisit;
-       visit vbuf;
-       struct VPurgeList *VisitPurgeList = NULL;
-       struct VPurgeList *vptr;
-       int purged = 0;
-       char IndexBuf[32];
-       int IndexLen;
-       struct ValidRoom *vrptr;
-       struct ValidUser *vuptr;
-       int RoomIsValid, UserIsValid;
-
-       // First, load up a table full of valid room/gen combinations
-       CtdlForEachRoom(AddValidRoom, NULL);
-
-       // Then load up a table full of valid user numbers
-       ForEachUser(AddValidUser, NULL);
-
-       // Now traverse through the visits, purging irrelevant records...
-       cdb_rewind(CDB_VISIT);
-       while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
-               memset(&vbuf, 0, sizeof(visit));
-               memcpy(&vbuf, cdbvisit->ptr,
-                       ( (cdbvisit->len > sizeof(visit)) ?
-                         sizeof(visit) : cdbvisit->len) );
-               cdb_free(cdbvisit);
-
-               RoomIsValid = 0;
-               UserIsValid = 0;
-
-               // Check to see if the room exists
-               for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
-                       if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
-                            && (vrptr->vr_roomgen==vbuf.v_roomgen))
-                               RoomIsValid = 1;
-               }
-
-               // Check to see if the user exists
-               for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
-                       if (vuptr->vu_usernum == vbuf.v_usernum)
-                               UserIsValid = 1;
-               }
-
-               // Put the record on the purge list if it's dead
-               if ((RoomIsValid==0) || (UserIsValid==0)) {
-                       vptr = (struct VPurgeList *)
-                               malloc(sizeof(struct VPurgeList));
-                       vptr->next = VisitPurgeList;
-                       vptr->vp_roomnum = vbuf.v_roomnum;
-                       vptr->vp_roomgen = vbuf.v_roomgen;
-                       vptr->vp_usernum = vbuf.v_usernum;
-                       VisitPurgeList = vptr;
-               }
-
-       }
-
-       // Free the valid room/gen combination list
-       while (ValidRoomList != NULL) {
-               vrptr = ValidRoomList->next;
-               free(ValidRoomList);
-               ValidRoomList = vrptr;
-       }
-
-       // Free the valid user list
-       while (ValidUserList != NULL) {
-               vuptr = ValidUserList->next;
-               free(ValidUserList);
-               ValidUserList = vuptr;
-       }
-
-       // Now delete every visit on the purged list
-       while (VisitPurgeList != NULL) {
-               IndexLen = GenerateRelationshipIndex(IndexBuf,
-                               VisitPurgeList->vp_roomnum,
-                               VisitPurgeList->vp_roomgen,
-                               VisitPurgeList->vp_usernum);
-               cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
-               vptr = VisitPurgeList->next;
-               free(VisitPurgeList);
-               VisitPurgeList = vptr;
-               ++purged;
-       }
-
-       return(purged);
-}
-
-
-// Purge the use table of old entries.
-int PurgeUseTable(StrBuf *ErrMsg) {
-       int purged = 0;
-       struct cdbdata *cdbut;
-       struct UseTable ut;
-       struct UPurgeList *ul = NULL;
-       struct UPurgeList *uptr; 
-
-       // Phase 1: traverse through the table, discovering old records...
-
-       syslog(LOG_DEBUG, "Purge use table: phase 1");
-       cdb_rewind(CDB_USETABLE);
-       while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
-               if (cdbut->len > sizeof(struct UseTable))
-                       memcpy(&ut, cdbut->ptr, sizeof(struct UseTable));
-               else {
-                       memset(&ut, 0, sizeof(struct UseTable));
-                       memcpy(&ut, cdbut->ptr, cdbut->len);
-               }
-               cdb_free(cdbut);
-
-               if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
-                       uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
-                       if (uptr != NULL) {
-                               uptr->next = ul;
-                               safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
-                               ul = uptr;
-                       }
-                       ++purged;
-               }
-
-       }
-
-       // Phase 2: delete the records
-       syslog(LOG_DEBUG, "Purge use table: phase 2");
-       while (ul != NULL) {
-               cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
-               uptr = ul->next;
-               free(ul);
-               ul = uptr;
-       }
-
-       syslog(LOG_DEBUG, "Purge use table: finished (purged %d records)", purged);
-       return(purged);
-}
-
-
-// Purge the EUID Index of old records.
-int PurgeEuidIndexTable(void) {
-       int purged = 0;
-       struct cdbdata *cdbei;
-       struct EPurgeList *el = NULL;
-       struct EPurgeList *eptr; 
-       long msgnum;
-       struct CtdlMessage *msg = NULL;
-
-       // Phase 1: traverse through the table, discovering old records...
-       syslog(LOG_DEBUG, "Purge EUID index: phase 1");
-       cdb_rewind(CDB_EUIDINDEX);
-       while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
-
-               memcpy(&msgnum, cdbei->ptr, sizeof(long));
-
-               msg = CtdlFetchMessage(msgnum, 0);
-               if (msg != NULL) {
-                       CM_Free(msg);   // it still exists, so do nothing
-               }
-               else {
-                       eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
-                       if (eptr != NULL) {
-                               eptr->next = el;
-                               eptr->ep_keylen = cdbei->len - sizeof(long);
-                               eptr->ep_key = malloc(cdbei->len);
-                               memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
-                               el = eptr;
-                       }
-                       ++purged;
-               }
-
-              cdb_free(cdbei);
-
-       }
-
-       // Phase 2: delete the records
-       syslog(LOG_DEBUG, "Purge euid index: phase 2");
-       while (el != NULL) {
-               cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
-               free(el->ep_key);
-               eptr = el->next;
-               free(el);
-               el = eptr;
-       }
-
-       syslog(LOG_DEBUG, "Purge euid index: finished (purged %d records)", purged);
-       return(purged);
-}
-
-
-// Purge external auth assocations for missing users (theoretically this will never delete anything)
-int PurgeStaleExtAuthAssociations(void) {
-       struct cdbdata *cdboi;
-       struct ctdluser usbuf;
-       HashList *keys = NULL;
-       HashPos *HashPos;
-       char *deleteme = NULL;
-       long len;
-       void *Value;
-       const char *Key;
-       int num_deleted = 0;
-       long usernum = 0L;
-
-       keys = NewHash(1, NULL);
-       if (!keys) return(0);
-
-       cdb_rewind(CDB_EXTAUTH);
-       while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
-               if (cdboi->len > sizeof(long)) {
-                       memcpy(&usernum, cdboi->ptr, sizeof(long));
-                       if (CtdlGetUserByNumber(&usbuf, usernum) != 0) {
-                               deleteme = strdup(cdboi->ptr + sizeof(long)),
-                               Put(keys, deleteme, strlen(deleteme), deleteme, NULL);
-                       }
-               }
-               cdb_free(cdboi);
-       }
-
-       // Go through the hash list, deleting keys we stored in it
-
-       HashPos = GetNewHashPos(keys, 0);
-       while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0) {
-               syslog(LOG_DEBUG, "Deleting associated external authenticator <%s>",  (char*)Value);
-               cdb_delete(CDB_EXTAUTH, Value, strlen(Value));
-               // note: don't free(Value) -- deleting the hash list will handle this for us
-               ++num_deleted;
-       }
-       DeleteHashPos(&HashPos);
-       DeleteHash(&keys);
-       return num_deleted;
-}
-
-
-void purge_databases(void) {
-       int retval;
-       static time_t last_purge = 0;
-       time_t now;
-       struct tm tm;
-
-       // Do the auto-purge if the current hour equals the purge hour,
-       // but not if the operation has already been performed in the
-       // last twelve hours.  This is usually enough granularity.
-       now = time(NULL);
-       localtime_r(&now, &tm);
-       if (((tm.tm_hour != CtdlGetConfigInt("c_purge_hour")) || ((now - last_purge) < 43200)) && (force_purge_now == 0)) {
-               return;
-       }
-
-       syslog(LOG_INFO, "Auto-purger: starting.");
-
-       if (!server_shutting_down) {
-               retval = PurgeUsers();
-               syslog(LOG_NOTICE, "Purged %d users.", retval);
-       }
-               
-       if (!server_shutting_down) {
-               PurgeMessages();
-               syslog(LOG_NOTICE, "Expired %d messages.", messages_purged);
-       }
-
-       if (!server_shutting_down) {
-                       retval = PurgeRooms();
-                       syslog(LOG_NOTICE, "Expired %d rooms.", retval);
-       }
-
-       if (!server_shutting_down) {
-                       retval = PurgeVisits();
-                       syslog(LOG_NOTICE, "Purged %d visits.", retval);
-       }
-
-       if (!server_shutting_down) {
-               StrBuf *ErrMsg;
-               ErrMsg = NewStrBuf();
-               retval = PurgeUseTable(ErrMsg);
-                       syslog(LOG_NOTICE, "Purged %d entries from the use table.", retval);
-               FreeStrBuf(&ErrMsg);
-       }
-
-       if (!server_shutting_down) {
-                       retval = PurgeEuidIndexTable();
-                       syslog(LOG_NOTICE, "Purged %d entries from the EUID index.", retval);
-       }
-
-       if (!server_shutting_down) {
-               retval = PurgeStaleExtAuthAssociations();
-               syslog(LOG_NOTICE, "Purged %d stale external auth associations.", retval);
-       }
-
-       //if (!server_shutting_down) {
-       //      FIXME this is where we could do a non-interactive delete of zero-refcount messages
-       //}
-
-       if ( (!server_shutting_down) && (CtdlGetConfigInt("c_shrink_db_files") != 0) ) {
-               cdb_compact();                                  // Shrink the DB files on disk
-       }
-
-       if (!server_shutting_down) {
-               syslog(LOG_INFO, "Auto-purger: finished.");
-               last_purge = now;                               // So we don't do it again soon
-               force_purge_now = 0;
-       }
-       else {
-               syslog(LOG_INFO, "Auto-purger: STOPPED.");
-       }
-}
-
-
-// Manually initiate a run of The Dreaded Auto-Purger (tm)
-void cmd_tdap(char *argbuf) {
-       if (CtdlAccessCheck(ac_aide)) return;
-       force_purge_now = 1;
-       cprintf("%d Manually initiating a purger run now.\n", CIT_OK);
-}
-
-
-CTDL_MODULE_INIT(expire)
-{
-       if (!threading)
-       {
-               CtdlRegisterProtoHook(cmd_tdap, "TDAP", "Manually initiate auto-purger");
-               CtdlRegisterProtoHook(cmd_gpex, "GPEX", "Get expire policy");
-               CtdlRegisterProtoHook(cmd_spex, "SPEX", "Set expire policy");
-               CtdlRegisterSessionHook(purge_databases, EVT_TIMER, PRIO_CLEANUP + 20);
-       }
-
-       // return our module name for the log
-       return "expire";
-}
diff --git a/citadel/modules/fulltext/.gitignore b/citadel/modules/fulltext/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/fulltext/crc16.c b/citadel/modules/fulltext/crc16.c
deleted file mode 100644 (file)
index abfcd8e..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-/****************************************************************************
-
-  Filename:     crc16.c
-  Description:  Cyclic Redundancy Check 16 functions
-  Created:      24-Feb-1999
-
-  Copyright (c) 1999-2003, Indigo Systems Corporation
-  All rights reserved.
-
-  Redistribution and use in source and binary forms, with or without
-  modification, are permitted provided that the following conditions are met:
-
-  Redistributions of source code must retain the above copyright notice, this
-  list of conditions and the following disclaimer.
-
-  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.
-
-  Neither the name of the Indigo Systems Corporation nor the names of its
-  contributors may be used to endorse or promote products derived from this
-  software without specific prior written permission.
-
-  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
-
-****************************************************************************/
-
-#include "crc16.h"
-
-#ifdef _OPT_SIZE
-
-
-/*
- *  ===== ByteCRC16 =====
- *      Calculate (update) the CRC16 for a single 8-bit byte
- */
-int ByteCRC16(int value, int crcin)
-{
-    int k = (((crcin >> 8) ^ value) & 255) << 8;
-    int crc = 0;
-    int bits = 8;
-    do
-    {
-       if (( crc ^ k ) & 0x8000)
-           crc = (crc << 1) ^ 0x1021;
-       else
-           crc <<= 1;
-       k <<= 1;
-    }
-    while (--bits);
-    return ((crcin << 8) ^ crc);
-}
-
-#else
-
-const CRC16 ccitt_16Table[] = {
-   0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
-   0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
-   0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
-   0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
-   0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
-   0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
-   0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
-   0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
-   0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
-   0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
-   0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
-   0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
-   0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
-   0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
-   0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
-   0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
-   0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
-   0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
-   0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
-   0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
-   0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
-   0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
-   0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
-   0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
-   0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
-   0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
-   0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
-   0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
-   0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
-   0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
-   0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
-   0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
-};
-
-#define ByteCRC16(v, crc) \
-       (unsigned short)((crc << 8) ^  ccitt_16Table[((crc >> 8) ^ (v)) & 255])
-
-/*
- *  ===== CalcCRC16Words =====
- *      Calculate the CRC for a buffer of 16-bit words.  Supports both
- *  Little and Big Endian formats using conditional compilation.
- *      Note: minimum count is 1 (0 case not handled)
- */
-CRC16 CalcCRC16Words(unsigned int count, short *buffer) {
-
-    int crc = 0;
-
-    do {
-
-       int value = *buffer++;
-#ifdef _BIG_ENDIAN
-       crc = ByteCRC16(value >> 8, crc);
-       crc = ByteCRC16(value, crc);
-#else
-       crc = ByteCRC16(value, crc);
-       crc = ByteCRC16(value >> 8, crc);
-#endif
-    }
-       while (--count);
-    return (CRC16) crc;
-}
-
-#endif /* _OPT_SIZE */
-
-#ifdef _CRC16_BYTES
-
-/*
- *  ===== CalcCRC16Bytes =====
- *      Calculate the CRC for a buffer of 8-bit words.
- *      Note: minimum count is 1 (0 case not handled)
- */
-CRC16 CalcCRC16Bytes(unsigned int count, char *buffer) {
-
-    int crc = 0;
-
-    do {
-
-       int value = *buffer++;
-       crc = ByteCRC16(value, crc);
-    }
-       while (--count);
-    return crc;
-}
-
-#endif /* _CRC16_BYTES */
-
-
diff --git a/citadel/modules/fulltext/crc16.h b/citadel/modules/fulltext/crc16.h
deleted file mode 100644 (file)
index 9f13af7..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-/****************************************************************************
-
-  Filename:     crc16.h
-  Description:  Cyclic Redundancy Check 16 functions
-  Created:      24-Feb-1999
-
-  Copyright (c) 2002-2003, Indigo Systems Corporation
-  All rights reserved.
-
-  Redistribution and use in source and binary forms, with or without
-  modification, are permitted provided that the following conditions are met:
-
-  Redistributions of source code must retain the above copyright notice, this
-  list of conditions and the following disclaimer.
-
-  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.
-
-  Neither the name of the Indigo Systems Corporation nor the names of its
-  contributors may be used to endorse or promote products derived from this
-  software without specific prior written permission.
-
-  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
-
-****************************************************************************/
-
-#define _CRC16_BYTES   1               /* ig */
-
-#ifndef __CRC16_H__
-#define __CRC16_H__
-
-#ifdef __cplusplus
-extern "C"
-{
-#endif
-
-typedef unsigned short CRC16;
-
-#ifdef _OPT_SIZE
-    int ByteCRC16(int value, int crcin);
-#else
-    CRC16 CalcCRC16Words(unsigned int count, short *buffer);
-#endif
-
-#ifdef _CRC16_BYTES
-    CRC16 CalcCRC16Bytes(unsigned int count, char *buffer);
-#endif
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* __CRC16_H__ */
-
-
-
diff --git a/citadel/modules/fulltext/ft_wordbreaker.c b/citadel/modules/fulltext/ft_wordbreaker.c
deleted file mode 100644 (file)
index b236de3..0000000
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Default wordbreaker module for full text indexing.
- *
- * Copyright (c) 2005-2017 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "database.h"
-#include "msgbase.h"
-#include "control.h"
-#include "ft_wordbreaker.h"
-#include "crc16.h"
-#include "ctdl_module.h"
-
-/*
- * Noise words are not included in search indices.
- * NOTE: if the noise word list is altered in any way, the FT_WORDBREAKER_ID
- * must also be changed, so that the index is rebuilt.
- */
-static char *noise_words[] = {
-       "about",
-       "after",
-       "also",
-       "another",
-       "because",
-       "been",
-       "before",
-       "being",
-       "between",
-       "both",
-       "came",
-       "come",
-       "could",
-       "each",
-       "from",
-       "have",
-       "here",
-       "himself",
-       "into",
-       "like",
-       "make",
-       "many",
-       "might",
-       "more",
-       "most",
-       "much",
-       "must",
-       "never",
-       "only",
-       "other",
-       "over",
-       "said",
-       "same",
-       "should",
-       "since",
-       "some",
-       "still",
-       "such",
-       "take",
-       "than",
-       "that",
-       "their",
-       "them",
-       "then",
-       "there",
-       "these",
-       "they",
-       "this",
-       "those",
-       "through",
-       "under",
-       "very",
-       "well",
-       "were",
-       "what",
-       "where",
-       "which",
-       "while",
-       "with",
-       "would",
-       "your"
-};
-#define NUM_NOISE (sizeof(noise_words) / sizeof(char *))
-
-
-/*
- * Compare function
- */
-int intcmp(const void *rec1, const void *rec2) {
-       int i1, i2;
-
-       i1 = *(const int *)rec1;
-       i2 = *(const int *)rec2;
-
-       if (i1 > i2) return(1);
-       if (i1 < i2) return(-1);
-       return(0);
-}
-
-
-void wordbreaker(const char *text, int *num_tokens, int **tokens) {
-
-       int wb_num_tokens = 0;
-       int wb_num_alloc = 0;
-       int *wb_tokens = NULL;
-
-       const char *ptr;
-       const char *word_start;
-       const char *word_end;
-       char ch;
-       int word_len;
-       char word[256];
-       int i;
-       int word_crc;
-       
-       if (text == NULL) {             /* no NULL text please */
-               *num_tokens = 0;
-               *tokens = NULL;
-               return;
-       }
-
-       if (text[0] == 0) {             /* no empty text either */
-               *num_tokens = 0;
-               *tokens = NULL;
-               return;
-       }
-
-       ptr = text;
-       word_start = NULL;
-       while (*ptr) {
-               ch = *ptr;
-               if (isalnum(ch)) {
-                       if (!word_start) {
-                               word_start = ptr;
-                       }
-               }
-               ++ptr;
-               ch = *ptr;
-               if ( (!isalnum(ch)) && (word_start) ) {
-                       word_end = ptr;
-
-                       /* extract the word */
-                       word_len = word_end - word_start;
-                       if (word_len >= sizeof word) {
-                               syslog(LOG_DEBUG, "wordbreaker: invalid word length: %d", word_len);
-                               safestrncpy(word, word_start, sizeof word);
-                               word[(sizeof word) - 1] = 0;
-                       }
-                       else {
-                               safestrncpy(word, word_start, word_len+1);
-                               word[word_len] = 0;
-                       }
-                       word_start = NULL;
-
-                       /* are we ok with the length? */
-                       if ( (word_len >= WB_MIN) && (word_len <= WB_MAX) ) {
-                               for (i=0; i<word_len; ++i) {
-                                       word[i] = tolower(word[i]);
-                               }
-                               /* disqualify noise words */
-                               for (i=0; i<NUM_NOISE; ++i) {
-                                       if (!strcmp(word, noise_words[i])) {
-                                               word_len = 0;
-                                               break;
-                                       }
-                               }
-
-                               if (word_len == 0)
-                                       continue;
-
-                               word_crc = (int) CalcCRC16Bytes(word_len, word);
-
-                               ++wb_num_tokens;
-                               if (wb_num_tokens > wb_num_alloc) {
-                                       wb_num_alloc += 512;
-                                       wb_tokens = realloc(wb_tokens, (sizeof(int) * wb_num_alloc));
-                               }
-                               wb_tokens[wb_num_tokens - 1] = word_crc;
-                       }
-               }
-       }
-
-       /* sort and purge dups */
-       if (wb_num_tokens > 1) {
-               qsort(wb_tokens, wb_num_tokens, sizeof(int), intcmp);
-               for (i=0; i<(wb_num_tokens-1); ++i) {
-                       if (wb_tokens[i] == wb_tokens[i+1]) {
-                               memmove(&wb_tokens[i], &wb_tokens[i+1],
-                                       ((wb_num_tokens - i - 1)*sizeof(int)));
-                               --wb_num_tokens;
-                               --i;
-                       }
-               }
-       }
-
-       *num_tokens = wb_num_tokens;
-       *tokens = wb_tokens;
-}
-
diff --git a/citadel/modules/fulltext/ft_wordbreaker.h b/citadel/modules/fulltext/ft_wordbreaker.h
deleted file mode 100644 (file)
index e36d695..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (c) 2005-2021 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-/*
- * This is an ID for the wordbreaker module.  If we do pluggable wordbreakers
- * later on, or even if we update this one, we can use a different ID so the
- * system knows it needs to throw away the existing index and rebuild it.
- */
-#define        FT_WORDBREAKER_ID       0x0021
-
-/*
- * Minimum and maximum length of words to index
- */
-#define WB_MIN                 4       // nothing with 3 or less chars
-#define WB_MAX                 40
-
-void wordbreaker(const char *text, int *num_tokens, int **tokens);
diff --git a/citadel/modules/fulltext/serv_fulltext.c b/citadel/modules/fulltext/serv_fulltext.c
deleted file mode 100644 (file)
index dc5b2b1..0000000
+++ /dev/null
@@ -1,478 +0,0 @@
-/*
- * This module handles fulltext indexing of the message base.
- * Copyright (c) 2005-2021 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "database.h"
-#include "msgbase.h"
-#include "control.h"
-#include "serv_fulltext.h"
-#include "ft_wordbreaker.h"
-#include "threads.h"
-#include "context.h"
-
-#include "ctdl_module.h"
-
-long ft_newhighest = 0L;
-long *ft_newmsgs = NULL;
-int ft_num_msgs = 0;
-int ft_num_alloc = 0;
-
-int ftc_num_msgs[65536];
-long *ftc_msgs[65536];
-
-
-/*
- * Compare function
- */
-int longcmp(const void *rec1, const void *rec2) {
-       long i1, i2;
-
-       i1 = *(const long *)rec1;
-       i2 = *(const long *)rec2;
-
-       if (i1 > i2) return(1);
-       if (i1 < i2) return(-1);
-       return(0);
-}
-
-
-/*
- * Flush our index cache out to disk.
- */
-void ft_flush_cache(void) {
-       int i;
-       time_t last_update = 0;
-
-       for (i=0; i<65536; ++i) {
-               if ((time(NULL) - last_update) >= 10) {
-                       syslog(LOG_INFO,
-                               "fulltext: flushing index cache to disk (%d%% complete)",
-                               (i * 100 / 65536)
-                       );
-                       last_update = time(NULL);
-               }
-               if (ftc_msgs[i] != NULL) {
-                       cdb_store(CDB_FULLTEXT, &i, sizeof(int), ftc_msgs[i],
-                               (ftc_num_msgs[i] * sizeof(long)));
-                       ftc_num_msgs[i] = 0;
-                       free(ftc_msgs[i]);
-                       ftc_msgs[i] = NULL;
-               }
-       }
-       syslog(LOG_INFO, "fulltext: flushed index cache to disk (100%% complete)");
-}
-
-
-/*
- * Index or de-index a message.  (op == 1 to index, 0 to de-index)
- */
-void ft_index_message(long msgnum, int op) {
-       int num_tokens = 0;
-       int *tokens = NULL;
-       int i, j;
-       struct cdbdata *cdb_bucket;
-       StrBuf *msgtext;
-       char *txt;
-       int tok;
-       struct CtdlMessage *msg = NULL;
-
-       msg = CtdlFetchMessage(msgnum, 1);
-       if (msg == NULL) {
-               syslog(LOG_ERR, "fulltext: ft_index_message() could not load msg %ld", msgnum);
-               return;
-       }
-
-       if (!CM_IsEmpty(msg, eSuppressIdx)) {
-               syslog(LOG_DEBUG, "fulltext: ft_index_message() excluded msg %ld", msgnum);
-               CM_Free(msg);
-               return;
-       }
-
-       syslog(LOG_DEBUG, "fulltext: ft_index_message() %s msg %ld", (op ? "adding" : "removing") , msgnum);
-
-       /* Output the message as text before indexing it, so we don't end up
-        * indexing a bunch of encoded base64, etc.
-        */
-       CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
-       CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_ALL, 0, 1, 0);
-       CM_Free(msg);
-       msgtext = CC->redirect_buffer;
-       CC->redirect_buffer = NULL;
-       if (msgtext != NULL) {
-               syslog(LOG_DEBUG, "fulltext: wordbreaking message %ld (%d bytes)", msgnum, StrLength(msgtext));
-       }
-       txt = SmashStrBuf(&msgtext);
-       wordbreaker(txt, &num_tokens, &tokens);
-       free(txt);
-
-       syslog(LOG_DEBUG, "fulltext: indexing message %ld [%d tokens]", msgnum, num_tokens);
-       if (num_tokens > 0) {
-               for (i=0; i<num_tokens; ++i) {
-
-                       /* Add the message to the relevant token bucket */
-
-                       /* search for tokens[i] */
-                       tok = tokens[i];
-
-                       if ( (tok >= 0) && (tok <= 65535) ) {
-                               /* fetch the bucket, Liza */
-                               if (ftc_msgs[tok] == NULL) {
-                                       cdb_bucket = cdb_fetch(CDB_FULLTEXT, &tok, sizeof(int));
-                                       if (cdb_bucket != NULL) {
-                                               ftc_num_msgs[tok] = cdb_bucket->len / sizeof(long);
-                                               ftc_msgs[tok] = (long *)cdb_bucket->ptr;
-                                               cdb_bucket->ptr = NULL;
-                                               cdb_free(cdb_bucket);
-                                       }
-                                       else {
-                                               ftc_num_msgs[tok] = 0;
-                                               ftc_msgs[tok] = malloc(sizeof(long));
-                                       }
-                               }
-       
-       
-                               if (op == 1) {  /* add to index */
-                                       ++ftc_num_msgs[tok];
-                                       ftc_msgs[tok] = realloc(ftc_msgs[tok],
-                                                               ftc_num_msgs[tok]*sizeof(long));
-                                       ftc_msgs[tok][ftc_num_msgs[tok] - 1] = msgnum;
-                               }
-       
-                               if (op == 0) {  /* remove from index */
-                                       if (ftc_num_msgs[tok] >= 1) {
-                                               for (j=0; j<ftc_num_msgs[tok]; ++j) {
-                                                       if (ftc_msgs[tok][j] == msgnum) {
-                                                               memmove(&ftc_msgs[tok][j], &ftc_msgs[tok][j+1], ((ftc_num_msgs[tok] - j - 1)*sizeof(long)));
-                                                               --ftc_num_msgs[tok];
-                                                               --j;
-                                                       }
-                                               }
-                                       }
-                               }
-                       }
-                       else {
-                               syslog(LOG_ALERT, "fulltext: invalid token %d !!", tok);
-                       }
-               }
-
-               free(tokens);
-       }
-}
-
-
-/*
- * Add a message to the list of those to be indexed.
- */
-void ft_index_msg(long msgnum, void *userdata) {
-
-       if ((msgnum > CtdlGetConfigLong("MMfulltext")) && (msgnum <= ft_newhighest)) {
-               ++ft_num_msgs;
-               if (ft_num_msgs > ft_num_alloc) {
-                       ft_num_alloc += 1024;
-                       ft_newmsgs = realloc(ft_newmsgs, (ft_num_alloc * sizeof(long)));
-               }
-               ft_newmsgs[ft_num_msgs - 1] = msgnum;
-       }
-
-}
-
-
-/*
- * Scan a room for messages to index.
- */
-void ft_index_room(struct ctdlroom *qrbuf, void *data)
-{
-       if (server_shutting_down)
-               return;
-               
-       CtdlGetRoom(&CC->room, qrbuf->QRname);
-       CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, ft_index_msg, NULL);
-}
-
-
-/*
- * Begin the fulltext indexing process.
- */
-void do_fulltext_indexing(void) {
-       int i;
-       static time_t last_progress = 0L;
-       static int is_running = 0;
-       if (is_running) return;         /* Concurrency check - only one can run */
-       is_running = 1;
-
-       /*
-        * Don't do this if the site doesn't have it enabled.
-        */
-       if (!CtdlGetConfigInt("c_enable_fulltext")) {
-               return;
-       }
-
-       /*
-        * If we've switched wordbreaker modules, burn the index and start over.
-        */
-       begin_critical_section(S_CONTROL);
-       if (CtdlGetConfigInt("MM_fulltext_wordbreaker") != FT_WORDBREAKER_ID) {
-               syslog(LOG_DEBUG, "fulltext: wb ver on disk = %d, code ver = %d",
-                       CtdlGetConfigInt("MM_fulltext_wordbreaker"), FT_WORDBREAKER_ID
-               );
-               syslog(LOG_INFO, "fulltext: (re)initializing index");
-               cdb_trunc(CDB_FULLTEXT);
-               CtdlSetConfigLong("MMfulltext", 0);
-       }
-       end_critical_section(S_CONTROL);
-
-       /*
-        * Silently return if our fulltext index is up to date with new messages.
-        */
-       if ((CtdlGetConfigLong("MMfulltext") >= CtdlGetConfigLong("MMhighest"))) {
-               return;         /* nothing to do! */
-       }
-
-       /*
-        * Now go through each room and find messages to index.
-        */
-       ft_newhighest = CtdlGetConfigLong("MMhighest");
-       CtdlForEachRoom(ft_index_room, NULL);   /* load all msg pointers */
-
-       if (ft_num_msgs > 0) {
-               qsort(ft_newmsgs, ft_num_msgs, sizeof(long), longcmp);
-               for (i=0; i<(ft_num_msgs-1); ++i) { /* purge dups */
-                       if (ft_newmsgs[i] == ft_newmsgs[i+1]) {
-                               memmove(&ft_newmsgs[i], &ft_newmsgs[i+1],
-                                       ((ft_num_msgs - i - 1)*sizeof(long)));
-                               --ft_num_msgs;
-                               --i;
-                       }
-               }
-
-               /* Here it is ... do each message! */
-               for (i=0; i<ft_num_msgs; ++i) {
-                       if (time(NULL) != last_progress) {
-                               syslog(LOG_DEBUG,
-                                       "fulltext: indexed %d of %d messages (%d%%)",
-                                               i, ft_num_msgs,
-                                               ((i*100) / ft_num_msgs)
-                               );
-                               last_progress = time(NULL);
-                       }
-                       ft_index_message(ft_newmsgs[i], 1);
-
-                       /* Check to see if we need to quit early */
-                       if (server_shutting_down) {
-                               syslog(LOG_DEBUG, "fulltext: indexer quitting early");
-                               ft_newhighest = ft_newmsgs[i];
-                               break;
-                       }
-
-                       /* Check to see if we have to maybe flush to disk */
-                       if (i >= FT_MAX_CACHE) {
-                               syslog(LOG_DEBUG, "fulltext: time to flush.");
-                               ft_newhighest = ft_newmsgs[i];
-                               break;
-                       }
-
-               }
-
-               free(ft_newmsgs);
-               ft_num_msgs = 0;
-               ft_num_alloc = 0;
-               ft_newmsgs = NULL;
-       }
-
-       if (server_shutting_down) {
-               is_running = 0;
-               return;
-       }
-       
-       /* Save our place so we don't have to do this again */
-       ft_flush_cache();
-       begin_critical_section(S_CONTROL);
-       CtdlSetConfigLong("MMfulltext", ft_newhighest);
-       CtdlSetConfigInt("MM_fulltext_wordbreaker", FT_WORDBREAKER_ID);
-       end_critical_section(S_CONTROL);
-
-       syslog(LOG_DEBUG, "fulltext: indexing finished");
-       is_running = 0;
-       return;
-}
-
-
-/*
- * API call to perform searches.
- * (This one does the "all of these words" search.)
- * Caller is responsible for freeing the message list.
- */
-void ft_search(int *fts_num_msgs, long **fts_msgs, const char *search_string) {
-       int num_tokens = 0;
-       int *tokens = NULL;
-       int i, j;
-       struct cdbdata *cdb_bucket;
-       int num_all_msgs = 0;
-       long *all_msgs = NULL;
-       int num_ret_msgs = 0;
-       int num_ret_alloc = 0;
-       long *ret_msgs = NULL;
-       int tok;
-
-       wordbreaker(search_string, &num_tokens, &tokens);
-       if (num_tokens > 0) {
-               for (i=0; i<num_tokens; ++i) {
-
-                       /* search for tokens[i] */
-                       tok = tokens[i];
-
-                       /* fetch the bucket, Liza */
-                       if (ftc_msgs[tok] == NULL) {
-                               cdb_bucket = cdb_fetch(CDB_FULLTEXT, &tok, sizeof(int));
-                               if (cdb_bucket != NULL) {
-                                       ftc_num_msgs[tok] = cdb_bucket->len / sizeof(long);
-                                       ftc_msgs[tok] = (long *)cdb_bucket->ptr;
-                                       cdb_bucket->ptr = NULL;
-                                       cdb_free(cdb_bucket);
-                               }
-                               else {
-                                       ftc_num_msgs[tok] = 0;
-                                       ftc_msgs[tok] = malloc(sizeof(long));
-                               }
-                       }
-
-                       num_all_msgs += ftc_num_msgs[tok];
-                       if (num_all_msgs > 0) {
-                               all_msgs = realloc(all_msgs, num_all_msgs*sizeof(long) );
-                               memcpy(&all_msgs[num_all_msgs-ftc_num_msgs[tok]],
-                                       ftc_msgs[tok], ftc_num_msgs[tok]*sizeof(long) );
-                       }
-
-               }
-               free(tokens);
-               if (all_msgs != NULL) {
-                       qsort(all_msgs, num_all_msgs, sizeof(long), longcmp);
-
-                       /*
-                        * At this point, if a message appears num_tokens times in the
-                        * list, then it contains all of the search tokens.
-                        */
-                       if (num_all_msgs >= num_tokens)
-                               for (j=0; j<(num_all_msgs-num_tokens+1); ++j) {
-                                       if (all_msgs[j] == all_msgs[j+num_tokens-1]) {
-                                               
-                                               ++num_ret_msgs;
-                                               if (num_ret_msgs > num_ret_alloc) {
-                                                       num_ret_alloc += 64;
-                                                       ret_msgs = realloc(ret_msgs,
-                                                                          (num_ret_alloc*sizeof(long)) );
-                                               }
-                                               ret_msgs[num_ret_msgs - 1] = all_msgs[j];
-                                               
-                                       }
-                               }
-                       free(all_msgs);
-               }
-       }
-
-       *fts_num_msgs = num_ret_msgs;
-       *fts_msgs = ret_msgs;
-}
-
-
-/*
- * This search command is for diagnostic purposes and may be removed or replaced.
- */
-void cmd_srch(char *argbuf) {
-       int num_msgs = 0;
-       long *msgs = NULL;
-       int i;
-       char search_string[256];
-
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       if (!CtdlGetConfigInt("c_enable_fulltext")) {
-               cprintf("%d Full text index is not enabled on this server.\n",
-                       ERROR + CMD_NOT_SUPPORTED);
-               return;
-       }
-
-       extract_token(search_string, argbuf, 0, '|', sizeof search_string);
-       ft_search(&num_msgs, &msgs, search_string);
-
-       cprintf("%d %d msgs match all search words:\n",
-               LISTING_FOLLOWS, num_msgs);
-       if (num_msgs > 0) {
-               for (i=0; i<num_msgs; ++i) {
-                       cprintf("%ld\n", msgs[i]);
-               }
-       }
-       if (msgs != NULL) free(msgs);
-       cprintf("000\n");
-}
-
-
-/*
- * Zero out our index cache.
- */
-void initialize_ft_cache(void) {
-       memset(ftc_num_msgs, 0, (65536 * sizeof(int)));
-       memset(ftc_msgs, 0, (65536 * sizeof(long *)));
-}
-
-
-void ft_delete_remove(char *room, long msgnum)
-{
-       if (room) return;
-       
-       /* Remove from fulltext index */
-       if (CtdlGetConfigInt("c_enable_fulltext")) {
-               ft_index_message(msgnum, 0);
-       }
-}
-
-
-/*****************************************************************************/
-
-CTDL_MODULE_INIT(fulltext)
-{
-       if (!threading)
-       {
-               initialize_ft_cache();
-               CtdlRegisterProtoHook(cmd_srch, "SRCH", "Full text search");
-               CtdlRegisterDeleteHook(ft_delete_remove);
-               CtdlRegisterSearchFuncHook(ft_search, "fulltext");
-               CtdlRegisterSessionHook(do_fulltext_indexing, EVT_TIMER, PRIO_CLEANUP + 300);
-       }
-       /* return our module name for the log */
-       return "fulltext";
-}
diff --git a/citadel/modules/fulltext/serv_fulltext.h b/citadel/modules/fulltext/serv_fulltext.h
deleted file mode 100644 (file)
index bc05c51..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (c) 2005-2012 by the citadel.org team
- *
- *  This program is open source software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License version 3.
- *  
- *  
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  
- *  
- *  
- */
-
-void ft_index_message(long msgnum, int op);
-void ft_search(int *fts_num_msgs, long **fts_msgs, const char *search_string);
-void *indexer_thread(void *arg);
diff --git a/citadel/modules/image/serv_image.c b/citadel/modules/image/serv_image.c
deleted file mode 100644 (file)
index 6be5afc..0000000
+++ /dev/null
@@ -1,355 +0,0 @@
-/*
- * Copyright (c) 1987-2021 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "ctdl_module.h"
-#include "config.h"
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <dirent.h>
-
-
-/*
- * DownLoad Room Image (see its icon or whatever)
- * If this command succeeds, it follows the same protocol as the DLAT command.
- */
-void cmd_dlri(char *cmdbuf) {
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-       if (CC->room.msgnum_pic < 1) {
-               cprintf("%d No image found.\n", ERROR + FILE_NOT_FOUND);
-               return;
-       }
-
-       struct CtdlMessage *msg = CtdlFetchMessage(CC->room.msgnum_pic, 1);
-       if (msg != NULL) {
-               // The call to CtdlOutputPreLoadedMsg() with MT_SPEW_SECTION will cause the DLRI command
-               // to have the same output format as the DLAT command, because it calls the same code.
-               // For example: 600 402132|-1||image/gif|
-               safestrncpy(CC->download_desired_section, "1", sizeof CC->download_desired_section);
-               CtdlOutputPreLoadedMsg(msg, MT_SPEW_SECTION, HEADERS_NONE, 1, 0, 0);
-               CM_Free(msg);
-       }
-       else {
-               cprintf("%d No image found.\n", ERROR + MESSAGE_NOT_FOUND);
-               return;
-       }
-}
-
-
-/*
- * UpLoad Room Image (avatar or photo or whatever)
- */
-void cmd_ulri(char *cmdbuf) {
-       long data_length;
-       char mimetype[SIZ];
-
-       if (CtdlAccessCheck(ac_room_aide)) return;
-
-       data_length = extract_long(cmdbuf, 0);
-       extract_token(mimetype, cmdbuf, 1, '|', sizeof mimetype);
-
-       if (data_length < 20) {
-               cprintf("%d That's an awfully small file.  Try again.\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       if (strncasecmp(mimetype, "image/", 6)) {
-               cprintf("%d Only image files are permitted.\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       char *unencoded_data = malloc(data_length + 1);
-       if (!unencoded_data) {
-               cprintf("%d Could not allocate %ld bytes of memory\n", ERROR + INTERNAL_ERROR , data_length);
-               return;
-       }
-
-       cprintf("%d %ld\n", SEND_BINARY, data_length);
-       client_read(unencoded_data, data_length);
-
-       // We've got the data read from the client, now save it.
-       char *encoded_data = malloc((data_length * 2) + 100);
-       if (encoded_data) {
-               sprintf(encoded_data, "Content-type: %s\nContent-transfer-encoding: base64\n\n", mimetype);
-               CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
-               long new_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, encoded_data, FMT_RFC822, "Image uploaded by admin user");
-
-               if (CtdlGetRoomLock(&CC->room, CC->room.QRname) == 0) {
-                       long old_msgnum = CC->room.msgnum_pic;
-                       syslog(LOG_DEBUG, "Message %ld is now the photo for %s", new_msgnum, CC->room.QRname);
-                       CC->room.msgnum_pic = new_msgnum;
-                       CtdlPutRoomLock(&CC->room);
-                       if (old_msgnum > 0) {
-                               syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, SYSCONFIGROOM);
-                               CtdlDeleteMessages(SYSCONFIGROOM, &old_msgnum, 1, "");
-                       }
-               }
-               free(encoded_data);
-       }
-
-       free(unencoded_data);
-}
-
-
-/*
- * DownLoad User Image (see their avatar or photo or whatever)
- * If this command succeeds, it follows the same protocol as the DLAT command.
- */
-void cmd_dlui(char *cmdbuf) {
-       struct ctdluser ruser;
-       char buf[SIZ];
-
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-       extract_token(buf, cmdbuf, 0, '|', sizeof buf);
-       if (CtdlGetUser(&ruser, buf) != 0) {
-               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
-               return;
-       }
-       if (ruser.msgnum_pic < 1) {
-               cprintf("%d No image found.\n", ERROR + FILE_NOT_FOUND);
-               return;
-       }
-
-       struct CtdlMessage *msg = CtdlFetchMessage(ruser.msgnum_pic, 1);
-       if (msg != NULL) {
-               // The call to CtdlOutputPreLoadedMsg() with MT_SPEW_SECTION will cause the DLUI command
-               // to have the same output format as the DLAT command, because it calls the same code.
-               // For example: 600 402132|-1||image/gif|
-               safestrncpy(CC->download_desired_section, "1", sizeof CC->download_desired_section);
-               CtdlOutputPreLoadedMsg(msg, MT_SPEW_SECTION, HEADERS_NONE, 1, 0, 0);
-               CM_Free(msg);
-       }
-       else {
-               cprintf("%d No image found.\n", ERROR + MESSAGE_NOT_FOUND);
-               return;
-       }
-}
-
-
-/*
- * UpLoad User Image (avatar or photo or whatever)
- */
-void cmd_ului(char *cmdbuf) {
-       long data_length;
-       char mimetype[SIZ];
-       char username[USERNAME_SIZE];
-       char userconfigroomname[ROOMNAMELEN];
-
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
-       if (num_parms(cmdbuf) < 2) {
-               cprintf("%d Usage error\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       data_length = extract_long(cmdbuf, 0);
-       extract_token(mimetype, cmdbuf, 1, '|', sizeof mimetype);
-       extract_token(username, cmdbuf, 2, '|', sizeof username);
-
-       if (data_length < 20) {
-               cprintf("%d That's an awfully small file.  Try again.\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       if (strncasecmp(mimetype, "image/", 6)) {
-               cprintf("%d Only image files are permitted.\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       if (IsEmptyStr(username)) {
-               safestrncpy(username, CC->curr_user, sizeof username);
-       }
-
-       // Normal users can only change their own photo
-       if ( (strcasecmp(username, CC->curr_user)) && (CC->user.axlevel < AxAideU) && (!CC->internal_pgm) ) {
-               cprintf("%d Higher access required to change another user's photo.\n", ERROR + HIGHER_ACCESS_REQUIRED);
-       }
-
-       // Check to make sure the user exists
-       struct ctdluser usbuf;
-       if (CtdlGetUser(&usbuf, username) != 0) {               // check for existing user, don't lock it yet
-               cprintf("%d %s not found.\n", ERROR + NO_SUCH_USER , username);
-               return;
-       }
-       CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &usbuf, USERCONFIGROOM);
-
-       char *unencoded_data = malloc(data_length + 1);
-       if (!unencoded_data) {
-               cprintf("%d Could not allocate %ld bytes of memory\n", ERROR + INTERNAL_ERROR , data_length);
-               return;
-       }
-
-       cprintf("%d %ld\n", SEND_BINARY, data_length);
-       client_read(unencoded_data, data_length);
-
-       // We've got the data read from the client, now save it.
-       char *encoded_data = malloc((data_length * 2) + 100);
-       if (encoded_data) {
-               sprintf(encoded_data, "Content-type: %s\nContent-transfer-encoding: base64\n\n", mimetype);
-               CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
-               long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, encoded_data, FMT_RFC822, "Photo uploaded by user");
-
-               if (CtdlGetUserLock(&usbuf, username) == 0) {   // lock it this time
-                       long old_msgnum = usbuf.msgnum_pic;
-                       syslog(LOG_DEBUG, "Message %ld is now the photo for %s", new_msgnum, username);
-                       usbuf.msgnum_pic = new_msgnum;
-                       CtdlPutUserLock(&usbuf);
-                       if (old_msgnum > 0) {
-                               syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
-                               CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
-                       }
-               }
-
-               free(encoded_data);
-       }
-
-       free(unencoded_data);
-}
-
-
-/*
- * Import function called by import_old_userpic_files() for a single user
- */
-void import_one_userpic_file(char *username, long usernum, char *path) {
-       syslog(LOG_DEBUG, "Import legacy userpic for %s, usernum=%ld, filename=%s", username, usernum, path);
-
-       FILE *fp = fopen(path, "r");
-       if (!fp) return;
-
-       fseek(fp, 0, SEEK_END);
-       long data_length = ftell(fp);
-
-       if (data_length >= 1) {
-               rewind(fp);
-               char *unencoded_data = malloc(data_length);
-               if (unencoded_data) {
-                       fread(unencoded_data, data_length, 1, fp);
-                       char *encoded_data = malloc((data_length * 2) + 100);
-                       if (encoded_data) {
-                               sprintf(encoded_data, "Content-type: %s\nContent-transfer-encoding: base64\n\n", GuessMimeByFilename(path, strlen(path)));
-                               CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
-
-                               char userconfigroomname[ROOMNAMELEN];
-                               struct ctdluser usbuf;
-
-                               if (CtdlGetUser(&usbuf, username) == 0) {       // no need to lock it , we are still initializing
-                                       long old_msgnum = usbuf.msgnum_pic;
-                                       CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &usbuf, USERCONFIGROOM);
-                                       long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, encoded_data, FMT_RFC822, "Photo imported from file");
-                                       syslog(LOG_DEBUG, "Message %ld is now the photo for %s", new_msgnum, username);
-                                       usbuf.msgnum_pic = new_msgnum;
-                                       CtdlPutUser(&usbuf);
-                                       unlink(path);                           // delete the old file , it's in the database now
-                                       if (old_msgnum > 0) {
-                                               syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
-                                               CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
-                                       }
-                               }
-                               free(encoded_data);
-                       }
-                       free(unencoded_data);
-               }
-       }
-       fclose(fp);
-}
-
-
-/*
- * Look for old-format "userpic" files and import them into the message base
- */
-void import_old_userpic_files(void) {
-       DIR *filedir = NULL;
-       struct dirent *filedir_entry;
-       size_t d_namelen;
-       struct ctdluser usbuf;
-       long usernum = 0;
-       int d_type = 0;
-       struct stat s;
-       char path[PATH_MAX];
-
-
-       syslog(LOG_DEBUG, "Importing old style userpic files into the message base");
-       filedir = opendir (ctdl_usrpic_dir);
-       if (filedir == NULL) {
-               return;
-       }
-       while ( (filedir_entry = readdir(filedir)) , (filedir_entry != NULL))
-       {
-#ifdef _DIRENT_HAVE_D_NAMLEN
-               d_namelen = filedir_entry->d_namlen;
-
-#else
-               d_namelen = strlen(filedir_entry->d_name);
-#endif
-
-#ifdef _DIRENT_HAVE_D_TYPE
-               d_type = filedir_entry->d_type;
-#else
-
-#ifndef DT_UNKNOWN
-#define DT_UNKNOWN     0
-#define DT_DIR         4
-#define DT_REG         8
-#define DT_LNK         10
-
-#define IFTODT(mode)   (((mode) & 0170000) >> 12)
-#define DTTOIF(dirtype)        ((dirtype) << 12)
-#endif
-               d_type = DT_UNKNOWN;
-#endif
-               if ((d_namelen == 1) && 
-                   (filedir_entry->d_name[0] == '.'))
-                       continue;
-
-               if ((d_namelen == 2) && 
-                   (filedir_entry->d_name[0] == '.') &&
-                   (filedir_entry->d_name[1] == '.'))
-                       continue;
-
-               snprintf(path, PATH_MAX, "%s/%s", ctdl_usrpic_dir, filedir_entry->d_name);
-               if (d_type == DT_UNKNOWN) {
-                       if (lstat(path, &s) == 0) {
-                               d_type = IFTODT(s.st_mode);
-                       }
-               }
-               switch (d_type)
-               {
-               case DT_DIR:
-                       break;
-               case DT_LNK:
-               case DT_REG:
-                       usernum = atol(filedir_entry->d_name);
-                       if (CtdlGetUserByNumber(&usbuf, usernum) == 0) {
-                               import_one_userpic_file(usbuf.fullname, usernum, path);
-                       }
-               }
-       }
-       closedir(filedir);
-       rmdir(ctdl_usrpic_dir);
-}
-
-
-
-CTDL_MODULE_INIT(image)
-{
-       if (!threading)
-       {
-               import_old_userpic_files();
-               CtdlRegisterProtoHook(cmd_dlri, "DLRI", "DownLoad Room Image");
-               CtdlRegisterProtoHook(cmd_ulri, "ULRI", "UpLoad Room Image");
-               CtdlRegisterProtoHook(cmd_dlui, "DLUI", "DownLoad User Image");
-               CtdlRegisterProtoHook(cmd_ului, "ULUI", "UpLoad User Image");
-       }
-       /* return our module name for the log */
-        return "image";
-}
diff --git a/citadel/modules/imap/.gitignore b/citadel/modules/imap/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/imap/imap_acl.c b/citadel/modules/imap/imap_acl.c
deleted file mode 100644 (file)
index 97b5f96..0000000
+++ /dev/null
@@ -1,330 +0,0 @@
-/*
- * Functions which implement RFC2086 (and maybe RFC4314) (IMAP ACL extension)
- *
- * Copyright (c) 2007-2017 by the citadel.org team
- *
- *  This program is open source software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "imap_fetch.h"
-#include "imap_misc.h"
-#include "genstamp.h"
-#include "ctdl_module.h"
-
-
-/*
- * Implements the SETACL command.
- */
-void imap_setacl(int num_parms, ConstStr *Params) {
-
-       IReply("BAD not yet implemented FIXME");
-       return;
-}
-
-
-/*
- * Implements the DELETEACL command.
- */
-void imap_deleteacl(int num_parms, ConstStr *Params) {
-
-       IReply("BAD not yet implemented FIXME");
-       return;
-}
-
-
-/*
- * Given the bits returned by CtdlRoomAccess(), populate a string buffer
- * with IMAP ACL format flags.   This code is common to GETACL and MYRIGHTS.
- */
-void imap_acl_flags(StrBuf *rights, int ra)
-{
-       FlushStrBuf(rights);
-
-       /* l - lookup (mailbox is visible to LIST/LSUB commands)
-        * r - read (SELECT the mailbox, perform STATUS et al)
-        * s - keep seen/unseen information across sessions (STORE SEEN flag)
-        */
-       if (    (ra & UA_KNOWN)                                 /* known rooms */
-          ||   ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED))     /* zapped rooms */
-       ) {
-               StrBufAppendBufPlain(rights, HKEY("l"), 0);
-               StrBufAppendBufPlain(rights, HKEY("r"), 0);
-               StrBufAppendBufPlain(rights, HKEY("s"), 0);
-
-               /* Only output the remaining flags if the room is known */
-
-               /* w - write (set or clear flags other than SEEN or DELETED, not supported in Citadel */
-
-               /* i - insert (perform APPEND, COPY into mailbox) */
-               /* p - post (send mail to submission address for mailbox - not enforced) */
-               /* c - create (CREATE new sub-mailboxes) */
-               if (ra & UA_POSTALLOWED) {
-                       StrBufAppendBufPlain(rights, HKEY("i"), 0);
-                       StrBufAppendBufPlain(rights, HKEY("p"), 0);
-                       StrBufAppendBufPlain(rights, HKEY("c"), 0);
-               }
-
-               /* d - delete messages (STORE DELETED flag, perform EXPUNGE) */
-               if (ra & UA_DELETEALLOWED) {
-                       StrBufAppendBufPlain(rights, HKEY("d"), 0);
-               }
-
-               /* a - administer (perform SETACL/DELETEACL/GETACL/LISTRIGHTS) */
-               if (ra & UA_ADMINALLOWED) {
-                       /*
-                        * This is the correct place to put the "a" flag.  We are leaving
-                        * it commented out for now, because it implies that we could
-                        * perform any of SETACL/DELETEACL/GETACL/LISTRIGHTS.  Since these
-                        * commands are not yet implemented, omitting the flag should
-                        * theoretically prevent compliant clients from attempting to
-                        * perform them.
-                        *
-                        * StrBufAppendBufPlain(rights, HKEY("a"), 0);
-                        */
-               }
-       }
-}
-
-
-/*
- * Implements the GETACL command.
- */
-void imap_getacl(int num_parms, ConstStr *Params) {
-       char roomname[ROOMNAMELEN];
-       char savedroom[ROOMNAMELEN];
-       int msgs, new;
-       int ret;
-       struct ctdluser temp;
-       struct cdbdata *cdbus;
-       int ra;
-       StrBuf *rights;
-
-       if (num_parms != 3) {
-               IReply("BAD usage error");
-               return;
-       }
-
-       /*
-        * Search for the specified room or folder
-        */
-       ret = imap_grabroom(roomname, Params[2].Key, 1);
-       if (ret != 0) {
-               IReply("NO Invalid mailbox name or access denied");
-               return;
-       }
-
-       /*
-        * CtdlUserGoto() formally takes us to the desired room.  (If another
-        * folder is selected, save its name so we can return there!!!!!)
-        */
-       if (IMAP->selected) {
-               strcpy(savedroom, CC->room.QRname);
-       }
-       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
-       IAPuts("* ACL ");
-       IPutCParamStr(2);
-
-       /*
-        * Traverse the userlist
-        */
-       rights = NewStrBuf();
-       cdb_rewind(CDB_USERS);
-       while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) 
-       {
-               memset(&temp, 0, sizeof temp);
-               memcpy(&temp, cdbus->ptr, sizeof temp);
-               cdb_free(cdbus);
-
-               CtdlRoomAccess(&CC->room, &temp, &ra, NULL);
-               if (!IsEmptyStr(temp.fullname)) {
-                       imap_acl_flags(rights, ra);
-                       if (StrLength(rights) > 0) {
-                               IAPuts(" ");
-                               IPutStr(temp.fullname, strlen(temp.fullname));
-                               IAPuts(" ");
-                               iaputs(SKEY( rights));
-                       }
-               }
-       }
-       FreeStrBuf(&rights);
-       IAPuts("\r\n");
-
-       /*
-        * If another folder is selected, go back to that room so we can resume
-        * our happy day without violent explosions.
-        */
-       if (IMAP->selected) {
-               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
-       }
-
-       IReply("OK GETACL completed");
-}
-
-
-/*
- * Implements the LISTRIGHTS command.
- */
-void imap_listrights(int num_parms, ConstStr *Params) {
-       char roomname[ROOMNAMELEN];
-       char savedroom[ROOMNAMELEN];
-       int msgs, new;
-       int ret;
-       struct recptypes *valid;
-       struct ctdluser temp;
-
-       if (num_parms != 4) {
-               IReply("BAD usage error");
-               return;
-       }
-
-       /*
-        * Search for the specified room/folder
-        */
-       ret = imap_grabroom(roomname, Params[2].Key, 1);
-       if (ret != 0) {
-               IReply("NO Invalid mailbox name or access denied");
-               return;
-       }
-
-       /*
-        * Search for the specified user
-        */
-       ret = (-1);
-       valid = validate_recipients(Params[3].Key, NULL, 0);
-       if (valid != NULL) {
-               if (valid->num_local == 1) {
-                       ret = CtdlGetUser(&temp, valid->recp_local);
-               }
-               free_recipients(valid);
-       }
-       if (ret != 0) {
-               IReply("NO Invalid user name or access denied");
-               return;
-       }
-
-       /*
-        * CtdlUserGoto() formally takes us to the desired room.  (If another
-        * folder is selected, save its name so we can return there!!!!!)
-        */
-       if (IMAP->selected) {
-               strcpy(savedroom, CC->room.QRname);
-       }
-       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
-       /*
-        * Now output the list of rights
-        */
-       IAPuts("* LISTRIGHTS ");
-       IPutCParamStr(2);
-       IAPuts(" ");
-       IPutCParamStr(3);
-       IAPuts(" ");
-       IPutStr(HKEY(""));              /* FIXME ... do something here */
-       IAPuts("\r\n");
-
-       /*
-        * If another folder is selected, go back to that room so we can resume
-        * our happy day without violent explosions.
-        */
-       if (IMAP->selected) {
-               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
-       }
-
-       IReply("OK LISTRIGHTS completed");
-       return;
-}
-
-
-/*
- * Implements the MYRIGHTS command.
- */
-void imap_myrights(int num_parms, ConstStr *Params) {
-       char roomname[ROOMNAMELEN];
-       char savedroom[ROOMNAMELEN];
-       int msgs, new;
-       int ret;
-       int ra;
-       StrBuf *rights;
-
-       if (num_parms != 3) {
-               IReply("BAD usage error");
-               return;
-       }
-
-       ret = imap_grabroom(roomname, Params[2].Key, 1);
-       if (ret != 0) {
-               IReply("NO Invalid mailbox name or access denied");
-               return;
-       }
-
-       /*
-        * CtdlUserGoto() formally takes us to the desired room.  (If another
-        * folder is selected, save its name so we can return there!!!!!)
-        */
-       if (IMAP->selected) {
-               strcpy(savedroom, CC->room.QRname);
-       }
-       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
-       CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
-       rights = NewStrBuf();
-       imap_acl_flags(rights, ra);
-
-       IAPuts("* MYRIGHTS ");
-       IPutCParamStr(2);
-       IAPuts(" ");
-       IPutStr(SKEY(rights));
-       IAPuts("\r\n");
-
-       FreeStrBuf(&rights);
-
-       /*
-        * If a different folder was previously selected, return there now.
-        */
-       if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) {
-               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
-       }
-
-       IReply("OK MYRIGHTS completed");
-       return;
-}
diff --git a/citadel/modules/imap/imap_acl.h b/citadel/modules/imap/imap_acl.h
deleted file mode 100644 (file)
index a3b2605..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright (c) 2007-2017 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-void imap_setacl(int num_parms, ConstStr *Params);
-void imap_deleteacl(int num_parms, ConstStr *Params);
-void imap_getacl(int num_parms, ConstStr *Params);
-void imap_listrights(int num_parms, ConstStr *Params);
-void imap_myrights(int num_parms, ConstStr *Params);
-
diff --git a/citadel/modules/imap/imap_fetch.c b/citadel/modules/imap/imap_fetch.c
deleted file mode 100644 (file)
index 1e33fab..0000000
+++ /dev/null
@@ -1,1463 +0,0 @@
-/*
- * Implements the FETCH command in IMAP.
- * This is a good example of the protocol's gratuitous complexity.
- *
- * Copyright (c) 2001-2020 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "imap_fetch.h"
-#include "genstamp.h"
-#include "ctdl_module.h"
-
-
-
-/*
- * Individual field functions for imap_do_fetch_msg() ...
- */
-
-void imap_fetch_uid(int seq) {
-       IAPrintf("UID %ld", IMAP->msgids[seq-1]);
-}
-
-void imap_fetch_flags(int seq) 
-{
-       citimap *Imap = IMAP;
-       int num_flags_printed = 0;
-       IAPuts("FLAGS (");
-       if (Imap->flags[seq] & IMAP_DELETED) {
-               if (num_flags_printed > 0) 
-                       IAPuts(" ");
-               IAPuts("\\Deleted");
-               ++num_flags_printed;
-       }
-       if (Imap->flags[seq] & IMAP_SEEN) {
-               if (num_flags_printed > 0) 
-                       IAPuts(" ");
-               IAPuts("\\Seen");
-               ++num_flags_printed;
-       }
-       if (Imap->flags[seq] & IMAP_ANSWERED) {
-               if (num_flags_printed > 0) 
-                       IAPuts(" ");
-               IAPuts("\\Answered");
-               ++num_flags_printed;
-       }
-       if (Imap->flags[seq] & IMAP_RECENT) {
-               if (num_flags_printed > 0) 
-                       IAPuts(" ");
-               IAPuts("\\Recent");
-               ++num_flags_printed;
-       }
-       IAPuts(")");
-}
-
-
-void imap_fetch_internaldate(struct CtdlMessage *msg) {
-       char datebuf[64];
-       time_t msgdate;
-
-       if (!msg) return;
-       if (!CM_IsEmpty(msg, eTimestamp)) {
-               msgdate = atol(msg->cm_fields[eTimestamp]);
-       }
-       else {
-               msgdate = time(NULL);
-       }
-
-       datestring(datebuf, sizeof datebuf, msgdate, DATESTRING_IMAP);
-       IAPrintf( "INTERNALDATE \"%s\"", datebuf);
-}
-
-
-/*
- * Fetch RFC822-formatted messages.
- *
- * 'whichfmt' should be set to one of:
- *     "RFC822"        entire message
- *     "RFC822.HEADER" headers only (with trailing blank line)
- *     "RFC822.SIZE"   size of translated message
- *     "RFC822.TEXT"   body only (without leading blank line)
- */
-void imap_fetch_rfc822(long msgnum, const char *whichfmt) {
-       CitContext *CCC = CC;
-       citimap *Imap = CCCIMAP;
-       const char *ptr = NULL;
-       size_t headers_size, text_size, total_size;
-       size_t bytes_to_send = 0;
-       struct MetaData smi;
-       int need_to_rewrite_metadata = 0;
-       int need_body = 0;
-
-       /* Determine whether this particular fetch operation requires
-        * us to fetch the message body from disk.  If not, we can save
-        * on some disk operations...
-        */
-       if ( (!strcasecmp(whichfmt, "RFC822")) || (!strcasecmp(whichfmt, "RFC822.TEXT")) ) {
-               need_body = 1;
-       }
-
-       /* If this is an RFC822.SIZE fetch, first look in the message's
-        * metadata record to see if we've saved that information.
-        */
-       if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
-               GetMetaData(&smi, msgnum);
-               if (smi.meta_rfc822_length > 0L) {
-                       IAPrintf("RFC822.SIZE %ld", smi.meta_rfc822_length);
-                       return;
-               }
-               need_to_rewrite_metadata = 1;
-               need_body = 1;
-       }
-       
-       /* Cache the most recent RFC822 FETCH because some clients like to
-        * fetch in pieces, and we don't want to have to go back to the
-        * message store for each piece.  We also burn the cache if the
-        * client requests something that involves reading the message
-        * body, but we haven't fetched the body yet.
-        */
-       if ((Imap->cached_rfc822 != NULL)
-          && (Imap->cached_rfc822_msgnum == msgnum)
-          && (Imap->cached_rfc822_withbody || (!need_body)) ) {
-               /* Good to go! */
-       }
-       else if (Imap->cached_rfc822 != NULL) {
-               /* Some other message is cached -- free it */
-               FreeStrBuf(&Imap->cached_rfc822);
-               Imap->cached_rfc822_msgnum = (-1);
-       }
-
-       /* At this point, we now can fetch and convert the message iff it's not
-        * the one we had cached.
-        */
-       if (Imap->cached_rfc822 == NULL) {
-               /*
-                * Load the message into memory for translation & measurement
-                */
-               CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
-               CtdlOutputMsg(msgnum, MT_RFC822,
-                       (need_body ? HEADERS_ALL : HEADERS_FAST),
-                       0, 1, NULL, SUPPRESS_ENV_TO, NULL, NULL, NULL
-               );
-               if (!need_body) {
-                       client_write(HKEY("\r\n"));             // extra trailing newline -- to the redirect_buffer, *not* to the client
-               }
-               Imap->cached_rfc822 = CCC->redirect_buffer;
-               CCC->redirect_buffer = NULL;
-               Imap->cached_rfc822_msgnum = msgnum;
-               Imap->cached_rfc822_withbody = need_body;
-               if ( (need_to_rewrite_metadata) && 
-                    (StrLength(Imap->cached_rfc822) > 0) ) {
-                       smi.meta_rfc822_length = StrLength(Imap->cached_rfc822);
-                       PutMetaData(&smi);
-               }
-       }
-
-       /*
-        * Now figure out where the headers/text break is.  IMAP considers the
-        * intervening blank line to be part of the headers, not the text.
-        */
-       headers_size = 0;
-
-       if (need_body) {
-               StrBuf *Line = NewStrBuf();
-               ptr = NULL;
-               do {
-                       StrBufSipLine(Line, Imap->cached_rfc822, &ptr);
-
-                       if ((StrLength(Line) != 0)  && (ptr != StrBufNOTNULL))
-                       {
-                               StrBufTrim(Line);
-                               if ((StrLength(Line) != 0) && 
-                                   (ptr != StrBufNOTNULL)    )
-                               {
-                                       headers_size = ptr - ChrPtr(Imap->cached_rfc822);
-                               }
-                       }
-               } while ( (headers_size == 0)    && 
-                         (ptr != StrBufNOTNULL) );
-
-               total_size = StrLength(Imap->cached_rfc822);
-               text_size = total_size - headers_size;
-               FreeStrBuf(&Line);
-       }
-       else {
-               headers_size = total_size = StrLength(Imap->cached_rfc822);
-               text_size = 0;
-       }
-
-       syslog(LOG_DEBUG, "imap: RFC822 headers=" SIZE_T_FMT ", text=" SIZE_T_FMT ", total=" SIZE_T_FMT, headers_size, text_size, total_size);
-
-       if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
-               IAPrintf("RFC822.SIZE " SIZE_T_FMT, total_size);
-               return;
-       }
-
-       else if (!strcasecmp(whichfmt, "RFC822")) {
-               ptr = ChrPtr(Imap->cached_rfc822);
-               bytes_to_send = total_size;
-       }
-
-       else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
-               ptr = ChrPtr(Imap->cached_rfc822);
-               bytes_to_send = headers_size;
-       }
-
-       else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
-               ptr = &ChrPtr(Imap->cached_rfc822)[headers_size];
-               bytes_to_send = text_size;
-       }
-
-       IAPrintf("%s {" SIZE_T_FMT "}\r\n", whichfmt, bytes_to_send);
-       iaputs(ptr, bytes_to_send);
-}
-
-
-/*
- * Load a specific part of a message into the temp file to be output to a
- * client.  FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
- * but we still can't handle "2.HEADER" (which might not be a problem).
- *
- * Note: mime_parser() was called with dont_decode set to 1, so we have the
- * luxury of simply spewing without having to re-encode.
- */
-void imap_load_part(char *name, char *filename, char *partnum, char *disp,
-                   void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
-                   char *cbid, void *cbuserdata)
-{
-       char mimebuf2[SIZ];
-       StrBuf *desired_section;
-
-       desired_section = (StrBuf *)cbuserdata;
-       syslog(LOG_DEBUG, "imap: imap_load_part() looking for %s, found %s", ChrPtr(desired_section), partnum);
-
-       if (!strcasecmp(partnum, ChrPtr(desired_section))) {
-               client_write(content, length);
-       }
-
-       snprintf(mimebuf2, sizeof mimebuf2, "%s.MIME", partnum);
-
-       if (!strcasecmp(ChrPtr(desired_section), mimebuf2)) {
-               client_write(HKEY("Content-type: "));
-               client_write(cbtype, strlen(cbtype));
-               if (!IsEmptyStr(cbcharset)) {
-                       client_write(HKEY("; charset=\""));
-                       client_write(cbcharset, strlen(cbcharset));
-                       client_write(HKEY("\""));
-               }
-               if (!IsEmptyStr(name)) {
-                       client_write(HKEY("; name=\""));
-                       client_write(name, strlen(name));
-                       client_write(HKEY("\""));
-               }
-               client_write(HKEY("\r\n"));
-               if (!IsEmptyStr(encoding)) {
-                       client_write(HKEY("Content-Transfer-Encoding: "));
-                       client_write(encoding, strlen(encoding));
-                       client_write(HKEY("\r\n"));
-               }
-               if (!IsEmptyStr(encoding)) {
-                       client_write(HKEY("Content-Disposition: "));
-                       client_write(disp, strlen(disp));
-               
-                       if (!IsEmptyStr(filename)) {
-                               client_write(HKEY("; filename=\""));
-                               client_write(filename, strlen(filename));
-                               client_write(HKEY("\""));
-                       }
-                       client_write(HKEY("\r\n"));
-               }
-               cprintf("Content-Length: %ld\r\n\r\n", (long)length);
-       }
-}
-
-
-/* 
- * Called by imap_fetch_envelope() to output the "From" field.
- * This is in its own function because its logic is kind of complex.  We
- * really need to make this suck less.
- */
-void imap_output_envelope_from(struct CtdlMessage *msg) {
-       char user[SIZ], node[SIZ], name[SIZ];
-
-       if (!msg) return;
-
-       /* For anonymous messages, it's so easy! */
-       if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
-               IAPuts("((\"----\" NIL \"x\" \"x.org\")) ");
-               return;
-       }
-       if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
-               IAPuts("((\"anonymous\" NIL \"x\" \"x.org\")) ");
-               return;
-       }
-
-       /* For everything else, we do stuff. */
-       IAPuts("((");                           // open double-parens
-       IPutMsgField(eAuthor);                  // display name
-       IAPuts(" NIL ");                        // source route (not used)
-
-       if (!CM_IsEmpty(msg, erFc822Addr)) {
-               process_rfc822_addr(msg->cm_fields[erFc822Addr], user, node, name);
-               IPutStr(user, strlen(user));                    /* mailbox name (user id) */
-               IAPuts(" ");
-               IPutStr(node, strlen(node));                    /* host name */
-       }
-       else {
-               IPutMsgField(eAuthor);                          /* Make up a synthetic address */
-               IAPuts(" ");
-               IPutStr(CtdlGetConfigStr("c_fqdn"), strlen(CtdlGetConfigStr("c_fqdn")));
-       }
-       
-       IAPuts(")) "); /* close double-parens */
-}
-
-
-/*
- * Output an envelope address (or set of addresses) in the official,
- * convoluted, braindead format.  (Note that we can't use this for
- * the "From" address because its data may come from a number of different
- * fields.  But we can use it for "To" and possibly others.
- */
-void imap_output_envelope_addr(char *addr) {
-       char individual_addr[256];
-       int num_addrs;
-       int i;
-       char user[256];
-       char node[256];
-       char name[256];
-
-       if (addr == NULL) {
-               IAPuts("NIL ");
-               return;
-       }
-
-       if (IsEmptyStr(addr)) {
-               IAPuts("NIL ");
-               return;
-       }
-
-       IAPuts("(");
-
-       /* How many addresses are listed here? */
-       num_addrs = num_tokens(addr, ',');
-
-       /* Output them one by one. */
-       for (i=0; i<num_addrs; ++i) {
-               extract_token(individual_addr, addr, i, ',', sizeof individual_addr);
-               striplt(individual_addr);
-               process_rfc822_addr(individual_addr, user, node, name);
-               IAPuts("(");
-               IPutStr(name, strlen(name));
-               IAPuts(" NIL ");
-               IPutStr(user, strlen(user));
-               IAPuts(" ");
-               IPutStr(node, strlen(node));
-               IAPuts(")");
-               if (i < (num_addrs-1)) 
-                       IAPuts(" ");
-       }
-
-       IAPuts(") ");
-}
-
-
-/*
- * Implements the ENVELOPE fetch item
- * 
- * Note that the imap_strout() function can cleverly output NULL fields as NIL,
- * so we don't have to check for that condition like we do elsewhere.
- */
-void imap_fetch_envelope(struct CtdlMessage *msg) {
-       char datestringbuf[SIZ];
-       time_t msgdate;
-       char *fieldptr = NULL;
-       long len;
-
-       if (!msg) return;
-
-       /* Parse the message date into an IMAP-format date string */
-       if (!CM_IsEmpty(msg, eTimestamp)) {
-               msgdate = atol(msg->cm_fields[eTimestamp]);
-       }
-       else {
-               msgdate = time(NULL);
-       }
-       len = datestring(datestringbuf, sizeof datestringbuf, msgdate, DATESTRING_IMAP);
-
-       /* Now start spewing data fields.  The order is important, as it is
-        * defined by the protocol specification.  Nonexistent fields must
-        * be output as NIL, existent fields must be quoted or literalled.
-        * The imap_strout() function conveniently does all this for us.
-        */
-       IAPuts("ENVELOPE (");
-
-       /* Date */
-       IPutStr(datestringbuf, len);
-       IAPuts(" ");
-
-       /* Subject */
-       IPutMsgField(eMsgSubject);
-       IAPuts(" ");
-
-       /* From */
-       imap_output_envelope_from(msg);
-
-       /* Sender (default to same as 'From' if not present) */
-       fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Sender");
-       if (fieldptr != NULL) {
-               imap_output_envelope_addr(fieldptr);
-               free(fieldptr);
-       }
-       else {
-               imap_output_envelope_from(msg);
-       }
-
-       /* Reply-to */
-       fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Reply-to");
-       if (fieldptr != NULL) {
-               imap_output_envelope_addr(fieldptr);
-               free(fieldptr);
-       }
-       else {
-               imap_output_envelope_from(msg);
-       }
-
-       /* To */
-       imap_output_envelope_addr(msg->cm_fields[eRecipient]);
-
-       /* Cc (we do it this way because there might be a legacy non-Citadel Cc: field present) */
-       fieldptr = msg->cm_fields[eCarbonCopY];
-       if (fieldptr != NULL) {
-               imap_output_envelope_addr(fieldptr);
-       }
-       else {
-               fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Cc");
-               imap_output_envelope_addr(fieldptr);
-               if (fieldptr != NULL) free(fieldptr);
-       }
-
-       /* Bcc */
-       fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Bcc");
-       imap_output_envelope_addr(fieldptr);
-       if (fieldptr != NULL) free(fieldptr);
-
-       /* In-reply-to */
-       fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "In-reply-to");
-       IPutStr(fieldptr, (fieldptr)?strlen(fieldptr):0);
-       IAPuts(" ");
-       if (fieldptr != NULL) free(fieldptr);
-
-       /* message ID */
-       len = msg->cm_lengths[emessageId];
-       
-       if ((len == 0) || (
-               (msg->cm_fields[emessageId][0] == '<') && 
-               (msg->cm_fields[emessageId][len - 1] == '>'))
-       ) {
-               IPutMsgField(emessageId);
-       }
-       else 
-       {
-               char *Buf = malloc(len + 3);
-               long pos = 0;
-               
-               if (msg->cm_fields[emessageId][0] != '<')
-               {
-                       Buf[pos] = '<';
-                       pos ++;
-               }
-               memcpy(&Buf[pos], msg->cm_fields[emessageId], len);
-               pos += len;
-               if (msg->cm_fields[emessageId][len] != '>')
-               {
-                       Buf[pos] = '>';
-                       pos++;
-               }
-               Buf[pos] = '\0';
-               IPutStr(Buf, pos);
-               free(Buf);
-       }
-       IAPuts(")");
-}
-
-
-/*
- * This function is called only when CC->redirect_buffer contains a set of
- * RFC822 headers with no body attached.  Its job is to strip that set of
- * headers down to *only* the ones we're interested in.
- */
-void imap_strip_headers(StrBuf *section) {
-       citimap_command Cmd;
-       StrBuf *which_fields = NULL;
-       int doing_headers = 0;
-       int headers_not = 0;
-       int num_parms = 0;
-       int i;
-       StrBuf *boiled_headers = NULL;
-       StrBuf *Line;
-       int ok = 0;
-       int done_headers = 0;
-       const char *Ptr = NULL;
-       CitContext *CCC = CC;
-
-       if (CCC->redirect_buffer == NULL) return;
-
-       which_fields = NewStrBufDup(section);
-
-       if (!strncasecmp(ChrPtr(which_fields), "HEADER.FIELDS", 13))
-               doing_headers = 1;
-       if (doing_headers && 
-           !strncasecmp(ChrPtr(which_fields), "HEADER.FIELDS.NOT", 17))
-               headers_not = 1;
-
-       for (i=0; i < StrLength(which_fields); ++i) {
-               if (ChrPtr(which_fields)[i]=='(')
-                       StrBufReplaceToken(which_fields, i, 1, HKEY(""));
-       }
-       for (i=0; i < StrLength(which_fields); ++i) {
-               if (ChrPtr(which_fields)[i]==')') {
-                       StrBufCutAt(which_fields, i, NULL);
-                       break;
-               }
-       }
-       memset(&Cmd, 0, sizeof(citimap_command));
-       Cmd.CmdBuf = which_fields;
-       num_parms = imap_parameterize(&Cmd);
-
-       boiled_headers = NewStrBufPlain(NULL, StrLength(CCC->redirect_buffer));
-       Line = NewStrBufPlain(NULL, SIZ);
-       Ptr = NULL;
-       ok = 0;
-       do {
-               StrBufSipLine(Line, CCC->redirect_buffer, &Ptr);
-
-               if (!isspace(ChrPtr(Line)[0])) {
-
-                       if (doing_headers == 0) ok = 1;
-                       else {
-                               /* we're supposed to print all headers that are not matching the filter list */
-                               if (headers_not) for (i=0, ok = 1; (i < num_parms) && (ok == 1); ++i) {
-                                               if ( (!strncasecmp(ChrPtr(Line), 
-                                                                  Cmd.Params[i].Key,
-                                                                  Cmd.Params[i].len)) &&
-                                                    (ChrPtr(Line)[Cmd.Params[i].len]==':') ) {
-                                                       ok = 0;
-                                               }
-                               }
-                               /* we're supposed to print all headers matching the filterlist */
-                               else for (i=0, ok = 0; ((i < num_parms) && (ok == 0)); ++i) {
-                                               if ( (!strncasecmp(ChrPtr(Line), 
-                                                                  Cmd.Params[i].Key,
-                                                                  Cmd.Params[i].len)) &&
-                                                    (ChrPtr(Line)[Cmd.Params[i].len]==':') ) {
-                                                       ok = 1;
-                                       }
-                               }
-                       }
-               }
-
-               if (ok) {
-                       StrBufAppendBuf(boiled_headers, Line, 0);
-                       StrBufAppendBufPlain(boiled_headers, HKEY("\r\n"), 0);
-               }
-
-               if ((Ptr == StrBufNOTNULL)  ||
-                   (StrLength(Line) == 0)  ||
-                   (ChrPtr(Line)[0]=='\r') ||
-                   (ChrPtr(Line)[0]=='\n')   ) done_headers = 1;
-       } while (!done_headers);
-
-       StrBufAppendBufPlain(boiled_headers, HKEY("\r\n"), 0);
-
-       /* Now save it back (it'll always be smaller) */
-       FreeStrBuf(&CCC->redirect_buffer);
-       CCC->redirect_buffer = boiled_headers;
-
-       free(Cmd.Params);
-       FreeStrBuf(&which_fields);
-       FreeStrBuf(&Line);
-}
-
-
-/*
- * Implements the BODY and BODY.PEEK fetch items
- */
-void imap_fetch_body(long msgnum, ConstStr item, int is_peek) {
-       struct CtdlMessage *msg = NULL;
-       StrBuf *section;
-       StrBuf *partial;
-       int is_partial = 0;
-       size_t pstart, pbytes;
-       int loading_body_now = 0;
-       int need_body = 1;
-       int burn_the_cache = 0;
-       CitContext *CCC = CC;
-       citimap *Imap = CCCIMAP;
-
-       /* extract section */
-       section = NewStrBufPlain(CKEY(item));
-       
-       if (strchr(ChrPtr(section), '[') != NULL) {
-               StrBufStripAllBut(section, '[', ']');
-       }
-       syslog(LOG_DEBUG, "imap: selected section is [%s]", (StrLength(section) == 0) ? "(empty)" : ChrPtr(section));
-
-       /* Burn the cache if we don't have the same section of the 
-        * same message again.
-        */
-       if (Imap->cached_body != NULL) {
-               if (Imap->cached_bodymsgnum != msgnum) {
-                       burn_the_cache = 1;
-               }
-               else if ( (!Imap->cached_body_withbody) && (need_body) ) {
-                       burn_the_cache = 1;
-               }
-               else if (strcasecmp(Imap->cached_bodypart, ChrPtr(section))) {
-                       burn_the_cache = 1;
-               }
-               if (burn_the_cache) {
-                       /* Yup, go ahead and burn the cache. */
-                       free(Imap->cached_body);
-                       Imap->cached_body_len = 0;
-                       Imap->cached_body = NULL;
-                       Imap->cached_bodymsgnum = (-1);
-                       strcpy(Imap->cached_bodypart, "");
-               }
-       }
-
-       /* extract partial */
-       partial = NewStrBufPlain(CKEY(item));
-       if (strchr(ChrPtr(partial), '<') != NULL) {
-               StrBufStripAllBut(partial, '<', '>');
-               is_partial = 1;
-       }
-       if ( (is_partial == 1) && (StrLength(partial) > 0) ) {
-               syslog(LOG_DEBUG, "imap: selected partial is <%s>", ChrPtr(partial));
-       }
-
-       if (Imap->cached_body == NULL) {
-               CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
-               loading_body_now = 1;
-               msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
-       }
-
-       /* Now figure out what the client wants, and get it */
-
-       if (!loading_body_now) {
-               /* What we want is already in memory */
-       }
-
-       else if ( (!strcmp(ChrPtr(section), "1")) && (msg->cm_format_type != 4) ) {
-               CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, SUPPRESS_ENV_TO);
-       }
-
-       else if (StrLength(section) == 0) {
-               CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, SUPPRESS_ENV_TO);
-       }
-
-       /*
-        * If the client asked for just headers, or just particular header
-        * fields, strip it down.
-        */
-       else if (!strncasecmp(ChrPtr(section), "HEADER", 6)) {
-               /* This used to work with HEADERS_FAST, but then Apple got stupid with their
-                * IMAP library and this broke Mail.App and iPhone Mail, so we had to change it
-                * to HEADERS_ONLY so the trendy hipsters with their iPhones can read mail.
-                */
-               CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1, SUPPRESS_ENV_TO);
-               imap_strip_headers(section);
-       }
-
-       /*
-        * Strip it down if the client asked for everything _except_ headers.
-        */
-       else if (!strncasecmp(ChrPtr(section), "TEXT", 4)) {
-               CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, SUPPRESS_ENV_TO);
-       }
-
-       /*
-        * Anything else must be a part specifier.
-        * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
-        */
-       else {
-               mime_parser(CM_RANGE(msg, eMesageText),
-                           *imap_load_part, NULL, NULL,
-                           section,
-                           1
-                       );
-       }
-
-       if (loading_body_now) {
-               Imap->cached_body_len = StrLength(CCC->redirect_buffer);
-               Imap->cached_body = SmashStrBuf(&CCC->redirect_buffer);
-               Imap->cached_bodymsgnum = msgnum;
-               Imap->cached_body_withbody = need_body;
-               strcpy(Imap->cached_bodypart, ChrPtr(section));
-       }
-
-       if (is_partial == 0) {
-               IAPuts("BODY[");
-               iaputs(SKEY(section));
-               IAPrintf("] {" SIZE_T_FMT "}\r\n", Imap->cached_body_len);
-               pstart = 0;
-               pbytes = Imap->cached_body_len;
-       }
-       else {
-               sscanf(ChrPtr(partial), SIZE_T_FMT "." SIZE_T_FMT, &pstart, &pbytes);
-               if (pbytes > (Imap->cached_body_len - pstart)) {
-                       pbytes = Imap->cached_body_len - pstart;
-               }
-               IAPuts("BODY[");
-               iaputs(SKEY(section));
-               IAPrintf("]<" SIZE_T_FMT "> {" SIZE_T_FMT "}\r\n", pstart, pbytes);
-       }
-
-       FreeStrBuf(&partial);
-
-       /* Here we go -- output it */
-       iaputs(&Imap->cached_body[pstart], pbytes);
-
-       if (msg != NULL) {
-               CM_Free(msg);
-       }
-
-       /* Mark this message as "seen" *unless* this is a "peek" operation */
-       if (is_peek == 0) {
-               CtdlSetSeen(&msgnum, 1, 1, ctdlsetseen_seen, NULL, NULL);
-       }
-       FreeStrBuf(&section);
-}
-
-/*
- * Called immediately before outputting a multipart bodystructure
- */
-void imap_fetch_bodystructure_pre(
-               char *name, char *filename, char *partnum, char *disp,
-               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
-               char *cbid, void *cbuserdata
-               ) {
-
-       IAPuts("(");
-}
-
-
-
-/*
- * Called immediately after outputting a multipart bodystructure
- */
-void imap_fetch_bodystructure_post(
-               char *name, char *filename, char *partnum, char *disp,
-               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
-               char *cbid, void *cbuserdata
-               ) {
-       long len;
-       char subtype[128];
-
-       IAPuts(" ");
-
-       /* disposition */
-       len = extract_token(subtype, cbtype, 1, '/', sizeof subtype);
-       IPutStr(subtype, len);
-
-       /* body language */
-       /* IAPuts(" NIL"); We thought we needed this at one point, but maybe we don't... */
-
-       IAPuts(")");
-}
-
-
-
-/*
- * Output the info for a MIME part in the format required by BODYSTRUCTURE.
- *
- */
-void imap_fetch_bodystructure_part(
-               char *name, char *filename, char *partnum, char *disp,
-               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
-               char *cbid, void *cbuserdata
-               ) {
-
-       int have_cbtype = 0;
-       int have_encoding = 0;
-       int lines = 0;
-       size_t i;
-       char cbmaintype[128];
-       char cbsubtype[128];
-       long cbmaintype_len;
-       long cbsubtype_len;
-
-       if (cbtype != NULL) if (!IsEmptyStr(cbtype)) have_cbtype = 1;
-       if (have_cbtype) {
-               cbmaintype_len = extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
-               cbsubtype_len = extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
-       }
-       else {
-               strcpy(cbmaintype, "TEXT");
-               cbmaintype_len = 4;
-               strcpy(cbsubtype, "PLAIN");
-               cbsubtype_len = 5;
-       }
-
-       IAPuts("(");
-       IPutStr(cbmaintype, cbmaintype_len);                    /* body type */
-       IAPuts(" ");
-       IPutStr(cbsubtype, cbsubtype_len);                      /* body subtype */
-       IAPuts(" ");
-
-       IAPuts("(");                                            /* begin body parameter list */
-
-       /* "NAME" must appear as the first parameter.  This is not required by IMAP,
-        * but the Asterisk voicemail application blindly assumes that NAME will be in
-        * the first position.  If it isn't, it rejects the message.
-        */
-       if ((name != NULL) && (!IsEmptyStr(name))) {
-               IAPuts("\"NAME\" ");
-               IPutStr(name, strlen(name));
-               IAPuts(" ");
-       }
-
-       IAPuts("\"CHARSET\" ");
-       if ((cbcharset == NULL) || (cbcharset[0] == 0)){
-               IPutStr(HKEY("US-ASCII"));
-       }
-       else {
-               IPutStr(cbcharset, strlen(cbcharset));
-       }
-       IAPuts(") ");                                           /* end body parameter list */
-
-       IAPuts("NIL ");                                         /* Body ID */
-       IAPuts("NIL ");                                         /* Body description */
-
-       if ((encoding != NULL) && (encoding[0] != 0))  have_encoding = 1;
-       if (have_encoding) {
-               IPutStr(encoding, strlen(encoding));
-       }
-       else {
-               IPutStr(HKEY("7BIT"));
-       }
-       IAPuts(" ");
-
-       /* The next field is the size of the part in bytes. */
-       IAPrintf("%ld ", (long)length); /* bytes */
-
-       /* The next field is the number of lines in the part, if and only
-        * if the part is TEXT.  More gratuitous complexity.
-        */
-       if (!strcasecmp(cbmaintype, "TEXT")) {
-               if (length) for (i=0; i<length; ++i) {
-                       if (((char *)content)[i] == '\n') ++lines;
-               }
-               IAPrintf("%d ", lines);
-       }
-
-       /* More gratuitous complexity */
-       if ((!strcasecmp(cbmaintype, "MESSAGE"))
-          && (!strcasecmp(cbsubtype, "RFC822"))) {
-               /* FIXME: message/rfc822 also needs to output the envelope structure,
-                * body structure, and line count of the encapsulated message.  Fortunately
-                * there are not yet any clients depending on this, so we can get away
-                * with not implementing it for now.
-                */
-       }
-
-       /* MD5 value of body part; we can get away with NIL'ing this */
-       IAPuts("NIL ");
-
-       /* Disposition */
-       if ((disp == NULL) || IsEmptyStr(disp)) {
-               IAPuts("NIL");
-       }
-       else {
-               IAPuts("(");
-               IPutStr(disp, strlen(disp));
-               if ((filename != NULL) && (!IsEmptyStr(filename))) {
-                       IAPuts(" (\"FILENAME\" ");
-                       IPutStr(filename, strlen(filename));
-                       IAPuts(")");
-               }
-               IAPuts(")");
-       }
-
-       /* Body language (not defined yet) */
-       IAPuts(" NIL)");
-}
-
-
-
-/*
- * Spew the BODYSTRUCTURE data for a message.
- *
- */
-void imap_fetch_bodystructure (long msgnum, const char *item,
-               struct CtdlMessage *msg) {
-       const char *rfc822 = NULL;
-       const char *rfc822_body = NULL;
-       size_t rfc822_len;
-       size_t rfc822_headers_len;
-       size_t rfc822_body_len;
-       const char *ptr = NULL;
-       char *pch;
-       char buf[SIZ];
-       int lines = 0;
-
-       /* Handle NULL message gracefully */
-       if (msg == NULL) {
-               IAPuts("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
-                       "(\"CHARSET\" \"US-ASCII\") NIL NIL "
-                       "\"7BIT\" 0 0)");
-               return;
-       }
-
-       /* For non-RFC822 (ordinary Citadel) messages, this is short and
-        * sweet...
-        */
-       if (msg->cm_format_type != FMT_RFC822) {
-
-               /* *sigh* We have to RFC822-format the message just to be able
-                * to measure it.  FIXME use smi cached fields if possible
-                */
-
-               CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
-               CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1, SUPPRESS_ENV_TO);
-               rfc822_len = StrLength(CC->redirect_buffer);
-               rfc822 = pch = SmashStrBuf(&CC->redirect_buffer);
-
-               ptr = rfc822;
-               do {
-                       ptr = cmemreadline(ptr, buf, sizeof buf);
-                       ++lines;
-                       if ((IsEmptyStr(buf)) && (rfc822_body == NULL)) {
-                               rfc822_body = ptr;
-                       }
-               } while (*ptr != 0);
-
-               rfc822_headers_len = rfc822_body - rfc822;
-               rfc822_body_len = rfc822_len - rfc822_headers_len;
-               free(pch);
-
-               IAPuts("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
-                      "(\"CHARSET\" \"US-ASCII\") NIL NIL "
-                      "\"7BIT\" ");
-               IAPrintf(SIZE_T_FMT " %d)", rfc822_body_len, lines);
-
-               return;
-       }
-
-       /* For messages already stored in RFC822 format, we have to parse. */
-       IAPuts("BODYSTRUCTURE ");
-       mime_parser(CM_RANGE(msg, eMesageText),
-                   *imap_fetch_bodystructure_part,     /* part */
-                   *imap_fetch_bodystructure_pre,      /* pre-multi */
-                   *imap_fetch_bodystructure_post,     /* post-multi */
-                   NULL,
-                   1); /* don't decode -- we want it as-is */
-}
-
-
-/*
- * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
- * individual message, once it has been selected for output.
- */
-void imap_do_fetch_msg(int seq, citimap_command *Cmd) {
-       int i;
-       citimap *Imap = IMAP;
-       struct CtdlMessage *msg = NULL;
-       int body_loaded = 0;
-
-       /* Don't attempt to fetch bogus messages or UID's */
-       if (seq < 1) return;
-       if (Imap->msgids[seq-1] < 1L) return;
-
-       buffer_output();
-       IAPrintf("* %d FETCH (", seq);
-
-       for (i=0; i<Cmd->num_parms; ++i) {
-
-               /* Fetchable without going to the message store at all */
-               if (!strcasecmp(Cmd->Params[i].Key, "UID")) {
-                       imap_fetch_uid(seq);
-               }
-               else if (!strcasecmp(Cmd->Params[i].Key, "FLAGS")) {
-                       imap_fetch_flags(seq-1);
-               }
-
-               /* Potentially fetchable from cache, if the client requests
-                * stuff from the same message several times in a row.
-                */
-               else if (!strcasecmp(Cmd->Params[i].Key, "RFC822")) {
-                       imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
-               }
-               else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.HEADER")) {
-                       imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
-               }
-               else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.SIZE")) {
-                       imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
-               }
-               else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.TEXT")) {
-                       imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
-               }
-
-               /* BODY fetches do their own fetching and caching too. */
-               else if (!strncasecmp(Cmd->Params[i].Key, "BODY[", 5)) {
-                       imap_fetch_body(Imap->msgids[seq-1], Cmd->Params[i], 0);
-               }
-               else if (!strncasecmp(Cmd->Params[i].Key, "BODY.PEEK[", 10)) {
-                       imap_fetch_body(Imap->msgids[seq-1], Cmd->Params[i], 1);
-               }
-
-               /* Otherwise, load the message into memory.
-                */
-               else if (!strcasecmp(Cmd->Params[i].Key, "BODYSTRUCTURE")) {
-                       if ((msg != NULL) && (!body_loaded)) {
-                               CM_Free(msg);   /* need the whole thing */
-                               msg = NULL;
-                       }
-                       if (msg == NULL) {
-                               msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                               body_loaded = 1;
-                       }
-                       imap_fetch_bodystructure(Imap->msgids[seq-1],
-                                       Cmd->Params[i].Key, msg);
-               }
-               else if (!strcasecmp(Cmd->Params[i].Key, "ENVELOPE")) {
-                       if (msg == NULL) {
-                               msg = CtdlFetchMessage(Imap->msgids[seq-1], 0);
-                               body_loaded = 0;
-                       }
-                       imap_fetch_envelope(msg);
-               }
-               else if (!strcasecmp(Cmd->Params[i].Key, "INTERNALDATE")) {
-                       if (msg == NULL) {
-                               msg = CtdlFetchMessage(Imap->msgids[seq-1], 0);
-                               body_loaded = 0;
-                       }
-                       imap_fetch_internaldate(msg);
-               }
-
-               if (i != Cmd->num_parms-1) IAPuts(" ");
-       }
-
-       IAPuts(")\r\n");
-       unbuffer_output();
-       if (msg != NULL) {
-               CM_Free(msg);
-       }
-}
-
-
-
-/*
- * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
- * validated and boiled down the request a bit.
- */
-void imap_do_fetch(citimap_command *Cmd) {
-       citimap *Imap = IMAP;
-       int i;
-#if 0
-/* debug output the parsed vector */
-       {
-               int i;
-               syslog(LOG_DEBUG, "imap: ----- %ld params", Cmd->num_parms);
-
-       for (i=0; i < Cmd->num_parms; i++) {
-               if (Cmd->Params[i].len != strlen(Cmd->Params[i].Key))
-                       syslog(LOG_DEBUG, "imap: *********** %ld != %ld : %s",
-                                   Cmd->Params[i].len, 
-                                   strlen(Cmd->Params[i].Key),
-                                   Cmd->Params[i].Key);
-               else
-                       syslog(LOG_DEBUG, "imap: %ld : %s",
-                                   Cmd->Params[i].len, 
-                                   Cmd->Params[i].Key);
-       }}
-
-#endif
-
-       if (Imap->num_msgs > 0) {
-               for (i = 0; i < Imap->num_msgs; ++i) {
-
-                       /* Abort the fetch loop if the session breaks.
-                        * This is important for users who keep mailboxes
-                        * that are too big *and* are too impatient to
-                        * let them finish loading.  :)
-                        */
-                       if (CC->kill_me) return;
-
-                       /* Get any message marked for fetch. */
-                       if (Imap->flags[i] & IMAP_SELECTED) {
-                               imap_do_fetch_msg(i+1, Cmd);
-                       }
-               }
-       }
-}
-
-
-
-/*
- * Back end for imap_handle_macros()
- * Note that this function *only* looks at the beginning of the string.  It
- * is not a generic search-and-replace function.
- */
-void imap_macro_replace(StrBuf *Buf, long where, 
-                       StrBuf *TmpBuf,
-                       char *find, long findlen, 
-                       char *replace, long replacelen) 
-{
-
-       if (StrLength(Buf) - where > findlen)
-               return;
-
-       if (!strncasecmp(ChrPtr(Buf) + where, find, findlen)) {
-               if (ChrPtr(Buf)[where + findlen] == ' ') {
-                       StrBufPlain(TmpBuf, replace, replacelen);
-                       StrBufAppendBufPlain(TmpBuf, HKEY(" "), 0);
-                       StrBufReplaceToken(Buf, where, findlen, 
-                                          SKEY(TmpBuf));
-               }
-               if (where + findlen == StrLength(Buf)) {
-                       StrBufReplaceToken(Buf, where, findlen, 
-                                          replace, replacelen);
-               }
-       }
-}
-
-
-
-/*
- * Handle macros embedded in FETCH data items.
- * (What the heck are macros doing in a wire protocol?  Are we trying to save
- * the computer at the other end the trouble of typing a lot of characters?)
- */
-void imap_handle_macros(citimap_command *Cmd) {
-       long i;
-       int nest = 0;
-       StrBuf *Tmp = NewStrBuf();
-
-       for (i=0; i < StrLength(Cmd->CmdBuf); ++i) {
-               char ch = ChrPtr(Cmd->CmdBuf)[i];
-               if ((ch=='(') ||
-                   (ch=='[') ||
-                   (ch=='<') ||
-                   (ch=='{')) ++nest;
-               else if ((ch==')') ||
-                        (ch==']') ||
-                        (ch=='>') ||
-                        (ch=='}')) --nest;
-
-               if (nest <= 0) {
-                       imap_macro_replace(Cmd->CmdBuf, i,
-                                          Tmp, 
-                                          HKEY("ALL"),
-                                          HKEY("FLAGS INTERNALDATE RFC822.SIZE ENVELOPE")
-                       );
-                       imap_macro_replace(Cmd->CmdBuf, i,
-                                          Tmp, 
-                                          HKEY("BODY"),
-                                          HKEY("BODYSTRUCTURE")
-                       );
-                       imap_macro_replace(Cmd->CmdBuf, i,
-                                          Tmp, 
-                                          HKEY("FAST"),
-                                          HKEY("FLAGS INTERNALDATE RFC822.SIZE")
-                       );
-                       imap_macro_replace(Cmd->CmdBuf, i,
-                                          Tmp, 
-                                          HKEY("FULL"),
-                                          HKEY("FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY")
-                       );
-               }
-       }
-       FreeStrBuf(&Tmp);
-}
-
-
-/*
- * Break out the data items requested, possibly a parenthesized list.
- * Returns the number of data items, or -1 if the list is invalid.
- * NOTE: this function alters the string it is fed, and uses it as a buffer
- * to hold the data for the pointers it returns.
- */
-int imap_extract_data_items(citimap_command *Cmd) 
-{
-       int nArgs;
-       int nest = 0;
-       const char *pch, *end;
-
-       /* Convert all whitespace to ordinary space characters. */
-       pch = ChrPtr(Cmd->CmdBuf);
-       end = pch + StrLength(Cmd->CmdBuf);
-
-       while (pch < end)
-       {
-               if (isspace(*pch)) 
-                       StrBufPeek(Cmd->CmdBuf, pch, 0, ' ');
-               pch++;
-       }
-
-       /* Strip leading and trailing whitespace, then strip leading and
-        * trailing parentheses if it's a list
-        */
-       StrBufTrim(Cmd->CmdBuf);
-       pch = ChrPtr(Cmd->CmdBuf);
-       if ( (pch[0]=='(') && 
-            (pch[StrLength(Cmd->CmdBuf)-1]==')') ) 
-       {
-               StrBufCutRight(Cmd->CmdBuf, 1);
-               StrBufCutLeft(Cmd->CmdBuf, 1);
-               StrBufTrim(Cmd->CmdBuf);
-       }
-
-       /* Parse any macro data items */
-       imap_handle_macros(Cmd);
-
-       /*
-        * Now break out the data items.  We throw in one trailing space in
-        * order to avoid having to break out the last one manually.
-        */
-       nArgs = StrLength(Cmd->CmdBuf) / 10 + 10;
-       nArgs = CmdAdjust(Cmd, nArgs, 0);
-       Cmd->num_parms = 0;
-       Cmd->Params[Cmd->num_parms].Key = pch = ChrPtr(Cmd->CmdBuf);
-       end = Cmd->Params[Cmd->num_parms].Key + StrLength(Cmd->CmdBuf);
-
-       while (pch < end) 
-       {
-               if ((*pch=='(') ||
-                   (*pch=='[') ||
-                   (*pch=='<') ||
-                   (*pch=='{'))
-                       ++nest;
-
-               else if ((*pch==')') ||
-                        (*pch==']') ||
-                        (*pch=='>') ||
-                        (*pch=='}'))
-                       --nest;
-
-               if ((nest <= 0) && (*pch==' ')) {
-                       StrBufPeek(Cmd->CmdBuf, pch, 0, '\0');
-                       Cmd->Params[Cmd->num_parms].len = 
-                               pch - Cmd->Params[Cmd->num_parms].Key;
-
-                       if (Cmd->num_parms + 1 >= Cmd->avail_parms) {
-                               nArgs = CmdAdjust(Cmd, nArgs * 2, 1);
-                       }
-                       Cmd->num_parms++;                       
-                       Cmd->Params[Cmd->num_parms].Key = ++pch;
-               }
-               else if (pch + 1 == end) {
-                       Cmd->Params[Cmd->num_parms].len = 
-                               pch - Cmd->Params[Cmd->num_parms].Key + 1;
-
-                       Cmd->num_parms++;                       
-               }
-               pch ++;
-       }
-       return Cmd->num_parms;
-
-}
-
-
-/*
- * One particularly hideous aspect of IMAP is that we have to allow the client
- * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
- * handles this by setting the IMAP_SELECTED flag for each message specified in
- * the ranges/sets, then looping through the message array, outputting messages
- * with the flag set.  We don't bother returning an error if an out-of-range
- * number is specified (we just return quietly) because any client braindead
- * enough to request a bogus message number isn't going to notice the
- * difference anyway.
- *
- * This function clears out the IMAP_SELECTED bits, then sets that bit for each
- * message included in the specified range.
- *
- * Set is_uid to 1 to fetch by UID instead of sequence number.
- */
-void imap_pick_range(const char *supplied_range, int is_uid) {
-       citimap *Imap = IMAP;
-       int i;
-       int num_sets;
-       int s;
-       char setstr[SIZ], lostr[SIZ], histr[SIZ];
-       long lo, hi;
-       char actual_range[SIZ];
-
-       /* 
-        * Handle the "ALL" macro
-        */
-       if (!strcasecmp(supplied_range, "ALL")) {
-               safestrncpy(actual_range, "1:*", sizeof actual_range);
-       }
-       else {
-               safestrncpy(actual_range, supplied_range, sizeof actual_range);
-       }
-
-       /*
-        * Clear out the IMAP_SELECTED flags for all messages.
-        */
-       for (i = 0; i < Imap->num_msgs; ++i) {
-               Imap->flags[i] = Imap->flags[i] & ~IMAP_SELECTED;
-       }
-
-       /*
-        * Now set it for all specified messages.
-        */
-       num_sets = num_tokens(actual_range, ',');
-       for (s=0; s<num_sets; ++s) {
-               extract_token(setstr, actual_range, s, ',', sizeof setstr);
-
-               extract_token(lostr, setstr, 0, ':', sizeof lostr);
-               if (num_tokens(setstr, ':') >= 2) {
-                       extract_token(histr, setstr, 1, ':', sizeof histr);
-                       if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
-               } 
-               else {
-                       safestrncpy(histr, lostr, sizeof histr);
-               }
-               lo = atol(lostr);
-               hi = atol(histr);
-
-               /* Loop through the array, flipping bits where appropriate */
-               for (i = 1; i <= Imap->num_msgs; ++i) {
-                       if (is_uid) {   /* fetch by sequence number */
-                               if ( (Imap->msgids[i-1]>=lo)
-                                  && (Imap->msgids[i-1]<=hi)) {
-                                       Imap->flags[i-1] |= IMAP_SELECTED;
-                               }
-                       }
-                       else {          /* fetch by uid */
-                               if ( (i>=lo) && (i<=hi)) {
-                                       Imap->flags[i-1] |= IMAP_SELECTED;
-                               }
-                       }
-               }
-       }
-}
-
-
-
-/*
- * This function is called by the main command loop.
- */
-void imap_fetch(int num_parms, ConstStr *Params) {
-       citimap_command Cmd;
-       int num_items;
-       
-       if (num_parms < 4) {
-               IReply("BAD invalid parameters");
-               return;
-       }
-
-       imap_pick_range(Params[2].Key, 0);
-
-       memset(&Cmd, 0, sizeof(citimap_command));
-       Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
-       MakeStringOf(Cmd.CmdBuf, 3);
-
-       num_items = imap_extract_data_items(&Cmd);
-       if (num_items < 1) {
-               IReply("BAD invalid data item list");
-               FreeStrBuf(&Cmd.CmdBuf);
-               free(Cmd.Params);
-               return;
-       }
-
-       imap_do_fetch(&Cmd);
-       IReply("OK FETCH completed");
-       FreeStrBuf(&Cmd.CmdBuf);
-       free(Cmd.Params);
-}
-
-/*
- * This function is called by the main command loop.
- */
-void imap_uidfetch(int num_parms, ConstStr *Params) {
-       citimap_command Cmd;
-       int num_items;
-       int i;
-       int have_uid_item = 0;
-
-       if (num_parms < 5) {
-               IReply("BAD invalid parameters");
-               return;
-       }
-
-       imap_pick_range(Params[3].Key, 1);
-
-       memset(&Cmd, 0, sizeof(citimap_command));
-       Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
-
-       MakeStringOf(Cmd.CmdBuf, 4);
-#if 0
-       syslog(LOG_DEBUG, "imap: -------%s--------", ChrPtr(Cmd.CmdBuf));
-#endif
-       num_items = imap_extract_data_items(&Cmd);
-       if (num_items < 1) {
-               IReply("BAD invalid data item list");
-               FreeStrBuf(&Cmd.CmdBuf);
-               free(Cmd.Params);
-               return;
-       }
-
-       /* If the "UID" item was not included, we include it implicitly
-        * (at the beginning) because this is a UID FETCH command
-        */
-       for (i=0; i<num_items; ++i) {
-               if (!strcasecmp(Cmd.Params[i].Key, "UID")) ++have_uid_item;
-       }
-       if (have_uid_item == 0) {
-               if (Cmd.num_parms + 1 >= Cmd.avail_parms)
-                       CmdAdjust(&Cmd, Cmd.avail_parms + 1, 1);
-               memmove(&Cmd.Params[1], 
-                       &Cmd.Params[0], 
-                       sizeof(ConstStr) * Cmd.num_parms);
-
-               Cmd.num_parms++;
-               Cmd.Params[0] = (ConstStr){HKEY("UID")};
-       }
-
-       imap_do_fetch(&Cmd);
-       IReply("OK UID FETCH completed");
-       FreeStrBuf(&Cmd.CmdBuf);
-       free(Cmd.Params);
-}
-
-
diff --git a/citadel/modules/imap/imap_fetch.h b/citadel/modules/imap/imap_fetch.h
deleted file mode 100644 (file)
index 7847fce..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright (c) 2001-2017 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-void imap_pick_range(const char *range, int is_uid);
-void imap_fetch(int num_parms, ConstStr *Params);
-void imap_uidfetch(int num_parms, ConstStr *Params);
-void imap_fetch_flags(int seq);
-int imap_extract_data_items(citimap_command *Cmd);
diff --git a/citadel/modules/imap/imap_list.c b/citadel/modules/imap/imap_list.c
deleted file mode 100644 (file)
index 1be8bab..0000000
+++ /dev/null
@@ -1,417 +0,0 @@
-/*
- * Implements the LIST and LSUB commands.
- *
- * Copyright (c) 2000-2017 by Art Cancro and others.
- *
- *  This program is open source software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "imap_fetch.h"
-#include "imap_search.h"
-#include "imap_store.h"
-#include "imap_acl.h"
-#include "imap_misc.h"
-#include "imap_list.h"
-#include "ctdl_module.h"
-
-
-typedef struct __ImapRoomListFilter {
-       char verb[16];
-       int subscribed_rooms_only;
-       int return_subscribed;
-       int return_children;
-
-       int num_patterns;
-       int num_patterns_avail;
-       StrBuf **patterns;
-}ImapRoomListFilter;
-
-/*
- * Used by LIST and LSUB to show the floors in the listing
- */
-void imap_list_floors(char *verb, int num_patterns, StrBuf **patterns)
-{
-       int i;
-       struct floor *fl;
-       int j = 0;
-       int match = 0;
-
-       for (i = 0; i < MAXFLOORS; ++i) {
-               fl = CtdlGetCachedFloor(i);
-               if (fl->f_flags & F_INUSE) {
-                       match = 0;
-                       for (j=0; j<num_patterns; ++j) {
-                               if (imap_mailbox_matches_pattern (ChrPtr(patterns[j]), fl->f_name)) {
-                                       match = 1;
-                               }
-                       }
-                       if (match) {
-                               IAPrintf("* %s (\\NoSelect \\HasChildren) \"/\" ", verb);
-                               IPutStr(fl->f_name, (fl->f_name)?strlen(fl->f_name):0);
-                               IAPuts("\r\n");
-                       }
-               }
-       }
-}
-
-
-/*
- * Back end for imap_list()
- *
- * Implementation note: IMAP "subscribed folder" is equivalent to Citadel "known room"
- *
- * The "user data" field is actually an array of pointers; see below for the breakdown
- *
- */
-void imap_listroom(struct ctdlroom *qrbuf, void *data)
-{
-#define SUBSCRIBED_STR "\\Subscribed"
-#define HASCHILD_STR "\\HasChildren"
-       char MailboxName[SIZ];
-       char return_options[256];
-       int ra;
-       int yes_output_this_room;
-       ImapRoomListFilter *ImapFilter;
-       int i = 0;
-       int match = 0;
-       int ROLen;
-
-       /* Here's how we break down the array of pointers passed to us */
-       ImapFilter = (ImapRoomListFilter*)data;
-
-       /* Only list rooms to which the user has access!! */
-       yes_output_this_room = 0;
-       *return_options = '\0';
-       ROLen = 0;
-       CtdlRoomAccess(qrbuf, &CC->user, &ra, NULL);
-
-       if (ImapFilter->return_subscribed) {
-               if (ra & UA_KNOWN) {
-                       memcpy(return_options, HKEY(SUBSCRIBED_STR) + 1);
-                       ROLen += sizeof(SUBSCRIBED_STR) - 1;
-               }
-       }
-
-       /* Warning: ugly hack.
-        * We don't have any way to determine the presence of child mailboxes
-        * without refactoring this entire module.  So we're just going to return
-        * the \HasChildren attribute for every room.
-        * We'll fix this later when we have time.
-        */
-       if (ImapFilter->return_children) {
-               if (!IsEmptyStr(return_options)) {
-                       memcpy(return_options + ROLen, HKEY(" "));
-                       ROLen ++;
-               }
-               memcpy(return_options + ROLen, HKEY(SUBSCRIBED_STR) + 1);
-       }
-
-       if (ImapFilter->subscribed_rooms_only) {
-               if (ra & UA_KNOWN) {
-                       yes_output_this_room = 1;
-               }
-       }
-       else {
-               if ((ra & UA_KNOWN) || ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED))) {
-                       yes_output_this_room = 1;
-               }
-       }
-
-       if (yes_output_this_room) {
-               long len;
-               len = imap_mailboxname(MailboxName, sizeof MailboxName, qrbuf);
-               match = 0;
-               for (i=0; i<ImapFilter->num_patterns; ++i) {
-                       if (imap_mailbox_matches_pattern(ChrPtr(ImapFilter->patterns[i]), MailboxName)) {
-                               match = 1;
-                       }
-               }
-               if (match) {
-                       IAPrintf("* %s (%s) \"/\" ", ImapFilter->verb, return_options);
-                       IPutStr(MailboxName, len);
-                       IAPuts("\r\n");
-               }
-       }
-}
-
-
-/*
- * Implements the LIST and LSUB commands
- */
-void imap_list(int num_parms, ConstStr *Params)
-{
-       struct CitContext *CCC = CC;
-       citimap *Imap = CCCIMAP;
-       int i, j, paren_nest;
-       ImapRoomListFilter ImapFilter;
-       int selection_left = (-1);
-       int selection_right = (-1);
-       int return_left = (-1);
-       int root_pos = 2;
-       int patterns_left = 3;
-       int patterns_right = 3;
-       int extended_list_in_use = 0;
-
-       if (num_parms < 4) {
-               IReply("BAD arguments invalid");
-               return;
-       }
-
-       ImapFilter.num_patterns = 1;
-       ImapFilter.return_subscribed = 0;
-       ImapFilter.return_children = 0;
-       ImapFilter.subscribed_rooms_only = 0;
-       
-
-       /* parms[1] is the IMAP verb being used (e.g. LIST or LSUB)
-        * This tells us how to behave, and what verb to return back to the caller
-        */
-       safestrncpy(ImapFilter.verb, Params[1].Key, sizeof ImapFilter.verb);
-       j = Params[1].len;
-       for (i=0; i<j; ++i) {
-               ImapFilter.verb[i] = toupper(ImapFilter.verb[i]);
-       }
-
-       if (!strcasecmp(ImapFilter.verb, "LSUB")) {
-               ImapFilter.subscribed_rooms_only = 1;
-       }
-
-       /*
-        * Partial implementation of LIST-EXTENDED (which will not get used because
-        * we don't advertise it in our capabilities string).  Several requirements:
-        *
-        * Extraction of selection options:
-        *      SUBSCRIBED option: done
-        *      RECURSIVEMATCH option: not done yet
-        *      REMOTE: safe to silently ignore
-        *
-        * Extraction of return options:
-        *      SUBSCRIBED option: done
-        *      CHILDREN option: done, but needs a non-ugly rewrite
-        *
-        * Multiple match patterns: done
-        */
-
-       /*
-        * If parameter 2 begins with a '(' character, the client is specifying
-        * selection options.  Extract their exact position, and then modify our
-        * expectation of where the root folder will be specified.
-        */
-       if (Params[2].Key[0] == '(') {
-               extended_list_in_use = 1;
-               selection_left = 2;
-               paren_nest = 0;
-               for (i=2; i<num_parms; ++i) {
-                       for (j=0; Params[i].Key[j]; ++j) {
-                               if (Params[i].Key[j] == '(') ++paren_nest;
-                               if (Params[i].Key[j] == ')') --paren_nest;
-                       }
-                       if (paren_nest == 0) {
-                               selection_right = i;    /* found end of selection options */
-                               root_pos = i+1;         /* folder root appears after selection options */
-                               i = num_parms + 1;      /* break out of the loop */
-                       }
-               }
-       }
-
-       /* If selection options were found, do something with them.
-        */
-       if ((selection_left > 0) && (selection_right >= selection_left)) {
-
-               /* Strip off the outer parentheses */
-               if (Params[selection_left].Key[0] == '(') {
-                       TokenCutLeft(&Imap->Cmd, 
-                                    &Params[selection_left], 
-                                    1);
-               }
-               if (Params[selection_right].Key[Params[selection_right].len-1] == ')') {
-                       TokenCutRight(&Imap->Cmd, 
-                                     &Params[selection_right], 
-                                     1);
-               }
-
-               for (i=selection_left; i<=selection_right; ++i) {
-
-                       if (!strcasecmp(Params[i].Key, "SUBSCRIBED")) {
-                               ImapFilter.subscribed_rooms_only = 1;
-                       }
-
-                       else if (!strcasecmp(Params[i].Key, "RECURSIVEMATCH")) {
-                               /* FIXME - do this! */
-                       }
-
-               }
-
-       }
-
-       /* The folder root appears immediately after the selection options,
-        * or in position 2 if no selection options were specified.
-        */
-       ImapFilter.num_patterns_avail = num_parms + 1;
-       ImapFilter.patterns = malloc(ImapFilter.num_patterns_avail * sizeof(StrBuf*));
-       memset(ImapFilter.patterns, 0, ImapFilter.num_patterns_avail * sizeof(StrBuf*));
-
-       patterns_left = root_pos + 1;
-       patterns_right = root_pos + 1;
-
-       if (Params[patterns_left].Key[0] == '(') {
-               extended_list_in_use = 1;
-               paren_nest = 0;
-               for (i=patterns_left; i<num_parms; ++i) {
-                       for (j=0; &Params[i].Key[j]; ++j) {
-                               if (Params[i].Key[j] == '(') ++paren_nest;
-                               if (Params[i].Key[j] == ')') --paren_nest;
-                       }
-                       if (paren_nest == 0) {
-                               patterns_right = i;     /* found end of patterns */
-                               i = num_parms + 1;      /* break out of the loop */
-                       }
-               }
-               ImapFilter.num_patterns = patterns_right - patterns_left + 1;
-               for (i=0; i<ImapFilter.num_patterns; ++i) {
-                       if (i < MAX_PATTERNS) {
-                               ImapFilter.patterns[i] = NewStrBufPlain(NULL, 
-                                                                       Params[root_pos].len + 
-                                                                       Params[patterns_left+i].len);
-                               if (i == 0) {
-                                       if (Params[root_pos].len > 1)
-                                               StrBufAppendBufPlain(ImapFilter.patterns[i], 
-                                                                    1 + CKEY(Params[root_pos]) - 1, 0);
-                               }
-                               else
-                                       StrBufAppendBufPlain(ImapFilter.patterns[i], 
-                                                            CKEY(Params[root_pos]), 0);
-
-                               if (i == ImapFilter.num_patterns-1) {
-                                       if (Params[patterns_left+i].len > 1)
-                                               StrBufAppendBufPlain(ImapFilter.patterns[i], 
-                                                                    CKEY(Params[patterns_left+i]) - 1, 0);
-                               }
-                               else StrBufAppendBufPlain(ImapFilter.patterns[i], 
-                                                         CKEY(Params[patterns_left+i]), 0);
-
-                       }
-
-               }
-       }
-       else {
-               ImapFilter.num_patterns = 1;
-               ImapFilter.patterns[0] = NewStrBufPlain(NULL, 
-                                                       Params[root_pos].len + 
-                                                       Params[patterns_left].len);
-               StrBufAppendBufPlain(ImapFilter.patterns[0], 
-                                    CKEY(Params[root_pos]), 0);
-               StrBufAppendBufPlain(ImapFilter.patterns[0], 
-                                    CKEY(Params[patterns_left]), 0);
-       }
-
-       /* If the word "RETURN" appears after the folder pattern list, then the client
-        * is specifying return options.
-        */
-       if (num_parms - patterns_right > 2) if (!strcasecmp(Params[patterns_right+1].Key, "RETURN")) {
-               return_left = patterns_right + 2;
-               extended_list_in_use = 1;
-               paren_nest = 0;
-               for (i=return_left; i<num_parms; ++i) {
-                       for (j=0;   Params[i].Key[j]; ++j) {
-                               if (Params[i].Key[j] == '(') ++paren_nest;
-                               if (Params[i].Key[j] == ')') --paren_nest;
-                       }
-
-                       /* Might as well look for these while we're in here... */
-                       if (Params[i].Key[0] == '(') 
-                               TokenCutLeft(&Imap->Cmd, 
-                                            &Params[i], 
-                                            1);
-                       if (Params[i].Key[Params[i].len-1] == ')')
-                           TokenCutRight(&Imap->Cmd, 
-                                         &Params[i], 
-                                         1);
-
-                       syslog(LOG_DEBUG, "evaluating <%s>", Params[i].Key);
-
-                       if (!strcasecmp(Params[i].Key, "SUBSCRIBED")) {
-                               ImapFilter.return_subscribed = 1;
-                       }
-
-                       else if (!strcasecmp(Params[i].Key, "CHILDREN")) {
-                               ImapFilter.return_children = 1;
-                       }
-
-                       if (paren_nest == 0) {
-                               i = num_parms + 1;      /* break out of the loop */
-                       }
-               }
-       }
-
-       /* Now start setting up the data we're going to send to the CtdlForEachRoom() callback.
-        */
-       
-       /* The non-extended LIST command is required to treat an empty
-        * ("" string) mailbox name argument as a special request to return the
-        * hierarchy delimiter and the root name of the name given in the
-        * reference parameter.
-        */
-       if ( (StrLength(ImapFilter.patterns[0]) == 0) && (extended_list_in_use == 0) ) {
-               IAPrintf("* %s (\\Noselect) \"/\" \"\"\r\n", ImapFilter.verb);
-       }
-
-       /* Non-empty mailbox names, and any form of the extended LIST command,
-        * is handled by this loop.
-        */
-       else {
-               imap_list_floors(ImapFilter.verb, 
-                                ImapFilter.num_patterns, 
-                                ImapFilter.patterns);
-               CtdlForEachRoom(imap_listroom, (char**)&ImapFilter);
-       }
-
-       /* 
-        * Free the pattern buffers we allocated above.
-        */
-       for (i=0; i<ImapFilter.num_patterns; ++i) {
-               FreeStrBuf(&ImapFilter.patterns[i]);
-       }
-       free(ImapFilter.patterns);
-
-       IReplyPrintf("OK %s completed", ImapFilter.verb);
-}
diff --git a/citadel/modules/imap/imap_list.h b/citadel/modules/imap/imap_list.h
deleted file mode 100644 (file)
index 902c6d1..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-
-/*
- * In the extended form of LIST the client is allowed to specify
- * multiple match patterns.  How many will we allow?
- */
-#define MAX_PATTERNS 20
-
-void imap_list(int num_parms, ConstStr *Params);
diff --git a/citadel/modules/imap/imap_metadata.c b/citadel/modules/imap/imap_metadata.c
deleted file mode 100644 (file)
index 5d5e95f..0000000
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * IMAP METADATA extension
- *
- * This is an implementation of the Bynari variant of the METADATA extension.
- *
- * Copyright (c) 2007-2017 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "imap_fetch.h"
-#include "imap_misc.h"
-#include "genstamp.h"
-
-#include "ctdl_module.h"
-
-/*
- * Implements the SETMETADATA command.
- *
- * Again, the only thing we're interested in setting here is the folder type.
- *
- * Attempting to set anything else calls a stub which fools the client into
- * thinking that there is no remaining space available to store annotations.
- */
-void imap_setmetadata(int num_parms, ConstStr *Params) {
-       char roomname[ROOMNAMELEN];
-       char savedroom[ROOMNAMELEN];
-       int msgs, new;
-       int ret;
-       int setting_user_value = 0;
-       char set_value[32];
-       int set_view = VIEW_BBS;
-       visit vbuf;
-
-       if (num_parms != 6) {
-               IReply("BAD usage error");
-               return;
-       }
-
-       /*
-        * Don't allow other types of metadata to be set
-        */
-       if (strcasecmp(Params[3].Key, "/vendor/kolab/folder-type")) {
-               IReply("NO [METADATA TOOMANY] SETMETADATA failed");
-               return;
-       }
-
-       if (!strcasecmp(Params[4].Key, "(value.shared")) {
-               setting_user_value = 0;                         /* global view */
-       }
-       else if (!strcasecmp(Params[4].Key, "(value.priv")) {
-               setting_user_value = 1;                         /* per-user view */
-       }
-       else {
-               IReply("NO [METADATA TOOMANY] SETMETADATA failed");
-               return;
-       }
-
-       /*
-        * Extract the folder type without any parentheses.  Then learn
-        * the Citadel view type based on the supplied folder type.
-        */
-       extract_token(set_value, Params[5].Key, 0, ')', sizeof set_value);
-       if (!strncasecmp(set_value, "mail", 4)) {
-               set_view = VIEW_MAILBOX;
-       }
-       else if (!strncasecmp(set_value, "event", 5)) {
-               set_view = VIEW_CALENDAR;
-       }
-       else if (!strncasecmp(set_value, "contact", 7)) {
-               set_view = VIEW_ADDRESSBOOK;
-       }
-       else if (!strncasecmp(set_value, "journal", 7)) {
-               set_view = VIEW_JOURNAL;
-       }
-       else if (!strncasecmp(set_value, "note", 4)) {
-               set_view = VIEW_NOTES;
-       }
-       else if (!strncasecmp(set_value, "task", 4)) {
-               set_view = VIEW_TASKS;
-       }
-       else {
-               set_view = VIEW_MAILBOX;
-       }
-
-       ret = imap_grabroom(roomname, Params[2].Key, 1);
-       if (ret != 0) {
-               IReply("NO Invalid mailbox name or access denied");
-               return;
-       }
-
-       /*
-        * CtdlUserGoto() formally takes us to the desired room.  (If another
-        * folder is selected, save its name so we can return there!!!!!)
-        */
-       if (IMAP->selected) {
-               strcpy(savedroom, CC->room.QRname);
-       }
-       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
-       /*
-        * Always set the per-user view to the requested one.
-        */
-       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
-       vbuf.v_view = set_view;
-       CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
-
-       /* If this is a "value.priv" set operation, we're done. */
-
-       if (setting_user_value)
-       {
-               IReply("OK SETANNOTATION complete");
-       }
-
-       /* If this is a "value.shared" set operation, we are allowed to perform it
-        * under certain conditions.
-        */
-       else if (       (is_room_aide())                                        /* aide or room aide */
-               ||      (       (CC->room.QRflags & QR_MAILBOX)
-                       &&      (CC->user.usernum == atol(CC->room.QRname))     /* mailbox owner */
-                       )
-               ||      (msgs == 0)             /* hack: if room is empty, assume we just created it */
-       ) {
-               CtdlGetRoomLock(&CC->room, CC->room.QRname);
-               CC->room.QRdefaultview = set_view;
-               CtdlPutRoomLock(&CC->room);
-               IReply("OK SETANNOTATION complete");
-       }
-
-       /* If we got to this point, we don't have permission to set the default view. */
-       else {
-               IReply("NO [METADATA TOOMANY] SETMETADATA failed");
-       }
-
-       /*
-        * If a different folder was previously selected, return there now.
-        */
-       if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) {
-               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
-       }
-       return;
-}
-
-
-/*
- * Implements the GETMETADATA command.
- *
- * Regardless of what the client asked for, we are going to supply them with
- * the folder type.  It's the only metadata we have anyway.
- */
-void imap_getmetadata(int num_parms, ConstStr *Params) {
-       char roomname[ROOMNAMELEN];
-       char savedroom[ROOMNAMELEN];
-       int msgs, new;
-       int ret;
-       int found = 0;
-
-/* this doesn't work if you have rooms/floors with spaces. 
-   we need this for the bynari connector.
-       if (num_parms > 5) {
-               IReply("BAD usage error");
-               return;
-       }
-*/
-       ret = imap_grabroom(roomname, Params[2].Key, 1);
-       if (ret != 0) {
-               IReply("NO Invalid mailbox name or access denied");
-               return;
-       }
-
-       /*
-        * CtdlUserGoto() formally takes us to the desired room.  (If another
-        * folder is selected, save its name so we can return there!!!!!)
-        */
-       if (IMAP->selected) {
-               strcpy(savedroom, CC->room.QRname);
-       }
-       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
-       IAPuts("* METADATA ");
-       IPutCParamStr(2);
-       IAPuts(" \"/vendor/kolab/folder-type\" (\"value.shared\" \"");
-
-       /* If it's one of our hard-coded default rooms, we know what to do... */
-
-       if (CC->room.QRname[10] == '.')
-       {
-               if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
-                       found = 1;
-                       IAPuts("mail.inbox");
-               }
-               else if (!strcasecmp(&CC->room.QRname[11], SENTITEMS)) {
-                       found = 1;
-                       IAPuts("mail.sentitems");
-               }
-               else if (!strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) {
-                       found = 1;
-                       IAPuts("mail.drafts");
-               }
-               else if (!strcasecmp(&CC->room.QRname[11], USERCALENDARROOM)) {
-                       found = 1;
-                       IAPuts("event.default");
-               }
-               else if (!strcasecmp(&CC->room.QRname[11], USERCONTACTSROOM)) {
-                       found = 1;
-                       IAPuts("contact.default");
-               }
-               else if (!strcasecmp(&CC->room.QRname[11], USERNOTESROOM)) {
-                       found = 1;
-                       IAPuts("note.default");
-               }
-               else if (!strcasecmp(&CC->room.QRname[11], USERTASKSROOM)) {
-                       found = 1;
-                       IAPuts("task.default");
-               }
-       }
-
-       /* Otherwise, use the view for this room to determine the type of data.
-        * We are going with the default view rather than the user's view, because
-        * the default view almost always defines the actual contents, while the
-        * user's view might only make changes to presentation.  It also saves us
-        * an extra database access because we don't need to load the visit record.
-        */
-       if (!found)
-       {
-               if (CC->room.QRdefaultview == VIEW_CALENDAR) {
-                       IAPuts("event");
-               }
-               else if (CC->room.QRdefaultview == VIEW_ADDRESSBOOK) {
-                       IAPuts("contact");
-               }
-               else if (CC->room.QRdefaultview == VIEW_TASKS) {
-                       IAPuts("task");
-               }
-               else if (CC->room.QRdefaultview == VIEW_NOTES) {
-                       IAPuts("note");
-               }
-               else if (CC->room.QRdefaultview == VIEW_JOURNAL) {
-                       IAPuts("journal");
-               }
-       }
-       /* If none of the above conditions were met, consider it an ordinary mailbox. */
-
-       if (!found) {
-               IAPuts("mail");
-       }
-
-       /* "mail.outbox" and "junkemail" are not implemented. */
-
-       IAPuts("\")\r\n");
-
-       /*
-        * If a different folder was previously selected, return there now.
-        */
-       if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) {
-               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
-       }
-
-       IReply("OK GETMETADATA complete");
-       return;
-}
-
diff --git a/citadel/modules/imap/imap_metadata.h b/citadel/modules/imap/imap_metadata.h
deleted file mode 100644 (file)
index 3f2a8bc..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Copyright (c) 2007-2017 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-void imap_getmetadata(int num_parms, ConstStr *Params);
-void imap_setmetadata(int num_parms, ConstStr *Params);
diff --git a/citadel/modules/imap/imap_misc.c b/citadel/modules/imap/imap_misc.c
deleted file mode 100644 (file)
index 4cd8ece..0000000
+++ /dev/null
@@ -1,412 +0,0 @@
-/*
- * Copyright (c) 1987-2020 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "room_ops.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "imap_fetch.h"
-#include "imap_misc.h"
-#include "genstamp.h"
-#include "ctdl_module.h"
-
-
-
-
-
-/*
- * imap_copy() calls imap_do_copy() to do its actual work, once it's
- * validated and boiled down the request a bit.  (returns 0 on success)
- */
-int imap_do_copy(const char *destination_folder) {
-       citimap *Imap = IMAP;
-       int i;
-       char roomname[ROOMNAMELEN];
-       struct ctdlroom qrbuf;
-       long *selected_msgs = NULL;
-       int num_selected = 0;
-
-       if (Imap->num_msgs < 1) {
-               return(0);
-       }
-
-       i = imap_grabroom(roomname, destination_folder, 1);
-       if (i != 0) return(i);
-
-       /*
-        * Copy all the message pointers in one shot.
-        */
-       selected_msgs = malloc(sizeof(long) * Imap->num_msgs);
-       if (selected_msgs == NULL) return(-1);
-
-       for (i = 0; i < Imap->num_msgs; ++i) {
-               if (Imap->flags[i] & IMAP_SELECTED) {
-                       selected_msgs[num_selected++] = Imap->msgids[i];
-               }
-       }
-
-       if (num_selected > 0) {
-               CtdlSaveMsgPointersInRoom(roomname, selected_msgs, num_selected, 1, NULL, 0);
-       }
-       free(selected_msgs);
-
-       /* Don't bother wasting any more time if there were no messages. */
-       if (num_selected == 0) {
-               return(0);
-       }
-
-       /* Enumerate lists of messages for which flags are toggled */
-       long *seen_yes = NULL;
-       int num_seen_yes = 0;
-       long *seen_no = NULL;
-       int num_seen_no = 0;
-       long *answ_yes = NULL;
-       int num_answ_yes = 0;
-       long *answ_no = NULL;
-       int num_answ_no = 0;
-
-       seen_yes = malloc(num_selected * sizeof(long));
-       seen_no = malloc(num_selected * sizeof(long));
-       answ_yes = malloc(num_selected * sizeof(long));
-       answ_no = malloc(num_selected * sizeof(long));
-
-       for (i = 0; i < Imap->num_msgs; ++i) {
-               if (Imap->flags[i] & IMAP_SELECTED) {
-                       if (Imap->flags[i] & IMAP_SEEN) {
-                               seen_yes[num_seen_yes++] = Imap->msgids[i];
-                       }
-                       if ((Imap->flags[i] & IMAP_SEEN) == 0) {
-                               seen_no[num_seen_no++] = Imap->msgids[i];
-                       }
-                       if (Imap->flags[i] & IMAP_ANSWERED) {
-                               answ_yes[num_answ_yes++] = Imap->msgids[i];
-                       }
-                       if ((Imap->flags[i] & IMAP_ANSWERED) == 0) {
-                               answ_no[num_answ_no++] = Imap->msgids[i];
-                       }
-               }
-       }
-
-       /* Set the flags... */
-       i = CtdlGetRoom(&qrbuf, roomname);
-       if (i == 0) {
-               CtdlSetSeen(seen_yes, num_seen_yes, 1, ctdlsetseen_seen, NULL, &qrbuf);
-               CtdlSetSeen(seen_no, num_seen_no, 0, ctdlsetseen_seen, NULL, &qrbuf);
-               CtdlSetSeen(answ_yes, num_answ_yes, 1, ctdlsetseen_answered, NULL, &qrbuf);
-               CtdlSetSeen(answ_no, num_answ_no, 0, ctdlsetseen_answered, NULL, &qrbuf);
-       }
-
-       free(seen_yes);
-       free(seen_no);
-       free(answ_yes);
-       free(answ_no);
-
-       return(0);
-}
-
-
-/*
- * Output the [COPYUID xxx yyy] response code required by RFC2359
- * to tell the client the UID's of the messages that were copied (if any).
- * We are assuming that the IMAP_SELECTED flag is still set on any relevant
- * messages in our source room.  Since the Citadel system uses UID's that
- * are both globally unique and persistent across a room-to-room copy, we
- * can get this done quite easily.
- */
-void imap_output_copyuid_response(citimap *Imap) {
-       int i;
-       StrBuf *MsgsCopied = NewStrBuf();
-
-       for (i = 0; i < Imap->num_msgs; ++i) {
-               if (Imap->flags[i] & IMAP_SELECTED) {
-                       if (StrLength(MsgsCopied) > 0) {
-                               StrBufAppendBufPlain(MsgsCopied, HKEY(","), 0);
-                       }
-                       StrBufAppendPrintf(MsgsCopied, "%ld", Imap->msgids[i]);
-               }
-       }
-
-       if (StrLength(MsgsCopied) > 0) {
-               IAPrintf("[COPYUID %ld %s %s] ", GLOBAL_UIDVALIDITY_VALUE, ChrPtr(MsgsCopied), ChrPtr(MsgsCopied));
-       }
-
-       FreeStrBuf(&MsgsCopied);
-}
-
-
-/*
- * This function is called by the main command loop.
- */
-void imap_copy(int num_parms, ConstStr *Params) {
-       int ret;
-
-       if (num_parms != 4) {
-               IReply("BAD invalid parameters");
-               return;
-       }
-
-       if (imap_is_message_set(Params[2].Key)) {
-               imap_pick_range(Params[2].Key, 0);
-       }
-       else {
-               IReply("BAD invalid parameters");
-               return;
-       }
-
-       ret = imap_do_copy(Params[3].Key);
-       if (!ret) {
-               IAPrintf("%s OK ", Params[0].Key);
-               imap_output_copyuid_response(IMAP);
-               IAPuts("COPY completed\r\n");
-       }
-       else {
-               IReplyPrintf("NO COPY failed (error %d)", ret);
-       }
-}
-
-/*
- * This function is called by the main command loop.
- */
-void imap_uidcopy(int num_parms, ConstStr *Params) {
-
-       if (num_parms != 5) {
-               IReply("BAD invalid parameters");
-               return;
-       }
-
-       if (imap_is_message_set(Params[3].Key)) {
-               imap_pick_range(Params[3].Key, 1);
-       }
-       else {
-               IReply("BAD invalid parameters");
-               return;
-       }
-
-       if (imap_do_copy(Params[4].Key) == 0) {
-               IAPrintf("%s OK ", Params[0].Key);
-               imap_output_copyuid_response(IMAP);
-               IAPuts("UID COPY completed\r\n");
-       }
-       else {
-               IReply("NO UID COPY failed");
-       }
-}
-
-
-/*
- * imap_do_append_flags() is called by imap_append() to set any flags that
- * the client specified at append time.
- *
- * FIXME find a way to do these in bulk so we don't max out our db journal
- */
-void imap_do_append_flags(long new_msgnum, char *new_message_flags) {
-       char flags[32];
-       char this_flag[sizeof flags];
-       int i;
-
-       if (new_message_flags == NULL) return;
-       if (IsEmptyStr(new_message_flags)) return;
-
-       safestrncpy(flags, new_message_flags, sizeof flags);
-
-       for (i=0; i<num_tokens(flags, ' '); ++i) {
-               extract_token(this_flag, flags, i, ' ', sizeof this_flag);
-               if (this_flag[0] == '\\') strcpy(this_flag, &this_flag[1]);
-               if (!strcasecmp(this_flag, "Seen")) {
-                       CtdlSetSeen(&new_msgnum, 1, 1, ctdlsetseen_seen,
-                               NULL, NULL);
-               }
-               if (!strcasecmp(this_flag, "Answered")) {
-                       CtdlSetSeen(&new_msgnum, 1, 1, ctdlsetseen_answered,
-                               NULL, NULL);
-               }
-       }
-}
-
-
-/*
- * This function is called by the main command loop.
- */
-void imap_append(int num_parms, ConstStr *Params) {
-       long literal_length;
-       struct CtdlMessage *msg = NULL;
-       long new_msgnum = (-1L);
-       int ret = 0;
-       char roomname[ROOMNAMELEN];
-       char errbuf[SIZ];
-       char dummy[SIZ];
-       char savedroom[ROOMNAMELEN];
-       int msgs, new;
-       int i;
-       char new_message_flags[SIZ];
-       citimap *Imap;
-
-       if (num_parms < 4) {
-               IReply("BAD usage error");
-               return;
-       }
-
-       if ( (Params[num_parms-1].Key[0] != '{')
-          || (Params[num_parms-1].Key[Params[num_parms-1].len-1] != '}') )  {
-               IReply("BAD no message literal supplied");
-               return;
-       }
-
-       *new_message_flags = '\0';
-       if (num_parms >= 5) {
-               for (i=3; i<num_parms; ++i) {
-                       strcat(new_message_flags, Params[i].Key);
-                       strcat(new_message_flags, " ");
-               }
-               stripallbut(new_message_flags, '(', ')');
-       }
-
-       /* This is how we'd do this if it were relevant in our data store.
-        * if (num_parms >= 6) {
-        *  new_message_internaldate = parms[4];
-        * }
-        */
-
-       literal_length = atol(&Params[num_parms-1].Key[1]);
-       if (literal_length < 1) {
-               IReply("BAD Message length must be at least 1.");
-               return;
-       }
-
-       Imap = IMAP;
-       imap_free_transmitted_message();        /* just in case. */
-
-       Imap->TransmittedMessage = NewStrBufPlain(NULL, literal_length);
-
-       if (Imap->TransmittedMessage == NULL) {
-               IReply("NO Cannot allocate memory.");
-               return;
-       }
-       
-       IAPrintf("+ Transmit message now.\r\n");
-       
-       IUnbuffer ();
-
-       client_read_blob(Imap->TransmittedMessage, literal_length, CtdlGetConfigInt("c_sleeping"));
-
-       if ((ret < 0) || (StrLength(Imap->TransmittedMessage) < literal_length)) {
-               IReply("NO Read failed.");
-               return;
-       }
-
-       /* Client will transmit a trailing CRLF after the literal (the message
-        * text) is received.  This call to client_getln() absorbs it.
-        */
-       flush_output();
-       client_getln(dummy, sizeof dummy);
-
-       /* Convert RFC822 newlines (CRLF) to Unix newlines (LF) */
-       syslog(LOG_DEBUG, "Converting CRLF to LF");
-       StrBufToUnixLF(Imap->TransmittedMessage);
-
-       syslog(LOG_DEBUG, "Converting message format");
-       msg = convert_internet_message_buf(&Imap->TransmittedMessage);
-
-       ret = imap_grabroom(roomname, Params[2].Key, 1);
-       if (ret != 0) {
-               IReply("NO Invalid mailbox name or access denied");
-               return;
-       }
-
-       /*
-        * CtdlUserGoto() formally takes us to the desired room.  (If another
-        * folder is selected, save its name so we can return there!!!!!)
-        */
-       if (Imap->selected) {
-               strcpy(savedroom, CC->room.QRname);
-       }
-       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
-       /* If the user is locally authenticated, FORCE the From: header to
-        * show up as the real sender.  (Configurable setting)
-        */
-       if (CC->logged_in) {
-               if ( ((CC->room.QRflags & QR_MAILBOX) == 0) && (CtdlGetConfigInt("c_imap_keep_from") == 0))
-               {
-                       CM_SetField(msg, eAuthor, CC->user.fullname, strlen(CC->user.fullname));
-               }
-       }
-
-       /* 
-        * Can we post here?
-        */
-       ret = CtdlDoIHavePermissionToPostInThisRoom(errbuf, sizeof errbuf, NULL, POST_LOGGED_IN, 0);
-
-       if (ret) {
-               /* Nope ... print an error message */
-               IReplyPrintf("NO %s", errbuf);
-       }
-
-       else {
-               /* Yes ... go ahead and post! */
-               if (msg != NULL) {
-                       new_msgnum = CtdlSubmitMsg(msg, NULL, "");
-               }
-               if (new_msgnum >= 0L) {
-                       IReplyPrintf("OK [APPENDUID %ld %ld] APPEND completed",
-                                    GLOBAL_UIDVALIDITY_VALUE, new_msgnum);
-               }
-               else {
-                       IReplyPrintf("BAD Error %ld saving message to disk.",
-                                    new_msgnum);
-               }
-       }
-
-       /*
-        * IMAP protocol response to client has already been sent by now.
-        *
-        * If another folder is selected, go back to that room so we can resume
-        * our happy day without violent explosions.
-        */
-       if (Imap->selected) {
-               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
-       }
-
-       /* We don't need this buffer anymore */
-       CM_Free(msg);
-
-       if (IsEmptyStr(new_message_flags)) {
-               imap_do_append_flags(new_msgnum, new_message_flags);
-       }
-}
diff --git a/citadel/modules/imap/imap_misc.h b/citadel/modules/imap/imap_misc.h
deleted file mode 100644 (file)
index d0dfa40..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright (c) 2001-2017 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-void imap_copy(int num_parms, ConstStr *Params);
-void imap_uidcopy(int num_parms, ConstStr *Params);
-void imap_append(int num_parms, ConstStr *Params);
diff --git a/citadel/modules/imap/imap_search.c b/citadel/modules/imap/imap_search.c
deleted file mode 100644 (file)
index efc1992..0000000
+++ /dev/null
@@ -1,648 +0,0 @@
-/*
- * Implements IMAP's gratuitously complex SEARCH command.
- *
- * Copyright (c) 2001-2020 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "ctdl_module.h"
-
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "imap_fetch.h"
-#include "imap_search.h"
-#include "genstamp.h"
-
-
-/*
- * imap_do_search() calls imap_do_search_msg() to search an individual
- * message after it has been fetched from the disk.  This function returns
- * nonzero if there is a match.
- *
- * supplied_msg MAY be used to pass a pointer to the message in memory,
- * if for some reason it's already been loaded.  If not, the message will
- * be loaded only if one or more search criteria require it.
- */
-int imap_do_search_msg(int seq, struct CtdlMessage *supplied_msg,
-                       int num_items, ConstStr *itemlist, int is_uid) {
-
-       citimap *Imap = IMAP;
-       int match = 0;
-       int is_not = 0;
-       int is_or = 0;
-       int pos = 0;
-       int i;
-       char *fieldptr;
-       struct CtdlMessage *msg = NULL;
-       int need_to_free_msg = 0;
-
-       if (num_items == 0) {
-               return(0);
-       }
-       msg = supplied_msg;
-
-       /* Initially we start at the beginning. */
-       pos = 0;
-
-       /* Check for the dreaded NOT criterion. */
-       if (!strcasecmp(itemlist[0].Key, "NOT")) {
-               is_not = 1;
-               pos = 1;
-       }
-
-       /* Check for the dreaded OR criterion. */
-       if (!strcasecmp(itemlist[0].Key, "OR")) {
-               is_or = 1;
-               pos = 1;
-       }
-
-       /* Now look for criteria. */
-       if (!strcasecmp(itemlist[pos].Key, "ALL")) {
-               match = 1;
-               ++pos;
-       }
-       
-       else if (!strcasecmp(itemlist[pos].Key, "ANSWERED")) {
-               if (Imap->flags[seq-1] & IMAP_ANSWERED) {
-                       match = 1;
-               }
-               ++pos;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "BCC")) {
-               if (msg == NULL) {
-                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                       need_to_free_msg = 1;
-               }
-               if (msg != NULL) {
-                       fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Bcc");
-                       if (fieldptr != NULL) {
-                               if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) {
-                                       match = 1;
-                               }
-                               free(fieldptr);
-                       }
-               }
-               pos += 2;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "BEFORE")) {
-               if (msg == NULL) {
-                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                       need_to_free_msg = 1;
-               }
-               if (msg != NULL) {
-                       if (!CM_IsEmpty(msg, eTimestamp)) {
-                               if (imap_datecmp(itemlist[pos+1].Key,
-                                               atol(msg->cm_fields[eTimestamp])) < 0) {
-                                       match = 1;
-                               }
-                       }
-               }
-               pos += 2;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "BODY")) {
-
-               /* If fulltext indexing is active, on this server,
-                *  all messages have already been qualified.
-                */
-               if (CtdlGetConfigInt("c_enable_fulltext")) {
-                       match = 1;
-               }
-
-               /* Otherwise, we have to do a slow search. */
-               else {
-                       if (msg == NULL) {
-                               msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                               need_to_free_msg = 1;
-                       }
-                       if (msg != NULL) {
-                               if (bmstrcasestr(msg->cm_fields[eMesageText], itemlist[pos+1].Key)) {
-                                       match = 1;
-                               }
-                       }
-               }
-
-               pos += 2;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "CC")) {
-               if (msg == NULL) {
-                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                       need_to_free_msg = 1;
-               }
-               if (msg != NULL) {
-                       fieldptr = msg->cm_fields[eCarbonCopY];
-                       if (fieldptr != NULL) {
-                               if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) {
-                                       match = 1;
-                               }
-                       }
-                       else {
-                               fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Cc");
-                               if (fieldptr != NULL) {
-                                       if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) {
-                                               match = 1;
-                                       }
-                                       free(fieldptr);
-                               }
-                       }
-               }
-               pos += 2;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "DELETED")) {
-               if (Imap->flags[seq-1] & IMAP_DELETED) {
-                       match = 1;
-               }
-               ++pos;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "DRAFT")) {
-               if (Imap->flags[seq-1] & IMAP_DRAFT) {
-                       match = 1;
-               }
-               ++pos;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "FLAGGED")) {
-               if (Imap->flags[seq-1] & IMAP_FLAGGED) {
-                       match = 1;
-               }
-               ++pos;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "FROM")) {
-               if (msg == NULL) {
-                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                       need_to_free_msg = 1;
-               }
-               if (msg != NULL) {
-                       if (bmstrcasestr(msg->cm_fields[eAuthor], itemlist[pos+1].Key)) {
-                               match = 1;
-                       }
-                       if (bmstrcasestr(msg->cm_fields[erFc822Addr], itemlist[pos+1].Key)) {
-                               match = 1;
-                       }
-               }
-               pos += 2;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "HEADER")) {
-
-               /* We've got to do a slow search for this because the client
-                * might be asking for an RFC822 header field that has not been
-                * converted into a Citadel header field.  That requires
-                * examining the message body.
-                */
-               if (msg == NULL) {
-                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                       need_to_free_msg = 1;
-               }
-
-               if (msg != NULL) {
-       
-                       CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
-                       CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_FAST, 0, 1, 0);
-       
-                       fieldptr = rfc822_fetch_field(ChrPtr(CC->redirect_buffer), itemlist[pos+1].Key);
-                       if (fieldptr != NULL) {
-                               if (bmstrcasestr(fieldptr, itemlist[pos+2].Key)) {
-                                       match = 1;
-                               }
-                               free(fieldptr);
-                       }
-       
-                       FreeStrBuf(&CC->redirect_buffer);
-               }
-
-               pos += 3;       /* Yes, three */
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "KEYWORD")) {
-               /* not implemented */
-               pos += 2;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "LARGER")) {
-               if (msg == NULL) {
-                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                       need_to_free_msg = 1;
-               }
-               if (msg != NULL) {
-                       if (msg->cm_lengths[eMesageText] > atoi(itemlist[pos+1].Key)) {
-                               match = 1;
-                       }
-               }
-               pos += 2;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "NEW")) {
-               if ( (Imap->flags[seq-1] & IMAP_RECENT) && (!(Imap->flags[seq-1] & IMAP_SEEN))) {
-                       match = 1;
-               }
-               ++pos;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "OLD")) {
-               if (!(Imap->flags[seq-1] & IMAP_RECENT)) {
-                       match = 1;
-               }
-               ++pos;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "ON")) {
-               if (msg == NULL) {
-                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                       need_to_free_msg = 1;
-               }
-               if (msg != NULL) {
-                       if (!CM_IsEmpty(msg, eTimestamp)) {
-                               if (imap_datecmp(itemlist[pos+1].Key,
-                                               atol(msg->cm_fields[eTimestamp])) == 0) {
-                                       match = 1;
-                               }
-                       }
-               }
-               pos += 2;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "RECENT")) {
-               if (Imap->flags[seq-1] & IMAP_RECENT) {
-                       match = 1;
-               }
-               ++pos;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "SEEN")) {
-               if (Imap->flags[seq-1] & IMAP_SEEN) {
-                       match = 1;
-               }
-               ++pos;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "SENTBEFORE")) {
-               if (msg == NULL) {
-                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                       need_to_free_msg = 1;
-               }
-               if (msg != NULL) {
-                       if (!CM_IsEmpty(msg, eTimestamp)) {
-                               if (imap_datecmp(itemlist[pos+1].Key,
-                                               atol(msg->cm_fields[eTimestamp])) < 0) {
-                                       match = 1;
-                               }
-                       }
-               }
-               pos += 2;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "SENTON")) {
-               if (msg == NULL) {
-                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                       need_to_free_msg = 1;
-               }
-               if (msg != NULL) {
-                       if (!CM_IsEmpty(msg, eTimestamp)) {
-                               if (imap_datecmp(itemlist[pos+1].Key,
-                                               atol(msg->cm_fields[eTimestamp])) == 0) {
-                                       match = 1;
-                               }
-                       }
-               }
-               pos += 2;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "SENTSINCE")) {
-               if (msg == NULL) {
-                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                       need_to_free_msg = 1;
-               }
-               if (msg != NULL) {
-                       if (!CM_IsEmpty(msg, eTimestamp)) {
-                               if (imap_datecmp(itemlist[pos+1].Key,
-                                               atol(msg->cm_fields[eTimestamp])) >= 0) {
-                                       match = 1;
-                               }
-                       }
-               }
-               pos += 2;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "SINCE")) {
-               if (msg == NULL) {
-                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                       need_to_free_msg = 1;
-               }
-               if (msg != NULL) {
-                       if (!CM_IsEmpty(msg, eTimestamp)) {
-                               if (imap_datecmp(itemlist[pos+1].Key,
-                                               atol(msg->cm_fields[eTimestamp])) >= 0) {
-                                       match = 1;
-                               }
-                       }
-               }
-               pos += 2;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "SMALLER")) {
-               if (msg == NULL) {
-                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                       need_to_free_msg = 1;
-               }
-               if (msg != NULL) {
-                       if (msg->cm_lengths[eMesageText] < atoi(itemlist[pos+1].Key)) {
-                               match = 1;
-                       }
-               }
-               pos += 2;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "SUBJECT")) {
-               if (msg == NULL) {
-                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                       need_to_free_msg = 1;
-               }
-               if (msg != NULL) {
-                       if (bmstrcasestr(msg->cm_fields[eMsgSubject], itemlist[pos+1].Key)) {
-                               match = 1;
-                       }
-               }
-               pos += 2;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "TEXT")) {
-               if (msg == NULL) {
-                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                       need_to_free_msg = 1;
-               }
-               if (msg != NULL) {
-                       for (i='A'; i<='Z'; ++i) {
-                               if (bmstrcasestr(msg->cm_fields[i], itemlist[pos+1].Key)) {
-                                       match = 1;
-                               }
-                       }
-               }
-               pos += 2;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "TO")) {
-               if (msg == NULL) {
-                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
-                       need_to_free_msg = 1;
-               }
-               if (msg != NULL) {
-                       if (bmstrcasestr(msg->cm_fields[eRecipient], itemlist[pos+1].Key)) {
-                               match = 1;
-                       }
-               }
-               pos += 2;
-       }
-
-       /* FIXME this is b0rken.  fix it. */
-       else if (imap_is_message_set(itemlist[pos].Key)) {
-               if (is_msg_in_sequence_set(itemlist[pos].Key, seq)) {
-                       match = 1;
-               }
-               pos += 1;
-       }
-
-       /* FIXME this is b0rken.  fix it. */
-       else if (!strcasecmp(itemlist[pos].Key, "UID")) {
-               if (is_msg_in_sequence_set(itemlist[pos+1].Key, Imap->msgids[seq-1])) {
-                       match = 1;
-               }
-               pos += 2;
-       }
-
-       /* Now here come the 'UN' criteria.  Why oh why do we have to
-        * implement *both* the 'UN' criteria *and* the 'NOT' keyword?  Why
-        * can't there be *one* way to do things?  More gratuitous complexity.
-        */
-
-       else if (!strcasecmp(itemlist[pos].Key, "UNANSWERED")) {
-               if ((Imap->flags[seq-1] & IMAP_ANSWERED) == 0) {
-                       match = 1;
-               }
-               ++pos;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "UNDELETED")) {
-               if ((Imap->flags[seq-1] & IMAP_DELETED) == 0) {
-                       match = 1;
-               }
-               ++pos;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "UNDRAFT")) {
-               if ((Imap->flags[seq-1] & IMAP_DRAFT) == 0) {
-                       match = 1;
-               }
-               ++pos;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "UNFLAGGED")) {
-               if ((Imap->flags[seq-1] & IMAP_FLAGGED) == 0) {
-                       match = 1;
-               }
-               ++pos;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "UNKEYWORD")) {
-               /* FIXME */
-               pos += 2;
-       }
-
-       else if (!strcasecmp(itemlist[pos].Key, "UNSEEN")) {
-               if ((Imap->flags[seq-1] & IMAP_SEEN) == 0) {
-                       match = 1;
-               }
-               ++pos;
-       }
-
-       /* Remember to negate if we were told to */
-       if (is_not) {
-               match = !match;
-       }
-
-       /* Keep going if there are more criteria! */
-       if (pos < num_items) {
-
-               if (is_or) {
-                       match = (match || imap_do_search_msg(seq, msg,
-                               num_items - pos, &itemlist[pos], is_uid));
-               }
-               else {
-                       match = (match && imap_do_search_msg(seq, msg,
-                               num_items - pos, &itemlist[pos], is_uid));
-               }
-
-       }
-
-       if (need_to_free_msg) {
-               CM_Free(msg);
-       }
-       return(match);
-}
-
-
-/*
- * imap_search() calls imap_do_search() to do its actual work, once it's
- * validated and boiled down the request a bit.
- */
-void imap_do_search(int num_items, ConstStr *itemlist, int is_uid) {
-       citimap *Imap = IMAP;
-       int i, j, k;
-       int fts_num_msgs = 0;
-       long *fts_msgs = NULL;
-       int is_in_list = 0;
-       int num_results = 0;
-
-       /* Strip parentheses.  We realize that this method will not work
-        * in all cases, but it seems to work with all currently available
-        * client software.  Revisit later...
-        */
-       for (i=0; i<num_items; ++i) {
-               if (itemlist[i].Key[0] == '(') {
-                       
-                       TokenCutLeft(&Imap->Cmd, 
-                                    &itemlist[i], 
-                                    1);
-               }
-               if (itemlist[i].Key[itemlist[i].len-1] == ')') {
-                       TokenCutRight(&Imap->Cmd, 
-                                     &itemlist[i], 
-                                     1);
-               }
-       }
-
-       /* If there is a BODY search criterion in the query, use our full
-        * text index to disqualify messages that don't have any chance of
-        * matching.  (Only do this if the index is enabled!!)
-        */
-       if (CtdlGetConfigInt("c_enable_fulltext")) for (i=0; i<(num_items-1); ++i) {
-               if (!strcasecmp(itemlist[i].Key, "BODY")) {
-                       CtdlModuleDoSearch(&fts_num_msgs, &fts_msgs, itemlist[i+1].Key, "fulltext");
-                       if (fts_num_msgs > 0) {
-                               for (j=0; j < Imap->num_msgs; ++j) {
-                                       if (Imap->flags[j] & IMAP_SELECTED) {
-                                               is_in_list = 0;
-                                               for (k=0; k<fts_num_msgs; ++k) {
-                                                       if (Imap->msgids[j] == fts_msgs[k]) {
-                                                               ++is_in_list;
-                                                       }
-                                               }
-                                       }
-                                       if (!is_in_list) {
-                                               Imap->flags[j] = Imap->flags[j] & ~IMAP_SELECTED;
-                                       }
-                               }
-                       }
-                       else {          /* no hits on the index; disqualify every message */
-                               for (j=0; j < Imap->num_msgs; ++j) {
-                                       Imap->flags[j] = Imap->flags[j] & ~IMAP_SELECTED;
-                               }
-                       }
-                       if (fts_msgs) {
-                               free(fts_msgs);
-                       }
-               }
-       }
-
-       /* Now go through the messages and apply all search criteria. */
-       buffer_output();
-       IAPuts("* SEARCH ");
-       if (Imap->num_msgs > 0)
-        for (i = 0; i < Imap->num_msgs; ++i)
-         if (Imap->flags[i] & IMAP_SELECTED) {
-               if (imap_do_search_msg(i+1, NULL, num_items, itemlist, is_uid)) {
-                       if (num_results != 0) {
-                               IAPuts(" ");
-                       }
-                       if (is_uid) {
-                               IAPrintf("%ld", Imap->msgids[i]);
-                       }
-                       else {
-                               IAPrintf("%d", i+1);
-                       }
-                       ++num_results;
-               }
-       }
-       IAPuts("\r\n");
-       unbuffer_output();
-}
-
-
-/*
- * This function is called by the main command loop.
- */
-void imap_search(int num_parms, ConstStr *Params) {
-       int i;
-
-       if (num_parms < 3) {
-               IReply("BAD invalid parameters");
-               return;
-       }
-
-       for (i = 0; i < IMAP->num_msgs; ++i) {
-               IMAP->flags[i] |= IMAP_SELECTED;
-       }
-
-       imap_do_search(num_parms-2, &Params[2], 0);
-       IReply("OK SEARCH completed");
-}
-
-/*
- * This function is called by the main command loop.
- */
-void imap_uidsearch(int num_parms, ConstStr *Params) {
-       int i;
-
-       if (num_parms < 4) {
-               IReply("BAD invalid parameters");
-               return;
-       }
-
-       for (i = 0; i < IMAP->num_msgs; ++i) {
-               IMAP->flags[i] |= IMAP_SELECTED;
-       }
-
-       imap_do_search(num_parms-3, &Params[3], 1);
-       IReply("OK UID SEARCH completed");
-}
-
-
diff --git a/citadel/modules/imap/imap_search.h b/citadel/modules/imap/imap_search.h
deleted file mode 100644 (file)
index ce2f27c..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Copyright (c) 2001-2017 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-void imap_search(int num_parms, ConstStr *Params);
-void imap_uidsearch(int num_parms, ConstStr *Params);
diff --git a/citadel/modules/imap/imap_store.c b/citadel/modules/imap/imap_store.c
deleted file mode 100644 (file)
index 429f8ed..0000000
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Implements the STORE command in IMAP.
- *
- * Copyright (c) 2001-2009 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-#include "ctdl_module.h"
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "room_ops.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "imap_fetch.h"
-#include "imap_store.h"
-#include "genstamp.h"
-
-
-/*
- * imap_do_store() calls imap_do_store_msg() to tweak the settings of
- * an individual message.
- *
- * We also implement the ".SILENT" protocol option here.  :(
- */
-void imap_do_store_msg(int seq, const char *oper, unsigned int bits_to_twiddle) {
-       citimap *Imap = IMAP;
-
-       if (!strncasecmp(oper, "FLAGS", 5)) {
-               Imap->flags[seq] &= IMAP_MASK_SYSTEM;
-               Imap->flags[seq] |= bits_to_twiddle;
-       }
-       else if (!strncasecmp(oper, "+FLAGS", 6)) {
-               Imap->flags[seq] |= bits_to_twiddle;
-       }
-       else if (!strncasecmp(oper, "-FLAGS", 6)) {
-               Imap->flags[seq] &= (~bits_to_twiddle);
-       }
-}
-
-
-/*
- * imap_store() calls imap_do_store() to perform the actual bit twiddling
- * on the flags.
- */
-void imap_do_store(citimap_command *Cmd) {
-       int i, j;
-       unsigned int bits_to_twiddle = 0;
-       const char *oper;
-       char flag[32];
-       char whichflags[256];
-       char num_flags;
-       int silent = 0;
-       long *ss_msglist;
-       int num_ss = 0;
-       int last_item_twiddled = (-1);
-       citimap *Imap = IMAP;
-
-       if (Cmd->num_parms < 2) return;
-       oper = Cmd->Params[0].Key;
-       if (cbmstrcasestr(oper, ".SILENT")) {
-               silent = 1;
-       }
-
-       /*
-        * ss_msglist is an array of message numbers to manipulate.  We
-        * are going to supply this array to CtdlSetSeen() later.
-        */
-       ss_msglist = malloc(Imap->num_msgs * sizeof(long));
-       if (ss_msglist == NULL) return;
-
-       /*
-        * Ok, go ahead and parse the flags.
-        */
-       for (i=1; i<Cmd->num_parms; ++i) {///TODO: why strcpy? 
-               strcpy(whichflags, Cmd->Params[i].Key);
-               if (whichflags[0]=='(') {
-                       safestrncpy(whichflags, &whichflags[1], 
-                               sizeof whichflags);
-               }
-               if (whichflags[strlen(whichflags)-1]==')') {
-                       whichflags[strlen(whichflags)-1]=0;
-               }
-               striplt(whichflags);
-
-               /* A client might twiddle more than one bit at a time.
-                * Note that we check for the flag names without the leading
-                * backslash because imap_parameterize() strips them out.
-                */
-               num_flags = num_tokens(whichflags, ' ');
-               for (j=0; j<num_flags; ++j) {
-                       extract_token(flag, whichflags, j, ' ', sizeof flag);
-
-                       if ((!strcasecmp(flag, "\\Deleted"))
-                          || (!strcasecmp(flag, "Deleted"))) {
-                               if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) {
-                                       bits_to_twiddle |= IMAP_DELETED;
-                               }
-                       }
-                       if ((!strcasecmp(flag, "\\Seen"))
-                          || (!strcasecmp(flag, "Seen"))) {
-                               bits_to_twiddle |= IMAP_SEEN;
-                       }
-                       if ((!strcasecmp(flag, "\\Answered")) 
-                          || (!strcasecmp(flag, "Answered"))) {
-                               bits_to_twiddle |= IMAP_ANSWERED;
-                       }
-               }
-       }
-
-       if (Imap->num_msgs > 0) {
-               for (i = 0; i < Imap->num_msgs; ++i) {
-                       if (Imap->flags[i] & IMAP_SELECTED) {
-                               last_item_twiddled = i;
-
-                               ss_msglist[num_ss++] = Imap->msgids[i];
-                               imap_do_store_msg(i, oper, bits_to_twiddle);
-
-                               if (!silent) {
-                                       IAPrintf("* %d FETCH (", i+1);
-                                       imap_fetch_flags(i);
-                                       IAPuts(")\r\n");
-                               }
-
-                       }
-               }
-       }
-
-       /*
-        * Now manipulate the database -- all in one shot.
-        */
-       if ( (last_item_twiddled >= 0) && (num_ss > 0) ) {
-
-               if (bits_to_twiddle & IMAP_SEEN) {
-                       CtdlSetSeen(ss_msglist, num_ss,
-                               ((Imap->flags[last_item_twiddled] & IMAP_SEEN) ? 1 : 0),
-                               ctdlsetseen_seen,
-                               NULL, NULL
-                       );
-               }
-
-               if (bits_to_twiddle & IMAP_ANSWERED) {
-                       CtdlSetSeen(ss_msglist, num_ss,
-                               ((Imap->flags[last_item_twiddled] & IMAP_ANSWERED) ? 1 : 0),
-                               ctdlsetseen_answered,
-                               NULL, NULL
-                       );
-               }
-
-       }
-
-       free(ss_msglist);
-       imap_do_expunge();              // Citadel always expunges immediately.
-       imap_rescan_msgids();
-}
-
-
-/*
- * This function is called by the main command loop.
- */
-void imap_store(int num_parms, ConstStr *Params) {
-       citimap_command Cmd;
-       int num_items;
-
-       if (num_parms < 3) {
-               IReply("BAD invalid parameters");
-               return;
-       }
-
-       if (imap_is_message_set(Params[2].Key)) {
-               imap_pick_range(Params[2].Key, 0);
-       }
-       else {
-               IReply("BAD invalid parameters");
-               return;
-       }
-
-       memset(&Cmd, 0, sizeof(citimap_command));
-       Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
-       MakeStringOf(Cmd.CmdBuf, 3);
-
-       num_items = imap_extract_data_items(&Cmd);
-       if (num_items < 1) {
-               IReply("BAD invalid data item list");
-               FreeStrBuf(&Cmd.CmdBuf);
-               free(Cmd.Params);
-               return;
-       }
-
-       imap_do_store(&Cmd);
-       IReply("OK STORE completed");
-       FreeStrBuf(&Cmd.CmdBuf);
-       free(Cmd.Params);
-}
-
-/*
- * This function is called by the main command loop.
- */
-void imap_uidstore(int num_parms, ConstStr *Params) {
-       citimap_command Cmd;
-       int num_items;
-
-       if (num_parms < 4) {
-               IReply("BAD invalid parameters");
-               return;
-       }
-
-       if (imap_is_message_set(Params[3].Key)) {
-               imap_pick_range(Params[3].Key, 1);
-       }
-       else {
-               IReply("BAD invalid parameters");
-               return;
-       }
-
-       memset(&Cmd, 0, sizeof(citimap_command));
-       Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
-       MakeStringOf(Cmd.CmdBuf, 4);
-
-       num_items = imap_extract_data_items(&Cmd);
-       if (num_items < 1) {
-               IReply("BAD invalid data item list");
-               FreeStrBuf(&Cmd.CmdBuf);
-               free(Cmd.Params);
-               return;
-       }
-
-       imap_do_store(&Cmd);
-       IReply("OK UID STORE completed");
-       FreeStrBuf(&Cmd.CmdBuf);
-       free(Cmd.Params);
-}
-
-
diff --git a/citadel/modules/imap/imap_store.h b/citadel/modules/imap/imap_store.h
deleted file mode 100644 (file)
index cf47377..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Copyright (c) 2001-2017 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-void imap_store(int num_parms, ConstStr *Params);
-void imap_uidstore(int num_parms, ConstStr *Params);
diff --git a/citadel/modules/imap/imap_tools.c b/citadel/modules/imap/imap_tools.c
deleted file mode 100644 (file)
index 7d7c080..0000000
+++ /dev/null
@@ -1,992 +0,0 @@
-/*
- * Utility functions for the IMAP module.
- *
- * Copyright (c) 2001-2017 by the citadel.org team and others, except for
- * most of the UTF7 and UTF8 handling code which was lifted from Evolution.
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-#define SHOW_ME_VAPPEND_PRINTF
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <ctype.h>
-#include <string.h>
-#include <stdarg.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "sysdep_decls.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "ctdl_module.h"
-
-/* String handling helpers */
-
-/* This code uses some pretty nasty string manipulation. To make everything
- * manageable, we use this semi-high-level string manipulation API. Strings are
- * always \0-terminated, despite the fact that we keep track of the size.
- */
-struct string {
-       char* buffer;
-       int maxsize;
-       int size;
-};
-
-static void string_init(struct string* s, char* buf, int bufsize)
-{
-       s->buffer = buf;
-       s->maxsize = bufsize-1;
-       s->size = strlen(buf);
-}
-
-static char* string_end(struct string* s)
-{
-       return s->buffer + s->size;
-}
-
-/* Append a UTF8 string of a particular length (in bytes). -1 to autocalculate. */
-
-static void string_append_sn(struct string* s, char* p, int len)
-{
-       if (len == -1)
-               len = strlen(p);
-       if ((s->size+len) > s->maxsize)
-               len = s->maxsize - s->size;
-       memcpy(s->buffer + s->size, p, len);
-       s->size += len;
-       s->buffer[s->size] = '\0';
-}
-
-/* As above, always autocalculate. */
-
-#define string_append_s(s, p) string_append_sn((s), (p), -1)
-
-/* Appends a UTF8 character --- which may make the size change by more than 1!
- * If the string overflows, the last character may become invalid. */
-
-static void string_append_c(struct string* s, int c)
-{
-       char UmlChar[5];
-       int len = 0;
-
-       /* Don't do anything if there's no room. */
-
-       if (s->size == s->maxsize)
-               return;
-
-       if (c <= 0x7F)
-       {
-               /* This is the most common case, so we optimise it. */
-
-               s->buffer[s->size++] = c;
-               s->buffer[s->size] = 0;
-               return;
-       }
-       else if (c <= 0x7FF)
-       {
-               UmlChar[0] = 0xC0 | (c >> 6);
-               UmlChar[1] = 0x80 | (c & 0x3F);
-               len = 2;
-       }
-       else if (c <= 0xFFFF)
-       {
-               UmlChar[0] = 0xE0 | (c >> 12);
-               UmlChar[1] = 0x80 | ((c >> 6) & 0x3f);
-               UmlChar[2] = 0x80 | (c & 0x3f);
-               len = 3;
-       }
-       else
-       {
-               UmlChar[0] = 0xf0 | c >> 18;
-               UmlChar[1] = 0x80 | ((c >> 12) & 0x3f);
-               UmlChar[2] = 0x80 | ((c >> 6) & 0x3f);
-               UmlChar[3] = 0x80 | (c & 0x3f);
-               len = 4;
-       }
-
-       string_append_sn(s, UmlChar, len);
-}      
-
-/* Reads a UTF8 character from a char*, advancing the pointer. */
-
-int utf8_getc(char** ptr)
-{
-       unsigned char* p = (unsigned char*) *ptr;
-       unsigned char c, r;
-       int v, m;
-
-       for (;;)
-       {
-               r = *p++;
-       loop:
-               if (r < 0x80)
-               {
-                       *ptr = (char*) p;
-                       v = r;
-                       break;
-               }
-               else if (r < 0xf8)
-               {
-                       /* valid start char? (max 4 octets) */
-                       v = r;
-                       m = 0x7f80;     /* used to mask out the length bits */
-                       do {
-                               c = *p++;
-                               if ((c & 0xc0) != 0x80)
-                               {
-                                       r = c;
-                                       goto loop;
-                               }
-                               v = (v<<6) | (c & 0x3f);
-                               r<<=1;
-                               m<<=5;
-                       } while (r & 0x40);
-                       
-                       *ptr = (char*)p;
-
-                       v &= ~m;
-                       break;
-               }
-       }
-
-       return v;
-}
-
-/* IMAP name safety */
-
-/* IMAP has certain special requirements in its character set, which means we
- * have to do a fair bit of work to convert Citadel's UTF8 strings to IMAP
- * strings. The next two routines (and their data tables) do that.
- */
-
-static char *utf7_alphabet =
-       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
-
-static unsigned char utf7_rank[256] = {
-       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
-       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
-       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3E,0x3F,0xFF,0xFF,0xFF,
-       0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
-       0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,
-       0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF,
-       0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
-       0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0xFF,0xFF,0xFF,0xFF,0xFF,
-       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
-       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
-       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
-       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
-       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
-       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
-       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
-       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
-};
-
-/* Base64 helpers. */
-
-static void utf7_closeb64(struct string* out, int v, int i)
-{
-       int x;
-
-       if (i > 0)
-       {
-               x = (v << (6-i)) & 0x3F;
-               string_append_c(out, utf7_alphabet[x]);
-       }
-       string_append_c(out, '-');
-}
-
-/* Convert from a Citadel name to an IMAP-safe name. Returns the end
- * of the destination.
- */
-static char* toimap(char* destp, char* destend, char* src)
-{
-       struct string dest;
-       int state = 0;
-       int v = 0;
-       int i = 0;
-
-       *destp = 0;
-       string_init(&dest, destp, destend-destp);
-       /* syslog(LOG_DEBUG, "toimap %s", src); */
-
-       for (;;)
-       {
-               int c = utf8_getc(&src);
-               if (c == '\0')
-                       break;
-
-               if (c >= 0x20 && c <= 0x7e)
-               {
-                       if (state == 1)
-                       {
-                               utf7_closeb64(&dest, v, i);
-                               state = 0;
-                               i = 0;
-                       }
-
-                       switch (c)
-                       {
-                               case '&':
-                                       string_append_sn(&dest, "&-", 2);
-                                       break;
-
-                               case '/':
-                                       /* Citadel extension: / becomes |, because /
-                                        * isn't valid as part of an IMAP name. */
-
-                                       c = '|';
-                                       goto defaultcase;
-
-                               case '\\':
-                                       /* Citadel extension: backslashes mark folder
-                                        * seperators in the IMAP subfolder emulation
-                                        * hack. We turn them into / characters,
-                                        * *except* if it's the last character in the
-                                        * string. */
-
-                                       if (*src != '\0')
-                                               c = '/';
-                                       /* fall through */
-
-                               default:
-                               defaultcase:
-                                       string_append_c(&dest, c);
-                       }
-               }
-               else
-               {
-                       if (state == 0)
-                       {
-                               string_append_c(&dest, '&');
-                               state = 1;
-                       }
-                       v = (v << 16) | c;
-                       i += 16;
-                       while (i >= 6)
-                       {
-                               int x = (v >> (i-6)) & 0x3f;
-                               string_append_c(&dest, utf7_alphabet[x]);
-                               i -= 6;
-                       }
-               }
-       }
-
-       if (state == 1)
-               utf7_closeb64(&dest, v, i);
-       /* syslog(LOG_DEBUG, "    -> %s", destp); */
-       return string_end(&dest);
-}
-
-/* Convert from an IMAP-safe name back into a Citadel name. Returns the end of the destination. */
-
-static int cfrommap(int c);
-static char* fromimap(char* destp, char* destend, const char* src)
-{
-       struct string dest;
-       unsigned const char *p = (unsigned const char*) src;
-       int v = 0;
-       int i = 0;
-       int state = 0;
-       int c;
-
-       *destp = 0;
-       string_init(&dest, destp, destend-destp);
-       /* syslog(LOG_DEBUG, "fromimap %s", src); */
-
-       do {
-               c = *p++;
-               switch (state)
-               {
-                       case 0:
-                               /* US-ASCII characters. */
-                               
-                               if (c == '&')
-                                       state = 1;
-                               else
-                                       string_append_c(&dest, cfrommap(c));
-                               break;
-
-                       case 1:
-                               if (c == '-')
-                               {
-                                       string_append_c(&dest, '&');
-                                       state = 0;
-                               }
-                               else if (utf7_rank[c] != 0xff)
-                               {
-                                       v = utf7_rank[c];
-                                       i = 6;
-                                       state = 2;
-                               }
-                               else
-                               {
-                                       /* invalid char */
-                                       string_append_sn(&dest, "&-", 2);
-                                       state = 0;
-                               }
-                               break;
-                               
-                       case 2:
-                               if (c == '-')
-                                       state = 0;
-                               else if (utf7_rank[c] != 0xFF)
-                               {
-                                       v = (v<<6) | utf7_rank[c];
-                                       i += 6;
-                                       if (i >= 16)
-                                       {
-                                               int x = (v >> (i-16)) & 0xFFFF;
-                                               string_append_c(&dest, cfrommap(x));
-                                               i -= 16;
-                                       }
-                               }
-                               else
-                               {
-                                       string_append_c(&dest, cfrommap(c));
-                                       state = 0;
-                               }
-                               break;
-                       }
-       } while (c != '\0');
-
-       /* syslog(LOG_DEBUG, "      -> %s", destp); */
-       return string_end(&dest);
-}
-
-/* Undoes the special character conversion. */
-static int cfrommap(int c)
-{
-       switch (c)
-       {
-               case '|':       return '/';
-               case '/':       return '\\';
-       }
-       return c;               
-}
-
-
-/* Break a command down into tokens, unquoting any escaped characters. */
-void MakeStringOf(StrBuf *Buf, int skip)
-{
-       int i;
-       citimap_command *Cmd = &IMAP->Cmd;
-
-       for (i=skip; i<Cmd->num_parms; ++i) {
-               StrBufAppendBufPlain(Buf, Cmd->Params[i].Key, Cmd->Params[i].len, 0);
-               if (i < (Cmd->num_parms-1)) StrBufAppendBufPlain(Buf, HKEY(" "), 0);
-       }
-}
-
-
-void TokenCutRight(citimap_command *Cmd, 
-                  ConstStr *CutMe,
-                  int n)
-{
-       const char *CutAt;
-
-       if (CutMe->len < n) {
-               CutAt = CutMe->Key;
-               CutMe->len = 0;
-       }
-       else {
-               CutAt = CutMe->Key + CutMe->len - n;
-               CutMe->len -= n;
-       }
-       StrBufPeek(Cmd->CmdBuf, CutAt, -1, '\0');
-}
-
-void TokenCutLeft(citimap_command *Cmd, 
-                 ConstStr *CutMe,
-                 int n)
-{
-       if (CutMe->len < n) {
-               CutMe->Key += CutMe->len;
-               CutMe->len = 0;
-       }
-       else {
-               CutMe->Key += n;
-               CutMe->len -= n;
-       }
-}
-
-
-
-int CmdAdjust(citimap_command *Cmd, 
-             int nArgs,
-             int Realloc)
-{
-       ConstStr *Params;
-       if (nArgs > Cmd->avail_parms) {
-               Params = (ConstStr*) malloc(sizeof(ConstStr) * nArgs);
-               if (Realloc) {
-                       memcpy(Params, 
-                              Cmd->Params, 
-                              sizeof(ConstStr) * Cmd->avail_parms);
-
-                       memset(Cmd->Params + 
-                              sizeof(ConstStr) * Cmd->avail_parms,
-                              0, 
-                              sizeof(ConstStr) * nArgs - 
-                              sizeof(ConstStr) * Cmd->avail_parms 
-                               );
-               }
-               else {
-                       Cmd->num_parms = 0;
-                       memset(Params, 0, 
-                              sizeof(ConstStr) * nArgs);
-               }
-               Cmd->avail_parms = nArgs;
-               if (Cmd->Params != NULL)
-                       free (Cmd->Params);
-               Cmd->Params = Params;
-       }
-       else {
-               if (!Realloc) {
-                       memset(Cmd->Params, 
-                              0,
-                              sizeof(ConstStr) * Cmd->avail_parms);
-                       Cmd->num_parms = 0;
-               }
-       }
-       return Cmd->avail_parms;
-}
-
-int imap_parameterize(citimap_command *Cmd)
-{
-       int nArgs;
-       const char *In, *End;
-
-       In = ChrPtr(Cmd->CmdBuf);
-       End = In + StrLength(Cmd->CmdBuf);
-
-       /* we start with 10 chars per arg, maybe we need to realloc later. */
-       nArgs = StrLength(Cmd->CmdBuf) / 10 + 10;
-       nArgs = CmdAdjust(Cmd, nArgs, 0);
-       while (In < End)
-       {
-               /* Skip whitespace. */
-               while (isspace(*In))
-                       In++;
-               if (*In == '\0')
-                       break;
-
-               /* Found the start of a token. */
-               
-               Cmd->Params[Cmd->num_parms].Key = In;
-
-               /* Read in the token. */
-
-               for (;;)
-               {
-                       if (isspace(*In))
-                               break;
-                       
-                       if (*In == '\"')
-                       {
-                               /* Found a quoted section. */
-
-                               Cmd->Params[Cmd->num_parms].Key++; 
-                               //In++;
-                               for (;;)
-                               {
-                                       In++;
-                                       if (*In == '\"') {
-                                               StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
-                                               break;
-                                       }
-                                       else if (*In == '\\')
-                                               In++;
-
-                                       if (*In == '\0') {
-                                               Cmd->Params[Cmd->num_parms].len = 
-                                                       In - Cmd->Params[Cmd->num_parms].Key;
-                                               Cmd->num_parms++;
-                                               return Cmd->num_parms;
-                                       }
-                               }
-                               break;
-                       }
-                       else if (*In == '\\')
-                       {
-                               In++;
-                       }
-
-                       if (*In == '\0') {
-                               Cmd->Params[Cmd->num_parms].len = 
-                                       In - Cmd->Params[Cmd->num_parms].Key;
-                               Cmd->num_parms++;
-                               return Cmd->num_parms;
-                       }
-                       In++;
-               }
-               StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
-               Cmd->Params[Cmd->num_parms].len = 
-                       In - Cmd->Params[Cmd->num_parms].Key;
-               if (Cmd->num_parms + 1 >= Cmd->avail_parms) {
-                       nArgs = CmdAdjust(Cmd, nArgs * 2, 1);
-               }
-               Cmd->num_parms ++;
-               In++;
-       }
-       return Cmd->num_parms;
-}
-
-
-/* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */
-long imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
-{
-       char* bufend = buf+bufsize;
-       struct floor *fl;
-       char* p = buf;
-       const char *pend;
-
-       /* For mailboxes, just do it straight.
-        * Do the Cyrus-compatible thing: all private folders are
-        * subfolders of INBOX. */
-
-       if (qrbuf->QRflags & QR_MAILBOX)
-       {
-               if (strcasecmp(qrbuf->QRname+11, MAILROOM) == 0)
-               {
-                       pend = toimap(p, bufend, "INBOX");
-                       return pend - buf;
-               }
-               else
-               {
-                       p = toimap(p, bufend, "INBOX");
-                       if (p < bufend)
-                               *p++ = '/';
-                       pend = toimap(p, bufend, qrbuf->QRname+11);
-                       return pend - buf;
-               }
-       }
-       else
-       {
-               /* Otherwise, prefix the floor name as a "public folders" moniker. */
-
-               fl = CtdlGetCachedFloor(qrbuf->QRfloor);
-               p = toimap(p, bufend, fl->f_name);
-               if (p < bufend)
-                       *p++ = '/';
-               pend = toimap(p, bufend, qrbuf->QRname);
-               return pend - buf;
-       }
-}
-
-/*
- * Convert an inputted folder name to our best guess as to what an equivalent
- * room name should be.
- *
- * If an error occurs, it returns -1.  Otherwise...
- *
- * The lower eight bits of the return value are the floor number on which the
- * room most likely resides.   The upper eight bits may contain flags,
- * including IR_MAILBOX if we're dealing with a personal room.
- *
- */
-int imap_roomname(char *rbuf, int bufsize, const char *foldername)
-{
-       int levels;
-       char floorname[ROOMNAMELEN*2];
-       char roomname[ROOMNAMELEN];
-       int i;
-       struct floor *fl;
-       int ret = (-1);
-
-       if (foldername == NULL)
-               return(-1);
-
-       /* Unmunge the entire string into the output buffer. */
-
-       fromimap(rbuf, rbuf+bufsize, foldername);
-
-       /* Is this an IMAP inbox? */
-
-       if (strncasecmp(rbuf, "INBOX", 5) == 0)
-       {
-               if (rbuf[5] == 0)
-               {
-                       /* It's the system inbox. */
-
-                       safestrncpy(rbuf, MAILROOM, bufsize);
-                       ret = (0 | IR_MAILBOX);
-                       goto exit;
-               }
-               else if (rbuf[5] == FDELIM)
-               {
-                       /* It's another personal mail folder. */
-
-                       safestrncpy(rbuf, rbuf+6, bufsize);
-                       ret = (0 | IR_MAILBOX);
-                       goto exit;
-               }
-
-               /* If we get here, the folder just happens to start with INBOX
-                * --- fall through. */
-       }
-
-       /* Is this a multi-level room name? */
-
-       levels = num_tokens(rbuf, FDELIM);
-       if (levels > 1)
-       {
-               long len;
-               /* Extract the main room name. */
-               
-               len = extract_token(floorname, rbuf, 0, FDELIM, sizeof floorname);
-               if (len < 0) len = 0;
-               safestrncpy(roomname, &rbuf[len  + 1], sizeof(roomname));
-
-               /* Try and find it on any floor. */
-               
-               for (i = 0; i < MAXFLOORS; ++i)
-               {
-                       fl = CtdlGetCachedFloor(i);
-                       if (fl->f_flags & F_INUSE)
-                       {
-                               if (strcasecmp(floorname, fl->f_name) == 0)
-                               {
-                                       /* Got it! */
-
-                                       safestrncpy(rbuf, roomname, bufsize);
-                                       ret = i;
-                                       goto exit;
-                               }
-                       }
-               }
-       }
-
-       /* Meh. It's either not a multi-level room name, or else we
-        * couldn't find it.
-        */
-       ret = (0 | IR_MAILBOX);
-
-exit:
-       syslog(LOG_DEBUG, "(That translates to \"%s\")", rbuf);
-       return(ret);
-}
-
-
-/*
- * Determine whether the supplied string is a valid message set.
- * If the string contains only numbers, colons, commas, and asterisks,
- * return 1 for a valid message set.  If any other character is found, 
- * return 0.
- */
-int imap_is_message_set(const char *buf)
-{
-       int i;
-
-       if (buf == NULL)
-               return (0);     /* stupidity checks */
-       if (IsEmptyStr(buf))
-               return (0);
-
-       if (!strcasecmp(buf, "ALL"))
-               return (1);     /* macro?  why?  */
-
-       for (i = 0; buf[i]; ++i) {      /* now start the scan */
-               if (
-                          (!isdigit(buf[i]))
-                          && (buf[i] != ':')
-                          && (buf[i] != ',')
-                          && (buf[i] != '*')
-                   )
-                       return (0);
-       }
-
-       return (1);             /* looks like we're good */
-}
-
-
-/*
- * imap_match.c, based on wildmat.c from INN
- * hacked for Citadel/IMAP by Daniel Malament
- */
-
-/* note: not all return statements use these; don't change them */
-#define WILDMAT_TRUE   1
-#define WILDMAT_FALSE  0
-#define WILDMAT_ABORT  -1
-#define WILDMAT_DELIM  '/'
-
-/*
- * Match text and p, return TRUE, FALSE, or ABORT.
- */
-static int do_imap_match(const char *supplied_text, const char *supplied_p)
-{
-       int matched, i;
-       char lcase_text[SIZ], lcase_p[SIZ];
-       char *text;
-       char *p;
-       
-       /* Copy both strings and lowercase them, in order to
-        * make this entire operation case-insensitive.
-        */
-       for (i=0; 
-            ((supplied_text[i] != '\0') && 
-             (i < sizeof(lcase_text)));
-            ++i)
-               lcase_text[i] = tolower(supplied_text[i]);
-       lcase_text[i] = '\0';
-
-       for (i=0; 
-            ((supplied_p[i] != '\0') && 
-             (i < sizeof(lcase_p))); 
-            ++i)
-               lcase_p[i] = tolower(supplied_p[i]);
-       lcase_p[i] = '\0';
-
-       /* Start matching */
-       for (p = lcase_p, text = lcase_text; 
-            !IsEmptyStr(p) && !IsEmptyStr(text); 
-            text++, p++) {
-               if ((*text == '\0') && (*p != '*') && (*p != '%')) {
-                       return WILDMAT_ABORT;
-               }
-               switch (*p) {
-               default:
-                       if (*text != *p) {
-                               return WILDMAT_FALSE;
-                       }
-                       continue;
-               case '*':
-star:
-                       while (++p, ((*p == '*') || (*p == '%'))) {
-                               /* Consecutive stars or %'s act
-                                * just like one star.
-                                */
-                               continue;
-                       }
-                       if (*p == '\0') {
-                               /* Trailing star matches everything. */
-                               return WILDMAT_TRUE;
-                       }
-                       while (*text) {
-                               if ((matched = do_imap_match(text++, p))
-                                  != WILDMAT_FALSE) {
-                                       return matched;
-                               }
-                       }
-                       return WILDMAT_ABORT;
-               case '%':
-                       while (++p, (!IsEmptyStr(p) && ((*p == '*') || (*p == '%'))))
-                       {
-                               /* Consecutive %'s act just like one, but even
-                                * a single star makes the sequence act like
-                                * one star, instead.
-                                */
-                               if (*p == '*') {
-                                       goto star;
-                               }
-                               continue;
-                       }
-                       if (*p == '\0') {
-                               /*
-                                * Trailing % matches everything
-                                * without a delimiter.
-                                */
-                               while (!IsEmptyStr(text)) {
-                                       if (*text == WILDMAT_DELIM) {
-                                               return WILDMAT_FALSE;
-                                       }
-                                       text++;
-                               }
-                               return WILDMAT_TRUE;
-                       }
-                       while (!IsEmptyStr(text) &&
-                              /* make sure text - 1 isn't before lcase_p */
-                              ((text == lcase_text) || (*(text - 1) != WILDMAT_DELIM)))
-                       {
-                               if ((matched = do_imap_match(text++, p))
-                                  != WILDMAT_FALSE) {
-                                       return matched;
-                               }
-                       }
-                       return WILDMAT_ABORT;
-               }
-       }
-
-       if ((*text == '\0') && (*p == '\0')) return WILDMAT_TRUE;
-       else return WILDMAT_FALSE;
-}
-
-
-/*
- * Support function for mailbox pattern name matching in LIST and LSUB
- * Returns nonzero if the supplied mailbox name matches the supplied pattern.
- */
-int imap_mailbox_matches_pattern(const char *pattern, char *mailboxname)
-{
-       /* handle just-star case quickly */
-       if ((pattern[0] == '*') && (pattern[1] == '\0')) {
-               return WILDMAT_TRUE;
-       }
-       return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
-}
-
-
-
-/*
- * Compare an IMAP date string (date only, no time) to the date found in
- * a Unix timestamp.
- */
-int imap_datecmp(const char *datestr, time_t msgtime) {
-       char daystr[256];
-       char monthstr[256];
-       char yearstr[256];
-       int i;
-       int day, month, year;
-       int msgday, msgmonth, msgyear;
-       struct tm msgtm;
-
-       char *imap_datecmp_ascmonths[12] = {
-               "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
-       };
-
-       if (datestr == NULL) return(0);
-
-       /* Expecting a date in the form dd-Mmm-yyyy */
-       extract_token(daystr, datestr, 0, '-', sizeof daystr);
-       extract_token(monthstr, datestr, 1, '-', sizeof monthstr);
-       extract_token(yearstr, datestr, 2, '-', sizeof yearstr);
-
-       day = atoi(daystr);
-       year = atoi(yearstr);
-       month = 0;
-       for (i=0; i<12; ++i) {
-               if (!strcasecmp(monthstr, imap_datecmp_ascmonths[i])) {
-                       month = i;
-               }
-       }
-
-       /* Extract day/month/year from message timestamp */
-       localtime_r(&msgtime, &msgtm);
-       msgday = msgtm.tm_mday;
-       msgmonth = msgtm.tm_mon;
-       msgyear = msgtm.tm_year + 1900;
-
-       /* Now start comparing */
-
-       if (year < msgyear) return(+1);
-       if (year > msgyear) return(-1);
-
-       if (month < msgmonth) return(+1);
-       if (month > msgmonth) return(-1);
-
-       if (day < msgday) return(+1);
-       if (day > msgday) return(-1);
-
-       return(0);
-}
-
-
-void IAPrintf(const char *Format, ...)
-{
-       va_list arg_ptr;
-       
-       va_start(arg_ptr, Format);
-       StrBufVAppendPrintf(IMAP->Reply, Format, arg_ptr);
-       va_end(arg_ptr);
-}
-
-
-void iaputs(const char *Str, long Len)
-{
-       StrBufAppendBufPlain(IMAP->Reply, Str, Len, 0);
-}
-
-
-void ireply(const char *Msg, long len)
-{
-       citimap *Imap = IMAP;
-
-       StrBufAppendBufPlain(Imap->Reply, 
-                            CKEY(Imap->Cmd.Params[0]), 0);
-       StrBufAppendBufPlain(Imap->Reply, 
-                            HKEY(" "), 0);
-       StrBufAppendBufPlain(Imap->Reply, 
-                            Msg, len, 0);
-       
-       StrBufAppendBufPlain(Imap->Reply, 
-                            HKEY("\r\n"), 0);
-       
-}
-
-
-void IReplyPrintf(const char *Format, ...)
-{
-       citimap *Imap = IMAP;
-       va_list arg_ptr;
-       
-
-       StrBufAppendBufPlain(Imap->Reply, 
-                            CKEY(Imap->Cmd.Params[0]), 0);
-
-       StrBufAppendBufPlain(Imap->Reply, 
-                            HKEY(" "), 0);
-
-       va_start(arg_ptr, Format);
-       StrBufVAppendPrintf(IMAP->Reply, Format, arg_ptr);
-       va_end(arg_ptr);
-       
-       StrBufAppendBufPlain(Imap->Reply, 
-                            HKEY("\r\n"), 0);
-       
-}
-
-
-/* Output a string to the IMAP client, either as a literal or quoted.
- * (We do a literal if it has any double-quotes or backslashes.) */
-void IPutStr(const char *Msg, long Len)
-{
-       int i;
-       int is_literal = 0;
-       citimap *Imap = IMAP;
-
-       
-       if ((Msg == NULL) || (Len == 0))
-       {       /* yeah, we handle this */
-               StrBufAppendBufPlain(Imap->Reply, HKEY("NIL"), 0);
-               return;
-       }
-
-       for (i = 0; i < Len; ++i) {
-               if ((Msg[i] == '\"') || (Msg[i] == '\\'))
-                       is_literal = 1;
-       }
-
-       if (is_literal) {
-               StrBufAppendPrintf(Imap->Reply, "{%ld}\r\n", Len);
-               StrBufAppendBufPlain(Imap->Reply, Msg, Len, 0);
-       } else {
-               StrBufAppendBufPlain(Imap->Reply, 
-                                    HKEY("\""), 0);
-               StrBufAppendBufPlain(Imap->Reply, 
-                                    Msg, Len, 0);
-               StrBufAppendBufPlain(Imap->Reply, 
-                                    HKEY("\""), 0);
-       }
-}
-
-void IUnbuffer (void)
-{
-       citimap *Imap = IMAP;
-
-       cputbuf(Imap->Reply);
-       FlushStrBuf(Imap->Reply);
-}
diff --git a/citadel/modules/imap/imap_tools.h b/citadel/modules/imap/imap_tools.h
deleted file mode 100644 (file)
index 2117a6e..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-
-/* 
- * since we work with shifted pointers to ConstStrs in some places, 
- * we can't just say we want to cut the n'th of Cmd, we have to pass it in
- * and rely on that CutMe references Cmd->CmdBuf; else peek won't work out
- * and len will differ.
- */
-void TokenCutRight(citimap_command *Cmd, 
-                  ConstStr *CutMe,
-                  int n);
-/*
- * since we just move Key around here, Cmd is just here so the syntax is identical.
- */
-void TokenCutLeft(citimap_command *Cmd, 
-                 ConstStr *CutMe,
-                 int n);
-void MakeStringOf(StrBuf *Buf, int skip);
-
-int CmdAdjust(citimap_command *Cmd, 
-             int nArgs,
-             int Realloc);
-
-
-void imap_strout(ConstStr *args);
-void imap_strbuffer(StrBuf *Reply, ConstStr *args);
-void plain_imap_strbuffer(StrBuf *Reply, char *buf);
-int imap_parameterize(citimap_command *Cmd);
-long imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf);
-int imap_roomname(char *buf, int bufsize, const char *foldername);
-int imap_is_message_set(const char *);
-int imap_mailbox_matches_pattern(const char *pattern, char *mailboxname);
-int imap_datecmp(const char *datestr, time_t msgtime);
-
-
-/* Imap Append Printf, send to the outbuffer */
-void IAPrintf(const char *Format, ...) __attribute__((__format__(__printf__,1,2)));
-
-void iaputs(const char *Str, long Len);
-#define IAPuts(Msg) iaputs(HKEY(Msg))
-/* give it a naughty name since its ugly. */
-#define _iaputs(Msg) iaputs(Msg, strlen(Msg))
-
-/* outputs a static message prepended by the sequence no */
-void ireply(const char *Msg, long len);
-#define IReply(msg) ireply(HKEY(msg))
-/* outputs a dynamic message prepended by the sequence no */
-void IReplyPrintf(const char *Format, ...);
-
-
-/* output a string like that {%ld}%s */
-void IPutStr(const char *Msg, long Len);
-#define IPutCStr(_ConstStr) IPutStr(CKEY(_ConstStr))
-#define IPutCParamStr(n) IPutStr(CKEY(Params[n]))
-#define IPutMsgField(Which) IPutStr(CM_KEY(msg, Which))
-void IUnbuffer (void);
diff --git a/citadel/modules/imap/serv_imap.c b/citadel/modules/imap/serv_imap.c
deleted file mode 100644 (file)
index 1609f8e..0000000
+++ /dev/null
@@ -1,1730 +0,0 @@
-/*
- * IMAP server for the Citadel system
- *
- * Copyright (C) 2000-2021 by Art Cancro and others.
- * This code is released under the terms of the GNU General Public License.
- *
- * WARNING: the IMAP protocol is badly designed.  No implementation of it
- * is perfect.  Indeed, with so much gratuitous complexity, *all* IMAP
- * implementations have bugs.
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "imap_list.h"
-#include "imap_fetch.h"
-#include "imap_search.h"
-#include "imap_store.h"
-#include "imap_acl.h"
-#include "imap_metadata.h"
-#include "imap_misc.h"
-
-#include "ctdl_module.h"
-HashList *ImapCmds = NULL;
-void registerImapCMD(const char *First, long FLen, 
-                    const char *Second, long SLen,
-                    imap_handler H,
-                    int Flags)
-{
-       imap_handler_hook *h;
-
-       h = (imap_handler_hook*) malloc(sizeof(imap_handler_hook));
-       memset(h, 0, sizeof(imap_handler_hook));
-
-       h->Flags = Flags;
-       h->h = H;
-       if (SLen == 0) {
-               Put(ImapCmds, First, FLen, h, NULL);
-       }
-       else {
-               char CMD[SIZ];
-               memcpy(CMD, First, FLen);
-               memcpy(CMD+FLen, Second, SLen);
-               CMD[FLen+SLen] = '\0';
-               Put(ImapCmds, CMD, FLen + SLen, h, NULL);
-       }
-}
-
-
-const imap_handler_hook *imap_lookup(int num_parms, ConstStr *Params)
-{
-       struct CitContext *CCC = CC;
-       void *v;
-       citimap *Imap = CCCIMAP;
-
-       if (num_parms < 1)
-               return NULL;
-
-       /* we abuse the Reply-buffer for uppercasing... */
-       StrBufPlain(Imap->Reply, CKEY(Params[1]));
-       StrBufUpCase(Imap->Reply);
-
-       syslog(LOG_DEBUG, "---- Looking up [%s] -----", ChrPtr(Imap->Reply));
-       if (GetHash(ImapCmds, SKEY(Imap->Reply), &v))
-       {
-               syslog(LOG_DEBUG, "Found."); 
-               FlushStrBuf(Imap->Reply);
-               return (imap_handler_hook *) v;
-       }
-
-       if (num_parms == 1)
-       {
-               syslog(LOG_DEBUG, "NOT Found."); 
-               FlushStrBuf(Imap->Reply);
-               return NULL;
-       }
-       
-       syslog(LOG_DEBUG, "---- Looking up [%s] -----", ChrPtr(Imap->Reply));
-       StrBufAppendBufPlain(Imap->Reply, CKEY(Params[2]), 0);
-       StrBufUpCase(Imap->Reply);
-       if (GetHash(ImapCmds, SKEY(Imap->Reply), &v))
-       {
-               syslog(LOG_DEBUG, "Found."); 
-               FlushStrBuf(Imap->Reply);
-               return (imap_handler_hook *) v;
-       }
-       syslog(LOG_DEBUG, "NOT Found."); 
-       FlushStrBuf(Imap->Reply);
-               return NULL;
-}
-
-/* imap_rename() uses this struct containing list of rooms to rename */
-struct irl {
-       struct irl *next;
-       char irl_oldroom[ROOMNAMELEN];
-       char irl_newroom[ROOMNAMELEN];
-       int irl_newfloor;
-};
-
-/* Data which is passed between imap_rename() and imap_rename_backend() */
-typedef struct __irlparms {
-       const char *oldname;
-       long oldnamelen;
-       const char *newname;
-       long newnamelen;
-       struct irl **irl;
-}irlparms;
-
-
-/*
- * If there is a message ID map in memory, free it
- */
-void imap_free_msgids(void)
-{
-       citimap *Imap = IMAP;
-       if (Imap->msgids != NULL) {
-               free(Imap->msgids);
-               Imap->msgids = NULL;
-               Imap->num_msgs = 0;
-               Imap->num_alloc = 0;
-       }
-       if (Imap->flags != NULL) {
-               free(Imap->flags);
-               Imap->flags = NULL;
-       }
-       Imap->last_mtime = (-1);
-}
-
-
-/*
- * If there is a transmitted message in memory, free it
- */
-void imap_free_transmitted_message(void)
-{
-       FreeStrBuf(&IMAP->TransmittedMessage);
-}
-
-
-/*
- * Set the \Seen, \Recent. and \Answered flags, based on the sequence
- * sets stored in the visit record for this user/room.  Note that we have
- * to parse each sequence set manually here, because calling the utility
- * function is_msg_in_sequence_set() over and over again is too expensive.
- *
- * first_msg should be set to 0 to rescan the flags for every message in the
- * room, or some other value if we're only interested in an incremental
- * update.
- */
-void imap_set_seen_flags(int first_msg)
-{
-       citimap *Imap = IMAP;
-       visit vbuf;
-       int i;
-       int num_sets;
-       int s;
-       char setstr[64], lostr[64], histr[64];
-       long lo, hi;
-
-       if (Imap->num_msgs < 1) return;
-       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
-
-       for (i = first_msg; i < Imap->num_msgs; ++i) {
-               Imap->flags[i] = Imap->flags[i] & ~IMAP_SEEN;
-               Imap->flags[i] |= IMAP_RECENT;
-               Imap->flags[i] = Imap->flags[i] & ~IMAP_ANSWERED;
-       }
-
-       /*
-        * Do the "\Seen" flag.
-        * (Any message not "\Seen" is considered "\Recent".)
-        */
-       num_sets = num_tokens(vbuf.v_seen, ',');
-       for (s=0; s<num_sets; ++s) {
-               extract_token(setstr, vbuf.v_seen, s, ',', sizeof setstr);
-
-               extract_token(lostr, setstr, 0, ':', sizeof lostr);
-               if (num_tokens(setstr, ':') >= 2) {
-                       extract_token(histr, setstr, 1, ':', sizeof histr);
-                       if (!strcmp(histr, "*")) {
-                               snprintf(histr, sizeof histr, "%ld", LONG_MAX);
-                       }
-               } 
-               else {
-                       strcpy(histr, lostr);
-               }
-               lo = atol(lostr);
-               hi = atol(histr);
-
-               for (i = first_msg; i < Imap->num_msgs; ++i) {
-                       if ((Imap->msgids[i] >= lo) && (Imap->msgids[i] <= hi)){
-                               Imap->flags[i] |= IMAP_SEEN;
-                               Imap->flags[i] = Imap->flags[i] & ~IMAP_RECENT;
-                       }
-               }
-       }
-
-       /* Do the ANSWERED flag */
-       num_sets = num_tokens(vbuf.v_answered, ',');
-       for (s=0; s<num_sets; ++s) {
-               extract_token(setstr, vbuf.v_answered, s, ',', sizeof setstr);
-
-               extract_token(lostr, setstr, 0, ':', sizeof lostr);
-               if (num_tokens(setstr, ':') >= 2) {
-                       extract_token(histr, setstr, 1, ':', sizeof histr);
-                       if (!strcmp(histr, "*")) {
-                               snprintf(histr, sizeof histr, "%ld", LONG_MAX);
-                       }
-               } 
-               else {
-                       strcpy(histr, lostr);
-               }
-               lo = atol(lostr);
-               hi = atol(histr);
-
-               for (i = first_msg; i < Imap->num_msgs; ++i) {
-                       if ((Imap->msgids[i] >= lo) && (Imap->msgids[i] <= hi)){
-                               Imap->flags[i] |= IMAP_ANSWERED;
-                       }
-               }
-       }
-
-}
-
-
-
-/*
- * Back end for imap_load_msgids()
- *
- * Optimization: instead of calling realloc() to add each message, we
- * allocate space in the list for REALLOC_INCREMENT messages at a time.  This
- * allows the mapping to proceed much faster.
- */
-void imap_add_single_msgid(long msgnum, void *userdata)
-{
-       citimap *Imap = IMAP;
-
-       ++Imap->num_msgs;
-       if (Imap->num_msgs > Imap->num_alloc) {
-               Imap->num_alloc += REALLOC_INCREMENT;
-               Imap->msgids = realloc(Imap->msgids, (Imap->num_alloc * sizeof(long)) );
-               Imap->flags = realloc(Imap->flags, (Imap->num_alloc * sizeof(unsigned int)) );
-       }
-       Imap->msgids[Imap->num_msgs - 1] = msgnum;
-       Imap->flags[Imap->num_msgs - 1] = 0;
-}
-
-
-
-/*
- * Set up a message ID map for the current room (folder)
- */
-void imap_load_msgids(void)
-{
-       struct CitContext *CCC = CC;
-       struct cdbdata *cdbfr;
-       citimap *Imap = CCCIMAP;
-
-       if (Imap->selected == 0) {
-               syslog(LOG_ERR, "imap_load_msgids() can't run; no room selected");
-               return;
-       }
-
-       imap_free_msgids();     /* If there was already a map, free it */
-
-       /* Load the message list */
-       cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
-       if (cdbfr != NULL) {
-               Imap->msgids = (long*)cdbfr->ptr;
-               Imap->num_msgs = cdbfr->len / sizeof(long);
-               Imap->num_alloc = cdbfr->len / sizeof(long);
-               cdbfr->ptr = NULL;
-               cdbfr->len = 0;
-               cdb_free(cdbfr);
-       }
-
-       if (Imap->num_msgs) {
-               Imap->flags = malloc(Imap->num_alloc * sizeof(unsigned int));
-               memset(Imap->flags, 0, (Imap->num_alloc * sizeof(unsigned int)) );
-       }
-
-       imap_set_seen_flags(0);
-}
-
-
-/*
- * Re-scan the selected room (folder) and see if it's been changed at all
- */
-void imap_rescan_msgids(void)
-{
-       struct CitContext *CCC = CC;
-       citimap *Imap = CCCIMAP;
-       int original_num_msgs = 0;
-       long original_highest = 0L;
-       int i, j, jstart;
-       int message_still_exists;
-       struct cdbdata *cdbfr;
-       long *msglist = NULL;
-       int num_msgs = 0;
-       int num_recent = 0;
-
-       if (Imap->selected == 0) {
-               syslog(LOG_ERR, "imap_load_msgids() can't run; no room selected");
-               return;
-       }
-
-       /*
-        * Check to see if the room's contents have changed.
-        * If not, we can avoid this rescan.
-        */
-       CtdlGetRoom(&CC->room, CC->room.QRname);
-       if (Imap->last_mtime == CC->room.QRmtime) {     /* No changes! */
-               return;
-       }
-
-       /* Load the *current* message list from disk, so we can compare it
-        * to what we have in memory.
-        */
-       cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
-       if (cdbfr != NULL) {
-               msglist = (long*)cdbfr->ptr;
-               cdbfr->ptr = NULL;
-               num_msgs = cdbfr->len / sizeof(long);
-               cdbfr->len = 0;
-               cdb_free(cdbfr);
-       } else {
-               num_msgs = 0;
-       }
-
-       /*
-        * Check to see if any of the messages we know about have been expunged
-        */
-       if (Imap->num_msgs > 0) {
-               jstart = 0;
-               for (i = 0; i < Imap->num_msgs; ++i) {
-
-                       message_still_exists = 0;
-                       if (num_msgs > 0) {
-                               for (j = jstart; j < num_msgs; ++j) {
-                                       if (msglist[j] == Imap->msgids[i]) {
-                                               message_still_exists = 1;
-                                               jstart = j;
-                                               break;
-                                       }
-                               }
-                       }
-
-                       if (message_still_exists == 0) {
-                               IAPrintf("* %d EXPUNGE\r\n", i + 1);
-
-                               /* Here's some nice stupid nonsense.  When a
-                                * message is expunged, we have to slide all
-                                * the existing messages up in the message
-                                * array.
-                                */
-                               --Imap->num_msgs;
-                               memmove(&Imap->msgids[i],
-                                       &Imap->msgids[i + 1],
-                                       (sizeof(long) *
-                                        (Imap->num_msgs - i)));
-                               memmove(&Imap->flags[i],
-                                       &Imap->flags[i + 1],
-                                       (sizeof(unsigned int) *
-                                        (Imap->num_msgs - i)));
-                               --i;
-                       }
-
-               }
-       }
-
-       /*
-        * Remember how many messages were here before we re-scanned.
-        */
-       original_num_msgs = Imap->num_msgs;
-       if (Imap->num_msgs > 0) {
-               original_highest = Imap->msgids[Imap->num_msgs - 1];
-       } else {
-               original_highest = 0L;
-       }
-
-       /*
-        * Now peruse the room for *new* messages only.
-        * This logic is probably the cause of Bug # 368
-        * [ http://bugzilla.citadel.org/show_bug.cgi?id=368 ]
-        */
-       if (num_msgs > 0) {
-               for (j = 0; j < num_msgs; ++j) {
-                       if (msglist[j] > original_highest) {
-                               imap_add_single_msgid(msglist[j], NULL);
-                       }
-               }
-       }
-       imap_set_seen_flags(original_num_msgs);
-
-       /*
-        * If new messages have arrived, tell the client about them.
-        */
-       if (Imap->num_msgs > original_num_msgs) {
-
-               for (j = 0; j < num_msgs; ++j) {
-                       if (Imap->flags[j] & IMAP_RECENT) {
-                               ++num_recent;
-                       }
-               }
-
-               IAPrintf("* %d EXISTS\r\n", Imap->num_msgs);
-               IAPrintf("* %d RECENT\r\n", num_recent);
-       }
-
-       if (msglist != NULL) {
-               free(msglist);
-       }
-       Imap->last_mtime = CC->room.QRmtime;
-}
-
-
-/*
- * This cleanup function blows away the temporary memory and files used by
- * the IMAP server.
- */
-void imap_cleanup_function(void)
-{
-       struct CitContext *CCC = CC;
-       citimap *Imap = CCCIMAP;
-
-       /* Don't do this stuff if this is not a Imap session! */
-       if (CC->h_command_function != imap_command_loop)
-               return;
-
-       /* If there is a mailbox selected, auto-expunge it. */
-       if (Imap->selected) {
-               imap_do_expunge();
-       }
-
-       syslog(LOG_DEBUG, "Performing IMAP cleanup hook");
-       imap_free_msgids();
-       imap_free_transmitted_message();
-
-       if (Imap->cached_rfc822 != NULL) {
-               FreeStrBuf(&Imap->cached_rfc822);
-               Imap->cached_rfc822_msgnum = (-1);
-               Imap->cached_rfc822_withbody = 0;
-       }
-
-       if (Imap->cached_body != NULL) {
-               free(Imap->cached_body);
-               Imap->cached_body = NULL;
-               Imap->cached_body_len = 0;
-               Imap->cached_bodymsgnum = (-1);
-       }
-       FreeStrBuf(&Imap->Cmd.CmdBuf);
-       FreeStrBuf(&Imap->Reply);
-       if (Imap->Cmd.Params != NULL) free(Imap->Cmd.Params);
-       free(Imap);
-       syslog(LOG_DEBUG, "Finished IMAP cleanup hook");
-}
-
-
-/*
- * Does the actual work of the CAPABILITY command (because we need to
- * output this stuff in other places as well)
- */
-void imap_output_capability_string(void) {
-       IAPuts("CAPABILITY IMAP4REV1 NAMESPACE ID AUTH=PLAIN AUTH=LOGIN UIDPLUS");
-
-#ifdef HAVE_OPENSSL
-       if (!CC->redirect_ssl) IAPuts(" STARTTLS");
-#endif
-
-#ifndef DISABLE_IMAP_ACL
-       IAPuts(" ACL");
-#endif
-
-       /* We are building a partial implementation of METADATA for the sole purpose
-        * of interoperating with the ical/vcard version of the Bynari Insight Connector.
-        * It is not a full RFC5464 implementation, but it should refuse non-Bynari
-        * metadata in a compatible and graceful way.
-        */
-       IAPuts(" METADATA");
-
-       /*
-        * LIST-EXTENDED was originally going to be required by the METADATA extension.
-        * It was mercifully removed prior to the finalization of RFC5464.  We started
-        * implementing this but stopped when we learned that it would not be needed.
-        * If you uncomment this declaration you are responsible for writing a lot of new
-        * code.
-        *
-        * IAPuts(" LIST-EXTENDED")
-        */
-}
-
-
-/*
- * implements the CAPABILITY command
- */
-void imap_capability(int num_parms, ConstStr *Params)
-{
-       IAPuts("* ");
-       imap_output_capability_string();
-       IAPuts("\r\n");
-       IReply("OK CAPABILITY completed");
-}
-
-
-/*
- * Implements the ID command (specified by RFC2971)
- *
- * We ignore the client-supplied information, and output a NIL response.
- * Although this is technically a valid implementation of the extension, it
- * is quite useless.  It exists only so that we may see which clients are
- * making use of this extension.
- * 
- */
-void imap_id(int num_parms, ConstStr *Params)
-{
-       IAPuts("* ID NIL\r\n");
-       IReply("OK ID completed");
-}
-
-
-/*
- * Here's where our IMAP session begins its happy day.
- */
-void imap_greeting(void)
-{
-       citimap *Imap;
-       CitContext *CCC = CC;
-
-       strcpy(CCC->cs_clientname, "IMAP session");
-       CCC->session_specific_data = malloc(sizeof(citimap));
-       Imap = (citimap *)CCC->session_specific_data;
-       memset(Imap, 0, sizeof(citimap));
-       Imap->authstate = imap_as_normal;
-       Imap->cached_rfc822_msgnum = (-1);
-       Imap->cached_rfc822_withbody = 0;
-       Imap->Reply = NewStrBufPlain(NULL, SIZ * 10); /* 40k */
-
-       if (CCC->nologin)
-       {
-               IAPuts("* BYE; Server busy, try later\r\n");
-               CCC->kill_me = KILLME_NOLOGIN;
-               IUnbuffer();
-               return;
-       }
-
-       IAPuts("* OK [");
-       imap_output_capability_string();
-       IAPrintf("] %s IMAP4rev1 %s ready\r\n", CtdlGetConfigStr("c_fqdn"), CITADEL);
-       IUnbuffer();
-}
-
-
-/*
- * IMAPS is just like IMAP, except it goes crypto right away.
- */
-void imaps_greeting(void) {
-       CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
-#ifdef HAVE_OPENSSL
-       if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;          /* kill session if no crypto */
-#endif
-       imap_greeting();
-}
-
-
-/*
- * implements the LOGIN command (ordinary username/password login)
- */
-void imap_login(int num_parms, ConstStr *Params)
-{
-
-       switch (num_parms) {
-       case 3:
-               if (Params[2].Key[0] == '{') {
-                       IAPuts("+ go ahead\r\n");
-                       IMAP->authstate = imap_as_expecting_multilineusername;
-                       strcpy(IMAP->authseq, Params[0].Key);
-                       return;
-               }
-               else {
-                       IReply("BAD incorrect number of parameters");
-                       return;
-               }
-       case 4:
-               if (CtdlLoginExistingUser(Params[2].Key) == login_ok) {
-                       if (CtdlTryPassword(Params[3].Key, Params[3].len) == pass_ok) {
-                               /* hm, thats not doable by IReply :-( */
-                               IAPrintf("%s OK [", Params[0].Key);
-                               imap_output_capability_string();
-                               IAPrintf("] Hello, %s\r\n", CC->user.fullname);
-                               return;
-                       }
-                       else
-                       {
-                               IReplyPrintf("NO AUTHENTICATE %s failed", Params[3].Key);
-                               return;
-                       }
-               }
-
-               IReply("BAD Login incorrect");
-               return;
-       default:
-               IReply("BAD incorrect number of parameters");
-               return;
-       }
-
-}
-
-
-/*
- * Implements the AUTHENTICATE command
- */
-void imap_authenticate(int num_parms, ConstStr *Params)
-{
-       char UsrBuf[SIZ];
-
-       if (num_parms != 3) {
-               IReply("BAD incorrect number of parameters");
-               return;
-       }
-
-       if (CC->logged_in) {
-               IReply("BAD Already logged in.");
-               return;
-       }
-
-       if (!strcasecmp(Params[2].Key, "LOGIN")) {
-               size_t len = CtdlEncodeBase64(UsrBuf, "Username:", 9, 0);
-               if (UsrBuf[len - 1] == '\n') {
-                       UsrBuf[len - 1] = '\0';
-               }
-
-               IAPrintf("+ %s\r\n", UsrBuf);
-               IMAP->authstate = imap_as_expecting_username;
-               strcpy(IMAP->authseq, Params[0].Key);
-               return;
-       }
-
-       if (!strcasecmp(Params[2].Key, "PLAIN")) {
-               // size_t len = CtdlEncodeBase64(UsrBuf, "Username:", 9, 0);
-               // if (UsrBuf[len - 1] == '\n') {
-               //   UsrBuf[len - 1] = '\0';
-               // }
-               // IAPuts("+ %s\r\n", UsrBuf);
-               IAPuts("+ \r\n");
-               IMAP->authstate = imap_as_expecting_plainauth;
-               strcpy(IMAP->authseq, Params[0].Key);
-               return;
-       }
-
-       else {
-               IReplyPrintf("NO AUTHENTICATE %s failed",
-                            Params[1].Key);
-       }
-}
-
-
-void imap_auth_plain(void)
-{
-       citimap *Imap = IMAP;
-       const char *decoded_authstring;
-       char ident[256] = "";
-       char user[256] = "";
-       char pass[256] = "";
-       int result;
-       long decoded_len;
-       long len = 0;
-       long plen = 0;
-
-       memset(pass, 0, sizeof(pass));
-       decoded_len = StrBufDecodeBase64(Imap->Cmd.CmdBuf);
-
-       if (decoded_len > 0)
-       {
-               decoded_authstring = ChrPtr(Imap->Cmd.CmdBuf);
-
-               len = safestrncpy(ident, decoded_authstring, sizeof ident);
-
-               decoded_len -= len - 1;
-               decoded_authstring += len + 1;
-
-               if (decoded_len > 0)
-               {
-                       len = safestrncpy(user, decoded_authstring, sizeof user);
-
-                       decoded_authstring += len + 1;
-                       decoded_len -= len - 1;
-               }
-
-               if (decoded_len > 0)
-               {
-                       plen = safestrncpy(pass, decoded_authstring, sizeof pass);
-
-                       if (plen < 0)
-                               plen = sizeof(pass) - 1;
-               }
-       }
-       Imap->authstate = imap_as_normal;
-
-       if (!IsEmptyStr(ident)) {
-               result = CtdlLoginExistingUser(ident);
-       }
-       else {
-               result = CtdlLoginExistingUser(user);
-       }
-
-       if (result == login_ok) {
-               if (CtdlTryPassword(pass, plen) == pass_ok) {
-                       IAPrintf("%s OK authentication succeeded\r\n", Imap->authseq);
-                       return;
-               }
-       }
-       IAPrintf("%s NO authentication failed\r\n", Imap->authseq);
-}
-
-
-void imap_auth_login_user(long state)
-{
-       char PWBuf[SIZ];
-       citimap *Imap = IMAP;
-
-       switch (state){
-       case imap_as_expecting_username:
-               StrBufDecodeBase64(Imap->Cmd.CmdBuf);
-               CtdlLoginExistingUser(ChrPtr(Imap->Cmd.CmdBuf));
-               size_t len = CtdlEncodeBase64(PWBuf, "Password:", 9, 0);
-               if (PWBuf[len - 1] == '\n') {
-                       PWBuf[len - 1] = '\0';
-               }
-
-               IAPrintf("+ %s\r\n", PWBuf);
-               
-               Imap->authstate = imap_as_expecting_password;
-               return;
-       case imap_as_expecting_multilineusername:
-               extract_token(PWBuf, ChrPtr(Imap->Cmd.CmdBuf), 1, ' ', sizeof(PWBuf));
-               CtdlLoginExistingUser(ChrPtr(Imap->Cmd.CmdBuf));
-               IAPuts("+ go ahead\r\n");
-               Imap->authstate = imap_as_expecting_multilinepassword;
-               return;
-       }
-}
-
-
-void imap_auth_login_pass(long state)
-{
-       citimap *Imap = IMAP;
-       const char *pass = NULL;
-       long len = 0;
-
-       switch (state) {
-       default:
-       case imap_as_expecting_password:
-               StrBufDecodeBase64(Imap->Cmd.CmdBuf);
-               pass = ChrPtr(Imap->Cmd.CmdBuf);
-               len = StrLength(Imap->Cmd.CmdBuf);
-               break;
-       case imap_as_expecting_multilinepassword:
-               pass = ChrPtr(Imap->Cmd.CmdBuf);
-               len = StrLength(Imap->Cmd.CmdBuf);
-               break;
-       }
-       if (len > USERNAME_SIZE)
-               StrBufCutAt(Imap->Cmd.CmdBuf, USERNAME_SIZE, NULL);
-
-       if (CtdlTryPassword(pass, len) == pass_ok) {
-               IAPrintf("%s OK authentication succeeded\r\n", Imap->authseq);
-       } else {
-               IAPrintf("%s NO authentication failed\r\n", Imap->authseq);
-       }
-       Imap->authstate = imap_as_normal;
-       return;
-}
-
-
-/*
- * implements the STARTTLS command (Citadel API version)
- */
-void imap_starttls(int num_parms, ConstStr *Params)
-{
-       char ok_response[SIZ];
-       char nosup_response[SIZ];
-       char error_response[SIZ];
-
-       snprintf(ok_response, SIZ,      "%s OK begin TLS negotiation now\r\n",  Params[0].Key);
-       snprintf(nosup_response, SIZ,   "%s NO TLS not supported here\r\n",     Params[0].Key);
-       snprintf(error_response, SIZ,   "%s BAD Internal error\r\n",            Params[0].Key);
-       CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
-}
-
-
-/*
- * implements the SELECT command
- */
-void imap_select(int num_parms, ConstStr *Params)
-{
-       citimap *Imap = IMAP;
-       char towhere[ROOMNAMELEN];
-       char augmented_roomname[ROOMNAMELEN];
-       int c = 0;
-       int ok = 0;
-       int ra = 0;
-       struct ctdlroom QRscratch;
-       int msgs, new;
-       int i;
-
-       /* Convert the supplied folder name to a roomname */
-       i = imap_roomname(towhere, sizeof towhere, Params[2].Key);
-       if (i < 0) {
-               IReply("NO Invalid mailbox name.");
-               Imap->selected = 0;
-               return;
-       }
-
-       /* First try a regular match */
-       c = CtdlGetRoom(&QRscratch, towhere);
-
-       /* Then try a mailbox name match */
-       if (c != 0) {
-               CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CC->user, towhere);
-               c = CtdlGetRoom(&QRscratch, augmented_roomname);
-               if (c == 0) {
-                       safestrncpy(towhere, augmented_roomname, sizeof(towhere));
-               }
-       }
-
-       /* If the room exists, check security/access */
-       if (c == 0) {
-               /* See if there is an existing user/room relationship */
-               CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
-
-               /* normal clients have to pass through security */
-               if (ra & UA_KNOWN) {
-                       ok = 1;
-               }
-       }
-
-       /* Fail here if no such room */
-       if (!ok) {
-               IReply("NO ... no such room, or access denied");
-               return;
-       }
-
-       /* If we already had some other folder selected, auto-expunge it */
-       imap_do_expunge();
-
-       /*
-        * CtdlUserGoto() formally takes us to the desired room, happily returning
-        * the number of messages and number of new messages.
-        */
-       memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom));
-       CtdlUserGoto(NULL, 0, 0, &msgs, &new, NULL, NULL);
-       Imap->selected = 1;
-
-       if (!strcasecmp(Params[1].Key, "EXAMINE")) {
-               Imap->readonly = 1;
-       } else {
-               Imap->readonly = 0;
-       }
-
-       imap_load_msgids();
-       Imap->last_mtime = CC->room.QRmtime;
-
-       IAPrintf("* %d EXISTS\r\n", msgs);
-       IAPrintf("* %d RECENT\r\n", new);
-
-       IAPrintf("* OK [UIDVALIDITY %ld] UID validity status\r\n", GLOBAL_UIDVALIDITY_VALUE);
-       IAPrintf("* OK [UIDNEXT %ld] Predicted next UID\r\n", CtdlGetConfigLong("MMhighest") + 1);
-
-       /* Technically, \Deleted is a valid flag, but not a permanent flag,
-        * because we don't maintain its state across sessions.  Citadel
-        * automatically expunges mailboxes when they are de-selected.
-        * 
-        * Unfortunately, omitting \Deleted as a PERMANENTFLAGS flag causes
-        * some clients (particularly Thunderbird) to misbehave -- they simply
-        * elect not to transmit the flag at all.  So we have to advertise
-        * \Deleted as a PERMANENTFLAGS flag, even though it technically isn't.
-        */
-       IAPuts("* FLAGS (\\Deleted \\Seen \\Answered)\r\n");
-       IAPuts("* OK [PERMANENTFLAGS (\\Deleted \\Seen \\Answered)] permanent flags\r\n");
-
-       IReplyPrintf("OK [%s] %s completed",
-               (Imap->readonly ? "READ-ONLY" : "READ-WRITE"), Params[1].Key
-       );
-}
-
-
-/*
- * Does the real work for expunge.
- */
-int imap_do_expunge(void)
-{
-       struct CitContext *CCC = CC;
-       citimap *Imap = CCCIMAP;
-       int i;
-       int num_expunged = 0;
-       long *delmsgs = NULL;
-       int num_delmsgs = 0;
-
-       syslog(LOG_DEBUG, "imap_do_expunge() called");
-       if (Imap->selected == 0) {
-               return (0);
-       }
-
-       if (Imap->num_msgs > 0) {
-               delmsgs = malloc(Imap->num_msgs * sizeof(long));
-               for (i = 0; i < Imap->num_msgs; ++i) {
-                       if (Imap->flags[i] & IMAP_DELETED) {
-                               delmsgs[num_delmsgs++] = Imap->msgids[i];
-                       }
-               }
-               if (num_delmsgs > 0) {
-                       CtdlDeleteMessages(CC->room.QRname, delmsgs, num_delmsgs, "");
-               }
-               num_expunged += num_delmsgs;
-               free(delmsgs);
-       }
-
-       if (num_expunged > 0) {
-               imap_rescan_msgids();
-       }
-
-       syslog(LOG_DEBUG, "Expunged %d messages from <%s>", num_expunged, CC->room.QRname);
-       return (num_expunged);
-}
-
-
-/*
- * implements the EXPUNGE command syntax
- */
-void imap_expunge(int num_parms, ConstStr *Params)
-{
-       int num_expunged = 0;
-
-       num_expunged = imap_do_expunge();
-       IReplyPrintf("OK expunged %d messages.", num_expunged);
-}
-
-
-/*
- * implements the CLOSE command
- */
-void imap_close(int num_parms, ConstStr *Params)
-{
-
-       /* Yes, we always expunge on close. */
-       if (IMAP->selected) {
-               imap_do_expunge();
-       }
-
-       IMAP->selected = 0;
-       IMAP->readonly = 0;
-       imap_free_msgids();
-       IReply("OK CLOSE completed");
-}
-
-
-/*
- * Implements the NAMESPACE command.
- */
-void imap_namespace(int num_parms, ConstStr *Params)
-{
-       long len;
-       int i;
-       struct floor *fl;
-       int floors = 0;
-       char Namespace[SIZ];
-
-       IAPuts("* NAMESPACE ");
-
-       /* All personal folders are subordinate to INBOX. */
-       IAPuts("((\"INBOX/\" \"/\")) ");
-
-       /* Other users' folders ... coming soon! FIXME */
-       IAPuts("NIL ");
-
-       /* Show all floors as shared namespaces.  Neato! */
-       IAPuts("(");
-       for (i = 0; i < MAXFLOORS; ++i) {
-               fl = CtdlGetCachedFloor(i);
-               if (fl->f_flags & F_INUSE) {
-                       /* if (floors > 0) IAPuts(" "); samjam says this confuses javamail */
-                       IAPuts("(");
-                       len = snprintf(Namespace, sizeof(Namespace), "%s/", fl->f_name);
-                       IPutStr(Namespace, len);
-                       IAPuts(" \"/\")");
-                       ++floors;
-               }
-       }
-       IAPuts(")");
-
-       /* Wind it up with a newline and a completion message. */
-       IAPuts("\r\n");
-       IReply("OK NAMESPACE completed");
-}
-
-
-/*
- * Implements the CREATE command
- *
- */
-void imap_create(int num_parms, ConstStr *Params)
-{
-       int ret;
-       char roomname[ROOMNAMELEN];
-       int floornum;
-       int flags;
-       int newroomtype = 0;
-       int newroomview = 0;
-       char *notification_message = NULL;
-
-       if (num_parms < 3) {
-               IReply("NO A foder name must be specified");
-               return;
-       }
-
-       if (strchr(Params[2].Key, '\\') != NULL) {
-               IReply("NO Invalid character in folder name");
-               syslog(LOG_ERR, "invalid character in folder name");
-               return;
-       }
-
-       ret = imap_roomname(roomname, sizeof roomname, Params[2].Key);
-       if (ret < 0) {
-               IReply("NO Invalid mailbox name or location");
-               syslog(LOG_ERR, "invalid mailbox name or location");
-               return;
-       }
-       floornum = (ret & 0x00ff);      /* lower 8 bits = floor number */
-       flags = (ret & 0xff00); /* upper 8 bits = flags        */
-
-       if (flags & IR_MAILBOX) {
-               if (strncasecmp(Params[2].Key, "INBOX/", 6)) {
-                       IReply("NO Personal folders must be created under INBOX");
-                       syslog(LOG_ERR, "not subordinate to inbox");
-                       return;
-               }
-       }
-
-       if (flags & IR_MAILBOX) {
-               newroomtype = 4;                /* private mailbox */
-               newroomview = VIEW_MAILBOX;
-       } else {
-               newroomtype = 0;                /* public folder */
-               newroomview = VIEW_BBS;
-       }
-
-       syslog(LOG_INFO, "Create new room <%s> on floor <%d> with type <%d>",
-                   roomname, floornum, newroomtype);
-
-       ret = CtdlCreateRoom(roomname, newroomtype, "", floornum, 1, 0, newroomview);
-       if (ret == 0) {
-               /*** DO NOT CHANGE THIS ERROR MESSAGE IN ANY WAY!  BYNARI CONNECTOR DEPENDS ON IT! ***/
-               IReply("NO Mailbox already exists, or create failed");
-       } else {
-               IReply("OK CREATE completed");
-               /* post a message in Aide> describing the new room */
-               notification_message = malloc(1024);
-               snprintf(notification_message, 1024,
-                       "A new room called \"%s\" has been created by %s%s%s%s\n",
-                       roomname,
-                       CC->user.fullname,
-                       ((ret & QR_MAILBOX) ? " [personal]" : ""),
-                       ((ret & QR_PRIVATE) ? " [private]" : ""),
-                       ((ret & QR_GUESSNAME) ? " [hidden]" : "")
-               );
-               CtdlAideMessage(notification_message, "Room Creation Message");
-               free(notification_message);
-       }
-       syslog(LOG_DEBUG, "imap_create() completed");
-}
-
-
-/*
- * Locate a room by its IMAP folder name, and check access to it.
- * If zapped_ok is nonzero, we can also look for the room in the zapped list.
- */
-int imap_grabroom(char *returned_roomname, const char *foldername, int zapped_ok)
-{
-       int ret;
-       char augmented_roomname[ROOMNAMELEN];
-       char roomname[ROOMNAMELEN];
-       int c;
-       struct ctdlroom QRscratch;
-       int ra;
-       int ok = 0;
-
-       ret = imap_roomname(roomname, sizeof roomname, foldername);
-       if (ret < 0) {
-               return (1);
-       }
-
-       /* First try a regular match */
-       c = CtdlGetRoom(&QRscratch, roomname);
-
-       /* Then try a mailbox name match */
-       if (c != 0) {
-               CtdlMailboxName(augmented_roomname, sizeof augmented_roomname,
-                           &CC->user, roomname);
-               c = CtdlGetRoom(&QRscratch, augmented_roomname);
-               if (c == 0)
-                       safestrncpy(roomname, augmented_roomname, sizeof(roomname));
-       }
-
-       /* If the room exists, check security/access */
-       if (c == 0) {
-               /* See if there is an existing user/room relationship */
-               CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
-
-               /* normal clients have to pass through security */
-               if (ra & UA_KNOWN) {
-                       ok = 1;
-               }
-               if ((zapped_ok) && (ra & UA_ZAPPED)) {
-                       ok = 1;
-               }
-       }
-
-       /* Fail here if no such room */
-       if (!ok) {
-               strcpy(returned_roomname, "");
-               return (2);
-       } else {
-               safestrncpy(returned_roomname, QRscratch.QRname, ROOMNAMELEN);
-               return (0);
-       }
-}
-
-
-/*
- * Implements the STATUS command (sort of)
- *
- */
-void imap_status(int num_parms, ConstStr *Params)
-{
-       long len;
-       int ret;
-       char roomname[ROOMNAMELEN];
-       char imaproomname[SIZ];
-       char savedroom[ROOMNAMELEN];
-       int msgs, new;
-
-       ret = imap_grabroom(roomname, Params[2].Key, 1);
-       if (ret != 0) {
-               IReply("NO Invalid mailbox name or location, or access denied");
-               return;
-       }
-
-       /*
-        * CtdlUserGoto() formally takes us to the desired room, happily returning
-        * the number of messages and number of new messages.  (If another
-        * folder is selected, save its name so we can return there!!!!!)
-        */
-       if (IMAP->selected) {
-               strcpy(savedroom, CC->room.QRname);
-       }
-       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
-       /*
-        * Tell the client what it wants to know.  In fact, tell it *more* than
-        * it wants to know.  We happily IGnore the supplied status data item
-        * names and simply spew all possible data items.  It's far easier to
-        * code and probably saves us some processing time too.
-        */
-       len = imap_mailboxname(imaproomname, sizeof imaproomname, &CC->room);
-       IAPuts("* STATUS ");
-       IPutStr(imaproomname, len);
-       IAPrintf(" (MESSAGES %d ", msgs);
-       IAPrintf("RECENT %d ", new);    /* Initially, new==recent */
-       IAPrintf("UIDNEXT %ld ", CtdlGetConfigLong("MMhighest") + 1);
-       IAPrintf("UNSEEN %d)\r\n", new);
-       
-       /*
-        * If another folder is selected, go back to that room so we can resume
-        * our happy day without violent explosions.
-        */
-       if (IMAP->selected) {
-               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
-       }
-
-       /*
-        * Oooh, look, we're done!
-        */
-       IReply("OK STATUS completed");
-}
-
-
-/*
- * Implements the SUBSCRIBE command
- *
- */
-void imap_subscribe(int num_parms, ConstStr *Params)
-{
-       int ret;
-       char roomname[ROOMNAMELEN];
-       char savedroom[ROOMNAMELEN];
-       int msgs, new;
-
-       ret = imap_grabroom(roomname, Params[2].Key, 1);
-       if (ret != 0) {
-               IReplyPrintf(
-                       "NO Error %d: invalid mailbox name or location, or access denied",
-                       ret
-               );
-               return;
-       }
-
-       /*
-        * CtdlUserGoto() formally takes us to the desired room, which has the side
-        * effect of marking the room as not-zapped ... exactly the effect
-        * we're looking for.
-        */
-       if (IMAP->selected) {
-               strcpy(savedroom, CC->room.QRname);
-       }
-       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
-       /*
-        * If another folder is selected, go back to that room so we can resume
-        * our happy day without violent explosions.
-        */
-       if (IMAP->selected) {
-               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
-       }
-
-       IReply("OK SUBSCRIBE completed");
-}
-
-
-/*
- * Implements the UNSUBSCRIBE command
- *
- */
-void imap_unsubscribe(int num_parms, ConstStr *Params)
-{
-       int ret;
-       char roomname[ROOMNAMELEN];
-       char savedroom[ROOMNAMELEN];
-       int msgs, new;
-
-       ret = imap_grabroom(roomname, Params[2].Key, 1);
-       if (ret != 0) {
-               IReply("NO Invalid mailbox name or location, or access denied");
-               return;
-       }
-
-       /*
-        * CtdlUserGoto() formally takes us to the desired room.
-        */
-       if (IMAP->selected) {
-               strcpy(savedroom, CC->room.QRname);
-       }
-       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
-       /* 
-        * Now make the API call to zap the room
-        */
-       if (CtdlForgetThisRoom() == 0) {
-               IReply("OK UNSUBSCRIBE completed");
-       } else {
-               IReply("NO You may not unsubscribe from this folder.");
-       }
-
-       /*
-        * If another folder is selected, go back to that room so we can resume
-        * our happy day without violent explosions.
-        */
-       if (IMAP->selected) {
-               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
-       }
-}
-
-
-/*
- * Implements the DELETE command
- *
- */
-void imap_delete(int num_parms, ConstStr *Params)
-{
-       int ret;
-       char roomname[ROOMNAMELEN];
-       char savedroom[ROOMNAMELEN];
-       int msgs, new;
-
-       ret = imap_grabroom(roomname, Params[2].Key, 1);
-       if (ret != 0) {
-               IReply("NO Invalid mailbox name, or access denied");
-               return;
-       }
-
-       /*
-        * CtdlUserGoto() formally takes us to the desired room, happily returning
-        * the number of messages and number of new messages.  (If another
-        * folder is selected, save its name so we can return there!!!!!)
-        */
-       if (IMAP->selected) {
-               strcpy(savedroom, CC->room.QRname);
-       }
-       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
-       /*
-        * Now delete the room.
-        */
-       if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->room)) {
-               CtdlScheduleRoomForDeletion(&CC->room);
-               IReply("OK DELETE completed");
-       } else {
-               IReply("NO Can't delete this folder.");
-       }
-
-       /*
-        * If another folder is selected, go back to that room so we can resume
-        * our happy day without violent explosions.
-        */
-       if (IMAP->selected) {
-               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
-       }
-}
-
-
-/*
- * Back end function for imap_rename()
- */
-void imap_rename_backend(struct ctdlroom *qrbuf, void *data)
-{
-       char foldername[SIZ];
-       char newfoldername[SIZ];
-       char newroomname[ROOMNAMELEN];
-       int newfloor = 0;
-       struct irl *irlp = NULL;        /* scratch pointer */
-       irlparms *myirlparms;
-
-       myirlparms = (irlparms *) data;
-       imap_mailboxname(foldername, sizeof foldername, qrbuf);
-
-       /* Rename subfolders */
-       if ((!strncasecmp(foldername, myirlparms->oldname,
-                         myirlparms->oldnamelen)
-           && (foldername[myirlparms->oldnamelen] == '/'))) {
-
-               sprintf(newfoldername, "%s/%s",
-                       myirlparms->newname,
-                       &foldername[myirlparms->oldnamelen + 1]
-                   );
-
-               newfloor = imap_roomname(newroomname,
-                                        sizeof newroomname,
-                                        newfoldername) & 0xFF;
-
-               irlp = (struct irl *) malloc(sizeof(struct irl));
-               strcpy(irlp->irl_newroom, newroomname);
-               strcpy(irlp->irl_oldroom, qrbuf->QRname);
-               irlp->irl_newfloor = newfloor;
-               irlp->next = *(myirlparms->irl);
-               *(myirlparms->irl) = irlp;
-       }
-}
-
-
-/*
- * Implements the RENAME command
- *
- */
-void imap_rename(int num_parms, ConstStr *Params)
-{
-       char old_room[ROOMNAMELEN];
-       char new_room[ROOMNAMELEN];
-       int newr;
-       int new_floor;
-       int r;
-       struct irl *irl = NULL; /* the list */
-       struct irl *irlp = NULL;        /* scratch pointer */
-       irlparms irlparms;
-       char aidemsg[1024];
-
-       if (strchr(Params[3].Key, '\\') != NULL) {
-               IReply("NO Invalid character in folder name");
-               return;
-       }
-
-       imap_roomname(old_room, sizeof old_room, Params[2].Key);
-       newr = imap_roomname(new_room, sizeof new_room, Params[3].Key);
-       new_floor = (newr & 0xFF);
-
-       r = CtdlRenameRoom(old_room, new_room, new_floor);
-
-       if (r == crr_room_not_found) {
-               IReply("NO Could not locate this folder");
-               return;
-       }
-       if (r == crr_already_exists) {
-               IReplyPrintf("NO '%s' already exists.");
-               return;
-       }
-       if (r == crr_noneditable) {
-               IReply("NO This folder is not editable.");
-               return;
-       }
-       if (r == crr_invalid_floor) {
-               IReply("NO Folder root does not exist.");
-               return;
-       }
-       if (r == crr_access_denied) {
-               IReply("NO You do not have permission to edit this folder.");
-               return;
-       }
-       if (r != crr_ok) {
-               IReplyPrintf("NO Rename failed - undefined error %d", r);
-               return;
-       }
-
-       /* If this is the INBOX, then RFC2060 says we have to just move the
-        * contents.  In a Citadel environment it's easier to rename the room
-        * (already did that) and create a new inbox.
-        */
-       if (!strcasecmp(Params[2].Key, "INBOX")) {
-               CtdlCreateRoom(MAILROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
-       }
-
-       /* Otherwise, do the subfolders.  Build a list of rooms to rename... */
-       else {
-               irlparms.oldname = Params[2].Key;
-               irlparms.oldnamelen = Params[2].len;
-               irlparms.newname = Params[3].Key;
-               irlparms.newnamelen = Params[3].len;
-               irlparms.irl = &irl;
-               CtdlForEachRoom(imap_rename_backend, (void *) &irlparms);
-
-               /* ... and now rename them. */
-               while (irl != NULL) {
-                       r = CtdlRenameRoom(irl->irl_oldroom,
-                                          irl->irl_newroom,
-                                          irl->irl_newfloor);
-                       if (r != crr_ok) {
-                               /* FIXME handle error returns better */
-                               syslog(LOG_ERR, "CtdlRenameRoom() error %d", r);
-                       }
-                       irlp = irl;
-                       irl = irl->next;
-                       free(irlp);
-               }
-       }
-
-       snprintf(aidemsg, sizeof aidemsg, "IMAP folder \"%s\" renamed to \"%s\" by %s\n",
-               Params[2].Key,
-               Params[3].Key,
-               CC->curr_user
-       );
-       CtdlAideMessage(aidemsg, "IMAP folder rename");
-
-       IReply("OK RENAME completed");
-}
-
-
-/* 
- * Main command loop for IMAP sessions.
- */
-void imap_command_loop(void)
-{
-       struct CitContext *CCC = CC;
-       struct timeval tv1, tv2;
-       suseconds_t total_time = 0;
-       citimap *Imap;
-       const char *pchs, *pche;
-       const imap_handler_hook *h;
-
-       gettimeofday(&tv1, NULL);
-       CCC->lastcmd = time(NULL);
-       Imap = CCCIMAP;
-
-       flush_output();
-       if (Imap->Cmd.CmdBuf == NULL)
-               Imap->Cmd.CmdBuf = NewStrBufPlain(NULL, SIZ);
-       else
-               FlushStrBuf(Imap->Cmd.CmdBuf);
-
-       if (CtdlClientGetLine(Imap->Cmd.CmdBuf) < 1) {
-               syslog(LOG_ERR, "client disconnected: ending session.");
-               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
-               return;
-       }
-
-       if (Imap->authstate == imap_as_expecting_password) {
-               syslog(LOG_INFO, "<password>");
-       }
-       else if (Imap->authstate == imap_as_expecting_plainauth) {
-               syslog(LOG_INFO, "<plain_auth>");
-       }
-       else if ((Imap->authstate == imap_as_expecting_multilineusername) || 
-                cbmstrcasestr(ChrPtr(Imap->Cmd.CmdBuf), " LOGIN ")) {
-               syslog(LOG_INFO, "LOGIN...");
-       }
-       else {
-               syslog(LOG_DEBUG, "%s", ChrPtr(Imap->Cmd.CmdBuf));
-       }
-
-       pchs = ChrPtr(Imap->Cmd.CmdBuf);
-       pche = pchs + StrLength(Imap->Cmd.CmdBuf);
-
-       while ((pche > pchs) &&
-              ((*pche == '\n') ||
-               (*pche == '\r')))
-       {
-               pche --;
-               StrBufCutRight(Imap->Cmd.CmdBuf, 1);
-       }
-       StrBufTrim(Imap->Cmd.CmdBuf);
-
-       /* If we're in the middle of a multi-line command, handle that */
-       switch (Imap->authstate){
-       case imap_as_expecting_username:
-               imap_auth_login_user(imap_as_expecting_username);
-               IUnbuffer();
-               return;
-       case imap_as_expecting_multilineusername:
-               imap_auth_login_user(imap_as_expecting_multilineusername);
-               IUnbuffer();
-               return;
-       case imap_as_expecting_plainauth:
-               imap_auth_plain();
-               IUnbuffer();
-               return;
-       case imap_as_expecting_password:
-               imap_auth_login_pass(imap_as_expecting_password);
-               IUnbuffer();
-               return;
-       case imap_as_expecting_multilinepassword:
-               imap_auth_login_pass(imap_as_expecting_multilinepassword);
-               IUnbuffer();
-               return;
-       default:
-               break;
-       }
-
-       /* Ok, at this point we're in normal command mode.
-        * If the command just submitted does not contain a literal, we
-        * might think about delivering some untagged stuff...
-        */
-
-       /* Grab the tag, command, and parameters. */
-       imap_parameterize(&Imap->Cmd);
-#if 0 
-/* debug output the parsed vector */
-       {
-               int i;
-               syslog(LOG_DEBUG, "----- %ld params", Imap->Cmd.num_parms);
-
-       for (i=0; i < Imap->Cmd.num_parms; i++) {
-               if (Imap->Cmd.Params[i].len != strlen(Imap->Cmd.Params[i].Key))
-                       syslog(LOG_DEBUG, "*********** %ld != %ld : %s",
-                                   Imap->Cmd.Params[i].len, 
-                                   strlen(Imap->Cmd.Params[i].Key),
-                                     Imap->Cmd.Params[i].Key);
-               else
-                       syslog(LOG_DEBUG, "%ld : %s",
-                                   Imap->Cmd.Params[i].len, 
-                                   Imap->Cmd.Params[i].Key);
-       }}
-#endif
-
-       /* Now for the command set. */
-       h = imap_lookup(Imap->Cmd.num_parms, Imap->Cmd.Params);
-
-       if (h == NULL)
-       {
-               IReply("BAD command unrecognized");
-               goto BAIL;
-       }
-
-       /* RFC3501 says that we cannot output untagged data during these commands */
-       if ((h->Flags & I_FLAG_UNTAGGED) == 0) {
-
-               /* we can put any additional untagged stuff right here in the future */
-
-               /*
-                * Before processing the command that was just entered... if we happen
-                * to have a folder selected, we'd like to rescan that folder for new
-                * messages, and for deletions/changes of existing messages.  This
-                * could probably be optimized better with some deep thought...
-                */
-               if (Imap->selected) {
-                       imap_rescan_msgids();
-               }
-       }
-
-       /* does our command require a logged-in state */
-       if ((!CC->logged_in) && ((h->Flags & I_FLAG_LOGGED_IN) != 0)) {
-               IReply("BAD Not logged in.");
-               goto BAIL;
-       }
-
-       /* does our command require the SELECT state on a mailbox */
-       if ((Imap->selected == 0) && ((h->Flags & I_FLAG_SELECT) != 0)){
-               IReply("BAD no folder selected");
-               goto BAIL;
-       }
-       h->h(Imap->Cmd.num_parms, Imap->Cmd.Params);
-
-       /* If the client transmitted a message we can free it now */
-
-BAIL:
-       IUnbuffer();
-
-       imap_free_transmitted_message();
-
-       gettimeofday(&tv2, NULL);
-       total_time = (tv2.tv_usec + (tv2.tv_sec * 1000000)) - (tv1.tv_usec + (tv1.tv_sec * 1000000));
-       syslog(LOG_DEBUG, "IMAP command completed in %ld.%ld seconds",
-                   (total_time / 1000000),
-                   (total_time % 1000000)
-               );
-}
-
-void imap_noop (int num_parms, ConstStr *Params)
-{
-       IReply("OK No operation");
-}
-
-void imap_logout(int num_parms, ConstStr *Params)
-{
-       if (IMAP->selected) {
-               imap_do_expunge();      /* yes, we auto-expunge at logout */
-       }
-       IAPrintf("* BYE %s logging out\r\n", CtdlGetConfigStr("c_fqdn"));
-       IReply("OK Citadel IMAP session ended.");
-       CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
-       return;
-}
-
-const char *CitadelServiceIMAP="IMAP";
-const char *CitadelServiceIMAPS="IMAPS";
-
-/*
- * This function is called to register the IMAP extension with Citadel.
- */
-CTDL_MODULE_INIT(imap)
-{
-       if (ImapCmds == NULL)
-               ImapCmds = NewHash(1, NULL);
-
-       RegisterImapCMD("NOOP", "", imap_noop, I_FLAG_NONE);
-       RegisterImapCMD("CHECK", "", imap_noop, I_FLAG_NONE);
-       RegisterImapCMD("ID", "", imap_id, I_FLAG_NONE);
-       RegisterImapCMD("LOGOUT", "", imap_logout, I_FLAG_NONE);
-       RegisterImapCMD("LOGIN", "", imap_login, I_FLAG_NONE);
-       RegisterImapCMD("AUTHENTICATE", "", imap_authenticate, I_FLAG_NONE);
-       RegisterImapCMD("CAPABILITY", "", imap_capability, I_FLAG_NONE);
-#ifdef HAVE_OPENSSL
-       RegisterImapCMD("STARTTLS", "", imap_starttls, I_FLAG_NONE);
-#endif
-
-       /* The commans below require a logged-in state */
-       RegisterImapCMD("SELECT", "", imap_select, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("EXAMINE", "", imap_select, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("LSUB", "", imap_list, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("LIST", "", imap_list, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("CREATE", "", imap_create, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("DELETE", "", imap_delete, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("RENAME", "", imap_rename, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("STATUS", "", imap_status, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("SUBSCRIBE", "", imap_subscribe, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("UNSUBSCRIBE", "", imap_unsubscribe, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("APPEND", "", imap_append, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("NAMESPACE", "", imap_namespace, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("SETACL", "", imap_setacl, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("DELETEACL", "", imap_deleteacl, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("GETACL", "", imap_getacl, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("LISTRIGHTS", "", imap_listrights, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("MYRIGHTS", "", imap_myrights, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("GETMETADATA", "", imap_getmetadata, I_FLAG_LOGGED_IN);
-       RegisterImapCMD("SETMETADATA", "", imap_setmetadata, I_FLAG_LOGGED_IN);
-
-       /* The commands below require the SELECT state on a mailbox */
-       RegisterImapCMD("FETCH", "", imap_fetch, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED);
-       RegisterImapCMD("UID", "FETCH", imap_uidfetch, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
-       RegisterImapCMD("SEARCH", "", imap_search, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED);
-       RegisterImapCMD("UID", "SEARCH", imap_uidsearch, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
-       RegisterImapCMD("STORE", "", imap_store, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED);
-       RegisterImapCMD("UID", "STORE", imap_uidstore, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
-       RegisterImapCMD("COPY", "", imap_copy, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
-       RegisterImapCMD("UID", "COPY", imap_uidcopy, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
-       RegisterImapCMD("EXPUNGE", "", imap_expunge, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
-       RegisterImapCMD("UID", "EXPUNGE", imap_expunge, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
-       RegisterImapCMD("CLOSE", "", imap_close, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
-
-       if (!threading)
-       {
-               CtdlRegisterServiceHook(CtdlGetConfigInt("c_imap_port"),
-                                       NULL, imap_greeting, imap_command_loop, NULL, CitadelServiceIMAP);
-#ifdef HAVE_OPENSSL
-               CtdlRegisterServiceHook(CtdlGetConfigInt("c_imaps_port"),
-                                       NULL, imaps_greeting, imap_command_loop, NULL, CitadelServiceIMAPS);
-#endif
-               CtdlRegisterSessionHook(imap_cleanup_function, EVT_STOP, PRIO_STOP + 30);
-       }
-       
-       /* return our module name for the log */
-       return "imap";
-}
diff --git a/citadel/modules/imap/serv_imap.h b/citadel/modules/imap/serv_imap.h
deleted file mode 100644 (file)
index bd5b933..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-#define GLOBAL_UIDVALIDITY_VALUE       1L
-
-
-void imap_cleanup_function(void);
-void imap_greeting(void);
-void imap_command_loop(void);
-int imap_grabroom(char *returned_roomname, const char *foldername, int zapped_ok);
-void imap_free_transmitted_message(void);
-int imap_do_expunge(void);
-void imap_rescan_msgids(void);
-
-/*
- * FDELIM defines which character we want to use as a folder delimiter
- * in room names.  Originally we used a forward slash, but that caused
- * rooms with names like "Sent/Received Pages" to get delimited, so we
- * changed it to a backslash.  This is completely irrelevant to how Citadel
- * speaks to IMAP clients -- the delimiter used in the IMAP protocol is
- * a vertical bar, which is illegal in Citadel room names anyway.
- */
-
-typedef void (*imap_handler)(int num_parms, ConstStr *Params);
-
-typedef struct _imap_handler_hook {
-       imap_handler h;
-       int Flags;
-} imap_handler_hook;
-
-typedef struct __citimap_command {
-       StrBuf *CmdBuf;                 /* our current commandline; gets chopped into: */
-       ConstStr *Params;               /* Commandline tokens */
-       int num_parms;                  /* Number of Commandline tokens available */
-       int avail_parms;                /* Number of ConstStr args is big */
-       const imap_handler_hook *hh;
-} citimap_command;
-
-
-typedef struct __citimap {
-       StrBuf *Reply;
-       int authstate;
-       char authseq[SIZ];
-       int selected;                   /* set to 1 if in the SELECTED state */
-       int readonly;                   /* mailbox is open read only */
-       int num_msgs;                   /* Number of messages being mapped */
-       int num_alloc;                  /* Number of messages for which we've allocated space */
-       time_t last_mtime;              /* For checking whether the room was modified... */
-       long *msgids;
-       unsigned int *flags;
-
-       StrBuf *TransmittedMessage;     /* for APPEND command... */
-
-       citimap_command Cmd;            /* our current commandline */
-
-       /* Cache most recent RFC822 FETCH because client might load in pieces */
-       StrBuf *cached_rfc822;
-       long cached_rfc822_msgnum;
-       char cached_rfc822_withbody;    /* 1 = body cached; 0 = only headers cached */
-
-       /* Cache most recent BODY FETCH because client might load in pieces */
-       char *cached_body;
-       size_t cached_body_len;
-       char cached_bodypart[SIZ];
-       long cached_bodymsgnum;
-       char cached_body_withbody;      /* 1 = body cached; 0 = only headers cached */
-} citimap;
-
-/*
- * values of 'authstate'
- */
-enum {
-       imap_as_normal,
-       imap_as_expecting_username,
-       imap_as_expecting_password,
-       imap_as_expecting_plainauth,
-       imap_as_expecting_multilineusername,
-       imap_as_expecting_multilinepassword
-};
-
-/* Flags for the above struct.  Note that some of these are for internal use,
- * and are not to be reported to IMAP clients.
- */
-#define IMAP_ANSWERED          1       /* reportable and setable */
-#define IMAP_FLAGGED           2       /* reportable and setable */
-#define IMAP_DELETED           4       /* reportable and setable */
-#define IMAP_DRAFT             8       /* reportable and setable */
-#define IMAP_SEEN              16      /* reportable and setable */
-
-#define IMAP_MASK_SETABLE      0x1f
-#define IMAP_MASK_SYSTEM       0xe0
-
-#define IMAP_SELECTED          32      /* neither reportable nor setable */
-#define IMAP_RECENT            64      /* reportable but not setable */
-
-
-/*
- * Flags that may be returned by imap_roomname()
- * (the lower eight bits will be the floor number)
- */
-#define IR_MAILBOX     0x0100          /* Mailbox                       */
-#define IR_EXISTS      0x0200          /* Room exists (not implemented) */
-#define IR_BABOON      0x0000          /* Just had to put this here :)  */
-
-#define FDELIM '\\'
-
-#define IMAP ((citimap *)CC->session_specific_data)
-#define CCCIMAP ((citimap *)CCC->session_specific_data)
-
-#define IMAPDBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (IMAPDebugEnabled != 0))
-
-#define I_FLAG_NONE          (0)
-#define I_FLAG_LOGGED_IN  (1<<0)
-#define I_FLAG_SELECT     (1<<1)
-/* RFC3501 says that we cannot output untagged data during these commands */
-#define I_FLAG_UNTAGGED   (1<<2)
-
-/*
- * When loading arrays of message ID's into memory, increase the buffer to
- * hold this many additional messages instead of calling realloc() each time.
- */
-#define REALLOC_INCREMENT 100
-
-
-void registerImapCMD(const char *First, long FLen, 
-                    const char *Second, long SLen,
-                    imap_handler H,
-                    int Flags);
-
-#define RegisterImapCMD(First, Second, H, Flags) \
-       registerImapCMD(HKEY(First), HKEY(Second), H, Flags)
diff --git a/citadel/modules/inboxrules/.gitignore b/citadel/modules/inboxrules/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/inboxrules/serv_inboxrules.c b/citadel/modules/inboxrules/serv_inboxrules.c
deleted file mode 100644 (file)
index a1a5f7c..0000000
+++ /dev/null
@@ -1,982 +0,0 @@
-// Inbox handling rules
-//
-// Copyright (c) 1987-2022 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "ctdl_module.h"
-
-
-// The next sections are enums and keys that drive the serialize/deserialize functions for the inbox rules/state configuration.
-
-// Fields to be compared
-enum {
-       field_from,             
-       field_tocc,             
-       field_subject,  
-       field_replyto,  
-       field_sender,   
-       field_resentfrom,       
-       field_resentto, 
-       field_envfrom,  
-       field_envto,    
-       field_xmailer,  
-       field_xspamflag,        
-       field_xspamstatus,      
-       field_listid,   
-       field_size,             
-       field_all
-};
-char *field_keys[] = {
-       "from",
-       "tocc",
-       "subject",
-       "replyto",
-       "sender",
-       "resentfrom",
-       "resentto",
-       "envfrom",
-       "envto",
-       "xmailer",
-       "xspamflag",
-       "xspamstatus",
-       "listid",
-       "size",
-       "all"
-};
-
-// Field comparison operators
-enum {
-       fcomp_contains,
-       fcomp_notcontains,
-       fcomp_is,
-       fcomp_isnot,
-       fcomp_matches,
-       fcomp_notmatches
-};
-char *fcomp_keys[] = {
-       "contains",
-       "notcontains",
-       "is",
-       "isnot",
-       "matches",
-       "notmatches"
-};
-
-// Actions
-enum {
-       action_keep,
-       action_discard,
-       action_reject,
-       action_fileinto,
-       action_redirect,
-       action_vacation
-};
-char *action_keys[] = {
-       "keep",
-       "discard",
-       "reject",
-       "fileinto",
-       "redirect",
-       "vacation"
-};
-
-// Size comparison operators
-enum {
-       scomp_larger,
-       scomp_smaller
-};
-char *scomp_keys[] = {
-       "larger",
-       "smaller"
-};
-
-// Final actions
-enum {
-       final_continue,
-       final_stop
-};
-char *final_keys[] = {
-       "continue",
-       "stop"
-};
-
-// This data structure represents ONE inbox rule within the configuration.
-struct irule {
-       int compared_field;
-       int field_compare_op;
-       char compared_value[128];
-       int size_compare_op;
-       long compared_size;
-       int action;
-       char file_into[ROOMNAMELEN];
-       char redirect_to[1024];
-       char autoreply_message[SIZ];
-       int final_action;
-};
-
-// This data structure represents the entire inbox rules configuration AND current state for a single user.
-struct inboxrules {
-       long lastproc;
-       int num_rules;
-       struct irule *rules;
-};
-
-
-// Destructor for 'struct inboxrules'
-void free_inbox_rules(struct inboxrules *ibr) {
-       free(ibr->rules);
-       free(ibr);
-}
-
-
-// Constructor for 'struct inboxrules' that deserializes the configuration from text input.
-struct inboxrules *deserialize_inbox_rules(char *serialized_rules) {
-       int i;
-
-       if (!serialized_rules) {
-               return NULL;
-       }
-
-       // Make a copy of the supplied buffer because we're going to shit all over it with strtok_r()
-       char *sr = strdup(serialized_rules);
-       if (!sr) {
-               return NULL;
-       }
-
-       struct inboxrules *ibr = malloc(sizeof(struct inboxrules));
-       if (ibr == NULL) {
-               return NULL;
-       }
-       memset(ibr, 0, sizeof(struct inboxrules));
-
-       char *token; 
-       char *rest = sr;
-       while ((token = strtok_r(rest, "\n", &rest))) {
-
-               // For backwards compatibility, "# WEBCIT_RULE" is an alias for "rule".
-               // Prior to version 930, WebCit converted its rules to Sieve scripts, but saved the rules as comments for
-               // later re-editing.  Now, the rules hidden in the comments become the real rules.
-               if (!strncasecmp(token, "# WEBCIT_RULE|", 14)) {
-                       strcpy(token, "rule|"); 
-                       strcpy(&token[5], &token[14]);
-               }
-
-               // Lines containing actual rules are double-serialized with Base64.  It seemed like a good idea at the time :(
-               if (!strncasecmp(token, "rule|", 5)) {
-                       remove_token(&token[5], 0, '|');
-                       char *decoded_rule = malloc(strlen(token));
-                       CtdlDecodeBase64(decoded_rule, &token[5], strlen(&token[5]));
-                       ibr->num_rules++;
-                       ibr->rules = realloc(ibr->rules, (sizeof(struct irule) * ibr->num_rules));
-                       struct irule *new_rule = &ibr->rules[ibr->num_rules - 1];
-                       memset(new_rule, 0, sizeof(struct irule));
-
-                       // We have a rule , now parse it
-                       char rtoken[SIZ];
-                       int nt = num_tokens(decoded_rule, '|');
-                       int t = 0;
-                       for (t=0; t<nt; ++t) {
-                               extract_token(rtoken, decoded_rule, t, '|', sizeof(rtoken));
-                               striplt(rtoken);
-                               switch(t) {
-                                       case 1:                                                         // field to compare
-                                               for (i=0; i<=field_all; ++i) {
-                                                       if (!strcasecmp(rtoken, field_keys[i])) {
-                                                               new_rule->compared_field = i;
-                                                       }
-                                               }
-                                               break;
-                                       case 2:                                                         // field comparison operation
-                                               for (i=0; i<=fcomp_notmatches; ++i) {
-                                                       if (!strcasecmp(rtoken, fcomp_keys[i])) {
-                                                               new_rule->field_compare_op = i;
-                                                       }
-                                               }
-                                               break;
-                                       case 3:                                                         // field comparison value
-                                               safestrncpy(new_rule->compared_value, rtoken, sizeof(new_rule->compared_value));
-                                               break;
-                                       case 4:                                                         // size comparison operation
-                                               for (i=0; i<=scomp_smaller; ++i) {
-                                                       if (!strcasecmp(rtoken, scomp_keys[i])) {
-                                                               new_rule->size_compare_op = i;
-                                                       }
-                                               }
-                                               break;
-                                       case 5:                                                         // size comparison value
-                                               new_rule->compared_size = atol(rtoken);
-                                               break;
-                                       case 6:                                                         // action
-                                               for (i=0; i<=action_vacation; ++i) {
-                                                       if (!strcasecmp(rtoken, action_keys[i])) {
-                                                               new_rule->action = i;
-                                                       }
-                                               }
-                                               break;
-                                       case 7:                                                         // file into (target room)
-                                               safestrncpy(new_rule->file_into, rtoken, sizeof(new_rule->file_into));
-                                               break;
-                                       case 8:                                                         // redirect to (target address)
-                                               safestrncpy(new_rule->redirect_to, rtoken, sizeof(new_rule->redirect_to));
-                                               break;
-                                       case 9:                                                         // autoreply message
-                                               safestrncpy(new_rule->autoreply_message, rtoken, sizeof(new_rule->autoreply_message));
-                                               break;
-                                       case 10:                                                        // final_action;
-                                               for (i=0; i<=final_stop; ++i) {
-                                                       if (!strcasecmp(rtoken, final_keys[i])) {
-                                                               new_rule->final_action = i;
-                                                       }
-                                               }
-                                               break;
-                                       default:
-                                               break;
-                               }
-                       }
-                       free(decoded_rule);
-               }
-
-               // "lastproc" indicates the newest message number in the inbox that was previously processed by our inbox rules.
-               // This is a legacy location for this value and will only be used if it's the only one present.
-               else if (!strncasecmp(token, "lastproc|", 5)) {
-                       ibr->lastproc = atol(&token[9]);
-               }
-
-               // Lines which do not contain a recognizable token must be IGNORED.  These lines may be left over
-               // from a previous version and will disappear when we rewrite the config.
-
-       }
-
-       free(sr);               // free our copy of the source buffer that has now been trashed with null bytes...
-       return(ibr);            // and return our complex data type to the caller.
-}
-
-
-// Perform the "fileinto" action (save the message in another room)
-// Returns: 1 or 0 to tell the caller to keep (1) or delete (0) the inbox copy of the message.
-//
-int inbox_do_fileinto(struct irule *rule, long msgnum) {
-       char *dest_folder = rule->file_into;
-       char original_room_name[ROOMNAMELEN];
-       char foldername[ROOMNAMELEN];
-       int c;
-
-       // Situations where we want to just keep the message in the inbox:
-       if (
-               (IsEmptyStr(dest_folder))                       // no destination room was specified
-               || (!strcasecmp(dest_folder, "INBOX"))          // fileinto inbox is the same as keep
-               || (!strcasecmp(dest_folder, MAILROOM))         // fileinto "Mail" is the same as keep
-       ) {
-               return(1);                                      // don't delete the inbox copy if this failed
-       }
-
-       // Remember what room we came from
-       safestrncpy(original_room_name, CC->room.QRname, sizeof original_room_name);
-
-       // First try a mailbox name match (check personal mail folders first)
-       strcpy(foldername, original_room_name);                                 // This keeps the user namespace of the inbox
-       snprintf(&foldername[10], sizeof(foldername)-10, ".%s", dest_folder);   // And this tacks on the target room name
-       c = CtdlGetRoom(&CC->room, foldername);
-
-       // Then a regular room name match (public and private rooms)
-       if (c != 0) {
-               safestrncpy(foldername, dest_folder, sizeof foldername);
-               c = CtdlGetRoom(&CC->room, foldername);
-       }
-
-       if (c != 0) {
-               syslog(LOG_WARNING, "inboxrules: target <%s> does not exist", dest_folder);
-               return(1);                                      // don't delete the inbox copy if this failed
-       }
-
-       // Yes, we actually have to go there
-       CtdlUserGoto(NULL, 0, 0, NULL, NULL, NULL, NULL);
-
-       c = CtdlSaveMsgPointersInRoom(NULL, &msgnum, 1, 0, NULL, 0);
-
-       // Go back to the room we came from
-       if (strcasecmp(original_room_name, CC->room.QRname)) {
-               CtdlUserGoto(original_room_name, 0, 0, NULL, NULL, NULL, NULL);
-       }
-
-       return(0);                                              // delete the inbox copy
-}
-
-
-// Perform the "redirect" action (divert the message to another email address)
-// Returns: 1 or 0 to tell the caller to keep (1) or delete (0) the inbox copy of the message.
-//
-int inbox_do_redirect(struct irule *rule, long msgnum) {
-       if (IsEmptyStr(rule->redirect_to)) {
-               syslog(LOG_WARNING, "inboxrules: inbox_do_redirect() invalid recipient <%s>", rule->redirect_to);
-               return(1);                                      // don't delete the inbox copy if this failed
-       }
-
-       struct recptypes *valid = validate_recipients(rule->redirect_to, NULL, 0);
-       if (valid == NULL) {
-               syslog(LOG_WARNING, "inboxrules: inbox_do_redirect() invalid recipient <%s>", rule->redirect_to);
-               return(1);                                      // don't delete the inbox copy if this failed
-       }
-       if (valid->num_error > 0) {
-               free_recipients(valid);
-               syslog(LOG_WARNING, "inboxrules: inbox_do_redirect() invalid recipient <%s>", rule->redirect_to);
-               return(1);                                      // don't delete the inbox copy if this failed
-       }
-
-       struct CtdlMessage *msg = CtdlFetchMessage(msgnum, 1);
-       if (msg == NULL) {
-               free_recipients(valid);
-               syslog(LOG_WARNING, "inboxrules: cannot reload message %ld for forwarding", msgnum);
-               return(1);                                      // don't delete the inbox copy if this failed
-       }
-
-       CtdlSubmitMsg(msg, valid, NULL);                        // send the message to the new recipient
-       free_recipients(valid);
-       CM_Free(msg);
-       return(0);                                              // delete the inbox copy
-}
-
-
-// Perform the "reject" action (delete the message, and tell the sender we deleted it)
-void inbox_do_reject(struct irule *rule, struct CtdlMessage *msg) {
-       syslog(LOG_DEBUG, "inbox_do_reject: sender: <%s>, reject", msg->cm_fields[erFc822Addr]);
-
-       // If we can't determine who sent the message, reject silently.
-       char *sender;
-       if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
-               sender = msg->cm_fields[eMessagePath];
-       }
-       else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
-               sender = msg->cm_fields[erFc822Addr];
-       }
-       else {
-               return;
-       }
-
-       // Assemble the reject message.
-       char *reject_text = malloc(strlen(rule->autoreply_message) + 1024);
-       if (reject_text == NULL) {
-               return;
-       }
-       sprintf(reject_text, 
-               "Content-type: text/plain\n"
-               "\n"
-               "The message was refused by the recipient's mail filtering program.\n"
-               "The reason given was as follows:\n"
-               "\n"
-               "%s\n"
-               "\n"
-       ,
-               rule->autoreply_message
-       );
-
-       // Deliver the message
-       quickie_message(
-               " ",
-               msg->cm_fields[eenVelopeTo],
-               sender,
-               MAILROOM,
-               reject_text,
-               FMT_RFC822,
-               "Delivery status notification"
-       );
-       free(reject_text);
-}
-
-
-// Perform the "vacation" action (send an automatic response)
-void inbox_do_vacation(struct irule *rule, struct CtdlMessage *msg) {
-       syslog(LOG_DEBUG, "inbox_do_vacation: sender: <%s>, vacation", msg->cm_fields[erFc822Addr]);
-
-       // If we can't determine who sent the message, no auto-reply can be sent.
-       char *sender;
-       if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
-               sender = msg->cm_fields[eMessagePath];
-       }
-       else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
-               sender = msg->cm_fields[erFc822Addr];
-       }
-       else {
-               return;
-       }
-
-       // Avoid repeatedly sending auto-replies to the same correspondent over and over again by creating
-       // a hash of the user, correspondent, and reply text, and hitting the S_USETABLE database.
-       StrBuf *u = NewStrBuf();
-       StrBufPrintf(u, "vacation/%x/%x/%x",
-               HashLittle(sender, strlen(sender)),
-               HashLittle(msg->cm_fields[eenVelopeTo], msg->cm_lengths[eenVelopeTo]),
-               HashLittle(rule->autoreply_message, strlen(rule->autoreply_message))
-       );
-       int already_seen = CheckIfAlreadySeen(u);
-       FreeStrBuf(&u);
-
-       if (!already_seen) {
-               // Assemble the auto-reply message.
-               StrBuf *reject_text = NewStrBuf();
-               if (reject_text == NULL) {
-                       return;
-               }
-
-               StrBufPrintf(reject_text, 
-                       "Content-type: text/plain\n"
-                       "\n"
-                       "%s\n"
-                       "\n"
-               ,
-                       rule->autoreply_message
-               );
-       
-               // Deliver the auto-reply.
-               quickie_message(
-                       "",
-                       msg->cm_fields[eenVelopeTo],
-                       sender,
-                       MAILROOM,
-                       ChrPtr(reject_text),
-                       FMT_RFC822,
-                       "Delivery status notification"
-               );
-               FreeStrBuf(&reject_text);
-       }
-}
-
-
-/*
- * Process a single message.  We know the room, the user, the rules, the message number, etc.
- */
-void inbox_do_msg(long msgnum, void *userdata) {
-       struct inboxrules *ii = (struct inboxrules *) userdata;
-       struct CtdlMessage *msg = NULL;         // If we are loading a message to process, put it here.
-       int headers_loaded = 0;                 // Did we load the headers yet?  Do it only once.
-       int body_loaded = 0;                    // Did we load the message body yet?  Do it only once.
-       int metadata_loaded = 0;                // Did we load the metadata yet?  Do it only once.
-       struct MetaData smi;                    // If we are loading the metadata to compare, put it here.
-       int rule_activated = 0;                 // On each rule, this is set if the compare succeeds and the rule activates.
-       char compare_me[SIZ];                   // On each rule, we will store the field to be compared here.
-       int compare_compound = 0;               // Set to 1 when we are comparing both display name and email address
-       int keep_message = 1;                   // Nonzero to keep the message in the inbox after processing, 0 to delete it.
-       int i;
-
-       syslog(LOG_DEBUG, "inboxrules: processing message #%ld which is higher than %ld, we are in %s", msgnum, ii->lastproc, CC->room.QRname);
-
-       if (ii->num_rules <= 0) {
-               syslog(LOG_DEBUG, "inboxrules: rule set is empty");
-               return;
-       }
-
-       for (i=0; i<ii->num_rules; ++i) {
-               syslog(LOG_DEBUG, "inboxrules: processing rule %d is %s", i, field_keys[ ii->rules[i].compared_field ]);
-               rule_activated = 0;
-
-               // Before doing a field compare, check to see if we have the correct parts of the message in memory.
-
-               switch(ii->rules[i].compared_field) {
-                       // These fields require loading only the top-level headers
-                       case field_from:                // From:
-                       case field_tocc:                // To: or Cc:
-                       case field_subject:             // Subject:
-                       case field_replyto:             // Reply-to:
-                       case field_listid:              // List-ID:
-                       case field_envto:               // Envelope-to:
-                       case field_envfrom:             // Return-path:
-                               if (!headers_loaded) {
-                                       syslog(LOG_DEBUG, "inboxrules: loading headers for message %ld", msgnum);
-                                       msg = CtdlFetchMessage(msgnum, 0);
-                                       if (!msg) {
-                                               return;
-                                       }
-                                       headers_loaded = 1;
-                               }
-                               break;
-                       // These fields are not stored as Citadel headers, and therefore require a full message load.
-                       case field_sender:
-                       case field_resentfrom:
-                       case field_resentto:
-                       case field_xmailer:
-                       case field_xspamflag:
-                       case field_xspamstatus:
-                               if (!body_loaded) {
-                                       syslog(LOG_DEBUG, "inboxrules: loading all of message %ld", msgnum);
-                                       if (msg != NULL) {
-                                               CM_Free(msg);
-                                       }
-                                       msg = CtdlFetchMessage(msgnum, 1);
-                                       if (!msg) {
-                                               return;
-                                       }
-                                       headers_loaded = 1;
-                                       body_loaded = 1;
-                               }
-                               break;
-                       case field_size:
-                               if (!metadata_loaded) {
-                                       syslog(LOG_DEBUG, "inboxrules: loading metadata for message %ld", msgnum);
-                                       GetMetaData(&smi, msgnum);
-                                       metadata_loaded = 1;
-                               }
-                               break;
-                       case field_all:
-                               syslog(LOG_DEBUG, "inboxrules: this is an always-on rule");
-                               break;
-                       default:
-                               syslog(LOG_DEBUG, "inboxrules: unknown rule key");
-               }
-
-               // If the rule involves a field comparison, load the field to be compared.
-               compare_me[0] = 0;
-               compare_compound = 0;
-               switch(ii->rules[i].compared_field) {
-                       case field_from:                // From:
-                               if ( (!IsEmptyStr(msg->cm_fields[erFc822Addr])) && (!IsEmptyStr(msg->cm_fields[erFc822Addr])) ) {
-                                       snprintf(compare_me, sizeof compare_me, "%s|%s",
-                                               msg->cm_fields[eAuthor],
-                                               msg->cm_fields[erFc822Addr]
-                                       );
-                                       compare_compound = 1;           // there will be two fields to compare "name|address"
-                               }
-                               else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
-                                       safestrncpy(compare_me, msg->cm_fields[erFc822Addr], sizeof compare_me);
-                               }
-                               else if (!IsEmptyStr(msg->cm_fields[eAuthor])) {
-                                       safestrncpy(compare_me, msg->cm_fields[eAuthor], sizeof compare_me);
-                               }
-                               break;
-                       case field_tocc:                // To: or Cc:
-                               if (!IsEmptyStr(msg->cm_fields[eRecipient])) {
-                                       safestrncpy(compare_me, msg->cm_fields[eRecipient], sizeof compare_me);
-                               }
-                               if (!IsEmptyStr(msg->cm_fields[eCarbonCopY])) {
-                                       if (!IsEmptyStr(compare_me)) {
-                                               strcat(compare_me, ",");
-                                       }
-                                       safestrncpy(&compare_me[strlen(compare_me)], msg->cm_fields[eCarbonCopY], (sizeof compare_me - strlen(compare_me)));
-                               }
-                               break;
-                       case field_subject:             // Subject:
-                               if (!IsEmptyStr(msg->cm_fields[eMsgSubject])) {
-                                       safestrncpy(compare_me, msg->cm_fields[eMsgSubject], sizeof compare_me);
-                               }
-                               break;
-                       case field_replyto:             // Reply-to:
-                               if (!IsEmptyStr(msg->cm_fields[eReplyTo])) {
-                                       safestrncpy(compare_me, msg->cm_fields[eReplyTo], sizeof compare_me);
-                               }
-                               break;
-                       case field_listid:              // List-ID:
-                               if (!IsEmptyStr(msg->cm_fields[eListID])) {
-                                       safestrncpy(compare_me, msg->cm_fields[eListID], sizeof compare_me);
-                               }
-                               break;
-                       case field_envto:               // Envelope-to:
-                               if (!IsEmptyStr(msg->cm_fields[eenVelopeTo])) {
-                                       safestrncpy(compare_me, msg->cm_fields[eenVelopeTo], sizeof compare_me);
-                               }
-                               break;
-                       case field_envfrom:             // Return-path:
-                               if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
-                                       safestrncpy(compare_me, msg->cm_fields[eMessagePath], sizeof compare_me);
-                               }
-                               break;
-
-                       case field_sender:
-                       case field_resentfrom:
-                       case field_resentto:
-                       case field_xmailer:
-                       case field_xspamflag:
-                       case field_xspamstatus:
-
-                       default:
-                               break;
-               }
-
-               // Message data to compare is loaded, now do something.
-               switch(ii->rules[i].compared_field) {
-                       case field_from:                // From:
-                       case field_tocc:                // To: or Cc:
-                       case field_subject:             // Subject:
-                       case field_replyto:             // Reply-to:
-                       case field_listid:              // List-ID:
-                       case field_envto:               // Envelope-to:
-                       case field_envfrom:             // Return-path:
-                       case field_sender:
-                       case field_resentfrom:
-                       case field_resentto:
-                       case field_xmailer:
-                       case field_xspamflag:
-                       case field_xspamstatus:
-
-                               // For all of the above fields, we can compare the field we've loaded into the buffer.
-                               syslog(LOG_DEBUG, "Value of field to compare is: <%s>", compare_me);
-                               int substring_match = (bmstrcasestr(compare_me, ii->rules[i].compared_value) ? 1 : 0);
-                               int exact_match = 0;
-                               if (compare_compound) {
-                                       char *sep = strchr(compare_me, '|');
-                                       if (sep) {
-                                               *sep = 0;
-                                               exact_match =
-                                                       (strcasecmp(compare_me, ii->rules[i].compared_value) ? 0 : 1)
-                                                       + (strcasecmp(++sep, ii->rules[i].compared_value) ? 0 : 1)
-                                               ;
-                                       }
-                               }
-                               else {
-                                       exact_match = (strcasecmp(compare_me, ii->rules[i].compared_value) ? 0 : 1);
-                               }
-                               syslog(LOG_DEBUG, "substring match: %d", substring_match);
-                               syslog(LOG_DEBUG, "exact match: %d", exact_match);
-                               switch(ii->rules[i].field_compare_op) {
-                                       case fcomp_contains:
-                                       case fcomp_matches:
-                                               rule_activated = substring_match;
-                                               break;
-                                       case fcomp_notcontains:
-                                       case fcomp_notmatches:
-                                               rule_activated = !substring_match;
-                                               break;
-                                       case fcomp_is:
-                                               rule_activated = exact_match;
-                                               break;
-                                       case fcomp_isnot:
-                                               rule_activated = !exact_match;
-                                               break;
-                               }
-                               break;
-
-                       case field_size:
-                               rule_activated = 0;
-                               switch(ii->rules[i].field_compare_op) {
-                                       case scomp_larger:
-                                               rule_activated = ((smi.meta_rfc822_length > ii->rules[i].compared_size) ? 1 : 0);
-                                               break;
-                                       case scomp_smaller:
-                                               rule_activated = ((smi.meta_rfc822_length < ii->rules[i].compared_size) ? 1 : 0);
-                                               break;
-                               }
-                               break;
-                       case field_all:                 // The "all messages" rule ALWAYS triggers
-                               rule_activated = 1;
-                               break;
-                       default:                        // no matches, fall through and do nothing
-                               syslog(LOG_WARNING, "inboxrules: an unknown field comparison was encountered");
-                               rule_activated = 0;
-                               break;
-               }
-
-               // If the rule matched, perform the requested action.
-               if (rule_activated) {
-                       syslog(LOG_DEBUG, "inboxrules: rule activated");
-
-                       // Perform the requested action
-                       switch(ii->rules[i].action) {
-                               case action_keep:
-                                       keep_message = 1;
-                                       break;
-                               case action_discard:
-                                       keep_message = 0;
-                                       break;
-                               case action_reject:
-                                       inbox_do_reject(&ii->rules[i], msg);
-                                       keep_message = 0;
-                                       break;
-                               case action_fileinto:
-                                       keep_message = inbox_do_fileinto(&ii->rules[i], msgnum);
-                                       break;
-                               case action_redirect:
-                                       keep_message = inbox_do_redirect(&ii->rules[i], msgnum);
-                                       break;
-                               case action_vacation:
-                                       inbox_do_vacation(&ii->rules[i], msg);
-                                       keep_message = 1;
-                                       break;
-                       }
-
-                       // Now perform the "final" action (anything other than "stop" means continue)
-                       if (ii->rules[i].final_action == final_stop) {
-                               syslog(LOG_DEBUG, "inboxrules: stop processing");
-                               i = ii->num_rules + 1;                                  // throw us out of scope to stop
-                       }
-
-
-               }
-               else {
-                       syslog(LOG_DEBUG, "inboxrules: rule not activated");
-               }
-       }
-
-       if (msg != NULL) {              // Delete the copy of the message that is currently in memory.  We don't need it anymore.
-               CM_Free(msg);
-       }
-
-       if (!keep_message) {            // Delete the copy of the message that is currently in the inbox, if rules dictated that.
-               syslog(LOG_DEBUG, "inboxrules: delete %ld from inbox", msgnum);
-               CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");                    // we're in the inbox already
-       }
-
-       ii->lastproc = msgnum;          // make note of the last message we processed, so we don't scan the whole inbox again
-}
-
-
-/*
- * A user account is identified as requring inbox processing.
- * Do it.
- */
-void do_inbox_processing_for_user(long usernum) {
-       struct CtdlMessage *msg;
-       struct inboxrules *ii;
-       char roomname[ROOMNAMELEN];
-       char username[64];
-
-       if (CtdlGetUserByNumber(&CC->user, usernum) != 0) {     // grab the user record
-               return;                                         // and bail out if we were given an invalid user
-       }
-
-       strcpy(username, CC->user.fullname);                    // save the user name so we can fetch it later and lock it
-
-       if (CC->user.msgnum_inboxrules <= 0) {
-               return;                                         // this user has no inbox rules
-       }
-
-       msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
-       if (msg == NULL) {
-               return;                                         // config msgnum is set but that message does not exist
-       }
-
-       ii = deserialize_inbox_rules(msg->cm_fields[eMesageText]);
-       CM_Free(msg);
-
-       if (ii == NULL) {
-               return;                                         // config message exists but body is null
-       }
-
-       if (ii->lastproc > CC->user.lastproc_inboxrules) {      // There might be a "last message processed" number left over
-               CC->user.lastproc_inboxrules = ii->lastproc;    // in the ruleset from a previous version.  Use this if it is
-       }                                                       // a higher number.
-       else {
-               ii->lastproc = CC->user.lastproc_inboxrules;
-       }
-
-       long original_lastproc = ii->lastproc;
-       syslog(LOG_DEBUG, "inboxrules: for %s, messages newer than %ld", CC->user.fullname, original_lastproc);
-
-       // Go to the user's inbox room and process all new messages
-       snprintf(roomname, sizeof roomname, "%010ld.%s", usernum, MAILROOM);
-       if (CtdlGetRoom(&CC->room, roomname) == 0) {
-               CtdlForEachMessage(MSGS_GT, ii->lastproc, NULL, NULL, NULL, inbox_do_msg, (void *) ii);
-       }
-
-       // Record the number of the last message we processed
-       if (ii->lastproc > original_lastproc) {
-               CtdlGetUserLock(&CC->user, username);
-               CC->user.lastproc_inboxrules = ii->lastproc;    // Avoid processing the entire inbox next time
-               CtdlPutUserLock(&CC->user);
-       }
-
-       // And free the memory.
-       free_inbox_rules(ii);
-}
-
-
-/*
- * Here is an array of users (by number) who have received messages in their inbox and may require processing.
- */
-long *users_requiring_inbox_processing = NULL;
-int num_urip = 0;
-int num_urip_alloc = 0;
-
-
-/*
- * Perform inbox processing for all rooms which require it
- */
-void perform_inbox_processing(void) {
-       int i = 0;
-
-       if (num_urip == 0) {
-               return;                                                                                 // no action required
-       }
-
-       for (i=0; i<num_urip; ++i) {
-               do_inbox_processing_for_user(users_requiring_inbox_processing[i]);
-       }
-
-       free(users_requiring_inbox_processing);
-       users_requiring_inbox_processing = NULL;
-       num_urip = 0;
-       num_urip_alloc = 0;
-}
-
-
-/*
- * This function is called after a message is saved to a room.
- * If it's someone's inbox, we have to check for inbox rules
- */
-int serv_inboxrules_roomhook(struct ctdlroom *room) {
-       int i = 0;
-
-       // Is this someone's inbox?
-       if (!strcasecmp(&room->QRname[11], MAILROOM)) {
-               long usernum = atol(room->QRname);
-               if (usernum > 0) {
-
-                       // first check to see if this user is already on the list
-                       if (num_urip > 0) {
-                               for (i=0; i<=num_urip; ++i) {
-                                       if (users_requiring_inbox_processing[i] == usernum) {           // already on the list!
-                                               return(0);
-                                       }
-                               }
-                       }
-
-                       // make room if we need to
-                       if (num_urip_alloc == 0) {
-                               num_urip_alloc = 100;
-                               users_requiring_inbox_processing = malloc(sizeof(long) * num_urip_alloc);
-                       }
-                       else if (num_urip >= num_urip_alloc) {
-                               num_urip_alloc += 100;
-                               users_requiring_inbox_processing = realloc(users_requiring_inbox_processing, (sizeof(long) * num_urip_alloc));
-                       }
-                       
-                       // now add the user to the list
-                       users_requiring_inbox_processing[num_urip++] = usernum;
-               }
-       }
-
-       // No errors are possible from this function.
-       return(0);
-}
-
-
-
-/*
- * Get InBox Rules
- *
- * This is a client-facing function which fetches the user's inbox rules -- it omits all lines containing anything other than a rule.
- * 
- * hmmmmm ... should we try to rebuild this in terms of deserialize_inbox_rules() instread?
- */
-void cmd_gibr(char *argbuf) {
-
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       cprintf("%d inbox rules for %s\n", LISTING_FOLLOWS, CC->user.fullname);
-
-       struct CtdlMessage *msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
-       if (msg != NULL) {
-               if (!CM_IsEmpty(msg, eMesageText)) {
-                       char *token; 
-                       char *rest = msg->cm_fields[eMesageText];
-                       while ((token = strtok_r(rest, "\n", &rest))) {
-
-                               // for backwards compatibility, "# WEBCIT_RULE" is an alias for "rule" 
-                               if (!strncasecmp(token, "# WEBCIT_RULE|", 14)) {
-                                       strcpy(token, "rule|"); 
-                                       strcpy(&token[5], &token[14]);
-                               }
-
-                               // Output only lines containing rules.
-                               if (!strncasecmp(token, "rule|", 5)) {
-                                       cprintf("%s\n", token); 
-                               }
-                               else {
-                                       cprintf("# invalid rule found : %s\n", token);
-                               }
-                       }
-               }
-               CM_Free(msg);
-       }
-       cprintf("000\n");
-}
-
-
-/*
- * Rewrite the rule set to disk after it has been modified
- * Called by cmd_pibr()
- * Returns the msgnum of the new rules message
- */
-void rewrite_rules_to_disk(const char *new_config) {
-       long old_msgnum = CC->user.msgnum_inboxrules;
-       char userconfigroomname[ROOMNAMELEN];
-       CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &CC->user, USERCONFIGROOM);
-       long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, new_config, FMT_RFC822, "inbox rules configuration");
-       CtdlGetUserLock(&CC->user, CC->curr_user);
-       CC->user.msgnum_inboxrules = new_msgnum;                // Now we know where to get the rules next time
-       CC->user.lastproc_inboxrules = new_msgnum;              // Avoid processing the entire inbox next time
-       CtdlPutUserLock(&CC->user);
-       if (old_msgnum > 0) {
-               syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
-               CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
-       }
-}
-
-
-/*
- * Put InBox Rules
- *
- * User transmits the new inbox rules for the account.  They are inserted into the account, replacing the ones already there.
- */
-void cmd_pibr(char *argbuf) {
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       unbuffer_output();
-       cprintf("%d send new rules\n", SEND_LISTING);
-       char *newrules = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
-       StrBuf *NewConfig = NewStrBufPlain("Content-type: application/x-citadel-sieve-config; charset=UTF-8\nContent-transfer-encoding: 8bit\n\n", -1);
-
-       char *token; 
-       char *rest = newrules;
-       while ((token = strtok_r(rest, "\n", &rest))) {
-               // Accept only lines containing rules
-               if (!strncasecmp(token, "rule|", 5)) {
-                       StrBufAppendBufPlain(NewConfig, token, -1, 0);
-                       StrBufAppendBufPlain(NewConfig, HKEY("\n"), 0);
-               }
-       }
-       free(newrules);
-       rewrite_rules_to_disk(ChrPtr(NewConfig));
-       FreeStrBuf(&NewConfig);
-}
-
-
-CTDL_MODULE_INIT(sieve)
-{
-       if (!threading)
-       {
-               CtdlRegisterProtoHook(cmd_gibr, "GIBR", "Get InBox Rules");
-               CtdlRegisterProtoHook(cmd_pibr, "PIBR", "Put InBox Rules");
-               CtdlRegisterRoomHook(serv_inboxrules_roomhook);
-               CtdlRegisterSessionHook(perform_inbox_processing, EVT_HOUSE, PRIO_HOUSE + 10);
-       }
-       
-        /* return our module name for the log */
-       return "inboxrules";
-}
diff --git a/citadel/modules/inetcfg/.gitignore b/citadel/modules/inetcfg/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/inetcfg/serv_inetcfg.c b/citadel/modules/inetcfg/serv_inetcfg.c
deleted file mode 100644 (file)
index 814659d..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * This module handles the loading/saving and maintenance of the
- * system's Internet configuration.  It's not an optional component; I
- * wrote it as a module merely to keep things as clean and loosely coupled
- * as possible.
- *
- * Copyright (c) 1987-2021 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "genstamp.h"
-#include "domain.h"
-#include "ctdl_module.h"
-
-
-void inetcfg_setTo(struct CtdlMessage *msg) {
-       char *conf;
-       char buf[SIZ];
-       
-       if (CM_IsEmpty(msg, eMesageText)) return;
-       conf = strdup(msg->cm_fields[eMesageText]);
-
-       if (conf != NULL) {
-               do {
-                       extract_token(buf, conf, 0, '\n', sizeof buf);
-                       strcpy(conf, &conf[strlen(buf)+1]);
-               } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
-
-               if (inetcfg != NULL) free(inetcfg);
-               inetcfg = conf;
-       }
-}
-
-
-/*
- * This handler detects changes being made to the system's Internet
- * configuration.
- */
-int inetcfg_aftersave(struct CtdlMessage *msg, struct recptypes *recp) {
-       char *ptr;
-       int linelen;
-
-       /* If this isn't the configuration room, or if this isn't a MIME
-        * message, don't bother.
-        */
-       if ((msg->cm_fields[eOriginalRoom]) && (strcasecmp(msg->cm_fields[eOriginalRoom], SYSCONFIGROOM))) {
-               return(0);
-       }
-       if (msg->cm_format_type != 4) {
-               return(0);
-       }
-
-       ptr = msg->cm_fields[eMesageText];
-       while (ptr != NULL) {
-       
-               linelen = strcspn(ptr, "\n");
-               if (linelen == 0) {
-                       return(0);      /* end of headers */    
-               }
-               
-               if (!strncasecmp(ptr, "Content-type: ", 14)) {
-                       if (!strncasecmp(&ptr[14], INTERNETCFG, strlen(INTERNETCFG))) {
-                               inetcfg_setTo(msg);     /* changing configs */
-                       }
-               }
-
-               ptr = strchr((char *)ptr, '\n');
-               if (ptr != NULL) ++ptr;
-       }
-
-       return(0);
-}
-
-
-void inetcfg_init_backend(long msgnum, void *userdata) {
-       struct CtdlMessage *msg;
-
-               msg = CtdlFetchMessage(msgnum, 1);
-               if (msg != NULL) {
-               inetcfg_setTo(msg);
-                       CM_Free(msg);
-       }
-}
-
-
-void inetcfg_init(void) {
-       syslog(LOG_DEBUG, "EVQ: called inetcfg_init()");
-       if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
-               return;
-       }
-       CtdlForEachMessage(MSGS_LAST, 1, NULL, INTERNETCFG, NULL, inetcfg_init_backend, NULL);
-}
-
-
-/*****************************************************************************/
-/*                      MODULE INITIALIZATION STUFF                          */
-/*****************************************************************************/
-
-
-CTDL_MODULE_INIT(inetcfg)
-{
-       if (!threading)
-       {
-               CtdlRegisterMessageHook(inetcfg_aftersave, EVT_AFTERSAVE);
-               inetcfg_init();
-       }
-       
-       /* return our module name for the log */
-       return "inetcfg";
-}
diff --git a/citadel/modules/instmsg/.gitignore b/citadel/modules/instmsg/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/instmsg/serv_instmsg.c b/citadel/modules/instmsg/serv_instmsg.c
deleted file mode 100644 (file)
index b54276c..0000000
+++ /dev/null
@@ -1,586 +0,0 @@
-/*
- * This module handles instant messaging between users.
- * 
- * Copyright (c) 1987-2020 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "serv_instmsg.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "msgbase.h"
-#include "user_ops.h"
-#include "ctdl_module.h"
-
-struct imlog {
-       struct imlog *next;
-       long usernums[2];
-       char usernames[2][128];
-       time_t lastmsg;
-       int last_serial;
-       StrBuf *conversation;
-};
-
-struct imlog *imlist = NULL;
-
-/*
- * This function handles the logging of instant messages to disk.
- */
-void log_instant_message(struct CitContext *me, struct CitContext *them, char *msgtext, int serial_number)
-{
-       long usernums[2];
-       long t;
-       struct imlog *iptr = NULL;
-       struct imlog *this_im = NULL;
-       
-       memset(usernums, 0, sizeof usernums);
-       usernums[0] = me->user.usernum;
-       usernums[1] = them->user.usernum;
-
-       /* Always put the lower user number first, so we can use the array as a hash value which
-        * represents a pair of users.  For a broadcast message one of the users will be 0.
-        */
-       if (usernums[0] > usernums[1]) {
-               t = usernums[0];
-               usernums[0] = usernums[1];
-               usernums[1] = t;
-       }
-
-       begin_critical_section(S_IM_LOGS);
-
-       /* Look for an existing conversation in the hash table.
-        * If not found, create a new one.
-        */
-
-       this_im = NULL;
-       for (iptr = imlist; iptr != NULL; iptr = iptr->next) {
-               if ((iptr->usernums[0] == usernums[0]) && (iptr->usernums[1] == usernums[1])) {
-                       /* Existing conversation */
-                       this_im = iptr;
-               }
-       }
-       if (this_im == NULL) {
-               /* New conversation */
-               this_im = malloc(sizeof(struct imlog));
-               memset(this_im, 0, sizeof (struct imlog));
-               this_im->usernums[0] = usernums[0];
-               this_im->usernums[1] = usernums[1];
-               /* usernames[] and usernums[] might not be in the same order.  This is not an error. */
-               if (me) {
-                       safestrncpy(this_im->usernames[0], me->user.fullname, sizeof this_im->usernames[0]);
-               }
-               if (them) {
-                       safestrncpy(this_im->usernames[1], them->user.fullname, sizeof this_im->usernames[1]);
-               }
-               this_im->conversation = NewStrBuf();
-               this_im->next = imlist;
-               imlist = this_im;
-               StrBufAppendBufPlain(this_im->conversation, HKEY("<html><body>\r\n"), 0);
-       }
-
-       /* Since it's possible for this function to get called more than once if a user is logged
-        * in on multiple sessions, we use the message's serial number to keep track of whether
-        * we've already logged it.
-        */
-       if (this_im->last_serial != serial_number)
-       {
-               this_im->lastmsg = time(NULL);          /* Touch the timestamp so we know when to flush */
-               this_im->last_serial = serial_number;
-               StrBufAppendBufPlain(this_im->conversation, HKEY("<p><b>"), 0);
-               StrBufAppendBufPlain(this_im->conversation, me->user.fullname, -1, 0);
-               StrBufAppendBufPlain(this_im->conversation, HKEY(":</b> "), 0);
-               StrEscAppend(this_im->conversation, NULL, msgtext, 0, 0);
-               StrBufAppendBufPlain(this_im->conversation, HKEY("</p>\r\n"), 0);
-       }
-       end_critical_section(S_IM_LOGS);
-}
-
-
-/*
- * Delete any remaining instant messages
- */
-void delete_instant_messages(void) {
-       struct ExpressMessage *ptr;
-
-       begin_critical_section(S_SESSION_TABLE);
-       while (CC->FirstExpressMessage != NULL) {
-               ptr = CC->FirstExpressMessage->next;
-               if (CC->FirstExpressMessage->text != NULL)
-                       free(CC->FirstExpressMessage->text);
-               free(CC->FirstExpressMessage);
-               CC->FirstExpressMessage = ptr;
-       }
-       end_critical_section(S_SESSION_TABLE);
-}
-
-
-/*
- * Retrieve instant messages
- */
-void cmd_gexp(char *argbuf) {
-       struct ExpressMessage *ptr;
-
-       if (CC->FirstExpressMessage == NULL) {
-               cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
-               return;
-       }
-
-       begin_critical_section(S_SESSION_TABLE);
-       ptr = CC->FirstExpressMessage;
-       CC->FirstExpressMessage = CC->FirstExpressMessage->next;
-       end_critical_section(S_SESSION_TABLE);
-
-       cprintf("%d %d|%ld|%d|%s|%s|%s\n",
-               LISTING_FOLLOWS,
-               ((ptr->next != NULL) ? 1 : 0),          /* more msgs? */
-               (long)ptr->timestamp,                   /* time sent */
-               ptr->flags,                             /* flags */
-               ptr->sender,                            /* sender of msg */
-               CtdlGetConfigStr("c_nodename"),         /* static for now (and possibly deprecated) */
-               ptr->sender_email                       /* email or jid of sender */
-       );
-
-       if (ptr->text != NULL) {
-               memfmout(ptr->text, "\n");
-               free(ptr->text);
-       }
-
-       cprintf("000\n");
-       free(ptr);
-}
-
-
-/*
- * Asynchronously deliver instant messages
- */
-void cmd_gexp_async(void) {
-
-       /* Only do this if the session can handle asynchronous protocol */
-       if (CC->is_async == 0) return;
-
-       /* And don't do it if there's nothing to send. */
-       if (CC->FirstExpressMessage == NULL) return;
-
-       cprintf("%d instant msg\n", ASYNC_MSG + ASYNC_GEXP);
-}
-
-
-/*
- * Back end support function for send_instant_message() and company
- */
-void add_xmsg_to_context(struct CitContext *ccptr, struct ExpressMessage *newmsg) 
-{
-       struct ExpressMessage *findend;
-
-       if (ccptr->FirstExpressMessage == NULL) {
-               ccptr->FirstExpressMessage = newmsg;
-       }
-       else {
-               findend = ccptr->FirstExpressMessage;
-               while (findend->next != NULL) {
-                       findend = findend->next;
-               }
-               findend->next = newmsg;
-       }
-
-       /* If the target context is a session which can handle asynchronous
-        * messages, go ahead and set the flag for that.
-        */
-       set_async_waiting(ccptr);
-}
-
-
-/* 
- * This is the back end to the instant message sending function.  
- * Returns the number of users to which the message was sent.
- * Sending a zero-length message tests for recipients without sending messages.
- */
-int send_instant_message(char *lun, char *lem, char *x_user, char *x_msg)
-{
-       int message_sent = 0;           /* number of successful sends */
-       struct CitContext *ccptr;
-       struct ExpressMessage *newmsg = NULL;
-       int do_send = 0;                /* 1 = send message; 0 = only check for valid recipient */
-       static int serial_number = 0;   /* this keeps messages from getting logged twice */
-
-       if (!IsEmptyStr(x_msg)) {
-               do_send = 1;
-       }
-
-       /* find the target user's context and append the message */
-       begin_critical_section(S_SESSION_TABLE);
-       ++serial_number;
-       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
-
-               if ( ((!strcasecmp(ccptr->user.fullname, x_user))
-                   || (!strcasecmp(x_user, "broadcast")))
-                   && (ccptr->can_receive_im)
-                   && ((ccptr->disable_exp == 0)
-                   || (CC->user.axlevel >= AxAideU)) ) {
-                       if (do_send) {
-                               newmsg = (struct ExpressMessage *) malloc(sizeof (struct ExpressMessage));
-                               memset(newmsg, 0, sizeof (struct ExpressMessage));
-                               time(&(newmsg->timestamp));
-                               safestrncpy(newmsg->sender, lun, sizeof newmsg->sender);
-                               safestrncpy(newmsg->sender_email, lem, sizeof newmsg->sender_email);
-                               if (!strcasecmp(x_user, "broadcast")) {
-                                       newmsg->flags |= EM_BROADCAST;
-                               }
-                               newmsg->text = strdup(x_msg);
-
-                               add_xmsg_to_context(ccptr, newmsg);
-
-                               /* and log it ... */
-                               if (ccptr != CC) {
-                                       log_instant_message(CC, ccptr, newmsg->text, serial_number);
-                               }
-                       }
-                       ++message_sent;
-               }
-       }
-       end_critical_section(S_SESSION_TABLE);
-       return (message_sent);
-}
-
-
-/*
- * send instant messages
- */
-void cmd_sexp(char *argbuf)
-{
-       int message_sent = 0;
-       char x_user[USERNAME_SIZE];
-       char x_msg[1024];
-       char *lem;
-       char *x_big_msgbuf = NULL;
-
-       if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
-               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
-               return;
-       }
-
-       lem = CC->cs_principal_id;
-
-       extract_token(x_user, argbuf, 0, '|', sizeof x_user);
-       extract_token(x_msg, argbuf, 1, '|', sizeof x_msg);
-
-       if (!x_user[0]) {
-               cprintf("%d You were not previously paged.\n", ERROR + NO_SUCH_USER);
-               return;
-       }
-       if ((!strcasecmp(x_user, "broadcast")) && (CC->user.axlevel < AxAideU)) {
-               cprintf("%d Higher access required to send a broadcast.\n",
-                       ERROR + HIGHER_ACCESS_REQUIRED);
-               return;
-       }
-       /* This loop handles text-transfer pages */
-       if (!strcmp(x_msg, "-")) {
-               message_sent = PerformXmsgHooks(CC->user.fullname, lem, x_user, "");
-               if (message_sent == 0) {
-                       if (CtdlGetUser(NULL, x_user))
-                               cprintf("%d '%s' does not exist.\n",
-                                               ERROR + NO_SUCH_USER, x_user);
-                       else
-                               cprintf("%d '%s' is not logged in "
-                                               "or is not accepting pages.\n",
-                                               ERROR + RESOURCE_NOT_OPEN, x_user);
-                       return;
-               }
-               unbuffer_output();
-               cprintf("%d Transmit message (will deliver to %d users)\n",
-                       SEND_LISTING, message_sent);
-               x_big_msgbuf = malloc(SIZ);
-               memset(x_big_msgbuf, 0, SIZ);
-               while (client_getln(x_msg, sizeof x_msg) >= 0 && strcmp(x_msg, "000")) {
-                       x_big_msgbuf = realloc(x_big_msgbuf,
-                              strlen(x_big_msgbuf) + strlen(x_msg) + 4);
-                       if (!IsEmptyStr(x_big_msgbuf))
-                          if (x_big_msgbuf[strlen(x_big_msgbuf)] != '\n')
-                               strcat(x_big_msgbuf, "\n");
-                       strcat(x_big_msgbuf, x_msg);
-               }
-               PerformXmsgHooks(CC->user.fullname, lem, x_user, x_big_msgbuf);
-               free(x_big_msgbuf);
-
-               /* This loop handles inline pages */
-       } else {
-               message_sent = PerformXmsgHooks(CC->user.fullname, lem, x_user, x_msg);
-
-               if (message_sent > 0) {
-                       if (!IsEmptyStr(x_msg)) {
-                               cprintf("%d Message sent", CIT_OK);
-                       }
-                       else {
-                               cprintf("%d Ok to send message", CIT_OK);
-                       }
-                       if (message_sent > 1) {
-                               cprintf(" to %d users", message_sent);
-                       }
-                       cprintf(".\n");
-               } else {
-                       if (CtdlGetUser(NULL, x_user)) {
-                               cprintf("%d '%s' does not exist.\n", ERROR + NO_SUCH_USER, x_user);
-                       }
-                       else {
-                               cprintf("%d '%s' is not logged in or is not accepting instant messages.\n",
-                                               ERROR + RESOURCE_NOT_OPEN, x_user);
-                       }
-               }
-
-
-       }
-}
-
-
-/*
- * Enter or exit paging-disabled mode
- */
-void cmd_dexp(char *argbuf)
-{
-       int new_state;
-
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       new_state = extract_int(argbuf, 0);
-       if ((new_state == 0) || (new_state == 1)) {
-               CC->disable_exp = new_state;
-       }
-
-       cprintf("%d %d\n", CIT_OK, CC->disable_exp);
-}
-
-
-/*
- * Request client termination
- */
-void cmd_reqt(char *argbuf) {
-       struct CitContext *ccptr;
-       int sessions = 0;
-       int which_session;
-       struct ExpressMessage *newmsg;
-
-       if (CtdlAccessCheck(ac_aide)) return;
-       which_session = extract_int(argbuf, 0);
-
-       begin_critical_section(S_SESSION_TABLE);
-       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
-               if ((ccptr->cs_pid == which_session) || (which_session == 0)) {
-
-                       newmsg = (struct ExpressMessage *)
-                               malloc(sizeof (struct ExpressMessage));
-                       memset(newmsg, 0,
-                               sizeof (struct ExpressMessage));
-                       time(&(newmsg->timestamp));
-                       safestrncpy(newmsg->sender, CC->user.fullname,
-                                   sizeof newmsg->sender);
-                       newmsg->flags |= EM_GO_AWAY;
-                       newmsg->text = strdup("Automatic logoff requested.");
-
-                       add_xmsg_to_context(ccptr, newmsg);
-                       ++sessions;
-
-               }
-       }
-       end_critical_section(S_SESSION_TABLE);
-       cprintf("%d Sent termination request to %d sessions.\n", CIT_OK, sessions);
-}
-
-
-/*
- * This is the back end for flush_conversations_to_disk()
- * At this point we've isolated a single conversation (struct imlog)
- * and are ready to write it to disk.
- */
-void flush_individual_conversation(struct imlog *im) {
-       struct CtdlMessage *msg;
-       long msgnum = 0;
-       char roomname[ROOMNAMELEN];
-       StrBuf *MsgBuf, *FullMsgBuf;
-
-       StrBufAppendBufPlain(im->conversation, HKEY(
-               "</body>\r\n"
-               "</html>\r\n"
-               ), 0
-       );
-
-       MsgBuf = StrBufRFC2047encodeMessage(im->conversation);
-       FlushStrBuf(im->conversation);
-       FullMsgBuf = NewStrBufPlain(NULL, StrLength(im->conversation) + 100);
-
-       StrBufAppendBufPlain(FullMsgBuf, HKEY(
-               "Content-type: text/html; charset=UTF-8\r\n"
-               "Content-Transfer-Encoding: quoted-printable\r\n"
-               "\r\n"
-               ), 0
-       );
-       StrBufAppendBuf (FullMsgBuf, MsgBuf, 0);
-       FreeStrBuf(&MsgBuf);
-
-       msg = malloc(sizeof(struct CtdlMessage));
-       memset(msg, 0, sizeof(struct CtdlMessage));
-       msg->cm_magic = CTDLMESSAGE_MAGIC;
-       msg->cm_anon_type = MES_NORMAL;
-       msg->cm_format_type = FMT_RFC822;
-       if (!IsEmptyStr(im->usernames[0])) {
-               CM_SetField(msg, eAuthor, im->usernames[0], strlen(im->usernames[0]));
-       } else {
-               CM_SetField(msg, eAuthor, HKEY("Citadel"));
-       }
-       if (!IsEmptyStr(im->usernames[1])) {
-               CM_SetField(msg, eRecipient, im->usernames[1], strlen(im->usernames[1]));
-       }
-
-       CM_SetField(msg, eOriginalRoom, HKEY(PAGELOGROOM));
-       CM_SetAsFieldSB(msg, eMesageText, &FullMsgBuf); /* we own this memory now */
-
-       /* Start with usernums[1] because it's guaranteed to be higher than usernums[0],
-        * so if there's only one party, usernums[0] will be zero but usernums[1] won't.
-        * Create the room if necessary.  Note that we create as a type 5 room rather
-        * than 4, which indicates that it's a personal room but we've already supplied
-        * the namespace prefix.
-        *
-        * In the unlikely event that usernums[1] is zero, a room with an invalid namespace
-        * prefix will be created.  That's ok because the auto-purger will clean it up later.
-        */
-       snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[1], PAGELOGROOM);
-       CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS);
-       msgnum = CtdlSubmitMsg(msg, NULL, roomname);
-       CM_Free(msg);
-
-       /* If there is a valid user number in usernums[0], save a copy for them too. */
-       if (im->usernums[0] > 0) {
-               snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[0], PAGELOGROOM);
-               CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS);
-               CtdlSaveMsgPointerInRoom(roomname, msgnum, 0, NULL);
-       }
-
-       /* Finally, if we're logging instant messages globally, do that now. */
-       if (!IsEmptyStr(CtdlGetConfigStr("c_logpages"))) {
-               CtdlCreateRoom(CtdlGetConfigStr("c_logpages"), 3, "", 0, 1, 1, VIEW_BBS);
-               CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_logpages"), msgnum, 0, NULL);
-       }
-
-}
-
-/*
- * Locate instant message conversations which have gone idle
- * (or, if the server is shutting down, locate *all* conversations)
- * and flush them to disk (in the participants' log rooms, etc.)
- */
-void flush_conversations_to_disk(time_t if_older_than) {
-
-       struct imlog *flush_these = NULL;
-       struct imlog *dont_flush_these = NULL;
-       struct imlog *imptr = NULL;
-       struct CitContext *nptr;
-       int nContexts, i;
-
-       nptr = CtdlGetContextArray(&nContexts) ;        /* Make a copy of the current wholist */
-
-       begin_critical_section(S_IM_LOGS);
-       while (imlist)
-       {
-               imptr = imlist;
-               imlist = imlist->next;
-
-               /* For a two party conversation, if one party has logged out, force flush. */
-               if (nptr) {
-                       int user0_is_still_online = 0;
-                       int user1_is_still_online = 0;
-                       for (i=0; i<nContexts; i++)  {
-                               if (nptr[i].user.usernum == imptr->usernums[0]) ++user0_is_still_online;
-                               if (nptr[i].user.usernum == imptr->usernums[1]) ++user1_is_still_online;
-                       }
-                       if (imptr->usernums[0] != imptr->usernums[1]) {         /* two party conversation */
-                               if ((!user0_is_still_online) || (!user1_is_still_online)) {
-                                       imptr->lastmsg = 0L;    /* force flush */
-                               }
-                       }
-                       else {          /* one party conversation (yes, people do IM themselves) */
-                               if (!user0_is_still_online) {
-                                       imptr->lastmsg = 0L;    /* force flush */
-                               }
-                       }
-               }
-
-               /* Now test this conversation to see if it qualifies for flushing. */
-               if ((time(NULL) - imptr->lastmsg) > if_older_than)
-               {
-                       /* This conversation qualifies.  Move it to the list of ones to flush. */
-                       imptr->next = flush_these;
-                       flush_these = imptr;
-               }
-               else  {
-                       /* Move it to the list of ones not to flush. */
-                       imptr->next = dont_flush_these;
-                       dont_flush_these = imptr;
-               }
-       }
-       imlist = dont_flush_these;
-       end_critical_section(S_IM_LOGS);
-       free(nptr);
-
-       /* We are now outside of the critical section, and we are the only thread holding a
-        * pointer to a linked list of conversations to be flushed to disk.
-        */
-       while (flush_these) {
-
-               flush_individual_conversation(flush_these);     /* This will free the string buffer */
-               imptr = flush_these;
-               flush_these = flush_these->next;
-               free(imptr);
-       }
-}
-
-
-
-void instmsg_timer(void) {
-       flush_conversations_to_disk(300);       /* Anything that hasn't peeped in more than 5 minutes */
-}
-
-void instmsg_shutdown(void) {
-       flush_conversations_to_disk(0);         /* Get it ALL onto disk NOW. */
-}
-
-CTDL_MODULE_INIT(instmsg)
-{
-       if (!threading)
-       {
-               CtdlRegisterProtoHook(cmd_gexp, "GEXP", "Get instant messages");
-               CtdlRegisterProtoHook(cmd_sexp, "SEXP", "Send an instant message");
-               CtdlRegisterProtoHook(cmd_dexp, "DEXP", "Disable instant messages");
-               CtdlRegisterProtoHook(cmd_reqt, "REQT", "Request client termination");
-               CtdlRegisterSessionHook(cmd_gexp_async, EVT_ASYNC, PRIO_ASYNC + 1);
-               CtdlRegisterSessionHook(delete_instant_messages, EVT_STOP, PRIO_STOP + 1);
-               CtdlRegisterXmsgHook(send_instant_message, XMSG_PRI_LOCAL);
-               CtdlRegisterSessionHook(instmsg_timer, EVT_TIMER, PRIO_CLEANUP + 400);
-               CtdlRegisterSessionHook(instmsg_shutdown, EVT_SHUTDOWN, PRIO_SHUTDOWN + 10);
-       }
-       
-       /* return our module name for the log */
-       return "instmsg";
-}
diff --git a/citadel/modules/instmsg/serv_instmsg.h b/citadel/modules/instmsg/serv_instmsg.h
deleted file mode 100644 (file)
index a1654a0..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#include "ctdl_module.h"
-
-void ChatUnloadingTest(void);
-void allwrite (char *cmdbuf, int flag, char *username);
-CitContext *find_context (char **unstr);
-void cmd_pexp (char *argbuf); /* arg unused */
-void cmd_sexp (char *argbuf);
-void delete_instant_messages(void);
-void cmd_gexp(char *);
-int send_instant_message(char *, char *, char *, char *);
diff --git a/citadel/modules/listdeliver/serv_listdeliver.c b/citadel/modules/listdeliver/serv_listdeliver.c
deleted file mode 100644 (file)
index 043d9c7..0000000
+++ /dev/null
@@ -1,250 +0,0 @@
-// This module delivers messages to mailing lists.
-//
-// Copyright (c) 2002-2022 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//  
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <dirent.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "clientsocket.h"
-#include "ctdl_module.h"
-
-int doing_listdeliver = 0;
-
-
-// data passed back and forth between listdeliver_do_msg() and listdeliver_sweep_room()
-struct lddata {
-       long msgnum;            // number of most recent message processed
-       char *netconf;          // netconfig for this room (contains the recipients)
-};
-
-
-
-void listdeliver_do_msg(long msgnum, void *userdata) {
-       struct lddata *ld = (struct lddata *) userdata;
-       if (!ld) return;
-       char buf[SIZ];
-       char *ch;
-       char bounce_to[256];
-       int i = 0;
-
-       ld->msgnum = msgnum;
-       if (msgnum <= 0) return;
-
-       struct CtdlMessage *TheMessage = CtdlFetchMessage(msgnum, 1);
-       if (!TheMessage) return;
-
-       // If the subject line does not contain the name of the room, add it now.
-       if (!bmstrcasestr(TheMessage->cm_fields[eMsgSubject], CC->room.QRname)) {
-               snprintf(buf, sizeof buf, "[%s] %s", CC->room.QRname, TheMessage->cm_fields[eMsgSubject]);
-               CM_SetField(TheMessage, eMsgSubject, buf, strlen(buf));
-       }
-
-       // From: should be set to the list address because doing otherwise makes DKIM parsers angry.
-       // Reply-to: should be set to the list address so that replies come to the list.
-       snprintf(buf, sizeof buf, "room_%s@%s", CC->room.QRname, CtdlGetConfigStr("c_fqdn"));
-       for (ch=buf; *ch; ++ch) {
-               if (isspace(*ch)) *ch = '_';
-       }
-       CM_SetField(TheMessage, erFc822Addr, buf, strlen(buf));
-       CM_SetField(TheMessage, eReplyTo, buf, strlen(buf));
-
-       // With that out of the way, let's figure out who this message needs to be sent to.
-       char *recipients = malloc(strlen(ld->netconf));
-       if (recipients) {
-               recipients[0] = 0;
-
-               int config_lines = num_tokens(ld->netconf, '\n');
-               for (i=0; i<config_lines; ++i) {
-                       extract_token(buf, ld->netconf, i, '\n', sizeof buf);
-                       if (!strncasecmp(buf, "listrecp|", 9)) {
-                               if (recipients[0] != 0) {
-                                       strcat(recipients, ",");
-                               }
-                               strcat(recipients, &buf[9]);
-                       }
-                       if (!strncasecmp(buf, "digestrecp|", 11)) {
-                               if (recipients[0] != 0) {
-                                       strcat(recipients, ",");
-                               }
-                               strcat(recipients, &buf[11]);
-                       }
-               }
-
-               // Where do we want bounces and other noise to be sent?  Certainly not to the list members!
-               snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", CtdlGetConfigStr("c_fqdn"));
-
-               // Now submit the message
-               struct recptypes *valid = validate_recipients(recipients, NULL, 0);
-               if (valid) {
-                       valid->bounce_to = strdup(bounce_to);
-                       valid->envelope_from = strdup(bounce_to);
-                       valid->sending_room = strdup(CC->room.QRname);
-                       CtdlSubmitMsg(TheMessage, valid, "");
-                       free_recipients(valid);
-               }
-       }
-       CM_Free(TheMessage);
-}
-
-
-// Sweep through one room looking for mailing list deliveries to do
-void listdeliver_sweep_room(char *roomname) {
-       char *netconfig = NULL;
-       char *newnetconfig = NULL;
-       long lastsent = 0;
-       char buf[SIZ];
-       int config_lines;
-       int i;
-       int number_of_messages_processed = 0;
-       int number_of_recipients = 0;
-       struct lddata ld;
-
-       if (CtdlGetRoom(&CC->room, roomname)) {
-               syslog(LOG_DEBUG, "listdeliver: no room <%s>", roomname);
-               return;
-       }
-
-        netconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
-        if (!netconfig) {
-               return;                         // no netconfig, no processing, no problem
-       }
-
-       config_lines = num_tokens(netconfig, '\n');
-       for (i=0; i<config_lines; ++i) {
-               extract_token(buf, netconfig, i, '\n', sizeof buf);
-
-               if (!strncasecmp(buf, "lastsent|", 9)) {
-                       lastsent = atol(&buf[9]);
-               }
-               else if ( (!strncasecmp(buf, "listrecp|", 9)) || (!strncasecmp(buf, "digestrecp|", 11)) ) {
-                       ++number_of_recipients;
-               }
-       }
-
-       if (number_of_recipients > 0) {
-               syslog(LOG_DEBUG, "listdeliver: processing new messages in <%s> for <%d> recipients", CC->room.QRname, number_of_recipients);
-               ld.netconf = netconfig;
-               number_of_messages_processed = CtdlForEachMessage(MSGS_GT, lastsent, NULL, NULL, NULL, listdeliver_do_msg, &ld);
-               syslog(LOG_INFO, "listdeliver: processed <%d> messages in <%s> for <%d> recipients", number_of_messages_processed, CC->room.QRname, number_of_recipients);
-       
-               if (number_of_messages_processed > 0) {
-                       syslog(LOG_DEBUG, "listdeliver: new lastsent is %ld", ld.msgnum);
-
-                       // Update this room's netconfig with the updated lastsent
-                       begin_critical_section(S_NETCONFIGS);
-                       netconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
-                       if (!netconfig) {
-                               netconfig = strdup("");
-                       }
-
-                       // The new netconfig begins with the new lastsent directive
-                       newnetconfig = malloc(strlen(netconfig) + 1024);
-                       sprintf(newnetconfig, "lastsent|%ld\n", ld.msgnum);
-
-                       // And then we append all of the old netconfig, minus the old lastsent.  Also omit blank lines.
-                       config_lines = num_tokens(netconfig, '\n');
-                       for (i=0; i<config_lines; ++i) {
-                               extract_token(buf, netconfig, i, '\n', sizeof buf);
-                               if ( (!IsEmptyStr(buf)) && (strncasecmp(buf, "lastsent|", 9)) ) {
-                                       sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);
-                               }
-                       }
-
-                       // Write the new netconfig back to disk
-                       SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig);
-                       end_critical_section(S_NETCONFIGS);
-                       free(newnetconfig);     // this was the new netconfig, free it because we're done with it
-               }
-       }
-       free(netconfig);                        // this was the old netconfig, free it even if we didn't do anything
-}
-
-
-// Callback for listdeliver_sweep()
-// Adds one room to the queue
-void listdeliver_queue_room(struct ctdlroom *qrbuf, void *data) {
-       Array *roomlistarr = (Array *)data;
-       array_append(roomlistarr, qrbuf->QRname);
-}
-
-
-// Queue up the list of rooms so we can sweep them for mailing list delivery instructions
-void listdeliver_sweep(void) {
-       static time_t last_run = 0L;
-       int i = 0;
-       time_t now = time(NULL);
-
-       // Run mailing list delivery no more frequently than once every 15 minutes (we should make this configurable)
-       if ( (now - last_run) < 900 ) {
-               syslog(LOG_DEBUG,
-                       "listdeliver: delivery interval not yet reached; last run was %ldm%lds ago",
-                       ((now - last_run) / 60),
-                       ((now - last_run) % 60)
-               );
-               return;
-       }
-
-       // This is a simple concurrency check to make sure only one listdeliver
-       // run is done at a time.  We could do this with a mutex, but since we
-       // don't really require extremely fine granularity here, we'll do it
-       // with a static variable instead.
-       if (doing_listdeliver) return;
-       doing_listdeliver = 1;
-
-       // Go through each room looking for mailing lists to process
-       syslog(LOG_DEBUG, "listdeliver: sweep started");
-
-       Array *roomlistarr = array_new(ROOMNAMELEN);                    // we have to queue them
-       CtdlForEachRoom(listdeliver_queue_room, roomlistarr);           // otherwise we get multiple cursors in progress
-
-       for (i=0; i<array_len(roomlistarr); ++i) {
-               listdeliver_sweep_room((char *)array_get_element_at(roomlistarr, i));
-       }
-
-       array_free(roomlistarr);
-       syslog(LOG_DEBUG, "listdeliver: ended");
-       last_run = time(NULL);
-       doing_listdeliver = 0;
-}
-
-
-// Module entry point
-CTDL_MODULE_INIT(listdeliver)
-{
-       if (!threading) {
-               CtdlRegisterSessionHook(listdeliver_sweep, EVT_TIMER, PRIO_AGGR + 50);
-       }
-       
-       // return our module name for the log
-       return "listsub";
-}
diff --git a/citadel/modules/listsub/.gitignore b/citadel/modules/listsub/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/listsub/serv_listsub.c b/citadel/modules/listsub/serv_listsub.c
deleted file mode 100644 (file)
index 7d7e936..0000000
+++ /dev/null
@@ -1,306 +0,0 @@
-// This module handles self-service subscription/unsubscription to mail lists.
-//
-// Copyright (c) 2002-2022 by the citadel.org team
-//
-// This program is open source software.  It runs great on the
-// Linux operating system (and probably elsewhere).  You can use,
-// copy, and run it under the terms of the GNU General Public
-// License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <dirent.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <crypt.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "clientsocket.h"
-#include "ctdl_module.h"
-
-
-enum {                         // one of these gets passed to do_subscribe_or_unsubscribe() so it knows what we asked for
-       UNSUBSCRIBE,
-       SUBSCRIBE
-};
-
-
-// The confirmation token will be generated by combining the room name and email address with the host key,
-// and then generating an encrypted hash of that string.  The encrypted hash is included as part of the
-// confirmation link.
-void generate_confirmation_token(char *token_buf, size_t token_buf_len, char *roomname, char *emailaddr) {
-       char string_to_hash[1024];
-       struct crypt_data cd;
-       char *ptr;
-
-       snprintf(string_to_hash, sizeof string_to_hash, "%s|%s|%s", roomname, emailaddr, CtdlGetConfigStr("host_key"));
-       memset(&cd, 0, sizeof cd);
-
-       strncpy(token_buf, crypt_r(string_to_hash, "$1$ctdl", &cd), token_buf_len);
-
-       for (ptr=token_buf; *ptr; ++ptr) {
-               if (!isalnum((char)*ptr)) *ptr='X';
-       }
-}
-
-
-// This generates an email with a link the user clicks to confirm a list subscription.
-void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
-       // We need a URL-safe representation of the room name
-       char urlroom[ROOMNAMELEN+10];
-       urlesc(urlroom, sizeof(urlroom), roomname);
-
-       char from_address[1024];
-       snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
-
-       char emailtext[SIZ];
-       snprintf(emailtext, sizeof emailtext,
-               "MIME-Version: 1.0\n"
-               "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
-               "\n"
-               "This is a multipart message in MIME format.\n"
-               "\n"
-               "--__ctdlmultipart__\n"
-               "Content-type: text/plain\n"
-               "\n"
-               "Someone (probably you) has submitted a request to subscribe\n"
-               "<%s> to the <%s> mailing list.\n"
-               "\n"
-               "Please go here to confirm this request:\n"
-               "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\n"
-               "\n"
-               "If this request has been submitted in error and you do not\n"
-               "wish to receive the <%s> mailing list, simply do nothing,\n"
-               "and you will not receive any further mailings.\n"
-               "\n"
-               "--__ctdlmultipart__\n"
-               "Content-type: text/html\n"
-               "\n"
-               "<html><body><p>Someone (probably you) has submitted a request to subscribe "
-               "<strong>%s</strong> to the <strong>%s</strong> mailing list.</p>"
-               "<p>Please go here to confirm this request:</p>"
-               "<p><a href=\"%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\">"
-               "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s</a></p>"
-               "<p>If this request has been submitted in error and you do not "
-               "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
-               "and you will not receive any further mailings.</p>"
-               "</body></html>\n"
-               "\n"
-               "--__ctdlmultipart__--\n"
-               ,
-               emailaddr, roomname,
-               url, emailaddr, urlroom, confirmation_token,
-               roomname
-               ,
-               emailaddr, roomname,
-               url, emailaddr, urlroom, confirmation_token,
-               url, emailaddr, urlroom, confirmation_token,
-               roomname
-       );
-
-       quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list subscription");
-       cprintf("%d confirmation email sent\n", CIT_OK);
-}
-
-
-// This generates an email with a link the user clicks to confirm a list unsubscription.
-void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
-       // We need a URL-safe representation of the room name
-       char urlroom[ROOMNAMELEN+10];
-       urlesc(urlroom, sizeof(urlroom), roomname);
-
-       char from_address[1024];
-       snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
-
-       char emailtext[SIZ];
-       snprintf(emailtext, sizeof emailtext,
-               "MIME-Version: 1.0\n"
-               "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
-               "\n"
-               "This is a multipart message in MIME format.\n"
-               "\n"
-               "--__ctdlmultipart__\n"
-               "Content-type: text/plain\n"
-               "\n"
-               "Someone (probably you) has submitted a request to unsubscribe\n"
-               "<%s> from the <%s> mailing list.\n"
-               "\n"
-               "Please go here to confirm this request:\n"
-               "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\n"
-               "\n"
-               "If this request has been submitted in error and you still\n"
-               "wish to receive the <%s> mailing list, simply do nothing,\n"
-               "and you will remain subscribed.\n"
-               "\n"
-               "--__ctdlmultipart__\n"
-               "Content-type: text/html\n"
-               "\n"
-               "<html><body><p>Someone (probably you) has submitted a request to unsubscribe "
-               "<strong>%s</strong> from the <strong>%s</strong> mailing list.</p>"
-               "<p>Please go here to confirm this request:</p>"
-               "<p><a href=\"%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\">"
-               "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s</a></p>"
-               "<p>If this request has been submitted in error and you still "
-               "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
-               "and you will remain subscribed.</p>"
-               "</body></html>\n"
-               "\n"
-               "--__ctdlmultipart__--\n"
-               ,
-               emailaddr, roomname,
-               url, emailaddr, urlroom, confirmation_token,
-               roomname
-               ,
-               emailaddr, roomname,
-               url, emailaddr, urlroom, confirmation_token,
-               url, emailaddr, urlroom, confirmation_token,
-               roomname
-       );
-
-       quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list unsubscription");
-       cprintf("%d confirmation email sent\n", CIT_OK);
-}
-
-
-// Confirm a list subscription or unsubscription
-void do_confirm(int cmd, char *roomname, char *emailaddr, char *url, char *generated_token, char *supplied_token) {
-       int i;
-       char buf[1024];
-       int config_lines = 0;
-       char *oldnetconfig, *newnetconfig;
-
-       // During opt #1, the server generated a persistent confirmation token for the user+room combination.
-       // Let's see if the user has supplied the same token during opt #2.
-       if (strcmp(generated_token, supplied_token)) {
-               cprintf("%d This request could not be authenticated.\n", ERROR + PASSWORD_REQUIRED);
-               return;
-       }
-
-       // If the generated token matches the supplied token, the request is authentic.  Do what it says.
-
-       // Load the room's network configuration...
-        oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
-        if (!oldnetconfig) {
-               oldnetconfig = strdup("");
-       }
-
-       // The new netconfig begins with an empty buffer...
-       begin_critical_section(S_NETCONFIGS);
-       newnetconfig = malloc(strlen(oldnetconfig) + 1024);
-       newnetconfig[0] = 0;
-
-       // Load the config lines in one by one, skipping any that reference this subscriber.  Also remove blank lines.
-       config_lines = num_tokens(oldnetconfig, '\n');
-       for (i=0; i<config_lines; ++i) {
-               char buf_email[256];
-               extract_token(buf, oldnetconfig, i, '\n', sizeof buf);
-               extract_token(buf_email, buf, 1, '|', sizeof buf_email);
-               if ( !IsEmptyStr(buf) && (strcasecmp(buf_email, emailaddr)) ) {
-                       sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);
-               }
-       }
-
-       // We have now removed all lines containing the subscriber's email address.  This deletes any pending requests.
-       // If this was an unsubscribe operation, they're now gone from the list.
-       // But if this was a subscribe operation, we now need to add them.
-       if (cmd == SUBSCRIBE) {
-               sprintf(&newnetconfig[strlen(newnetconfig)], "listrecp|%s\n", emailaddr);
-       }
-
-       // write it back to disk
-       SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig);
-       end_critical_section(S_NETCONFIGS);
-       free(oldnetconfig);
-       free(newnetconfig);
-       cprintf("%d The pending request was confirmed.\n", CIT_OK);
-}
-
-
-// process subscribe/unsubscribe requests and confirmations
-void cmd_lsub(char *cmdbuf) {
-       char cmd[20];
-       char roomname[ROOMNAMELEN];
-       char emailaddr[1024];
-       char url[1024];
-       char generated_token[128];
-       char supplied_token[128];
-
-       extract_token(cmd, cmdbuf, 0, '|', sizeof cmd);                         // token 0 is the sub-command being sent
-       extract_token(roomname, cmdbuf, 1, '|', sizeof roomname);               // token 1 is always a room name
-       extract_token(emailaddr, cmdbuf, 2, '|', sizeof emailaddr);             // token 2 is the subscriber's email address
-       extract_token(url, cmdbuf, 3, '|', sizeof url);                         // token 3 is the URL at which we subscribed
-       extract_token(supplied_token, cmdbuf, 4, '|', sizeof supplied_token);   // token 4 is the token supplied by the caller
-
-       // First confirm that the caller is referencing a room that actually exists.
-       if (CtdlGetRoom(&CC->room, roomname) != 0) {
-               cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, roomname);
-               return;
-       }
-
-       if ((CC->room.QRflags2 & QR2_SELFLIST) == 0) {
-               cprintf("%d '%s' does not accept subscribe/unsubscribe requests.\n", ERROR + ROOM_NOT_FOUND, roomname);
-               return;
-       }
-
-       // Generate a confirmation token -- either to supply to the user for opt #1 or to compare for opt #2
-       generate_confirmation_token(generated_token, sizeof generated_token, roomname, emailaddr);
-
-       // Now parse the command.
-       if (!strcasecmp(cmd, "subscribe")) {
-               send_subscribe_confirmation_email(roomname, emailaddr, url, generated_token);
-       }
-
-       else if (!strcasecmp(cmd, "unsubscribe")) {
-               send_unsubscribe_confirmation_email(roomname, emailaddr, url, generated_token);
-       }
-
-       else if (!strcasecmp(cmd, "confirm_subscribe")) {
-               do_confirm(SUBSCRIBE, roomname, emailaddr, url, generated_token, supplied_token);
-       }
-
-       else if (!strcasecmp(cmd, "confirm_unsubscribe")) {
-               do_confirm(UNSUBSCRIBE, roomname, emailaddr, url, generated_token, supplied_token);
-       }
-
-       else {                                                                  // sorry man, I can't deal with that
-               cprintf("%d Invalid command '%s'\n", ERROR + ILLEGAL_VALUE, cmd);
-       }
-}
-
-
-/*
- * Module entry point
- */
-CTDL_MODULE_INIT(listsub)
-{
-       if (!threading)
-       {
-               CtdlRegisterProtoHook(cmd_lsub, "LSUB", "List subscribe/unsubscribe");
-       }
-       
-       /* return our module name for the log */
-       return "listsub";
-}
diff --git a/citadel/modules/migrate/.gitignore b/citadel/modules/migrate/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/migrate/serv_migrate.c b/citadel/modules/migrate/serv_migrate.c
deleted file mode 100644 (file)
index 31815f4..0000000
+++ /dev/null
@@ -1,1130 +0,0 @@
-// This module dumps and/or loads the Citadel database in XML format.
-//
-// Copyright (c) 1987-2021 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// Explanation of <progress> tags:
-//
-// 0%          started
-// 2%          finished exporting configuration
-// 7%          finished exporting users
-// 12%         finished exporting openids
-// 17%         finished exporting rooms
-// 18%         finished exporting floors
-// 25%         finished exporting visits
-// 26-99%      exporting messages
-// 100%                finished exporting messages
-//
-// These tags are inserted into the XML stream to give the reader an approximation of its progress.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <ctype.h>
-#include <limits.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "database.h"
-#include "msgbase.h"
-#include "user_ops.h"
-#include "euidindex.h"
-#include "ctdl_module.h"
-
-char migr_tempfilename1[PATH_MAX];
-char migr_tempfilename2[PATH_MAX];
-FILE *migr_global_message_list;
-int total_msgs = 0;
-char *ikey = NULL;                     // If we're importing a config key we store it here.
-
-//*****************************************************************************
-//*       Code which implements the export appears in this section            *
-//*****************************************************************************
-
-// Output a string to the client with these characters escaped:  & < > " '
-void xml_strout(char *str) {
-
-       char *c = str;
-
-       if (str == NULL) {
-               return;
-       }
-
-       while (*c != 0) {
-               if (*c == '\"') {
-                       client_write(HKEY("&quot;"));
-               }
-               else if (*c == '\'') {
-                       client_write(HKEY("&apos;"));
-               }
-               else if (*c == '<') {
-                       client_write(HKEY("&lt;"));
-               }
-               else if (*c == '>') {
-                       client_write(HKEY("&gt;"));
-               }
-               else if (*c == '&') {
-                       client_write(HKEY("&amp;"));
-               }
-               else {
-                       client_write(c, 1);
-               }
-               ++c;
-       }
-}
-
-
-// Export a user record as XML
-void migr_export_users_backend(char *username, void *data) {
-
-       struct ctdluser u;
-       if (CtdlGetUser(&u, username) != 0) {
-               return;
-       }
-
-       client_write(HKEY("<user>\n"));
-       cprintf("<u_version>%d</u_version>\n", u.version);
-       cprintf("<u_uid>%ld</u_uid>\n", (long)u.uid);
-       client_write(HKEY("<u_password>"));     xml_strout(u.password);         client_write(HKEY("</u_password>\n"));
-       cprintf("<u_flags>%u</u_flags>\n", u.flags);
-       cprintf("<u_timescalled>%ld</u_timescalled>\n", u.timescalled);
-       cprintf("<u_posted>%ld</u_posted>\n", u.posted);
-       cprintf("<u_axlevel>%d</u_axlevel>\n", u.axlevel);
-       cprintf("<u_usernum>%ld</u_usernum>\n", u.usernum);
-       cprintf("<u_lastcall>%ld</u_lastcall>\n", (long)u.lastcall);
-       cprintf("<u_USuserpurge>%d</u_USuserpurge>\n", u.USuserpurge);
-       client_write(HKEY("<u_fullname>"));     xml_strout(u.fullname);         client_write(HKEY("</u_fullname>\n"));
-       cprintf("<u_msgnum_bio>%ld</u_msgnum_bio>\n", u.msgnum_bio);
-       cprintf("<u_msgnum_pic>%ld</u_msgnum_pic>\n", u.msgnum_pic);
-       cprintf("<u_emailaddrs>%s</u_emailaddrs>\n", u.emailaddrs);
-       cprintf("<u_msgnum_inboxrules>%ld</u_msgnum_inboxrules>\n", u.msgnum_inboxrules);
-       cprintf("<u_lastproc_inboxrules>%ld</u_lastproc_inboxrules>\n", u.lastproc_inboxrules);
-       client_write(HKEY("</user>\n"));
-}
-
-
-void migr_export_users(void) {
-       ForEachUser(migr_export_users_backend, NULL);
-}
-
-
-void migr_export_room_msg(long msgnum, void *userdata) {
-       static int count = 0;
-
-       cprintf("%ld,", msgnum);
-       if (++count%10==0) {
-               cprintf("\n");
-       }
-       fprintf(migr_global_message_list, "%ld\n", msgnum);
-}
-
-
-void migr_export_rooms_backend(struct ctdlroom *buf, void *data) {
-       client_write(HKEY("<room>\n"));
-       client_write(HKEY("<QRname>"));
-       xml_strout(buf->QRname);
-       client_write(HKEY("</QRname>\n"));
-       client_write(HKEY("<QRpasswd>"));
-       xml_strout(buf->QRpasswd);
-       client_write(HKEY("</QRpasswd>\n"));
-       cprintf("<QRroomaide>%ld</QRroomaide>\n", buf->QRroomaide);
-       cprintf("<QRhighest>%ld</QRhighest>\n", buf->QRhighest);
-       cprintf("<QRgen>%ld</QRgen>\n", (long)buf->QRgen);
-       cprintf("<QRflags>%u</QRflags>\n", buf->QRflags);
-       if (buf->QRflags & QR_DIRECTORY) {
-               client_write(HKEY("<QRdirname>"));
-               xml_strout(buf->QRdirname);
-               client_write(HKEY("</QRdirname>\n"));
-       }
-       cprintf("<QRfloor>%d</QRfloor>\n", buf->QRfloor);
-       cprintf("<QRmtime>%ld</QRmtime>\n", (long)buf->QRmtime);
-       cprintf("<QRexpire_mode>%d</QRexpire_mode>\n", buf->QRep.expire_mode);
-       cprintf("<QRexpire_value>%d</QRexpire_value>\n", buf->QRep.expire_value);
-       cprintf("<QRnumber>%ld</QRnumber>\n", buf->QRnumber);
-       cprintf("<QRorder>%d</QRorder>\n", buf->QRorder);
-       cprintf("<QRflags2>%u</QRflags2>\n", buf->QRflags2);
-       cprintf("<QRdefaultview>%d</QRdefaultview>\n", buf->QRdefaultview);
-       cprintf("<msgnum_info>%ld</msgnum_info>\n", buf->msgnum_info);
-       cprintf("<msgnum_pic>%ld</msgnum_pic>\n", buf->msgnum_pic);
-       client_write(HKEY("</room>\n"));
-
-       // message list goes inside this tag
-       CtdlGetRoom(&CC->room, buf->QRname);
-       client_write(HKEY("<room_messages>"));
-       client_write(HKEY("<FRname>"));
-       xml_strout(buf->QRname);                        // buf->QRname rather than CC->room.QRname to guarantee consistency
-       client_write(HKEY("</FRname>\n"));
-       client_write(HKEY("<FRmsglist>\n"));
-       CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, migr_export_room_msg, NULL);
-       client_write(HKEY("</FRmsglist>\n"));
-       client_write(HKEY("</room_messages>\n"));
-}
-
-
-void migr_export_rooms(void) {
-       char cmd[SIZ];
-       migr_global_message_list = fopen(migr_tempfilename1, "w");
-       if (migr_global_message_list != NULL) {
-               CtdlForEachRoom(migr_export_rooms_backend, NULL);
-               fclose(migr_global_message_list);
-       }
-
-       // Process the 'global' message list.  (Sort it and remove dups.
-       // Dups are ok because a message may be in more than one room, but
-       // this will be handled by exporting the reference count, not by
-       // exporting the message multiple times.)
-       snprintf(cmd, sizeof cmd, "sort -n <%s >%s", migr_tempfilename1, migr_tempfilename2);
-       if (system(cmd) != 0) {
-               syslog(LOG_ERR, "migrate: error %d", errno);
-       }
-       snprintf(cmd, sizeof cmd, "uniq <%s >%s", migr_tempfilename2, migr_tempfilename1);
-       if (system(cmd) != 0) {
-               syslog(LOG_ERR, "migrate: error %d", errno);
-       }
-
-       snprintf(cmd, sizeof cmd, "wc -l %s", migr_tempfilename1);
-       FILE *fp = popen(cmd, "r");
-       if (fp) {
-               fgets(cmd, sizeof cmd, fp);
-               pclose(fp);
-               total_msgs = atoi(cmd);
-       }
-       else {
-               total_msgs = 1; // any nonzero just to keep it from barfing
-       }
-       syslog(LOG_DEBUG, "migrate: total messages to be exported: %d", total_msgs);
-}
-
-
-void migr_export_floors(void) {
-        struct floor qfbuf, *buf;
-        int i;
-
-        for (i=0; i < MAXFLOORS; ++i) {
-               client_write(HKEY("<floor>\n"));
-               cprintf("<f_num>%d</f_num>\n", i);
-                CtdlGetFloor(&qfbuf, i);
-               buf = &qfbuf;
-               cprintf("<f_flags>%u</f_flags>\n", buf->f_flags);
-               client_write(HKEY("<f_name>")); xml_strout(buf->f_name); client_write(HKEY("</f_name>\n"));
-               cprintf("<f_ref_count>%d</f_ref_count>\n", buf->f_ref_count);
-               cprintf("<f_ep_expire_mode>%d</f_ep_expire_mode>\n", buf->f_ep.expire_mode);
-               cprintf("<f_ep_expire_value>%d</f_ep_expire_value>\n", buf->f_ep.expire_value);
-               client_write(HKEY("</floor>\n"));
-       }
-}
-
-
-// Return nonzero if the supplied string contains only characters which are valid in a sequence set.
-int is_sequence_set(char *s) {
-       if (!s) return(0);
-
-       char *c = s;
-       char ch;
-       while (ch = *c++, ch) {
-               if (!strchr("0123456789*,:", ch)) {
-                       return(0);
-               }
-       }
-       return(1);
-}
-
-
-// Traverse the visits file...
-void migr_export_visits(void) {
-       visit vbuf;
-       struct cdbdata *cdbv;
-
-       cdb_rewind(CDB_VISIT);
-
-       while (cdbv = cdb_next_item(CDB_VISIT), cdbv != NULL) {
-               memset(&vbuf, 0, sizeof(visit));
-               memcpy(&vbuf, cdbv->ptr,
-                      ((cdbv->len > sizeof(visit)) ?
-                       sizeof(visit) : cdbv->len));
-               cdb_free(cdbv);
-
-               client_write(HKEY("<visit>\n"));
-               cprintf("<v_roomnum>%ld</v_roomnum>\n", vbuf.v_roomnum);
-               cprintf("<v_roomgen>%ld</v_roomgen>\n", vbuf.v_roomgen);
-               cprintf("<v_usernum>%ld</v_usernum>\n", vbuf.v_usernum);
-
-               client_write(HKEY("<v_seen>"));
-               if ( (!IsEmptyStr(vbuf.v_seen)) && (is_sequence_set(vbuf.v_seen)) ) {
-                       xml_strout(vbuf.v_seen);
-               }
-               else {
-                       cprintf("%ld", vbuf.v_lastseen);
-               }
-               client_write(HKEY("</v_seen>"));
-
-               if ( (!IsEmptyStr(vbuf.v_answered)) && (is_sequence_set(vbuf.v_answered)) ) {
-                       client_write(HKEY("<v_answered>"));
-                       xml_strout(vbuf.v_answered);
-                       client_write(HKEY("</v_answered>\n"));
-               }
-
-               cprintf("<v_flags>%u</v_flags>\n", vbuf.v_flags);
-               cprintf("<v_view>%d</v_view>\n", vbuf.v_view);
-               client_write(HKEY("</visit>\n"));
-       }
-}
-
-
-void migr_export_message(long msgnum) {
-       struct MetaData smi;
-       struct CtdlMessage *msg;
-       struct ser_ret smr;
-       long bytes_written = 0;
-       long this_block = 0;
-
-       // We can use a static buffer here because there will never be more than
-       // one of this operation happening at any given time, and it's really best
-       // to just keep it allocated once instead of torturing malloc/free.
-       // Call this function with msgnum "-1" to free the buffer when finished.
-
-       static int encoded_alloc = 0;
-       static char *encoded_msg = NULL;
-
-       if (msgnum < 0) {
-               if ((encoded_alloc == 0) && (encoded_msg != NULL)) {
-                       free(encoded_msg);
-                       encoded_alloc = 0;
-                       encoded_msg = NULL;
-               }
-               return;
-       }
-
-       // Ok, here we go ...
-
-       msg = CtdlFetchMessage(msgnum, 1);
-       if (msg == NULL) return;                                // fail silently
-
-       client_write(HKEY("<message>\n"));
-       GetMetaData(&smi, msgnum);
-       cprintf("<msg_msgnum>%ld</msg_msgnum>\n", msgnum);
-       cprintf("<msg_meta_refcount>%d</msg_meta_refcount>\n", smi.meta_refcount);
-       cprintf("<msg_meta_rfc822_length>%ld</msg_meta_rfc822_length>\n", smi.meta_rfc822_length);
-       client_write(HKEY("<msg_meta_content_type>")); xml_strout(smi.meta_content_type); client_write(HKEY("</msg_meta_content_type>\n"));
-
-       client_write(HKEY("<msg_text>"));
-       CtdlSerializeMessage(&smr, msg);
-       CM_Free(msg);
-
-       // Predict the buffer size we need.  Expand the buffer if necessary.
-       int encoded_len = smr.len * 15 / 10 ;
-       if (encoded_len > encoded_alloc) {
-               encoded_alloc = encoded_len;
-               encoded_msg = realloc(encoded_msg, encoded_alloc);
-       }
-
-       if (encoded_msg == NULL) {
-               // Questionable hack that hopes it'll work next time and we only lose one message
-               encoded_alloc = 0;
-       }
-       else {
-               // Once we do the encoding we know the exact size
-               encoded_len = CtdlEncodeBase64(encoded_msg, (char *)smr.ser, smr.len, 1);
-
-               // Careful now.  If the message is gargantuan, trying to write multiple gigamegs in one
-               // big write operation can make our transport unhappy.  So we'll chunk it up 10 KB at a time.
-               bytes_written = 0;
-               while ( (bytes_written < encoded_len) && (!server_shutting_down) ) {
-                       this_block = encoded_len - bytes_written;
-                       if (this_block > 10240) {
-                               this_block = 10240;
-                       }
-                       client_write(&encoded_msg[bytes_written], this_block);
-                       bytes_written += this_block;
-               }
-       }
-
-       free(smr.ser);
-
-       client_write(HKEY("</msg_text>\n"));
-       client_write(HKEY("</message>\n"));
-}
-
-
-void migr_export_openids(void) {
-       struct cdbdata *cdboi;
-       long usernum;
-       char url[512];
-
-       cdb_rewind(CDB_EXTAUTH);
-       while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
-               if (cdboi->len > sizeof(long)) {
-                       client_write(HKEY("<openid>\n"));
-                       memcpy(&usernum, cdboi->ptr, sizeof(long));
-                       snprintf(url, sizeof url, "%s", (cdboi->ptr)+sizeof(long) );
-                       client_write(HKEY("<oid_url>"));
-                       xml_strout(url);
-                       client_write(HKEY("</oid_url>\n"));
-                       cprintf("<oid_usernum>%ld</oid_usernum>\n", usernum);
-                       client_write(HKEY("</openid>\n"));
-               }
-               cdb_free(cdboi);
-       }
-}
-
-
-void migr_export_configs(void) {
-       struct cdbdata *cdbcfg;
-       int keylen = 0;
-       char *key = NULL;
-       char *value = NULL;
-
-       cdb_rewind(CDB_CONFIG);
-       while (cdbcfg = cdb_next_item(CDB_CONFIG), cdbcfg != NULL) {
-
-               keylen = strlen(cdbcfg->ptr);
-               key = cdbcfg->ptr;
-               value = cdbcfg->ptr + keylen + 1;
-
-               client_write("<config key=\"", 13);
-               xml_strout(key);
-               client_write("\">", 2);
-               xml_strout(value);
-               client_write("</config>\n", 10);
-               cdb_free(cdbcfg);
-       }
-}
-
-
-void migr_export_messages(void) {
-       char buf[SIZ];
-       long msgnum;
-       int count = 0;
-       int progress = 0;
-       int prev_progress = 0;
-       CitContext *Ctx;
-
-       Ctx = CC;
-       migr_global_message_list = fopen(migr_tempfilename1, "r");
-       if (migr_global_message_list != NULL) {
-               syslog(LOG_INFO, "migrate: opened %s", migr_tempfilename1);
-               while ((Ctx->kill_me == 0) && 
-                      (fgets(buf, sizeof(buf), migr_global_message_list) != NULL)) {
-                       msgnum = atol(buf);
-                       if (msgnum > 0L) {
-                               migr_export_message(msgnum);
-                               ++count;
-                       }
-                       progress = (count * 74 / total_msgs) + 25 ;
-                       if ((progress > prev_progress) && (progress < 100)) {
-                               cprintf("<progress>%d</progress>\n", progress);
-                       }
-                       prev_progress = progress;
-               }
-               fclose(migr_global_message_list);
-       }
-       if (Ctx->kill_me == 0) {
-               syslog(LOG_INFO, "migrate: exported %d messages.", count);
-       }
-       else {
-               syslog(LOG_ERR, "migrate: export aborted due to client disconnect!");
-       }
-
-       migr_export_message(-1L);       // This frees the encoding buffer
-}
-
-
-void migr_do_export(void) {
-       CitContext *Ctx;
-
-       Ctx = CC;
-       cprintf("%d Exporting all Citadel databases.\n", LISTING_FOLLOWS);
-       Ctx->dont_term = 1;
-
-       client_write(HKEY("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"));
-       client_write(HKEY("<citadel_migrate_data>\n"));
-       cprintf("<version>%d</version>\n", REV_LEVEL);
-       cprintf("<progress>%d</progress>\n", 0);
-
-       // export the configuration database
-       migr_export_configs();
-       cprintf("<progress>%d</progress>\n", 2);
-       
-       if (Ctx->kill_me == 0)  migr_export_users();
-       cprintf("<progress>%d</progress>\n", 7);
-       if (Ctx->kill_me == 0)  migr_export_openids();
-       cprintf("<progress>%d</progress>\n", 12);
-       if (Ctx->kill_me == 0)  migr_export_rooms();
-       cprintf("<progress>%d</progress>\n", 17);
-       if (Ctx->kill_me == 0)  migr_export_floors();
-       cprintf("<progress>%d</progress>\n", 18);
-       if (Ctx->kill_me == 0)  migr_export_visits();
-       cprintf("<progress>%d</progress>\n", 25);
-       if (Ctx->kill_me == 0)  migr_export_messages();
-       cprintf("<progress>%d</progress>\n", 100);
-       client_write(HKEY("</citadel_migrate_data>\n"));
-       client_write(HKEY("000\n"));
-       Ctx->dont_term = 0;
-}
-
-
-//                              Import code
-//   Here's the code that implements the import side.  It's going to end up
-//       being one big loop with lots of global variables.  I don't care.
-// You wouldn't run multiple concurrent imports anyway.  If this offends your
-//  delicate sensibilities  then go rewrite it in Ruby on Rails or something.
-
-
-int citadel_migrate_data = 0;          // Are we inside a <citadel_migrate_data> tag pair?
-StrBuf *migr_chardata = NULL;
-StrBuf *migr_MsgData = NULL;
-struct ctdluser usbuf;
-struct ctdlroom qrbuf;
-char openid_url[512];
-long openid_usernum = 0;
-char FRname[ROOMNAMELEN];
-struct floor flbuf;
-int floornum = 0;
-visit vbuf;
-struct MetaData smi;
-long import_msgnum = 0;
-
-// This callback stores up the data which appears in between tags.
-void migr_xml_chardata(void *data, const XML_Char *s, int len) {
-       StrBufAppendBufPlain(migr_chardata, s, len, 0);
-}
-
-
-void migr_xml_start(void *data, const char *el, const char **attr) {
-       int i;
-
-       //  *** GENERAL STUFF ***
-
-       // Reset chardata_len to zero and init buffer
-       FlushStrBuf(migr_chardata);
-       FlushStrBuf(migr_MsgData);
-
-       if (!strcasecmp(el, "citadel_migrate_data")) {
-               ++citadel_migrate_data;
-
-               // As soon as it looks like the input data is a genuine Citadel XML export,
-               // whack the existing database on disk to make room for the new one.
-               if (citadel_migrate_data == 1) {
-                       syslog(LOG_INFO, "migrate: erasing existing databases so we can receive the incoming import");
-                       for (i = 0; i < MAXCDB; ++i) {
-                               cdb_trunc(i);
-                       }
-               }
-               return;
-       }
-
-       if (citadel_migrate_data != 1) {
-               syslog(LOG_ERR, "migrate: out-of-sequence tag <%s> detected.  Warning: ODD-DATA!", el);
-               return;
-       }
-
-       // When we begin receiving XML for one of these record types, clear out the associated
-       // buffer so we don't accidentally carry over any data from a previous record.
-       if (!strcasecmp(el, "user"))                    memset(&usbuf, 0, sizeof (struct ctdluser));
-       else if (!strcasecmp(el, "openid"))             memset(openid_url, 0, sizeof openid_url);
-       else if (!strcasecmp(el, "room"))               memset(&qrbuf, 0, sizeof (struct ctdlroom));
-       else if (!strcasecmp(el, "room_messages"))      memset(FRname, 0, sizeof FRname);
-       else if (!strcasecmp(el, "floor"))              memset(&flbuf, 0, sizeof (struct floor));
-       else if (!strcasecmp(el, "visit"))              memset(&vbuf, 0, sizeof (visit));
-
-       else if (!strcasecmp(el, "message")) {
-               memset(&smi, 0, sizeof (struct MetaData));
-               import_msgnum = 0;
-       }
-       else if (!strcasecmp(el, "config")) {
-               if (ikey != NULL) {
-                       free(ikey);
-                       ikey = NULL;
-               }
-               while (*attr) {
-                       if (!strcasecmp(attr[0], "key")) {
-                               ikey = strdup(attr[1]);
-                       }
-                       attr += 2;
-               }
-       }
-
-}
-
-
-int migr_userrecord(void *data, const char *el) {
-       if (!strcasecmp(el, "u_version"))                       usbuf.version = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "u_uid"))                      usbuf.uid = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "u_password"))                 safestrncpy(usbuf.password, ChrPtr(migr_chardata), sizeof usbuf.password);
-       else if (!strcasecmp(el, "u_flags"))                    usbuf.flags = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "u_timescalled"))              usbuf.timescalled = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "u_posted"))                   usbuf.posted = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "u_axlevel"))                  usbuf.axlevel = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "u_usernum"))                  usbuf.usernum = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "u_lastcall"))                 usbuf.lastcall = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "u_USuserpurge"))              usbuf.USuserpurge = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "u_fullname"))                 safestrncpy(usbuf.fullname, ChrPtr(migr_chardata), sizeof usbuf.fullname);
-       else if (!strcasecmp(el, "u_msgnum_bio"))               usbuf.msgnum_bio = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "u_msgnum_pic"))               usbuf.msgnum_pic = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "u_emailaddrs"))               safestrncpy(usbuf.emailaddrs, ChrPtr(migr_chardata), sizeof usbuf.emailaddrs);
-       else if (!strcasecmp(el, "u_msgnum_inboxrules"))        usbuf.msgnum_inboxrules = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "u_lastproc_inboxrules"))      usbuf.lastproc_inboxrules = atol(ChrPtr(migr_chardata));
-       else return 0;
-       return 1;
-}
-
-
-int migr_roomrecord(void *data, const char *el) {
-       if (!strcasecmp(el, "QRname"))                          safestrncpy(qrbuf.QRname, ChrPtr(migr_chardata), sizeof qrbuf.QRname);
-       else if (!strcasecmp(el, "QRpasswd"))                   safestrncpy(qrbuf.QRpasswd, ChrPtr(migr_chardata), sizeof qrbuf.QRpasswd);
-       else if (!strcasecmp(el, "QRroomaide"))                 qrbuf.QRroomaide = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "QRhighest"))                  qrbuf.QRhighest = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "QRgen"))                      qrbuf.QRgen = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "QRflags"))                    qrbuf.QRflags = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "QRdirname"))                  safestrncpy(qrbuf.QRdirname, ChrPtr(migr_chardata), sizeof qrbuf.QRdirname);
-       else if (!strcasecmp(el, "QRfloor"))                    qrbuf.QRfloor = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "QRmtime"))                    qrbuf.QRmtime = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "QRexpire_mode"))              qrbuf.QRep.expire_mode = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "QRexpire_value"))             qrbuf.QRep.expire_value = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "QRnumber"))                   qrbuf.QRnumber = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "QRorder"))                    qrbuf.QRorder = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "QRflags2"))                   qrbuf.QRflags2 = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "QRdefaultview"))              qrbuf.QRdefaultview = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "msgnum_info"))                qrbuf.msgnum_info = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "msgnum_pic"))                 qrbuf.msgnum_pic = atol(ChrPtr(migr_chardata));
-       else return 0;
-       return 1;
-}
-
-
-int migr_floorrecord(void *data, const char *el) {
-       if (!strcasecmp(el, "f_num"))                           floornum = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "f_flags"))                    flbuf.f_flags = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "f_name"))                     safestrncpy(flbuf.f_name, ChrPtr(migr_chardata), sizeof flbuf.f_name);
-       else if (!strcasecmp(el, "f_ref_count"))                flbuf.f_ref_count = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "f_ep_expire_mode"))           flbuf.f_ep.expire_mode = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "f_ep_expire_value"))          flbuf.f_ep.expire_value = atoi(ChrPtr(migr_chardata));
-       else return 0;
-       return 1;
-}
-
-
-int migr_visitrecord(void *data, const char *el) {
-       if (!strcasecmp(el, "v_roomnum"))                       vbuf.v_roomnum = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "v_roomgen"))                  vbuf.v_roomgen = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "v_usernum"))                  vbuf.v_usernum = atol(ChrPtr(migr_chardata));
-
-       else if (!strcasecmp(el, "v_seen")) {
-               int is_textual_seen = 0;
-               int i;
-               int max = StrLength(migr_chardata);
-
-               vbuf.v_lastseen = atol(ChrPtr(migr_chardata));
-               is_textual_seen = 0;
-               for (i=0; i < max; ++i) 
-                       if (!isdigit(ChrPtr(migr_chardata)[i]))
-                               is_textual_seen = 1;
-               if (is_textual_seen)
-                       safestrncpy(vbuf.v_seen, ChrPtr(migr_chardata), sizeof vbuf.v_seen);
-       }
-
-       else if (!strcasecmp(el, "v_answered"))                 safestrncpy(vbuf.v_answered, ChrPtr(migr_chardata), sizeof vbuf.v_answered);
-       else if (!strcasecmp(el, "v_flags"))                    vbuf.v_flags = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "v_view"))                     vbuf.v_view = atoi(ChrPtr(migr_chardata));
-       else return 0;
-       return 1;
-}
-
-
-void migr_xml_end(void *data, const char *el) {
-       const char *ptr;
-       int msgcount = 0;
-       long msgnum = 0L;
-       long *msglist = NULL;
-       int msglist_alloc = 0;
-       // *** GENERAL STUFF ***
-
-       if (!strcasecmp(el, "citadel_migrate_data")) {
-               --citadel_migrate_data;
-               return;
-       }
-
-       if (citadel_migrate_data != 1) {
-               syslog(LOG_ERR, "migrate: out-of-sequence tag <%s> detected.  Warning: ODD-DATA!", el);
-               return;
-       }
-
-       // syslog(LOG_DEBUG, "END TAG: <%s> DATA: <%s>\n", el, (migr_chardata_len ? migr_chardata : ""));
-
-       // *** CONFIG ***
-
-       if (!strcasecmp(el, "config")) {
-               syslog(LOG_DEBUG, "migrate: imported config key=%s", ikey);
-
-               if (ikey != NULL) {
-                       CtdlSetConfigStr(ikey, (char *)ChrPtr(migr_chardata));
-                       free(ikey);
-                       ikey = NULL;
-               }
-               else {
-                       syslog(LOG_INFO, "migrate: closed a <config> tag but no key name was supplied.");
-               }
-       }
-
-       // *** USER ***
-       else if ((!strncasecmp(el, HKEY("u_"))) && 
-                migr_userrecord(data, el))
-               ; /* Nothing to do anymore */
-       else if (!strcasecmp(el, "user")) {
-               CtdlPutUser(&usbuf);
-               syslog(LOG_INFO, "migrate: imported user: %s", usbuf.fullname);
-       }
-
-       // *** OPENID ***
-
-       else if (!strcasecmp(el, "oid_url"))                    safestrncpy(openid_url, ChrPtr(migr_chardata), sizeof openid_url);
-       else if (!strcasecmp(el, "oid_usernum"))                openid_usernum = atol(ChrPtr(migr_chardata));
-
-       else if (!strcasecmp(el, "openid")) {                   // see serv_openid_rp.c for a description of the record format
-               char *oid_data;
-               int oid_data_len;
-               oid_data_len = sizeof(long) + strlen(openid_url) + 1;
-               oid_data = malloc(oid_data_len);
-               memcpy(oid_data, &openid_usernum, sizeof(long));
-               memcpy(&oid_data[sizeof(long)], openid_url, strlen(openid_url) + 1);
-               cdb_store(CDB_EXTAUTH, openid_url, strlen(openid_url), oid_data, oid_data_len);
-               free(oid_data);
-               syslog(LOG_INFO, "migrate: imported OpenID: %s (%ld)", openid_url, openid_usernum);
-       }
-
-       // *** ROOM ***
-       else if ((!strncasecmp(el, HKEY("QR"))) && 
-                migr_roomrecord(data, el))
-               ; // Nothing to do anymore
-       else if (!strcasecmp(el, "room")) {
-               CtdlPutRoom(&qrbuf);
-               syslog(LOG_INFO, "migrate: imported room: %s", qrbuf.QRname);
-       }
-
-       // *** ROOM MESSAGE POINTERS ***
-
-       else if (!strcasecmp(el, "FRname")) {
-               safestrncpy(FRname, ChrPtr(migr_chardata), sizeof FRname);
-       }
-
-       else if (!strcasecmp(el, "FRmsglist")) {
-               if (!IsEmptyStr(FRname)) {
-                       msgcount = 0;
-                       msglist_alloc = 1000;
-                       msglist = malloc(sizeof(long) * msglist_alloc);
-
-                       syslog(LOG_DEBUG, "migrate: message list for: %s", FRname);
-
-                       ptr = ChrPtr(migr_chardata);
-                       while (*ptr != 0) {
-                               while ((*ptr != 0) && (!isdigit(*ptr))) {
-                                       ++ptr;
-                               }
-                               if ((*ptr != 0) && (isdigit(*ptr))) {
-                                       msgnum = atol(ptr);
-                                       if (msgnum > 0L) {
-                                               if (msgcount >= msglist_alloc) {
-                                                       msglist_alloc *= 2;
-                                                       msglist = realloc(msglist, sizeof(long) * msglist_alloc);
-                                               }
-                                               msglist[msgcount++] = msgnum;
-                                               }
-                                       }
-                                       while ((*ptr != 0) && (isdigit(*ptr))) {
-                                               ++ptr;
-                                       }
-                               }
-                       }
-                       if (msgcount > 0) {
-                               CtdlSaveMsgPointersInRoom(FRname, msglist, msgcount, 0, NULL, 1);
-                       }
-                       free(msglist);
-                       msglist = NULL;
-                       msglist_alloc = 0;
-                       syslog(LOG_DEBUG, "migrate: imported %d messages.", msgcount);
-                       if (server_shutting_down) {
-                               return;
-               }
-       }
-
-       // *** FLOORS ***
-       else if ((!strncasecmp(el, HKEY("f_"))) && 
-                migr_floorrecord(data, el))
-               ; // Nothing to do anymore
-
-       else if (!strcasecmp(el, "floor")) {
-               CtdlPutFloor(&flbuf, floornum);
-               syslog(LOG_INFO, "migrate: imported floor #%d (%s)", floornum, flbuf.f_name);
-       }
-
-       // *** VISITS ***
-       else if ((!strncasecmp(el, HKEY("v_"))) && migr_visitrecord(data, el)) {
-               ; // Nothing to do anymore
-       }
-       else if (!strcasecmp(el, "visit")) {
-               put_visit(&vbuf);
-               syslog(LOG_INFO, "migrate: imported visit: %ld/%ld/%ld", vbuf.v_roomnum, vbuf.v_roomgen, vbuf.v_usernum);
-       }
-
-       // *** MESSAGES ***
-       
-       else if (!strcasecmp(el, "msg_msgnum"))                 smi.meta_msgnum = import_msgnum = atol(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "msg_meta_refcount"))          smi.meta_refcount = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "msg_meta_rfc822_length"))     smi.meta_rfc822_length = atoi(ChrPtr(migr_chardata));
-       else if (!strcasecmp(el, "msg_meta_content_type"))      safestrncpy(smi.meta_content_type, ChrPtr(migr_chardata), sizeof smi.meta_content_type);
-
-       else if (!strcasecmp(el, "msg_text")) {
-               long rc;
-               struct CtdlMessage *msg;
-
-               FlushStrBuf(migr_MsgData);
-               StrBufDecodeBase64To(migr_chardata, migr_MsgData);
-
-               msg = CtdlDeserializeMessage(import_msgnum, -1,
-                                            ChrPtr(migr_MsgData), 
-                                            StrLength(migr_MsgData));
-               if (msg != NULL) {
-                       rc = CtdlSaveThisMessage(msg, import_msgnum, 0);
-                       if (rc == 0) {
-                               PutMetaData(&smi);
-                       }
-                       CM_Free(msg);
-               }
-               else {
-                       rc = -1;
-               }
-
-               syslog(LOG_INFO,
-                      "migrate: %s message #%ld, size=%d, ref=%d, body=%ld, content-type: %s",
-                      (rc!= 0)?"failed to import":"imported",
-                      import_msgnum,
-                      StrLength(migr_MsgData),
-                      smi.meta_refcount,
-                      smi.meta_rfc822_length,
-                      smi.meta_content_type
-               );
-               memset(&smi, 0, sizeof(smi));
-       }
-
-       // *** MORE GENERAL STUFF ***
-
-       FlushStrBuf(migr_chardata);
-}
-
-
-// Import begins here
-void migr_do_import(void) {
-       XML_Parser xp;
-       char buf[SIZ];
-       
-       unbuffer_output();
-       migr_chardata = NewStrBufPlain(NULL, SIZ * 20);
-       migr_MsgData = NewStrBufPlain(NULL, SIZ * 20);
-       xp = XML_ParserCreate(NULL);
-       if (!xp) {
-               cprintf("%d Failed to create XML parser instance\n", ERROR+INTERNAL_ERROR);
-               return;
-       }
-       XML_SetElementHandler(xp, migr_xml_start, migr_xml_end);
-       XML_SetCharacterDataHandler(xp, migr_xml_chardata);
-
-       CC->dont_term = 1;
-
-       cprintf("%d sock it to me\n", SEND_LISTING);
-       unbuffer_output();
-
-       client_set_inbound_buf(SIZ * 10);
-
-       while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
-               XML_Parse(xp, buf, strlen(buf), 0);
-               if (server_shutting_down)
-                       break;  // Should we break or return?
-       }
-
-       XML_Parse(xp, "", 0, 1);
-       XML_ParserFree(xp);
-       FreeStrBuf(&migr_chardata);
-       FreeStrBuf(&migr_MsgData);
-       rebuild_euid_index();
-       rebuild_usersbynumber();
-       CtdlSetConfigInt("MM_fulltext_wordbreaker", -1);        // Set an invalid wordbreaker to force re-indexing
-       CC->dont_term = 0;
-}
-
-
-// ******************************************************************************
-// *                         Dispatcher, Common code                            *
-// ******************************************************************************
-
-// Dump out the pathnames of directories which can be copied "as is"
-void migr_do_listdirs(void) {
-       cprintf("%d Don't forget these:\n", LISTING_FOLLOWS);
-       cprintf("files|%s\n",           ctdl_file_dir);
-       cprintf("messages|%s\n",        ctdl_message_dir);
-       cprintf("keys|%s\n",            ctdl_key_dir);
-       cprintf("000\n");
-}
-
-
-// ******************************************************************************
-// *                    Repair database integrity                               *
-// ******************************************************************************
-
-StrBuf *PlainMessageBuf = NULL;
-HashList *UsedMessageIDS = NULL;
-
-int migr_restore_message_metadata(long msgnum, int refcount) {
-       struct MetaData smi;
-       struct CtdlMessage *msg;
-       char *mptr = NULL;
-
-       // We can use a static buffer here because there will never be more than
-       // one of this operation happening at any given time, and it's really best
-       // to just keep it allocated once instead of torturing malloc/free.
-       // Call this function with msgnum "-1" to free the buffer when finished.
-       static int encoded_alloc = 0;
-       static char *encoded_msg = NULL;
-
-       if (msgnum < 0) {
-               if ((encoded_alloc == 0) && (encoded_msg != NULL)) {
-                       free(encoded_msg);
-                       encoded_alloc = 0;
-                       encoded_msg = NULL;
-                       // todo FreeStrBuf(&PlainMessageBuf); PlainMessageBuf = NULL;
-               }
-               return 0;
-       }
-
-       if (PlainMessageBuf == NULL) {
-               PlainMessageBuf = NewStrBufPlain(NULL, 10*SIZ);
-       }
-
-       // Ok, here we go ...
-
-       msg = CtdlFetchMessage(msgnum, 1);
-       if (msg == NULL) {
-               return 1;
-       }
-
-       GetMetaData(&smi, msgnum);
-       smi.meta_msgnum = msgnum;
-       smi.meta_refcount = refcount;
-       
-       // restore the content type from the message body:
-       mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
-       if (mptr != NULL) {
-               char *aptr;
-               safestrncpy(smi.meta_content_type, &mptr[13], sizeof smi.meta_content_type);
-               striplt(smi.meta_content_type);
-               aptr = smi.meta_content_type;
-               while (!IsEmptyStr(aptr)) {
-                       if ((*aptr == ';')
-                           || (*aptr == ' ')
-                           || (*aptr == 13)
-                           || (*aptr == 10)) {
-                               memset(aptr, 0, sizeof(smi.meta_content_type) - (aptr - smi.meta_content_type));
-                       }
-                       else aptr++;
-               }
-       }
-
-       CC->redirect_buffer = PlainMessageBuf;
-       CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
-       smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
-       CC->redirect_buffer = NULL;
-
-       syslog(LOG_INFO,
-              "migrate: setting message #%ld meta data to: refcount=%d, bodylength=%ld, content-type: %s",
-              smi.meta_msgnum,
-              smi.meta_refcount,
-              smi.meta_rfc822_length,
-              smi.meta_content_type
-       );
-
-       PutMetaData(&smi);
-       CM_Free(msg);
-       return 0;
-}
-
-
-void migr_check_room_msg(long msgnum, void *userdata) {
-       fprintf(migr_global_message_list, "%ld %s\n", msgnum, CC->room.QRname);
-}
-
-
-void migr_check_rooms_backend(struct ctdlroom *buf, void *data) {
-       // message list goes inside this tag
-       CtdlGetRoom(&CC->room, buf->QRname);
-       CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, migr_check_room_msg, NULL);
-}
-
-
-void RemoveMessagesFromRooms(StrBuf *RoomNameVec, long msgnum) {
-       struct MetaData smi;
-       const char *Pos = NULL;
-       StrBuf *oneRoom = NewStrBuf();
-
-       syslog(LOG_INFO, "migrate: removing message pointer %ld from these rooms: %s", msgnum, ChrPtr(RoomNameVec));
-
-       while (Pos != StrBufNOTNULL){
-               StrBufExtract_NextToken(oneRoom, RoomNameVec, &Pos, '|');
-               CtdlDeleteMessages(ChrPtr(oneRoom), &msgnum, 1, "");
-       };
-       GetMetaData(&smi, msgnum);
-       AdjRefCount(msgnum, -smi.meta_refcount);
-}
-
-
-void migr_do_restore_meta(void) {
-       char buf[SIZ];
-       int failGetMessage;
-       long msgnum = 0;
-       int lastnum = 0;
-       int refcount = 0;
-       CitContext *Ctx;
-       char *prn;
-       StrBuf *RoomNames;
-       char cmd[SIZ];
-
-       migr_global_message_list = fopen(migr_tempfilename1, "w");
-       if (migr_global_message_list != NULL) {
-               CtdlForEachRoom(migr_check_rooms_backend, NULL);
-               fclose(migr_global_message_list);
-       }
-
-       // Process the 'global' message list.  (Sort it and remove dups.
-       // Dups are ok because a message may be in more than one room, but
-       // this will be handled by exporting the reference count, not by
-       // exporting the message multiple times.)
-       snprintf(cmd, sizeof cmd, "sort -n <%s >%s", migr_tempfilename1, migr_tempfilename2);
-       if (system(cmd) != 0) {
-               syslog(LOG_ERR, "migrate: error %d", errno);
-       }
-
-       RoomNames = NewStrBuf();
-       Ctx = CC;
-       migr_global_message_list = fopen(migr_tempfilename2, "r");
-       if (migr_global_message_list != NULL) {
-               syslog(LOG_INFO, "migrate: opened %s", migr_tempfilename1);
-               while ((Ctx->kill_me == 0) && 
-                      (fgets(buf, sizeof(buf), migr_global_message_list) != NULL)) {
-                       msgnum = atol(buf);
-                       if (msgnum == 0L) 
-                               continue;
-                       if (lastnum == 0) {
-                               lastnum = msgnum;
-                       }
-                       prn = strchr(buf, ' ');
-                       if (lastnum != msgnum) {
-                               failGetMessage = migr_restore_message_metadata(lastnum, refcount);
-                               if (failGetMessage) {
-                                       RemoveMessagesFromRooms(RoomNames, lastnum);
-                               }
-                               refcount = 1;
-                               lastnum = msgnum;
-                               if (prn != NULL) {
-                                       StrBufPlain(RoomNames, prn + 1, -1);
-                               }
-                               StrBufTrim(RoomNames);
-                       }
-                       else {
-                               if (prn != NULL) {
-                                       if (StrLength(RoomNames) > 0) {
-                                               StrBufAppendBufPlain(RoomNames, HKEY("|"), 0);
-                                       }
-                                       StrBufAppendBufPlain(RoomNames, prn, -1, 1);
-                                       StrBufTrim(RoomNames);
-                               }
-                               refcount ++;
-                       }
-                       lastnum = msgnum;
-               }
-               failGetMessage = migr_restore_message_metadata(msgnum, refcount);
-               if (failGetMessage) {
-                       RemoveMessagesFromRooms(RoomNames, lastnum);
-               }
-               fclose(migr_global_message_list);
-       }
-
-       migr_restore_message_metadata(-1L, -1); // This frees the encoding buffer
-       cprintf("%d system analysis completed", CIT_OK);
-       Ctx->kill_me = 1;
-}
-
-
-// ******************************************************************************
-// *                         Dispatcher, Common code                            *
-// ******************************************************************************
-void cmd_migr(char *cmdbuf) {
-       char cmd[32];
-       
-       if (CtdlAccessCheck(ac_aide)) return;
-       
-       if (CtdlTrySingleUser()) {
-               CtdlDisableHouseKeeping();
-               CtdlMakeTempFileName(migr_tempfilename1, sizeof migr_tempfilename1);
-               CtdlMakeTempFileName(migr_tempfilename2, sizeof migr_tempfilename2);
-
-               extract_token(cmd, cmdbuf, 0, '|', sizeof cmd);
-               if (!strcasecmp(cmd, "export")) {
-                       migr_do_export();
-               }
-               else if (!strcasecmp(cmd, "import")) {
-                       migr_do_import();
-               }
-               else if (!strcasecmp(cmd, "listdirs")) {
-                       migr_do_listdirs();
-               }
-               else if (!strcasecmp(cmd, "restoremeta")) {
-                       migr_do_restore_meta();
-               }
-               else {
-                       cprintf("%d illegal command\n", ERROR + ILLEGAL_VALUE);
-               }
-
-               unlink(migr_tempfilename1);
-               unlink(migr_tempfilename2);
-
-               CtdlEnableHouseKeeping();
-               CtdlEndSingleUser();
-       }
-       else {
-               cprintf("%d The migrator is already running.\n", ERROR + RESOURCE_BUSY);
-       }
-}
-
-
-// ******************************************************************************
-// *                              Module Hook                                   *
-// ******************************************************************************
-
-CTDL_MODULE_INIT(migrate)
-{
-       if (!threading)
-       {
-               CtdlRegisterProtoHook(cmd_migr, "MIGR", "Across-the-wire migration");
-       }
-       
-       // return our module name for the log
-       return "migrate";
-}
diff --git a/citadel/modules/newuser/.gitignore b/citadel/modules/newuser/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/newuser/serv_newuser.c b/citadel/modules/newuser/serv_newuser.c
deleted file mode 100644 (file)
index 146dae7..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Automatically copies the contents of a "New User Greetings" room to the
- * inbox of any new user upon account creation.
- *
- * Copyright (c) 1987-2012 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-/*
- * Name of the New User Greetings room.
- */
-#define NEWUSERGREETINGS       "New User Greetings"
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "ctdl_module.h"
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-
-/*
- * Copy the contents of the New User Greetings> room to the user's Mail> room.
- */
-void CopyNewUserGreetings(void) {
-       struct cdbdata *cdbfr;
-       long *msglist = NULL;
-       int num_msgs = 0;
-       char mailboxname[ROOMNAMELEN];
-
-
-       /* Only do this for new users. */
-       if (CC->user.timescalled != 1) return;
-
-       /* This user's mailbox. */
-       CtdlMailboxName(mailboxname, sizeof mailboxname, &CC->user, MAILROOM);
-
-       /* Go to the source room ... bail out silently if it's not there,
-        * or if it's not private.
-        */
-       if (CtdlGetRoom(&CC->room, NEWUSERGREETINGS) != 0) return;
-       if ((CC->room.QRflags & QR_PRIVATE) == 0) return;
-
-       cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
-
-       if (cdbfr != NULL) {
-               msglist = malloc(cdbfr->len);
-               memcpy(msglist, cdbfr->ptr, cdbfr->len);
-               num_msgs = cdbfr->len / sizeof(long);
-               cdb_free(cdbfr);
-       }
-
-       if (num_msgs > 0) {
-               CtdlSaveMsgPointersInRoom(mailboxname, msglist, num_msgs, 1, NULL, 0);
-       }
-
-       /* Now free the memory we used, and go away. */
-       if (msglist != NULL) free(msglist);
-}
-
-
-CTDL_MODULE_INIT(newuser)
-{
-       if (!threading)
-       {
-               CtdlRegisterSessionHook(CopyNewUserGreetings, EVT_LOGIN, PRIO_LOGIN + 1);
-       }
-       
-       /* return our module name for the log */
-       return "newuser";
-}
diff --git a/citadel/modules/nntp/serv_nntp.c b/citadel/modules/nntp/serv_nntp.c
deleted file mode 100644 (file)
index 135d773..0000000
+++ /dev/null
@@ -1,1170 +0,0 @@
-//
-// NNTP server module (RFC 3977)
-//
-// Copyright (c) 2014-2020 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//  
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <termios.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <syslog.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "room_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "genstamp.h"
-#include "domain.h"
-#include "clientsocket.h"
-#include "locate_host.h"
-#include "citadel_dirs.h"
-#include "ctdl_module.h"
-#include "serv_nntp.h"
-
-#ifndef __FreeBSD__
-extern long timezone;
-#endif
-
-//
-// Tests whether the supplied string is a valid newsgroup name
-// Returns true (nonzero) or false (0)
-//
-int is_valid_newsgroup_name(char *name) {
-       char *ptr = name;
-       int has_a_letter = 0;
-       int num_dots = 0;
-
-       if (!ptr) return(0);
-       if (!strncasecmp(name, "ctdl.", 5)) return(0);
-
-       while (*ptr != 0) {
-
-               if (isalpha(ptr[0])) {
-                       has_a_letter = 1;
-               }
-
-               if (ptr[0] == '.') {
-                       ++num_dots;
-               }
-
-               if (    (isalnum(ptr[0]))
-                       || (ptr[0] == '.')
-                       || (ptr[0] == '+')
-                       || (ptr[0] == '-')
-               ) {
-                       ++ptr;
-               }
-               else {
-                       return(0);
-               }
-       }
-       return( (has_a_letter) && (num_dots >= 1) ) ;
-}
-
-
-//
-// Convert a Citadel room name to a valid newsgroup name
-//
-void room_to_newsgroup(char *target, char *source, size_t target_size) {
-
-       if (!target) return;
-       if (!source) return;
-
-       if (is_valid_newsgroup_name(source)) {
-               strncpy(target, source, target_size);
-               return;
-       }
-
-       strcpy(target, "ctdl.");
-       int len = 5;
-       char *ptr = source;
-       char ch;
-
-       while (ch=*ptr++, ch!=0) {
-               if (len >= target_size) return;
-               if (    (isalnum(ch))
-                       || (ch == '.')
-                       || (ch == '-')
-               ) {
-                       target[len++] = tolower(ch);
-                       target[len] = 0;
-               }
-               else {
-                       target[len++] = '+' ;
-                       sprintf(&target[len], "%02x", ch);
-                       len += 2;
-                       target[len] = 0;
-               }
-       }
-}
-
-
-//
-// Convert a newsgroup name to a Citadel room name.
-// This function recognizes names converted with room_to_newsgroup() and restores them with full fidelity.
-//
-void newsgroup_to_room(char *target, char *source, size_t target_size) {
-
-       if (!target) return;
-       if (!source) return;
-
-       if (strncasecmp(source, "ctdl.", 5)) {                  // not a converted room name; pass through as-is
-               strncpy(target, source, target_size);
-               return;
-       }
-
-       target[0] = 0;
-       int len = 0;
-       char *ptr = &source[5];
-       char ch;
-
-       while (ch=*ptr++, ch!=0) {
-               if (len >= target_size) return;
-               if (ch == '+') {
-                       char hex[3];
-                       long digit;
-                       hex[0] = *ptr++;
-                       hex[1] = *ptr++;
-                       hex[2] = 0;
-                       digit = strtol(hex, NULL, 16);
-                       ch = (char)digit;
-               }
-               target[len++] = ch;
-               target[len] = 0;
-       }
-}
-
-
-//
-// Here's where our NNTP session begins its happy day.
-//
-void nntp_greeting(void) {
-       strcpy(CC->cs_clientname, "NNTP session");
-       CC->cs_flags |= CS_STEALTH;
-
-       CC->session_specific_data = malloc(sizeof(citnntp));
-       citnntp *nntpstate = (citnntp *) CC->session_specific_data;
-       memset(nntpstate, 0, sizeof(citnntp));
-
-       if (CC->nologin==1) {
-               cprintf("451 Too many connections are already open; please try again later.\r\n");
-               CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED;
-               return;
-       }
-
-       // Display the standard greeting
-       cprintf("200 %s NNTP Citadel server is not finished yet\r\n", CtdlGetConfigStr("c_fqdn"));
-}
-
-
-//
-// NNTPS is just like NNTP, except it goes crypto right away.
-//
-void nntps_greeting(void) {
-       CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
-#ifdef HAVE_OPENSSL
-       if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;          /* kill session if no crypto */
-#endif
-       nntp_greeting();
-}
-
-
-//
-// implements the STARTTLS command
-//
-void nntp_starttls(void) {
-       char ok_response[SIZ];
-       char nosup_response[SIZ];
-       char error_response[SIZ];
-
-       sprintf(ok_response, "382 Begin TLS negotiation now\r\n");
-       sprintf(nosup_response, "502 Can not initiate TLS negotiation\r\n");
-       sprintf(error_response, "580 Internal error\r\n");
-       CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
-}
-
-
-//
-// Implements the CAPABILITY command
-//
-void nntp_capabilities(void) {
-       cprintf("101 Capability list:\r\n");
-       cprintf("IMPLEMENTATION Citadel %d\r\n", REV_LEVEL);
-       cprintf("VERSION 2\r\n");
-       cprintf("READER\r\n");
-       cprintf("MODE-READER\r\n");
-       cprintf("LIST ACTIVE NEWSGROUPS\r\n");
-       cprintf("OVER\r\n");
-#ifdef HAVE_OPENSSL
-       cprintf("STARTTLS\r\n");
-#endif
-       if (!CC->logged_in) {
-               cprintf("AUTHINFO USER\r\n");
-       }
-       cprintf(".\r\n");
-}
-
-
-// 
-// Implements the QUIT command
-//
-void nntp_quit(void) {
-       cprintf("221 Goodbye...\r\n");
-       CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
-}
-
-
-//
-// Implements the AUTHINFO USER command (RFC 4643)
-//
-void nntp_authinfo_user(const char *username) {
-       int a = CtdlLoginExistingUser(username);
-       switch (a) {
-       case login_already_logged_in:
-               cprintf("482 Already logged in\r\n");
-               return;
-       case login_too_many_users:
-               cprintf("481 Too many users are already online (maximum is %d)\r\n", CtdlGetConfigInt("c_maxsessions"));
-               return;
-       case login_ok:
-               cprintf("381 Password required for %s\r\n", CC->curr_user);
-               return;
-       case login_not_found:
-               cprintf("481 %s not found\r\n", username);
-               return;
-       default:
-               cprintf("502 Internal error\r\n");
-       }
-}
-
-
-//
-// Implements the AUTHINFO PASS command (RFC 4643)
-//
-void nntp_authinfo_pass(const char *buf) {
-       int a;
-
-       a = CtdlTryPassword(buf, strlen(buf));
-
-       switch (a) {
-       case pass_already_logged_in:
-               cprintf("482 Already logged in\r\n");
-               return;
-       case pass_no_user:
-               cprintf("482 Authentication commands issued out of sequence\r\n");
-               return;
-       case pass_wrong_password:
-               cprintf("481 Authentication failed\r\n");
-               return;
-       case pass_ok:
-               cprintf("281 Authentication accepted\r\n");
-               return;
-       }
-}
-
-
-//
-// Implements the AUTHINFO extension (RFC 4643) in USER/PASS mode
-//
-void nntp_authinfo(const char *cmd) {
-
-       if (!strncasecmp(cmd, "authinfo user ", 14)) {
-               nntp_authinfo_user(&cmd[14]);
-       }
-
-       else if (!strncasecmp(cmd, "authinfo pass ", 14)) {
-               nntp_authinfo_pass(&cmd[14]);
-       }
-
-       else {
-               cprintf("502 command unavailable\r\n");
-       }
-}
-
-
-//
-// Utility function to fetch the current list of message numbers in a room
-//
-struct nntp_msglist nntp_fetch_msglist(struct ctdlroom *qrbuf) {
-       struct nntp_msglist nm;
-       struct cdbdata *cdbfr;
-
-       cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
-       if (cdbfr != NULL) {
-               nm.msgnums = (long*)cdbfr->ptr;
-               cdbfr->ptr = NULL;
-               nm.num_msgs = cdbfr->len / sizeof(long);
-               cdbfr->len = 0;
-               cdb_free(cdbfr);
-       } else {
-               nm.num_msgs = 0;
-               nm.msgnums = NULL;
-       }
-       return(nm);
-}
-
-
-//
-// Output a room name (newsgroup name) in formats required for LIST and NEWGROUPS command
-//
-void output_roomname_in_list_format(struct ctdlroom *qrbuf, int which_format, char *wildmat_pattern) {
-       char n_name[1024];
-       struct nntp_msglist nm;
-       long low_water_mark = 0;
-       long high_water_mark = 0;
-
-       room_to_newsgroup(n_name, qrbuf->QRname, sizeof n_name);
-
-       if ((wildmat_pattern != NULL) && (!IsEmptyStr(wildmat_pattern))) {
-               if (!wildmat(n_name, wildmat_pattern)) {
-                       return;
-               }
-       }
-
-       nm = nntp_fetch_msglist(qrbuf);
-       if ((nm.num_msgs > 0) && (nm.msgnums != NULL)) {
-               low_water_mark = nm.msgnums[0];
-               high_water_mark = nm.msgnums[nm.num_msgs - 1];
-       }
-
-       // Only the mandatory formats are supported
-       switch(which_format) {
-       case NNTP_LIST_ACTIVE:
-               // FIXME we have hardcoded "n" for "no posting allowed" -- fix when we add posting
-               cprintf("%s %ld %ld n\r\n", n_name, high_water_mark, low_water_mark);
-               break;
-       case NNTP_LIST_NEWSGROUPS:
-               cprintf("%s %s\r\n", n_name, qrbuf->QRname);
-               break;
-       }
-
-       if (nm.msgnums != NULL) {
-               free(nm.msgnums);
-       }
-}
-
-
-//
-// Called once per room by nntp_newgroups() to qualify and possibly output a single room
-//
-void nntp_newgroups_backend(struct ctdlroom *qrbuf, void *data) {
-       int ra;
-       int view;
-       time_t thetime = *(time_t *)data;
-
-       CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
-
-       /*
-        * The "created after <date/time>" heuristics depend on the happy coincidence
-        * that for a very long time we have used a unix timestamp as the room record's
-        * generation number (QRgen).  When this module is merged into the master
-        * source tree we should rename QRgen to QR_create_time or something like that.
-        */
-
-       if (ra & UA_KNOWN) {
-               if (qrbuf->QRgen >= thetime) {
-                       output_roomname_in_list_format(qrbuf, NNTP_LIST_ACTIVE, NULL);
-               }
-       }
-}
-
-
-//
-// Implements the NEWGROUPS command
-//
-void nntp_newgroups(const char *cmd) {
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
-       char stringy_date[16];
-       char stringy_time[16];
-       char stringy_gmt[16];
-       struct tm tm;
-       time_t thetime;
-
-       extract_token(stringy_date, cmd, 1, ' ', sizeof stringy_date);
-       extract_token(stringy_time, cmd, 2, ' ', sizeof stringy_time);
-       extract_token(stringy_gmt, cmd, 3, ' ', sizeof stringy_gmt);
-
-       memset(&tm, 0, sizeof tm);
-       if (strlen(stringy_date) == 6) {
-               sscanf(stringy_date, "%2d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday);
-               tm.tm_year += 100;
-       }
-       else {
-               sscanf(stringy_date, "%4d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday);
-               tm.tm_year -= 1900;
-       }
-       tm.tm_mon -= 1;         // tm_mon is zero based (0=January)
-       tm.tm_isdst = (-1);     // let the C library figure out whether DST is in effect
-       sscanf(stringy_time, "%2d%2d%2d", &tm.tm_hour, &tm.tm_min ,&tm.tm_sec);
-       thetime = mktime(&tm);
-       if (!strcasecmp(stringy_gmt, "GMT")) {
-               tzset();
-#ifdef __FreeBSD__
-               thetime += &tm.tm_gmtoff;
-#else
-               thetime += timezone;
-#endif
-       }
-
-
-       cprintf("231 list of new newsgroups follows\r\n");
-       CtdlGetUser(&CC->user, CC->curr_user);
-       CtdlForEachRoom(nntp_newgroups_backend, &thetime);
-       cprintf(".\r\n");
-}
-
-
-//
-// Called once per room by nntp_list() to qualify and possibly output a single room
-//
-void nntp_list_backend(struct ctdlroom *qrbuf, void *data) {
-       int ra;
-       int view;
-       struct nntp_list_data *nld = (struct nntp_list_data *)data;
-
-       CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
-       if (ra & UA_KNOWN) {
-               output_roomname_in_list_format(qrbuf, nld->list_format, nld->wildmat_pattern);
-       }
-}
-
-
-//
-// Implements the LIST commands
-//
-void nntp_list(const char *cmd) {
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
-       char list_format[64];
-       char wildmat_pattern[1024];
-       struct nntp_list_data nld;
-
-       extract_token(list_format, cmd, 1, ' ', sizeof list_format);
-       extract_token(wildmat_pattern, cmd, 2, ' ', sizeof wildmat_pattern);
-
-       if (strlen(wildmat_pattern) > 0) {
-               nld.wildmat_pattern = wildmat_pattern;
-       }
-       else {
-               nld.wildmat_pattern = NULL;
-       }
-
-       if ( (strlen(cmd) < 6) || (!strcasecmp(list_format, "ACTIVE")) ) {
-               nld.list_format = NNTP_LIST_ACTIVE;
-       }
-       else if (!strcasecmp(list_format, "NEWSGROUPS")) {
-               nld.list_format = NNTP_LIST_NEWSGROUPS;
-       }
-       else if (!strcasecmp(list_format, "OVERVIEW.FMT")) {
-               nld.list_format = NNTP_LIST_OVERVIEW_FMT;
-       }
-       else {
-               cprintf("501 syntax error , unsupported list format\r\n");
-               return;
-       }
-
-       // OVERVIEW.FMT delivers a completely different type of data than all of the
-       // other LIST commands.  It's a stupid place to put it.  But that's how it's
-       // written into RFC3977, so we have to handle it here.
-       if (nld.list_format == NNTP_LIST_OVERVIEW_FMT) {
-               cprintf("215 Order of fields in overview database.\r\n");
-               cprintf("Subject:\r\n");
-               cprintf("From:\r\n");
-               cprintf("Date:\r\n");
-               cprintf("Message-ID:\r\n");
-               cprintf("References:\r\n");
-               cprintf("Bytes:\r\n");
-               cprintf("Lines:\r\n");
-               cprintf(".\r\n");
-               return;
-       }
-
-       cprintf("215 list of newsgroups follows\r\n");
-       CtdlGetUser(&CC->user, CC->curr_user);
-       CtdlForEachRoom(nntp_list_backend, &nld);
-       cprintf(".\r\n");
-}
-
-
-//
-// Implement HELP command.
-//
-void nntp_help(void) {
-       cprintf("100 This is the Citadel NNTP service.\r\n");
-       cprintf("RTFM http://www.ietf.org/rfc/rfc3977.txt\r\n");
-       cprintf(".\r\n");
-}
-
-
-//
-// Implement DATE command.
-//
-void nntp_date(void) {
-       time_t now;
-       struct tm nowLocal;
-       struct tm nowUtc;
-       char tsFromUtc[32];
-
-       now = time(NULL);
-       localtime_r(&now, &nowLocal);
-       gmtime_r(&now, &nowUtc);
-
-       strftime(tsFromUtc, sizeof(tsFromUtc), "%Y%m%d%H%M%S", &nowUtc);
-
-       cprintf("111 %s\r\n", tsFromUtc);
-}
-
-
-//
-// back end for the LISTGROUP command , called for each message number
-//
-void nntp_listgroup_backend(long msgnum, void *userdata) {
-
-       struct listgroup_range *lr = (struct listgroup_range *)userdata;
-
-       // check range if supplied
-       if (msgnum < lr->lo) return;
-       if ((lr->hi != 0) && (msgnum > lr->hi)) return;
-
-       cprintf("%ld\r\n", msgnum);
-}
-
-
-//
-// Implements the GROUP and LISTGROUP commands
-//
-void nntp_group(const char *cmd) {
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
-       citnntp *nntpstate = (citnntp *) CC->session_specific_data;
-       char verb[16];
-       char requested_group[1024];
-       char message_range[256];
-       char range_lo[256];
-       char range_hi[256];
-       char requested_room[ROOMNAMELEN];
-       char augmented_roomname[ROOMNAMELEN];
-       int c = 0;
-       int ok = 0;
-       int ra = 0;
-       struct ctdlroom QRscratch;
-       int msgs, new;
-       long oldest,newest;
-       struct listgroup_range lr;
-
-       extract_token(verb, cmd, 0, ' ', sizeof verb);
-       extract_token(requested_group, cmd, 1, ' ', sizeof requested_group);
-       extract_token(message_range, cmd, 2, ' ', sizeof message_range);
-       extract_token(range_lo, message_range, 0, '-', sizeof range_lo);
-       extract_token(range_hi, message_range, 1, '-', sizeof range_hi);
-       lr.lo = atoi(range_lo);
-       lr.hi = atoi(range_hi);
-
-       /* In LISTGROUP mode we can specify an empty name for 'currently selected' */
-       if ((!strcasecmp(verb, "LISTGROUP")) && (IsEmptyStr(requested_group))) {
-               room_to_newsgroup(requested_group, CC->room.QRname, sizeof requested_group);
-       }
-
-       /* First try a regular match */
-       newsgroup_to_room(requested_room, requested_group, sizeof requested_room);
-       c = CtdlGetRoom(&QRscratch, requested_room);
-
-       /* Then try a mailbox name match */
-       if (c != 0) {
-               CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CC->user, requested_room);
-               c = CtdlGetRoom(&QRscratch, augmented_roomname);
-               if (c == 0) {
-                       safestrncpy(requested_room, augmented_roomname, sizeof(requested_room));
-               }
-       }
-
-       /* If the room exists, check security/access */
-       if (c == 0) {
-               /* See if there is an existing user/room relationship */
-               CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
-
-               /* normal clients have to pass through security */
-               if (ra & UA_KNOWN) {
-                       ok = 1;
-               }
-       }
-
-       /* Fail here if no such room */
-       if (!ok) {
-               cprintf("411 no such newsgroup\r\n");
-               return;
-       }
-
-
-       /*
-        * CtdlUserGoto() formally takes us to the desired room, happily returning
-        * the number of messages and number of new messages.
-        */
-       memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom));
-       CtdlUserGoto(NULL, 0, 0, &msgs, &new, &oldest, &newest);
-       cprintf("211 %d %ld %ld %s\r\n", msgs, oldest, newest, requested_group);
-
-       // If this is a GROUP command, set the "current article number" to zero, and then stop here.
-       if (!strcasecmp(verb, "GROUP")) {
-               nntpstate->current_article_number = oldest;
-               return;
-       }
-
-       // If we get to this point we are running a LISTGROUP command.  Fetch those message numbers.
-       CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, nntp_listgroup_backend, &lr);
-       cprintf(".\r\n");
-}
-
-
-//
-// Implements the MODE command
-//
-void nntp_mode(const char *cmd) {
-
-       char which_mode[16];
-
-       extract_token(which_mode, cmd, 1, ' ', sizeof which_mode);
-
-       if (!strcasecmp(which_mode, "reader")) {
-               // FIXME implement posting and change to 200
-               cprintf("201 Reader mode activated\r\n");
-       }
-       else {
-               cprintf("501 unknown mode\r\n");
-       }
-}
-
-
-//
-// Implements the ARTICLE, HEAD, BODY, and STAT commands.
-// (These commands all accept the same parameters; they differ only in how they output the retrieved message.)
-//
-void nntp_article(const char *cmd) {
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
-       citnntp *nntpstate = (citnntp *) CC->session_specific_data;
-       char which_command[16];
-       int acmd = 0;
-       char requested_article[256];
-       long requested_msgnum = 0;
-       char *lb, *rb = NULL;
-       int must_change_currently_selected_article = 0;
-
-       // We're going to store one of these values in the variable 'acmd' so that
-       // we can quickly check later which version of the output we want.
-       enum {
-               ARTICLE,
-               HEAD,
-               BODY,
-               STAT
-       };
-
-       extract_token(which_command, cmd, 0, ' ', sizeof which_command);
-
-       if (!strcasecmp(which_command, "article")) {
-               acmd = ARTICLE;
-       }
-       else if (!strcasecmp(which_command, "head")) {
-               acmd = HEAD;
-       }
-       else if (!strcasecmp(which_command, "body")) {
-               acmd = BODY;
-       }
-       else if (!strcasecmp(which_command, "stat")) {
-               acmd = STAT;
-       }
-       else {
-               cprintf("500 I'm afraid I can't do that.\r\n");
-               return;
-       }
-
-       // Which NNTP command was issued, determines whether we will fetch headers, body, or both.
-       int                     headers_only = HEADERS_ALL;
-       if (acmd == HEAD)       headers_only = HEADERS_FAST;
-       else if (acmd == BODY)  headers_only = HEADERS_NONE;
-       else if (acmd == STAT)  headers_only = HEADERS_FAST;
-
-       // now figure out what the client is asking for.
-       extract_token(requested_article, cmd, 1, ' ', sizeof requested_article);
-       lb = strchr(requested_article, '<');
-       rb = strchr(requested_article, '>');
-       requested_msgnum = atol(requested_article);
-
-       // If no article number or message-id is specified, the client wants the "currently selected article"
-       if (IsEmptyStr(requested_article)) {
-               if (nntpstate->current_article_number < 1) {
-                       cprintf("420 No current article selected\r\n");
-                       return;
-               }
-               requested_msgnum = nntpstate->current_article_number;
-               must_change_currently_selected_article = 1;
-               // got it -- now fall through and keep going
-       }
-
-       // If the requested article is numeric, it maps directly to a message number.  Good.
-       else if (requested_msgnum > 0) {
-               must_change_currently_selected_article = 1;
-               // good -- fall through and keep going
-       }
-
-       // If the requested article has angle brackets, the client wants a specific message-id.
-       // We don't know how to do that yet.
-       else if ( (lb != NULL) && (rb != NULL) && (lb < rb) ) {
-               must_change_currently_selected_article = 0;
-               cprintf("500 I don't know how to fetch by message-id yet.\r\n");        // FIXME
-               return;
-       }
-
-       // Anything else is noncompliant gobbledygook and should die in a car fire.
-       else {
-               must_change_currently_selected_article = 0;
-               cprintf("500 syntax error\r\n");
-               return;
-       }
-
-       // At this point we know the message number of the "article" being requested.
-       // We have an awesome API call that does all the heavy lifting for us.
-       char *fetched_message_id = NULL;
-       CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
-       int fetch = CtdlOutputMsg(requested_msgnum,
-                       MT_RFC822,              // output in RFC822 format ... sort of
-                       headers_only,           // headers, body, or both?
-                       0,                      // don't do Citadel protocol responses
-                       1,                      // CRLF newlines
-                       NULL,                   // teh whole thing, not just a section
-                       0,                      // no flags yet ... maybe new ones for Path: etc ?
-                       NULL,
-                       NULL,
-                       &fetched_message_id     // extract the message ID from the message as we go...
-       );
-       StrBuf *msgtext = CC->redirect_buffer;
-       CC->redirect_buffer = NULL;
-
-       if (fetch != om_ok) {
-               cprintf("423 no article with that number\r\n");
-               FreeStrBuf(&msgtext);
-               return;
-       }
-
-       // RFC3977 6.2.1.2 specifes conditions under which the "currently selected article"
-       // MUST or MUST NOT be set to the message we just referenced.
-       if (must_change_currently_selected_article) {
-               nntpstate->current_article_number = requested_msgnum;
-       }
-
-       // Now give the client what it asked for.
-       if (acmd == ARTICLE) {
-               cprintf("220 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
-       }
-       if (acmd == HEAD) {
-               cprintf("221 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
-       }
-       if (acmd == BODY) {
-               cprintf("222 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
-       }
-       if (acmd == STAT) {
-               cprintf("223 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
-               FreeStrBuf(&msgtext);
-               return;
-       }
-
-       client_write(SKEY(msgtext));
-       cprintf(".\r\n");                       // this protocol uses a dot terminator
-       FreeStrBuf(&msgtext);
-       if (fetched_message_id) free(fetched_message_id);
-}
-
-
-//
-// Utility function for nntp_last_next() that turns a msgnum into a message ID.
-// The memory for the returned string is pwnz0red by the caller.
-//
-char *message_id_from_msgnum(long msgnum) {
-       char *fetched_message_id = NULL;
-       CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
-       CtdlOutputMsg(msgnum,
-                       MT_RFC822,              // output in RFC822 format ... sort of
-                       HEADERS_FAST,           // headers, body, or both?
-                       0,                      // don't do Citadel protocol responses
-                       1,                      // CRLF newlines
-                       NULL,                   // teh whole thing, not just a section
-                       0,                      // no flags yet ... maybe new ones for Path: etc ?
-                       NULL,
-                       NULL,
-                       &fetched_message_id     // extract the message ID from the message as we go...
-       );
-       StrBuf *msgtext = CC->redirect_buffer;
-       CC->redirect_buffer = NULL;
-
-       FreeStrBuf(&msgtext);
-       return(fetched_message_id);
-}
-
-
-//
-// The LAST and NEXT commands are so similar that they are handled by a single function.
-//
-void nntp_last_next(const char *cmd) {
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
-       citnntp *nntpstate = (citnntp *) CC->session_specific_data;
-       char which_command[16];
-       int acmd = 0;
-
-       // We're going to store one of these values in the variable 'acmd' so that
-       // we can quickly check later which version of the output we want.
-       enum {
-               NNTP_LAST,
-               NNTP_NEXT
-       };
-
-       extract_token(which_command, cmd, 0, ' ', sizeof which_command);
-
-       if (!strcasecmp(which_command, "last")) {
-               acmd = NNTP_LAST;
-       }
-       else if (!strcasecmp(which_command, "next")) {
-               acmd = NNTP_NEXT;
-       }
-       else {
-               cprintf("500 I'm afraid I can't do that.\r\n");
-               return;
-       }
-
-       // ok, here we go ... fetch the msglist so we can figure out our place in the universe
-       struct nntp_msglist nm;
-       int i = 0;
-       long selected_msgnum = 0;
-       char *message_id = NULL;
-
-       nm = nntp_fetch_msglist(&CC->room);
-       if ((nm.num_msgs < 0) || (nm.msgnums == NULL)) {
-               cprintf("500 something bad happened\r\n");
-               return;
-       }
-
-       if ( (acmd == NNTP_LAST) && (nm.num_msgs == 0) ) {
-                       cprintf("422 no previous article in this group\r\n");   // nothing here
-       }
-
-       else if ( (acmd == NNTP_LAST) && (nntpstate->current_article_number <= nm.msgnums[0]) ) {
-                       cprintf("422 no previous article in this group\r\n");   // already at the beginning
-       }
-
-       else if (acmd == NNTP_LAST) {
-               for (i=0; ((i<nm.num_msgs)&&(selected_msgnum<=0)); ++i) {
-                       if ( (nm.msgnums[i] >= nntpstate->current_article_number) && (i > 0) ) {
-                               selected_msgnum = nm.msgnums[i-1];
-                       }
-               }
-               if (selected_msgnum > 0) {
-                       nntpstate->current_article_number = selected_msgnum;
-                       message_id = message_id_from_msgnum(nntpstate->current_article_number);
-                       cprintf("223 %ld <%s>\r\n", nntpstate->current_article_number, message_id);
-                       if (message_id) free(message_id);
-               }
-               else {
-                       cprintf("422 no previous article in this group\r\n");
-               }
-       }
-
-       else if ( (acmd == NNTP_NEXT) && (nm.num_msgs == 0) ) {
-                       cprintf("421 no next article in this group\r\n");       // nothing here
-       }
-
-       else if ( (acmd == NNTP_NEXT) && (nntpstate->current_article_number >= nm.msgnums[nm.num_msgs-1]) ) {
-                       cprintf("421 no next article in this group\r\n");       // already at the end
-       }
-
-       else if (acmd == NNTP_NEXT) {
-               for (i=0; ((i<nm.num_msgs)&&(selected_msgnum<=0)); ++i) {
-                       if (nm.msgnums[i] > nntpstate->current_article_number) {
-                               selected_msgnum = nm.msgnums[i];
-                       }
-               }
-               if (selected_msgnum > 0) {
-                       nntpstate->current_article_number = selected_msgnum;
-                       message_id = message_id_from_msgnum(nntpstate->current_article_number);
-                       cprintf("223 %ld <%s>\r\n", nntpstate->current_article_number, message_id);
-                       if (message_id) free(message_id);
-               }
-               else {
-                       cprintf("421 no next article in this group\r\n");
-               }
-       }
-
-       // should never get here
-       else {
-               cprintf("500 internal error\r\n");
-       }
-
-       if (nm.msgnums != NULL) {
-               free(nm.msgnums);
-       }
-
-}
-
-
-//
-// back end for the XOVER command , called for each message number
-//
-void nntp_xover_backend(long msgnum, void *userdata) {
-
-       struct listgroup_range *lr = (struct listgroup_range *)userdata;
-
-       // check range if supplied
-       if (msgnum < lr->lo) return;
-       if ((lr->hi != 0) && (msgnum > lr->hi)) return;
-
-       struct CtdlMessage *msg = CtdlFetchMessage(msgnum, 0);
-       if (msg == NULL) {
-               return;
-       }
-
-       // Teh RFC says we need:
-       // -------------------------
-       // Subject header content
-       // From header content
-       // Date header content
-       // Message-ID header content
-       // References header content
-       // :bytes metadata item
-       // :lines metadata item
-
-       time_t msgtime = atol(msg->cm_fields[eTimestamp]);
-       char strtimebuf[26];
-       ctime_r(&msgtime, strtimebuf);
-
-       // here we go -- print the line o'data
-       cprintf("%ld\t%s\t%s <%s>\t%s\t%s\t%s\t100\t10\r\n",
-               msgnum,
-               msg->cm_fields[eMsgSubject],
-               msg->cm_fields[eAuthor],
-               msg->cm_fields[erFc822Addr],
-               strtimebuf,
-               msg->cm_fields[emessageId],
-               msg->cm_fields[eWeferences]
-       );
-
-       CM_Free(msg);
-}
-
-
-//
-//
-// XOVER is used by some clients, even if we don't offer it
-//
-void nntp_xover(const char *cmd) {
-       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
-       citnntp *nntpstate = (citnntp *) CC->session_specific_data;
-       char range[256];
-       struct listgroup_range lr;
-
-       extract_token(range, cmd, 1, ' ', sizeof range);
-       lr.lo = atol(range);
-       if (lr.lo <= 0) {
-               lr.lo = nntpstate->current_article_number;
-               lr.hi = nntpstate->current_article_number;
-       }
-       else {
-               char *dash = strchr(range, '-');
-               if (dash != NULL) {
-                       ++dash;
-                       lr.hi = atol(dash);
-                       if (lr.hi == 0) {
-                               lr.hi = LONG_MAX;
-                       }
-                       if (lr.hi < lr.lo) {
-                               lr.hi = lr.lo;
-                       }
-               }
-               else {
-                       lr.hi = lr.lo;
-               }
-       }
-
-       cprintf("224 Overview information follows\r\n");
-       CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, nntp_xover_backend, &lr);
-       cprintf(".\r\n");
-}
-
-
-// 
-// Main command loop for NNTP server sessions.
-//
-void nntp_command_loop(void) {
-       StrBuf *Cmd = NewStrBuf();
-       char cmdname[16];
-
-       time(&CC->lastcmd);
-       if (CtdlClientGetLine(Cmd) < 1) {
-               syslog(LOG_CRIT, "NNTP: client disconnected: ending session.\n");
-               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
-               FreeStrBuf(&Cmd);
-               return;
-       }
-       syslog(LOG_DEBUG, "NNTP: %s\n", ((!strncasecmp(ChrPtr(Cmd), "AUTHINFO", 8)) ? "AUTHINFO ..." : ChrPtr(Cmd)));
-       extract_token(cmdname, ChrPtr(Cmd), 0, ' ', sizeof cmdname);
-
-       // Rumpelstiltskin lookups are *awesome*
-
-       if (!strcasecmp(cmdname, "quit")) {
-               nntp_quit();
-       }
-
-       else if (!strcasecmp(cmdname, "help")) {
-               nntp_help();
-       }
-
-       else if (!strcasecmp(cmdname, "date")) {
-               nntp_date();
-       }
-
-       else if (!strcasecmp(cmdname, "capabilities")) {
-               nntp_capabilities();
-       }
-
-       else if (!strcasecmp(cmdname, "starttls")) {
-               nntp_starttls();
-       }
-
-       else if (!strcasecmp(cmdname, "authinfo")) {
-               nntp_authinfo(ChrPtr(Cmd));
-       }
-
-       else if (!strcasecmp(cmdname, "newgroups")) {
-               nntp_newgroups(ChrPtr(Cmd));
-       }
-
-       else if (!strcasecmp(cmdname, "list")) {
-               nntp_list(ChrPtr(Cmd));
-       }
-
-       else if (!strcasecmp(cmdname, "group")) {
-               nntp_group(ChrPtr(Cmd));
-       }
-
-       else if (!strcasecmp(cmdname, "listgroup")) {
-               nntp_group(ChrPtr(Cmd));
-       }
-
-       else if (!strcasecmp(cmdname, "mode")) {
-               nntp_mode(ChrPtr(Cmd));
-       }
-
-       else if (
-                       (!strcasecmp(cmdname, "article"))
-                       || (!strcasecmp(cmdname, "head"))
-                       || (!strcasecmp(cmdname, "body"))
-                       || (!strcasecmp(cmdname, "stat"))
-               )
-       {
-               nntp_article(ChrPtr(Cmd));
-       }
-
-       else if (
-                       (!strcasecmp(cmdname, "last"))
-                       || (!strcasecmp(cmdname, "next"))
-               )
-       {
-               nntp_last_next(ChrPtr(Cmd));
-       }
-
-       else if (
-                       (!strcasecmp(cmdname, "xover"))
-                       || (!strcasecmp(cmdname, "over"))
-               )
-       {
-               nntp_xover(ChrPtr(Cmd));
-       }
-
-       else {
-               cprintf("500 I'm afraid I can't do that.\r\n");
-       }
-
-       FreeStrBuf(&Cmd);
-}
-
-
-//     ****************************************************************************
-//                           MODULE INITIALIZATION STUFF
-//     ****************************************************************************
-
-
-//
-// This cleanup function blows away the temporary memory used by
-// the NNTP server.
-//
-void nntp_cleanup_function(void) {
-       /* Don't do this stuff if this is not an NNTP session! */
-       if (CC->h_command_function != nntp_command_loop) return;
-
-       syslog(LOG_DEBUG, "Performing NNTP cleanup hook\n");
-       citnntp *nntpstate = (citnntp *) CC->session_specific_data;
-       if (nntpstate != NULL) {
-               free(nntpstate);
-               nntpstate = NULL;
-       }
-}
-
-const char *CitadelServiceNNTP="NNTP";
-const char *CitadelServiceNNTPS="NNTPS";
-
-CTDL_MODULE_INIT(nntp)
-{
-       if (!threading)
-       {
-               CtdlRegisterServiceHook(CtdlGetConfigInt("c_nntp_port"),
-                                       NULL,
-                                       nntp_greeting,
-                                       nntp_command_loop,
-                                       NULL, 
-                                       CitadelServiceNNTP);
-
-#ifdef HAVE_OPENSSL
-               CtdlRegisterServiceHook(CtdlGetConfigInt("c_nntps_port"),
-                                       NULL,
-                                       nntps_greeting,
-                                       nntp_command_loop,
-                                       NULL,
-                                       CitadelServiceNNTPS);
-#endif
-
-               CtdlRegisterSessionHook(nntp_cleanup_function, EVT_STOP, PRIO_STOP + 250);
-       }
-       
-       /* return our module name for the log */
-       return "nntp";
-}
diff --git a/citadel/modules/nntp/serv_nntp.h b/citadel/modules/nntp/serv_nntp.h
deleted file mode 100644 (file)
index 672dd67..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-//
-// Header file for NNTP server module
-//
-// Copyright (c) 2014 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//  
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-
-
-// data returned by a message list fetch
-struct nntp_msglist {
-       int num_msgs;
-       long *msgnums;
-};
-
-
-// data passed by the LIST commands to its helper function
-struct nntp_list_data {
-       int list_format;
-       char *wildmat_pattern;
-};
-
-
-//
-// data passed between nntp_listgroup() and nntp_listgroup_backend()
-//
-struct listgroup_range {
-       long lo;
-       long hi;
-};
-
-
-typedef struct _citnntp {              // Information about the current session
-       long current_article_number;
-} citnntp;
-
-
-//
-// Various output formats for the LIST commands
-//
-enum {
-       NNTP_LIST_ACTIVE,
-       NNTP_LIST_ACTIVE_TIMES,
-       NNTP_LIST_DISTRIB_PATS,
-       NNTP_LIST_HEADERS,
-       NNTP_LIST_NEWSGROUPS,
-       NNTP_LIST_OVERVIEW_FMT
-};
-
-
-int wildmat(const char *text, const char *p);
-
diff --git a/citadel/modules/nntp/wildmat.c b/citadel/modules/nntp/wildmat.c
deleted file mode 100644 (file)
index 4047cfd..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-/* wildmat.h - NNTP wildmat processing functions
- *
- * Copyright (c) 1994-2008 Carnegie Mellon University.  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. The name "Carnegie Mellon University" must not be used to
- *    endorse or promote products derived from this software without
- *    prior written permission. For permission or any legal
- *    details, please contact
- *      Carnegie Mellon University
- *      Center for Technology Transfer and Enterprise Creation
- *      4615 Forbes Avenue
- *      Suite 302
- *      Pittsburgh, PA  15213
- *      (412) 268-7393, fax: (412) 268-7395
- *      innovation@andrew.cmu.edu
- *
- * 4. Redistributions of any form whatsoever must retain the following
- *    acknowledgment:
- *    "This product includes software developed by Computing Services
- *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
- *
- * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
- * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
- * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
- * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
- * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- *
- * $Id: wildmat.c,v 1.4 2010/01/06 17:01:47 murch Exp $
- */
-
-/*
-**
-**  Do shell-style pattern matching for ?, \, [], and * characters.
-**  Might not be robust in face of malformed patterns; e.g., "foo[a-"
-**  could cause a segmentation violation.  It is 8bit clean.
-**
-**  Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
-**  Rich $alz is now <rsalz@osf.org>.
-**  April, 1991:  Replaced mutually-recursive calls with in-line code
-**  for the star character.
-**
-**  Special thanks to Lars Mathiesen <thorinn@diku.dk> for the ABORT code.
-**  This can greatly speed up failing wildcard patterns.  For example:
-**     pattern: -*-*-*-*-*-*-12-*-*-*-m-*-*-*
-**     text 1:  -adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1
-**     text 2:  -adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1
-**  Text 1 matches with 51 calls, while text 2 fails with 54 calls.  Without
-**  the ABORT code, it takes 22310 calls to fail.  Ugh.  The following
-**  explanation is from Lars:
-**  The precondition that must be fulfilled is that DoMatch will consume
-**  at least one character in text.  This is true if *p is neither '*' nor
-**  '\0'.)  The last return has ABORT instead of FALSE to avoid quadratic
-**  behaviour in cases like pattern "*a*b*c*d" with text "abcxxxxx".  With
-**  FALSE, each star-loop has to run to the end of the text; with ABORT
-**  only the last one does.
-**
-**  Once the control of one instance of DoMatch enters the star-loop, that
-**  instance will return either TRUE or ABORT, and any calling instance
-**  will therefore return immediately after (without calling recursively
-**  again).  In effect, only one star-loop is ever active.  It would be
-**  possible to modify the code to maintain this context explicitly,
-**  eliminating all recursive calls at the cost of some complication and
-**  loss of clarity (and the ABORT stuff seems to be unclear enough by
-**  itself).  I think it would be unwise to try to get this into a
-**  released version unless you have a good test data base to try it out
-**  on.
-*/
-#include <stdio.h>
-#include <sys/types.h>
-
-
-
-#define TRUE                   1
-#define FALSE                  0
-#define ABORT                  -1
-
-
-    /* What character marks an inverted character class? */
-#define NEGATE_CLASS           '^'
-    /* Is "*" a common pattern? */
-#define OPTIMIZE_JUST_STAR
-    /* Do tar(1) matching rules, which ignore a trailing slash? */
-#undef MATCH_TAR_PATTERN
-
-
-/*
-**  Match text and p, return TRUE, FALSE, or ABORT.
-*/
-static int DoMatch(const char *text, const char *p)
-{
-    int                        last;
-    int                        matched;
-    int                        reverse;
-
-    for ( ; *p; text++, p++) {
-       if (*text == '\0' && *p != '*')
-           return ABORT;
-       switch (*p) {
-       case '\\':
-           /* Literal match with following character. */
-           p++;
-           /* FALLTHROUGH */
-       default:
-           if (*text != *p)
-               return FALSE;
-           continue;
-       case '?':
-           /* Match anything. */
-           continue;
-       case '*':
-           while (*++p == '*')
-               /* Consecutive stars act just like one. */
-               continue;
-           if (*p == '\0')
-               /* Trailing star matches everything. */
-               return TRUE;
-           while (*text)
-               if ((matched = DoMatch(text++, p)) != FALSE)
-                   return matched;
-           return ABORT;
-       case '[':
-           reverse = p[1] == NEGATE_CLASS ? TRUE : FALSE;
-           if (reverse)
-               /* Inverted character class. */
-               p++;
-           matched = FALSE;
-           if (p[1] == ']' || p[1] == '-')
-               if (*++p == *text)
-                   matched = TRUE;
-           for (last = *p; *++p && *p != ']'; last = *p)
-               /* This next line requires a good C compiler. */
-               if (*p == '-' && p[1] != ']'
-                   ? *text <= *++p && *text >= last : *text == *p)
-                   matched = TRUE;
-           if (matched == reverse)
-               return FALSE;
-           continue;
-       }
-    }
-
-#ifdef MATCH_TAR_PATTERN
-    if (*text == '/')
-       return TRUE;
-#endif /* MATCH_TAR_ATTERN */
-    return *text == '\0';
-}
-
-
-/*
-**  User-level routine.  Returns TRUE or FALSE.
-*/
-int wildmat(const char *text, const char *p)
-{
-#ifdef OPTIMIZE_JUST_STAR
-    if (p[0] == '*' && p[1] == '\0')
-       return TRUE;
-#endif /* OPTIMIZE_JUST_STAR */
-    return DoMatch(text, p) == TRUE;
-}
diff --git a/citadel/modules/notes/.gitignore b/citadel/modules/notes/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/notes/serv_notes.c b/citadel/modules/notes/serv_notes.c
deleted file mode 100644 (file)
index 4ae8ac0..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Handles functions related to yellow sticky notes.
- *
- * Copyright (c) 2007-2012 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "ctdl_module.h"
-
-
-/*
- * Callback function for serv_notes_beforesave() hunts for a vNote in the MIME structure
- */
-void notes_extract_vnote(char *name, char *filename, char *partnum, char *disp,
-                  void *content, char *cbtype, char *cbcharset, size_t length,
-                  char *encoding, char *cbid, void *cbuserdata)
-{
-       struct vnote **v = (struct vnote **) cbuserdata;
-
-       if (!strcasecmp(cbtype, "text/vnote")) {
-
-               syslog(LOG_DEBUG, "Part %s contains a vNote!  Loading...\n", partnum);
-               if (*v != NULL) {
-                       vnote_free(*v);
-               }
-               *v = vnote_new_from_str(content);
-       }
-}
-
-
-/*
- * Before-save hook searches for two different types of notes (legacy Kolab/Aethera notes
- * and modern vNote format notes) and does its best to learn the subject (summary)
- * and EUID (uid) of the note for Citadel's own nefarious purposes.
- */
-int serv_notes_beforesave(struct CtdlMessage *msg, struct recptypes *recp)
-{
-       char *p;
-       int a, i;
-       char uuid[512];
-       struct vnote *v = NULL;
-
-       /* First determine if this room has the "notes" view set */
-
-       if (CC->room.QRdefaultview != VIEW_NOTES) {
-               return(0);                      /* not notes; do nothing */
-       }
-
-       /* It must be an RFC822 message! */
-       if (msg->cm_format_type != 4) {
-               return(0);      /* You tried to save a non-RFC822 message! */
-       }
-       
-       /*
-        * If we are in a "notes" view room, and the client has sent an RFC822
-        * message containing an X-KOrg-Note-Id: field (Aethera does this, as
-        * do some Kolab clients) then set both the Subject and the Exclusive ID
-        * of the message to that.  It's going to be a UUID so we want to replace
-        * any existing message containing that UUID.
-        */
-       strcpy(uuid, "");
-       p = msg->cm_fields[eMesageText];
-       a = msg->cm_lengths[eMesageText];
-       while (--a > 0) {
-               if (!strncasecmp(p, "X-KOrg-Note-Id: ", 16)) {  /* Found it */
-                       safestrncpy(uuid, p + 16, sizeof(uuid));
-                       for (i = 0; uuid[i]; ++i) {
-                               if ( (uuid[i] == '\r') || (uuid[i] == '\n') ) {
-                                       uuid[i] = 0;
-                                       break;
-                               }
-                       }
-
-                       syslog(LOG_DEBUG, "UUID of note is: %s\n", uuid);
-                       if (!IsEmptyStr(uuid)) {
-                               CM_SetField(msg, eExclusiveID, uuid, strlen(uuid));
-
-                               CM_CopyField(msg, eMsgSubject, eExclusiveID);
-                       }
-               }
-               p++;
-       }
-
-       /* Modern clients are using vNote format.  Check for one... */
-
-       mime_parser(CM_RANGE(msg, eMesageText),
-                   *notes_extract_vnote,
-                   NULL, NULL,
-                   &v,         /* user data ptr - put the vnote here */
-                   0
-       );
-
-       if (v == NULL) return(0);       /* no vNotes were found in this message */
-
-       /* Set the message EUID to the vNote UID */
-
-       if ((v->uid) && (!IsEmptyStr(v->uid))) {
-               syslog(LOG_DEBUG, "UID of vNote is: %s\n", v->uid);
-               CM_SetField(msg, eExclusiveID, v->uid, strlen(v->uid));
-       }
-
-       /* Set the message Subject to the vNote Summary */
-
-       if ((v->summary) && (!IsEmptyStr(v->summary))) {
-               CM_SetField(msg, eMsgSubject, v->summary, strlen(v->summary));
-
-               if (msg->cm_lengths[eMsgSubject] > 72) {
-                       strcpy(&msg->cm_fields[eMsgSubject][68], "...");
-                       CM_CutFieldAt(msg, eMsgSubject, 72);
-               }
-       }
-
-       vnote_free(v);
-       
-       return(0);
-}
-
-
-CTDL_MODULE_INIT(notes)
-{
-       if (!threading)
-       {
-               CtdlRegisterMessageHook(serv_notes_beforesave, EVT_BEFORESAVE);
-       }
-       
-       /* return our module name for the log */
-       return "notes";
-}
diff --git a/citadel/modules/openid/.gitignore b/citadel/modules/openid/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/openid/serv_openid_rp.c b/citadel/modules/openid/serv_openid_rp.c
deleted file mode 100644 (file)
index bea03b4..0000000
+++ /dev/null
@@ -1,1170 +0,0 @@
-/*
- * This is an implementation of OpenID 2.0 relying party support in stateless mode.
- *
- * Copyright (c) 2007-2020 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <curl/curl.h>
-#include <expat.h>
-#include "ctdl_module.h"
-#include "config.h"
-#include "citserver.h"
-#include "user_ops.h"
-
-typedef struct _ctdl_openid {
-       StrBuf *op_url;                 /* OpenID Provider Endpoint URL */
-       StrBuf *claimed_id;             /* Claimed Identifier */
-       int verified;
-       HashList *sreg_keys;
-} ctdl_openid;
-
-enum {
-       openid_disco_none,
-       openid_disco_xrds,
-       openid_disco_html
-};
-
-
-void Free_ctdl_openid(ctdl_openid **FreeMe)
-{
-       if (*FreeMe == NULL) {
-               return;
-       }
-       FreeStrBuf(&(*FreeMe)->op_url);
-       FreeStrBuf(&(*FreeMe)->claimed_id);
-       DeleteHash(&(*FreeMe)->sreg_keys);
-       free(*FreeMe);
-       *FreeMe = NULL;
-}
-
-
-/*
- * This cleanup function blows away the temporary memory used by this module.
- */
-void openid_cleanup_function(void) {
-       struct CitContext *CCC = CC;    /* CachedCitContext - performance boost */
-
-       if (CCC->openid_data != NULL) {
-               syslog(LOG_DEBUG, "openid: Clearing OpenID session state");
-               Free_ctdl_openid((ctdl_openid **) &CCC->openid_data);
-       }
-}
-
-
-/**************************************************************************/
-/*                                                                        */
-/* Functions in this section handle Citadel internal OpenID mapping stuff */
-/*                                                                        */
-/**************************************************************************/
-
-
-/*
- * The structure of an openid record *key* is:
- *
- * |--------------claimed_id-------------|
- *     (actual length of claimed id)
- *
- *
- * The structure of an openid record *value* is:
- *
- * |-----user_number----|------------claimed_id---------------|
- *    (sizeof long)          (actual length of claimed id)
- *
- */
-
-
-
-/*
- * Attach an external authenticator (such as an OpenID) to a Citadel account
- */
-int attach_extauth(struct ctdluser *who, StrBuf *claimed_id)
-{
-       struct cdbdata *cdboi;
-       long fetched_usernum;
-       char *data;
-       int data_len;
-       char buf[2048];
-
-       if (!who) return(1);
-       if (StrLength(claimed_id)==0) return(1);
-
-       /* Check to see if this authenticator is already in the database */
-
-       cdboi = cdb_fetch(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id));
-       if (cdboi != NULL) {
-               memcpy(&fetched_usernum, cdboi->ptr, sizeof(long));
-               cdb_free(cdboi);
-
-               if (fetched_usernum == who->usernum) {
-                       syslog(LOG_INFO, "openid: %s already associated; no action is taken", ChrPtr(claimed_id));
-                       return(0);
-               }
-               else {
-                       syslog(LOG_INFO, "openid: %s already belongs to another user", ChrPtr(claimed_id));
-                       return(3);
-               }
-       }
-
-       /* Not already in the database, so attach it now */
-
-       data_len = sizeof(long) + StrLength(claimed_id) + 1;
-       data = malloc(data_len);
-
-       memcpy(data, &who->usernum, sizeof(long));
-       memcpy(&data[sizeof(long)], ChrPtr(claimed_id), StrLength(claimed_id) + 1);
-
-       cdb_store(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id), data, data_len);
-       free(data);
-
-       snprintf(buf, sizeof buf, "User <%s> (#%ld) is now associated with %s\n", who->fullname, who->usernum, ChrPtr(claimed_id));
-       CtdlAideMessage(buf, "External authenticator claim");
-       syslog(LOG_INFO, "openid: %s", buf);
-       return(0);
-}
-
-
-/*
- * When a user is being deleted, we have to delete any OpenID associations
- */
-void extauth_purge(struct ctdluser *usbuf) {
-       struct cdbdata *cdboi;
-       HashList *keys = NULL;
-       HashPos *HashPos;
-       char *deleteme = NULL;
-       long len;
-       void *Value;
-       const char *Key;
-       long usernum = 0L;
-
-       keys = NewHash(1, NULL);
-       if (!keys) return;
-
-       cdb_rewind(CDB_EXTAUTH);
-       while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
-               if (cdboi->len > sizeof(long)) {
-                       memcpy(&usernum, cdboi->ptr, sizeof(long));
-                       if (usernum == usbuf->usernum) {
-                               deleteme = strdup(cdboi->ptr + sizeof(long)),
-                               Put(keys, deleteme, strlen(deleteme), deleteme, NULL);
-                       }
-               }
-               cdb_free(cdboi);
-       }
-
-       /* Go through the hash list, deleting keys we stored in it */
-
-       HashPos = GetNewHashPos(keys, 0);
-       while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
-       {
-               syslog(LOG_DEBUG, "openid: deleting associated external authenticator <%s>", (char*)Value);
-               cdb_delete(CDB_EXTAUTH, Value, strlen(Value));
-               /* note: don't free(Value) -- deleting the hash list will handle this for us */
-       }
-       DeleteHashPos(&HashPos);
-       DeleteHash(&keys);
-}
-
-
-/*
- * List the OpenIDs associated with the currently logged in account
- */
-void cmd_oidl(char *argbuf) {
-       struct cdbdata *cdboi;
-       long usernum = 0L;
-
-       if (CtdlGetConfigInt("c_disable_newu"))
-       {
-               cprintf("%d this system does not support openid.\n", ERROR + CMD_NOT_SUPPORTED);
-               return;
-       }
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       cdb_rewind(CDB_EXTAUTH);
-       cprintf("%d Associated external authenticators:\n", LISTING_FOLLOWS);
-
-       while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
-               if (cdboi->len > sizeof(long)) {
-                       memcpy(&usernum, cdboi->ptr, sizeof(long));
-                       if (usernum == CC->user.usernum) {
-                               cprintf("%s\n", cdboi->ptr + sizeof(long));
-                       }
-               }
-               cdb_free(cdboi);
-       }
-       cprintf("000\n");
-}
-
-
-/*
- * List ALL OpenIDs in the database
- */
-void cmd_oida(char *argbuf) {
-       struct cdbdata *cdboi;
-       long usernum;
-       struct ctdluser usbuf;
-
-       if (CtdlGetConfigInt("c_disable_newu"))
-       {
-               cprintf("%d this system does not support openid.\n",
-                       ERROR + CMD_NOT_SUPPORTED);
-               return;
-       }
-       if (CtdlAccessCheck(ac_aide)) return;
-       cdb_rewind(CDB_EXTAUTH);
-       cprintf("%d List of all OpenIDs in the database:\n", LISTING_FOLLOWS);
-
-       while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
-               if (cdboi->len > sizeof(long)) {
-                       memcpy(&usernum, cdboi->ptr, sizeof(long));
-                       if (CtdlGetUserByNumber(&usbuf, usernum) != 0) {
-                               usbuf.fullname[0] = 0;
-                       } 
-                       cprintf("%s|%ld|%s\n",
-                               cdboi->ptr + sizeof(long),
-                               usernum,
-                               usbuf.fullname
-                       );
-               }
-               cdb_free(cdboi);
-       }
-       cprintf("000\n");
-}
-
-
-/*
- * Create a new user account, manually specifying the name, after successfully
- * verifying an OpenID (which will of course be attached to the account)
- */
-void cmd_oidc(char *argbuf) {
-       ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
-
-       if (CtdlGetConfigInt("c_disable_newu"))
-       {
-               cprintf("%d this system does not support openid.\n",
-                       ERROR + CMD_NOT_SUPPORTED);
-               return;
-       }
-       if ( (!oiddata) || (!oiddata->verified) ) {
-               cprintf("%d You have not verified an OpenID yet.\n", ERROR);
-               return;
-       }
-
-       /* We can make the semantics of OIDC exactly the same as NEWU, simply
-        * by _calling_ cmd_newu() and letting it run.  Very clever!
-        */
-       cmd_newu(argbuf);
-
-       /* Now, if this logged us in, we have to attach the OpenID */
-       if (CC->logged_in) {
-               attach_extauth(&CC->user, oiddata->claimed_id);
-       }
-
-}
-
-
-/*
- * Detach an OpenID from the currently logged in account
- */
-void cmd_oidd(char *argbuf) {
-       struct cdbdata *cdboi;
-       char id_to_detach[1024];
-       int this_is_mine = 0;
-       long usernum = 0L;
-
-       if (CtdlGetConfigInt("c_disable_newu"))
-       {
-               cprintf("%d this system does not support openid.\n",
-                       ERROR + CMD_NOT_SUPPORTED);
-               return;
-       }
-       if (CtdlAccessCheck(ac_logged_in)) return;
-       extract_token(id_to_detach, argbuf, 0, '|', sizeof id_to_detach);
-       if (IsEmptyStr(id_to_detach)) {
-               cprintf("%d An empty OpenID URL is not allowed.\n", ERROR + ILLEGAL_VALUE);
-       }
-
-       cdb_rewind(CDB_EXTAUTH);
-       while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
-               if (cdboi->len > sizeof(long)) {
-                       memcpy(&usernum, cdboi->ptr, sizeof(long));
-                       if (usernum == CC->user.usernum) {
-                               this_is_mine = 1;
-                       }
-               }
-               cdb_free(cdboi);
-       }
-
-       if (!this_is_mine) {
-               cprintf("%d That OpenID was not found or not associated with your account.\n",
-                       ERROR + ILLEGAL_VALUE);
-               return;
-       }
-
-       cdb_delete(CDB_EXTAUTH, id_to_detach, strlen(id_to_detach));
-       cprintf("%d %s detached from your account.\n", CIT_OK, id_to_detach);
-}
-
-
-/*
- * Attempt to auto-create a new Citadel account using the nickname from Attribute Exchange
- */
-int openid_create_user_via_ax(StrBuf *claimed_id, HashList *sreg_keys)
-{
-       char *nickname = NULL;
-       char *firstname = NULL;
-       char *lastname = NULL;
-       char new_password[32];
-       long len;
-       const char *Key;
-       void *Value;
-
-       if (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) return(1);
-       if (CtdlGetConfigInt("c_disable_newu")) return(2);
-       if (CC->logged_in) return(3);
-
-       HashPos *HashPos = GetNewHashPos(sreg_keys, 0);
-       while (GetNextHashPos(sreg_keys, HashPos, &len, &Key, &Value) != 0) {
-               syslog(LOG_DEBUG, "openid: %s = %s", Key, (char *)Value);
-
-               if (cbmstrcasestr(Key, "value.nickname") != NULL) {
-                       nickname = (char *)Value;
-               }
-               else if ( (nickname == NULL) && (cbmstrcasestr(Key, "value.nickname") != NULL)) {
-                       nickname = (char *)Value;
-               }
-               else if (cbmstrcasestr(Key, "value.firstname") != NULL) {
-                       firstname = (char *)Value;
-               }
-               else if (cbmstrcasestr(Key, "value.lastname") != NULL) {
-                       lastname = (char *)Value;
-               }
-
-       }
-       DeleteHashPos(&HashPos);
-
-       if (nickname == NULL) {
-               if ((firstname != NULL) || (lastname != NULL)) {
-                       char fullname[1024] = "";
-                       if (firstname) strcpy(fullname, firstname);
-                       if (firstname && lastname) strcat(fullname, " ");
-                       if (lastname) strcat(fullname, lastname);
-                       nickname = fullname;
-               }
-       }
-
-       if (nickname == NULL) {
-               return(4);
-       }
-       syslog(LOG_DEBUG, "openid: the desired account name is <%s>", nickname);
-
-       if (!CtdlGetUser(&CC->user, nickname)) {
-               syslog(LOG_DEBUG, "openid: <%s> is already taken by another user.", nickname);
-               memset(&CC->user, 0, sizeof(struct ctdluser));
-               return(5);
-       }
-
-       /* The desired account name is available.  Create the account and log it in! */
-       if (create_user(nickname, CREATE_USER_BECOME_USER, NATIVE_AUTH_UID)) return(6);
-
-       /* Generate a random password.
-        * The user doesn't care what the password is since he is using OpenID.
-        */
-       snprintf(new_password, sizeof new_password, "%08lx%08lx", random(), random());
-       CtdlSetPassword(new_password);
-
-       /* Now attach the verified OpenID to this account. */
-       attach_extauth(&CC->user, claimed_id);
-
-       return(0);
-}
-
-
-/*
- * If a user account exists which is associated with the Claimed ID, log it in and return zero.
- * Otherwise it returns nonzero.
- */
-int login_via_extauth(StrBuf *claimed_id)
-{
-       struct cdbdata *cdboi;
-       long usernum = 0;
-
-       cdboi = cdb_fetch(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id));
-       if (cdboi == NULL) {
-               return(-1);
-       }
-
-       memcpy(&usernum, cdboi->ptr, sizeof(long));
-       cdb_free(cdboi);
-
-       if (!CtdlGetUserByNumber(&CC->user, usernum)) {
-               /* Now become the user we just created */
-               safestrncpy(CC->curr_user, CC->user.fullname, sizeof CC->curr_user);
-               do_login();
-               return(0);
-       }
-       else {
-               memset(&CC->user, 0, sizeof(struct ctdluser));
-               return(-1);
-       }
-}
-
-
-
-
-/**************************************************************************/
-/*                                                                        */
-/* Functions in this section handle OpenID protocol                       */
-/*                                                                        */
-/**************************************************************************/
-
-
-/* 
- * Locate a <link> tag and, given its 'rel=' parameter, return its 'href' parameter
- */
-void extract_link(StrBuf *target_buf, const char *rel, long repllen, StrBuf *source_buf)
-{
-       int i;
-       const char *ptr;
-       const char *href_start = NULL;
-       const char *href_end = NULL;
-       const char *link_tag_start = NULL;
-       const char *link_tag_end = NULL;
-       const char *rel_start = NULL;
-       const char *rel_end = NULL;
-
-       if (!target_buf) return;
-       if (!rel) return;
-       if (!source_buf) return;
-
-       ptr = ChrPtr(source_buf);
-
-       FlushStrBuf(target_buf);
-       while (ptr = cbmstrcasestr(ptr, "<link"), ptr != NULL) {
-
-               link_tag_start = ptr;
-               link_tag_end = strchr(ptr, '>');
-               if (link_tag_end == NULL)
-                       break;
-               for (i=0; i < 1; i++ ){
-                       rel_start = cbmstrcasestr(link_tag_start, "rel=");
-                       if ((rel_start == NULL) ||
-                           (rel_start > link_tag_end)) 
-                               continue;
-
-                       rel_start = strchr(rel_start, '\"');
-                       if ((rel_start == NULL) ||
-                           (rel_start > link_tag_end)) 
-                               continue;
-                       ++rel_start;
-                       rel_end = strchr(rel_start, '\"');
-                       if ((rel_end == NULL) ||
-                           (rel_end == rel_start) ||
-                           (rel_end >= link_tag_end) ) 
-                               continue;
-                       if (strncasecmp(rel, rel_start, repllen)!= 0)
-                               continue; /* didn't match? never mind... */
-                       
-                       href_start = cbmstrcasestr(link_tag_start, "href=");
-                       if ((href_start == NULL) || 
-                           (href_start >= link_tag_end)) 
-                               continue;
-                       href_start = strchr(href_start, '\"');
-                       if ((href_start == NULL) |
-                           (href_start >= link_tag_end)) 
-                               continue;
-                       ++href_start;
-                       href_end = strchr(href_start, '\"');
-                       if ((href_end == NULL) || 
-                           (href_end == href_start) ||
-                           (href_start >= link_tag_end)) 
-                               continue;
-                       StrBufPlain(target_buf, href_start, href_end - href_start);
-               }
-               ptr = link_tag_end;     
-       }
-}
-
-
-/*
- * Wrapper for curl_easy_init() that includes the options common to all calls
- * used in this module. 
- */
-CURL *ctdl_openid_curl_easy_init(char *errmsg) {
-       CURL *curl;
-
-       curl = curl_easy_init();
-       if (!curl) {
-               return(curl);
-       }
-
-       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
-       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
-
-       if (errmsg) {
-               curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errmsg);
-       }
-       curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
-#ifdef CURLOPT_HTTP_CONTENT_DECODING
-       curl_easy_setopt(curl, CURLOPT_HTTP_CONTENT_DECODING, 1);
-       curl_easy_setopt(curl, CURLOPT_ENCODING, "");
-#endif
-       curl_easy_setopt(curl, CURLOPT_USERAGENT, CITADEL);
-       curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30);            /* die after 30 seconds */
-
-       if (
-               (!IsEmptyStr(CtdlGetConfigStr("c_ip_addr")))
-               && (strcmp(CtdlGetConfigStr("c_ip_addr"), "*"))
-               && (strcmp(CtdlGetConfigStr("c_ip_addr"), "::"))
-               && (strcmp(CtdlGetConfigStr("c_ip_addr"), "0.0.0.0"))
-       ) {
-               curl_easy_setopt(curl, CURLOPT_INTERFACE, CtdlGetConfigStr("c_ip_addr"));
-       }
-
-       return(curl);
-}
-
-
-struct xrds {
-       StrBuf *CharData;
-       int nesting_level;
-       int in_xrd;
-       int current_service_priority;
-       int selected_service_priority;
-       StrBuf *current_service_uri;
-       StrBuf *selected_service_uri;
-       int current_service_is_oid2auth;
-};
-
-
-void xrds_xml_start(void *data, const char *supplied_el, const char **attr) {
-       struct xrds *xrds = (struct xrds *) data;
-       int i;
-
-       ++xrds->nesting_level;
-
-       if (!strcasecmp(supplied_el, "XRD")) {
-               ++xrds->in_xrd;
-       }
-
-       else if (!strcasecmp(supplied_el, "service")) {
-               xrds->current_service_priority = 0;
-               xrds->current_service_is_oid2auth = 0;
-               for (i=0; attr[i] != NULL; i+=2) {
-                       if (!strcasecmp(attr[i], "priority")) {
-                               xrds->current_service_priority = atoi(attr[i+1]);
-                       }
-               }
-       }
-
-       FlushStrBuf(xrds->CharData);
-}
-
-
-void xrds_xml_end(void *data, const char *supplied_el) {
-       struct xrds *xrds = (struct xrds *) data;
-
-       --xrds->nesting_level;
-
-       if (!strcasecmp(supplied_el, "XRD")) {
-               --xrds->in_xrd;
-       }
-
-       else if (!strcasecmp(supplied_el, "type")) {
-               if (    (xrds->in_xrd)
-                       && (!strcasecmp(ChrPtr(xrds->CharData), "http://specs.openid.net/auth/2.0/server"))
-               ) {
-                       xrds->current_service_is_oid2auth = 1;
-               }
-               if (    (xrds->in_xrd)
-                       && (!strcasecmp(ChrPtr(xrds->CharData), "http://specs.openid.net/auth/2.0/signon"))
-               ) {
-                       xrds->current_service_is_oid2auth = 1;
-                       /* FIXME in this case, the Claimed ID should be considered immutable */
-               }
-       }
-
-       else if (!strcasecmp(supplied_el, "uri")) {
-               if (xrds->in_xrd) {
-                       FlushStrBuf(xrds->current_service_uri);
-                       StrBufAppendBuf(xrds->current_service_uri, xrds->CharData, 0);
-               }
-       }
-
-       else if (!strcasecmp(supplied_el, "service")) {
-               if (    (xrds->in_xrd)
-                       && (xrds->current_service_priority < xrds->selected_service_priority)
-                       && (xrds->current_service_is_oid2auth)
-               ) {
-                       xrds->selected_service_priority = xrds->current_service_priority;
-                       FlushStrBuf(xrds->selected_service_uri);
-                       StrBufAppendBuf(xrds->selected_service_uri, xrds->current_service_uri, 0);
-               }
-
-       }
-
-       FlushStrBuf(xrds->CharData);
-}
-
-
-void xrds_xml_chardata(void *data, const XML_Char *s, int len) {
-       struct xrds *xrds = (struct xrds *) data;
-
-       StrBufAppendBufPlain (xrds->CharData, s, len, 0);
-}
-
-
-/*
- * Parse an XRDS document.
- * If an OpenID Provider URL is discovered, op_url to that value and return nonzero.
- * If nothing useful happened, return 0.
- */
-int parse_xrds_document(StrBuf *ReplyBuf) {
-       ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
-       struct xrds xrds;
-       int return_value = 0;
-
-       memset(&xrds, 0, sizeof (struct xrds));
-       xrds.selected_service_priority = INT_MAX;
-       xrds.CharData = NewStrBuf();
-       xrds.current_service_uri = NewStrBuf();
-       xrds.selected_service_uri = NewStrBuf();
-       XML_Parser xp = XML_ParserCreate(NULL);
-       if (xp) {
-               XML_SetUserData(xp, &xrds);
-               XML_SetElementHandler(xp, xrds_xml_start, xrds_xml_end);
-               XML_SetCharacterDataHandler(xp, xrds_xml_chardata);
-               XML_Parse(xp, ChrPtr(ReplyBuf), StrLength(ReplyBuf), 0);
-               XML_Parse(xp, "", 0, 1);
-               XML_ParserFree(xp);
-       }
-       else {
-               syslog(LOG_ERR, "openid: cannot create XML parser");
-       }
-
-       if (xrds.selected_service_priority < INT_MAX) {
-               if (oiddata->op_url == NULL) {
-                       oiddata->op_url = NewStrBuf();
-               }
-               FlushStrBuf(oiddata->op_url);
-               StrBufAppendBuf(oiddata->op_url, xrds.selected_service_uri, 0);
-               return_value = openid_disco_xrds;
-       }
-
-       FreeStrBuf(&xrds.CharData);
-       FreeStrBuf(&xrds.current_service_uri);
-       FreeStrBuf(&xrds.selected_service_uri);
-
-       return(return_value);
-}
-
-
-/*
- * Callback function for perform_openid2_discovery()
- * We're interested in the X-XRDS-Location: header.
- */
-size_t yadis_headerfunction(void *ptr, size_t size, size_t nmemb, void *userdata) {
-       char hdr[1024];
-       StrBuf **x_xrds_location = (StrBuf **) userdata;
-
-       memcpy(hdr, ptr, (size*nmemb));
-       hdr[size*nmemb] = 0;
-
-       if (!strncasecmp(hdr, "X-XRDS-Location:", 16)) {
-               *x_xrds_location = NewStrBufPlain(&hdr[16], ((size*nmemb)-16));
-               StrBufTrim(*x_xrds_location);
-       }
-
-       return(size * nmemb);
-}
-
-
-/* Attempt to perform Yadis discovery as specified in Yadis 1.0 section 6.2.5.
- * 
- * If Yadis fails, we then attempt HTML discovery using the same document.
- *
- * If successful, returns nonzero and calls parse_xrds_document() to act upon the received data.
- * If fails, returns 0 and does nothing else.
- */
-int perform_openid2_discovery(StrBuf *SuppliedURL) {
-       ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
-       int docbytes = (-1);
-       StrBuf *ReplyBuf = NULL;
-       int return_value = 0;
-       CURL *curl;
-       CURLcode result;
-       char errmsg[1024] = "";
-       struct curl_slist *my_headers = NULL;
-       StrBuf *x_xrds_location = NULL;
-
-       if (!SuppliedURL) return(0);
-       syslog(LOG_DEBUG, "openid: perform_openid2_discovery(%s)", ChrPtr(SuppliedURL));
-       if (StrLength(SuppliedURL) == 0) return(0);
-
-       ReplyBuf = NewStrBuf();
-       if (!ReplyBuf) return(0);
-
-       curl = ctdl_openid_curl_easy_init(errmsg);
-       if (!curl) return(0);
-
-       curl_easy_setopt(curl, CURLOPT_URL, ChrPtr(SuppliedURL));
-       curl_easy_setopt(curl, CURLOPT_WRITEDATA, ReplyBuf);
-       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback);
-
-       my_headers = curl_slist_append(my_headers, "Accept:");  /* disable the default Accept: header */
-       my_headers = curl_slist_append(my_headers, "Accept: application/xrds+xml");
-       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, my_headers);
-
-       curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &x_xrds_location);
-       curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, yadis_headerfunction);
-
-       result = curl_easy_perform(curl);
-       if (result) {
-               syslog(LOG_DEBUG, "openid: libcurl error %d: %s", result, errmsg);
-       }
-       curl_slist_free_all(my_headers);
-       curl_easy_cleanup(curl);
-       docbytes = StrLength(ReplyBuf);
-
-       /*
-        * The response from the server will be one of:
-        * 
-        * Option 1: An HTML document with a <head> element that includes a <meta> element with http-equiv
-        * attribute, X-XRDS-Location,
-        *
-        * Does any provider actually do this?  If so then we will implement it in the future.
-        */
-
-       /*
-        * Option 2: HTTP response-headers that include an X-XRDS-Location response-header,
-        *           together with a document.
-        * Option 3: HTTP response-headers only, which MAY include an X-XRDS-Location response-header,
-        *           a contenttype response-header specifying MIME media type,
-        *           application/xrds+xml, or both.
-        *
-        * If the X-XRDS-Location header was delivered, we know about it at this point...
-        */
-       if (    (x_xrds_location)
-               && (strcmp(ChrPtr(x_xrds_location), ChrPtr(SuppliedURL)))
-       ) {
-               syslog(LOG_DEBUG, "openid: X-XRDS-Location: %s ... recursing!", ChrPtr(x_xrds_location));
-               return_value = perform_openid2_discovery(x_xrds_location);
-               FreeStrBuf(&x_xrds_location);
-       }
-
-       /*
-        * Option 4: the returned web page may *be* an XRDS document.  Try to parse it.
-        */
-       if ( (return_value == 0) && (docbytes >= 0)) {
-               return_value = parse_xrds_document(ReplyBuf);
-       }
-
-       /*
-        * Option 5: if all else fails, attempt HTML based discovery.
-        */
-       if ( (return_value == 0) && (docbytes >= 0)) {
-               if (oiddata->op_url == NULL) {
-                       oiddata->op_url = NewStrBuf();
-               }
-               extract_link(oiddata->op_url, HKEY("openid2.provider"), ReplyBuf);
-               if (StrLength(oiddata->op_url) > 0) {
-                       return_value = openid_disco_html;
-               }
-       }
-
-       if (ReplyBuf != NULL) {
-               FreeStrBuf(&ReplyBuf);
-       }
-       return(return_value);
-}
-
-
-/*
- * Setup an OpenID authentication
- */
-void cmd_oids(char *argbuf) {
-       struct CitContext *CCC = CC;    /* CachedCitContext - performance boost */
-       const char *Pos = NULL;
-       StrBuf *ArgBuf = NULL;
-       StrBuf *ReplyBuf = NULL;
-       StrBuf *return_to = NULL;
-       StrBuf *RedirectUrl = NULL;
-       ctdl_openid *oiddata;
-       int discovery_succeeded = 0;
-
-       if (CtdlGetConfigInt("c_disable_newu"))
-       {
-               cprintf("%d this system does not support openid.\n",
-                       ERROR + CMD_NOT_SUPPORTED);
-               return;
-       }
-       Free_ctdl_openid ((ctdl_openid**)&CCC->openid_data);
-
-       CCC->openid_data = oiddata = malloc(sizeof(ctdl_openid));
-       if (oiddata == NULL) {
-               syslog(LOG_ERR, "openid: malloc() failed: %m");
-               cprintf("%d malloc failed\n", ERROR + INTERNAL_ERROR);
-               return;
-       }
-       memset(oiddata, 0, sizeof(ctdl_openid));
-
-       ArgBuf = NewStrBufPlain(argbuf, -1);
-
-       oiddata->verified = 0;
-       oiddata->claimed_id = NewStrBufPlain(NULL, StrLength(ArgBuf));
-       return_to = NewStrBufPlain(NULL, StrLength(ArgBuf));
-
-       StrBufExtract_NextToken(oiddata->claimed_id, ArgBuf, &Pos, '|');
-       StrBufExtract_NextToken(return_to, ArgBuf, &Pos, '|');
-
-       syslog(LOG_DEBUG, "openid: user-Supplied Identifier is: %s", ChrPtr(oiddata->claimed_id));
-
-       /********** OpenID 2.0 section 7.3 - Discovery **********/
-
-       /* Section 7.3.1 says we have to attempt XRI based discovery.
-        * No one is using this, no one is asking for it, no one wants it.
-        * So we're not even going to bother attempting this mode.
-        */
-
-       /* Attempt section 7.3.2 (Yadis discovery) and section 7.3.3 (HTML discovery);
-        */
-       discovery_succeeded = perform_openid2_discovery(oiddata->claimed_id);
-
-       if (discovery_succeeded == 0) {
-               cprintf("%d There is no OpenID identity provider at this location.\n", ERROR);
-       }
-
-       else {
-               /*
-                * If we get to this point we are in possession of a valid OpenID Provider URL.
-                */
-               syslog(LOG_DEBUG, "openid: OP URI '%s' discovered using method %d",
-                       ChrPtr(oiddata->op_url),
-                       discovery_succeeded
-               );
-
-               /* We have to "normalize" our Claimed ID otherwise it will cause some OP's to barf */
-               if (cbmstrcasestr(ChrPtr(oiddata->claimed_id), "://") == NULL) {
-                       StrBuf *cid = oiddata->claimed_id;
-                       oiddata->claimed_id = NewStrBufPlain(HKEY("http://"));
-                       StrBufAppendBuf(oiddata->claimed_id, cid, 0);
-                       FreeStrBuf(&cid);
-               }
-
-               /*
-                * OpenID 2.0 section 9: request authentication
-                * Assemble a URL to which the user-agent will be redirected.
-                */
-       
-               RedirectUrl = NewStrBufDup(oiddata->op_url);
-
-               StrBufAppendBufPlain(RedirectUrl, HKEY("?openid.ns="), 0);
-               StrBufUrlescAppend(RedirectUrl, NULL, "http://specs.openid.net/auth/2.0");
-
-               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.mode=checkid_setup"), 0);
-
-               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.claimed_id="), 0);
-               StrBufUrlescAppend(RedirectUrl, oiddata->claimed_id, NULL);
-
-               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.identity="), 0);
-               StrBufUrlescAppend(RedirectUrl, oiddata->claimed_id, NULL);
-
-               /* return_to tells the provider how to complete the round trip back to our site */
-               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.return_to="), 0);
-               StrBufUrlescAppend(RedirectUrl, return_to, NULL);
-
-               /* Attribute Exchange
-                * See:
-                *      http://openid.net/specs/openid-attribute-exchange-1_0.html
-                *      http://code.google.com/apis/accounts/docs/OpenID.html#endpoint
-                *      http://test-id.net/OP/AXFetch.aspx
-                */
-
-               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ns.ax="), 0);
-               StrBufUrlescAppend(RedirectUrl, NULL, "http://openid.net/srv/ax/1.0");
-
-               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.mode=fetch_request"), 0);
-
-               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.required=firstname,lastname,friendly,nickname"), 0);
-
-               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.firstname="), 0);
-               StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/first");
-
-               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.lastname="), 0);
-               StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/last");
-
-               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.friendly="), 0);
-               StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/friendly");
-
-               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.nickname="), 0);
-               StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/nickname");
-
-               syslog(LOG_DEBUG, "openid: redirecting client to %s", ChrPtr(RedirectUrl));
-               cprintf("%d %s\n", CIT_OK, ChrPtr(RedirectUrl));
-       }
-       
-       FreeStrBuf(&ArgBuf);
-       FreeStrBuf(&ReplyBuf);
-       FreeStrBuf(&return_to);
-       FreeStrBuf(&RedirectUrl);
-}
-
-
-/*
- * Finalize an OpenID authentication
- */
-void cmd_oidf(char *argbuf) {
-       long len;
-       char buf[2048];
-       char thiskey[1024];
-       char thisdata[1024];
-       HashList *keys = NULL;
-       const char *Key;
-       void *Value;
-       ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
-
-       if (CtdlGetConfigInt("c_disable_newu"))
-       {
-               cprintf("%d this system does not support openid.\n",
-                       ERROR + CMD_NOT_SUPPORTED);
-               return;
-       }
-       if (oiddata == NULL) {
-               cprintf("%d run OIDS first.\n", ERROR + INTERNAL_ERROR);
-               return;
-       }
-       if (StrLength(oiddata->op_url) == 0){
-               cprintf("%d No OpenID Endpoint URL has been obtained.\n", ERROR + ILLEGAL_VALUE);
-               return;
-       }
-       keys = NewHash(1, NULL);
-       if (!keys) {
-               cprintf("%d NewHash() failed\n", ERROR + INTERNAL_ERROR);
-               return;
-       }
-       cprintf("%d Transmit OpenID data now\n", START_CHAT_MODE);
-
-       while (client_getln(buf, sizeof buf), strcmp(buf, "000")) {
-               len = extract_token(thiskey, buf, 0, '|', sizeof thiskey);
-               if (len < 0) {
-                       len = sizeof(thiskey) - 1;
-               }
-               extract_token(thisdata, buf, 1, '|', sizeof thisdata);
-               Put(keys, thiskey, len, strdup(thisdata), NULL);
-       }
-
-       /* Check to see if this is a correct response.
-        * Start with verified=1 but then set it to 0 if anything looks wrong.
-        */
-       oiddata->verified = 1;
-
-       char *openid_ns = NULL;
-       if (    (!GetHash(keys, "ns", 2, (void *) &openid_ns))
-               || (strcasecmp(openid_ns, "http://specs.openid.net/auth/2.0"))
-       ) {
-               syslog(LOG_DEBUG, "openid: this is not an an OpenID assertion");
-               oiddata->verified = 0;
-       }
-
-       char *openid_mode = NULL;
-       if (    (!GetHash(keys, "mode", 4, (void *) &openid_mode))
-               || (strcasecmp(openid_mode, "id_res"))
-       ) {
-               oiddata->verified = 0;
-       }
-
-       char *openid_claimed_id = NULL;
-       if (GetHash(keys, "claimed_id", 10, (void *) &openid_claimed_id)) {
-               FreeStrBuf(&oiddata->claimed_id);
-               oiddata->claimed_id = NewStrBufPlain(openid_claimed_id, -1);
-               syslog(LOG_DEBUG, "openid: provider is asserting the Claimed ID '%s'", ChrPtr(oiddata->claimed_id));
-       }
-
-       /* Validate the assertion against the server */
-       syslog(LOG_DEBUG, "openid: validating...");
-
-       CURL *curl;
-       CURLcode res;
-       struct curl_httppost *formpost = NULL;
-       struct curl_httppost *lastptr = NULL;
-       char errmsg[1024] = "";
-       StrBuf *ReplyBuf = NewStrBuf();
-
-       curl_formadd(&formpost, &lastptr,
-               CURLFORM_COPYNAME,      "openid.mode",
-               CURLFORM_COPYCONTENTS,  "check_authentication",
-               CURLFORM_END
-       );
-
-       HashPos *HashPos = GetNewHashPos(keys, 0);
-       while (GetNextHashPos(keys, HashPos, &len, &Key, &Value) != 0) {
-               if (strcasecmp(Key, "mode")) {
-                       char k_o_keyname[1024];
-                       snprintf(k_o_keyname, sizeof k_o_keyname, "openid.%s", (const char *)Key);
-                       curl_formadd(&formpost, &lastptr,
-                               CURLFORM_COPYNAME,      k_o_keyname,
-                               CURLFORM_COPYCONTENTS,  (char *)Value,
-                               CURLFORM_END
-                       );
-               }
-       }
-       DeleteHashPos(&HashPos);
-
-       curl = ctdl_openid_curl_easy_init(errmsg);
-       curl_easy_setopt(curl, CURLOPT_URL, ChrPtr(oiddata->op_url));
-       curl_easy_setopt(curl, CURLOPT_WRITEDATA, ReplyBuf);
-       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback);
-       curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
-
-       res = curl_easy_perform(curl);
-       if (res) {
-               syslog(LOG_DEBUG, "openid: cmd_oidf() libcurl error %d: %s", res, errmsg);
-               oiddata->verified = 0;
-       }
-       curl_easy_cleanup(curl);
-       curl_formfree(formpost);
-
-       if (cbmstrcasestr(ChrPtr(ReplyBuf), "is_valid:true") == NULL) {
-               oiddata->verified = 0;
-       }
-       FreeStrBuf(&ReplyBuf);
-
-       syslog(LOG_DEBUG, "openid: authentication %s", (oiddata->verified ? "succeeded" : "failed") );
-
-       /* Respond to the client */
-
-       if (oiddata->verified) {
-
-               /* If we were already logged in, attach the OpenID to the user's account */
-               if (CC->logged_in) {
-                       if (attach_extauth(&CC->user, oiddata->claimed_id) == 0) {
-                               cprintf("attach\n");
-                               syslog(LOG_DEBUG, "openid: attach succeeded");
-                       }
-                       else {
-                               cprintf("fail\n");
-                               syslog(LOG_DEBUG, "openid: attach failed");
-                       }
-               }
-
-               /* Otherwise, a user is attempting to log in using the verified OpenID */       
-               else {
-                       /*
-                        * Existing user who has claimed this OpenID?
-                        *
-                        * Note: if you think that sending the password back over the wire is insecure,
-                        * check your assumptions.  If someone has successfully asserted an OpenID that
-                        * is associated with the account, they already have password equivalency and can
-                        * login, so they could just as easily change the password, etc.
-                        */
-                       if (login_via_extauth(oiddata->claimed_id) == 0) {
-                               cprintf("authenticate\n%s\n%s\n", CC->user.fullname, CC->user.password);
-                               logged_in_response();
-                               syslog(LOG_DEBUG, "openid: logged in using previously claimed OpenID");
-                       }
-
-                       /*
-                        * If this system does not allow self-service new user registration, the
-                        * remaining modes do not apply, so fail here and now.
-                        */
-                       else if (CtdlGetConfigInt("c_disable_newu")) {
-                               cprintf("fail\n");
-                               syslog(LOG_DEBUG, "openid: creating user failed due to local policy");
-                       }
-
-                       /*
-                        * New user whose OpenID is verified and Attribute Exchange gave us a name?
-                        */
-                       else if (openid_create_user_via_ax(oiddata->claimed_id, keys) == 0) {
-                               cprintf("authenticate\n%s\n%s\n", CC->user.fullname, CC->user.password);
-                               logged_in_response();
-                               syslog(LOG_DEBUG, "openid: successfully auto-created new user");
-                       }
-
-                       /*
-                        * OpenID is verified, but the desired username either was not specified or
-                        * conflicts with an existing user.  Manual account creation is required.
-                        */
-                       else {
-                               char *desired_name = NULL;
-                               cprintf("verify_only\n");
-                               cprintf("%s\n", ChrPtr(oiddata->claimed_id));
-                               if (GetHash(keys, "sreg.nickname", 13, (void *) &desired_name)) {
-                                       cprintf("%s\n", desired_name);
-                               }
-                               else {
-                                       cprintf("\n");
-                               }
-                               syslog(LOG_DEBUG, "openid: the desired display name is already taken.");
-                       }
-               }
-       }
-       else {
-               cprintf("fail\n");
-       }
-       cprintf("000\n");
-
-       if (oiddata->sreg_keys != NULL) {
-               DeleteHash(&oiddata->sreg_keys);
-               oiddata->sreg_keys = NULL;
-       }
-       oiddata->sreg_keys = keys;
-}
-
-
-
-/**************************************************************************/
-/*                                                                        */
-/* Functions in this section handle module initialization and shutdown    */
-/*                                                                        */
-/**************************************************************************/
-
-
-
-
-CTDL_MODULE_INIT(openid_rp)
-{
-       if (!threading) {
-
-               /* Only enable the OpenID command set when native mode authentication is in use. */
-               if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_NATIVE) {
-                       CtdlRegisterProtoHook(cmd_oids, "OIDS", "Setup OpenID authentication");
-                       CtdlRegisterProtoHook(cmd_oidf, "OIDF", "Finalize OpenID authentication");
-                       CtdlRegisterProtoHook(cmd_oidl, "OIDL", "List OpenIDs associated with an account");
-                       CtdlRegisterProtoHook(cmd_oidd, "OIDD", "Detach an OpenID from an account");
-                       CtdlRegisterProtoHook(cmd_oidc, "OIDC", "Create new user after validating OpenID");
-                       CtdlRegisterProtoHook(cmd_oida, "OIDA", "List all OpenIDs in the database");
-               }
-               CtdlRegisterSessionHook(openid_cleanup_function, EVT_LOGOUT, PRIO_LOGOUT + 10);
-               CtdlRegisterUserHook(extauth_purge, EVT_PURGEUSER);
-               openid_level_supported = 1;     /* This module supports OpenID 1.0 only */
-       }
-
-       /* return our module name for the log */
-       return "openid_rp";
-}
diff --git a/citadel/modules/pop3/.gitignore b/citadel/modules/pop3/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/pop3/serv_pop3.c b/citadel/modules/pop3/serv_pop3.c
deleted file mode 100644 (file)
index 6cdd55c..0000000
+++ /dev/null
@@ -1,599 +0,0 @@
-// POP3 service for the Citadel system
-//
-// Copyright (c) 1998-2021 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// Current status of standards conformance:
-//
-// -> All required POP3 commands described in RFC1939 are implemented.
-// -> All optional POP3 commands described in RFC1939 are also implemented.
-// -> The deprecated "LAST" command is included in this implementation, because
-//    there exist mail clients which insist on using it (such as Bynari
-//    TradeMail, and certain versions of Eudora).
-// -> Capability detection via the method described in RFC2449 is implemented.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_pop3.h"
-#include "ctdl_module.h"
-
-
-// This cleanup function blows away the temporary memory and files used by
-// the POP3 server.
-void pop3_cleanup_function(void) {
-       /* Don't do this stuff if this is not a POP3 session! */
-       if (CC->h_command_function != pop3_command_loop) return;
-
-       struct citpop3 *pop3 = ((struct citpop3 *)CC->session_specific_data);
-       syslog(LOG_DEBUG, "pop3: performing cleanup hook");
-       if (pop3->msgs != NULL) {
-               free(pop3->msgs);
-       }
-
-       free(pop3);
-}
-
-
-// Here's where our POP3 session begins its happy day.
-void pop3_greeting(void) {
-       strcpy(CC->cs_clientname, "POP3 session");
-       CC->internal_pgm = 1;
-       CC->session_specific_data = malloc(sizeof(struct citpop3));
-       memset(POP3, 0, sizeof(struct citpop3));
-
-       cprintf("+OK Citadel POP3 server ready.\r\n");
-}
-
-
-// POP3S is just like POP3, except it goes crypto right away.
-void pop3s_greeting(void) {
-       CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
-
-/* kill session if no crypto */
-#ifdef HAVE_OPENSSL
-       if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;
-#else
-       CC->kill_me = KILLME_NO_CRYPTO;
-#endif
-
-       pop3_greeting();
-}
-
-
-// Specify user name (implements POP3 "USER" command)
-void pop3_user(char *argbuf) {
-       char username[SIZ];
-
-       if (CC->logged_in) {
-               cprintf("-ERR You are already logged in.\r\n");
-               return;
-       }
-
-       strcpy(username, argbuf);
-       striplt(username);
-
-       if (CtdlLoginExistingUser(username) == login_ok) {
-               cprintf("+OK Password required for %s\r\n", username);
-       }
-       else {
-               cprintf("-ERR No such user.\r\n");
-       }
-}
-
-
-// Back end for pop3_grab_mailbox()
-void pop3_add_message(long msgnum, void *userdata) {
-       struct MetaData smi;
-
-       ++POP3->num_msgs;
-       if (POP3->num_msgs < 2) {
-               POP3->msgs = malloc(sizeof(struct pop3msg));
-       }
-       else {
-               POP3->msgs = realloc(POP3->msgs, (POP3->num_msgs * sizeof(struct pop3msg)) ) ;
-       }
-       POP3->msgs[POP3->num_msgs-1].msgnum = msgnum;
-       POP3->msgs[POP3->num_msgs-1].deleted = 0;
-
-       // We need to know the length of this message when it is printed in
-       // RFC822 format.  Perhaps we have cached this length in the message's
-       // metadata record.  If so, great; if not, measure it and then cache
-       // it for next time.
-       GetMetaData(&smi, msgnum);
-       if (smi.meta_rfc822_length <= 0L) {
-               CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
-               CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, SUPPRESS_ENV_TO, NULL, NULL, NULL);
-               smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
-               FreeStrBuf(&CC->redirect_buffer);
-               PutMetaData(&smi);
-       }
-       POP3->msgs[POP3->num_msgs-1].rfc822_length = smi.meta_rfc822_length;
-}
-
-
-// Open the inbox and read its contents.
-// (This should be called only once, by pop3_pass(), and returns the number
-// of messages in the inbox, or -1 for error)
-int pop3_grab_mailbox(void) {
-        visit vbuf;
-       int i;
-
-       if (CtdlGetRoom(&CC->room, MAILROOM) != 0) return(-1);
-
-       /* Load up the messages */
-       CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, pop3_add_message, NULL);
-
-       /* Figure out which are old and which are new */
-        CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
-       POP3->lastseen = (-1);
-       if (POP3->num_msgs) for (i=0; i<POP3->num_msgs; ++i) {
-               if (is_msg_in_sequence_set(vbuf.v_seen, (POP3->msgs[POP3->num_msgs-1].msgnum) )) {
-                       POP3->lastseen = i;
-               }
-       }
-
-       return(POP3->num_msgs);
-}
-
-
-void pop3_login(void) {
-       int msgs;
-       
-       msgs = pop3_grab_mailbox();
-       if (msgs >= 0) {
-               cprintf("+OK %s is logged in (%d messages)\r\n",
-                       CC->user.fullname, msgs);
-               syslog(LOG_DEBUG, "pop3: authenticated %s", CC->user.fullname);
-       }
-       else {
-               cprintf("-ERR Can't open your mailbox\r\n");
-       }
-       
-}
-
-
-// Authorize with password (implements POP3 "PASS" command)
-void pop3_pass(char *argbuf) {
-       char password[SIZ];
-
-       safestrncpy(password, argbuf, sizeof password);
-       striplt(password);
-
-       if (CtdlTryPassword(password, strlen(password)) == pass_ok) {
-               pop3_login();
-       }
-       else {
-               cprintf("-ERR That is NOT the password.\r\n");
-       }
-}
-
-
-// list available msgs
-void pop3_list(char *argbuf) {
-       int i;
-       int which_one;
-
-       which_one = atoi(argbuf);
-
-       // "list one" mode
-       if (which_one > 0) {
-               if (which_one > POP3->num_msgs) {
-                       cprintf("-ERR no such message, only %d are here\r\n", POP3->num_msgs);
-                       return;
-               }
-               else if (POP3->msgs[which_one-1].deleted) {
-                       cprintf("-ERR Sorry, you deleted that message.\r\n");
-                       return;
-               }
-               else {
-                       cprintf("+OK %d %ld\r\n", which_one, (long)POP3->msgs[which_one-1].rfc822_length);
-                       return;
-               }
-       }
-
-       // "list all" (scan listing) mode
-       else {
-               cprintf("+OK Here's your mail:\r\n");
-               if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
-                       if (! POP3->msgs[i].deleted) {
-                               cprintf("%d %ld\r\n", i+1, (long)POP3->msgs[i].rfc822_length);
-                       }
-               }
-               cprintf(".\r\n");
-       }
-}
-
-
-// STAT (tally up the total message count and byte count) command
-void pop3_stat(char *argbuf) {
-       int total_msgs = 0;
-       size_t total_octets = 0;
-       int i;
-       
-       if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
-               if (! POP3->msgs[i].deleted) {
-                       ++total_msgs;
-                       total_octets += POP3->msgs[i].rfc822_length;
-               }
-       }
-
-       cprintf("+OK %d %ld\r\n", total_msgs, (long)total_octets);
-}
-
-
-// RETR command (fetch a message)
-void pop3_retr(char *argbuf) {
-       int which_one;
-
-       which_one = atoi(argbuf);
-       if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
-               cprintf("-ERR No such message.\r\n");
-               return;
-       }
-
-       if (POP3->msgs[which_one - 1].deleted) {
-               cprintf("-ERR Sorry, you deleted that message.\r\n");
-               return;
-       }
-
-       cprintf("+OK Message %d:\r\n", which_one);
-       CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum, MT_RFC822,
-               HEADERS_ALL, 0, 1, NULL,
-               (ESC_DOT|SUPPRESS_ENV_TO), NULL, NULL, NULL
-       );
-       cprintf(".\r\n");
-}
-
-
-// TOP command (dumb way of fetching a partial message or headers-only)
-void pop3_top(char *argbuf) {
-       int which_one;
-       int lines_requested = 0;
-       int lines_dumped = 0;
-       char buf[1024];
-       StrBuf *msgtext;
-       const char *ptr;
-       int in_body = 0;
-       int done = 0;
-
-       sscanf(argbuf, "%d %d", &which_one, &lines_requested);
-       if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
-               cprintf("-ERR No such message.\r\n");
-               return;
-       }
-
-       if (POP3->msgs[which_one - 1].deleted) {
-               cprintf("-ERR Sorry, you deleted that message.\r\n");
-               return;
-       }
-
-       CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
-
-       CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum,
-                     MT_RFC822,
-                     HEADERS_ALL,
-                     0, 1, NULL,
-                     SUPPRESS_ENV_TO,
-                     NULL, NULL, NULL);
-
-       msgtext = CC->redirect_buffer;
-       CC->redirect_buffer = NULL;
-
-       cprintf("+OK Message %d:\r\n", which_one);
-       
-       ptr = ChrPtr(msgtext);
-       while (ptr = cmemreadline(ptr, buf, (sizeof buf - 2)),
-             ( (*ptr != 0) && (done == 0))) {
-               strcat(buf, "\r\n");
-               if (in_body == 1) {
-                       if (lines_dumped >= lines_requested) {
-                               done = 1;
-                       }
-               }
-               if ((in_body == 0) || (done == 0)) {
-                       client_write(buf, strlen(buf));
-               }
-               if (in_body) {
-                       ++lines_dumped;
-               }
-               if ((buf[0]==13)||(buf[0]==10)) in_body = 1;
-       }
-
-       if (buf[strlen(buf)-1] != 10) cprintf("\n");
-       FreeStrBuf(&msgtext);
-
-       cprintf(".\r\n");
-}
-
-
-// DELE (delete message from mailbox)
-void pop3_dele(char *argbuf) {
-       int which_one;
-
-       which_one = atoi(argbuf);
-       if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
-               cprintf("-ERR No such message.\r\n");
-               return;
-       }
-
-       if (POP3->msgs[which_one - 1].deleted) {
-               cprintf("-ERR You already deleted that message.\r\n");
-               return;
-       }
-
-       // Flag the message as deleted.  Will expunge during QUIT command.
-       POP3->msgs[which_one - 1].deleted = 1;
-       cprintf("+OK Message %d deleted.\r\n",
-               which_one);
-}
-
-
-// Perform "UPDATE state" stuff
-void pop3_update(void) {
-       int i;
-        visit vbuf;
-
-       long *deletemsgs = NULL;
-       int num_deletemsgs = 0;
-
-       // Remove messages marked for deletion
-       if (POP3->num_msgs > 0) {
-               deletemsgs = malloc(POP3->num_msgs * sizeof(long));
-               for (i=0; i<POP3->num_msgs; ++i) {
-                       if (POP3->msgs[i].deleted) {
-                               deletemsgs[num_deletemsgs++] = POP3->msgs[i].msgnum;
-                       }
-               }
-               if (num_deletemsgs > 0) {
-                       CtdlDeleteMessages(MAILROOM, deletemsgs, num_deletemsgs, "");
-               }
-               free(deletemsgs);
-       }
-
-       // Set last read pointer
-       if (POP3->num_msgs > 0) {
-               CtdlLockGetCurrentUser();
-               CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
-               snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld", POP3->msgs[POP3->num_msgs-1].msgnum);
-               CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
-               CtdlPutCurrentUserLock();
-       }
-
-}
-
-
-// RSET (reset, i.e. undelete any deleted messages) command
-void pop3_rset(char *argbuf) {
-       int i;
-
-       if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
-               if (POP3->msgs[i].deleted) {
-                       POP3->msgs[i].deleted = 0;
-               }
-       }
-       cprintf("+OK Reset completed.\r\n");
-}
-
-
-// LAST (Determine which message is the last unread message)
-void pop3_last(char *argbuf) {
-       cprintf("+OK %d\r\n", POP3->lastseen + 1);
-}
-
-
-// CAPA is a command which tells the client which POP3 extensions are supported.
-void pop3_capa(void) {
-       cprintf("+OK Capability list follows\r\n"
-               "TOP\r\n"
-               "USER\r\n"
-               "UIDL\r\n"
-               "IMPLEMENTATION %s\r\n"
-               ".\r\n"
-               ,
-               CITADEL
-       );
-}
-
-
-// UIDL (Universal IDentifier Listing) is easy.  Our 'unique' message
-// identifiers are simply the Citadel message numbers in the database.
-void pop3_uidl(char *argbuf) {
-       int i;
-       int which_one;
-
-       which_one = atoi(argbuf);
-
-       // "list one" mode
-       if (which_one > 0) {
-               if (which_one > POP3->num_msgs) {
-                       cprintf("-ERR no such message, only %d are here\r\n", POP3->num_msgs);
-                       return;
-               }
-               else if (POP3->msgs[which_one-1].deleted) {
-                       cprintf("-ERR Sorry, you deleted that message.\r\n");
-                       return;
-               }
-               else {
-                       cprintf("+OK %d %ld\r\n", which_one, POP3->msgs[which_one-1].msgnum);
-                       return;
-               }
-       }
-
-       // "list all" (scan listing) mode
-       else {
-               cprintf("+OK Here's your mail:\r\n");
-               if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
-                       if (! POP3->msgs[i].deleted) {
-                               cprintf("%d %ld\r\n", i+1, POP3->msgs[i].msgnum);
-                       }
-               }
-               cprintf(".\r\n");
-       }
-}
-
-
-// implements the STLS command (Citadel API version)
-void pop3_stls(void) {
-       char ok_response[SIZ];
-       char nosup_response[SIZ];
-       char error_response[SIZ];
-
-       sprintf(ok_response,    "+OK Begin TLS negotiation now\r\n");
-       sprintf(nosup_response, "-ERR TLS not supported here\r\n");
-       sprintf(error_response, "-ERR Internal error\r\n");
-       CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
-}
-
-
-// Main command loop for POP3 sessions.
-void pop3_command_loop(void) {
-       char cmdbuf[SIZ];
-
-       time(&CC->lastcmd);
-       memset(cmdbuf, 0, sizeof cmdbuf);                       // Clear it, just in case
-       if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
-               syslog(LOG_INFO, "pop3: client disconnected; ending session.");
-               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
-               return;
-       }
-       if (!strncasecmp(cmdbuf, "PASS", 4)) {
-               syslog(LOG_DEBUG, "pop3: PASS...");
-       }
-       else {
-               syslog(LOG_DEBUG, "pop3: %s", cmdbuf);
-       }
-       while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
-
-       if (!strncasecmp(cmdbuf, "NOOP", 4)) {
-               cprintf("+OK No operation.\r\n");
-       }
-
-       else if (!strncasecmp(cmdbuf, "CAPA", 4)) {
-               pop3_capa();
-       }
-
-       else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
-               cprintf("+OK Goodbye...\r\n");
-               pop3_update();
-               CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
-               return;
-       }
-
-       else if (!strncasecmp(cmdbuf, "USER", 4)) {
-               pop3_user(&cmdbuf[5]);
-       }
-
-       else if (!strncasecmp(cmdbuf, "PASS", 4)) {
-               pop3_pass(&cmdbuf[5]);
-       }
-
-#ifdef HAVE_OPENSSL
-       else if (!strncasecmp(cmdbuf, "STLS", 4)) {
-               pop3_stls();
-       }
-#endif
-
-       else if (!CC->logged_in) {
-               cprintf("-ERR Not logged in.\r\n");
-       }
-       
-       else if (CC->nologin) {
-               cprintf("-ERR System busy, try later.\r\n");
-               CC->kill_me = KILLME_NOLOGIN;
-       }
-
-       else if (!strncasecmp(cmdbuf, "LIST", 4)) {
-               pop3_list(&cmdbuf[5]);
-       }
-
-       else if (!strncasecmp(cmdbuf, "STAT", 4)) {
-               pop3_stat(&cmdbuf[5]);
-       }
-
-       else if (!strncasecmp(cmdbuf, "RETR", 4)) {
-               pop3_retr(&cmdbuf[5]);
-       }
-
-       else if (!strncasecmp(cmdbuf, "DELE", 4)) {
-               pop3_dele(&cmdbuf[5]);
-       }
-
-       else if (!strncasecmp(cmdbuf, "RSET", 4)) {
-               pop3_rset(&cmdbuf[5]);
-       }
-
-       else if (!strncasecmp(cmdbuf, "UIDL", 4)) {
-               pop3_uidl(&cmdbuf[5]);
-       }
-
-       else if (!strncasecmp(cmdbuf, "TOP", 3)) {
-               pop3_top(&cmdbuf[4]);
-       }
-
-       else if (!strncasecmp(cmdbuf, "LAST", 4)) {
-               pop3_last(&cmdbuf[4]);
-       }
-
-       else {
-               cprintf("-ERR I'm afraid I can't do that.\r\n");
-       }
-
-}
-
-const char *CitadelServicePop3="POP3";
-const char *CitadelServicePop3S="POP3S";
-
-
-CTDL_MODULE_INIT(pop3)
-{
-       if(!threading)
-       {
-               CtdlRegisterServiceHook(CtdlGetConfigInt("c_pop3_port"),
-                                       NULL,
-                                       pop3_greeting,
-                                       pop3_command_loop,
-                                       NULL,
-                                       CitadelServicePop3);
-#ifdef HAVE_OPENSSL
-               CtdlRegisterServiceHook(CtdlGetConfigInt("c_pop3s_port"),
-                                       NULL,
-                                       pop3s_greeting,
-                                       pop3_command_loop,
-                                       NULL,
-                                       CitadelServicePop3S);
-#endif
-               CtdlRegisterSessionHook(pop3_cleanup_function, EVT_STOP, PRIO_STOP + 30);
-       }
-       
-       /* return our module name for the log */
-       return "pop3";
-}
diff --git a/citadel/modules/pop3/serv_pop3.h b/citadel/modules/pop3/serv_pop3.h
deleted file mode 100644 (file)
index e10213c..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (c) 1998-2017 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-struct pop3msg {
-       long msgnum;
-       size_t rfc822_length;
-       int deleted;
-};
-
-struct citpop3 {               /* Information about the current session */
-       struct pop3msg *msgs;   /* Array of message pointers */
-       int num_msgs;           /* Number of messages in array */
-       int lastseen;           /* Offset of last-read message in array */
-};
-                               /* Note: the "lastseen" is represented as the
-                                * offset in this array (zero-based), so when
-                                * displaying it to a POP3 client, it must be
-                                * incremented by one.
-                                */
-
-#define POP3 ((struct citpop3 *)CC->session_specific_data)
-
-void pop3_cleanup_function(void);
-void pop3_greeting(void);
-void pop3_user(char *argbuf);
-void pop3_pass(char *argbuf);
-void pop3_list(char *argbuf);
-void pop3_command_loop(void);
-void pop3_login(void);
-
diff --git a/citadel/modules/pop3client/.gitignore b/citadel/modules/pop3client/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/pop3client/serv_pop3client.c b/citadel/modules/pop3client/serv_pop3client.c
deleted file mode 100644 (file)
index a859fb5..0000000
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Consolidate mail from remote POP3 accounts.
- *
- * Copyright (c) 2007-2021 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <sysconfig.h>
-#include <time.h>
-#include <ctype.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <libcitadel.h>
-#include <curl/curl.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "ctdl_module.h"
-#include "clientsocket.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "database.h"
-#include "citadel_dirs.h"
-
-struct p3cq {                          // module-local queue of pop3 client work that needs processing
-       struct p3cq *next;
-       char *room;
-       char *host;
-       char *user;
-       char *pass;
-       int keep;
-       long interval;
-};
-
-static int doing_pop3client = 0;
-struct p3cq *p3cq = NULL;
-
-/*
- * Process one mailbox.
- */
-void pop3client_one_mailbox(char *room, const char *host, const char *user, const char *pass, int keep, long interval) {
-       syslog(LOG_DEBUG, "pop3client: room=<%s> host=<%s> user=<%s> keep=<%d> interval=<%ld>", room, host, user, keep, interval);
-
-       char url[SIZ];
-       CURL *curl;
-       CURLcode res = CURLE_OK;
-       StrBuf *Uidls = NULL;
-       int i;
-       char cmd[1024];
-
-       curl = curl_easy_init();
-       if (!curl) {
-               return;
-       }
-
-       Uidls = NewStrBuf();
-
-       curl_easy_setopt(curl, CURLOPT_USERNAME, user);
-       curl_easy_setopt(curl, CURLOPT_PASSWORD, pass);
-       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
-       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
-       curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15);
-       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback); // What to do with downloaded data
-       curl_easy_setopt(curl, CURLOPT_WRITEDATA, Uidls);                       // Give it our StrBuf to work with
-       curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "UIDL");
-
-       /* Try POP3S (SSL encrypted) first */
-       snprintf(url, sizeof url, "pop3s://%s", host);
-       curl_easy_setopt(curl, CURLOPT_URL, url);
-       res = curl_easy_perform(curl);
-       if (res == CURLE_OK) {
-       }
-       else {
-               syslog(LOG_DEBUG, "pop3client: POP3S connection failed: %s , trying POP3 next", curl_easy_strerror(res));
-               snprintf(url, sizeof url, "pop3://%s", host);                   // try unencrypted next
-               curl_easy_setopt(curl, CURLOPT_URL, url);
-               FlushStrBuf(Uidls);
-               res = curl_easy_perform(curl);
-       }
-
-       if (res != CURLE_OK) {
-               syslog(LOG_DEBUG, "pop3client: POP3 connection failed: %s", curl_easy_strerror(res));
-               curl_easy_cleanup(curl);
-               FreeStrBuf(&Uidls);
-               return;
-       }
-
-       // If we got this far, a connection was established, we know whether it's pop3s or pop3, and UIDL is supported.
-       // Now go through the UIDL list and look for messages.
-
-       int num_msgs = num_tokens(ChrPtr(Uidls), '\n');
-       syslog(LOG_DEBUG, "pop3client: there are %d messages", num_msgs);
-       for (i=0; i<num_msgs; ++i) {
-               char oneuidl[1024];
-               extract_token(oneuidl, ChrPtr(Uidls), i, '\n', sizeof oneuidl);
-               if (strlen(oneuidl) > 2) {
-                       if (oneuidl[strlen(oneuidl)-1] == '\r') {
-                               oneuidl[strlen(oneuidl)-1] = 0;
-                       }
-                       int this_msg = atoi(oneuidl);
-                       char *c = strchr(oneuidl, ' ');
-                       if (c) strcpy(oneuidl, ++c);
-
-                       // Make up the Use Table record so we can check if we've already seen this message.
-                       StrBuf *UT = NewStrBuf();
-                       StrBufPrintf(UT, "pop3/%s/%s:%s@%s", room, oneuidl, user, host);
-                       int already_seen = CheckIfAlreadySeen(UT);
-                       FreeStrBuf(&UT);
-
-                       // Only fetch the message if we haven't seen it before.
-                       if (already_seen == 0) {
-                               StrBuf *TheMsg = NewStrBuf();
-                               snprintf(cmd, sizeof cmd, "RETR %d", this_msg);
-                               curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, cmd);
-                               curl_easy_setopt(curl, CURLOPT_WRITEDATA, TheMsg);
-                               res = curl_easy_perform(curl);
-                               if (res == CURLE_OK) {
-                                       struct CtdlMessage *msg = convert_internet_message_buf(&TheMsg);
-                                       CtdlSubmitMsg(msg, NULL, room);
-                                       CM_Free(msg);
-                               }
-                               else {
-                                       FreeStrBuf(&TheMsg);
-                               }
-
-                               // Unless the configuration says to keep the message on the server, delete it.
-                               if (keep == 0) {
-                                       snprintf(cmd, sizeof cmd, "DELE %d", this_msg);
-                                       curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, cmd);
-                                       res = curl_easy_perform(curl);
-                               }
-                       }
-                       else {
-                               syslog(LOG_DEBUG, "pop3client: %s has already been retrieved", oneuidl);
-                       }
-               }
-       }
-
-       curl_easy_cleanup(curl);
-       FreeStrBuf(&Uidls);
-       return;
-}
-
-
-// Scan a room's netconfig looking for RSS feed parsing requests
-//
-void pop3client_scan_room(struct ctdlroom *qrbuf, void *data)
-{
-       char *serialized_config = NULL;
-       int num_configs = 0;
-       char cfgline[SIZ];
-       char cfgelement[SIZ];
-       int i = 0;
-
-        serialized_config = LoadRoomNetConfigFile(qrbuf->QRnumber);
-        if (!serialized_config) {
-               return;
-       }
-
-       num_configs = num_tokens(serialized_config, '\n');
-       for (i=0; i<num_configs; ++i) {
-               extract_token(cfgline, serialized_config, i, '\n', sizeof cfgline);
-               if (!strncasecmp(cfgline, HKEY("pop3client|"))) {
-                       struct p3cq *pptr = malloc(sizeof(struct p3cq));
-                       pptr->next = p3cq;
-                       p3cq = pptr;
-                       p3cq->room = strdup(qrbuf->QRname);
-                       extract_token(cfgelement, cfgline, 1, '|', sizeof cfgelement);
-                       p3cq->host = strdup(cfgelement);
-                       extract_token(cfgelement, cfgline, 2, '|', sizeof cfgelement);
-                       p3cq->user = strdup(cfgelement);
-                       extract_token(cfgelement, cfgline, 3, '|', sizeof cfgelement);
-                       p3cq->pass = strdup(cfgelement);
-                       p3cq->keep = extract_int(cfgline, 4);
-                       p3cq->interval = extract_long(cfgline, 5);
-               }
-       }
-
-       free(serialized_config);
-}
-
-
-void pop3client_scan(void) {
-       static time_t last_run = 0L;
-       time_t fastest_scan;
-       struct p3cq *pptr = NULL;
-
-       if (CtdlGetConfigLong("c_pop3_fastest") < CtdlGetConfigLong("c_pop3_fetch")) {
-               fastest_scan = CtdlGetConfigLong("c_pop3_fastest");
-       }
-       else {
-               fastest_scan = CtdlGetConfigLong("c_pop3_fetch");
-       }
-
-       /*
-        * Run POP3 aggregation no more frequently than once every n seconds
-        */
-       if ( (time(NULL) - last_run) < fastest_scan ) {
-               return;
-       }
-
-       /*
-        * This is a simple concurrency check to make sure only one pop3client
-        * run is done at a time.  We could do this with a mutex, but since we
-        * don't really require extremely fine granularity here, we'll do it
-        * with a static variable instead.
-        */
-       if (doing_pop3client) return;
-       doing_pop3client = 1;
-
-       syslog(LOG_DEBUG, "pop3client: scan started");
-       CtdlForEachRoom(pop3client_scan_room, NULL);
-
-       /*
-        * We have to queue and process in separate phases, otherwise we leave a cursor open
-        */
-       syslog(LOG_DEBUG, "pop3client: processing started");
-       while (p3cq != NULL) {
-               pptr = p3cq;
-               p3cq = p3cq->next;
-
-               pop3client_one_mailbox(pptr->room, pptr->host, pptr->user, pptr->pass, pptr->keep, pptr->interval);
-
-               free(pptr->room);
-               free(pptr->host);
-               free(pptr->user);
-               free(pptr->pass);
-               free(pptr);
-       }
-
-       syslog(LOG_DEBUG, "pop3client: ended");
-       last_run = time(NULL);
-       doing_pop3client = 0;
-}
-
-
-CTDL_MODULE_INIT(pop3client)
-{
-       if (!threading)
-       {
-               CtdlRegisterSessionHook(pop3client_scan, EVT_TIMER, PRIO_AGGR + 50);
-       }
-
-       /* return our module id for the log */
-       return "pop3client";
-}
diff --git a/citadel/modules/roomchat/.gitignore b/citadel/modules/roomchat/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/roomchat/serv_roomchat.c b/citadel/modules/roomchat/serv_roomchat.c
deleted file mode 100644 (file)
index 35a83db..0000000
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * This module handles instant messaging between users.
- * 
- * Copyright (c) 2012 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <assert.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "msgbase.h"
-#include "user_ops.h"
-#include "ctdl_module.h"
-
-struct chatmsg {
-       struct chatmsg *next;
-       time_t timestamp;
-       int seq;
-       long roomnum;
-       char *sender;
-       char *msgtext;
-};
-
-struct chatmsg *first_chat_msg = NULL;
-struct chatmsg *last_chat_msg = NULL;
-
-
-/* 
- * Periodically called for housekeeping.  Expire old chat messages so they don't take up memory forever.
- */
-void roomchat_timer(void) {
-       struct chatmsg *ptr;
-
-       begin_critical_section(S_CHATQUEUE);
-
-       while ((first_chat_msg != NULL) && ((time(NULL) - first_chat_msg->timestamp) > 300)) {
-               ptr = first_chat_msg->next;
-               free(first_chat_msg->sender);
-               free(first_chat_msg->msgtext);
-               free(first_chat_msg);
-               first_chat_msg = ptr;
-               if (first_chat_msg == NULL) {
-                       last_chat_msg = NULL;
-               }
-       }
-
-       end_critical_section(S_CHATQUEUE);
-}
-
-
-/*
- * Perform shutdown-related activities...
- */
-void roomchat_shutdown(void) {
-       /* if we ever start logging chats, we have to flush them to disk here .*/
-}
-
-
-/*
- * Add a message into the chat queue
- */
-void add_to_chat_queue(char *msg) {
-       static int seq = 0;
-
-       struct chatmsg *m = malloc(sizeof(struct chatmsg));
-       if (!m) return;
-
-       m->next = NULL;
-       m->timestamp = time(NULL);
-       m->roomnum = CC->room.QRnumber;
-       m->sender = strdup(CC->user.fullname);
-       m->msgtext = strdup(msg);
-
-       if ((m->sender == NULL) || (m->msgtext == NULL)) {
-               free(m->sender);
-               free(m->msgtext);
-               free(m);
-               return;
-       }
-
-       begin_critical_section(S_CHATQUEUE);
-       m->seq = ++seq;
-
-       if (first_chat_msg == NULL) {
-               assert(last_chat_msg == NULL);
-               first_chat_msg = m;
-               last_chat_msg = m;
-       }
-       else {
-               assert(last_chat_msg != NULL);
-               assert(last_chat_msg->next == NULL);
-               last_chat_msg->next = m;
-               last_chat_msg = m;
-       }
-
-       end_critical_section(S_CHATQUEUE);
-}
-
-
-/*
- * Transmit a message into a room chat
- */
-void roomchat_send(char *argbuf) {
-       char buf[1024];
-
-       if ((CC->cs_flags & CS_CHAT) == 0) {
-               cprintf("%d Session is not in chat mode.\n", ERROR);
-               return;
-       }
-
-       cprintf("%d send now\n", SEND_LISTING);
-       while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
-               add_to_chat_queue(buf);
-       }
-}
-
-
-/*
- * Poll room for incoming chat messages
- */
-void roomchat_poll(char *argbuf) {
-       int newer_than = 0;
-       struct chatmsg *found = NULL;
-       struct chatmsg *ptr = NULL;
-
-       newer_than = extract_int(argbuf, 1);
-
-       if ((CC->cs_flags & CS_CHAT) == 0) {
-               cprintf("%d Session is not in chat mode.\n", ERROR);
-               return;
-       }
-
-       begin_critical_section(S_CHATQUEUE);
-       for (ptr = first_chat_msg; ((ptr != NULL) && (found == NULL)); ptr = ptr->next) {
-               if ((ptr->seq > newer_than) && (ptr->roomnum == CC->room.QRnumber)) {
-                       found = ptr;
-               }
-       }
-       end_critical_section(S_CHATQUEUE);
-
-       if (found == NULL) {
-               cprintf("%d no messages\n", ERROR + MESSAGE_NOT_FOUND);
-               return;
-       }
-
-       cprintf("%d %d|%ld|%s\n", LISTING_FOLLOWS, found->seq, found->timestamp, found->sender);
-       cprintf("%s\n", found->msgtext);
-       cprintf("000\n");
-}
-
-
-
-/*
- * list users in chat in this room
- */
-void roomchat_rwho(char *argbuf) {
-       struct CitContext *nptr;
-       int nContexts, i;
-
-       if ((CC->cs_flags & CS_CHAT) == 0) {
-               cprintf("%d Session is not in chat mode.\n", ERROR);
-               return;
-       }
-
-       cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() );
-       
-       nptr = CtdlGetContextArray(&nContexts) ;                // grab a copy of the wholist
-       if (nptr) {
-               for (i=0; i<nContexts; i++)  {                  // list the users
-                       if ( (nptr[i].room.QRnumber == CC->room.QRnumber) 
-                          && (nptr[i].cs_flags & CS_CHAT)
-                       ) {
-                               cprintf("%s\n", nptr[i].user.fullname);
-                       }
-               }
-               free(nptr);                                     // free our copy
-       }
-
-       cprintf("000\n");
-}
-
-
-
-/*
- * Participate in real time chat in a room
- */
-void cmd_rcht(char *argbuf)
-{
-       char subcmd[16];
-
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
-
-       if (!strcasecmp(subcmd, "enter")) {
-               CC->cs_flags |= CS_CHAT;
-               cprintf("%d Entering chat mode.\n", CIT_OK);
-       }
-       else if (!strcasecmp(subcmd, "exit")) {
-               CC->cs_flags &= ~CS_CHAT;
-               cprintf("%d Exiting chat mode.\n", CIT_OK);
-       }
-       else if (!strcasecmp(subcmd, "send")) {
-               roomchat_send(argbuf);
-       }
-       else if (!strcasecmp(subcmd, "poll")) {
-               roomchat_poll(argbuf);
-       }
-       else if (!strcasecmp(subcmd, "rwho")) {
-               roomchat_rwho(argbuf);
-       }
-       else {
-               cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
-       }
-}
-
-
-CTDL_MODULE_INIT(roomchat)
-{
-       if (!threading)
-       {
-               CtdlRegisterProtoHook(cmd_rcht, "RCHT", "Participate in real time chat in a room");
-               CtdlRegisterSessionHook(roomchat_timer, EVT_TIMER, PRIO_CLEANUP + 400);
-               CtdlRegisterSessionHook(roomchat_shutdown, EVT_SHUTDOWN, PRIO_SHUTDOWN + 55);
-       }
-       
-       /* return our module name for the log */
-       return "roomchat";
-}
diff --git a/citadel/modules/rssclient/.gitignore b/citadel/modules/rssclient/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/rssclient/serv_rssclient.c b/citadel/modules/rssclient/serv_rssclient.c
deleted file mode 100644 (file)
index e0fba5f..0000000
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * Bring external RSS and/or Atom feeds into rooms.  This module implements a
- * very loose parser that scrapes both kinds of feeds and is not picky about
- * the standards compliance of the source data.
- *
- * Copyright (c) 2007-2021 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <time.h>
-#include <ctype.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <expat.h>
-#include <curl/curl.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "threads.h"
-#include "ctdl_module.h"
-#include "msgbase.h"
-#include "parsedate.h"
-#include "database.h"
-#include "citadel_dirs.h"
-#include "context.h"
-#include "internet_addressing.h"
-
-struct rssroom {
-       struct rssroom *next;
-       char *room;
-};
-
-struct rssurl {
-       struct rssurl *next;
-       char *url;
-       struct rssroom *rooms;
-};
-
-struct rssparser {
-       StrBuf *CData;
-       struct CtdlMessage *msg;
-       char *link;
-       char *description;
-       char *item_id;
-       struct rssroom *rooms;
-};
-
-time_t last_run = 0L;
-struct rssurl *rsstodo = NULL;
-
-
-// This handler is called whenever an XML tag opens.
-//
-void rss_start_element(void *data, const char *el, const char **attribute) {
-       struct rssparser *r = (struct rssparser *)data;
-       int i;
-
-       if (server_shutting_down) return;                       // shunt the whole operation if we're exiting
-
-       if (
-               (!strcasecmp(el, "entry"))
-               || (!strcasecmp(el, "item"))
-       ) {
-               // this is the start of a new item(rss) or entry(atom)
-               if (r->msg != NULL) {
-                       CM_Free(r->msg);
-                       r->msg = NULL;
-               }
-               r->msg = malloc(sizeof(struct CtdlMessage));
-               memset(r->msg, 0, sizeof(struct CtdlMessage));
-               r->msg->cm_magic = CTDLMESSAGE_MAGIC;
-               r->msg->cm_anon_type = MES_NORMAL;
-               r->msg->cm_format_type = FMT_RFC822;
-       }
-
-       else if (!strcasecmp(el, "link")) {                     // atom feeds have the link as an attribute
-               for(i = 0; attribute[i]; i += 2) {
-                       if (!strcasecmp(attribute[i], "href")) {
-                               if (r->link != NULL) {
-                                       free(r->link);
-                                       r->link = NULL;
-                               }
-                               r->link = strdup(attribute[i+1]);
-                               striplt(r->link);
-                       }
-               }
-       }
-}
-
-
-// This handler is called whenever an XML tag closes.
-//
-void rss_end_element(void *data, const char *el) {
-       struct rssparser *r = (struct rssparser *)data;
-       StrBuf *encoded_field;
-
-       if (server_shutting_down) return;                       // shunt the whole operation if we're exiting
-
-       if (StrLength(r->CData) > 0) {                          // strip leading/trailing whitespace from field
-               StrBufTrim(r->CData);
-       }
-
-       if (                                                    // end of a new item(rss) or entry(atom)
-               (!strcasecmp(el, "entry"))
-               || (!strcasecmp(el, "item"))
-       ) {
-               if (r->msg != NULL) {                           // Save the message to the rooms
-
-                       // use the link as an item id if nothing else is available
-                       if ((r->item_id == NULL) && (r->link != NULL)) {
-                               r->item_id = strdup(r->link);
-                       }
-
-                       // check the use table
-                       StrBuf *u = NewStrBuf();
-                       StrBufAppendPrintf(u, "rss/%s", r->item_id);
-                       int already_seen = CheckIfAlreadySeen(u);
-                       FreeStrBuf(&u);
-
-                       if (already_seen == 0) {
-
-                               // Compose the message text
-                               StrBuf *TheMessage = NewStrBuf();
-                               StrBufAppendPrintf(TheMessage,
-                                       "Content-type: text/html\n\n"
-                                       "\n\n"
-                                       "<html><head></head><body>"
-                               );
-               
-                               if (r->description != NULL) {
-                                       StrBufAppendPrintf(TheMessage, "%s<br><br>\r\n", r->description);
-                                       free(r->description);
-                                       r->description = NULL;
-                               }
-               
-                               if (r->link != NULL) {
-                                       StrBufAppendPrintf(TheMessage, "<a href=\"%s\">%s</a>\r\n", r->link, r->link);
-                                       free(r->link);
-                                       r->link = NULL;
-                               }
-       
-                               StrBufAppendPrintf(TheMessage, "</body></html>\r\n");
-                               CM_SetField(r->msg, eMesageText, ChrPtr(TheMessage), StrLength(TheMessage));
-                               FreeStrBuf(&TheMessage);
-       
-                               if (CM_IsEmpty(r->msg, eAuthor)) {
-                                       CM_SetField(r->msg, eAuthor, HKEY("rss"));
-                               }
-       
-                               if (CM_IsEmpty(r->msg, eTimestamp)) {
-                                       CM_SetFieldLONG(r->msg, eTimestamp, time(NULL));
-                               }
-       
-                               // Save it to the room(s)
-                               struct rssroom *rr = NULL;
-                               long msgnum = (-1);
-                               for (rr=r->rooms; rr!=NULL; rr=rr->next) {
-                                       if (rr == r->rooms) {
-                                               msgnum = CtdlSubmitMsg(r->msg, NULL, rr->room);         // in first room, save msg
-                                       }
-                                       else {
-                                               CtdlSaveMsgPointerInRoom(rr->room, msgnum, 0, NULL);    // elsewhere, save a pointer
-                                       }
-                                       syslog(LOG_DEBUG, "rssclient: saved message %ld to %s", msgnum, rr->room);
-                               }
-                       }
-                       else {
-                               syslog(LOG_DEBUG, "rssclient: already seen %s", r->item_id);
-                       }
-       
-                       CM_Free(r->msg);
-                       r->msg = NULL;
-               }
-
-               if (r->item_id != NULL) {
-                       free(r->item_id);
-                       r->item_id = NULL;
-               }
-       }
-
-       else if (!strcasecmp(el, "title")) {                    // item subject (rss and atom)
-               if ((r->msg != NULL) && (CM_IsEmpty(r->msg, eMsgSubject))) {
-                       encoded_field = NewStrBuf();
-                       StrBufRFC2047encode(&encoded_field, r->CData);
-                       CM_SetAsFieldSB(r->msg, eMsgSubject, &encoded_field);
-               }
-       }
-
-       else if (!strcasecmp(el, "creator")) {                  // <creator> can be used if <author> is not present
-               if ((r->msg != NULL) && (CM_IsEmpty(r->msg, eAuthor))) {
-                       encoded_field = NewStrBuf();
-                       StrBufRFC2047encode(&encoded_field, r->CData);
-                       CM_SetAsFieldSB(r->msg, eAuthor, &encoded_field);
-               }
-       }
-
-       else if (!strcasecmp(el, "author")) {                   // <author> supercedes <creator> if both are present
-               if (r->msg != NULL) {
-                       encoded_field = NewStrBuf();
-                       StrBufRFC2047encode(&encoded_field, r->CData);
-                       CM_SetAsFieldSB(r->msg, eAuthor, &encoded_field);
-               }
-       }
-
-       else if (!strcasecmp(el, "pubdate")) {                  // date/time stamp (rss) Sat, 25 Feb 2017 14:28:01 EST
-               if ((r->msg)&&(r->msg->cm_fields[eTimestamp]==NULL)) {
-                       CM_SetFieldLONG(r->msg, eTimestamp, parsedate(ChrPtr(r->CData)));
-               }
-       }
-
-       else if (!strcasecmp(el, "updated")) {                  // date/time stamp (atom) 2003-12-13T18:30:02Z
-               if ((r->msg)&&(r->msg->cm_fields[eTimestamp]==NULL)) {
-                       struct tm t;
-                       char zulu;
-                       memset(&t, 0, sizeof t);
-                       sscanf(ChrPtr(r->CData), "%d-%d-%dT%d:%d:%d%c", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec, &zulu);
-                       t.tm_year -= 1900;
-                       t.tm_mon -= 1;
-                       CM_SetFieldLONG(r->msg, eTimestamp, mktime(&t));
-               }
-       }
-
-       else if (!strcasecmp(el, "link")) {                     // link to story (rss)
-               if (r->link != NULL) {
-                       free(r->link);
-                       r->link = NULL;
-               }
-               r->link = strdup(ChrPtr(r->CData));
-       }
-
-       else if (
-               (!strcasecmp(el, "guid"))                       // unique item id (rss)
-               || (!strcasecmp(el, "id"))                      // unique item id (atom)
-       ) {
-               if (r->item_id != NULL) {
-                       free(r->item_id);
-                       r->item_id = NULL;
-               }
-               r->item_id = strdup(ChrPtr(r->CData));
-       }
-
-       else if (
-               (!strcasecmp(el, "description"))                // message text (rss)
-               || (!strcasecmp(el, "summary"))                 // message text (atom)
-               || (!strcasecmp(el, "content"))                 // message text (atom)
-       ) {
-               if (r->description != NULL) {
-                       free(r->description);
-                       r->description = NULL;
-               }
-               r->description = strdup(ChrPtr(r->CData));
-       }
-
-       if (r->CData != NULL) {
-               FreeStrBuf(&r->CData);
-               r->CData = NULL;
-       }
-}
-
-
-// This handler is called whenever data appears between opening and closing tags.
-//
-void rss_handle_data(void *data, const char *content, int length)
-{
-       struct rssparser *r = (struct rssparser *)data;
-
-       if (r->CData == NULL) {
-               r->CData = NewStrBuf();
-       }
-
-       StrBufAppendBufPlain(r->CData, content, length, 0);
-}
-
-
-// Feed has been downloaded, now parse it.
-//
-void rss_parse_feed(StrBuf *Feed, struct rssroom *rooms)
-{
-       struct rssparser r;
-
-       memset(&r, 0, sizeof r);
-       r.rooms = rooms;
-       XML_Parser p = XML_ParserCreate("UTF-8");
-       XML_SetElementHandler(p, rss_start_element, rss_end_element);
-       XML_SetCharacterDataHandler(p, rss_handle_data);
-       XML_SetUserData(p, (void *)&r);
-       XML_Parse(p, ChrPtr(Feed), StrLength(Feed), XML_TRUE);
-       XML_ParserFree(p);
-}
-
-
-// Add a feed/room pair into the todo list
-//
-void rssclient_push_todo(char *rssurl, char *roomname)
-{
-       struct rssurl *r = NULL;
-       struct rssurl *thisone = NULL;
-       struct rssroom *newroom = NULL;
-
-       syslog(LOG_DEBUG, "rssclient: will fetch %s to %s", rssurl, roomname);
-
-       for (r=rsstodo; r!=NULL; r=r->next) {
-               if (!strcasecmp(r->url, rssurl)) {
-                       thisone = r;
-               }
-       }
-
-       if (thisone == NULL) {
-               thisone = malloc(sizeof(struct rssurl));
-               thisone->url = strdup(rssurl);
-               thisone->rooms = NULL;
-               thisone->next = rsstodo;
-               rsstodo = thisone;
-       }
-
-       newroom = malloc(sizeof(struct rssroom));
-       newroom->room = strdup(roomname);
-       newroom->next = thisone->rooms;
-       thisone->rooms = newroom;
-}
-
-
-// pull one feed (possibly multiple rooms)
-//
-void rss_pull_one_feed(struct rssurl *url)
-{
-       CURL *curl;
-       CURLcode res;
-       StrBuf *Downloaded = NULL;
-
-       syslog(LOG_DEBUG, "rssclient: fetching %s", url->url);
-
-       curl = curl_easy_init();
-       if (!curl) {
-               return;
-       }
-
-       Downloaded = NewStrBuf();
-
-       curl_easy_setopt(curl, CURLOPT_URL, url->url);
-       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
-       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
-       curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);                     // Follow redirects
-       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback); // What to do with downloaded data
-       curl_easy_setopt(curl, CURLOPT_WRITEDATA, Downloaded);                  // Give it our StrBuf to work with
-       curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L);                           // Time out after 20 seconds
-       res = curl_easy_perform(curl);                                          // Perform the request
-       if (res != CURLE_OK) {
-               syslog(LOG_WARNING, "rssclient: failed to load feed: %s", curl_easy_strerror(res));
-       }
-       curl_easy_cleanup(curl);
-
-       rss_parse_feed(Downloaded, url->rooms);                                 // parse the feed
-       FreeStrBuf(&Downloaded);                                                // free the downloaded feed data
-}
-
-
-// We have a list, now download the feeds
-//
-void rss_pull_feeds(void)
-{
-       struct rssurl *r;
-       struct rssroom *rr;
-
-       while ((rsstodo != NULL) && (!server_shutting_down)) {
-               rss_pull_one_feed(rsstodo);
-               r = rsstodo;
-               rsstodo = rsstodo->next;
-               while (r->rooms != NULL) {
-                       rr = r->rooms;
-                       r->rooms = r->rooms->next;
-                       free(rr->room);
-                       free(rr);
-               }
-               free(r->url);
-               free(r);
-       }
-}
-
-
-// Scan a room's netconfig looking for RSS feed parsing requests
-//
-void rssclient_scan_room(struct ctdlroom *qrbuf, void *data)
-{
-       char *serialized_config = NULL;
-       int num_configs = 0;
-       char cfgline[SIZ];
-       int i = 0;
-
-       if (server_shutting_down) return;
-
-        serialized_config = LoadRoomNetConfigFile(qrbuf->QRnumber);
-        if (!serialized_config) {
-               return;
-       }
-
-       num_configs = num_tokens(serialized_config, '\n');
-       for (i=0; i<num_configs; ++i) {
-               extract_token(cfgline, serialized_config, i, '\n', sizeof cfgline);
-               if (!strncasecmp(cfgline, HKEY("rssclient|"))) {
-                       strcpy(cfgline, &cfgline[10]);
-                       char *vbar = strchr(cfgline, '|');
-                       if (vbar != NULL) {
-                               *vbar = 0;
-                       }
-                       rssclient_push_todo(cfgline, qrbuf->QRname);
-               }
-       }
-
-       free(serialized_config);
-}
-
-
-/*
- * Scan for rooms that have RSS client requests configured
- */
-void rssclient_scan(void) {
-       time_t now = time(NULL);
-
-       /* Run no more than once every 15 minutes. */
-       if ((now - last_run) < 900) {
-               syslog(LOG_DEBUG,
-                       "rssclient: polling interval not yet reached; last run was %ldm%lds ago",
-                       ((now - last_run) / 60),
-                       ((now - last_run) % 60)
-               );
-               return;
-       }
-
-       syslog(LOG_DEBUG, "rssclient: started");
-       CtdlForEachRoom(rssclient_scan_room, NULL);
-       rss_pull_feeds();
-       syslog(LOG_DEBUG, "rssclient: ended");
-       last_run = time(NULL);
-       return;
-}
-
-
-CTDL_MODULE_INIT(rssclient)
-{
-       if (!threading)
-       {
-               syslog(LOG_INFO, "rssclient: using %s", curl_version());
-               CtdlRegisterSessionHook(rssclient_scan, EVT_TIMER, PRIO_AGGR + 300);
-       }
-       return "rssclient";
-}
diff --git a/citadel/modules/rwho/.gitignore b/citadel/modules/rwho/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/rwho/serv_rwho.c b/citadel/modules/rwho/serv_rwho.c
deleted file mode 100644 (file)
index a134a8c..0000000
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * This module implements server commands related to the display and
- * manipulation of the "Who's online" list.
- *
- * Copyright (c) 1987-2019 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "ctdl_module.h"
-
-/* Don't show the names of private rooms unless the viewing
- * user also knows the rooms.
- */
-void GenerateRoomDisplay(char *real_room,
-                       CitContext *viewed,
-                       CitContext *viewer) {
-
-       int ra;
-
-       strcpy(real_room, viewed->room.QRname);
-       if (viewed->room.QRflags & QR_MAILBOX) {
-               strcpy(real_room, &real_room[11]);
-       }
-       if (viewed->room.QRflags & QR_PRIVATE) {
-               CtdlRoomAccess(&viewed->room, &viewer->user, &ra, NULL);
-               if ( (ra & UA_KNOWN) == 0) {
-                       strcpy(real_room, " ");
-               }
-       }
-
-       if (viewed->cs_flags & CS_CHAT) {
-               while (strlen(real_room) < 14) {
-                       strcat(real_room, " ");
-               }
-               strcpy(&real_room[14], "<chat>");
-       }
-
-}
-
-
-
-/*
- * display who's online
- */
-void cmd_rwho(char *argbuf) {
-       struct CitContext *nptr;
-       int nContexts, i;
-       int spoofed = 0;
-       int aide;
-       char room[ROOMNAMELEN];
-       char flags[5];
-       
-       /* So that we don't keep the context list locked for a long time
-        * we create a copy of it first
-        */
-       nptr = CtdlGetContextArray(&nContexts) ;
-       if (!nptr)
-       {
-               /* Couldn't malloc so we have to bail but stick to the protocol */
-               cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() );
-               cprintf("000\n");
-               return;
-       }
-       
-       aide = ( (CC->user.axlevel >= AxAideU) || (CC->internal_pgm) ) ;
-       cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() );
-       
-       for (i=0; i<nContexts; i++)  {
-               flags[0] = '\0';
-               spoofed = 0;
-               
-               if (!aide && nptr[i].state == CON_SYS)
-                       continue;
-
-               if (!aide && nptr[i].kill_me != 0)
-                       continue;
-
-               if (nptr[i].cs_flags & CS_POSTING) {
-                       strcat(flags, "*");
-               }
-               else {
-                       strcat(flags, ".");
-               }
-                  
-               GenerateRoomDisplay(room, &nptr[i], CC);
-
-                if ((aide) && (spoofed)) {
-                       strcat(flags, "+");
-               }
-               
-               if ((nptr[i].cs_flags & CS_STEALTH) && (aide)) {
-                       strcat(flags, "-");
-               }
-               
-               if (((nptr[i].cs_flags&CS_STEALTH)==0) || (aide)) {
-
-                       cprintf("%d|%s|%s|%s|%s|%ld|%s|%s|",
-                               nptr[i].cs_pid, nptr[i].curr_user, room,
-                               nptr[i].cs_host, nptr[i].cs_clientname,
-                               (long)(nptr[i].lastidle),
-                               nptr[i].lastcmdname, flags
-                       );
-
-                       cprintf("|");   // no spoofed user names anymore
-                       cprintf("|");   // no spoofed room names anymore
-                       cprintf("|");   // no spoofed host names anymore
-       
-                       cprintf("%d\n", nptr[i].logged_in);
-               }
-       }
-       
-       /* release our copy of the context list */
-       free(nptr);
-
-       /* Now it's magic time.  Before we finish, call any EVT_RWHO hooks
-        * so that external paging modules such as serv_icq can add more
-        * content to the Wholist.
-        */
-       PerformSessionHooks(EVT_RWHO);
-       cprintf("000\n");
-}
-
-
-/*
- * enter or exit "stealth mode"
- */
-void cmd_stel(char *cmdbuf)
-{
-       int requested_mode;
-
-       requested_mode = extract_int(cmdbuf,0);
-
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       if (requested_mode == 1) {
-               CC->cs_flags = CC->cs_flags | CS_STEALTH;
-               PerformSessionHooks(EVT_STEALTH);
-       }
-       if (requested_mode == 0) {
-               CC->cs_flags = CC->cs_flags & ~CS_STEALTH;
-               PerformSessionHooks(EVT_UNSTEALTH);
-       }
-
-       cprintf("%d %d\n", CIT_OK,
-               ((CC->cs_flags & CS_STEALTH) ? 1 : 0) );
-}
-
-
-CTDL_MODULE_INIT(rwho)
-{
-       if(!threading)
-       {
-               CtdlRegisterProtoHook(cmd_rwho, "RWHO", "Display who is online");
-               CtdlRegisterProtoHook(cmd_stel, "STEL", "Enter/exit stealth mode");
-               //CtdlRegisterSessionHook(dead_io_check, EVT_TIMER, PRIO_QUEUE + 50);
-
-       }
-       
-       /* return our module name for the log */
-        return "rwho";
-}
diff --git a/citadel/modules/smtp/.gitignore b/citadel/modules/smtp/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/smtp/serv_smtp.c b/citadel/modules/smtp/serv_smtp.c
deleted file mode 100644 (file)
index b567f58..0000000
+++ /dev/null
@@ -1,1066 +0,0 @@
-// This module is an SMTP and ESMTP server for the Citadel system.
-// It is compliant with all of the following:
-//
-// RFC  821 - Simple Mail Transfer Protocol
-// RFC  876 - Survey of SMTP Implementations
-// RFC 1047 - Duplicate messages and SMTP
-// RFC 1652 - 8 bit MIME
-// RFC 1869 - Extended Simple Mail Transfer Protocol
-// RFC 1870 - SMTP Service Extension for Message Size Declaration
-// RFC 2033 - Local Mail Transfer Protocol
-// RFC 2197 - SMTP Service Extension for Command Pipelining
-// RFC 2476 - Message Submission
-// RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
-// RFC 2554 - SMTP Service Extension for Authentication
-// RFC 2821 - Simple Mail Transfer Protocol
-// RFC 2822 - Internet Message Format
-// RFC 2920 - SMTP Service Extension for Command Pipelining
-//  
-// The VRFY and EXPN commands have been removed from this implementation
-// because nobody uses these commands anymore, except for spammers.
-//
-// Copyright (c) 1998-2022 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//  
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <termios.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <syslog.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "room_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "genstamp.h"
-#include "domain.h"
-#include "clientsocket.h"
-#include "locate_host.h"
-#include "citadel_dirs.h"
-#include "ctdl_module.h"
-
-#include "smtp_util.h"
-
-enum {                         // Command states for login authentication
-       smtp_command,
-       smtp_user,
-       smtp_password,
-       smtp_plain
-};
-
-enum SMTP_FLAGS {
-       HELO,
-       EHLO,
-       LHLO
-};
-
-
-// Here's where our SMTP session begins its happy day.
-void smtp_greeting(int is_msa) {
-       char message_to_spammer[1024];
-
-       strcpy(CC->cs_clientname, "SMTP session");
-       CC->internal_pgm = 1;
-       CC->cs_flags |= CS_STEALTH;
-       CC->session_specific_data = malloc(sizeof(struct citsmtp));
-       memset(SMTP, 0, sizeof(struct citsmtp));
-       SMTP->is_msa = is_msa;
-       SMTP->Cmd = NewStrBufPlain(NULL, SIZ);
-       SMTP->helo_node = NewStrBuf();
-       SMTP->from = NewStrBufPlain(NULL, SIZ);
-       SMTP->recipients = NewStrBufPlain(NULL, SIZ);
-       SMTP->OneRcpt = NewStrBufPlain(NULL, SIZ);
-       SMTP->preferred_sender_email = NULL;
-       SMTP->preferred_sender_name = NULL;
-
-       // If this config option is set, reject connections from problem
-       // addresses immediately instead of after they execute a RCPT
-       if ( (CtdlGetConfigInt("c_rbl_at_greeting")) && (SMTP->is_msa == 0) ) {
-               if (rbl_check(CC->cs_addr, message_to_spammer)) {
-                       if (server_shutting_down)
-                               cprintf("421 %s\r\n", message_to_spammer);
-                       else
-                               cprintf("550 %s\r\n", message_to_spammer);
-                       CC->kill_me = KILLME_SPAMMER;
-                       /* no need to free_recipients(valid), it's not allocated yet */
-                       return;
-               }
-       }
-
-       // Otherwise we're either clean or we check later.
-
-       if (CC->nologin==1) {
-               cprintf("451 Too many connections are already open; please try again later.\r\n");
-               CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED;
-               // no need to free_recipients(valid), it's not allocated yet
-               return;
-       }
-
-       // Note: the FQDN *must* appear as the first thing after the 220 code.
-       // Some clients (including citmail.c) depend on it being there.
-       cprintf("220 %s ESMTP Citadel server ready.\r\n", CtdlGetConfigStr("c_fqdn"));
-}
-
-
-// SMTPS is just like SMTP, except it goes crypto right away.
-void smtps_greeting(void) {
-       CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
-#ifdef HAVE_OPENSSL
-       if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;          // kill session if no crypto
-#endif
-       smtp_greeting(0);
-}
-
-
-// SMTP MSA port requires authentication.
-void smtp_msa_greeting(void) {
-       smtp_greeting(1);
-}
-
-
-// LMTP is like SMTP but with some extra bonus footage added.
-void lmtp_greeting(void) {
-
-       smtp_greeting(0);
-       SMTP->is_lmtp = 1;
-}
-
-
-// Generic SMTP MTA greeting
-void smtp_mta_greeting(void) {
-       smtp_greeting(0);
-}
-
-
-// We also have an unfiltered LMTP socket that bypasses spam filters.
-void lmtp_unfiltered_greeting(void) {
-       smtp_greeting(0);
-       SMTP->is_lmtp = 1;
-       SMTP->is_unfiltered = 1;
-}
-
-
-// Login greeting common to all auth methods
-void smtp_auth_greeting(void) {
-       cprintf("235 Hello, %s\r\n", CC->user.fullname);
-       syslog(LOG_INFO, "serv_smtp: SMTP authenticated %s", CC->user.fullname);
-       CC->internal_pgm = 0;
-       CC->cs_flags &= ~CS_STEALTH;
-}
-
-
-// Implement HELO and EHLO commands.
-// which_command:  0=HELO, 1=EHLO, 2=LHLO
-void smtp_hello(int which_command) {
-
-       if (StrLength(SMTP->Cmd) >= 6) {
-               FlushStrBuf(SMTP->helo_node);
-               StrBufAppendBuf(SMTP->helo_node, SMTP->Cmd, 5);
-       }
-
-       if ( (which_command != LHLO) && (SMTP->is_lmtp) ) {
-               cprintf("500 Only LHLO is allowed when running LMTP\r\n");
-               return;
-       }
-
-       if ( (which_command == LHLO) && (SMTP->is_lmtp == 0) ) {
-               cprintf("500 LHLO is only allowed when running LMTP\r\n");
-               return;
-       }
-
-       if (which_command == HELO) {
-               cprintf("250 Hello %s (%s [%s])\r\n",
-                       ChrPtr(SMTP->helo_node),
-                       CC->cs_host,
-                       CC->cs_addr
-               );
-       }
-       else {
-               if (which_command == EHLO) {
-                       cprintf("250-Hello %s (%s [%s])\r\n",
-                               ChrPtr(SMTP->helo_node),
-                               CC->cs_host,
-                               CC->cs_addr
-                       );
-               }
-               else {
-                       cprintf("250-Greetings and joyous salutations.\r\n");
-               }
-               cprintf("250-HELP\r\n");
-               cprintf("250-SIZE %ld\r\n", CtdlGetConfigLong("c_maxmsglen"));
-
-#ifdef HAVE_OPENSSL
-               // Offer TLS, but only if TLS is not already active.
-               // Furthermore, only offer TLS when running on
-               // the SMTP-MSA port, not on the SMTP-MTA port, due to
-               // questionable reliability of TLS in certain sending MTA's.
-               if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
-                       cprintf("250-STARTTLS\r\n");
-               }
-#endif
-
-               cprintf("250-AUTH LOGIN PLAIN\r\n"
-                       "250-AUTH=LOGIN PLAIN\r\n"
-                       "250 8BITMIME\r\n"
-               );
-       }
-}
-
-
-// Backend function for smtp_webcit_preferences_hack().
-// Look at a message and determine if it's the preferences file.
-void smtp_webcit_preferences_hack_backend(long msgnum, void *userdata) {
-       struct CtdlMessage *msg;
-       char **webcit_conf = (char **) userdata;
-
-       if (*webcit_conf) {
-               return;         // already got it
-       }
-
-       msg = CtdlFetchMessage(msgnum, 1);
-       if (msg == NULL) {
-               return;
-       }
-
-       if ( !CM_IsEmpty(msg, eMsgSubject) && (!strcasecmp(msg->cm_fields[eMsgSubject], "__ WebCit Preferences __"))) {
-               // This is it!  Change ownership of the message text so it doesn't get freed.
-               *webcit_conf = (char *)msg->cm_fields[eMesageText];
-               msg->cm_fields[eMesageText] = NULL;
-       }
-       CM_Free(msg);
-}
-
-
-// The configuration item for the user's preferred display name for outgoing email is, unfortunately,
-// stored in the account's WebCit configuration.  We have to fetch it now.
-void smtp_webcit_preferences_hack(void) {
-       char config_roomname[ROOMNAMELEN];
-       char *webcit_conf = NULL;
-
-       snprintf(config_roomname, sizeof config_roomname, "%010ld.%s", CC->user.usernum, USERCONFIGROOM);
-       if (CtdlGetRoom(&CC->room, config_roomname) != 0) {
-               return;
-       }
-
-       // Find the WebCit configuration message
-       CtdlForEachMessage(MSGS_ALL, 1, NULL, NULL, NULL, smtp_webcit_preferences_hack_backend, (void *)&webcit_conf);
-
-       if (!webcit_conf) {
-               return;
-       }
-
-       // Parse the webcit configuration and attempt to do something useful with it
-       char *str = webcit_conf;
-       char *saveptr = str;
-       char *this_line = NULL;
-       while (this_line = strtok_r(str, "\n", &saveptr), this_line != NULL) {
-               str = NULL;
-               if (!strncasecmp(this_line, "defaultfrom|", 12)) {
-                       SMTP->preferred_sender_email = NewStrBufPlain(&this_line[12], -1);
-               }
-               if (!strncasecmp(this_line, "defaultname|", 12)) {
-                       SMTP->preferred_sender_name = NewStrBufPlain(&this_line[12], -1);
-               }
-               if ((!strncasecmp(this_line, "defaultname|", 12)) && (SMTP->preferred_sender_name == NULL)) {
-                       SMTP->preferred_sender_name = NewStrBufPlain(&this_line[12], -1);
-               }
-
-       }
-       free(webcit_conf);
-}
-
-
-// Implement HELP command.
-void smtp_help(void) {
-       cprintf("214 RTFM http://www.ietf.org/rfc/rfc2821.txt\r\n");
-}
-
-
-void smtp_get_user(int offset) {
-       char buf[SIZ];
-
-       StrBuf *UserName = NewStrBufDup(SMTP->Cmd);
-       StrBufCutLeft(UserName, offset);
-       StrBufDecodeBase64(UserName);
-
-       if (CtdlLoginExistingUser(ChrPtr(UserName)) == login_ok) {
-               size_t len = CtdlEncodeBase64(buf, "Password:", 9, 0);
-
-               if (buf[len - 1] == '\n') {
-                       buf[len - 1] = '\0';
-               }
-               cprintf("334 %s\r\n", buf);
-               SMTP->command_state = smtp_password;
-       }
-       else {
-               cprintf("500 No such user.\r\n");
-               SMTP->command_state = smtp_command;
-       }
-       FreeStrBuf(&UserName);
-}
-
-
-void smtp_get_pass(void) {
-       char password[SIZ];
-
-       memset(password, 0, sizeof(password));
-       StrBufDecodeBase64(SMTP->Cmd);
-       syslog(LOG_DEBUG, "serv_smtp: trying <%s>", password);
-       if (CtdlTryPassword(SKEY(SMTP->Cmd)) == pass_ok) {
-               smtp_auth_greeting();
-       }
-       else {
-               cprintf("535 Authentication failed.\r\n");
-       }
-       SMTP->command_state = smtp_command;
-}
-
-
-// Back end for PLAIN auth method (either inline or multistate)
-void smtp_try_plain(void) {
-       const char*decoded_authstring;
-       char ident[256] = "";
-       char user[256] = "";
-       char pass[256] = "";
-       int result;
-
-       long decoded_len;
-       long len = 0;
-       long plen = 0;
-
-       memset(pass, 0, sizeof(pass));
-       decoded_len = StrBufDecodeBase64(SMTP->Cmd);
-
-       if (decoded_len > 0) {
-               decoded_authstring = ChrPtr(SMTP->Cmd);
-
-               len = safestrncpy(ident, decoded_authstring, sizeof ident);
-
-               decoded_len -= len - 1;
-               decoded_authstring += len + 1;
-
-               if (decoded_len > 0) {
-                       len = safestrncpy(user, decoded_authstring, sizeof user);
-
-                       decoded_authstring += len + 1;
-                       decoded_len -= len - 1;
-               }
-
-               if (decoded_len > 0) {
-                       plen = safestrncpy(pass, decoded_authstring, sizeof pass);
-
-                       if (plen < 0)
-                               plen = sizeof(pass) - 1;
-               }
-       }
-
-       SMTP->command_state = smtp_command;
-
-       if (!IsEmptyStr(ident)) {
-               result = CtdlLoginExistingUser(ident);
-       }
-       else {
-               result = CtdlLoginExistingUser(user);
-       }
-
-       if (result == login_ok) {
-               if (CtdlTryPassword(pass, plen) == pass_ok) {
-                       smtp_webcit_preferences_hack();
-                       smtp_auth_greeting();
-                       return;
-               }
-       }
-       cprintf("504 Authentication failed.\r\n");
-}
-
-
-// Attempt to perform authenticated SMTP
-void smtp_auth(void) {
-       char username_prompt[64];
-       char method[64];
-       char encoded_authstring[1024];
-
-       if (CC->logged_in) {
-               cprintf("504 Already logged in.\r\n");
-               return;
-       }
-
-       if (StrLength(SMTP->Cmd) < 6) {
-               cprintf("501 Syntax error\r\n");
-               return;
-       }
-
-       extract_token(method, ChrPtr(SMTP->Cmd) + 5, 0, ' ', sizeof method);
-
-       if (!strncasecmp(method, "login", 5) ) {
-               if (StrLength(SMTP->Cmd) >= 12) {
-                       syslog(LOG_DEBUG, "serv_smtp: username <%s> supplied inline", ChrPtr(SMTP->Cmd)+11);
-                       smtp_get_user(11);
-               }
-               else {
-                       size_t len = CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
-                       if (username_prompt[len - 1] == '\n') {
-                               username_prompt[len - 1] = '\0';
-                       }
-                       cprintf("334 %s\r\n", username_prompt);
-                       SMTP->command_state = smtp_user;
-               }
-               return;
-       }
-
-       if (!strncasecmp(method, "plain", 5) ) {
-               long len;
-               if (num_tokens(ChrPtr(SMTP->Cmd) + 5, ' ') < 2) {
-                       cprintf("334 \r\n");
-                       SMTP->command_state = smtp_plain;
-                       return;
-               }
-
-               len = extract_token(encoded_authstring, 
-                                   ChrPtr(SMTP->Cmd) + 5,
-                                   1, ' ',
-                                   sizeof encoded_authstring);
-               StrBufPlain(SMTP->Cmd, encoded_authstring, len);
-               smtp_try_plain();
-               return;
-       }
-
-       cprintf("504 Unknown authentication method.\r\n");
-       return;
-}
-
-
-// Implements the RSET (reset state) command.
-// Currently this just zeroes out the state buffer.  If pointers to data
-// allocated with malloc() are ever placed in the state buffer, we have to
-// be sure to free() them first!
-//
-// Set do_response to nonzero to output the SMTP RSET response code.
-void smtp_rset(int do_response) {
-       FlushStrBuf(SMTP->Cmd);
-       FlushStrBuf(SMTP->helo_node);
-       FlushStrBuf(SMTP->from);
-       FlushStrBuf(SMTP->recipients);
-       FlushStrBuf(SMTP->OneRcpt);
-
-       SMTP->command_state = 0;
-       SMTP->number_of_recipients = 0;
-       SMTP->delivery_mode = 0;
-       SMTP->message_originated_locally = 0;
-       SMTP->is_msa = 0;
-       // is_lmtp and is_unfiltered should not be cleared. 
-
-       if (do_response) {
-               cprintf("250 Zap!\r\n");
-       }
-}
-
-
-// Clear out the portions of the state buffer that need to be cleared out
-// after the DATA command finishes.
-void smtp_data_clear(void) {
-       FlushStrBuf(SMTP->from);
-       FlushStrBuf(SMTP->recipients);
-       FlushStrBuf(SMTP->OneRcpt);
-       SMTP->number_of_recipients = 0;
-       SMTP->delivery_mode = 0;
-       SMTP->message_originated_locally = 0;
-}
-
-
-// Implements the "MAIL FROM:" command
-void smtp_mail(void) {
-       char user[SIZ];
-       char node[SIZ];
-       char name[SIZ];
-
-       if (StrLength(SMTP->from) > 0) {
-               cprintf("503 Only one sender permitted\r\n");
-               return;
-       }
-
-       if (StrLength(SMTP->Cmd) < 6) {
-               cprintf("501 Syntax error\r\n");
-               return;
-       }
-
-       if (strncasecmp(ChrPtr(SMTP->Cmd) + 5, "From:", 5)) {
-               cprintf("501 Syntax error\r\n");
-               return;
-       }
-
-       StrBufAppendBuf(SMTP->from, SMTP->Cmd, 5);
-       StrBufTrim(SMTP->from);
-       if (strchr(ChrPtr(SMTP->from), '<') != NULL) {
-               StrBufStripAllBut(SMTP->from, '<', '>');
-       }
-
-       // We used to reject empty sender names, until it was brought to our
-       // attention that RFC1123 5.2.9 requires that this be allowed.  So now
-       // we allow it, but replace the empty string with a fake
-       // address so we don't have to contend with the empty string causing
-       // other code to fail when it's expecting something there.
-       if (StrLength(SMTP->from) == 0) {
-               StrBufPlain(SMTP->from, HKEY("someone@example.com"));
-       }
-
-       // If this SMTP connection is from a logged-in user, force the 'from'
-       // to be the user's Internet e-mail address as Citadel knows it.
-       if (CC->logged_in) {
-               StrBufPlain(SMTP->from, CC->cs_inet_email, -1);
-               cprintf("250 Sender ok <%s>\r\n", ChrPtr(SMTP->from));
-               SMTP->message_originated_locally = 1;
-               return;
-       }
-
-       else if (SMTP->is_lmtp) {
-               // Bypass forgery checking for LMTP
-       }
-
-       // Otherwise, make sure outsiders aren't trying to forge mail from
-       // this system (unless, of course, c_allow_spoofing is enabled)
-       else if (CtdlGetConfigInt("c_allow_spoofing") == 0) {
-               process_rfc822_addr(ChrPtr(SMTP->from), user, node, name);
-               syslog(LOG_DEBUG, "serv_smtp: claimed envelope sender is '%s' == '%s' @ '%s' ('%s')",
-                       ChrPtr(SMTP->from), user, node, name
-               );
-               if (CtdlHostAlias(node) != hostalias_nomatch) {
-                       cprintf("550 You must log in to send mail from %s\r\n", node);
-                       FlushStrBuf(SMTP->from);
-                       syslog(LOG_DEBUG, "serv_smtp: rejecting unauthenticated mail from %s", node);
-                       return;
-               }
-       }
-
-       cprintf("250 Sender ok\r\n");
-}
-
-
-// Implements the "RCPT To:" command
-void smtp_rcpt(void) {
-       char message_to_spammer[SIZ];
-       struct recptypes *valid = NULL;
-
-       if (StrLength(SMTP->from) == 0) {
-               cprintf("503 Need MAIL before RCPT\r\n");
-               return;
-       }
-       
-       if (StrLength(SMTP->Cmd) < 6) {
-               cprintf("501 Syntax error\r\n");
-               return;
-       }
-
-       if (strncasecmp(ChrPtr(SMTP->Cmd) + 5, "To:", 3)) {
-               cprintf("501 Syntax error\r\n");
-               return;
-       }
-
-       if ( (SMTP->is_msa) && (!CC->logged_in) ) {
-               cprintf("550 You must log in to send mail on this port.\r\n");
-               FlushStrBuf(SMTP->from);
-               return;
-       }
-
-       FlushStrBuf(SMTP->OneRcpt);
-       StrBufAppendBuf(SMTP->OneRcpt, SMTP->Cmd, 8);
-       StrBufTrim(SMTP->OneRcpt);
-       StrBufStripAllBut(SMTP->OneRcpt, '<', '>');
-
-       if ( (StrLength(SMTP->OneRcpt) + StrLength(SMTP->recipients)) >= SIZ) {
-               cprintf("452 Too many recipients\r\n");
-               return;
-       }
-
-       // RBL check
-       if (
-               (!CC->logged_in)                                                // Don't RBL authenticated users
-               && (!SMTP->is_lmtp)                                             // Don't RBL LMTP clients
-               && (CtdlGetConfigInt("c_rbl_at_greeting") == 0)                 // Don't RBL if we did it at connection time
-               && (rbl_check(CC->cs_addr, message_to_spammer))
-       ) {
-               cprintf("550 %s\r\n", message_to_spammer);
-               return;                                                         // no need to free_recipients(valid)
-       }                                                                       // because it hasn't been allocated yet
-
-       // This is a *preliminary* call to validate_recipients() to evaluate one recipient.
-       valid = validate_recipients(
-               (char *)ChrPtr(SMTP->OneRcpt), 
-               smtp_get_Recipients(),
-               (SMTP->is_lmtp)? POST_LMTP: (CC->logged_in)? POST_LOGGED_IN: POST_EXTERNAL
-       );
-
-       // Any type of error thrown by validate_recipients() will make the SMTP transaction fail at this point.
-       if (valid->num_error != 0) {
-               cprintf("550 %s\r\n", valid->errormsg);
-               free_recipients(valid);
-               return;
-       }
-
-       if (
-               (valid->num_internet > 0)                                       // If it's outbound Internet mail...
-               && (CC->logged_in)                                              // ...and we're a logged-in user...
-               && (CtdlCheckInternetMailPermission(&CC->user)==0)              // ...who does not have Internet mail rights...
-       ) {
-               cprintf("551 <%s> - you do not have permission to send Internet mail\r\n", ChrPtr(SMTP->OneRcpt));
-               free_recipients(valid);
-               return;
-       }
-
-       if (
-               (valid->num_internet > 0)                                       // If it's outbound Internet mail...
-               && (SMTP->message_originated_locally == 0)                      // ...and also inbound Internet mail...
-               && (SMTP->is_lmtp == 0)                                         /// ...and didn't arrive via LMTP...
-       ) {
-               cprintf("551 <%s> - relaying denied\r\n", ChrPtr(SMTP->OneRcpt));
-               free_recipients(valid);
-               return;
-       }
-
-       if (
-               (valid->num_room > 0)                                           // If it's mail to a room (mailing list)...
-               && (SMTP->message_originated_locally == 0)                      // ...and also inbound Internet mail...
-               && (is_email_subscribed_to_list((char *)ChrPtr(SMTP->from), valid->recp_room) == 0)     // ...and not a subscriber
-       ) {
-               cprintf("551 <%s> - The message is not from a list member\r\n", ChrPtr(SMTP->OneRcpt));
-               free_recipients(valid);
-               return;
-       }
-
-       cprintf("250 RCPT ok <%s>\r\n", ChrPtr(SMTP->OneRcpt));
-       if (StrLength(SMTP->recipients) > 0) {
-               StrBufAppendBufPlain(SMTP->recipients, HKEY(","), 0);
-       }
-       StrBufAppendBuf(SMTP->recipients, SMTP->OneRcpt, 0);
-       SMTP->number_of_recipients ++;
-       if (valid != NULL)  {
-               free_recipients(valid);
-       }
-}
-
-
-// Implements the DATA command
-void smtp_data(void) {
-       StrBuf *body;
-       StrBuf *defbody; 
-       struct CtdlMessage *msg = NULL;
-       long msgnum = (-1L);
-       char nowstamp[SIZ];
-       struct recptypes *valid;
-       int scan_errors;
-       int i;
-
-       if (StrLength(SMTP->from) == 0) {
-               cprintf("503 Need MAIL command first.\r\n");
-               return;
-       }
-
-       if (SMTP->number_of_recipients < 1) {
-               cprintf("503 Need RCPT command first.\r\n");
-               return;
-       }
-
-       cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
-       
-       datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
-       defbody = NewStrBufPlain(NULL, SIZ);
-
-       if (defbody != NULL) {
-               if (SMTP->is_lmtp && (CC->cs_UDSclientUID != -1)) {
-                       StrBufPrintf(
-                               defbody,
-                               "Received: from %s (Citadel from userid %ld)\n"
-                               "       by %s; %s\n",
-                               ChrPtr(SMTP->helo_node),
-                               (long int) CC->cs_UDSclientUID,
-                               CtdlGetConfigStr("c_fqdn"),
-                               nowstamp);
-               }
-               else {
-                       StrBufPrintf(
-                               defbody,
-                               "Received: from %s (%s [%s])\n"
-                               "       by %s; %s\n",
-                               ChrPtr(SMTP->helo_node),
-                               CC->cs_host,
-                               CC->cs_addr,
-                               CtdlGetConfigStr("c_fqdn"),
-                               nowstamp);
-               }
-       }
-       body = CtdlReadMessageBodyBuf(HKEY("."), CtdlGetConfigLong("c_maxmsglen"), defbody, 1);
-       FreeStrBuf(&defbody);
-       if (body == NULL) {
-               cprintf("550 Unable to save message: internal error.\r\n");
-               return;
-       }
-
-       syslog(LOG_DEBUG, "serv_smtp: converting message...");
-       msg = convert_internet_message_buf(&body);
-
-       // If the user is locally authenticated, FORCE the From: header to
-       // show up as the real sender.  Yes, this violates the RFC standard,
-       // but IT MAKES SENSE.  If you prefer strict RFC adherence over
-       // common sense, you can disable this in the configuration.
-       //
-       // We also set the "message room name" ('O' field) to MAILROOM
-       // (which is Mail> on most systems) to prevent it from getting set
-       // to something ugly like "0000058008.Sent Items>" when the message
-       // is read with a Citadel client.
-
-       if ( (CC->logged_in) && (CtdlGetConfigInt("c_rfc822_strict_from") != CFG_SMTP_FROM_NOFILTER) ) {
-               int validemail = 0;
-               
-               if (!CM_IsEmpty(msg, erFc822Addr)       &&
-                   ((CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_CORRECT) || 
-                    (CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_REJECT)    )  )
-               {
-                       if (!IsEmptyStr(CC->cs_inet_email))
-                               validemail = strcmp(CC->cs_inet_email, msg->cm_fields[erFc822Addr]) == 0;
-                       if ((!validemail) && 
-                           (!IsEmptyStr(CC->cs_inet_other_emails)))
-                       {
-                               int num_secondary_emails = 0;
-                               int i;
-                               num_secondary_emails = num_tokens(CC->cs_inet_other_emails, '|');
-                               for (i=0; i < num_secondary_emails && !validemail; ++i) {
-                                       char buf[256];
-                                       extract_token(buf, CC->cs_inet_other_emails,i,'|',sizeof CC->cs_inet_other_emails);
-                                       validemail = strcmp(buf, msg->cm_fields[erFc822Addr]) == 0;
-                               }
-                       }
-               }
-
-               if (!validemail && (CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_REJECT)) {
-                       syslog(LOG_ERR, "serv_smtp: invalid sender '%s' - rejecting this message", msg->cm_fields[erFc822Addr]);
-                       cprintf("550 Invalid sender '%s' - rejecting this message.\r\n", msg->cm_fields[erFc822Addr]);
-                       return;
-               }
-
-               CM_SetField(msg, eOriginalRoom, HKEY(MAILROOM));
-               if (SMTP->preferred_sender_name != NULL)
-                       CM_SetField(msg, eAuthor, SKEY(SMTP->preferred_sender_name));
-               else 
-                       CM_SetField(msg, eAuthor, CC->user.fullname, strlen(CC->user.fullname));
-
-               if (!validemail) {
-                       if (SMTP->preferred_sender_email != NULL) {
-                               CM_SetField(msg, erFc822Addr, SKEY(SMTP->preferred_sender_email));
-                       }
-                       else {
-                               CM_SetField(msg, erFc822Addr, CC->cs_inet_email, strlen(CC->cs_inet_email));
-                       }
-               }
-       }
-
-       // Set the "envelope from" address
-       CM_SetField(msg, eMessagePath, SKEY(SMTP->from));
-
-       // Set the "envelope to" address
-       CM_SetField(msg, eenVelopeTo, SKEY(SMTP->recipients));
-
-       // Submit the message into the Citadel system.
-       valid = validate_recipients(
-               (char *)ChrPtr(SMTP->recipients),
-               smtp_get_Recipients(),
-               (SMTP->is_lmtp)? POST_LMTP: (CC->logged_in)? POST_LOGGED_IN: POST_EXTERNAL
-       );
-
-       // If there are modules that want to scan this message before final
-       // submission (such as virus checkers or spam filters), call them now
-       // and give them an opportunity to reject the message.
-       if (SMTP->is_unfiltered) {
-               scan_errors = 0;
-       }
-       else {
-               scan_errors = PerformMessageHooks(msg, valid, EVT_SMTPSCAN);
-       }
-
-       if (scan_errors > 0) {  // We don't want this message!
-
-               if (CM_IsEmpty(msg, eErrorMsg)) {
-                       CM_SetField(msg, eErrorMsg, HKEY("Message rejected by filter"));
-               }
-
-               StrBufPrintf(SMTP->OneRcpt, "550 %s\r\n", msg->cm_fields[eErrorMsg]);
-       }
-       
-       else {                  // Ok, we'll accept this message.
-               msgnum = CtdlSubmitMsg(msg, valid, "");
-               if (msgnum > 0L) {
-                       StrBufPrintf(SMTP->OneRcpt, "250 Message accepted.\r\n");
-               }
-               else {
-                       StrBufPrintf(SMTP->OneRcpt, "550 Internal delivery error\r\n");
-               }
-       }
-
-       // For SMTP and ESMTP, just print the result message.  For LMTP, we
-       // have to print one result message for each recipient.  Since there
-       // is nothing in Citadel which would cause different recipients to
-       // have different results, we can get away with just spitting out the
-       // same message once for each recipient.
-       if (SMTP->is_lmtp) {
-               for (i=0; i<SMTP->number_of_recipients; ++i) {
-                       cputbuf(SMTP->OneRcpt);
-               }
-       }
-       else {
-               cputbuf(SMTP->OneRcpt);
-       }
-
-       // Write something to the syslog(which may or may not be where the
-       // rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
-       syslog((LOG_MAIL | LOG_INFO),
-                   "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
-                   msgnum,
-                   ChrPtr(SMTP->from),
-                   SMTP->number_of_recipients,
-                   CC->cs_host,
-                   CC->cs_addr,
-                   ChrPtr(SMTP->OneRcpt)
-       );
-
-       // Clean up
-       CM_Free(msg);
-       free_recipients(valid);
-       smtp_data_clear();      // clear out the buffers now
-}
-
-
-// Implements the STARTTLS command
-void smtp_starttls(void) {
-       char ok_response[SIZ];
-       char nosup_response[SIZ];
-       char error_response[SIZ];
-
-       sprintf(ok_response, "220 Begin TLS negotiation now\r\n");
-       sprintf(nosup_response, "554 TLS not supported here\r\n");
-       sprintf(error_response, "554 Internal error\r\n");
-       CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
-       smtp_rset(0);
-}
-
-
-// Implements the NOOP (NO OPeration) command
-void smtp_noop(void) {
-       cprintf("250 NOOP\r\n");
-}
-
-
-// Implements the QUIT command
-void smtp_quit(void) {
-       cprintf("221 Goodbye...\r\n");
-       CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
-}
-
-
-// Main command loop for SMTP server sessions.
-void smtp_command_loop(void) {
-       static const ConstStr AuthPlainStr = {HKEY("AUTH PLAIN")};
-
-       if (SMTP == NULL) {
-               syslog(LOG_ERR, "serv_smtp: Session SMTP data is null.  WTF?  We will crash now.");
-               abort();
-       }
-
-       time(&CC->lastcmd);
-       if (CtdlClientGetLine(SMTP->Cmd) < 1) {
-               syslog(LOG_INFO, "SMTP: client disconnected: ending session.");
-               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
-               return;
-       }
-
-       if (SMTP->command_state == smtp_user) {
-               if (!strncmp(ChrPtr(SMTP->Cmd), AuthPlainStr.Key, AuthPlainStr.len)) {
-                       smtp_try_plain();
-               }
-               else {
-                       smtp_get_user(0);
-               }
-               return;
-       }
-
-       else if (SMTP->command_state == smtp_password) {
-               smtp_get_pass();
-               return;
-       }
-
-       else if (SMTP->command_state == smtp_plain) {
-               smtp_try_plain();
-               return;
-       }
-
-       syslog(LOG_DEBUG, "serv_smtp: client sent command <%s>", ChrPtr(SMTP->Cmd));
-
-       if (!strncasecmp(ChrPtr(SMTP->Cmd), "NOOP", 4)) {
-               smtp_noop();
-               return;
-       }
-
-       if (!strncasecmp(ChrPtr(SMTP->Cmd), "QUIT", 4)) {
-               smtp_quit();
-               return;
-       }
-
-       if (!strncasecmp(ChrPtr(SMTP->Cmd), "HELO", 4)) {
-               smtp_hello(HELO);
-               return;
-       }
-
-       if (!strncasecmp(ChrPtr(SMTP->Cmd), "EHLO", 4)) {
-               smtp_hello(EHLO);
-               return;
-       }
-
-       if (!strncasecmp(ChrPtr(SMTP->Cmd), "LHLO", 4)) {
-               smtp_hello(LHLO);
-               return;
-       }
-
-       if (!strncasecmp(ChrPtr(SMTP->Cmd), "RSET", 4)) {
-               smtp_rset(1);
-               return;
-       }
-
-       if (!strncasecmp(ChrPtr(SMTP->Cmd), "AUTH", 4)) {
-               smtp_auth();
-               return;
-       }
-
-       if (!strncasecmp(ChrPtr(SMTP->Cmd), "DATA", 4)) {
-               smtp_data();
-               return;
-       }
-
-       if (!strncasecmp(ChrPtr(SMTP->Cmd), "HELP", 4)) {
-               smtp_help();
-               return;
-       }
-
-       if (!strncasecmp(ChrPtr(SMTP->Cmd), "MAIL", 4)) {
-               smtp_mail();
-               return;
-       }
-       
-       if (!strncasecmp(ChrPtr(SMTP->Cmd), "RCPT", 4)) {
-               smtp_rcpt();
-               return;
-       }
-#ifdef HAVE_OPENSSL
-       if (!strncasecmp(ChrPtr(SMTP->Cmd), "STARTTLS", 8)) {
-               smtp_starttls();
-               return;
-       }
-#endif
-
-       cprintf("502 I'm afraid I can't do that.\r\n");
-}
-
-
-/*****************************************************************************/
-/*                      MODULE INITIALIZATION STUFF                          */
-/*****************************************************************************/
-/*
- * This cleanup function blows away the temporary memory used by
- * the SMTP server.
- */
-void smtp_cleanup_function(void)
-{
-       /* Don't do this stuff if this is not an SMTP session! */
-       if (CC->h_command_function != smtp_command_loop) return;
-
-       syslog(LOG_DEBUG, "Performing SMTP cleanup hook");
-
-       FreeStrBuf(&SMTP->Cmd);
-       FreeStrBuf(&SMTP->helo_node);
-       FreeStrBuf(&SMTP->from);
-       FreeStrBuf(&SMTP->recipients);
-       FreeStrBuf(&SMTP->OneRcpt);
-       FreeStrBuf(&SMTP->preferred_sender_email);
-       FreeStrBuf(&SMTP->preferred_sender_name);
-
-       free(SMTP);
-}
-
-const char *CitadelServiceSMTP_MTA="SMTP-MTA";
-const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
-const char *CitadelServiceSMTP_MSA="SMTP-MSA";
-const char *CitadelServiceSMTP_LMTP="LMTP";
-const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
-
-
-CTDL_MODULE_INIT(smtp)
-{
-       if (!threading) {
-               CtdlRegisterServiceHook(CtdlGetConfigInt("c_smtp_port"),        /* SMTP MTA */
-                                       NULL,
-                                       smtp_mta_greeting,
-                                       smtp_command_loop,
-                                       NULL, 
-                                       CitadelServiceSMTP_MTA);
-
-#ifdef HAVE_OPENSSL
-               CtdlRegisterServiceHook(CtdlGetConfigInt("c_smtps_port"),       /* SMTPS MTA */
-                                       NULL,
-                                       smtps_greeting,
-                                       smtp_command_loop,
-                                       NULL,
-                                       CitadelServiceSMTPS_MTA);
-#endif
-
-               CtdlRegisterServiceHook(CtdlGetConfigInt("c_msa_port"),         /* SMTP MSA */
-                                       NULL,
-                                       smtp_msa_greeting,
-                                       smtp_command_loop,
-                                       NULL,
-                                       CitadelServiceSMTP_MSA);
-
-               CtdlRegisterServiceHook(0,                      /* local LMTP */
-                                       file_lmtp_socket,
-                                       lmtp_greeting,
-                                       smtp_command_loop,
-                                       NULL,
-                                       CitadelServiceSMTP_LMTP);
-
-               CtdlRegisterServiceHook(0,                      /* local LMTP */
-                                       file_lmtp_unfiltered_socket,
-                                       lmtp_unfiltered_greeting,
-                                       smtp_command_loop,
-                                       NULL,
-                                       CitadelServiceSMTP_LMTP_UNF);
-
-               CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP, PRIO_STOP + 250);
-       }
-       
-       /* return our module name for the log */
-       return "smtp";
-}
diff --git a/citadel/modules/smtp/serv_smtpclient.c b/citadel/modules/smtp/serv_smtpclient.c
deleted file mode 100644 (file)
index b528b5f..0000000
+++ /dev/null
@@ -1,572 +0,0 @@
-// Transmit outbound SMTP mail to the big wide world of the Internet
-//
-// This is the new, exciting, clever version that makes libcurl do all the work  :)
-//
-// Copyright (c) 1997-2022 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License as published
-// by the Free Software Foundation; either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <sysconfig.h>
-#include <time.h>
-#include <ctype.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <libcitadel.h>
-#include <curl/curl.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "ctdl_module.h"
-#include "clientsocket.h"
-#include "msgbase.h"
-#include "domain.h"
-#include "internet_addressing.h"
-#include "citadel_dirs.h"
-#include "modules/smtp/smtp_util.h"
-
-struct smtpmsgsrc {            // Data passed in and out of libcurl for message upload
-       StrBuf *TheMessage;
-       int bytes_total;
-       int bytes_sent;
-};
-
-static int doing_smtpclient = 0;
-long *smtpq = NULL;            // array of msgnums containing queue instructions
-int smtpq_count = 0;           // number of queue messages in smtpq
-int smtpq_alloc = 0;           // current allocation size for smtpq
-
-
-// Initialize the SMTP outbound queue
-void smtp_init_spoolout(void) {
-       struct ctdlroom qrbuf;
-
-       // Create the room.  This will silently fail if the room already
-       // exists, and that's perfectly ok, because we want it to exist.
-       CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_QUEUE);
-
-       // Make sure it's set to be a "system room" so it doesn't show up
-       // in the <K>nown rooms list for administrators.
-       if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
-               qrbuf.QRflags2 |= QR2_SYSTEM;
-               CtdlPutRoomLock(&qrbuf);
-       }
-}
-
-
-// For internet mail, generate delivery instructions.
-// Yes, this is recursive.  Deal with it.  Infinite recursion does
-// not happen because the delivery instructions message does not
-// contain a recipient.
-int smtp_aftersave(struct CtdlMessage *msg, struct recptypes *recps) {
-       if ((recps != NULL) && (recps->num_internet > 0)) {
-               struct CtdlMessage *imsg = NULL;
-               char recipient[SIZ];
-               StrBuf *SpoolMsg = NewStrBuf();
-               long nTokens;
-               int i;
-
-               syslog(LOG_DEBUG, "smtpclient: generating delivery instructions");
-
-               StrBufPrintf(SpoolMsg,
-                            "Content-type: " SPOOLMIME "\n"
-                            "\n"
-                            "msgid|%s\n"
-                            "submitted|%ld\n" "bounceto|%s\n", msg->cm_fields[eVltMsgNum], (long) time(NULL), recps->bounce_to);
-
-               if (recps->envelope_from != NULL) {
-                       StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
-                       StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
-                       StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
-               }
-               if (recps->sending_room != NULL) {
-                       StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
-                       StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
-                       StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
-               }
-
-               nTokens = num_tokens(recps->recp_internet, '|');
-               for (i = 0; i < nTokens; i++) {
-                       long len;
-                       len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
-                       if (len > 0) {
-                               StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
-                               StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
-                               StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
-                       }
-               }
-
-               imsg = malloc(sizeof(struct CtdlMessage));
-               memset(imsg, 0, sizeof(struct CtdlMessage));
-               imsg->cm_magic = CTDLMESSAGE_MAGIC;
-               imsg->cm_anon_type = MES_NORMAL;
-               imsg->cm_format_type = FMT_RFC822;
-               CM_SetField(imsg, eMsgSubject, HKEY("QMSG"));
-               CM_SetField(imsg, eAuthor, HKEY("Citadel"));
-               CM_SetField(imsg, eJournal, HKEY("do not journal"));
-               CM_SetAsFieldSB(imsg, eMesageText, &SpoolMsg);
-               CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
-               CM_Free(imsg);
-       }
-       return 0;
-}
-
-
-// Callback for smtp_attempt_delivery() to supply libcurl with upload data.
-static size_t upload_source(void *ptr, size_t size, size_t nmemb, void *userp) {
-       struct smtpmsgsrc *s = (struct smtpmsgsrc *) userp;
-       int sendbytes = 0;
-       const char *send_this = NULL;
-
-       sendbytes = (size * nmemb);
-
-       if (s->bytes_sent >= s->bytes_total) {
-               return (0);                                     // no data remaining; we are done
-       }
-
-       if (sendbytes > (s->bytes_total - s->bytes_sent)) {
-               sendbytes = s->bytes_total - s->bytes_sent;     // can't send more than we have
-       }
-
-       send_this = ChrPtr(s->TheMessage);
-       send_this += s->bytes_sent;                             // start where we last left off
-
-       memcpy(ptr, send_this, sendbytes);
-       s->bytes_sent += sendbytes;
-       return(sendbytes);                                      // return the number of bytes _actually_ copied
-}
-
-
-// The libcurl API doesn't provide a way to capture the actual SMTP result message returned
-// by the remote server.  This is an ugly way to extract it, by capturing debug data from
-// the library and filtering on the lines we want.
-int ctdl_libcurl_smtp_debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *userptr) {
-       if (type != CURLINFO_HEADER_IN)
-               return 0;
-       if (!userptr)
-               return 0;
-       char *debugbuf = (char *) userptr;
-
-       int len = strlen(debugbuf);
-       if (len + size > SIZ)
-               return 0;
-
-       memcpy(&debugbuf[len], data, size);
-       debugbuf[len + size] = 0;
-       return 0;
-}
-
-
-// Go through the debug output of an SMTP transaction, and boil it down to just the final success or error response message.
-void trim_response(long response_code, char *response) {
-       if ((response_code < 100) || (response_code > 999) || (IsEmptyStr(response))) {
-               return;
-       }
-
-       char *t = malloc(strlen(response));
-       if (!t) {
-               return;
-       }
-       t[0] = 0;
-
-       char *p;
-       for (p = response; *p != 0; ++p) {
-               if ( (*p != '\n') && (!isprint(*p)) ) {         // expunge any nonprintables except for newlines
-                       *p = ' ';
-               }
-       }
-
-       char response_code_str[4];
-       snprintf(response_code_str, sizeof response_code_str, "%ld", response_code);
-       char *respstart = strstr(response, response_code_str);
-       if (respstart == NULL) {                                // If we have a response code but no response text,
-               strcpy(response, smtpstatus(response_code));    // use one of our canned messages.
-               return;
-       }
-       strcpy(response, respstart);
-
-       p = strstr(response, "\n");
-       if (p != NULL) {
-               *p = 0;
-       }
-}
-
-
-// Attempt a delivery to one recipient.
-// Returns a three-digit SMTP status code.
-int smtp_attempt_delivery(long msgid, char *recp, char *envelope_from, char *source_room, char *response) {
-       struct smtpmsgsrc s;
-       char *fromaddr = NULL;
-       CURL *curl;
-       CURLcode res = CURLE_OK;
-       struct curl_slist *recipients = NULL;
-       long response_code = 421;
-       int num_mx = 0;
-       char mxes[SIZ];
-       char user[1024];
-       char node[1024];
-       char name[1024];
-       char try_this_mx[256];
-       char smtp_url[512];
-       int i;
-
-       syslog(LOG_DEBUG, "smtpclient: smtp_attempt_delivery(%ld, %s)", msgid, recp);
-
-       process_rfc822_addr(recp, user, node, name);    // split recipient address into username, hostname, displayname
-       num_mx = getmx(mxes, node);
-       if (num_mx < 1) {
-               return (421);
-       }
-
-       CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
-       if (!IsEmptyStr(source_room)) {
-               // If we have a source room, it's probably a mailing list message; generate an unsubscribe header
-               char esc_room[ROOMNAMELEN*2];
-               char esc_email[1024];
-               urlesc(esc_room, sizeof esc_room, source_room);
-               urlesc(esc_email, sizeof esc_email, recp);
-               cprintf("List-Unsubscribe: <http://%s/listsub?cmd=unsubscribe&room=%s&email=%s>\r\n",
-                       CtdlGetConfigStr("c_fqdn"),
-                       esc_room,
-                       esc_email
-               );
-       }
-       CtdlOutputMsg(msgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0, NULL, &fromaddr, NULL);
-       s.TheMessage = CC->redirect_buffer;
-       s.bytes_total = StrLength(CC->redirect_buffer);
-       s.bytes_sent = 0;
-       CC->redirect_buffer = NULL;
-       response_code = 421;
-                                       // keep trying MXes until one works or we run out
-       for (i = 0; ((i < num_mx) && ((response_code / 100) == 4)); ++i) {
-               response_code = 421;    // default 421 makes non-protocol errors transient
-               s.bytes_sent = 0;       // rewind our buffer in case we try multiple MXes
-
-               curl = curl_easy_init();
-               if (curl) {
-                       response[0] = 0;
-
-                       if (!IsEmptyStr(envelope_from)) {
-                               curl_easy_setopt(curl, CURLOPT_MAIL_FROM, envelope_from);
-                       }
-                       else {
-                               curl_easy_setopt(curl, CURLOPT_MAIL_FROM, fromaddr);
-                       }
-
-                       recipients = curl_slist_append(recipients, recp);
-                       curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
-                       curl_easy_setopt(curl, CURLOPT_READFUNCTION, upload_source);
-                       curl_easy_setopt(curl, CURLOPT_READDATA, &s);
-                       curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);                              // tell libcurl we are uploading
-                       curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L);                           // Time out after 20 seconds
-                       if (CtdlGetConfigInt("c_smtpclient_disable_starttls") == 0) {
-                               curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_TRY);        // Attempt STARTTLS if offered
-                       }
-                       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
-                       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
-                       curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, ctdl_libcurl_smtp_debug_callback);
-                       curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void *) response);
-                       curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
-
-                       // Construct an SMTP URL in the form of:
-                       //      smtp[s]://target_host/source_host
-                       // This looks weird but libcurl uses that last part to set our name for EHLO or HELO.
-                       // We check for "smtp://" and "smtps://" because the admin may have put those prefixes in a smart-host entry.
-                       // If there is no prefix we add "smtp://"
-                       extract_token(try_this_mx, mxes, i, '|', (sizeof try_this_mx - 7));
-                       snprintf(smtp_url, sizeof smtp_url,
-                               "%s%s/%s",
-                               (((!strncasecmp(try_this_mx, HKEY("smtp://")))
-                               || (!strncasecmp(try_this_mx, HKEY("smtps://")))) ? "" : "smtp://"),
-                               try_this_mx, CtdlGetConfigStr("c_fqdn")
-                       );
-                       curl_easy_setopt(curl, CURLOPT_URL, smtp_url);
-                       syslog(LOG_DEBUG, "smtpclient: trying MX %d of %d <%s>", i+1, num_mx, smtp_url);        // send the message
-                       res = curl_easy_perform(curl);
-                       curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
-                       syslog(LOG_DEBUG,
-                              "smtpclient: libcurl returned %d (%s) , SMTP response %ld",
-                              res, curl_easy_strerror(res), response_code
-                       );
-
-                       if ((res != CURLE_OK) && (response_code == 0)) {        // check for errors
-                               response_code = 421;
-                       }
-
-                       curl_slist_free_all(recipients);
-                       recipients = NULL;                                      // this gets reused; avoid double-free
-                       curl_easy_cleanup(curl);
-                       curl = NULL;                                            // this gets reused; avoid double-free
-
-                       // Trim the error message buffer down to just the actual message
-                       trim_response(response_code, response);
-               }
-       }
-
-       FreeStrBuf(&s.TheMessage);
-       if (fromaddr) {
-               free(fromaddr);
-       }
-       return ((int) response_code);
-}
-
-
-// Process one outbound message.
-void smtp_process_one_msg(long qmsgnum) {
-       struct CtdlMessage *msg = NULL;
-       char *instr = NULL;
-       int i;
-       int num_success = 0;
-       int num_fail = 0;
-       int num_delayed = 0;
-       long deletes[2];
-       int delete_this_queue = 0;
-       char server_response[SIZ];
-
-       msg = CtdlFetchMessage(qmsgnum, 1);
-       if (msg == NULL) {
-               syslog(LOG_WARNING, "smtpclient: %ld does not exist", qmsgnum);
-               return;
-       }
-
-       instr = msg->cm_fields[eMesageText];
-       msg->cm_fields[eMesageText] = NULL;
-       CM_Free(msg);
-
-       // if the queue message has any CRLF's convert them to LF's
-       char *crlf = NULL;
-       while (crlf = strstr(instr, "\r\n"), crlf != NULL) {
-               strcpy(crlf, crlf + 1);
-       }
-
-       // Strip out the headers and we are now left with just the instructions.
-       char *soi = strstr(instr, "\n\n");
-       if (soi) {
-               strcpy(instr, soi + 2);
-       }
-
-       long msgid = 0;
-       time_t submitted = time(NULL);
-       time_t attempted = 0;
-       char *bounceto = NULL;
-       char *envelope_from = NULL;
-       char *source_room = NULL;
-
-       char cfgline[SIZ];
-       for (i = 0; i < num_tokens(instr, '\n'); ++i) {
-               extract_token(cfgline, instr, i, '\n', sizeof cfgline);
-               if (!strncasecmp(cfgline, HKEY("msgid|")))
-                       msgid = atol(&cfgline[6]);
-               if (!strncasecmp(cfgline, HKEY("submitted|")))
-                       submitted = atol(&cfgline[10]);
-               if (!strncasecmp(cfgline, HKEY("attempted|")))
-                       attempted = atol(&cfgline[10]);
-               if (!strncasecmp(cfgline, HKEY("bounceto|")))
-                       bounceto = strdup(&cfgline[9]);
-               if (!strncasecmp(cfgline, HKEY("envelope_from|")))
-                       envelope_from = strdup(&cfgline[14]);
-               if (!strncasecmp(cfgline, HKEY("source_room|")))
-                       source_room = strdup(&cfgline[12]);
-       }
-
-       int should_try_now = 0;
-       if (attempted < submitted) {                    // If no attempts have been made yet, try now
-               should_try_now = 1;
-       }
-       else if ((attempted - submitted) <= 14400) {
-               if ((time(NULL) - attempted) > 1800) {  // First four hours, retry every 30 minutes
-                       should_try_now = 1;
-               }
-       }
-       else {
-               if ((time(NULL) - attempted) > 14400) { // After that, retry once every 4 hours
-                       should_try_now = 1;
-               }
-       }
-
-       if (should_try_now) {
-               syslog(LOG_DEBUG, "smtpclient: attempting delivery of message <%ld> now", qmsgnum);
-               if (source_room) {
-                       syslog(LOG_DEBUG, "smtpclient: this message originated in <%s>", source_room);
-               }
-               StrBuf *NewInstr = NewStrBuf();
-               StrBufAppendPrintf(NewInstr, "Content-type: " SPOOLMIME "\n\n");
-               StrBufAppendPrintf(NewInstr, "msgid|%ld\n", msgid);
-               StrBufAppendPrintf(NewInstr, "submitted|%ld\n", submitted);
-               if (bounceto) {
-                       StrBufAppendPrintf(NewInstr, "bounceto|%s\n", bounceto);
-               }
-               if (envelope_from) {
-                       StrBufAppendPrintf(NewInstr, "envelope_from|%s\n", envelope_from);
-               }
-               for (i = 0; i < num_tokens(instr, '\n'); ++i) {
-                       extract_token(cfgline, instr, i, '\n', sizeof cfgline);
-                       if (!strncasecmp(cfgline, HKEY("remote|"))) {
-                               char recp[SIZ];
-                               int previous_result = extract_int(cfgline, 2);
-                               if ((previous_result == 0)
-                                   || (previous_result == 4)) {
-                                       int new_result = 421;
-                                       extract_token(recp, cfgline, 1, '|', sizeof recp);
-                                       new_result = smtp_attempt_delivery(msgid, recp, envelope_from, source_room, server_response);
-                                       syslog(LOG_DEBUG, "smtpclient: recp: <%s> , result: %d (%s)", recp, new_result, server_response);
-                                       if ((new_result / 100) == 2) {
-                                               ++num_success;
-                                       }
-                                       else {
-                                               if ((new_result / 100) == 5) {
-                                                       ++num_fail;
-                                               }
-                                               else {
-                                                       ++num_delayed;
-                                               }
-                                               StrBufAppendPrintf
-                                                   (NewInstr,
-                                                    "remote|%s|%ld|%ld (%s)\n",
-                                                    recp, (new_result / 100), new_result, server_response);
-                                       }
-                               }
-                       }
-               }
-
-               StrBufAppendPrintf(NewInstr, "attempted|%ld\n", time(NULL));
-
-               // All deliveries have now been attempted.  Now determine the disposition of this queue entry.
-
-               time_t age = time(NULL) - submitted;
-               syslog(LOG_DEBUG,
-                      "smtpclient: submission age: %ldd%ldh%ldm%lds",
-                      (age / 86400), ((age % 86400) / 3600), ((age % 3600) / 60), (age % 60));
-               syslog(LOG_DEBUG, "smtpclient: num_success=%d , num_fail=%d , num_delayed=%d", num_success, num_fail, num_delayed);
-
-               // If there are permanent fails on this attempt, deliver a bounce to the user.
-               // The 5XX fails will be recorded in the rewritten queue, but they will be removed before the next attempt.
-               if (num_fail > 0) {
-                       smtp_do_bounce(ChrPtr(NewInstr), SDB_BOUNCE_FATALS);
-               }
-               // If all deliveries have either succeeded or failed, we are finished with this queue entry.
-               if (num_delayed == 0) {
-                       delete_this_queue = 1;
-               }
-               // If it's been more than five days, give up and tell the sender that delivery failed
-               else if ((time(NULL) - submitted) > SMTP_DELIVER_FAIL) {
-                       smtp_do_bounce(ChrPtr(NewInstr), SDB_BOUNCE_ALL);
-                       delete_this_queue = 1;
-               }
-               // If it's been more than four hours but less than five days, warn the sender that delivery is delayed
-               else if (((attempted - submitted) < SMTP_DELIVER_WARN)
-                        && ((time(NULL) - submitted) >= SMTP_DELIVER_WARN)) {
-                       smtp_do_bounce(ChrPtr(NewInstr), SDB_WARN);
-               }
-
-               if (delete_this_queue) {
-                       syslog(LOG_DEBUG, "smtpclient: %ld deleting", qmsgnum);
-                       deletes[0] = qmsgnum;
-                       deletes[1] = msgid;
-                       CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, deletes, 2, "");
-                       FreeStrBuf(&NewInstr);  // We have to free NewInstr here, no longer needed
-               }
-               else {
-                       // replace the old queue entry with the new one
-                       syslog(LOG_DEBUG, "smtpclient: %ld rewriting", qmsgnum);
-                       msg = convert_internet_message_buf(&NewInstr);  // This function will free NewInstr for us
-                       CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
-                       CM_Free(msg);
-                       CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &qmsgnum, 1, "");
-               }
-       }
-       else {
-               syslog(LOG_DEBUG, "smtpclient: %ld retry time not reached", qmsgnum);
-       }
-
-       if (bounceto != NULL) {
-               free(bounceto);
-       }
-       if (envelope_from != NULL) {
-               free(envelope_from);
-       }
-       if (source_room != NULL) {
-               free(source_room);
-       }
-       free(instr);
-}
-
-
-// Callback for smtp_do_queue()
-void smtp_add_msg(long msgnum, void *userdata) {
-
-       if (smtpq == NULL) {
-               smtpq_count = 0;
-               smtpq_alloc = 100;
-               smtpq = malloc(smtpq_alloc * sizeof(long));
-       }
-
-       if (smtpq_alloc >= smtpq_count) {
-               smtpq_alloc += 100;
-               smtpq = realloc(smtpq, (smtpq_alloc * sizeof(long)));
-       }
-
-       smtpq[smtpq_count++] = msgnum;
-}
-
-
-// Run through the queue sending out messages.
-void smtp_do_queue(void) {
-       int i = 0;
-
-       // This is a simple concurrency check to make sure only one smtpclient
-       // run is done at a time.  We could do this with a mutex, but since we
-       // don't really require extremely fine granularity here, we'll do it
-       // with a static variable instead.
-       if (doing_smtpclient) {
-               return;
-       }
-       doing_smtpclient = 1;
-
-       syslog(LOG_DEBUG, "smtpclient: start queue run");
-
-       if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
-               syslog(LOG_WARNING, "Cannot find room <%s>", SMTP_SPOOLOUT_ROOM);
-               doing_smtpclient = 0;
-               return;
-       }
-       // Put the queue in memory so we can close the db cursor
-       CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_add_msg, NULL);
-
-       // We are ready to run through the queue now.
-       for (i = 0; i < smtpq_count; ++i) {
-               smtp_process_one_msg(smtpq[i]);
-       }
-
-       smtpq_count = 0;        // don't free it, we will use this memory on the next run
-       doing_smtpclient = 0;
-       syslog(LOG_DEBUG, "smtpclient: end queue run");
-}
-
-
-// Module entry point
-CTDL_MODULE_INIT(smtpclient)
-{
-       if (!threading) {
-               CtdlRegisterMessageHook(smtp_aftersave, EVT_AFTERSAVE);
-               CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER, PRIO_AGGR + 51);
-               smtp_init_spoolout();
-       }
-
-       // return our module id for the log
-       return "smtpclient";
-}
diff --git a/citadel/modules/smtp/smtp_util.c b/citadel/modules/smtp/smtp_util.c
deleted file mode 100644 (file)
index a77f73d..0000000
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Utility functions for the Citadel SMTP implementation
- *
- * Copyright (c) 1998-2021 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <termios.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <syslog.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "genstamp.h"
-#include "domain.h"
-#include "clientsocket.h"
-#include "locate_host.h"
-#include "citadel_dirs.h"
-#include "ctdl_module.h"
-#include "smtp_util.h"
-
-const char *smtp_get_Recipients(void) {
-       struct citsmtp *sSMTP = SMTP;
-
-       if (sSMTP == NULL)
-               return NULL;
-       else return ChrPtr(sSMTP->from);
-}
-
-
-/*
- * smtp_do_bounce() is caled by smtp_process_one_msg() to scan a set of delivery
- * instructions for errors and produce/deliver a "bounce" message (delivery
- * status notification).
- *
- * is_final should be set to:
- *     SDB_BOUNCE_FATALS       Advise the sender of all 5XX (permanent failures)
- *     SDB_BOUNCE_ALL          Advise the sender that all deliveries have failed and will not be retried
- *     SDB_WARN                Warn the sender about all 4XX transient delays
- */
-void smtp_do_bounce(const char *instr, int is_final) {
-       int i;
-       int lines;
-       int status;
-       char buf[1024];
-       char key[1024];
-       char addr[1024];
-       char dsn[1024];
-       char bounceto[1024];
-       StrBuf *boundary;
-       int num_bounces = 0;
-       int bounce_this = 0;
-       struct CtdlMessage *bmsg = NULL;
-       struct recptypes *valid;
-       int successful_bounce = 0;
-       static int seq = 0;
-       StrBuf *BounceMB;
-       long omsgid = (-1);
-
-       syslog(LOG_DEBUG, "smtp_do_bounce() called");
-       strcpy(bounceto, "");
-       boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
-
-       StrBufAppendPrintf(boundary, "%s_%04x%04x", CtdlGetConfigStr("c_fqdn"), getpid(), ++seq);
-
-       /* Start building our bounce message */
-
-       bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
-       if (bmsg == NULL) return;
-       memset(bmsg, 0, sizeof(struct CtdlMessage));
-       BounceMB = NewStrBufPlain(NULL, 1024);
-
-       bmsg->cm_magic = CTDLMESSAGE_MAGIC;
-       bmsg->cm_anon_type = MES_NORMAL;
-       bmsg->cm_format_type = FMT_RFC822;
-       CM_SetField(bmsg, eAuthor, HKEY("Citadel"));
-       CM_SetField(bmsg, eOriginalRoom, HKEY(MAILROOM));
-       CM_SetField(bmsg, eMsgSubject, HKEY("Delivery Status Notification (Failure)"));
-       StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
-       StrBufAppendBuf(BounceMB, boundary, 0);
-       StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
-       StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
-       StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
-       StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
-       StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
-       StrBufAppendBuf(BounceMB, boundary, 0);
-       StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
-       StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
-
-       if (is_final == SDB_BOUNCE_ALL) {
-               StrBufAppendBufPlain(
-                       BounceMB,
-                       HKEY(   "A message you sent could not be delivered "
-                               "to some or all of its recipients\ndue to "
-                               "prolonged unavailability of its destination(s).\n"
-                               "Giving up on the following addresses:\n\n"),
-                       0);
-       }
-       else if (is_final == SDB_BOUNCE_FATALS) {
-               StrBufAppendBufPlain(
-                       BounceMB,
-                       HKEY(   "A message you sent could not be delivered "
-                               "to some or all of its recipients.\n"
-                               "The following addresses were undeliverable:\n\n"),
-                       0);
-       }
-       else if (is_final == SDB_WARN) {
-               StrBufAppendBufPlain(
-                       BounceMB,
-                       HKEY("A message you sent has not been delivered "
-                               "to some or all of its recipients.\n"
-                               "Citadel will continue attempting delivery for five days.\n"
-                               "The following addresses were undeliverable:\n\n"),
-                       0);
-       }
-       else {  // should never get here
-               StrBufAppendBufPlain(BounceMB, HKEY("This message should never occur.\n\n"), 0);
-       }
-
-       /*
-        * Now go through the instructions checking for stuff.
-        */
-       lines = num_tokens(instr, '\n');
-       for (i=0; i<lines; ++i) {
-               long addrlen;
-               long dsnlen;
-               extract_token(buf, instr, i, '\n', sizeof buf);
-               extract_token(key, buf, 0, '|', sizeof key);
-               addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
-               status = extract_int(buf, 2);
-               dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
-               bounce_this = 0;
-
-               syslog(LOG_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>", key, addr, status, dsn);
-
-               if (!strcasecmp(key, "bounceto")) {
-                       strcpy(bounceto, addr);
-               }
-
-               if (!strcasecmp(key, "msgid")) {
-                       omsgid = atol(addr);
-               }
-
-               if (!strcasecmp(key, "remote")) {
-                       if ((is_final == SDB_BOUNCE_FATALS) && (status == 5)) bounce_this = 1;
-                       if ((is_final == SDB_BOUNCE_ALL) && (status != 2)) bounce_this = 1;
-                       if ((is_final == SDB_WARN) && (status == 4)) bounce_this = 1;
-               }
-
-               if (bounce_this) {
-                       ++num_bounces;
-                       StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
-                       StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
-                       StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
-                       StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
-               }
-       }
-
-       /* Attach the original message */
-       if (omsgid >= 0) {
-               StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
-               StrBufAppendBuf(BounceMB, boundary, 0);
-               StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
-               StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
-               StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
-               StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
-               StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
-
-               CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
-               CtdlOutputMsg(omsgid,
-                       MT_RFC822,
-                       HEADERS_ALL,
-                       0, 1, NULL, 0,
-                       NULL, NULL, NULL
-               );
-               StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
-               FreeStrBuf(&CC->redirect_buffer);
-       }
-
-       /* Close the multipart MIME scope */
-       StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
-       StrBufAppendBuf(BounceMB, boundary, 0);
-       StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
-       CM_SetAsFieldSB(bmsg, eMesageText, &BounceMB);
-
-       /* Deliver the bounce if there's anything worth mentioning */
-       syslog(LOG_DEBUG, "num_bounces = %d", num_bounces);
-       if (num_bounces > 0) {
-
-               /* First try the user who sent the message */
-               if (IsEmptyStr(bounceto)) {
-                       syslog(LOG_ERR, "No bounce address specified");
-               }
-               else {
-                       syslog(LOG_DEBUG, "bounce to user <%s>", bounceto);
-               }
-               /* Can we deliver the bounce to the original sender? */
-               valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
-               if (valid != NULL) {
-                       if (valid->num_error == 0) {
-                               CtdlSubmitMsg(bmsg, valid, "");
-                               successful_bounce = 1;
-                       }
-               }
-
-               /* If not, post it in the Aide> room */
-               if (successful_bounce == 0) {
-                       CtdlSubmitMsg(bmsg, NULL, CtdlGetConfigStr("c_aideroom"));
-               }
-
-               /* Free up the memory we used */
-               if (valid != NULL) {
-                       free_recipients(valid);
-               }
-       }
-       FreeStrBuf(&boundary);
-       CM_Free(bmsg);
-       syslog(LOG_DEBUG, "Done processing bounces");
-}
-
-
-char *smtpcodes[][2] = {
-       { "211 - System status / system help reply" },
-       { "214", "Help message" },
-       { "220", "Domain service ready" },
-       { "221", "Domain service closing transmission channel" },
-       { "250", "Requested mail action completed and OK" },
-       { "251", "Not Local User, forward email to forward path" },
-       { "252", "Cannot Verify user, will attempt delivery later" },
-       { "253", "Pending messages for node started" },
-       { "354", "Start mail input; end with ." },
-       { "355", "Octet-offset is the transaction offset" },
-       { "421", "Domain service not available, closing transmission channel" },
-       { "432", "Domain service not available, closing transmission channel" },
-       { "450", "Requested mail action not taken: mailbox unavailable. request refused" },
-       { "451", "Requested action aborted: local error in processing Request is unable to be processed, try again" },
-       { "452", "Requested action not taken: insufficient system storage" },
-       { "453", "No mail" },
-       { "454", "TLS not available due to temporary reason. Encryption required for requested authentication mechanism" },
-       { "458", "Unable to queue messages for node" },
-       { "459", "Node not allowed: reason" },
-       { "500", "Syntax error, command unrecognized" },
-       { "501", "Syntax error in parameters or arguments" },
-       { "502", "Command not implemented" },
-       { "503", "Bad sequence of commands" },
-       { "504", "Command parameter not implemented" },
-       { "510", "Check the recipient address" },
-       { "512", "Domain can not be found. Unknown host." },
-       { "515", "Destination mailbox address invalid" },
-       { "517", "Problem with senders mail attribute, check properties" },
-       { "521", "Domain does not accept mail" },
-       { "522", "Recipient has exceeded mailbox limit" },
-       { "523", "Server limit exceeded. Message too large" },
-       { "530", "Access Denied. Authentication required" },
-       { "531", "Mail system Full" },
-       { "533", "Remote server has insufficient disk space to hold email" },
-       { "534", "Authentication mechanism is too weak. Message too big" },
-       { "535", "Multiple servers using same IP. Required Authentication" },
-       { "538", "Encryption required for requested authentication mechanism" },
-       { "540", "Email address has no DNS Server" },
-       { "541", "No response from host" },
-       { "542", "Bad Connection" },
-       { "543", "Routing server failure. No available route" },
-       { "546", "Email looping" },
-       { "547", "Delivery time-out" },
-       { "550", "Requested action not taken: mailbox unavailable or relaying denied" },
-       { "551", "User not local; please try forward path" },
-       { "552", "Requested mail action aborted: exceeded storage allocation" },
-       { "553", "Requested action not taken: mailbox name not allowed" },
-       { "554", "Transaction failed" }
-};
-
-
-char *smtpstatus(int code) {
-       int i;
-
-       for (i=0; i<(sizeof(smtpcodes)/sizeof(char *)/2); ++i) {
-               if (atoi(smtpcodes[i][0]) == code) {
-                       return(smtpcodes[i][1]);
-               }
-       }
-       
-       return("Unknown or other SMTP status");
-}
diff --git a/citadel/modules/smtp/smtp_util.h b/citadel/modules/smtp/smtp_util.h
deleted file mode 100644 (file)
index ea2433c..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (c) 1998-2017 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- */
-
-const char *smtp_get_Recipients(void);
-
-struct citsmtp {               /* Information about the current session */
-       int command_state;
-       StrBuf *Cmd;
-       StrBuf *helo_node;
-       StrBuf *from;
-       StrBuf *recipients;
-       StrBuf *OneRcpt;
-       int number_of_recipients;
-       int delivery_mode;
-       int message_originated_locally;
-       int is_lmtp;
-       int is_unfiltered;
-       int is_msa;
-       StrBuf *preferred_sender_email;
-       StrBuf *preferred_sender_name;
-};
-
-#define SMTP           ((struct citsmtp *)CC->session_specific_data)
-
-// These are all the values that can be passed to the is_final parameter of smtp_do_bounce()
-enum {
-       SDB_BOUNCE_FATALS,
-       SDB_BOUNCE_ALL,
-       SDB_WARN
-};
-
-void smtp_do_bounce(const char *instr, int is_final);
-char *smtpstatus(int code);
diff --git a/citadel/modules/spam/.gitignore b/citadel/modules/spam/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/spam/serv_spam.c b/citadel/modules/spam/serv_spam.c
deleted file mode 100644 (file)
index 2b9331f..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * This module allows Citadel to use SpamAssassin to filter incoming messages
- * arriving via SMTP.  For more information on SpamAssassin, visit
- * http://www.spamassassin.org (the SpamAssassin project is not in any way
- * affiliated with the Citadel project).
- *
- * Copyright (c) 1998-2015 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#define SPAMASSASSIN_PORT       "783"
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "domain.h"
-#include "clientsocket.h"
-
-
-#include "ctdl_module.h"
-
-
-
-/*
- * Connect to the SpamAssassin server and scan a message.
- */
-int spam_assassin(struct CtdlMessage *msg, struct recptypes *recp) {
-       int sock = (-1);
-       char sahosts[SIZ];
-       int num_sahosts;
-       char buf[SIZ];
-       int is_spam = 0;
-       int sa;
-       StrBuf *msgtext;
-       CitContext *CCC=CC;
-
-       /* For users who have authenticated to this server we never want to
-        * apply spam filtering, because presumably they're trustworthy.
-        */
-       if (CC->logged_in) return(0);
-
-       /* See if we have any SpamAssassin hosts configured */
-       num_sahosts = get_hosts(sahosts, "spamassassin");
-       if (num_sahosts < 1) return(0);
-
-       /* Try them one by one until we get a working one */
-        for (sa=0; sa<num_sahosts; ++sa) {
-                extract_token(buf, sahosts, sa, '|', sizeof buf);
-                syslog(LOG_INFO, "Connecting to SpamAssassin at <%s>\n", buf);
-                sock = sock_connect(buf, SPAMASSASSIN_PORT);
-                if (sock >= 0) syslog(LOG_DEBUG, "Connected!\n");
-        }
-
-       if (sock < 0) {
-               /* If the service isn't running, just pass the mail
-                * through.  Potentially throwing away mails isn't good.
-                */
-               return(0);
-       }
-
-       CCC->SBuf.Buf = NewStrBuf();
-       CCC->sMigrateBuf = NewStrBuf();
-       CCC->SBuf.ReadWritePointer = NULL;
-
-       /* Command */
-       syslog(LOG_DEBUG, "Transmitting command\n");
-       sprintf(buf, "CHECK SPAMC/1.2\r\n\r\n");
-       sock_write(&sock, buf, strlen(buf));
-
-       /* Message */
-       CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
-       CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, 0);
-       msgtext = CC->redirect_buffer;
-       CC->redirect_buffer = NULL;
-
-       sock_write(&sock, SKEY(msgtext));
-       FreeStrBuf(&msgtext);
-
-       /* Close one end of the socket connection; this tells SpamAssassin
-        * that we're done.
-        */
-       if (sock != -1)
-               sock_shutdown(sock, SHUT_WR);
-       
-       /* Response */
-       syslog(LOG_DEBUG, "Awaiting response\n");
-        if (sock_getln(&sock, buf, sizeof buf) < 0) {
-                goto bail;
-        }
-        syslog(LOG_DEBUG, "<%s\n", buf);
-       if (strncasecmp(buf, "SPAMD", 5)) {
-               goto bail;
-       }
-        if (sock_getln(&sock, buf, sizeof buf) < 0) {
-                goto bail;
-        }
-        syslog(LOG_DEBUG, "<%s\n", buf);
-        syslog(LOG_DEBUG, "c_spam_flag_only setting %d\n", CtdlGetConfigInt("c_spam_flag_only"));
-        if (CtdlGetConfigInt("c_spam_flag_only")) {
-               int headerlen;
-               char *cur;
-               char sastatus[10];
-               char sascore[10];
-               char saoutof[10];
-               int numscore;
-
-                syslog(LOG_DEBUG, "flag spam code used");
-
-                extract_token(sastatus, buf, 1, ' ', sizeof sastatus);
-                extract_token(sascore, buf, 3, ' ', sizeof sascore);
-                extract_token(saoutof, buf, 5, ' ', sizeof saoutof);
-
-               memcpy(buf, HKEY("X-Spam-Level: "));
-               cur = buf + 14;
-               for (numscore = atoi(sascore); numscore>0; numscore--)
-                       *(cur++) = '*';
-               *cur = '\0';
-
-               headerlen  = cur - buf;
-               headerlen += snprintf(cur, (sizeof(buf) - headerlen), 
-                                    "\r\nX-Spam-Status: %s, score=%s required=%s\r\n",
-                                    sastatus, sascore, saoutof);
-
-               CM_PrependToField(msg, eMesageText, buf, headerlen);
-
-       } else {
-                syslog(LOG_DEBUG, "reject spam code used");
-               if (!strncasecmp(buf, "Spam: True", 10)) {
-                       is_spam = 1;
-               }
-
-               if (is_spam) {
-                       CM_SetField(msg, eErrorMsg, HKEY("message rejected by spam filter"));
-               }
-       }
-
-bail:  close(sock);
-       FreeStrBuf(&CCC->SBuf.Buf);
-       FreeStrBuf(&CCC->sMigrateBuf);
-       return(is_spam);
-}
-
-
-
-CTDL_MODULE_INIT(spam)
-{
-       if (!threading)
-       {
-               CtdlRegisterMessageHook(spam_assassin, EVT_SMTPSCAN);
-       }
-       
-       /* return our module name for the log */
-        return "spam";
-}
diff --git a/citadel/modules/test/.gitignore b/citadel/modules/test/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/test/serv_test.c b/citadel/modules/test/serv_test.c
deleted file mode 100644 (file)
index a7834ae..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * This is an empty skeleton of a Citadel server module, included to demonstrate
- * how to add a new module to the system and how to activate it in the server.
- * 
- * Copyright (c) 1998-2016 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "ctdl_module.h"
-
-
-void CleanupTest(void) {
-       syslog(LOG_DEBUG, "--- test of adding an unload hook --- \n");
-}
-
-void NewRoomTest(void) {
-       syslog(LOG_DEBUG, "--- test module was told we're now in a new room ---\n");
-}
-
-void SessionStartTest(void) {
-       syslog(LOG_DEBUG, "--- starting up session %d ---\n", CC->cs_pid);
-}
-
-void SessionStopTest(void) {
-       syslog(LOG_DEBUG, "--- ending session %d ---\n", CC->cs_pid);
-}
-
-void LoginTest(void) {
-       syslog(LOG_DEBUG, "--- Hello, %s ---\n", CC->curr_user);
-}
-
-/* To insert this module into the server activate the next block by changing the #if 0 to #if 1 */
-CTDL_MODULE_INIT(test)
-{
-#if 0
-       if (!threading)
-       {
-               CtdlRegisterCleanupHook(CleanupTest);
-               CtdlRegisterSessionHook(NewRoomTest, EVT_NEWROOM, 1);
-               CtdlRegisterSessionHook(SessionStartTest, EVT_START, 1);
-               CtdlRegisterSessionHook(SessionStopTest, EVT_STOP, 1);
-               CtdlRegisterSessionHook(LoginTest, EVT_LOGIN, 1);
-       }
-#endif
-
-/* return our module name for the log */
-return "test";
-}
diff --git a/citadel/modules/upgrade/.gitignore b/citadel/modules/upgrade/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/upgrade/serv_upgrade.c b/citadel/modules/upgrade/serv_upgrade.c
deleted file mode 100644 (file)
index b961e41..0000000
+++ /dev/null
@@ -1,586 +0,0 @@
-// Transparently handle the upgrading of server data formats.  If we see
-// an existing version number of our database, we can make some intelligent
-// guesses about what kind of data format changes need to be applied, and
-// we apply them transparently.
-//
-// Copyright (c) 1987-2022 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "database.h"
-#include "user_ops.h"
-#include "msgbase.h"
-#include "serv_upgrade.h"
-#include "euidindex.h"
-#include "ctdl_module.h"
-#include "serv_vcard.h"
-#include "internet_addressing.h"
-
-// oldver is the version number of Citadel Server which was active on the previous run of the program, learned from the system configuration.
-// If we are running a new Citadel Server for the first time, oldver will be 0.
-// We keep this value around for the entire duration of the program run because we'll need it during several stages of startup.
-int oldver = 0;
-
-// Try to remove any extra users with number 0
-void fix_sys_user_name(void) {
-       struct ctdluser usbuf;
-       char usernamekey[USERNAME_SIZE];
-
-       while (CtdlGetUserByNumber(&usbuf, 0) == 0) {
-               // delete user with number 0 and no name
-               if (IsEmptyStr(usbuf.fullname)) {
-                       cdb_delete(CDB_USERS, "", 0);
-               }
-               else {
-                       // temporarily set this user to -1
-                       usbuf.usernum = -1;
-                       CtdlPutUser(&usbuf);
-               }
-       }
-
-       // Delete any "user 0" accounts
-       while (CtdlGetUserByNumber(&usbuf, -1) == 0) {
-               makeuserkey(usernamekey, usbuf.fullname);
-               cdb_delete(CDB_USERS, usernamekey, strlen(usernamekey));
-       }
-}
-
-
-// Back end processing function for reindex_uids()
-void reindex_uids_backend(char *username, void *data) {
-
-       struct ctdluser us;
-
-       if (CtdlGetUserLock(&us, username) == 0) {
-               syslog(LOG_DEBUG, "Processing <%s> (%d)", us.fullname, us.uid);
-               if (us.uid == CTDLUID) {
-                       us.uid = NATIVE_AUTH_UID;
-               }
-               CtdlPutUserLock(&us);
-               if ((us.uid > 0) && (us.uid != NATIVE_AUTH_UID)) {              // if non-native auth , index by uid
-                       StrBuf *claimed_id = NewStrBuf();
-                       StrBufPrintf(claimed_id, "uid:%d", us.uid);
-                       attach_extauth(&us, claimed_id);
-                       FreeStrBuf(&claimed_id);
-               }
-       }
-}
-
-
-// Build extauth index of all users with uid-based join (system auth, LDAP auth)
-// Also changes all users with a uid of CTDLUID to NATIVE_AUTH_UID (-1)
-void reindex_uids(void) {
-       syslog(LOG_WARNING, "upgrade: reindexing and applying uid changes");
-       ForEachUser(reindex_uids_backend, NULL);
-       return;
-}
-
-
-// These accounts may have been created by code that ran between mid 2008 and early 2011.
-// If present they are no longer in use and may be deleted.
-void remove_thread_users(void) {
-       char *deleteusers[] = {
-               "SYS_checkpoint",
-               "SYS_extnotify",
-               "SYS_IGnet Queue",
-               "SYS_indexer",
-               "SYS_network",
-               "SYS_popclient",
-               "SYS_purger",
-               "SYS_rssclient",
-               "SYS_select_on_master",
-               "SYS_SMTP Send"
-       };
-
-       int i;
-       struct ctdluser usbuf;
-       for (i=0; i<(sizeof(deleteusers)/sizeof(char *)); ++i) {
-               if (CtdlGetUser(&usbuf, deleteusers[i]) == 0) {
-                       usbuf.axlevel = 0;
-                       strcpy(usbuf.password, "deleteme");
-                       CtdlPutUser(&usbuf);
-                       syslog(LOG_INFO,
-                               "System user account <%s> is no longer in use and will be deleted.",
-                               deleteusers[i]
-                       );
-               }
-       }
-}
-
-
-// Attempt to guess the name of the time zone currently in use
-// on the underlying host system.
-void guess_time_zone(void) {
-       FILE *fp;
-       char buf[PATH_MAX];
-
-       fp = popen(file_guesstimezone, "r");
-       if (fp) {
-               if (fgets(buf, sizeof buf, fp) && (strlen(buf) > 2)) {
-                       buf[strlen(buf)-1] = 0;
-                       CtdlSetConfigStr("c_default_cal_zone", buf);
-                       syslog(LOG_INFO, "Configuring timezone: %s", buf);
-               }
-               fclose(fp);
-       }
-}
-
-
-// Per-room callback function for ingest_old_roominfo_and_roompic_files()
-// This is the second pass, where we process the list of rooms with info or pic files.
-void iorarf_oneroom(char *roomname, char *infofile, char *picfile) {
-       FILE *fp;
-       long data_length;
-       char *unencoded_data;
-       char *encoded_data;
-       long info_msgnum = 0;
-       long pic_msgnum = 0;
-       char subject[SIZ];
-
-       // Test for the presence of a legacy "room info file"
-       if (!IsEmptyStr(infofile)) {
-               fp = fopen(infofile, "r");
-       }
-       else {
-               fp = NULL;
-       }
-       if (fp) {
-               fseek(fp, 0, SEEK_END);
-               data_length = ftell(fp);
-
-               if (data_length >= 1) {
-                       rewind(fp);
-                       unencoded_data = malloc(data_length);
-                       if (unencoded_data) {
-                               fread(unencoded_data, data_length, 1, fp);
-                               encoded_data = malloc((data_length * 2) + 100);
-                               if (encoded_data) {
-                                       sprintf(encoded_data, "Content-type: text/plain\nContent-transfer-encoding: base64\n\n");
-                                       CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
-                                       snprintf(subject, sizeof subject, "Imported room banner for %s", roomname);
-                                       info_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, encoded_data, FMT_RFC822, subject);
-                                       free(encoded_data);
-                               }
-                               free(unencoded_data);
-                       }
-               }
-               fclose(fp);
-               if (info_msgnum > 0) unlink(infofile);
-       }
-
-       // Test for the presence of a legacy "room picture file" and import it.
-       if (!IsEmptyStr(picfile)) {
-               fp = fopen(picfile, "r");
-       }
-       else {
-               fp = NULL;
-       }
-       if (fp) {
-               fseek(fp, 0, SEEK_END);
-               data_length = ftell(fp);
-
-               if (data_length >= 1) {
-                       rewind(fp);
-                       unencoded_data = malloc(data_length);
-                       if (unencoded_data) {
-                               fread(unencoded_data, data_length, 1, fp);
-                               encoded_data = malloc((data_length * 2) + 100);
-                               if (encoded_data) {
-                                       sprintf(encoded_data, "Content-type: image/gif\nContent-transfer-encoding: base64\n\n");
-                                       CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
-                                       snprintf(subject, sizeof subject, "Imported room icon for %s", roomname);
-                                       pic_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, encoded_data, FMT_RFC822, subject);
-                                       free(encoded_data);
-                               }
-                               free(unencoded_data);
-                       }
-               }
-               fclose(fp);
-               if (pic_msgnum > 0) unlink(picfile);
-       }
-
-       // Now we have the message numbers of our new banner and icon.  Record them in the room record.
-       // NOTE: we are not deleting the old msgnum_info because that position in the record was previously
-       // a pointer to the highest message number which existed in the room when the info file was saved,
-       // and we don't want to delete messages that are not *actually* old banners.
-       struct ctdlroom qrbuf;
-       if (CtdlGetRoomLock(&qrbuf, roomname) == 0) {
-               qrbuf.msgnum_info = info_msgnum;
-               qrbuf.msgnum_pic = pic_msgnum;
-               CtdlPutRoomLock(&qrbuf);
-       }
-
-}
-
-
-struct iorarf_list {
-       struct iorarf_list *next;
-       char name[ROOMNAMELEN];
-       char info[PATH_MAX];
-       char pic[PATH_MAX];
-};
-
-
-// Per-room callback function for ingest_old_roominfo_and_roompic_files()
-// This is the first pass, where the list of qualifying rooms is gathered.
-void iorarf_backend(struct ctdlroom *qrbuf, void *data) {
-       FILE *fp;
-       struct iorarf_list **iorarf_list = (struct iorarf_list **)data;
-
-       struct iorarf_list *i = malloc(sizeof(struct iorarf_list));
-       i->next = *iorarf_list;
-       strcpy(i->name, qrbuf->QRname);
-       strcpy(i->info, "");
-       strcpy(i->pic, "");
-
-       // Test for the presence of a legacy "room info file"
-       assoc_file_name(i->info, sizeof i->info, qrbuf, ctdl_info_dir);
-       fp = fopen(i->info, "r");
-       if (fp) {
-               fclose(fp);
-       }
-       else {
-               i->info[0] = 0;
-       }
-
-       // Test for the presence of a legacy "room picture file"
-       assoc_file_name(i->pic, sizeof i->pic, qrbuf, ctdl_image_dir);
-       fp = fopen(i->pic, "r");
-       if (fp) {
-               fclose(fp);
-       }
-       else {
-               i->pic[0] = 0;
-       }
-
-       if ( (!IsEmptyStr(i->info)) || (!IsEmptyStr(i->pic)) ) {
-               *iorarf_list = i;
-       }
-       else {
-               free(i);
-       }
-}
-
-
-// Prior to Citadel Server version 902, room info and pictures (which comprise the
-// displayed banner for each room) were stored in the filesystem.  If we are upgrading
-// from version >000 to version >=902, ingest those files into the database.
-void ingest_old_roominfo_and_roompic_files(void) {
-       struct iorarf_list *il = NULL;
-
-       CtdlForEachRoom(iorarf_backend, &il);
-
-       struct iorarf_list *p;
-       while (il) {
-               iorarf_oneroom(il->name, il->info, il->pic);
-               p = il->next;
-               free(il);
-               il = p;
-       }
-
-       unlink(ctdl_info_dir);
-}
-
-
-// For upgrades in which a new config setting appears for the first time, set default values.
-// For new installations (oldver == 0) also set default values.
-void update_config(void) {
-
-       if (oldver < 606) {
-               CtdlSetConfigInt("c_rfc822_strict_from", 0);
-       }
-
-       if (oldver < 609) {
-               CtdlSetConfigInt("c_purge_hour", 3);
-       }
-
-       if (oldver < 615) {
-               CtdlSetConfigInt("c_ldap_port", 389);
-       }
-
-       if (oldver < 623) {
-               CtdlSetConfigStr("c_ip_addr", "*");
-       }
-
-       if (oldver < 650) {
-               CtdlSetConfigInt("c_enable_fulltext", 1);
-       }
-
-       if (oldver < 652) {
-               CtdlSetConfigInt("c_auto_cull", 1);
-       }
-
-       if (oldver < 725) {
-               CtdlSetConfigInt("c_xmpp_c2s_port", 5222);
-               CtdlSetConfigInt("c_xmpp_s2s_port", 5269);
-       }
-
-       if (oldver < 830) {
-               CtdlSetConfigInt("c_nntp_port", 119);
-               CtdlSetConfigInt("c_nntps_port", 563);
-       }
-
-       if (IsEmptyStr(CtdlGetConfigStr("c_default_cal_zone"))) {
-               guess_time_zone();
-       }
-}
-
-
-// Helper function for move_inet_addrs_from_vcards_to_user_records()
-//
-// Call this function as a ForEachUser backend in order to queue up
-// user names, or call it with a null user to make it do the processing.
-// This allows us to maintain the list as a static instead of passing
-// pointers around.
-void miafvtur_backend(char *username, void *data) {
-       struct ctdluser usbuf;
-       char primary_inet_email[512] = { 0 };
-       char other_inet_emails[512] = { 0 };
-       char combined_inet_emails[512] = { 0 };
-
-       if (CtdlGetUser(&usbuf, username) != 0) {
-               return;
-       }
-
-       struct vCard *v = vcard_get_user(&usbuf);
-       if (!v) return;
-       extract_inet_email_addrs(primary_inet_email, sizeof primary_inet_email, other_inet_emails, sizeof other_inet_emails, v, 1);
-       vcard_free(v);
-       
-       if ( (IsEmptyStr(primary_inet_email)) && (IsEmptyStr(other_inet_emails)) ) {
-               return;
-       }
-
-       snprintf(combined_inet_emails, 512, "%s%s%s",
-               (!IsEmptyStr(primary_inet_email) ? primary_inet_email : ""),
-               ((!IsEmptyStr(primary_inet_email)&&(!IsEmptyStr(other_inet_emails))) ? "|" : ""),
-               (!IsEmptyStr(other_inet_emails) ? other_inet_emails : "")
-       );
-
-       CtdlSetEmailAddressesForUser(usbuf.fullname, combined_inet_emails);
-}
-
-
-// If our system still has a "refcount_adjustments.dat" sitting around from an old version, ingest it now.
-int ProcessOldStyleAdjRefCountQueue(void) {
-       int r;
-       FILE *fp;
-       struct arcq arcq_rec;
-       int num_records_processed = 0;
-
-       fp = fopen(file_arcq, "rb");
-       if (fp == NULL) {
-               return(num_records_processed);
-       }
-
-       syslog(LOG_INFO, "msgbase: ingesting %s", file_arcq);
-
-       while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
-               AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
-               ++num_records_processed;
-       }
-
-       fclose(fp);
-       r = unlink(file_arcq);
-       if (r != 0) {
-               syslog(LOG_ERR, "%s: %m", file_arcq);
-       }
-
-       return(num_records_processed);
-}
-
-
-// Prior to version 912 we kept a user's various Internet email addresses in their vCards.
-// This function moves them over to the user record, which is where we keep them now.
-void move_inet_addrs_from_vcards_to_user_records(void) {
-       ForEachUser(miafvtur_backend, NULL);
-       CtdlRebuildDirectoryIndex();
-}
-
-
-// We found the legacy sieve config in the user's config room.  Store the message number in the user record.
-void mifm_found_config(long msgnum, void *userdata) {
-       struct ctdluser *us = (struct ctdluser *)userdata;
-
-       us->msgnum_inboxrules = msgnum;
-       syslog(LOG_DEBUG, "user: <%s> inbox filter msgnum: <%ld>", us->fullname, us->msgnum_inboxrules);
-}
-
-
-// Helper function for migrate_inbox_filter_msgnums()
-void mifm_backend(char *username, void *data) {
-       struct ctdluser us;
-       char roomname[ROOMNAMELEN];
-
-       if (CtdlGetUserLock(&us, username) == 0) {
-               // Take a spin through the user's personal config room
-               syslog(LOG_DEBUG, "Processing <%s> (%ld)", us.fullname, us.usernum);
-               snprintf(roomname, sizeof roomname, "%010ld.%s", us.usernum, USERCONFIGROOM);
-               if (CtdlGetRoom(&CC->room, roomname) == 0) {
-                       CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL, mifm_found_config, (void *)&us );
-               }
-               CtdlPutUserLock(&us);
-       }
-}
-
-
-// Prior to version 930 we used a MIME type search to locate the user's inbox filter rules. 
-// This function locates those ruleset messages and simply stores the message number in the user record.
-void migrate_inbox_filter_msgnums(void) {
-       ForEachUser(mifm_backend, NULL);
-}
-
-
-// Create a default administrator account so we can log in to a new installation
-void create_default_admin_account(void) {
-       struct ctdluser usbuf;
-
-       create_user(DEFAULT_ADMIN_USERNAME, CREATE_USER_DO_NOT_BECOME_USER, (-1));
-       CtdlGetUser(&usbuf, DEFAULT_ADMIN_USERNAME);
-       safestrncpy(usbuf.password, DEFAULT_ADMIN_PASSWORD, sizeof(usbuf.password));
-       usbuf.axlevel = AxAideU;
-       CtdlPutUser(&usbuf);
-       CtdlSetConfigStr("c_sysadm", DEFAULT_ADMIN_USERNAME);
-}
-
-
-// Based on the server version number reported by the existing database,
-// run in-place data format upgrades until everything is up to date.
-void pre_startup_upgrades(void) {
-
-       oldver = CtdlGetConfigInt("MM_hosted_upgrade_level");
-       syslog(LOG_INFO, "Existing database version on disk is %d", oldver);
-       update_config();
-
-       if (oldver < REV_LEVEL) {
-               syslog(LOG_WARNING, "Running pre-startup database upgrades.");
-       }
-       else {
-               return;
-       }
-
-       if ((oldver > 000) && (oldver < 591)) {
-               syslog(LOG_EMERG, "This database is too old to be upgraded.  Citadel server will exit.");
-               exit(EXIT_FAILURE);
-       }
-       if ((oldver > 000) && (oldver < 913)) {
-               reindex_uids();
-       }
-       if ((oldver > 000) && (oldver < 659)) {
-               rebuild_euid_index();
-       }
-       if (oldver < 735) {
-               fix_sys_user_name();
-       }
-       if (oldver < 736) {
-               rebuild_usersbynumber();
-       }
-       if (oldver < 790) {
-               remove_thread_users();
-       }
-       if (oldver < 810) {
-               struct ctdlroom QRoom;
-               if (!CtdlGetRoom(&QRoom, SMTP_SPOOLOUT_ROOM)) {
-                       QRoom.QRdefaultview = VIEW_QUEUE;
-                       CtdlPutRoom(&QRoom);
-               }
-       }
-
-       if ((oldver > 000) && (oldver < 902)) {
-               ingest_old_roominfo_and_roompic_files();
-       }
-
-       CtdlSetConfigInt("MM_hosted_upgrade_level", REV_LEVEL);
-
-       // Negative values for maxsessions are not allowed.
-       if (CtdlGetConfigInt("c_maxsessions") < 0) {
-               CtdlSetConfigInt("c_maxsessions", 0);
-       }
-
-       // We need a system default message expiry policy, because this is
-       // the top level and there's no 'higher' policy to fall back on.
-       // By default, do not expire messages at all.
-       if (CtdlGetConfigInt("c_ep_mode") == 0) {
-               CtdlSetConfigInt("c_ep_mode", EXPIRE_MANUAL);
-               CtdlSetConfigInt("c_ep_value", 0);
-       }
-
-       // If this is the first run on an empty database, create a default administrator
-       if (oldver == 0) {
-               create_default_admin_account();
-       }
-}
-
-
-// Based on the server version number reported by the existing database,
-// run in-place data format upgrades until everything is up to date.
-void post_startup_upgrades(void) {
-
-       syslog(LOG_INFO, "Existing database version on disk is %d", oldver);
-
-       if (oldver < REV_LEVEL) {
-               syslog(LOG_WARNING, "Running post-startup database upgrades.");
-       }
-       else {
-               return;
-       }
-
-       if ((oldver > 000) && (oldver < 912)) {
-               move_inet_addrs_from_vcards_to_user_records();
-       }
-
-       if ((oldver > 000) && (oldver < 922)) {
-               ProcessOldStyleAdjRefCountQueue();
-       }
-
-       if ((oldver > 000) && (oldver < 930)) {
-               migrate_inbox_filter_msgnums();
-       }
-
-}
-
-
-CTDL_MODULE_UPGRADE(upgrade)
-{
-       pre_startup_upgrades();
-       
-       /* return our module id for the Log */
-       return "upgrade";
-}
-
-
-CTDL_MODULE_INIT(upgrade)
-{
-       if (!threading) {
-               post_startup_upgrades();
-       }
-
-       /* return our module name for the log */
-        return "upgrade";
-}
diff --git a/citadel/modules/upgrade/serv_upgrade.h b/citadel/modules/upgrade/serv_upgrade.h
deleted file mode 100644 (file)
index 14c6142..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright (c) 1987-2012 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- * 
- * 
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * 
- * 
- * 
- */
-
diff --git a/citadel/modules/vcard/.gitignore b/citadel/modules/vcard/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/vcard/serv_vcard.c b/citadel/modules/vcard/serv_vcard.c
deleted file mode 100644 (file)
index 8f0bc17..0000000
+++ /dev/null
@@ -1,1254 +0,0 @@
-/*
- * A server-side module for Citadel which supports address book information
- * using the standard vCard format.
- * 
- * Copyright (c) 1999-2021 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-/*
- * Format of the "Exclusive ID" field of the message containing a user's
- * vCard.  Doesn't matter what it really looks like as long as it's both
- * unique and consistent (because we use it for replication checking to
- * delete the old vCard network-wide when the user enters a new one).
- */
-#define VCARD_EXT_FORMAT       "Citadel vCard: personal card for %s at %s"
-
-/*
- * Citadel will accept either text/vcard or text/x-vcard as the MIME type
- * for a vCard.  The following definition determines which one it *generates*
- * when serializing.
- */
-#define VCARD_MIME_TYPE                "text/x-vcard"
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <ctype.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "room_ops.h"
-#include "internet_addressing.h"
-#include "serv_vcard.h"
-#include "citadel_ldap.h"
-#include "ctdl_module.h"
-
-/*
- * set global flag calling for an aide to validate new users
- */
-void set_mm_valid(void) {
-       int flags = 0;
-
-       begin_critical_section(S_CONTROL);
-       flags = CtdlGetConfigInt("MMflags");
-       flags = flags | MM_VALID ;
-       CtdlSetConfigInt("MMflags", flags);
-       end_critical_section(S_CONTROL);
-}
-
-
-///TODO: gettext!
-#define _(a) a
-
-/*
- * See if there is a valid Internet address in a vCard to use for outbound
- * Internet messages.  If there is, stick it in the buffer.
- */
-void extract_inet_email_addrs(char *emailaddrbuf, size_t emailaddrbuf_len,
-                             char *secemailaddrbuf, size_t secemailaddrbuf_len,
-                             struct vCard *v,
-                             int local_addrs_only
-) {
-       char *s, *k, *addr;
-       int instance = 0;
-       int IsDirectoryAddress;
-       int saved_instance = 0;
-
-       /* Go through the vCard searching for *all* Internet email addresses
-        */
-       while (s = vcard_get_prop(v, "email", 1, instance, 0),  s != NULL) {
-               k = vcard_get_prop(v, "email", 1, instance, 1);
-               if ( (s != NULL) && (k != NULL) && (bmstrcasestr(k, "internet")) ) {
-                       addr = strdup(s);
-                       striplt(addr);
-                       if (!IsEmptyStr(addr)) {
-                               IsDirectoryAddress = IsDirectory(addr, 1);
-
-                               syslog(LOG_DEBUG, "EVQ: addr=<%s> IsDirectoryAddress=<%d> local_addrs_only=<%d>", addr, IsDirectoryAddress, local_addrs_only);
-
-                               if ( IsDirectoryAddress || !local_addrs_only)
-                               {
-                                       ++saved_instance;
-                                       if ((saved_instance == 1) && (emailaddrbuf != NULL)) {
-                                               safestrncpy(emailaddrbuf, addr, emailaddrbuf_len);
-                                       }
-                                       else if ((saved_instance == 2) && (secemailaddrbuf != NULL)) {
-                                               safestrncpy(secemailaddrbuf, addr, secemailaddrbuf_len);
-                                       }
-                                       else if ((saved_instance > 2) && (secemailaddrbuf != NULL)) {
-                                               if ( (strlen(addr) + strlen(secemailaddrbuf) + 2) 
-                                               < secemailaddrbuf_len ) {
-                                                       strcat(secemailaddrbuf, "|");
-                                                       strcat(secemailaddrbuf, addr);
-                                               }
-                                       }
-                               }
-                               if (!IsDirectoryAddress && local_addrs_only)
-                               {
-                                       StrBufAppendPrintf(CC->StatusMessage, "\n%d|", ERROR+ ILLEGAL_VALUE);
-                                       StrBufAppendBufPlain(CC->StatusMessage, addr, -1, 0);
-                                       StrBufAppendBufPlain(CC->StatusMessage, HKEY("|"), 0);
-                                       StrBufAppendBufPlain(CC->StatusMessage, _("unable to add this emailaddress; its not matching our domain."), -1, 0);
-                               }
-                       }
-                       free(addr);
-               }
-               ++instance;
-       }
-}
-
-
-/*
- * See if there is a name / screen name / friendly name  in a vCard to use for outbound
- * Internet messages.  If there is, stick it in the buffer.
- */
-void extract_friendly_name(char *namebuf, size_t namebuf_len, struct vCard *v)
-{
-       char *s;
-
-       s = vcard_get_prop(v, "fn", 1, 0, 0);
-       if (s == NULL) {
-               s = vcard_get_prop(v, "n", 1, 0, 0);
-       }
-
-       if (s != NULL) {
-               safestrncpy(namebuf, s, namebuf_len);
-       }
-}
-
-
-/*
- * Callback function for vcard_upload_beforesave() hunts for the real vcard in the MIME structure
- */
-void vcard_extract_vcard(char *name, char *filename, char *partnum, char *disp,
-                  void *content, char *cbtype, char *cbcharset, size_t length,
-                  char *encoding, char *cbid, void *cbuserdata)
-{
-       struct vCard **v = (struct vCard **) cbuserdata;
-
-       if (  (!strcasecmp(cbtype, "text/x-vcard"))
-          || (!strcasecmp(cbtype, "text/vcard")) ) {
-
-               syslog(LOG_DEBUG, "vcard: part %s contains a vCard!  Loading...", partnum);
-               if (*v != NULL) {
-                       vcard_free(*v);
-               }
-               *v = vcard_load(content);
-       }
-}
-
-
-/*
- * This handler detects whether the user is attempting to save a new
- * vCard as part of his/her personal configuration, and handles the replace
- * function accordingly (delete the user's existing vCard in the config room
- * and in the global address book).
- */
-int vcard_upload_beforesave(struct CtdlMessage *msg, struct recptypes *recp) {
-       char *s;
-       char buf[SIZ];
-       struct ctdluser usbuf;
-       long what_user;
-       struct vCard *v = NULL;
-       char *ser = NULL;
-       int i = 0;
-       int yes_my_citadel_config = 0;
-       int yes_any_vcard_room = 0;
-
-       if ((!CC->logged_in) && (CC->vcard_updated_by_ldap==0)) return(0);      /* Only do this if logged in, or if ldap changed the vcard. */
-
-       /* Is this some user's "My Citadel Config" room? */
-       if (((CC->room.QRflags & QR_MAILBOX) != 0) &&
-             (!strcasecmp(&CC->room.QRname[11], USERCONFIGROOM)) ) {
-               /* Yes, we want to do this */
-               yes_my_citadel_config = 1;
-#ifdef VCARD_SAVES_BY_AIDES_ONLY
-               /* Prevent non-aides from performing registration changes, but ldap is ok. */
-               if ((CC->user.axlevel < AxAideU) && (CC->vcard_updated_by_ldap==0)) {
-                       return(1);
-               }
-#endif
-
-       }
-
-       /* Is this a room with an address book in it? */
-       if (CC->room.QRdefaultview == VIEW_ADDRESSBOOK) {
-               yes_any_vcard_room = 1;
-       }
-
-       /* If neither condition exists, don't run this hook. */
-       if ( (!yes_my_citadel_config) && (!yes_any_vcard_room) ) {
-               return(0);
-       }
-
-       /* If this isn't a MIME message, don't bother. */
-       if (msg->cm_format_type != 4) return(0);
-
-       /* Ok, if we got this far, look into the situation further... */
-
-       if (CM_IsEmpty(msg, eMesageText)) return(0);
-
-       mime_parser(CM_RANGE(msg, eMesageText),
-                   *vcard_extract_vcard,
-                   NULL, NULL,
-                   &v,         /* user data ptr - put the vcard here */
-                   0
-       );
-
-       if (v == NULL) return(0);       /* no vCards were found in this message */
-
-       /* If users cannot create their own accounts, they cannot re-register either. */
-       if ( (yes_my_citadel_config) &&
-            (CtdlGetConfigInt("c_disable_newu")) &&
-            (CC->user.axlevel < AxAideU) &&
-            (CC->vcard_updated_by_ldap==0) )
-       {
-               return(1);
-       }
-
-       vcard_get_prop(v, "fn", 1, 0, 0);
-
-
-       if (yes_my_citadel_config) {
-               /* Bingo!  The user is uploading a new vCard, so
-                * delete the old one.  First, figure out which user
-                * is being re-registered...
-                */
-               what_user = atol(CC->room.QRname);
-
-               if (what_user == CC->user.usernum) {
-                       /* It's the logged in user.  That was easy. */
-                       memcpy(&usbuf, &CC->user, sizeof(struct ctdluser));
-               }
-               
-               else if (CtdlGetUserByNumber(&usbuf, what_user) == 0) {
-                       /* We fetched a valid user record */
-               }
-
-               else {
-                       /* somebody set up us the bomb! */
-                       yes_my_citadel_config = 0;
-               }
-       }
-       
-       if (yes_my_citadel_config) {
-               /* Delete the user's old vCard.  This would probably
-                * get taken care of by the replication check, but we
-                * want to make sure there is absolutely only one
-                * vCard in the user's config room at all times.
-                *
-                */
-               CtdlDeleteMessages(CC->room.QRname, NULL, 0, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$");
-
-               /* Make the author of the message the name of the user. */
-               if (!IsEmptyStr(usbuf.fullname)) {
-                       CM_SetField(msg, eAuthor, usbuf.fullname, strlen(usbuf.fullname));
-               }
-       }
-
-       /* Insert or replace RFC2739-compliant free/busy URL */
-       if (yes_my_citadel_config) {
-               sprintf(buf, "http://%s/%s.vfb",
-                       CtdlGetConfigStr("c_fqdn"),
-                       usbuf.fullname);
-               for (i=0; buf[i]; ++i) {
-                       if (buf[i] == ' ') buf[i] = '_';
-               }
-               vcard_set_prop(v, "FBURL;PREF", buf, 0);
-       }
-
-
-       s = vcard_get_prop(v, "UID", 1, 0, 0);
-       if (s == NULL) { /* Note LDAP auth sets UID from the LDAP UUID, use that if it exists. */
-         /* Enforce local UID policy if applicable */
-         if (yes_my_citadel_config) {
-               snprintf(buf, sizeof buf, VCARD_EXT_FORMAT, msg->cm_fields[eAuthor], NODENAME);
-         } else {
-               /* If the vCard has no UID, then give it one. */
-               generate_uuid(buf);
-         }
-         vcard_set_prop(v, "UID", buf, 0);
-    }
-
-
-       /* 
-        * Set the EUID of the message to the UID of the vCard.
-        */
-       CM_FlushField(msg, eExclusiveID);
-
-       s = vcard_get_prop(v, "UID", 1, 0, 0);
-       if (!IsEmptyStr(s)) {
-               CM_SetField(msg, eExclusiveID, s, strlen(s));
-               if (CM_IsEmpty(msg, eMsgSubject)) {
-                       CM_CopyField(msg, eMsgSubject, eExclusiveID);
-               }
-       }
-
-       /*
-        * Set the Subject to the name in the vCard.
-        */
-       s = vcard_get_prop(v, "FN", 1, 0, 0);
-       if (s == NULL) {
-               s = vcard_get_prop(v, "N", 1, 0, 0);
-       }
-       if (!IsEmptyStr(s)) {
-               CM_SetField(msg, eMsgSubject, s, strlen(s));
-       }
-
-       /* Re-serialize it back into the msg body */
-       ser = vcard_serialize(v);
-       if (!IsEmptyStr(ser)) {
-               StrBuf *buf;
-               long serlen;
-
-               serlen = strlen(ser);
-               buf = NewStrBufPlain(NULL, serlen + 1024);
-
-               StrBufAppendBufPlain(buf, HKEY("Content-type: " VCARD_MIME_TYPE "\r\n\r\n"), 0);
-               StrBufAppendBufPlain(buf, ser, serlen, 0);
-               StrBufAppendBufPlain(buf, HKEY("\r\n"), 0);
-               CM_SetAsFieldSB(msg, eMesageText, &buf);
-               free(ser);
-       }
-
-       /* Now allow the save to complete. */
-       vcard_free(v);
-       return(0);
-}
-
-
-/*
- * This handler detects whether the user is attempting to save a new
- * vCard as part of his/her personal configuration, and handles the replace
- * function accordingly (copy the vCard from the config room to the global
- * address book).
- */
-int vcard_upload_aftersave(struct CtdlMessage *msg, struct recptypes *recp) {
-       char *ptr;
-       int linelen;
-       long I;
-       struct vCard *v;
-       int is_UserConf=0;
-       int is_MY_UserConf=0;
-       int is_GAB=0;
-       char roomname[ROOMNAMELEN];
-
-       if (msg->cm_format_type != 4) return(0);
-       if ((!CC->logged_in) && (CC->vcard_updated_by_ldap==0)) return(0);      /* Only do this if logged in, or if ldap changed the vcard. */
-
-       /* We're interested in user config rooms only. */
-
-       if ( !IsEmptyStr(CC->room.QRname) &&
-             (strlen(CC->room.QRname) >= 12) &&
-             (!strcasecmp(&CC->room.QRname[11], USERCONFIGROOM)) ) {
-               is_UserConf = 1;        /* It's someone's config room */
-       }
-       CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCONFIGROOM);
-       if (!strcasecmp(CC->room.QRname, roomname)) {
-               is_UserConf = 1;
-               is_MY_UserConf = 1;     /* It's MY config room */
-       }
-       if (!strcasecmp(CC->room.QRname, ADDRESS_BOOK_ROOM)) {
-               is_GAB = 1;             /* It's the Global Address Book */
-       }
-
-       if (!is_UserConf && !is_GAB) return(0);
-
-       if (CM_IsEmpty(msg, eMesageText))
-               return 0;
-
-       ptr = msg->cm_fields[eMesageText];
-
-       CC->vcard_updated_by_ldap=0;  /* As this will write LDAP's previous changes, disallow LDAP change auth until next LDAP change. */ 
-
-       NewStrBufDupAppendFlush(&CC->StatusMessage, NULL, NULL, 0);
-
-       StrBufPrintf(CC->StatusMessage, "%d\n", LISTING_FOLLOWS);
-
-       while (ptr != NULL) {
-       
-               linelen = strcspn(ptr, "\n");
-               if (linelen == 0) return(0);    /* end of headers */    
-               
-               if (  (!strncasecmp(ptr, "Content-type: text/x-vcard", 26))
-                  || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
-                       /*
-                        * Bingo!  The user is uploading a new vCard, so
-                        * copy it to the Global Address Book room.
-                        */
-
-                       I = atol(msg->cm_fields[eVltMsgNum]);
-                       if (I <= 0L) return(0);
-
-                       /* Store our friendly/display name in memory */
-                       if (is_MY_UserConf) {
-                               v = vcard_load(msg->cm_fields[eMesageText]);
-                               extract_friendly_name(CC->cs_inet_fn, sizeof CC->cs_inet_fn, v);
-                               vcard_free(v);
-                       }
-
-                       if (!is_GAB)
-                       {       // This is not the GAB
-                               /* Put it in the Global Address Book room... */
-                               CtdlSaveMsgPointerInRoom(ADDRESS_BOOK_ROOM, I, 1, msg);
-                       }
-
-                       /* Some sites want an Aide to be notified when a
-                        * user registers or re-registers
-                        * But if the user was an Aide or was edited by an Aide then we can
-                        * Assume they don't need validating.
-                        */
-                       if (CC->user.axlevel >= AxAideU) {
-                               CtdlLockGetCurrentUser();
-                               CC->user.flags |= US_REGIS;
-                               CtdlPutCurrentUserLock();
-                               return (0);
-                       }
-                       
-                       set_mm_valid();
-
-                       /* ...which also means we need to flag the user */
-                       CtdlLockGetCurrentUser();
-                       CC->user.flags |= (US_REGIS|US_NEEDVALID);
-                       CtdlPutCurrentUserLock();
-
-                       return(0);
-               }
-
-               ptr = strchr((char *)ptr, '\n');
-               if (ptr != NULL) ++ptr;
-       }
-
-       return(0);
-}
-
-
-
-/*
- * back end function used for callbacks
- */
-void vcard_gu_backend(long supplied_msgnum, void *userdata) {
-       long *msgnum;
-
-       msgnum = (long *) userdata;
-       *msgnum = supplied_msgnum;
-}
-
-
-/*
- * If this user has a vcard on disk, read it into memory, otherwise allocate
- * and return an empty vCard.
- */
-struct vCard *vcard_get_user(struct ctdluser *u) {
-       char hold_rm[ROOMNAMELEN];
-       char config_rm[ROOMNAMELEN];
-       struct CtdlMessage *msg = NULL;
-       struct vCard *v;
-       long VCmsgnum;
-
-       strcpy(hold_rm, CC->room.QRname);       /* save current room */
-       CtdlMailboxName(config_rm, sizeof config_rm, u, USERCONFIGROOM);
-
-       if (CtdlGetRoom(&CC->room, config_rm) != 0) {
-               CtdlGetRoom(&CC->room, hold_rm);
-               return vcard_new();
-       }
-
-       /* We want the last (and probably only) vcard in this room */
-       VCmsgnum = (-1);
-       CtdlForEachMessage(MSGS_LAST, 1, NULL, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$",
-               NULL, vcard_gu_backend, (void *)&VCmsgnum );
-       CtdlGetRoom(&CC->room, hold_rm);        /* return to saved room */
-
-       if (VCmsgnum < 0L) return vcard_new();
-
-       msg = CtdlFetchMessage(VCmsgnum, 1);
-       if (msg == NULL) return vcard_new();
-
-       v = vcard_load(msg->cm_fields[eMesageText]);
-       CM_Free(msg);
-       return v;
-}
-
-
-/*
- * Store this user's vCard in the appropriate place
- */
-/*
- * Write our config to disk
- */
-void vcard_write_user(struct ctdluser *u, struct vCard *v) {
-       char *ser;
-
-       ser = vcard_serialize(v);
-       if (ser == NULL) {
-               ser = strdup("begin:vcard\r\nend:vcard\r\n");
-       }
-       if (ser == NULL) return;
-
-       /* This handy API function does all the work for us.
-        * NOTE: normally we would want to set that last argument to 1, to
-        * force the system to delete the user's old vCard.  But it doesn't
-        * have to, because the vcard_upload_beforesave() hook above
-        * is going to notice what we're trying to do, and delete the old vCard.
-        */
-       CtdlWriteObject(USERCONFIGROOM,         /* which room */
-                       VCARD_MIME_TYPE,        /* MIME type */
-                       ser,                    /* data */
-                       strlen(ser)+1,          /* length */
-                       u,                      /* which user */
-                       0,                      /* not binary */
-                       0);                     /* no flags */
-
-       free(ser);
-}
-
-
-
-/*
- * Old style "enter registration info" command.  This function simply honors
- * the REGI protocol command, translates the entered parameters into a vCard,
- * and enters the vCard into the user's configuration.
- */
-void cmd_regi(char *argbuf) {
-       int a,b,c;
-       char buf[SIZ];
-       struct vCard *my_vcard;
-
-       char tmpaddr[SIZ];
-       char tmpcity[SIZ];
-       char tmpstate[SIZ];
-       char tmpzip[SIZ];
-       char tmpaddress[SIZ];
-       char tmpcountry[SIZ];
-
-       unbuffer_output();
-
-       if (!(CC->logged_in)) {
-               cprintf("%d Not logged in.\n",ERROR + NOT_LOGGED_IN);
-               return;
-       }
-
-       /* If users cannot create their own accounts, they cannot re-register either. */
-       if ( (CtdlGetConfigInt("c_disable_newu")) && (CC->user.axlevel < AxAideU) ) {
-               cprintf("%d Self-service registration is not allowed here.\n",
-                       ERROR + HIGHER_ACCESS_REQUIRED);
-       }
-
-       my_vcard = vcard_get_user(&CC->user);
-       strcpy(tmpaddr, "");
-       strcpy(tmpcity, "");
-       strcpy(tmpstate, "");
-       strcpy(tmpzip, "");
-       strcpy(tmpcountry, "USA");
-
-       cprintf("%d Send registration...\n", SEND_LISTING);
-       a=0;
-       while (client_getln(buf, sizeof buf), strcmp(buf,"000")) {
-               if (a==0) vcard_set_prop(my_vcard, "n", buf, 0);
-               if (a==1) strcpy(tmpaddr, buf);
-               if (a==2) strcpy(tmpcity, buf);
-               if (a==3) strcpy(tmpstate, buf);
-               if (a==4) {
-                       for (c=0; buf[c]; ++c) {
-                               if ((buf[c]>='0') && (buf[c]<='9')) {
-                                       b = strlen(tmpzip);
-                                       tmpzip[b] = buf[c];
-                                       tmpzip[b+1] = 0;
-                               }
-                       }
-               }
-               if (a==5) vcard_set_prop(my_vcard, "tel", buf, 0);
-               if (a==6) vcard_set_prop(my_vcard, "email;internet", buf, 0);
-               if (a==7) strcpy(tmpcountry, buf);
-               ++a;
-       }
-
-       snprintf(tmpaddress, sizeof tmpaddress, ";;%s;%s;%s;%s;%s",
-               tmpaddr, tmpcity, tmpstate, tmpzip, tmpcountry);
-       vcard_set_prop(my_vcard, "adr", tmpaddress, 0);
-       vcard_write_user(&CC->user, my_vcard);
-       vcard_free(my_vcard);
-}
-
-
-/*
- * Protocol command to fetch registration info for a user
- */
-void cmd_greg(char *argbuf)
-{
-       struct ctdluser usbuf;
-       struct vCard *v;
-       char *s;
-       char who[USERNAME_SIZE];
-       char adr[256];
-       char buf[256];
-
-       extract_token(who, argbuf, 0, '|', sizeof who);
-
-       if (!(CC->logged_in)) {
-               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
-               return;
-       }
-
-       if (!strcasecmp(who,"_SELF_")) strcpy(who,CC->curr_user);
-
-       if ((CC->user.axlevel < AxAideU) && (strcasecmp(who,CC->curr_user))) {
-               cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
-               return;
-       }
-
-       if (CtdlGetUser(&usbuf, who) != 0) {
-               cprintf("%d '%s' not found.\n", ERROR + NO_SUCH_USER, who);
-               return;
-       }
-
-       v = vcard_get_user(&usbuf);
-
-       cprintf("%d %s\n", LISTING_FOLLOWS, usbuf.fullname);
-       cprintf("%ld\n", usbuf.usernum);
-       cprintf("%s\n", usbuf.password);
-       s = vcard_get_prop(v, "n", 1, 0, 0);
-       cprintf("%s\n", s ? s : " ");                   /* name */
-       s = vcard_get_prop(v, "adr", 1, 0, 0);
-       snprintf(adr, sizeof adr, "%s", s ? s : " ");   /* address */
-       extract_token(buf, adr, 2, ';', sizeof buf);
-       cprintf("%s\n", buf);                           /* street */
-       extract_token(buf, adr, 3, ';', sizeof buf);
-       cprintf("%s\n", buf);                           /* city */
-       extract_token(buf, adr, 4, ';', sizeof buf);
-       cprintf("%s\n", buf);                           /* state */
-       extract_token(buf, adr, 5, ';', sizeof buf);
-       cprintf("%s\n", buf);                           /* zip */
-
-       s = vcard_get_prop(v, "tel", 1, 0, 0);
-       if (s == NULL) s = vcard_get_prop(v, "tel", 1, 0, 0);
-       if (s != NULL) {
-               cprintf("%s\n", s);
-       }
-       else {
-               cprintf(" \n");
-       }
-
-       cprintf("%d\n", usbuf.axlevel);
-
-       s = vcard_get_prop(v, "email;internet", 0, 0, 0);
-       cprintf("%s\n", s ? s : " ");
-       s = vcard_get_prop(v, "adr", 0, 0, 0);
-       snprintf(adr, sizeof adr, "%s", s ? s : " ");/* address... */
-
-       extract_token(buf, adr, 6, ';', sizeof buf);
-       cprintf("%s\n", buf);                           /* country */
-       cprintf("000\n");
-       vcard_free(v);
-}
-
-
-
-/*
- * When a user is being created, create his/her vCard.
- */
-void vcard_newuser(struct ctdluser *usbuf) {
-       char vname[256];
-       char buf[256];
-       int i;
-       struct vCard *v;
-       int need_default_vcard;
-
-       need_default_vcard =1;
-       vcard_fn_to_n(vname, usbuf->fullname, sizeof vname);
-       syslog(LOG_DEBUG, "vcard: converted <%s> to <%s>", usbuf->fullname, vname);
-
-       /* Create and save the vCard */
-       v = vcard_new();
-       if (v == NULL) return;
-       vcard_add_prop(v, "fn", usbuf->fullname);
-       vcard_add_prop(v, "n", vname);
-       vcard_add_prop(v, "adr", "adr:;;_;_;_;00000;__");
-
-#ifdef HAVE_GETPWUID_R
-       /* If using host auth mode, we add an email address based on the login */
-       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
-               struct passwd pwd;
-               char pwd_buffer[SIZ];
-               
-#ifdef SOLARIS_GETPWUID
-               if (getpwuid_r(usbuf->uid, &pwd, pwd_buffer, sizeof pwd_buffer) != NULL) {
-#else // SOLARIS_GETPWUID
-               struct passwd *result = NULL;
-               syslog(LOG_DEBUG, "vcard: searching for uid %d", usbuf->uid);
-               if (getpwuid_r(usbuf->uid, &pwd, pwd_buffer, sizeof pwd_buffer, &result) == 0) {
-#endif // HAVE_GETPWUID_R
-                       snprintf(buf, sizeof buf, "%s@%s", pwd.pw_name, CtdlGetConfigStr("c_fqdn"));
-                       vcard_add_prop(v, "email;internet", buf);
-                       need_default_vcard = 0;
-               }
-       }
-#endif
-
-       /*
-        * Is this an LDAP session?  If so, copy various LDAP attributes from the directory entry
-        * into the user's vCard.
-        */
-       if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
-               //uid_t ldap_uid;
-               int found_user;
-               char ldap_cn[512];
-               char ldap_dn[512];
-
-               syslog(LOG_DEBUG, "\033[31m FIXME BORK BORK BORK try lookup by uid , or maybe dn?\033[0m");
-
-               found_user = CtdlTryUserLDAP(usbuf->fullname, ldap_dn, sizeof ldap_dn, ldap_cn, sizeof ldap_cn, &usbuf->uid);
-               if (found_user == 0) {
-                       if (Ctdl_LDAP_to_vCard(ldap_dn, v)) {
-                               /* Allow global address book and internet directory update without login long enough to write this. */
-                               CC->vcard_updated_by_ldap++;  /* Otherwise we'll only update the user config. */
-                               need_default_vcard = 0;
-                               syslog(LOG_DEBUG, "vcard: LDAP Created Initial vCard for %s\n",usbuf->fullname);
-                       }
-               }
-       }
-
-       if (need_default_vcard!=0) {
-               /* Everyone gets an email address based on their display name */
-               snprintf(buf, sizeof buf, "%s@%s", usbuf->fullname, CtdlGetConfigStr("c_fqdn"));
-               for (i=0; buf[i]; ++i) {
-                       if (buf[i] == ' ') buf[i] = '_';
-               }
-               vcard_add_prop(v, "email;internet", buf);
-       }
-       vcard_write_user(usbuf, v);
-       vcard_free(v);
-}
-
-
-/*
- * Get Valid Screen Names
- */
-void cmd_gvsn(char *argbuf)
-{
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       cprintf("%d valid screen names:\n", LISTING_FOLLOWS);
-       cprintf("%s\n", CC->user.fullname);
-       if ( (!IsEmptyStr(CC->cs_inet_fn)) && (strcasecmp(CC->user.fullname, CC->cs_inet_fn)) ) {
-               cprintf("%s\n", CC->cs_inet_fn);
-       }
-       cprintf("000\n");
-}
-
-
-/*
- * Get Valid Email Addresses
- * FIXME this doesn't belong in serv_vcard.c anymore , maybe move it to internet_addressing.c
- */
-void cmd_gvea(char *argbuf)
-{
-       int num_secondary_emails = 0;
-       int i;
-       char buf[256];
-
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       cprintf("%d valid email addresses:\n", LISTING_FOLLOWS);
-       if (!IsEmptyStr(CC->cs_inet_email)) {
-               cprintf("%s\n", CC->cs_inet_email);
-       }
-       if (!IsEmptyStr(CC->cs_inet_other_emails)) {
-               num_secondary_emails = num_tokens(CC->cs_inet_other_emails, '|');
-               for (i=0; i<num_secondary_emails; ++i) {
-                       extract_token(buf, CC->cs_inet_other_emails,i,'|',sizeof CC->cs_inet_other_emails);
-                       cprintf("%s\n", buf);
-               }
-       }
-       cprintf("000\n");
-}
-
-
-/*
- * Callback function for cmd_dvca() that hunts for vCard content types
- * and outputs any email addresses found within.
- */
-void dvca_mime_callback(char *name, char *filename, char *partnum, char *disp,
-               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
-               char *cbid, void *cbuserdata) {
-
-       struct vCard *v;
-       char displayname[256] = "";
-       int displayname_len;
-       char emailaddr[256] = "";
-       int i;
-       int has_commas = 0;
-
-       if ( (strcasecmp(cbtype, "text/vcard")) && (strcasecmp(cbtype, "text/x-vcard")) ) {
-               return;
-       }
-
-       v = vcard_load(content);
-       if (v == NULL) return;
-
-       extract_friendly_name(displayname, sizeof displayname, v);
-       extract_inet_email_addrs(emailaddr, sizeof emailaddr, NULL, 0, v, 0);
-
-       displayname_len = strlen(displayname);
-       for (i=0; i<displayname_len; ++i) {
-               if (displayname[i] == '\"') displayname[i] = ' ';
-               if (displayname[i] == ';') displayname[i] = ',';
-               if (displayname[i] == ',') has_commas = 1;
-       }
-       striplt(displayname);
-
-       cprintf("%s%s%s <%s>\n",
-               (has_commas ? "\"" : ""),
-               displayname,
-               (has_commas ? "\"" : ""),
-               emailaddr
-       );
-
-       vcard_free(v);
-}
-
-
-/*
- * Back end callback function for cmd_dvca()
- *
- * It's basically just passed a list of message numbers, which we're going
- * to fetch off the disk and then pass along to the MIME parser via another
- * layer of callback...
- */
-void dvca_callback(long msgnum, void *userdata) {
-       struct CtdlMessage *msg = NULL;
-
-       msg = CtdlFetchMessage(msgnum, 1);
-       if (msg == NULL) return;
-       mime_parser(CM_RANGE(msg, eMesageText),
-                   *dvca_mime_callback,        /* callback function */
-                   NULL, NULL,
-                   NULL,                       /* user data */
-                   0
-               );
-       CM_Free(msg);
-}
-
-
-/*
- * Dump VCard Addresses
- */
-void cmd_dvca(char *argbuf)
-{
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       cprintf("%d addresses:\n", LISTING_FOLLOWS);
-       CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, dvca_callback, NULL);
-       cprintf("000\n");
-}
-
-
-/*
- * Query Directory
- */
-void cmd_qdir(char *argbuf) {
-       char citadel_addr[256];
-       char internet_addr[256];
-
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
-
-       if (CtdlDirectoryLookup(citadel_addr, internet_addr, sizeof citadel_addr) != 0) {
-               cprintf("%d %s was not found.\n",
-                       ERROR + NO_SUCH_USER, internet_addr);
-               return;
-       }
-
-       cprintf("%d %s\n", CIT_OK, citadel_addr);
-}
-
-
-/*
- * Query Directory, in fact an alias to match postfix tcp auth.
- */
-void check_get(void) {
-       char internet_addr[256];
-
-       char cmdbuf[SIZ];
-
-       time(&CC->lastcmd);
-       memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
-       if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
-               syslog(LOG_ERR, "vcard: client disconnected: ending session.");
-               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
-               return;
-       }
-       syslog(LOG_INFO, ": %s", cmdbuf);
-       while (strlen(cmdbuf) < 3) strcat(cmdbuf, " ");
-       syslog(LOG_INFO, "[ %s]", cmdbuf);
-       
-       if (strncasecmp(cmdbuf, "GET ", 4)==0)
-       {
-               struct recptypes *rcpt;
-               char *argbuf = &cmdbuf[4];
-               
-               extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
-               rcpt = validate_recipients(internet_addr, NULL, CHECK_EXIST);
-               if (    (rcpt != NULL) &&
-                       (
-                               (*rcpt->recp_local != '\0') ||
-                               (*rcpt->recp_room != '\0')
-                       )
-               ) {
-                       cprintf("200 OK %s\n", internet_addr);
-                       syslog(LOG_INFO, "vcard: sending 200 OK for the room %s", rcpt->display_recp);
-               }
-               else 
-               {
-                       cprintf("500 REJECT no one here by that name.\n");
-                       
-                       syslog(LOG_INFO, "vcard: sending 500 REJECT no one here by that name: %s", internet_addr);
-               }
-               if (rcpt != NULL) 
-                       free_recipients(rcpt);
-       }
-       else {
-               cprintf("500 REJECT invalid Query.\n");
-               syslog(LOG_INFO, "vcard: sending 500 REJECT invalid query: %s", internet_addr);
-       }
-}
-
-
-void check_get_greeting(void) {
-/* dummy function, we have no greeting in this very simple protocol. */
-}
-
-
-/*
- * We don't know if the Contacts room exists so we just create it at login
- */
-void vcard_CtdlCreateRoom(void)
-{
-       struct ctdlroom qr;
-       visit vbuf;
-
-       /* Create the calendar room if it doesn't already exist */
-       CtdlCreateRoom(USERCONTACTSROOM, 4, "", 0, 1, 0, VIEW_ADDRESSBOOK);
-
-       /* Set expiration policy to manual; otherwise objects will be lost! */
-       if (CtdlGetRoomLock(&qr, USERCONTACTSROOM)) {
-               syslog(LOG_ERR, "vcard: couldn't get the user CONTACTS room!");
-               return;
-       }
-       qr.QRep.expire_mode = EXPIRE_MANUAL;
-       qr.QRdefaultview = VIEW_ADDRESSBOOK;    /* 2 = address book view */
-       CtdlPutRoomLock(&qr);
-
-       /* Set the view to a calendar view */
-       CtdlGetRelationship(&vbuf, &CC->user, &qr);
-       vbuf.v_view = 2;        /* 2 = address book view */
-       CtdlSetRelationship(&vbuf, &CC->user, &qr);
-
-       return;
-}
-
-
-
-
-/*
- * When a user logs in...
- */
-void vcard_session_login_hook(void) {
-       struct vCard *v = NULL;
-
-       /*
-        * Is this an LDAP session?  If so, copy various LDAP attributes from the directory entry
-        * into the user's vCard.
-        */
-       if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
-               v = vcard_get_user(&CC->user);
-               if (v) {
-                       if (Ctdl_LDAP_to_vCard(CC->ldap_dn, v)) {
-                               CC->vcard_updated_by_ldap++; /* Make sure changes make it to the global address book and internet directory, not just the user config. */
-                               syslog(LOG_DEBUG, "vcard: LDAP Detected vcard change");
-                               vcard_write_user(&CC->user, v);
-                       }
-               }
-       }
-
-       /*
-        * Extract the user's friendly/screen name
-        * These are inserted into the session data for various message entry commands to use.
-        */
-       v = vcard_get_user(&CC->user);
-       if (v) {
-               extract_friendly_name(CC->cs_inet_fn, sizeof CC->cs_inet_fn, v);
-               vcard_free(v);
-       }
-
-       /*
-        * Create the user's 'Contacts' room (personal address book) if it doesn't already exist.
-        */
-       vcard_CtdlCreateRoom();
-}
-
-
-/* 
- * Turn an arbitrary RFC822 address into a struct vCard for possible
- * inclusion into an address book.
- */
-struct vCard *vcard_new_from_rfc822_addr(char *addr) {
-       struct vCard *v;
-       char user[256], node[256], name[256], email[256], n[256], uid[256];
-       int i;
-
-       v = vcard_new();
-       if (v == NULL) return(NULL);
-
-       process_rfc822_addr(addr, user, node, name);
-       vcard_set_prop(v, "fn", name, 0);
-
-       vcard_fn_to_n(n, name, sizeof n);
-       vcard_set_prop(v, "n", n, 0);
-
-       snprintf(email, sizeof email, "%s@%s", user, node);
-       vcard_set_prop(v, "email;internet", email, 0);
-
-       snprintf(uid, sizeof uid, "collected: %s %s@%s", name, user, node);
-       for (i=0; uid[i]; ++i) {
-               if (isspace(uid[i])) uid[i] = '_';
-               uid[i] = tolower(uid[i]);
-       }
-       vcard_set_prop(v, "UID", uid, 0);
-
-       return(v);
-}
-
-
-/*
- * This is called by store_harvested_addresses() to remove from the
- * list any addresses we already have in our address book.
- */
-void strip_addresses_already_have(long msgnum, void *userdata) {
-       char *collected_addresses;
-       struct CtdlMessage *msg = NULL;
-       struct vCard *v;
-       char *value = NULL;
-       int i, j;
-       char addr[256], user[256], node[256], name[256];
-
-       collected_addresses = (char *)userdata;
-
-       msg = CtdlFetchMessage(msgnum, 1);
-       if (msg == NULL) return;
-       v = vcard_load(msg->cm_fields[eMesageText]);
-       CM_Free(msg);
-
-       i = 0;
-       while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
-
-               for (j=0; j<num_tokens(collected_addresses, ','); ++j) {
-                       extract_token(addr, collected_addresses, j, ',', sizeof addr);
-
-                       /* Remove the address if we already have it! */
-                       process_rfc822_addr(addr, user, node, name);
-                       snprintf(addr, sizeof addr, "%s@%s", user, node);
-                       if (!strcasecmp(value, addr)) {
-                               remove_token(collected_addresses, j, ',');
-                       }
-               }
-
-       }
-
-       vcard_free(v);
-}
-
-
-
-/*
- * Back end function for store_harvested_addresses()
- */
-void store_this_ha(struct addresses_to_be_filed *aptr) {
-       struct CtdlMessage *vmsg = NULL;
-       char *ser = NULL;
-       struct vCard *v = NULL;
-       char recipient[256];
-       int i;
-
-       /* First remove any addresses we already have in the address book */
-       CtdlUserGoto(aptr->roomname, 0, 0, NULL, NULL, NULL, NULL);
-       CtdlForEachMessage(MSGS_ALL, 0, NULL, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$", NULL,
-               strip_addresses_already_have, aptr->collected_addresses);
-
-       if (!IsEmptyStr(aptr->collected_addresses))
-          for (i=0; i<num_tokens(aptr->collected_addresses, ','); ++i) {
-
-               /* Make a vCard out of each address */
-               extract_token(recipient, aptr->collected_addresses, i, ',', sizeof recipient);
-               striplt(recipient);
-               v = vcard_new_from_rfc822_addr(recipient);
-               if (v != NULL) {
-                       const char *s;
-                       vmsg = malloc(sizeof(struct CtdlMessage));
-                       memset(vmsg, 0, sizeof(struct CtdlMessage));
-                       vmsg->cm_magic = CTDLMESSAGE_MAGIC;
-                       vmsg->cm_anon_type = MES_NORMAL;
-                       vmsg->cm_format_type = FMT_RFC822;
-                       CM_SetField(vmsg, eAuthor, HKEY("Citadel"));
-                       s = vcard_get_prop(v, "UID", 1, 0, 0);
-                       if (!IsEmptyStr(s)) {
-                               CM_SetField(vmsg, eExclusiveID, s, strlen(s));
-                       }
-                       ser = vcard_serialize(v);
-                       if (ser != NULL) {
-                               StrBuf *buf;
-                               long serlen;
-                               
-                               serlen = strlen(ser);
-                               buf = NewStrBufPlain(NULL, serlen + 1024);
-
-                               StrBufAppendBufPlain(buf, HKEY("Content-type: " VCARD_MIME_TYPE "\r\n\r\n"), 0);
-                               StrBufAppendBufPlain(buf, ser, serlen, 0);
-                               StrBufAppendBufPlain(buf, HKEY("\r\n"), 0);
-                               CM_SetAsFieldSB(vmsg, eMesageText, &buf);
-                               free(ser);
-                       }
-                       vcard_free(v);
-
-                       syslog(LOG_DEBUG, "vcard: adding contact: %s", recipient);
-                       CtdlSubmitMsg(vmsg, NULL, aptr->roomname);
-                       CM_Free(vmsg);
-               }
-       }
-
-       free(aptr->roomname);
-       free(aptr->collected_addresses);
-       free(aptr);
-}
-
-
-/*
- * When a user sends a message, we may harvest one or more email addresses
- * from the recipient list to be added to the user's address book.  But we
- * want to do this asynchronously so it doesn't keep the user waiting.
- */
-void store_harvested_addresses(void) {
-
-       struct addresses_to_be_filed *aptr = NULL;
-
-       if (atbf == NULL) return;
-
-       begin_critical_section(S_ATBF);
-       while (atbf != NULL) {
-               aptr = atbf;
-               atbf = atbf->next;
-               end_critical_section(S_ATBF);
-               store_this_ha(aptr);
-               begin_critical_section(S_ATBF);
-       }
-       end_critical_section(S_ATBF);
-}
-
-
-/* 
- * Function to output vCard data as plain text.  Nobody uses MSG0 anymore, so
- * really this is just so we expose the vCard data to the full text indexer.
- */
-void vcard_fixed_output(char *ptr, int len) {
-       char *serialized_vcard;
-       struct vCard *v;
-       char *key, *value;
-       int i = 0;
-
-       serialized_vcard = malloc(len + 1);
-       safestrncpy(serialized_vcard, ptr, len+1);
-       v = vcard_load(serialized_vcard);
-       free(serialized_vcard);
-
-       i = 0;
-       while (key = vcard_get_prop(v, "", 0, i, 1), key != NULL) {
-               value = vcard_get_prop(v, "", 0, i++, 0);
-               cprintf("%s\n", value);
-       }
-
-       vcard_free(v);
-}
-
-
-const char *CitadelServiceDICT_TCP="DICT_TCP";
-
-CTDL_MODULE_INIT(vcard)
-{
-       struct ctdlroom qr;
-
-       if (!threading)
-       {
-               CtdlRegisterSessionHook(vcard_session_login_hook, EVT_LOGIN, PRIO_LOGIN + 70);
-               CtdlRegisterMessageHook(vcard_upload_beforesave, EVT_BEFORESAVE);
-               CtdlRegisterMessageHook(vcard_upload_aftersave, EVT_AFTERSAVE);
-               CtdlRegisterProtoHook(cmd_regi, "REGI", "Enter registration info");
-               CtdlRegisterProtoHook(cmd_greg, "GREG", "Get registration info");
-               CtdlRegisterProtoHook(cmd_qdir, "QDIR", "Query Directory");
-               CtdlRegisterProtoHook(cmd_gvsn, "GVSN", "Get Valid Screen Names");
-               CtdlRegisterProtoHook(cmd_gvea, "GVEA", "Get Valid Email Addresses");
-               CtdlRegisterProtoHook(cmd_dvca, "DVCA", "Dump VCard Addresses");
-               CtdlRegisterUserHook(vcard_newuser, EVT_NEWUSER);
-               CtdlRegisterSessionHook(store_harvested_addresses, EVT_TIMER, PRIO_CLEANUP + 470);
-               CtdlRegisterFixedOutputHook("text/x-vcard", vcard_fixed_output);
-               CtdlRegisterFixedOutputHook("text/vcard", vcard_fixed_output);
-
-               /* Create the Global Address Book room if necessary */
-               CtdlCreateRoom(ADDRESS_BOOK_ROOM, 3, "", 0, 1, 0, VIEW_ADDRESSBOOK);
-
-               /* Set expiration policy to manual; otherwise objects will be lost! */
-               if (!CtdlGetRoomLock(&qr, ADDRESS_BOOK_ROOM)) {
-                       qr.QRep.expire_mode = EXPIRE_MANUAL;
-                       qr.QRdefaultview = VIEW_ADDRESSBOOK;                    // 2 = address book view
-                       CtdlPutRoomLock(&qr);
-               }
-
-               /* for postfix tcpdict */
-               CtdlRegisterServiceHook(CtdlGetConfigInt("c_pftcpdict_port"),   // Postfix
-                                       NULL,
-                                       check_get_greeting,
-                                       check_get,
-                                       NULL,
-                                       CitadelServiceDICT_TCP
-               );
-       }
-
-       /* return our module name for the log */
-       return "vcard";
-}
diff --git a/citadel/modules/wiki/.gitignore b/citadel/modules/wiki/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/wiki/serv_wiki.c b/citadel/modules/wiki/serv_wiki.c
deleted file mode 100644 (file)
index ebbb329..0000000
+++ /dev/null
@@ -1,728 +0,0 @@
-/*
- * Server-side module for Wiki rooms.  This handles things like version control. 
- * 
- * Copyright (c) 2009-2021 by the citadel.org team
- *
- * This program is open source software.  You can redistribute it and/or
- * modify it under the terms of the GNU General Public License, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <ctype.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "room_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "euidindex.h"
-#include "ctdl_module.h"
-
-/*
- * Data passed back and forth between wiki_rev() and its MIME parser callback
- */
-struct HistoryEraserCallBackData {
-       char *tempfilename;             /* name of temp file being patched */
-       char *stop_when;                /* stop when we hit this uuid */
-       int done;                       /* set to nonzero when we're done patching */
-};
-
-/*
- * Name of the temporary room we create to store old revisions when someone requests them.
- * We put it in an invalid namespace so the DAP cleans up after us later.
- */
-char *wwm = "9999999999.WikiWaybackMachine";
-
-/*
- * Before allowing a wiki page save to execute, we have to perform version control.
- * This involves fetching the old version of the page if it exists.
- */
-int wiki_upload_beforesave(struct CtdlMessage *msg, struct recptypes *recp) {
-       long old_msgnum = (-1L);
-       struct CtdlMessage *old_msg = NULL;
-       long history_msgnum = (-1L);
-       struct CtdlMessage *history_msg = NULL;
-       char diff_old_filename[PATH_MAX];
-       char diff_new_filename[PATH_MAX];
-       char diff_out_filename[PATH_MAX];
-       char diff_cmd[PATH_MAX];
-       FILE *fp;
-       int rv;
-       char history_page[1024];
-       long history_page_len;
-       char boundary[256];
-       char prefixed_boundary[258];
-       char buf[1024];
-       char *diffbuf = NULL;
-       size_t diffbuf_len = 0;
-       char *ptr = NULL;
-       long newmsgid;
-       StrBuf *msgidbuf;
-
-       if (!CC->logged_in) return(0);  /* Only do this if logged in. */
-
-       /* Is this a room with a Wiki in it, don't run this hook. */
-       if (CC->room.QRdefaultview != VIEW_WIKI) {
-               return(0);
-       }
-
-       /* If this isn't a MIME message, don't bother. */
-       if (msg->cm_format_type != 4) return(0);
-
-       /* If there's no EUID we can't do this.  Reject the post. */
-       if (CM_IsEmpty(msg, eExclusiveID)) return(1);
-
-       newmsgid = get_new_message_number();
-       msgidbuf = NewStrBuf();
-       StrBufPrintf(msgidbuf, "%08lX-%08lX@%s/%s",
-                    (long unsigned int) time(NULL),
-                    (long unsigned int) newmsgid,
-                    CtdlGetConfigStr("c_fqdn"),
-                    msg->cm_fields[eExclusiveID]
-               );
-
-       CM_SetAsFieldSB(msg, emessageId, &msgidbuf);
-
-       history_page_len = snprintf(history_page, sizeof history_page,
-                                   "%s_HISTORY_", msg->cm_fields[eExclusiveID]);
-
-       /* Make sure we're saving a real wiki page rather than a wiki history page.
-        * This is important in order to avoid recursing infinitely into this hook.
-        */
-       if (    (msg->cm_lengths[eExclusiveID] >= 9)
-               && (!strcasecmp(&msg->cm_fields[eExclusiveID][msg->cm_lengths[eExclusiveID]-9], "_HISTORY_"))
-       ) {
-               syslog(LOG_DEBUG, "History page not being historied\n");
-               return(0);
-       }
-
-       /* If there's no message text, obviously this is all b0rken and shouldn't happen at all */
-       if (CM_IsEmpty(msg, eMesageText)) return(0);
-
-       /* Set the message subject identical to the page name */
-       CM_CopyField(msg, eMsgSubject, eExclusiveID);
-
-       /* See if we can retrieve the previous version. */
-       old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
-       if (old_msgnum > 0L) {
-               old_msg = CtdlFetchMessage(old_msgnum, 1);
-       }
-       else {
-               old_msg = NULL;
-       }
-
-       if ((old_msg != NULL) && (CM_IsEmpty(old_msg, eMesageText))) {  /* old version is corrupt? */
-               CM_Free(old_msg);
-               old_msg = NULL;
-       }
-       
-       /* If no changes were made, don't bother saving it again */
-       if ((old_msg != NULL) && (!strcmp(msg->cm_fields[eMesageText], old_msg->cm_fields[eMesageText]))) {
-               CM_Free(old_msg);
-               return(1);
-       }
-
-       /*
-        * Generate diffs
-        */
-       CtdlMakeTempFileName(diff_old_filename, sizeof diff_old_filename);
-       CtdlMakeTempFileName(diff_new_filename, sizeof diff_new_filename);
-       CtdlMakeTempFileName(diff_out_filename, sizeof diff_out_filename);
-
-       if (old_msg != NULL) {
-               fp = fopen(diff_old_filename, "w");
-               rv = fwrite(old_msg->cm_fields[eMesageText], old_msg->cm_lengths[eMesageText], 1, fp);
-               fclose(fp);
-               CM_Free(old_msg);
-       }
-
-       fp = fopen(diff_new_filename, "w");
-       rv = fwrite(msg->cm_fields[eMesageText], msg->cm_lengths[eMesageText], 1, fp);
-       fclose(fp);
-
-       snprintf(diff_cmd, sizeof diff_cmd,
-               DIFF " -u %s %s >%s",
-               diff_new_filename,
-               ((old_msg != NULL) ? diff_old_filename : "/dev/null"),
-               diff_out_filename
-       );
-       syslog(LOG_DEBUG, "diff cmd: %s", diff_cmd);
-       rv = system(diff_cmd);
-       syslog(LOG_DEBUG, "diff cmd returned %d", rv);
-
-       diffbuf_len = 0;
-       diffbuf = NULL;
-       fp = fopen(diff_out_filename, "r");
-       if (fp == NULL) {
-               fp = fopen("/dev/null", "r");
-       }
-       if (fp != NULL) {
-               fseek(fp, 0L, SEEK_END);
-               diffbuf_len = ftell(fp);
-               fseek(fp, 0L, SEEK_SET);
-               diffbuf = malloc(diffbuf_len + 1);
-               fread(diffbuf, diffbuf_len, 1, fp);
-               diffbuf[diffbuf_len] = '\0';
-               fclose(fp);
-       }
-
-       syslog(LOG_DEBUG, "diff length is "SIZE_T_FMT" bytes", diffbuf_len);
-
-       unlink(diff_old_filename);
-       unlink(diff_new_filename);
-       unlink(diff_out_filename);
-
-       /* Determine whether this was a bogus (empty) edit */
-       if ((diffbuf_len = 0) && (diffbuf != NULL)) {
-               free(diffbuf);
-               diffbuf = NULL;
-       }
-       if (diffbuf == NULL) {
-               return(1);              /* No changes at all?  Abandon the post entirely! */
-       }
-
-       /* Now look for the existing edit history */
-
-       history_msgnum = CtdlLocateMessageByEuid(history_page, &CC->room);
-       history_msg = NULL;
-       if (history_msgnum > 0L) {
-               history_msg = CtdlFetchMessage(history_msgnum, 1);
-       }
-
-       /* Create a new history message if necessary */
-       if (history_msg == NULL) {
-               char *buf;
-               long len;
-
-               history_msg = malloc(sizeof(struct CtdlMessage));
-               memset(history_msg, 0, sizeof(struct CtdlMessage));
-               history_msg->cm_magic = CTDLMESSAGE_MAGIC;
-               history_msg->cm_anon_type = MES_NORMAL;
-               history_msg->cm_format_type = FMT_RFC822;
-               CM_SetField(history_msg, eAuthor, HKEY("Citadel"));
-               if (!IsEmptyStr(CC->room.QRname)){
-                       CM_SetField(history_msg, eRecipient, CC->room.QRname, strlen(CC->room.QRname));
-               }
-               CM_SetField(history_msg, eExclusiveID, history_page, history_page_len);
-               CM_SetField(history_msg, eMsgSubject, history_page, history_page_len);
-               CM_SetField(history_msg, eSuppressIdx, HKEY("1")); /* suppress full text indexing */
-               snprintf(boundary, sizeof boundary, "Citadel--Multipart--%04x--%08lx", getpid(), time(NULL));
-               buf = (char*) malloc(1024);
-               len = snprintf(buf, 1024,
-                              "Content-type: multipart/mixed; boundary=\"%s\"\n\n"
-                              "This is a Citadel wiki history encoded as multipart MIME.\n"
-                              "Each part is comprised of a diff script representing one change set.\n"
-                              "\n"
-                              "--%s--\n",
-                              boundary, boundary
-               );
-               CM_SetAsField(history_msg, eMesageText, &buf, len);
-       }
-
-       /* Update the history message (regardless of whether it's new or existing) */
-
-       /* Remove the Message-ID from the old version of the history message.  This will cause a brand
-        * new one to be generated, avoiding an uninitentional hit of the loop zapper when we replicate.
-        */
-       CM_FlushField(history_msg, emessageId);
-
-       /* Figure out the boundary string.  We do this even when we generated the
-        * boundary string in the above code, just to be safe and consistent.
-        */
-       *boundary = '\0';
-
-       ptr = history_msg->cm_fields[eMesageText];
-       do {
-               ptr = memreadline(ptr, buf, sizeof buf);
-               if (*ptr != 0) {
-                       striplt(buf);
-                       if (!IsEmptyStr(buf) && (!strncasecmp(buf, "Content-type:", 13))) {
-                               if (
-                                       (bmstrcasestr(buf, "multipart") != NULL)
-                                       && (bmstrcasestr(buf, "boundary=") != NULL)
-                               ) {
-                                       safestrncpy(boundary, bmstrcasestr(buf, "\""), sizeof boundary);
-                                       char *qu;
-                                       qu = strchr(boundary, '\"');
-                                       if (qu) {
-                                               strcpy(boundary, ++qu);
-                                       }
-                                       qu = strchr(boundary, '\"');
-                                       if (qu) {
-                                               *qu = 0;
-                                       }
-                               }
-                       }
-               }
-       } while ( (IsEmptyStr(boundary)) && (*ptr != 0) );
-
-       /*
-        * Now look for the first boundary.  That is where we need to insert our fun.
-        */
-       if (!IsEmptyStr(boundary)) {
-               char *MsgText;
-               long MsgTextLen;
-               time_t Now = time(NULL);
-
-               snprintf(prefixed_boundary, sizeof(prefixed_boundary), "--%s", boundary);
-               
-               CM_GetAsField(history_msg, eMesageText, &MsgText, &MsgTextLen);
-
-               ptr = bmstrcasestr(MsgText, prefixed_boundary);
-               if (ptr != NULL) {
-                       StrBuf *NewMsgText;
-                       char uuid[64];
-                       char memo[512];
-                       long memolen;
-                       char encoded_memo[1024];
-                       
-                       NewMsgText = NewStrBufPlain(NULL, MsgTextLen + diffbuf_len + 1024);
-
-                       generate_uuid(uuid);
-                       memolen = snprintf(memo, sizeof(memo), "%s|%ld|%s|%s", 
-                                          uuid,
-                                          Now,
-                                          CC->user.fullname,
-                                          CtdlGetConfigStr("c_nodename"));
-
-                       memolen = CtdlEncodeBase64(encoded_memo, memo, memolen, 0);
-
-                       StrBufAppendBufPlain(NewMsgText, HKEY("--"), 0);
-                       StrBufAppendBufPlain(NewMsgText, boundary, -1, 0);
-                       StrBufAppendBufPlain(
-                               NewMsgText, 
-                               HKEY("\n"
-                                    "Content-type: text/plain\n"
-                                    "Content-Disposition: inline; filename=\""), 0);
-
-                       StrBufAppendBufPlain(NewMsgText, encoded_memo, memolen, 0);
-
-                       StrBufAppendBufPlain(
-                               NewMsgText, 
-                               HKEY("\"\n"
-                                    "Content-Transfer-Encoding: 8bit\n"
-                                    "\n"), 0);
-
-                       StrBufAppendBufPlain(NewMsgText, diffbuf, diffbuf_len, 0);
-                       StrBufAppendBufPlain(NewMsgText, HKEY("\n"), 0);
-
-                       StrBufAppendBufPlain(NewMsgText, ptr, MsgTextLen - (ptr - MsgText), 0);
-                       free(MsgText);
-                       CM_SetAsFieldSB(history_msg, eMesageText, &NewMsgText); 
-               }
-               else
-               {
-                       CM_SetAsField(history_msg, eMesageText, &MsgText, MsgTextLen); 
-               }
-
-               CM_SetFieldLONG(history_msg, eTimestamp, Now);
-       
-               CtdlSubmitMsg(history_msg, NULL, "");
-       }
-       else {
-               syslog(LOG_ALERT, "Empty boundary string in history message.  No history!\n");
-       }
-
-       free(diffbuf);
-       CM_Free(history_msg);
-       return(0);
-}
-
-
-/*
- * MIME Parser callback for wiki_history()
- *
- * The "filename" field will contain a memo field.  All we have to do is decode
- * the base64 and output it.  The data is already in a delimited format suitable
- * for our client protocol.
- */
-void wiki_history_callback(char *name, char *filename, char *partnum, char *disp,
-                  void *content, char *cbtype, char *cbcharset, size_t length,
-                  char *encoding, char *cbid, void *cbuserdata)
-{
-       char memo[1024];
-
-       CtdlDecodeBase64(memo, filename, strlen(filename));
-       cprintf("%s\n", memo);
-}
-
-
-/*
- * Fetch a list of revisions for a particular wiki page
- */
-void wiki_history(char *pagename) {
-       int r;
-       char history_page_name[270];
-       long msgnum;
-       struct CtdlMessage *msg;
-
-       r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
-       if (r != om_ok) {
-               if (r == om_not_logged_in) {
-                       cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
-               }
-               else {
-                       cprintf("%d An unknown error has occurred.\n", ERROR);
-               }
-               return;
-       }
-
-       snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename);
-       msgnum = CtdlLocateMessageByEuid(history_page_name, &CC->room);
-       if (msgnum > 0L) {
-               msg = CtdlFetchMessage(msgnum, 1);
-       }
-       else {
-               msg = NULL;
-       }
-
-       if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
-               CM_Free(msg);
-               msg = NULL;
-       }
-
-       if (msg == NULL) {
-               cprintf("%d Revision history for '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
-               return;
-       }
-
-       
-       cprintf("%d Revision history for '%s'\n", LISTING_FOLLOWS, pagename);
-       mime_parser(CM_RANGE(msg, eMesageText), *wiki_history_callback, NULL, NULL, NULL, 0);
-       cprintf("000\n");
-
-       CM_Free(msg);
-       return;
-}
-
-/*
- * MIME Parser callback for wiki_rev()
- *
- * The "filename" field will contain a memo field, which includes (among other things)
- * the uuid of this revision.  After we hit the desired revision, we stop processing.
- *
- * The "content" filed will contain "diff" output suitable for applying via "patch"
- * to our temporary file.
- */
-void wiki_rev_callback(char *name, char *filename, char *partnum, char *disp,
-                  void *content, char *cbtype, char *cbcharset, size_t length,
-                  char *encoding, char *cbid, void *cbuserdata)
-{
-       struct HistoryEraserCallBackData *hecbd = (struct HistoryEraserCallBackData *)cbuserdata;
-       char memo[1024];
-       char this_rev[256];
-       FILE *fp;
-       char *ptr = NULL;
-       char buf[1024];
-
-       /* Did a previous callback already indicate that we've reached our target uuid?
-        * If so, don't process anything else.
-        */
-       if (hecbd->done) {
-               return;
-       }
-
-       CtdlDecodeBase64(memo, filename, strlen(filename));
-       extract_token(this_rev, memo, 0, '|', sizeof this_rev);
-       striplt(this_rev);
-
-       /* Perform the patch */
-       fp = popen(PATCH " -f -s -p0 -r /dev/null >/dev/null 2>/dev/null", "w");
-       if (fp) {
-               /* Replace the filenames in the patch with the tempfilename we're actually tweaking */
-               fprintf(fp, "--- %s\n", hecbd->tempfilename);
-               fprintf(fp, "+++ %s\n", hecbd->tempfilename);
-
-               ptr = (char *)content;
-               int linenum = 0;
-               do {
-                       ++linenum;
-                       ptr = memreadline(ptr, buf, sizeof buf);
-                       if (*ptr != 0) {
-                               if (linenum <= 2) {
-                                       /* skip the first two lines; they contain bogus filenames */
-                               }
-                               else {
-                                       fprintf(fp, "%s\n", buf);
-                               }
-                       }
-               } while ((*ptr != 0) && (ptr < ((char*)content + length)));
-               if (pclose(fp) != 0) {
-                       syslog(LOG_ERR, "pclose() returned an error - patch failed\n");
-               }
-       }
-
-       if (!strcasecmp(this_rev, hecbd->stop_when)) {
-               /* Found our target rev.  Tell any subsequent callbacks to suppress processing. */
-               syslog(LOG_DEBUG, "Target revision has been reached -- stop patching.\n");
-               hecbd->done = 1;
-       }
-}
-
-
-/*
- * Fetch a specific revision of a wiki page.  The "operation" string may be set to "fetch" in order
- * to simply fetch the desired revision and store it in a temporary location for viewing, or "revert"
- * to revert the currently active page to that revision.
- */
-void wiki_rev(char *pagename, char *rev, char *operation)
-{
-       int r;
-       char history_page_name[270];
-       long msgnum;
-       char temp[PATH_MAX];
-       struct CtdlMessage *msg;
-       FILE *fp;
-       struct HistoryEraserCallBackData hecbd;
-       long len = 0L;
-       int rv;
-
-       r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
-       if (r != om_ok) {
-               if (r == om_not_logged_in) {
-                       cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
-               }
-               else {
-                       cprintf("%d An unknown error has occurred.\n", ERROR);
-               }
-               return;
-       }
-
-       if (!strcasecmp(operation, "revert")) {
-               r = CtdlDoIHavePermissionToPostInThisRoom(temp, sizeof temp, NULL, POST_LOGGED_IN, 0);
-               if (r != 0) {
-                       cprintf("%d %s\n", r, temp);
-                       return;
-               }
-       }
-
-       /* Begin by fetching the current version of the page.  We're going to patch
-        * backwards through the diffs until we get the one we want.
-        */
-       msgnum = CtdlLocateMessageByEuid(pagename, &CC->room);
-       if (msgnum > 0L) {
-               msg = CtdlFetchMessage(msgnum, 1);
-       }
-       else {
-               msg = NULL;
-       }
-
-       if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
-               CM_Free(msg);
-               msg = NULL;
-       }
-
-       if (msg == NULL) {
-               cprintf("%d Page '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
-               return;
-       }
-
-       /* Output it to a temporary file */
-
-       CtdlMakeTempFileName(temp, sizeof temp);
-       fp = fopen(temp, "w");
-       if (fp != NULL) {
-               r = fwrite(msg->cm_fields[eMesageText], msg->cm_lengths[eMesageText], 1, fp);
-               fclose(fp);
-       }
-       else {
-               syslog(LOG_ERR, "%s: %m", temp);
-       }
-       CM_Free(msg);
-
-       /* Get the revision history */
-
-       snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename);
-       msgnum = CtdlLocateMessageByEuid(history_page_name, &CC->room);
-       if (msgnum > 0L) {
-               msg = CtdlFetchMessage(msgnum, 1);
-       }
-       else {
-               msg = NULL;
-       }
-
-       if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
-               CM_Free(msg);
-               msg = NULL;
-       }
-
-       if (msg == NULL) {
-               cprintf("%d Revision history for '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
-               return;
-       }
-
-       /* Start patching backwards (newest to oldest) through the revision history until we
-        * hit the revision uuid requested by the user.  (The callback will perform each one.)
-        */
-
-       memset(&hecbd, 0, sizeof(struct HistoryEraserCallBackData));
-       hecbd.tempfilename = temp;
-       hecbd.stop_when = rev;
-       striplt(hecbd.stop_when);
-
-       mime_parser(CM_RANGE(msg, eMesageText), *wiki_rev_callback, NULL, NULL, (void *)&hecbd, 0);
-       CM_Free(msg);
-
-       /* Were we successful? */
-       if (hecbd.done == 0) {
-               cprintf("%d Revision '%s' of page '%s' was not found.\n",
-                       ERROR + MESSAGE_NOT_FOUND, rev, pagename
-               );
-       }
-
-       /* We have the desired revision on disk.  Now do something with it. */
-
-       else if ( (!strcasecmp(operation, "fetch")) || (!strcasecmp(operation, "revert")) ) {
-               msg = malloc(sizeof(struct CtdlMessage));
-               memset(msg, 0, sizeof(struct CtdlMessage));
-               msg->cm_magic = CTDLMESSAGE_MAGIC;
-               msg->cm_anon_type = MES_NORMAL;
-               msg->cm_format_type = FMT_RFC822;
-               fp = fopen(temp, "r");
-               if (fp) {
-                       char *msgbuf;
-                       fseek(fp, 0L, SEEK_END);
-                       len = ftell(fp);
-                       fseek(fp, 0L, SEEK_SET);
-                       msgbuf = malloc(len + 1);
-                       rv = fread(msgbuf, len, 1, fp);
-                       syslog(LOG_DEBUG, "did %d blocks of %ld bytes\n", rv, len);
-                       msgbuf[len] = '\0';
-                       CM_SetAsField(msg, eMesageText, &msgbuf, len);
-                       fclose(fp);
-               }
-               if (len <= 0) {
-                       msgnum = (-1L);
-               }
-               else if (!strcasecmp(operation, "fetch")) {
-                       CM_SetField(msg, eAuthor, HKEY("Citadel"));
-                       CtdlCreateRoom(wwm, 5, "", 0, 1, 1, VIEW_BBS);  /* Not an error if already exists */
-                       msgnum = CtdlSubmitMsg(msg, NULL, wwm);         /* Store the revision here */
-
-                       /*
-                        * WARNING: VILE SLEAZY HACK
-                        * This will avoid the 'message xxx is not in this room' security error,
-                        * but only if the client fetches the message we just generated immediately
-                        * without first trying to perform other fetch operations.
-                        */
-                       if (CC->cached_msglist != NULL) {
-                               free(CC->cached_msglist);
-                               CC->cached_msglist = NULL;
-                               CC->cached_num_msgs = 0;
-                       }
-                       CC->cached_msglist = malloc(sizeof(long));
-                       if (CC->cached_msglist != NULL) {
-                               CC->cached_num_msgs = 1;
-                               CC->cached_msglist[0] = msgnum;
-                       }
-
-               }
-               else if (!strcasecmp(operation, "revert")) {
-                       CM_SetFieldLONG(msg, eTimestamp, time(NULL));
-                       if (!IsEmptyStr(CC->user.fullname)) {
-                               CM_SetField(msg, eAuthor, CC->user.fullname, strlen(CC->user.fullname));
-                       }
-
-                       if (!IsEmptyStr(CC->cs_inet_email)) {
-                               CM_SetField(msg, erFc822Addr, CC->cs_inet_email, strlen(CC->cs_inet_email));
-                       }
-
-                       if (!IsEmptyStr(CC->room.QRname)) {
-                               CM_SetField(msg, eOriginalRoom, CC->room.QRname, strlen(CC->room.QRname));
-                       }
-
-                       if (!IsEmptyStr(pagename)) {
-                               CM_SetField(msg, eExclusiveID, pagename, strlen(pagename));
-                       }
-                       msgnum = CtdlSubmitMsg(msg, NULL, "");          /* Replace the current revision */
-               }
-               else {
-                       /* Theoretically it is impossible to get here, but throw an error anyway */
-                       msgnum = (-1L);
-               }
-               CM_Free(msg);
-               if (msgnum >= 0L) {
-                       cprintf("%d %ld\n", CIT_OK, msgnum);            /* Give the client a msgnum */
-               }
-               else {
-                       cprintf("%d Error %ld has occurred.\n", ERROR+INTERNAL_ERROR, msgnum);
-               }
-       }
-
-       /* We did all this work for nothing.  Express anguish to the caller. */
-       else {
-               cprintf("%d An unknown operation was requested.\n", ERROR+CMD_NOT_SUPPORTED);
-       }
-
-       unlink(temp);
-       return;
-}
-
-
-
-/*
- * commands related to wiki management
- */
-void cmd_wiki(char *argbuf) {
-       char subcmd[32];
-       char pagename[256];
-       char rev[128];
-       char operation[16];
-
-       extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
-
-       if (!strcasecmp(subcmd, "history")) {
-               extract_token(pagename, argbuf, 1, '|', sizeof pagename);
-               wiki_history(pagename);
-               return;
-       }
-
-       if (!strcasecmp(subcmd, "rev")) {
-               extract_token(pagename, argbuf, 1, '|', sizeof pagename);
-               extract_token(rev, argbuf, 2, '|', sizeof rev);
-               extract_token(operation, argbuf, 3, '|', sizeof operation);
-               wiki_rev(pagename, rev, operation);
-               return;
-       }
-
-       cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
-}
-
-
-
-/*
- * Module initialization
- */
-CTDL_MODULE_INIT(wiki)
-{
-       if (!threading)
-       {
-               CtdlRegisterMessageHook(wiki_upload_beforesave, EVT_BEFORESAVE);
-               CtdlRegisterProtoHook(cmd_wiki, "WIKI", "Commands related to Wiki management");
-       }
-
-       /* return our module name for the log */
-       return "wiki";
-}
diff --git a/citadel/modules/xmpp/.gitignore b/citadel/modules/xmpp/.gitignore
deleted file mode 100644 (file)
index 5761abc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.o
diff --git a/citadel/modules/xmpp/serv_xmpp.c b/citadel/modules/xmpp/serv_xmpp.c
deleted file mode 100644 (file)
index dfb8952..0000000
+++ /dev/null
@@ -1,674 +0,0 @@
-/*
- * XMPP (Jabber) service for the Citadel system
- * Copyright (c) 2007-2021 by Art Cancro and citadel.org
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <libcitadel.h>
-#include <expat.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-/* uncomment for more verbosity - it will log all received XML tags */
-// #define XMPP_XML_DEBUG
-
-/* XML_StopParser is present in expat 2.x */
-#if XML_MAJOR_VERSION > 1
-#define HAVE_XML_STOPPARSER
-#endif
-
-struct xmpp_event *xmpp_queue = NULL;
-
-#ifdef HAVE_XML_STOPPARSER
-/* Stop the parser if an entity declaration is hit. */
-static void xmpp_entity_declaration(void *userData, const XML_Char *entityName,
-                               int is_parameter_entity, const XML_Char *value,
-                               int value_length, const XML_Char *base,
-                               const XML_Char *systemId, const XML_Char *publicId,
-                               const XML_Char *notationName
-) {
-       syslog(LOG_WARNING, "xmpp: illegal entity declaration encountered; stopping parser.");
-       XML_StopParser(XMPP->xp, XML_FALSE);
-}
-#endif
-
-
-/*
- * support function for xmlesc() which helps with UTF-8 strings
- */
-static inline int Ctdl_GetUtf8SequenceLength(const char *CharS, const char *CharE)
-{
-       int n = 0;
-        unsigned char test = (1<<7);
-
-       if ((*CharS & 0xC0) != 0xC0) {
-               return 1;
-       }
-
-       while ((n < 8) && ((test & ((unsigned char)*CharS)) != 0))  {
-               test = test >> 1;
-               n ++;
-       }
-       if ((n > 6) || ((CharE - CharS) < n)) {
-               n = 0;
-       }
-       return n;
-}
-
-
-/*
- * Given a source string and a target buffer, returns the string
- * properly escaped for insertion into an XML stream.  Returns a
- * pointer to the target buffer for convenience.
- */
-char *xmlesc(char *buf, char *str, int bufsiz)
-{
-       int IsUtf8Sequence;
-       char *ptr, *pche;
-       unsigned char ch;
-       int inlen;
-       int len = 0;
-
-       if (!buf) return(NULL);
-       buf[0] = 0;
-       len = 0;
-       if (!str) {
-               return(buf);
-       }
-       inlen = strlen(str);
-       pche = str + inlen;
-
-       for (ptr=str; *ptr; ptr++) {
-               ch = *ptr;
-               if (ch == '<') {
-                       strcpy(&buf[len], "&lt;");
-                       len += 4;
-               }
-               else if (ch == '>') {
-                       strcpy(&buf[len], "&gt;");
-                       len += 4;
-               }
-               else if (ch == '&') {
-                       strcpy(&buf[len], "&amp;");
-                       len += 5;
-               }
-               else if ((ch >= 0x20) && (ch <= 0x7F)) {
-                       buf[len++] = ch;
-                       buf[len] = 0;
-               }
-               else if (ch < 0x20) {
-                       buf[len++] = '_';       // we probably shouldn't be doing this
-                       buf[len] = 0;
-               }
-               else {
-                       IsUtf8Sequence =  Ctdl_GetUtf8SequenceLength(ptr, pche);
-                       if (IsUtf8Sequence)
-                       {
-                               while ((IsUtf8Sequence > 0) && 
-                                      (ptr < pche))
-                               {
-                                       buf[len] = *ptr;
-                                       ptr ++;
-                                       --IsUtf8Sequence;
-                               }
-                       }
-                       else
-                       {
-                               char oct[10];
-                               sprintf(oct, "&#%o;", ch);
-                               strcpy(&buf[len], oct);
-                               len += strlen(oct);
-                       }
-               }
-               if ((len + 6) > bufsiz) {
-                       return(buf);
-               }
-       }
-       return(buf);
-}
-
-
-/*
- * We have just received a <stream> tag from the client, so send them ours
- */
-void xmpp_stream_start(void *data, const char *supplied_el, const char **attr)
-{
-       char xmlbuf[256];
-
-       while (*attr) {
-               if (!strcasecmp(attr[0], "to")) {
-                       safestrncpy(XMPP->server_name, attr[1], sizeof XMPP->server_name);
-               }
-               attr += 2;
-       }
-
-       cprintf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
-       cprintf("<stream:stream ");
-       cprintf("from=\"%s\" ", xmlesc(xmlbuf, XMPP->server_name, sizeof xmlbuf));
-       cprintf("id=\"%08x\" ", CC->cs_pid);
-       cprintf("version=\"1.0\" ");
-       cprintf("xmlns:stream=\"http://etherx.jabber.org/streams\" ");
-       cprintf("xmlns=\"jabber:client\">");
-
-       /* The features of this stream are... */
-       cprintf("<stream:features>");
-
-       /*
-        * TLS encryption (but only if it isn't already active)
-        */ 
-#ifdef HAVE_OPENSSL
-       if (!CC->redirect_ssl) {
-               cprintf("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>");
-       }
-#endif
-
-       if (!CC->logged_in) {
-               /* If we're not logged in yet, offer SASL as our feature set */
-               xmpp_output_auth_mechs();
-
-               /* Also offer non-SASL authentication */
-               cprintf("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>");
-       }
-
-       /* Offer binding and sessions as part of our feature set */
-       cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>");
-       cprintf("<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>");
-       cprintf("</stream:features>");
-
-       CC->is_async = 1;               /* XMPP sessions are inherently async-capable */
-}
-
-
-void xmpp_xml_start(void *data, const char *supplied_el, const char **attr) {
-       char el[256];
-       char *sep = NULL;
-       int i;
-
-       /* Create a version of the element with the namespace removed.
-        * Now we can access "el" or "supplied_el" depending on whether we want to see the whole namespace.
-        */
-       safestrncpy(el, supplied_el, sizeof el);
-       while (sep = strchr(el, ':'), sep) {
-               strcpy(el, ++sep);
-       }
-
-#ifdef XMPP_XML_DEBUG
-       syslog(LOG_DEBUG, "xmpp: ELEMENT START: <%s>", el);
-       for (i=0; attr[i] != NULL; i+=2) {
-               syslog(LOG_DEBUG, "xmpp: Attribute '%s' = '%s'", attr[i], attr[i+1]);
-       }
-#endif
-
-       if (!strcasecmp(el, "stream")) {
-               xmpp_stream_start(data, supplied_el, attr);
-       }
-
-       else if (!strcasecmp(el, "query")) {
-               XMPP->iq_query_xmlns[0] = 0;
-               safestrncpy(XMPP->iq_query_xmlns, supplied_el, sizeof XMPP->iq_query_xmlns);
-       }
-
-       else if (!strcasecmp(el, "bind")) {
-               XMPP->bind_requested = 1;
-       }
-
-       else if (!strcasecmp(el, "iq")) {
-               for (i=0; attr[i] != NULL; i+=2) {
-                       if (!strcasecmp(attr[i], "type")) {
-                               safestrncpy(XMPP->iq_type, attr[i+1], sizeof XMPP->iq_type);
-                       }
-                       else if (!strcasecmp(attr[i], "id")) {
-                               safestrncpy(XMPP->iq_id, attr[i+1], sizeof XMPP->iq_id);
-                       }
-                       else if (!strcasecmp(attr[i], "from")) {
-                               safestrncpy(XMPP->iq_from, attr[i+1], sizeof XMPP->iq_from);
-                       }
-                       else if (!strcasecmp(attr[i], "to")) {
-                               safestrncpy(XMPP->iq_to, attr[i+1], sizeof XMPP->iq_to);
-                       }
-               }
-       }
-
-       else if (!strcasecmp(el, "auth")) {
-               XMPP->sasl_auth_mech[0] = 0;
-               for (i=0; attr[i] != NULL; i+=2) {
-                       if (!strcasecmp(attr[i], "mechanism")) {
-                               safestrncpy(XMPP->sasl_auth_mech, attr[i+1], sizeof XMPP->sasl_auth_mech);
-                       }
-               }
-       }
-
-       else if (!strcasecmp(el, "message")) {
-               for (i=0; attr[i] != NULL; i+=2) {
-                       if (!strcasecmp(attr[i], "to")) {
-                               safestrncpy(XMPP->message_to, attr[i+1], sizeof XMPP->message_to);
-                       }
-               }
-       }
-
-       else if (!strcasecmp(el, "html")) {
-               ++XMPP->html_tag_level;
-       }
-}
-
-
-void xmpp_xml_end(void *data, const char *supplied_el) {
-       char el[256];
-       char *sep = NULL;
-       char xmlbuf[256];
-
-       /* Create a version of the element with the namespace removed.
-        * Now we can access "el" or "supplied_el" depending on whether we want to see the whole namespace.
-        */
-       safestrncpy(el, supplied_el, sizeof el);
-       while (sep = strchr(el, ':'), sep) {
-               strcpy(el, ++sep);
-       }
-
-#ifdef XMPP_XML_DEBUG
-       syslog(LOG_DEBUG, "xmpp: ELEMENT END  : <%s>", el);
-       if (XMPP->chardata_len > 0) {
-               syslog(LOG_DEBUG, "xmpp: chardata: %s", XMPP->chardata);
-       }
-#endif
-
-       if (!strcasecmp(el, "resource")) {
-               if (XMPP->chardata_len > 0) {
-                       safestrncpy(XMPP->iq_client_resource, XMPP->chardata, sizeof XMPP->iq_client_resource);
-                       striplt(XMPP->iq_client_resource);
-               }
-       }
-
-       else if (!strcasecmp(el, "username")) {         /* NON SASL ONLY */
-               if (XMPP->chardata_len > 0) {
-                       safestrncpy(XMPP->iq_client_username, XMPP->chardata, sizeof XMPP->iq_client_username);
-                       striplt(XMPP->iq_client_username);
-               }
-       }
-
-       else if (!strcasecmp(el, "password")) {         /* NON SASL ONLY */
-               if (XMPP->chardata_len > 0) {
-                       safestrncpy(XMPP->iq_client_password, XMPP->chardata, sizeof XMPP->iq_client_password);
-                       striplt(XMPP->iq_client_password);
-               }
-       }
-
-       else if (!strcasecmp(el, "iq")) {
-
-               /*
-                * iq type="get" (handle queries)
-                */
-               if (!strcasecmp(XMPP->iq_type, "get")) {
-
-                       /*
-                        * Query on a namespace
-                        */
-                       if (!IsEmptyStr(XMPP->iq_query_xmlns)) {
-                               xmpp_query_namespace(XMPP->iq_id, XMPP->iq_from, XMPP->iq_to, XMPP->iq_query_xmlns);
-                       }
-
-                       /*
-                        * ping ( http://xmpp.org/extensions/xep-0199.html )
-                        */
-                       else if (XMPP->ping_requested) {
-                               cprintf("<iq type=\"result\" ");
-                               if (!IsEmptyStr(XMPP->iq_from)) {
-                                       cprintf("to=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_from, sizeof xmlbuf));
-                               }
-                               if (!IsEmptyStr(XMPP->iq_to)) {
-                                       cprintf("from=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_to, sizeof xmlbuf));
-                               }
-                               cprintf("id=\"%s\"/>", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
-                       }
-
-                       /*
-                        * Client is requesting its own vCard
-                        * (If we make this more elaborate, move it to a separate function)
-                        */
-                       else if (XMPP->iq_vcard) {
-                               cprintf("<iq type=\"result\" id=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
-                               cprintf("to=\"%s\">", xmlesc(xmlbuf, XMPP->iq_from, sizeof xmlbuf));
-                               cprintf("<vCard xmlns=\"vcard-temp\">");
-                               cprintf("<fn>%s</fn>", xmlesc(xmlbuf, CC->user.fullname, sizeof xmlbuf));
-                               cprintf("<nickname>%s</nickname>", xmlesc(xmlbuf, CC->user.fullname, sizeof xmlbuf));
-                               cprintf("</vCard>");
-                               cprintf("</iq>");
-                       }
-
-                       /*
-                        * Unknown query ... return the XML equivalent of a blank stare
-                        */
-                       else {
-                               syslog(LOG_DEBUG, "xmpp: Unknown query <%s> - returning <service-unavailable/>", el);
-                               cprintf("<iq type=\"error\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
-                               cprintf("<error code=\"503\" type=\"cancel\">"
-                                       "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
-                                       "</error>"
-                               );
-                               cprintf("</iq>");
-                       }
-               }
-
-               /*
-                * Non SASL authentication
-                */
-               else if (
-                       (!strcasecmp(XMPP->iq_type, "set"))
-                       && (!strcasecmp(XMPP->iq_query_xmlns, "jabber:iq:auth:query"))
-               ) {
-                       xmpp_non_sasl_authenticate(XMPP->iq_id, XMPP->iq_client_username, XMPP->iq_client_password);
-               }       
-
-               /*
-                * If this <iq> stanza was a "bind" attempt, process it ...
-                */
-               else if (
-                       (XMPP->bind_requested)
-                       && (!IsEmptyStr(XMPP->iq_id))
-                       && (CC->logged_in)
-               ) {
-                       /* If the client has not specified a client resource, generate one */
-                       if (IsEmptyStr(XMPP->iq_client_resource)) {
-                               snprintf(XMPP->iq_client_resource, sizeof XMPP->iq_client_resource, "%d", CC->cs_pid);
-                       }
-
-                       /* Generate the "full JID" of the client (user@host/resource) */
-                       snprintf(XMPP->client_jid, sizeof XMPP->client_jid, "%s/%s", CC->cs_principal_id, XMPP->iq_client_resource);
-
-                       /* Tell the client what its JID is */
-                       cprintf("<iq type=\"result\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
-                       cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">");
-                       cprintf("<jid>%s</jid>", xmlesc(xmlbuf, XMPP->client_jid, sizeof xmlbuf));
-                       cprintf("</bind>");
-                       cprintf("</iq>");
-               }
-
-               else if (XMPP->iq_session) {
-                       cprintf("<iq type=\"result\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
-                       cprintf("</iq>");
-               }
-
-               else {
-                       cprintf("<iq type=\"error\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
-                       cprintf("<error>Don't know how to do '%s'!</error>", xmlesc(xmlbuf, XMPP->iq_type, sizeof xmlbuf));
-                       cprintf("</iq>");
-                       syslog(LOG_DEBUG, "XMPP: don't know how to do iq_type='%s' with iq_query_xmlns='%s'", XMPP->iq_type, XMPP->iq_query_xmlns);
-               }
-
-               /* Now clear these fields out so they don't get used by a future stanza */
-               XMPP->iq_id[0] = 0;
-               XMPP->iq_from[0] = 0;
-               XMPP->iq_to[0] = 0;
-               XMPP->iq_type[0] = 0;
-               XMPP->iq_client_resource[0] = 0;
-               XMPP->iq_session = 0;
-               XMPP->iq_vcard = 0;
-               XMPP->iq_query_xmlns[0] = 0;
-               XMPP->bind_requested = 0;
-               XMPP->ping_requested = 0;
-       }
-
-       else if (!strcasecmp(el, "auth")) {
-               /* Try to authenticate (this function is responsible for the output stanza) */
-               xmpp_sasl_auth(XMPP->sasl_auth_mech, (XMPP->chardata != NULL ? XMPP->chardata : "") );
-
-               /* Now clear these fields out so they don't get used by a future stanza */
-               XMPP->sasl_auth_mech[0] = 0;
-       }
-
-       else if (!strcasecmp(el, "session")) {
-               XMPP->iq_session = 1;
-       }
-
-       else if (!strcasecmp(supplied_el, "vcard-temp:vCard")) {
-               XMPP->iq_vcard = 1;
-       }
-
-       else if (!strcasecmp(el, "presence")) {
-               /* Respond to a <presence> update by firing back with presence information
-                * on the entire wholist.  Check this assumption, it's probably wrong.
-                */
-               xmpp_wholist_presence_dump();
-       }
-
-       else if ( (!strcasecmp(el, "body")) && (XMPP->html_tag_level == 0) ) {
-               if (XMPP->message_body != NULL) {
-                       free(XMPP->message_body);
-                       XMPP->message_body = NULL;
-               }
-               if (XMPP->chardata_len > 0) {
-                       XMPP->message_body = strdup(XMPP->chardata);
-               }
-       }
-
-       else if (!strcasecmp(el, "message")) {
-               xmpp_send_message(XMPP->message_to, XMPP->message_body);
-               XMPP->html_tag_level = 0;
-       }
-
-       else if (!strcasecmp(el, "html")) {
-               --XMPP->html_tag_level;
-       }
-
-       else if (!strcasecmp(el, "starttls")) {
-#ifdef HAVE_OPENSSL
-               cprintf("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
-               CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
-               if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;
-#else
-               cprintf("<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
-               CC->kill_me = KILLME_NO_CRYPTO;
-#endif
-       }
-
-       else if (!strcasecmp(el, "ping")) {
-               XMPP->ping_requested = 1;
-       }
-
-       else if (!strcasecmp(el, "stream")) {
-               syslog(LOG_DEBUG, "xmpp: client shut down their stream");
-               xmpp_massacre_roster();
-               cprintf("</stream>\n");
-               CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
-       }
-
-       else if (!strcasecmp(el, "query")) {
-               /* already processed , no further action needed here */
-       }
-
-       else if (!strcasecmp(el, "bind")) {
-               /* already processed , no further action needed here */
-       }
-
-       else {
-               syslog(LOG_DEBUG, "xmpp: ignoring unknown tag <%s>", el);
-       }
-
-       XMPP->chardata_len = 0;
-       if (XMPP->chardata_alloc > 0) {
-               XMPP->chardata[0] = 0;
-       }
-}
-
-
-void xmpp_xml_chardata(void *data, const XML_Char *s, int len)
-{
-       citxmpp *X = XMPP;
-
-       if (X->chardata_alloc == 0) {
-               X->chardata_alloc = SIZ;
-               X->chardata = malloc(X->chardata_alloc);
-       }
-       if ((X->chardata_len + len + 1) > X->chardata_alloc) {
-               X->chardata_alloc = X->chardata_len + len + 1024;
-               X->chardata = realloc(X->chardata, X->chardata_alloc);
-       }
-       memcpy(&X->chardata[X->chardata_len], s, len);
-       X->chardata_len += len;
-       X->chardata[X->chardata_len] = 0;
-}
-
-
-/*
- * This cleanup function blows away the temporary memory and files used by the XMPP service.
- */
-void xmpp_cleanup_function(void) {
-
-       /* Don't do this stuff if this is not a XMPP session! */
-       if (CC->h_command_function != xmpp_command_loop) return;
-
-       if (XMPP->chardata != NULL) {
-               free(XMPP->chardata);
-               XMPP->chardata = NULL;
-               XMPP->chardata_len = 0;
-               XMPP->chardata_alloc = 0;
-               if (XMPP->message_body != NULL) {
-                       free(XMPP->message_body);
-               }
-       }
-       XML_ParserFree(XMPP->xp);
-       free(XMPP);
-}
-
-
-/*
- * Here's where our XMPP session begins its happy day.
- */
-void xmpp_greeting(void) {
-       client_set_inbound_buf(4);
-       strcpy(CC->cs_clientname, "XMPP session");
-       CC->session_specific_data = malloc(sizeof(citxmpp));
-       memset(XMPP, 0, sizeof(citxmpp));
-       XMPP->last_event_processed = queue_event_seq;
-
-       /* XMPP does not use a greeting, but we still have to initialize some things. */
-
-       XMPP->xp = XML_ParserCreateNS("UTF-8", ':');
-       if (XMPP->xp == NULL) {
-               syslog(LOG_ERR, "xmpp: cannot create XML parser");
-               CC->kill_me = KILLME_XML_PARSER;
-               return;
-       }
-
-       XML_SetElementHandler(XMPP->xp, xmpp_xml_start, xmpp_xml_end);
-       XML_SetCharacterDataHandler(XMPP->xp, xmpp_xml_chardata);
-       // XML_SetUserData(XMPP->xp, something...);
-
-       /* Prevent the "billion laughs" attack against expat by disabling
-        * internal entity expansion.  With 2.x, forcibly stop the parser
-        * if an entity is declared - this is safer and a more obvious
-        * failure mode.  With older versions, simply prevent expansion
-        * of such entities. */
-#ifdef HAVE_XML_STOPPARSER
-       XML_SetEntityDeclHandler(XMPP->xp, xmpp_entity_declaration);
-#else
-       XML_SetDefaultHandler(XMPP->xp, NULL);
-#endif
-
-       CC->can_receive_im = 1;         /* This protocol is capable of receiving instant messages */
-}
-
-
-/* 
- * Main command loop for XMPP sessions.
- */
-void xmpp_command_loop(void) {
-       int rc;
-       StrBuf *stream_input = NewStrBuf();
-
-       time(&CC->lastcmd);
-       rc = client_read_random_blob(stream_input, 30);
-       if (rc > 0) {
-               XML_Parse(XMPP->xp, ChrPtr(stream_input), rc, 0);
-       }
-       else {
-               syslog(LOG_ERR, "xmpp: client disconnected: ending session.");
-               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
-       }
-       FreeStrBuf(&stream_input);
-}
-
-
-/*
- * Async loop for XMPP sessions (handles the transmission of unsolicited stanzas)
- */
-void xmpp_async_loop(void) {
-       xmpp_process_events();
-       xmpp_output_incoming_messages();
-}
-
-
-/*
- * Login hook for XMPP sessions
- */
-void xmpp_login_hook(void) {
-       xmpp_queue_event(XMPP_EVT_LOGIN, CC->cs_principal_id);
-}
-
-
-/*
- * Logout hook for XMPP sessions
- */
-void xmpp_logout_hook(void) {
-       xmpp_queue_event(XMPP_EVT_LOGOUT, CC->cs_principal_id);
-}
-
-
-const char *CitadelServiceXMPP="XMPP";
-CTDL_MODULE_INIT(xmpp)
-{
-       if (!threading) {
-               CtdlRegisterServiceHook(CtdlGetConfigInt("c_xmpp_c2s_port"),
-                                       NULL,
-                                       xmpp_greeting,
-                                       xmpp_command_loop,
-                                       xmpp_async_loop,
-                                       CitadelServiceXMPP
-               );
-               CtdlRegisterSessionHook(xmpp_cleanup_function, EVT_STOP, PRIO_STOP + 70);
-                CtdlRegisterSessionHook(xmpp_login_hook, EVT_LOGIN, PRIO_LOGIN + 90);
-                CtdlRegisterSessionHook(xmpp_logout_hook, EVT_LOGOUT, PRIO_LOGOUT + 90);
-                CtdlRegisterSessionHook(xmpp_login_hook, EVT_UNSTEALTH, PRIO_UNSTEALTH + 1);
-                CtdlRegisterSessionHook(xmpp_logout_hook, EVT_STEALTH, PRIO_STEALTH + 1);
-
-       }
-
-       /* return our module name for the log */
-       return "xmpp";
-}
diff --git a/citadel/modules/xmpp/serv_xmpp.h b/citadel/modules/xmpp/serv_xmpp.h
deleted file mode 100644 (file)
index c491427..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (c) 2007-2019 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-typedef struct _citxmpp {              /* Information about the current session */
-       XML_Parser xp;                  /* XML parser instance for incoming client stream */
-       char server_name[256];          /* who they think we are */
-       char *chardata;
-       int chardata_len;
-       int chardata_alloc;
-       char client_jid[256];           /* "full JID" of the client */
-       int last_event_processed;
-
-       char iq_type[256];              /* for <iq> stanzas */
-       char iq_id[256];
-       char iq_from[256];
-       char iq_to[256];
-       char iq_client_username[256];   /* username requested by the client (NON SASL ONLY) */
-       char iq_client_password[256];   /* password requested by the client (NON SASL ONLY) */
-       char iq_client_resource[256];   /* resource name requested by the client */
-       int iq_session;                 /* nonzero == client is requesting a session */
-       int iq_vcard;                   /* nonzero == client is requesting its vCard */
-       char iq_query_xmlns[256];       /* Namespace of <query> */
-
-       char sasl_auth_mech[32];        /* SASL auth mechanism requested by the client */
-
-       char message_to[256];
-       char *message_body;             /* Message body in transit */
-       int html_tag_level;             /* <html> tag nesting level */
-
-       int bind_requested;             /* In this stanza, client is asking server to bind a resource. */
-       int ping_requested;             /* In this stanza, client is pinging the server. */
-} citxmpp;
-
-#define XMPP ((citxmpp *)CC->session_specific_data)
-
-struct xmpp_event {
-       struct xmpp_event *next;
-       int event_seq;
-       time_t event_time;
-       int event_type;
-       char event_jid[256];
-       int session_which_generated_this_event;
-};
-
-extern struct xmpp_event *xmpp_queue;
-extern int queue_event_seq;
-
-enum {
-       XMPP_EVT_LOGIN,
-       XMPP_EVT_LOGOUT
-};
-
-void xmpp_cleanup_function(void);
-void xmpp_greeting(void);
-void xmpp_command_loop(void);
-void xmpp_async_loop(void);
-void xmpp_sasl_auth(char *, char *);
-void xmpp_output_auth_mechs(void);
-void xmpp_query_namespace(char *, char *, char *, char *);
-void xmpp_wholist_presence_dump(void);
-void xmpp_output_incoming_messages(void);
-void xmpp_queue_event(int, char *);
-void xmpp_process_events(void);
-void xmpp_presence_notify(char *, int);
-void xmpp_roster_item(struct CitContext *);
-void xmpp_send_message(char *, char *);
-void xmpp_non_sasl_authenticate(char *, char *, char *);
-void xmpp_massacre_roster(void);
-void xmpp_delete_old_buddies_who_no_longer_exist_from_the_client_roster(void);
-int xmpp_is_visible(struct CitContext *from, struct CitContext *to_whom);
-char *xmlesc(char *buf, char *str, int bufsiz);
diff --git a/citadel/modules/xmpp/xmpp_messages.c b/citadel/modules/xmpp/xmpp_messages.c
deleted file mode 100644 (file)
index ca8d439..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Handle messages sent and received using XMPP (Jabber) protocol
- *
- * Copyright (c) 2007-2010 by Art Cancro
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "internet_addressing.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-
-/*
- * This function is called by the XMPP service's async loop.
- * If the client session has instant messages waiting, it outputs
- * unsolicited XML stanzas containing them.
- */
-void xmpp_output_incoming_messages(void) {
-
-       struct ExpressMessage *ptr;
-       char xmlbuf1[4096];
-       char xmlbuf2[4096];
-
-       while (CC->FirstExpressMessage != NULL) {
-
-               begin_critical_section(S_SESSION_TABLE);
-               ptr = CC->FirstExpressMessage;
-               CC->FirstExpressMessage = CC->FirstExpressMessage->next;
-               end_critical_section(S_SESSION_TABLE);
-
-               cprintf("<message to=\"%s\" from=\"%s\" type=\"chat\">",
-                       xmlesc(xmlbuf1, XMPP->client_jid, sizeof xmlbuf1),
-                       xmlesc(xmlbuf2, ptr->sender_email, sizeof xmlbuf2)
-               );
-               if (ptr->text != NULL) {
-                       striplt(ptr->text);
-                       cprintf("<body>%s</body>", xmlesc(xmlbuf1, ptr->text, sizeof xmlbuf1));
-                       free(ptr->text);
-               }
-               cprintf("</message>");
-               free(ptr);
-       }
-}
-
-
-/*
- * Client is sending a message.
- */
-void xmpp_send_message(char *message_to, char *message_body) {
-       char *recp = NULL;
-       struct CitContext *cptr;
-
-       if (message_body == NULL) return;
-       if (message_to == NULL) return;
-       if (IsEmptyStr(message_to)) return;
-       if (!CC->logged_in) return;
-
-       for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
-               if (    (cptr->logged_in)
-                       && (cptr->can_receive_im)
-                       && (!strcasecmp(cptr->cs_principal_id, message_to))
-               ) {
-                       recp = cptr->user.fullname;
-               }
-       }
-
-       if (recp) {
-               PerformXmsgHooks(CC->user.fullname, CC->cs_principal_id, recp, message_body);
-       }
-
-       free(XMPP->message_body);
-       XMPP->message_body = NULL;
-       XMPP->message_to[0] = 0;
-       time(&CC->lastidle);
-}
-
diff --git a/citadel/modules/xmpp/xmpp_presence.c b/citadel/modules/xmpp/xmpp_presence.c
deleted file mode 100644 (file)
index 00fb4e3..0000000
+++ /dev/null
@@ -1,377 +0,0 @@
-/*
- * Handle XMPP presence exchanges
- *
- * Copyright (c) 2007-2021 by Art Cancro and citadel.org
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <assert.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "internet_addressing.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-
-/* 
- * Indicate the presence of another user to the client
- * (used in several places)
- */
-void xmpp_indicate_presence(char *presence_jid) {
-       char xmlbuf[256];
-
-       syslog(LOG_DEBUG, "xmpp: indicating presence of <%s> to <%s>", presence_jid, XMPP->client_jid);
-       cprintf("<presence from=\"%s\" ", xmlesc(xmlbuf, presence_jid, sizeof xmlbuf));
-       cprintf("to=\"%s\"></presence>", xmlesc(xmlbuf, XMPP->client_jid, sizeof xmlbuf));
-}
-
-
-/*
- * Convenience function to determine whether any given session is 'visible' to any other given session,
- * and is capable of receiving instant messages from that session.
- */
-int xmpp_is_visible(struct CitContext *cptr, struct CitContext *to_whom) {
-       int aide = (to_whom->user.axlevel >= AxAideU);
-
-       if (    (cptr->logged_in)
-               &&      (((cptr->cs_flags&CS_STEALTH)==0) || (aide))    /* aides see everyone */
-               &&      (cptr->user.usernum != to_whom->user.usernum)   /* don't show myself */
-               &&      (cptr->can_receive_im)                          /* IM-capable session */
-       ) {
-               return(1);
-       }
-       else {
-               return(0);
-       }
-}
-
-
-/* 
- * Initial dump of the entire wholist
- */
-void xmpp_wholist_presence_dump(void) {
-       struct CitContext *cptr = NULL;
-       int nContexts, i;
-       
-       cptr = CtdlGetContextArray(&nContexts);
-       if (!cptr) {
-               return;
-       }
-
-       for (i=0; i<nContexts; i++) {
-               if (xmpp_is_visible(&cptr[i], CC)) {
-                       xmpp_indicate_presence(cptr[i].cs_principal_id);
-               }
-       }
-       free(cptr);
-}
-
-
-/*
- * Function to remove a buddy subscription and delete from the roster
- * (used in several places)
- */
-void xmpp_destroy_buddy(char *presence_jid, int aggressively) {
-       static int unsolicited_id = 1;
-       char xmlbuf1[256];
-       char xmlbuf2[256];
-
-       if (!presence_jid) return;
-       if (!XMPP) return;
-       if (!XMPP->client_jid) return;
-
-       /* Transmit non-presence information */
-       cprintf("<presence type=\"unavailable\" from=\"%s\" to=\"%s\"></presence>",
-               xmlesc(xmlbuf1, presence_jid, sizeof xmlbuf1),
-               xmlesc(xmlbuf2, XMPP->client_jid, sizeof xmlbuf2)
-       );
-
-       /*
-        * Setting the "aggressively" flag also sends an "unsubscribed" presence update.
-        * We only ask for this when flushing the client side roster, because if we do it
-        * in the middle of a session when another user logs off, some clients (Jitsi) interpret
-        * it as a rejection of a subscription request.
-        */
-       if (aggressively) {
-               cprintf("<presence type=\"unsubscribed\" from=\"%s\" to=\"%s\"></presence>",
-                       xmlesc(xmlbuf1, presence_jid, sizeof xmlbuf1),
-                       xmlesc(xmlbuf2, XMPP->client_jid, sizeof xmlbuf2)
-               );
-       }
-
-       // note: we should implement xmpp_indicate_nonpresence so we can use it elsewhere
-
-       /* Do an unsolicited roster update that deletes the contact. */
-       cprintf("<iq from=\"%s\" to=\"%s\" id=\"unbuddy_%x\" type=\"result\">",
-               xmlesc(xmlbuf1, CC->cs_principal_id, sizeof xmlbuf1),
-               xmlesc(xmlbuf2, XMPP->client_jid, sizeof xmlbuf2),
-               ++unsolicited_id
-       );
-       cprintf("<query xmlns=\"jabber:iq:roster\">");
-       cprintf("<item jid=\"%s\" subscription=\"remove\">", xmlesc(xmlbuf1, presence_jid, sizeof xmlbuf1));
-       cprintf("<group>%s</group>", xmlesc(xmlbuf1, CtdlGetConfigStr("c_humannode"), sizeof xmlbuf1));
-       cprintf("</item>");
-       cprintf("</query>"
-               "</iq>"
-       );
-}
-
-
-/*
- * When a user logs in or out of the local Citadel system, notify all XMPP sessions about it.
- * THIS FUNCTION HAS A BUG IN IT THAT ENUMERATES THE SESSIONS WRONG.
- */
-void xmpp_presence_notify(char *presence_jid, int event_type) {
-       struct CitContext *cptr;
-       static int unsolicited_id = 12345;
-       int visible_sessions = 0;
-       int nContexts, i;
-       int which_cptr_is_relevant = (-1);
-
-       if (IsEmptyStr(presence_jid)) return;
-       if (CC->kill_me) return;
-
-       cptr = CtdlGetContextArray(&nContexts);
-       if (!cptr) {
-               return;
-       }
-
-       /* Count the visible sessions for this user */
-       for (i=0; i<nContexts; i++) {
-               if ( (!strcasecmp(cptr[i].cs_principal_id, presence_jid))
-                  && (xmpp_is_visible(&cptr[i], CC))
-               )  {
-                       ++visible_sessions;
-                       which_cptr_is_relevant = i;
-               }
-       }
-
-       syslog(LOG_DEBUG, "xmpp: %d sessions for <%s> are now visible to session %d", visible_sessions, presence_jid, CC->cs_pid);
-
-       if ( (event_type == XMPP_EVT_LOGIN) && (visible_sessions == 1) ) {
-
-               syslog(LOG_DEBUG, "xmpp: telling session %d that <%s> logged in", CC->cs_pid, presence_jid);
-
-               /* Do an unsolicited roster update that adds a new contact. */
-               assert(which_cptr_is_relevant >= 0);
-               cprintf("<iq id=\"unsolicited_%x\" type=\"result\">", ++unsolicited_id);
-               cprintf("<query xmlns=\"jabber:iq:roster\">");
-               xmpp_roster_item(&cptr[which_cptr_is_relevant]);
-               cprintf("</query></iq>");
-
-               /* Transmit presence information */
-               xmpp_indicate_presence(presence_jid);
-       }
-
-       if (visible_sessions == 0) {
-               syslog(LOG_DEBUG, "xmpp: telling session %d that <%s> logged out", CC->cs_pid, presence_jid);
-               xmpp_destroy_buddy(presence_jid, 0);    /* non aggressive presence update */
-       }
-
-       free(cptr);
-}
-
-
-void xmpp_fetch_mortuary_backend(long msgnum, void *userdata) {
-       HashList *mortuary = (HashList *) userdata;
-       struct CtdlMessage *msg;
-       char *ptr = NULL;
-       char *lasts = NULL;
-
-       msg = CtdlFetchMessage(msgnum, 1);
-       if (msg == NULL) {
-               return;
-       }
-
-       /* now add anyone we find into the hashlist */
-
-       /* skip past the headers */
-       ptr = strstr(msg->cm_fields[eMesageText], "\n\n");
-       if (ptr != NULL) {
-               ptr += 2;
-       }
-       else {
-               ptr = strstr(msg->cm_fields[eMesageText], "\n\r\n");
-               if (ptr != NULL) {
-                       ptr += 3;
-               }
-       }
-
-       /* the remaining lines are addresses */
-       if (ptr != NULL) {
-               ptr = strtok_r(ptr, "\n", &lasts);
-               while (ptr != NULL) {
-                       char *pch = strdup(ptr);
-                       Put(mortuary, pch, strlen(pch), pch, NULL);
-                       ptr = strtok_r(NULL, "\n", &lasts);
-               }
-       }
-
-       CM_Free(msg);
-}
-
-
-/*
- * Fetch the "mortuary" - a list of dead buddies which we keep around forever
- * so we can remove them from any client's roster that still has them listed
- */
-HashList *xmpp_fetch_mortuary(void) {
-       HashList *mortuary = NewHash(1, NULL);
-       if (!mortuary) {
-               syslog(LOG_ALERT, "xmpp: NewHash() failed!");
-               return(NULL);
-       }
-
-        if (CtdlGetRoom(&CC->room, USERCONFIGROOM) != 0) {
-               /* no config room exists - no further processing is required. */
-                return(mortuary);
-        }
-        CtdlForEachMessage(MSGS_LAST, 1, NULL, XMPPMORTUARY, NULL,
-                xmpp_fetch_mortuary_backend, (void *)mortuary );
-
-       return(mortuary);
-}
-
-
-/*
- * Fetch the "mortuary" - a list of dead buddies which we keep around forever
- * so we can remove them from any client's roster that still has them listed
- */
-void xmpp_store_mortuary(HashList *mortuary) {
-       HashPos *HashPos;
-       long len;
-       void *Value;
-       const char *Key;
-       StrBuf *themsg;
-
-       themsg = NewStrBuf();
-       StrBufPrintf(themsg,    "Content-type: " XMPPMORTUARY "\n"
-                               "Content-transfer-encoding: 7bit\n"
-                               "\n"
-       );
-
-       HashPos = GetNewHashPos(mortuary, 0);
-       while (GetNextHashPos(mortuary, HashPos, &len, &Key, &Value) != 0) {
-               StrBufAppendPrintf(themsg, "%s\n", (char *)Value);
-       }
-       DeleteHashPos(&HashPos);
-
-       /* Delete the old mortuary */
-       CtdlDeleteMessages(USERCONFIGROOM, NULL, 0, XMPPMORTUARY);
-
-       /* And save the new one to disk */
-       quickie_message(CC->user.fullname, NULL, NULL, USERCONFIGROOM, ChrPtr(themsg), 4, "XMPP Mortuary");
-       FreeStrBuf(&themsg);
-}
-
-
-/*
- * Upon logout we make an attempt to delete the whole roster, in order to
- * try to keep "ghost" buddies from remaining in the client-side roster.
- *
- * Since the client is probably not still alive, also remember the current
- * roster for next time so we can delete dead buddies then.
- */
-void xmpp_massacre_roster(void) {
-       struct CitContext *cptr;
-       int nContexts, i;
-       HashList *mortuary = xmpp_fetch_mortuary();
-
-       cptr = CtdlGetContextArray(&nContexts);
-       if (cptr) {
-               for (i=0; i<nContexts; i++) {
-                       if (xmpp_is_visible(&cptr[i], CC)) {
-                               if (mortuary) {
-                                       char *buddy = strdup(cptr[i].cs_principal_id);
-                                       Put(mortuary, buddy, strlen(buddy), buddy, NULL);
-                               }
-                       }
-               }
-               free (cptr);
-       }
-
-       if (mortuary) {
-               xmpp_store_mortuary(mortuary);
-               DeleteHash(&mortuary);
-       }
-}
-
-
-/*
- * Stupidly, XMPP does not specify a way to tell the client to flush its client-side roster
- * and prepare to receive a new one.  So instead we remember every buddy we've ever told the
- * client about, and push delete operations out at the beginning of a session.
- * 
- * We omit any users who happen to be online right now, but we still keep them in the mortuary,
- * which needs to be maintained as a list of every buddy the user has ever seen.  We don't know
- * when they're connecting from the same client and when they're connecting from a different client,
- * so we have no guarantee of what is in the client side roster at connect time.
- */
-void xmpp_delete_old_buddies_who_no_longer_exist_from_the_client_roster(void) {
-       long len;
-       void *Value;
-       const char *Key;
-       struct CitContext *cptr;
-       int nContexts, i;
-       int online_now = 0;
-       HashList *mortuary = xmpp_fetch_mortuary();
-       HashPos *HashPos = GetNewHashPos(mortuary, 0);
-
-       /* we need to omit anyone who is currently online */
-       cptr = CtdlGetContextArray(&nContexts);
-
-       /* go through the list of users in the mortuary... */
-       while (GetNextHashPos(mortuary, HashPos, &len, &Key, &Value) != 0)
-       {
-
-               online_now = 0;
-               if (cptr) for (i=0; i<nContexts; i++) {
-                       if (xmpp_is_visible(&cptr[i], CC)) {
-                               if (!strcasecmp(cptr[i].cs_principal_id, (char *)Value)) {
-                                       online_now = 1;
-                               }
-                       }
-               }
-
-               if (!online_now) {
-                       xmpp_destroy_buddy((char *)Value, 1);   /* aggressive presence update */
-               }
-
-       }
-       DeleteHashPos(&HashPos);
-       DeleteHash(&mortuary);
-       free(cptr);
-}
diff --git a/citadel/modules/xmpp/xmpp_query_namespace.c b/citadel/modules/xmpp/xmpp_query_namespace.c
deleted file mode 100644 (file)
index e7b01f8..0000000
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Handle <iq> <get> <query> type situations (namespace queries)
- *
- * Copyright (c) 2007-2015 by Art Cancro and citadel.org
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "internet_addressing.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-
-/*
- * Output a single roster item, for roster queries or pushes
- */
-void xmpp_roster_item(struct CitContext *cptr) {
-       char xmlbuf1[256];
-       char xmlbuf2[256];
-
-       cprintf("<item jid=\"%s\" name=\"%s\" subscription=\"both\">",
-               xmlesc(xmlbuf1, cptr->cs_principal_id, sizeof xmlbuf1),
-               xmlesc(xmlbuf2, cptr->user.fullname, sizeof xmlbuf2)
-       );
-       cprintf("<group>%s</group>", xmlesc(xmlbuf1, CtdlGetConfigStr("c_humannode"), sizeof xmlbuf1));
-       cprintf("</item>");
-}
-
-
-/* 
- * Return the results for a "jabber:iq:roster:query"
- *
- * Since we are not yet managing a roster, we simply return the entire wholist
- * (minus any entries for this user -- don't tell me about myself)
- *
- */
-void xmpp_iq_roster_query(void)
-{
-       struct CitContext *cptr;
-       int nContexts, i;
-
-       syslog(LOG_DEBUG, "xmpp: roster push!");
-       cprintf("<query xmlns=\"jabber:iq:roster\">");
-       cptr = CtdlGetContextArray(&nContexts);
-       if (cptr) {
-               for (i=0; i<nContexts; i++) {
-                       if (xmpp_is_visible(&cptr[i], CC)) {
-                               syslog(LOG_DEBUG, "xmpp: rosterizing %s", cptr[i].user.fullname);
-                               xmpp_roster_item(&cptr[i]);
-                       }
-               }
-               free (cptr);
-       }
-       cprintf("</query>");
-}
-
-
-/*
- * Client is doing a namespace query.  These are all handled differently.
- */
-void xmpp_query_namespace(char *iq_id, char *iq_from, char *iq_to, char *query_xmlns)
-{
-       int supported_namespace = 0;
-       int roster_query = 0;
-       char xmlbuf[256];
-       int reply_must_be_from_my_jid = 0;
-
-       /* We need to know before we begin the response whether this is a supported namespace, so
-        * unfortunately all supported namespaces need to be defined here *and* down below where
-        * they are handled.
-        */
-       if (
-               (!strcasecmp(query_xmlns, "jabber:iq:roster:query"))
-               || (!strcasecmp(query_xmlns, "jabber:iq:auth:query"))
-               || (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#items:query"))
-               || (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#info:query"))
-       ) {
-               supported_namespace = 1;
-       }
-
-       syslog(LOG_DEBUG, "xmpp: xmpp_query_namespace(id=%s, from=%s, to=%s, xmlns=%s)", iq_id, iq_from, iq_to, query_xmlns);
-
-       /*
-        * Beginning of query result.
-        */
-
-       if (!strcasecmp(query_xmlns, "jabber:iq:roster:query")) {
-               reply_must_be_from_my_jid = 1;
-       }
-
-       char dom[1024];                                                         // client is expecting to see the reply
-       if (reply_must_be_from_my_jid) {                                        // coming "from" the user's jid
-               safestrncpy(dom, XMPP->client_jid, sizeof(dom));
-               char *slash = strchr(dom, '/');
-               if (slash) {
-                       *slash = 0;
-               }
-       }
-       else {
-               safestrncpy(dom, XMPP->client_jid, sizeof(dom));                // client is expecting to see the reply
-               if (IsEmptyStr(dom)) {                                          // coming "from" the domain of the user's jid
-                       safestrncpy(dom, XMPP->server_name, sizeof(dom));
-               }
-               char *at = strrchr(dom, '@');
-               if (at) {
-                       strcpy(dom, ++at);
-               }
-               char *slash = strchr(dom, '/');
-               if (slash) {
-                       *slash = 0;
-               }
-       }
-
-       if (supported_namespace) {
-               cprintf("<iq type=\"result\" from=\"%s\" ", xmlesc(xmlbuf, dom, sizeof xmlbuf) );
-       }
-       else {
-               cprintf("<iq type=\"error\" from=\"%s\" ", xmlesc(xmlbuf, dom, sizeof xmlbuf) );
-       }
-       if (!IsEmptyStr(iq_from)) {
-               cprintf("to=\"%s\" ", xmlesc(xmlbuf, iq_from, sizeof xmlbuf));
-       }
-       cprintf("id=\"%s\">", xmlesc(xmlbuf, iq_id, sizeof xmlbuf));
-
-       /*
-        * Is this a query we know how to handle?
-        */
-
-       if (!strcasecmp(query_xmlns, "jabber:iq:roster:query")) {
-               roster_query = 1;
-               xmpp_iq_roster_query();
-       }
-
-       else if (!strcasecmp(query_xmlns, "jabber:iq:auth:query")) {
-               cprintf("<query xmlns=\"jabber:iq:auth\">"
-                       "<username/><password/><resource/>"
-                       "</query>"
-               );
-       }
-
-       // Extension "xep-0030" (http://xmpp.org/extensions/xep-0030.html) (return an empty set of results)
-       else if (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#items:query")) {
-               cprintf("<query xmlns=\"%s\"/>", xmlesc(xmlbuf, query_xmlns, sizeof xmlbuf));
-       }
-
-       // Extension "xep-0030" (http://xmpp.org/extensions/xep-0030.html) (return an empty set of results)
-       else if (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#info:query")) {
-               cprintf("<query xmlns=\"%s\"/>", xmlesc(xmlbuf, query_xmlns, sizeof xmlbuf));
-       }
-
-       /*
-        * If we didn't hit any known query namespaces then we should deliver a
-        * "service unavailable" error (see RFC3921 section 2.4 and 11.1.5.4)
-        */
-
-       else {
-               syslog(LOG_DEBUG, "xmpp: unknown query namespace '%s' - returning <service-unavailable/>", query_xmlns);
-               cprintf("<error code=\"503\" type=\"cancel\">"
-                       "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
-                       "</error>"
-               );
-       }
-
-       cprintf("</iq>");
-
-       /* If we told the client who is on the roster, we also need to tell the client
-        * who is *not* on the roster.  (It's down here because we can't do it in the same
-        * stanza; this will be an unsolicited push.)
-        */
-       if (roster_query) {
-               xmpp_delete_old_buddies_who_no_longer_exist_from_the_client_roster();
-       }
-}
diff --git a/citadel/modules/xmpp/xmpp_queue.c b/citadel/modules/xmpp/xmpp_queue.c
deleted file mode 100644 (file)
index e5b1112..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * XMPP event queue
- *
- * Copyright (c) 2007-2021 by Art Cancro
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "internet_addressing.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-int queue_event_seq = 0;
-
-void xmpp_queue_event(int event_type, char *email_addr) {
-
-       struct xmpp_event *xptr = NULL;
-       struct xmpp_event *new_event = NULL;
-       struct xmpp_event *last = NULL;
-       int purged_something = 0;
-       struct CitContext *cptr;
-
-       syslog(LOG_DEBUG, "xmpp: xmpp_queue_event(%d, %s)", event_type, email_addr);
-
-       /* Purge events more than a minute old */
-       begin_critical_section(S_XMPP_QUEUE);
-       do {
-               purged_something = 0;
-               if (xmpp_queue != NULL) {
-                       if ((time(NULL) - xmpp_queue->event_time) > 60) {
-                               xptr = xmpp_queue->next;
-                               free(xmpp_queue);
-                               xmpp_queue = xptr;
-                               purged_something = 1;
-                       }
-               }
-       } while(purged_something);
-       end_critical_section(S_XMPP_QUEUE);
-
-       /* Create a new event */
-       new_event = (struct xmpp_event *) malloc(sizeof(struct xmpp_event));
-       new_event->next = NULL;
-       new_event->event_time = time(NULL);
-       new_event->event_seq = ++queue_event_seq;
-       new_event->event_type = event_type;
-       new_event->session_which_generated_this_event = CC->cs_pid;
-       safestrncpy(new_event->event_jid, email_addr, sizeof new_event->event_jid);
-
-       /* Add it to the list */
-       begin_critical_section(S_XMPP_QUEUE);
-       if (xmpp_queue == NULL) {
-               xmpp_queue = new_event;
-       }
-       else {
-               for (xptr = xmpp_queue; xptr != NULL; xptr = xptr->next) {
-                       if (xptr->next == NULL) {
-                               last = xptr;
-                       }
-               }
-               last->next = new_event;
-       }
-       end_critical_section(S_XMPP_QUEUE);
-
-       /* Tell the sessions that something is happening */
-       begin_critical_section(S_SESSION_TABLE);
-       for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
-               if ((cptr->logged_in) && (cptr->h_async_function == xmpp_async_loop)) {
-                       set_async_waiting(cptr);
-               }
-       }
-       end_critical_section(S_SESSION_TABLE);
-}
-
-
-/* 
- * Are we interested in anything from the queue?  (Called in async loop)
- */
-void xmpp_process_events(void) {
-       struct xmpp_event *xptr = NULL;
-       int highest_event = 0;
-
-       for (xptr=xmpp_queue; xptr!=NULL; xptr=xptr->next) {
-               if (xptr->event_seq > XMPP->last_event_processed) {
-
-                       switch(xptr->event_type) {
-
-                               case XMPP_EVT_LOGIN:
-                               case XMPP_EVT_LOGOUT:
-                                       if (xptr->session_which_generated_this_event != CC->cs_pid) {
-                                               xmpp_presence_notify(xptr->event_jid, xptr->event_type);
-                                       }
-                                       break;
-                       }
-
-                       if (xptr->event_seq > highest_event) {
-                               highest_event = xptr->event_seq;
-                       }
-               }
-       }
-
-       XMPP->last_event_processed = highest_event;
-}
diff --git a/citadel/modules/xmpp/xmpp_sasl_service.c b/citadel/modules/xmpp/xmpp_sasl_service.c
deleted file mode 100644 (file)
index bb31dda..0000000
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Barebones SASL authentication service for XMPP (Jabber) clients.
- *
- * Note: RFC3920 says we "must" support DIGEST-MD5 but we only support PLAIN.
- *
- * Copyright (c) 2007-2019 by Art Cancro
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "internet_addressing.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-
-/*
- * PLAIN authentication.  Returns zero on success, nonzero on failure.
- */
-int xmpp_auth_plain(char *authstring)
-{
-       char decoded_authstring[1024];
-       char ident[256];
-       char user[256];
-       char pass[256];
-       int result;
-       long len;
-
-       /* Take apart the authentication string */
-       memset(pass, 0, sizeof(pass));
-
-       CtdlDecodeBase64(decoded_authstring, authstring, strlen(authstring));
-       safestrncpy(ident, decoded_authstring, sizeof ident);
-       safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
-       len = safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
-       if (len < 0) {
-               len = -len;
-       }
-
-       if (!IsEmptyStr(ident)) {
-               result = CtdlLoginExistingUser(ident);
-       }
-       else {
-               result = CtdlLoginExistingUser(user);
-       }
-
-       if (result == login_ok) {
-               if (CtdlTryPassword(pass, len) == pass_ok) {
-                       return(0);                              /* success */
-               }
-       }
-
-       return(1);                                              /* failure */
-}
-
-
-/*
- * Output the list of SASL mechanisms offered by this stream.
- */
-void xmpp_output_auth_mechs(void) {
-       cprintf("<mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
-       cprintf("<mechanism>PLAIN</mechanism>");
-       cprintf("</mechanisms>");
-}
-
-
-/*
- * Here we go ... client is trying to authenticate.
- */
-void xmpp_sasl_auth(char *sasl_auth_mech, char *authstring) {
-
-       if (strcasecmp(sasl_auth_mech, "PLAIN")) {
-               cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
-               cprintf("<invalid-mechanism/>");
-               cprintf("</failure>");
-               return;
-       }
-
-        if (CC->logged_in) {
-               CtdlUserLogout();  /* Client may try to log in twice.  Handle this. */
-       }
-
-       if (CC->nologin) {
-               cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
-               cprintf("<system-shutdown/>");
-               cprintf("</failure>");
-       }
-
-       else if (xmpp_auth_plain(authstring) == 0) {
-               cprintf("<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>");
-       }
-
-       else {
-               cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
-               cprintf("<not-authorized/>");
-               cprintf("</failure>");
-       }
-}
-
-
-/*
- * Non-SASL authentication
- */
-void xmpp_non_sasl_authenticate(char *iq_id, char *username, char *password) {
-       int result;
-       char xmlbuf[256];
-
-        if (CC->logged_in) {
-               CtdlUserLogout();  /* Client may try to log in twice.  Handle this. */
-       }
-
-       result = CtdlLoginExistingUser(username);
-       if (result == login_ok) {
-               result = CtdlTryPassword(password, strlen(password));
-               if (result == pass_ok) {
-                       cprintf("<iq type=\"result\" id=\"%s\"></iq>", xmlesc(xmlbuf, iq_id, sizeof xmlbuf));   /* success */
-                       return;
-               }
-       }
-
-       /* failure */
-       cprintf("<iq type=\"error\" id=\"%s\">", xmlesc(xmlbuf, iq_id, sizeof xmlbuf));
-       cprintf("<error code=\"401\" type=\"auth\">"
-               "<not-authorized xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
-               "</error>"
-               "</iq>"
-       );
-}
diff --git a/citadel/msgbase.c b/citadel/msgbase.c
deleted file mode 100644 (file)
index 0fbfbb6..0000000
+++ /dev/null
@@ -1,3560 +0,0 @@
-// Implements the message store.
-//
-// Copyright (c) 1987-2022 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <regex.h>
-#include <sys/stat.h>
-#include <assert.h>
-#include <libcitadel.h>
-#include "ctdl_module.h"
-#include "citserver.h"
-#include "control.h"
-#include "config.h"
-#include "clientsocket.h"
-#include "genstamp.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "internet_addressing.h"
-#include "euidindex.h"
-#include "msgbase.h"
-#include "journaling.h"
-
-struct addresses_to_be_filed *atbf = NULL;
-
-// These are the four-character field headers we use when outputting
-// messages in Citadel format (as opposed to RFC822 format).
-char *msgkeys[] = {
-       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
-       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
-       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
-       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
-       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
-       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
-       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
-       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
-       NULL, 
-       "from", // A -> eAuthor
-       NULL,   // B -> eBig_message
-       NULL,   // C (formerly used as eRemoteRoom)
-       NULL,   // D (formerly used as eDestination)
-       "exti", // E -> eXclusivID
-       "rfca", // F -> erFc822Addr
-       NULL,   // G
-       "hnod", // H (formerly used as eHumanNode)
-       "msgn", // I -> emessageId
-       "jrnl", // J -> eJournal
-       "rep2", // K -> eReplyTo
-       "list", // L -> eListID
-       "text", // M -> eMesageText
-       NULL,   // N (formerly used as eNodename)
-       "room", // O -> eOriginalRoom
-       "path", // P -> eMessagePath
-       NULL,   // Q
-       "rcpt", // R -> eRecipient
-       NULL,   // S (formerly used as eSpecialField)
-       "time", // T -> eTimestamp
-       "subj", // U -> eMsgSubject
-       "nvto", // V -> eenVelopeTo
-       "wefw", // W -> eWeferences
-       NULL,   // X
-       "cccc", // Y -> eCarbonCopY
-       NULL    // Z
-};
-
-
-HashList *msgKeyLookup = NULL;
-
-int GetFieldFromMnemonic(eMsgField *f, const char* c) {
-       void *v = NULL;
-       if (GetHash(msgKeyLookup, c, 4, &v)) {
-               *f = (eMsgField) v;
-               return 1;
-       }
-       return 0;
-}
-
-void FillMsgKeyLookupTable(void) {
-       long i;
-
-       msgKeyLookup = NewHash (1, FourHash);
-
-       for (i=0; i < 91; i++) {
-               if (msgkeys[i] != NULL) {
-                       Put(msgKeyLookup, msgkeys[i], 4, (void*)i, reference_free_handler);
-               }
-       }
-}
-
-
-eMsgField FieldOrder[]  = {
-/* Important fields */
-       emessageId   ,
-       eMessagePath ,
-       eTimestamp   ,
-       eAuthor      ,
-       erFc822Addr  ,
-       eOriginalRoom,
-       eRecipient   ,
-/* Semi-important fields */
-       eBig_message ,
-       eExclusiveID ,
-       eWeferences  ,
-       eJournal     ,
-/* G is not used yet */
-       eReplyTo     ,
-       eListID      ,
-/* Q is not used yet */
-       eenVelopeTo  ,
-/* X is not used yet */
-/* Z is not used yet */
-       eCarbonCopY  ,
-       eMsgSubject  ,
-/* internal only */
-       eErrorMsg    ,
-       eSuppressIdx ,
-       eExtnotify   ,
-/* Message text (MUST be last) */
-       eMesageText 
-/* Not saved to disk: 
-       eVltMsgNum
-*/
-};
-
-static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
-
-
-int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which) {
-       return !((Msg->cm_fields[which] != NULL) && (Msg->cm_fields[which][0] != '\0'));
-}
-
-
-void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
-       if (Msg->cm_fields[which] != NULL) {
-               free (Msg->cm_fields[which]);
-       }
-       if (length < 0) {                       // You can set the length to -1 to have CM_SetField measure it for you
-               length = strlen(buf);
-       }
-       Msg->cm_fields[which] = malloc(length + 1);
-       memcpy(Msg->cm_fields[which], buf, length);
-       Msg->cm_fields[which][length] = '\0';
-       Msg->cm_lengths[which] = length;
-}
-
-
-void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue) {
-       char buf[128];
-       long len;
-       len = snprintf(buf, sizeof(buf), "%ld", lvalue);
-       CM_SetField(Msg, which, buf, len);
-}
-
-
-void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen) {
-       if (Msg->cm_fields[WhichToCut] == NULL)
-               return;
-
-       if (Msg->cm_lengths[WhichToCut] > maxlen)
-       {
-               Msg->cm_fields[WhichToCut][maxlen] = '\0';
-               Msg->cm_lengths[WhichToCut] = maxlen;
-       }
-}
-
-
-void CM_FlushField(struct CtdlMessage *Msg, eMsgField which) {
-       if (Msg->cm_fields[which] != NULL)
-               free (Msg->cm_fields[which]);
-       Msg->cm_fields[which] = NULL;
-       Msg->cm_lengths[which] = 0;
-}
-
-
-void CM_Flush(struct CtdlMessage *Msg) {
-       int i;
-
-       if (CM_IsValidMsg(Msg) == 0) {
-               return;
-       }
-
-       for (i = 0; i < 256; ++i) {
-               CM_FlushField(Msg, i);
-       }
-}
-
-
-void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy) {
-       long len;
-       if (Msg->cm_fields[WhichToPutTo] != NULL) {
-               free (Msg->cm_fields[WhichToPutTo]);
-       }
-
-       if (Msg->cm_fields[WhichtToCopy] != NULL) {
-               len = Msg->cm_lengths[WhichtToCopy];
-               Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
-               memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len);
-               Msg->cm_fields[WhichToPutTo][len] = '\0';
-               Msg->cm_lengths[WhichToPutTo] = len;
-       }
-       else {
-               Msg->cm_fields[WhichToPutTo] = NULL;
-               Msg->cm_lengths[WhichToPutTo] = 0;
-       }
-}
-
-
-void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
-       if (Msg->cm_fields[which] != NULL) {
-               long oldmsgsize;
-               long newmsgsize;
-               char *new;
-
-               oldmsgsize = Msg->cm_lengths[which] + 1;
-               newmsgsize = length + oldmsgsize;
-
-               new = malloc(newmsgsize);
-               memcpy(new, buf, length);
-               memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
-               free(Msg->cm_fields[which]);
-               Msg->cm_fields[which] = new;
-               Msg->cm_lengths[which] = newmsgsize - 1;
-       }
-       else {
-               Msg->cm_fields[which] = malloc(length + 1);
-               memcpy(Msg->cm_fields[which], buf, length);
-               Msg->cm_fields[which][length] = '\0';
-               Msg->cm_lengths[which] = length;
-       }
-}
-
-
-void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length) {
-       if (Msg->cm_fields[which] != NULL) {
-               free (Msg->cm_fields[which]);
-       }
-
-       Msg->cm_fields[which] = *buf;
-       *buf = NULL;
-       if (length < 0) {                       // You can set the length to -1 to have CM_SetField measure it for you
-               Msg->cm_lengths[which] = strlen(Msg->cm_fields[which]);
-       }
-       else {
-               Msg->cm_lengths[which] = length;
-       }
-}
-
-
-void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf) {
-       if (Msg->cm_fields[which] != NULL) {
-               free (Msg->cm_fields[which]);
-       }
-
-       Msg->cm_lengths[which] = StrLength(*buf);
-       Msg->cm_fields[which] = SmashStrBuf(buf);
-}
-
-
-void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen) {
-       if (Msg->cm_fields[which] != NULL) {
-               *retlen = Msg->cm_lengths[which];
-               *ret = Msg->cm_fields[which];
-               Msg->cm_fields[which] = NULL;
-               Msg->cm_lengths[which] = 0;
-       }
-       else {
-               *ret = NULL;
-               *retlen = 0;
-       }
-}
-
-
-// Returns 1 if the supplied pointer points to a valid Citadel message.
-// If the pointer is NULL or the magic number check fails, returns 0.
-int CM_IsValidMsg(struct CtdlMessage *msg) {
-       if (msg == NULL) {
-               return 0;
-       }
-       if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
-               syslog(LOG_WARNING, "msgbase: CM_IsValidMsg() self-check failed");
-               return 0;
-       }
-       return 1;
-}
-
-
-void CM_FreeContents(struct CtdlMessage *msg) {
-       int i;
-
-       for (i = 0; i < 256; ++i)
-               if (msg->cm_fields[i] != NULL) {
-                       free(msg->cm_fields[i]);
-                       msg->cm_lengths[i] = 0;
-               }
-
-       msg->cm_magic = 0;      // just in case
-}
-
-
-// 'Destructor' for struct CtdlMessage
-void CM_Free(struct CtdlMessage *msg) {
-       if (CM_IsValidMsg(msg) == 0) {
-               if (msg != NULL) free (msg);
-               return;
-       }
-       CM_FreeContents(msg);
-       free(msg);
-}
-
-
-int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg) {
-       long len;
-       len = OrgMsg->cm_lengths[i];
-       NewMsg->cm_fields[i] = malloc(len + 1);
-       if (NewMsg->cm_fields[i] == NULL) {
-               return 0;
-       }
-       memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
-       NewMsg->cm_fields[i][len] = '\0';
-       NewMsg->cm_lengths[i] = len;
-       return 1;
-}
-
-
-struct CtdlMessage *CM_Duplicate(struct CtdlMessage *OrgMsg) {
-       int i;
-       struct CtdlMessage *NewMsg;
-
-       if (CM_IsValidMsg(OrgMsg) == 0) {
-               return NULL;
-       }
-       NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
-       if (NewMsg == NULL) {
-               return NULL;
-       }
-
-       memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
-
-       memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
-       
-       for (i = 0; i < 256; ++i) {
-               if (OrgMsg->cm_fields[i] != NULL) {
-                       if (!CM_DupField(i, OrgMsg, NewMsg)) {
-                               CM_Free(NewMsg);
-                               return NULL;
-                       }
-               }
-       }
-
-       return NewMsg;
-}
-
-
-// Determine if a given message matches the fields in a message template.
-// Return 0 for a successful match.
-int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
-       int i;
-
-       // If there aren't any fields in the template, all messages will match.
-       if (template == NULL) return(0);
-
-       // Null messages are bogus.
-       if (msg == NULL) return(1);
-
-       for (i='A'; i<='Z'; ++i) {
-               if (template->cm_fields[i] != NULL) {
-                       if (msg->cm_fields[i] == NULL) {
-                               // Considered equal if temmplate is empty string
-                               if (IsEmptyStr(template->cm_fields[i])) continue;
-                               return 1;
-                       }
-                       if ((template->cm_lengths[i] != msg->cm_lengths[i]) ||
-                           (strcasecmp(msg->cm_fields[i], template->cm_fields[i])))
-                               return 1;
-               }
-       }
-
-       // All compares succeeded: we have a match!
-       return 0;
-}
-
-
-// Retrieve the "seen" message list for the current room.
-void CtdlGetSeen(char *buf, int which_set) {
-       visit vbuf;
-
-       // Learn about the user and room in question
-       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
-
-       if (which_set == ctdlsetseen_seen) {
-               safestrncpy(buf, vbuf.v_seen, SIZ);
-       }
-       if (which_set == ctdlsetseen_answered) {
-               safestrncpy(buf, vbuf.v_answered, SIZ);
-       }
-}
-
-
-// Manipulate the "seen msgs" string (or other message set strings)
-void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
-               int target_setting, int which_set,
-               struct ctdluser *which_user, struct ctdlroom *which_room) {
-       struct cdbdata *cdbfr;
-       int i, k;
-       int is_seen = 0;
-       int was_seen = 0;
-       long lo = (-1L);
-       long hi = (-1L);
-       visit vbuf;
-       long *msglist;
-       int num_msgs = 0;
-       StrBuf *vset;
-       StrBuf *setstr;
-       StrBuf *lostr;
-       StrBuf *histr;
-       const char *pvset;
-       char *is_set;   // actually an array of booleans
-
-       // Don't bother doing *anything* if we were passed a list of zero messages
-       if (num_target_msgnums < 1) {
-               return;
-       }
-
-       // If no room was specified, we go with the current room.
-       if (!which_room) {
-               which_room = &CC->room;
-       }
-
-       // If no user was specified, we go with the current user.
-       if (!which_user) {
-               which_user = &CC->user;
-       }
-
-       syslog(LOG_DEBUG, "msgbase: CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>",
-                  num_target_msgnums, target_msgnums[0],
-                  (target_setting ? "SET" : "CLEAR"),
-                  which_set,
-                  which_room->QRname);
-
-       // Learn about the user and room in question
-       CtdlGetRelationship(&vbuf, which_user, which_room);
-
-       // Load the message list
-       cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
-       if (cdbfr != NULL) {
-               msglist = (long *) cdbfr->ptr;
-               cdbfr->ptr = NULL;      // CtdlSetSeen() now owns this memory
-               num_msgs = cdbfr->len / sizeof(long);
-               cdb_free(cdbfr);
-       }
-       else {
-               return; // No messages at all?  No further action.
-       }
-
-       is_set = malloc(num_msgs * sizeof(char));
-       memset(is_set, 0, (num_msgs * sizeof(char)) );
-
-       // Decide which message set we're manipulating
-       switch(which_set) {
-       case ctdlsetseen_seen:
-               vset = NewStrBufPlain(vbuf.v_seen, -1);
-               break;
-       case ctdlsetseen_answered:
-               vset = NewStrBufPlain(vbuf.v_answered, -1);
-               break;
-       default:
-               vset = NewStrBuf();
-       }
-
-
-#if 0  // This is a special diagnostic section.  Do not allow it to run during normal operation.
-       syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
-       for (i=0; i<num_msgs; ++i) {
-               if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
-       }
-       syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
-       for (k=0; k<num_target_msgnums; ++k) {
-               if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
-       }
-#endif
-
-       // Translate the existing sequence set into an array of booleans
-       setstr = NewStrBuf();
-       lostr = NewStrBuf();
-       histr = NewStrBuf();
-       pvset = NULL;
-       while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
-
-               StrBufExtract_token(lostr, setstr, 0, ':');
-               if (StrBufNum_tokens(setstr, ':') >= 2) {
-                       StrBufExtract_token(histr, setstr, 1, ':');
-               }
-               else {
-                       FlushStrBuf(histr);
-                       StrBufAppendBuf(histr, lostr, 0);
-               }
-               lo = StrTol(lostr);
-               if (!strcmp(ChrPtr(histr), "*")) {
-                       hi = LONG_MAX;
-               }
-               else {
-                       hi = StrTol(histr);
-               }
-
-               for (i = 0; i < num_msgs; ++i) {
-                       if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
-                               is_set[i] = 1;
-                       }
-               }
-       }
-       FreeStrBuf(&setstr);
-       FreeStrBuf(&lostr);
-       FreeStrBuf(&histr);
-
-       // Now translate the array of booleans back into a sequence set
-       FlushStrBuf(vset);
-       was_seen = 0;
-       lo = (-1);
-       hi = (-1);
-
-       for (i=0; i<num_msgs; ++i) {
-               is_seen = is_set[i];
-
-               // Apply changes
-               for (k=0; k<num_target_msgnums; ++k) {
-                       if (msglist[i] == target_msgnums[k]) {
-                               is_seen = target_setting;
-                       }
-               }
-
-               if ((was_seen == 0) && (is_seen == 1)) {
-                       lo = msglist[i];
-               }
-               else if ((was_seen == 1) && (is_seen == 0)) {
-                       hi = msglist[i-1];
-
-                       if (StrLength(vset) > 0) {
-                               StrBufAppendBufPlain(vset, HKEY(","), 0);
-                       }
-                       if (lo == hi) {
-                               StrBufAppendPrintf(vset, "%ld", hi);
-                       }
-                       else {
-                               StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
-                       }
-               }
-
-               if ((is_seen) && (i == num_msgs - 1)) {
-                       if (StrLength(vset) > 0) {
-                               StrBufAppendBufPlain(vset, HKEY(","), 0);
-                       }
-                       if ((i==0) || (was_seen == 0)) {
-                               StrBufAppendPrintf(vset, "%ld", msglist[i]);
-                       }
-                       else {
-                               StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
-                       }
-               }
-
-               was_seen = is_seen;
-       }
-
-       // We will have to stuff this string back into a 4096 byte buffer, so if it's
-       // larger than that now, truncate it by removing tokens from the beginning.
-       // The limit of 100 iterations is there to prevent an infinite loop in case
-       // something unexpected happens.
-       int number_of_truncations = 0;
-       while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
-               StrBufRemove_token(vset, 0, ',');
-               ++number_of_truncations;
-       }
-
-       // If we're truncating the sequence set of messages marked with the 'seen' flag,
-       // we want the earliest messages (the truncated ones) to be marked, not unmarked.
-       // Otherwise messages at the beginning will suddenly appear to be 'unseen'.
-       if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
-               StrBuf *first_tok;
-               first_tok = NewStrBuf();
-               StrBufExtract_token(first_tok, vset, 0, ',');
-               StrBufRemove_token(vset, 0, ',');
-
-               if (StrBufNum_tokens(first_tok, ':') > 1) {
-                       StrBufRemove_token(first_tok, 0, ':');
-               }
-               
-               StrBuf *new_set;
-               new_set = NewStrBuf();
-               StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
-               StrBufAppendBuf(new_set, first_tok, 0);
-               StrBufAppendBufPlain(new_set, HKEY(":"), 0);
-               StrBufAppendBuf(new_set, vset, 0);
-
-               FreeStrBuf(&vset);
-               FreeStrBuf(&first_tok);
-               vset = new_set;
-       }
-
-       // Decide which message set we're manipulating
-       switch (which_set) {
-               case ctdlsetseen_seen:
-                       safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
-                       break;
-               case ctdlsetseen_answered:
-                       safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
-                       break;
-       }
-
-       free(is_set);
-       free(msglist);
-       CtdlSetRelationship(&vbuf, which_user, which_room);
-       FreeStrBuf(&vset);
-}
-
-
-// API function to perform an operation for each qualifying message in the
-// current room.  (Returns the number of messages processed.)
-int CtdlForEachMessage(int mode, long ref, char *search_string,
-                       char *content_type,
-                       struct CtdlMessage *compare,
-                        ForEachMsgCallback CallBack,
-                       void *userdata)
-{
-       int a, i, j;
-       visit vbuf;
-       struct cdbdata *cdbfr;
-       long *msglist = NULL;
-       int num_msgs = 0;
-       int num_processed = 0;
-       long thismsg;
-       struct MetaData smi;
-       struct CtdlMessage *msg = NULL;
-       int is_seen = 0;
-       long lastold = 0L;
-       int printed_lastold = 0;
-       int num_search_msgs = 0;
-       long *search_msgs = NULL;
-       regex_t re;
-       int need_to_free_re = 0;
-       regmatch_t pm;
-
-       if ((content_type) && (!IsEmptyStr(content_type))) {
-               regcomp(&re, content_type, 0);
-               need_to_free_re = 1;
-       }
-
-       // Learn about the user and room in question
-       if (server_shutting_down) {
-               if (need_to_free_re) regfree(&re);
-               return -1;
-       }
-       CtdlGetUser(&CC->user, CC->curr_user);
-
-       if (server_shutting_down) {
-               if (need_to_free_re) regfree(&re);
-               return -1;
-       }
-       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
-
-       if (server_shutting_down) {
-               if (need_to_free_re) regfree(&re);
-               return -1;
-       }
-
-       // Load the message list
-       cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
-       if (cdbfr == NULL) {
-               if (need_to_free_re) regfree(&re);
-               return 0;       // No messages at all?  No further action.
-       }
-
-       msglist = (long *) cdbfr->ptr;
-       num_msgs = cdbfr->len / sizeof(long);
-
-       cdbfr->ptr = NULL;      // clear this so that cdb_free() doesn't free it
-       cdb_free(cdbfr);        // we own this memory now
-
-       /*
-        * Now begin the traversal.
-        */
-       if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
-
-               /* If the caller is looking for a specific MIME type, filter
-                * out all messages which are not of the type requested.
-                */
-               if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
-
-                       /* This call to GetMetaData() sits inside this loop
-                        * so that we only do the extra database read per msg
-                        * if we need to.  Doing the extra read all the time
-                        * really kills the server.  If we ever need to use
-                        * metadata for another search criterion, we need to
-                        * move the read somewhere else -- but still be smart
-                        * enough to only do the read if the caller has
-                        * specified something that will need it.
-                        */
-                       if (server_shutting_down) {
-                               if (need_to_free_re) regfree(&re);
-                               free(msglist);
-                               return -1;
-                       }
-                       GetMetaData(&smi, msglist[a]);
-
-                       /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
-                       if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
-                               msglist[a] = 0L;
-                       }
-               }
-       }
-
-       num_msgs = sort_msglist(msglist, num_msgs);
-
-       /* If a template was supplied, filter out the messages which
-        * don't match.  (This could induce some delays!)
-        */
-       if (num_msgs > 0) {
-               if (compare != NULL) {
-                       for (a = 0; a < num_msgs; ++a) {
-                               if (server_shutting_down) {
-                                       if (need_to_free_re) regfree(&re);
-                                       free(msglist);
-                                       return -1;
-                               }
-                               msg = CtdlFetchMessage(msglist[a], 1);
-                               if (msg != NULL) {
-                                       if (CtdlMsgCmp(msg, compare)) {
-                                               msglist[a] = 0L;
-                                       }
-                                       CM_Free(msg);
-                               }
-                       }
-               }
-       }
-
-       /* If a search string was specified, get a message list from
-        * the full text index and remove messages which aren't on both
-        * lists.
-        *
-        * How this works:
-        * Since the lists are sorted and strictly ascending, and the
-        * output list is guaranteed to be shorter than or equal to the
-        * input list, we overwrite the bottom of the input list.  This
-        * eliminates the need to memmove big chunks of the list over and
-        * over again.
-        */
-       if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
-
-               /* Call search module via hook mechanism.
-                * NULL means use any search function available.
-                * otherwise replace with a char * to name of search routine
-                */
-               CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
-
-               if (num_search_msgs > 0) {
-       
-                       int orig_num_msgs;
-
-                       orig_num_msgs = num_msgs;
-                       num_msgs = 0;
-                       for (i=0; i<orig_num_msgs; ++i) {
-                               for (j=0; j<num_search_msgs; ++j) {
-                                       if (msglist[i] == search_msgs[j]) {
-                                               msglist[num_msgs++] = msglist[i];
-                                       }
-                               }
-                       }
-               }
-               else {
-                       num_msgs = 0;   /* No messages qualify */
-               }
-               if (search_msgs != NULL) free(search_msgs);
-
-               /* Now that we've purged messages which don't contain the search
-                * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
-                * point on.
-                */
-               mode = MSGS_ALL;
-       }
-
-       /*
-        * Now iterate through the message list, according to the
-        * criteria supplied by the caller.
-        */
-       if (num_msgs > 0)
-               for (a = 0; a < num_msgs; ++a) {
-                       if (server_shutting_down) {
-                               if (need_to_free_re) regfree(&re);
-                               free(msglist);
-                               return num_processed;
-                       }
-                       thismsg = msglist[a];
-                       if (mode == MSGS_ALL) {
-                               is_seen = 0;
-                       }
-                       else {
-                               is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
-                               if (is_seen) lastold = thismsg;
-                       }
-                       if (
-                               (thismsg > 0L)
-                       && (
-                               (mode == MSGS_ALL)
-                               || ((mode == MSGS_OLD) && (is_seen))
-                               || ((mode == MSGS_NEW) && (!is_seen))
-                               || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
-                               || ((mode == MSGS_FIRST) && (a < ref))
-                               || ((mode == MSGS_GT) && (thismsg > ref))
-                               || ((mode == MSGS_LT) && (thismsg < ref))
-                               || ((mode == MSGS_EQ) && (thismsg == ref))
-                           )
-                           ) {
-                               if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
-                                       if (CallBack) {
-                                               CallBack(lastold, userdata);
-                                       }
-                                       printed_lastold = 1;
-                                       ++num_processed;
-                               }
-                               if (CallBack) {
-                                       CallBack(thismsg, userdata);
-                               }
-                               ++num_processed;
-                       }
-               }
-       if (need_to_free_re) regfree(&re);
-
-       /*
-        * We cache the most recent msglist in order to do security checks later
-        */
-       if (CC->client_socket > 0) {
-               if (CC->cached_msglist != NULL) {
-                       free(CC->cached_msglist);
-               }
-               CC->cached_msglist = msglist;
-               CC->cached_num_msgs = num_msgs;
-       }
-       else {
-               free(msglist);
-       }
-
-       return num_processed;
-}
-
-
-/*
- * memfmout()  -  Citadel text formatter and paginator.
- *          Although the original purpose of this routine was to format
- *          text to the reader's screen width, all we're really using it
- *          for here is to format text out to 80 columns before sending it
- *          to the client.  The client software may reformat it again.
- */
-void memfmout(
-       char *mptr,             /* where are we going to get our text from? */
-       const char *nl          /* string to terminate lines with */
-) {
-       int column = 0;
-       unsigned char ch = 0;
-       char outbuf[1024];
-       int len = 0;
-       int nllen = 0;
-
-       if (!mptr) return;
-       nllen = strlen(nl);
-       while (ch=*(mptr++), ch != 0) {
-
-               if (ch == '\n') {
-                       if (client_write(outbuf, len) == -1) {
-                               syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
-                               return;
-                       }
-                       len = 0;
-                       if (client_write(nl, nllen) == -1) {
-                               syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
-                               return;
-                       }
-                       column = 0;
-               }
-               else if (ch == '\r') {
-                       /* Ignore carriage returns.  Newlines are always LF or CRLF but never CR. */
-               }
-               else if (isspace(ch)) {
-                       if (column > 72) {              /* Beyond 72 columns, break on the next space */
-                               if (client_write(outbuf, len) == -1) {
-                                       syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
-                                       return;
-                               }
-                               len = 0;
-                               if (client_write(nl, nllen) == -1) {
-                                       syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
-                                       return;
-                               }
-                               column = 0;
-                       }
-                       else {
-                               outbuf[len++] = ch;
-                               ++column;
-                       }
-               }
-               else {
-                       outbuf[len++] = ch;
-                       ++column;
-                       if (column > 1000) {            /* Beyond 1000 columns, break anywhere */
-                               if (client_write(outbuf, len) == -1) {
-                                       syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
-                                       return;
-                               }
-                               len = 0;
-                               if (client_write(nl, nllen) == -1) {
-                                       syslog(LOG_ERR, "msgbase: memfmout(): aborting due to write failure");
-                                       return;
-                               }
-                               column = 0;
-                       }
-               }
-       }
-       if (len) {
-               if (client_write(outbuf, len) == -1) {
-                       syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
-                       return;
-               }
-               client_write(nl, nllen);
-               column = 0;
-       }
-}
-
-
-/*
- * Callback function for mime parser that simply lists the part
- */
-void list_this_part(char *name, char *filename, char *partnum, char *disp,
-                   void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
-                   char *cbid, void *cbuserdata)
-{
-       struct ma_info *ma;
-       
-       ma = (struct ma_info *)cbuserdata;
-       if (ma->is_ma == 0) {
-               cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
-                       name, 
-                       filename, 
-                       partnum, 
-                       disp, 
-                       cbtype, 
-                       (long)length, 
-                       cbid, 
-                       cbcharset);
-       }
-}
-
-
-/* 
- * Callback function for multipart prefix
- */
-void list_this_pref(char *name, char *filename, char *partnum, char *disp,
-                   void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
-                   char *cbid, void *cbuserdata)
-{
-       struct ma_info *ma;
-       
-       ma = (struct ma_info *)cbuserdata;
-       if (!strcasecmp(cbtype, "multipart/alternative")) {
-               ++ma->is_ma;
-       }
-
-       if (ma->is_ma == 0) {
-               cprintf("pref=%s|%s\n", partnum, cbtype);
-       }
-}
-
-
-/* 
- * Callback function for multipart sufffix
- */
-void list_this_suff(char *name, char *filename, char *partnum, char *disp,
-                   void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
-                   char *cbid, void *cbuserdata)
-{
-       struct ma_info *ma;
-       
-       ma = (struct ma_info *)cbuserdata;
-       if (ma->is_ma == 0) {
-               cprintf("suff=%s|%s\n", partnum, cbtype);
-       }
-       if (!strcasecmp(cbtype, "multipart/alternative")) {
-               --ma->is_ma;
-       }
-}
-
-
-/*
- * Callback function for mime parser that opens a section for downloading
- * we use serv_files function here: 
- */
-extern void OpenCmdResult(char *filename, const char *mime_type);
-void mime_download(char *name, char *filename, char *partnum, char *disp,
-                  void *content, char *cbtype, char *cbcharset, size_t length,
-                  char *encoding, char *cbid, void *cbuserdata)
-{
-       int rv = 0;
-
-       /* Silently go away if there's already a download open. */
-       if (CC->download_fp != NULL)
-               return;
-
-       if (
-               (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
-       ||      (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
-       ) {
-               CC->download_fp = tmpfile();
-               if (CC->download_fp == NULL) {
-                       syslog(LOG_ERR, "msgbase: mime_download() couldn't write: %m");
-                       cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
-                       return;
-               }
-       
-               rv = fwrite(content, length, 1, CC->download_fp);
-               if (rv <= 0) {
-                       syslog(LOG_ERR, "msgbase: mime_download() Couldn't write: %m");
-                       cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG);
-                       fclose(CC->download_fp);
-                       CC->download_fp = NULL;
-                       return;
-               }
-               fflush(CC->download_fp);
-               rewind(CC->download_fp);
-       
-               OpenCmdResult(filename, cbtype);
-       }
-}
-
-
-/*
- * Callback function for mime parser that outputs a section all at once.
- * We can specify the desired section by part number *or* content-id.
- */
-void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
-                  void *content, char *cbtype, char *cbcharset, size_t length,
-                  char *encoding, char *cbid, void *cbuserdata)
-{
-       int *found_it = (int *)cbuserdata;
-
-       if (
-               (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
-       ||      (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
-       ) {
-               *found_it = 1;
-               cprintf("%d %d|-1|%s|%s|%s\n",
-                       BINARY_FOLLOWS,
-                       (int)length,
-                       filename,
-                       cbtype,
-                       cbcharset
-               );
-               client_write(content, length);
-       }
-}
-
-
-struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length) {
-       struct CtdlMessage *ret = NULL;
-       const char *mptr;
-       const char *upper_bound;
-       cit_uint8_t ch;
-       cit_uint8_t field_header;
-       eMsgField which;
-
-       mptr = Buffer;
-       upper_bound = Buffer + Length;
-       if (msgnum <= 0) {
-               return NULL;
-       }
-
-       // Parse the three bytes that begin EVERY message on disk.
-       // The first is always 0xFF, the on-disk magic number.
-       // The second is the anonymous/public type byte.
-       // The third is the format type byte (vari, fixed, or MIME).
-       //
-       ch = *mptr++;
-       if (ch != 255) {
-               syslog(LOG_ERR, "msgbase: message %ld appears to be corrupted", msgnum);
-               return NULL;
-       }
-       ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
-       memset(ret, 0, sizeof(struct CtdlMessage));
-
-       ret->cm_magic = CTDLMESSAGE_MAGIC;
-       ret->cm_anon_type = *mptr++;                            // Anon type byte
-       ret->cm_format_type = *mptr++;                          // Format type byte
-
-       // The rest is zero or more arbitrary fields.  Load them in.
-       // We're done when we encounter either a zero-length field or
-       // have just processed the 'M' (message text) field.
-       //
-       do {
-               field_header = '\0';
-               long len;
-
-               while (field_header == '\0') {                  // work around possibly buggy messages
-                       if (mptr >= upper_bound) {
-                               break;
-                       }
-                       field_header = *mptr++;
-               }
-               if (mptr >= upper_bound) {
-                       break;
-               }
-               which = field_header;
-               len = strlen(mptr);
-
-               CM_SetField(ret, which, mptr, len);
-
-               mptr += len + 1;                                // advance to next field
-
-       } while ((mptr < upper_bound) && (field_header != 'M'));
-       return (ret);
-}
-
-
-// Load a message from disk into memory.
-// This is used by CtdlOutputMsg() and other fetch functions.
-//
-// NOTE: Caller is responsible for freeing the returned CtdlMessage struct
-//       using the CM_Free(); function.
-//
-struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body) {
-       struct cdbdata *dmsgtext;
-       struct CtdlMessage *ret = NULL;
-
-       syslog(LOG_DEBUG, "msgbase: CtdlFetchMessage(%ld, %d)", msgnum, with_body);
-       dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
-       if (dmsgtext == NULL) {
-               syslog(LOG_ERR, "msgbase: message #%ld was not found", msgnum);
-               return NULL;
-       }
-
-       if (dmsgtext->ptr[dmsgtext->len - 1] != '\0') {
-               syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Forcefully terminating message!!", msgnum, with_body);
-               dmsgtext->ptr[dmsgtext->len - 1] = '\0';
-       }
-
-       ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext->ptr, dmsgtext->len);
-
-       cdb_free(dmsgtext);
-
-       if (ret == NULL) {
-               return NULL;
-       }
-
-       // Always make sure there's something in the msg text field.  If
-       // it's NULL, the message text is most likely stored separately,
-       // so go ahead and fetch that.  Failing that, just set a dummy
-       // body so other code doesn't barf.
-       //
-       if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
-               dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
-               if (dmsgtext != NULL) {
-                       CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len - 1);
-                       cdb_free(dmsgtext);
-               }
-       }
-       if (CM_IsEmpty(ret, eMesageText)) {
-               CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
-       }
-
-       return (ret);
-}
-
-
-// Pre callback function for multipart/alternative
-//
-// NOTE: this differs from the standard behavior for a reason.  Normally when
-//       displaying multipart/alternative you want to show the _last_ usable
-//       format in the message.  Here we show the _first_ one, because it's
-//       usually text/plain.  Since this set of functions is designed for text
-//       output to non-MIME-aware clients, this is the desired behavior.
-//
-void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
-               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
-               char *cbid, void *cbuserdata)
-{
-       struct ma_info *ma;
-       
-       ma = (struct ma_info *)cbuserdata;
-       syslog(LOG_DEBUG, "msgbase: fixed_output_pre() type=<%s>", cbtype);     
-       if (!strcasecmp(cbtype, "multipart/alternative")) {
-               ++ma->is_ma;
-               ma->did_print = 0;
-       }
-       if (!strcasecmp(cbtype, "message/rfc822")) {
-               ++ma->freeze;
-       }
-}
-
-
-//
-// Post callback function for multipart/alternative
-//
-void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
-               void *content, char *cbtype, char *cbcharset, size_t length,
-               char *encoding, char *cbid, void *cbuserdata)
-{
-       struct ma_info *ma;
-       
-       ma = (struct ma_info *)cbuserdata;
-       syslog(LOG_DEBUG, "msgbase: fixed_output_post() type=<%s>", cbtype);    
-       if (!strcasecmp(cbtype, "multipart/alternative")) {
-               --ma->is_ma;
-               ma->did_print = 0;
-       }
-       if (!strcasecmp(cbtype, "message/rfc822")) {
-               --ma->freeze;
-       }
-}
-
-
-// Inline callback function for mime parser that wants to display text
-void fixed_output(char *name, char *filename, char *partnum, char *disp,
-               void *content, char *cbtype, char *cbcharset, size_t length,
-               char *encoding, char *cbid, void *cbuserdata)
-{
-       char *ptr;
-       char *wptr;
-       size_t wlen;
-       struct ma_info *ma;
-
-       ma = (struct ma_info *)cbuserdata;
-
-       syslog(LOG_DEBUG,
-               "msgbase: fixed_output() part %s: %s (%s) (%ld bytes)",
-               partnum, filename, cbtype, (long)length
-       );
-
-       // If we're in the middle of a multipart/alternative scope and
-       // we've already printed another section, skip this one.
-       if ( (ma->is_ma) && (ma->did_print) ) {
-               syslog(LOG_DEBUG, "msgbase: skipping part %s (%s)", partnum, cbtype);
-               return;
-       }
-       ma->did_print = 1;
-
-       if ( (!strcasecmp(cbtype, "text/plain")) 
-          || (IsEmptyStr(cbtype)) ) {
-               wptr = content;
-               if (length > 0) {
-                       client_write(wptr, length);
-                       if (wptr[length-1] != '\n') {
-                               cprintf("\n");
-                       }
-               }
-               return;
-       }
-
-       if (!strcasecmp(cbtype, "text/html")) {
-               ptr = html_to_ascii(content, length, 80);
-               wlen = strlen(ptr);
-               client_write(ptr, wlen);
-               if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
-                       cprintf("\n");
-               }
-               free(ptr);
-               return;
-       }
-
-       if (ma->use_fo_hooks) {
-               if (PerformFixedOutputHooks(cbtype, content, length)) {         // returns nonzero if it handled the part
-                       return;
-               }
-       }
-
-       if (strncasecmp(cbtype, "multipart/", 10)) {
-               cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
-                       partnum, filename, cbtype, (long)length);
-               return;
-       }
-}
-
-
-// The client is elegant and sophisticated and wants to be choosy about
-// MIME content types, so figure out which multipart/alternative part
-// we're going to send.
-//
-// We use a system of weights.  When we find a part that matches one of the
-// MIME types we've declared as preferential, we can store it in ma->chosen_part
-// and then set ma->chosen_pref to that MIME type's position in our preference
-// list.  If we then hit another match, we only replace the first match if
-// the preference value is lower.
-void choose_preferred(char *name, char *filename, char *partnum, char *disp,
-               void *content, char *cbtype, char *cbcharset, size_t length,
-               char *encoding, char *cbid, void *cbuserdata)
-{
-       char buf[1024];
-       int i;
-       struct ma_info *ma;
-       
-       ma = (struct ma_info *)cbuserdata;
-
-       for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
-               extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
-               if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
-                       if (i < ma->chosen_pref) {
-                               syslog(LOG_DEBUG, "msgbase: setting chosen part to <%s>", partnum);
-                               safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
-                               ma->chosen_pref = i;
-                       }
-               }
-       }
-}
-
-
-// Now that we've chosen our preferred part, output it.
-void output_preferred(char *name, 
-                     char *filename, 
-                     char *partnum, 
-                     char *disp,
-                     void *content, 
-                     char *cbtype, 
-                     char *cbcharset, 
-                     size_t length,
-                     char *encoding, 
-                     char *cbid, 
-                     void *cbuserdata)
-{
-       int i;
-       char buf[128];
-       int add_newline = 0;
-       char *text_content;
-       struct ma_info *ma;
-       char *decoded = NULL;
-       size_t bytes_decoded;
-       int rc = 0;
-
-       ma = (struct ma_info *)cbuserdata;
-
-       // This is not the MIME part you're looking for...
-       if (strcasecmp(partnum, ma->chosen_part)) return;
-
-       // If the content-type of this part is in our preferred formats
-       // list, we can simply output it verbatim.
-       for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
-               extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
-               if (!strcasecmp(buf, cbtype)) {
-                       /* Yeah!  Go!  W00t!! */
-                       if (ma->dont_decode == 0) 
-                               rc = mime_decode_now (content, 
-                                                     length,
-                                                     encoding,
-                                                     &decoded,
-                                                     &bytes_decoded);
-                       if (rc < 0)
-                               break; // Give us the chance, maybe theres another one.
-
-                       if (rc == 0) text_content = (char *)content;
-                       else {
-                               text_content = decoded;
-                               length = bytes_decoded;
-                       }
-
-                       if (text_content[length-1] != '\n') {
-                               ++add_newline;
-                       }
-                       cprintf("Content-type: %s", cbtype);
-                       if (!IsEmptyStr(cbcharset)) {
-                               cprintf("; charset=%s", cbcharset);
-                       }
-                       cprintf("\nContent-length: %d\n",
-                               (int)(length + add_newline) );
-                       if (!IsEmptyStr(encoding)) {
-                               cprintf("Content-transfer-encoding: %s\n", encoding);
-                       }
-                       else {
-                               cprintf("Content-transfer-encoding: 7bit\n");
-                       }
-                       cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
-                       cprintf("\n");
-                       if (client_write(text_content, length) == -1)
-                       {
-                               syslog(LOG_ERR, "msgbase: output_preferred() aborting due to write failure");
-                               return;
-                       }
-                       if (add_newline) cprintf("\n");
-                       if (decoded != NULL) free(decoded);
-                       return;
-               }
-       }
-
-       // No translations required or possible: output as text/plain
-       cprintf("Content-type: text/plain\n\n");
-       rc = 0;
-       if (ma->dont_decode == 0)
-               rc = mime_decode_now (content, 
-                                     length,
-                                     encoding,
-                                     &decoded,
-                                     &bytes_decoded);
-       if (rc < 0)
-               return; // Give us the chance, maybe theres another one.
-       
-       if (rc == 0) text_content = (char *)content;
-       else {
-               text_content = decoded;
-               length = bytes_decoded;
-       }
-
-       fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset, length, encoding, cbid, cbuserdata);
-       if (decoded != NULL) free(decoded);
-}
-
-
-struct encapmsg {
-       char desired_section[64];
-       char *msg;
-       size_t msglen;
-};
-
-
-// Callback function
-void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
-                  void *content, char *cbtype, char *cbcharset, size_t length,
-                  char *encoding, char *cbid, void *cbuserdata)
-{
-       struct encapmsg *encap;
-
-       encap = (struct encapmsg *)cbuserdata;
-
-       // Only proceed if this is the desired section...
-       if (!strcasecmp(encap->desired_section, partnum)) {
-               encap->msglen = length;
-               encap->msg = malloc(length + 2);
-               memcpy(encap->msg, content, length);
-               return;
-       }
-}
-
-
-// Determine whether the specified message exists in the cached_msglist
-// (This is a security check)
-int check_cached_msglist(long msgnum) {
-
-       // cases in which we skip the check
-       if (!CC) return om_ok;                                          // not a session
-       if (CC->client_socket <= 0) return om_ok;                       // not a client session
-       if (CC->cached_msglist == NULL) return om_access_denied;        // no msglist fetched
-       if (CC->cached_num_msgs == 0) return om_access_denied;          // nothing to check 
-
-       // Do a binary search within the cached_msglist for the requested msgnum
-       int min = 0;
-       int max = (CC->cached_num_msgs - 1);
-
-       while (max >= min) {
-               int middle = min + (max-min) / 2 ;
-               if (msgnum == CC->cached_msglist[middle]) {
-                       return om_ok;
-               }
-               if (msgnum > CC->cached_msglist[middle]) {
-                       min = middle + 1;
-               }
-               else {
-                       max = middle - 1;
-               }
-       }
-
-       return om_access_denied;
-}
-
-
-// Get a message off disk.  (returns om_* values found in msgbase.h)
-int CtdlOutputMsg(long msg_num,                // message number (local) to fetch
-               int mode,               // how would you like that message?
-               int headers_only,       // eschew the message body?
-               int do_proto,           // do Citadel protocol responses?
-               int crlf,               // Use CRLF newlines instead of LF?
-               char *section,          // NULL or a message/rfc822 section
-               int flags,              // various flags; see msgbase.h
-               char **Author,
-               char **Address,
-               char **MessageID
-) {
-       struct CtdlMessage *TheMessage = NULL;
-       int retcode = CIT_OK;
-       struct encapmsg encap;
-       int r;
-
-       syslog(LOG_DEBUG, "msgbase: CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)", 
-               msg_num, mode,
-               (section ? section : "<>")
-       );
-
-       r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
-       if (r != om_ok) {
-               if (do_proto) {
-                       if (r == om_not_logged_in) {
-                               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
-                       }
-                       else {
-                               cprintf("%d An unknown error has occurred.\n", ERROR);
-                       }
-               }
-               return(r);
-       }
-
-       /*
-        * Check to make sure the message is actually IN this room
-        */
-       r = check_cached_msglist(msg_num);
-       if (r == om_access_denied) {
-               /* Not in the cache?  We get ONE shot to check it again. */
-               CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
-               r = check_cached_msglist(msg_num);
-       }
-       if (r != om_ok) {
-               syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s",
-                          msg_num, CC->room.QRname
-               );
-               if (do_proto) {
-                       if (r == om_access_denied) {
-                               cprintf("%d message %ld was not found in this room\n",
-                                       ERROR + HIGHER_ACCESS_REQUIRED,
-                                       msg_num
-                               );
-                       }
-               }
-               return(r);
-       }
-
-       /*
-        * Fetch the message from disk.  If we're in HEADERS_FAST mode,
-        * request that we don't even bother loading the body into memory.
-        */
-       if (headers_only == HEADERS_FAST) {
-               TheMessage = CtdlFetchMessage(msg_num, 0);
-       }
-       else {
-               TheMessage = CtdlFetchMessage(msg_num, 1);
-       }
-
-       if (TheMessage == NULL) {
-               if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
-                       ERROR + MESSAGE_NOT_FOUND, msg_num);
-               return(om_no_such_msg);
-       }
-
-       /* Here is the weird form of this command, to process only an
-        * encapsulated message/rfc822 section.
-        */
-       if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
-               memset(&encap, 0, sizeof encap);
-               safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
-               mime_parser(CM_RANGE(TheMessage, eMesageText),
-                           *extract_encapsulated_message,
-                           NULL, NULL, (void *)&encap, 0
-                       );
-
-               if ((Author != NULL) && (*Author == NULL))
-               {
-                       long len;
-                       CM_GetAsField(TheMessage, eAuthor, Author, &len);
-               }
-               if ((Address != NULL) && (*Address == NULL))
-               {       
-                       long len;
-                       CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
-               }
-               if ((MessageID != NULL) && (*MessageID == NULL))
-               {       
-                       long len;
-                       CM_GetAsField(TheMessage, emessageId, MessageID, &len);
-               }
-               CM_Free(TheMessage);
-               TheMessage = NULL;
-
-               if (encap.msg) {
-                       encap.msg[encap.msglen] = 0;
-                       TheMessage = convert_internet_message(encap.msg);
-                       encap.msg = NULL;       /* no free() here, TheMessage owns it now */
-
-                       /* Now we let it fall through to the bottom of this
-                        * function, because TheMessage now contains the
-                        * encapsulated message instead of the top-level
-                        * message.  Isn't that neat?
-                        */
-               }
-               else {
-                       if (do_proto) {
-                               cprintf("%d msg %ld has no part %s\n",
-                                       ERROR + MESSAGE_NOT_FOUND,
-                                       msg_num,
-                                       section);
-                       }
-                       retcode = om_no_such_msg;
-               }
-
-       }
-
-       /* Ok, output the message now */
-       if (retcode == CIT_OK)
-               retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
-       if ((Author != NULL) && (*Author == NULL))
-       {
-               long len;
-               CM_GetAsField(TheMessage, eAuthor, Author, &len);
-       }
-       if ((Address != NULL) && (*Address == NULL))
-       {       
-               long len;
-               CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
-       }
-       if ((MessageID != NULL) && (*MessageID == NULL))
-       {       
-               long len;
-               CM_GetAsField(TheMessage, emessageId, MessageID, &len);
-       }
-
-       CM_Free(TheMessage);
-
-       return(retcode);
-}
-
-
-void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto) {
-       int i;
-       char buf[SIZ];
-       char display_name[256];
-
-       /* begin header processing loop for Citadel message format */
-       safestrncpy(display_name, "<unknown>", sizeof display_name);
-       if (!CM_IsEmpty(TheMessage, eAuthor)) {
-               strcpy(buf, TheMessage->cm_fields[eAuthor]);
-               if (TheMessage->cm_anon_type == MES_ANONONLY) {
-                       safestrncpy(display_name, "****", sizeof display_name);
-               }
-               else if (TheMessage->cm_anon_type == MES_ANONOPT) {
-                       safestrncpy(display_name, "anonymous", sizeof display_name);
-               }
-               else {
-                       safestrncpy(display_name, buf, sizeof display_name);
-               }
-               if ((is_room_aide())
-                   && ((TheMessage->cm_anon_type == MES_ANONONLY)
-                       || (TheMessage->cm_anon_type == MES_ANONOPT))) {
-                       size_t tmp = strlen(display_name);
-                       snprintf(&display_name[tmp],
-                                sizeof display_name - tmp,
-                                " [%s]", buf);
-               }
-       }
-
-       /* Now spew the header fields in the order we like them. */
-       for (i=0; i< NDiskFields; ++i) {
-               eMsgField Field;
-               Field = FieldOrder[i];
-               if (Field != eMesageText) {
-                       if ( (!CM_IsEmpty(TheMessage, Field)) && (msgkeys[Field] != NULL) ) {
-                               if ((Field == eenVelopeTo) || (Field == eRecipient) || (Field == eCarbonCopY)) {
-                                       sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
-                               }
-                               if (Field == eAuthor) {
-                                       if (do_proto) {
-                                               cprintf("%s=%s\n", msgkeys[Field], display_name);
-                                       }
-                               }
-                               /* Masquerade display name if needed */
-                               else {
-                                       if (do_proto) {
-                                               cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
-                                       }
-                               }
-                               /* Give the client a hint about whether the message originated locally */
-                               if (Field == erFc822Addr) {
-                                       if (IsDirectory(TheMessage->cm_fields[Field] ,0)) {
-                                               cprintf("locl=yes\n");                          // message originated locally.
-                                       }
-
-
-
-                               }
-                       }
-               }
-       }
-}
-
-
-void OutputRFC822MsgHeaders(
-       struct CtdlMessage *TheMessage,
-       int flags,              /* should the message be exported clean */
-       const char *nl, int nlen,
-       char *mid, long sizeof_mid,
-       char *suser, long sizeof_suser,
-       char *luser, long sizeof_luser,
-       char *fuser, long sizeof_fuser,
-       char *snode, long sizeof_snode)
-{
-       char datestamp[100];
-       int subject_found = 0;
-       char buf[SIZ];
-       int i, j, k;
-       char *mptr = NULL;
-       char *mpptr = NULL;
-       char *hptr;
-
-       for (i = 0; i < NDiskFields; ++i) {
-               if (TheMessage->cm_fields[FieldOrder[i]]) {
-                       mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
-                       switch (FieldOrder[i]) {
-                       case eAuthor:
-                               safestrncpy(luser, mptr, sizeof_luser);
-                               safestrncpy(suser, mptr, sizeof_suser);
-                               break;
-                       case eCarbonCopY:
-                               if ((flags & QP_EADDR) != 0) {
-                                       mptr = qp_encode_email_addrs(mptr);
-                               }
-                               sanitize_truncated_recipient(mptr);
-                               cprintf("CC: %s%s", mptr, nl);
-                               break;
-                       case eMessagePath:
-                               cprintf("Return-Path: %s%s", mptr, nl);
-                               break;
-                       case eListID:
-                               cprintf("List-ID: %s%s", mptr, nl);
-                               break;
-                       case eenVelopeTo:
-                               if ((flags & QP_EADDR) != 0) 
-                                       mptr = qp_encode_email_addrs(mptr);
-                               hptr = mptr;
-                               while ((*hptr != '\0') && isspace(*hptr))
-                                       hptr ++;
-                               if (!IsEmptyStr(hptr))
-                                       cprintf("Envelope-To: %s%s", hptr, nl);
-                               break;
-                       case eMsgSubject:
-                               cprintf("Subject: %s%s", mptr, nl);
-                               subject_found = 1;
-                               break;
-                       case emessageId:
-                               safestrncpy(mid, mptr, sizeof_mid);
-                               break;
-                       case erFc822Addr:
-                               safestrncpy(fuser, mptr, sizeof_fuser);
-                               break;
-                       case eRecipient:
-                               if (haschar(mptr, '@') == 0) {
-                                       sanitize_truncated_recipient(mptr);
-                                       cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
-                                       cprintf("%s", nl);
-                               }
-                               else {
-                                       if ((flags & QP_EADDR) != 0) {
-                                               mptr = qp_encode_email_addrs(mptr);
-                                       }
-                                       sanitize_truncated_recipient(mptr);
-                                       cprintf("To: %s", mptr);
-                                       cprintf("%s", nl);
-                               }
-                               break;
-                       case eTimestamp:
-                               datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
-                               cprintf("Date: %s%s", datestamp, nl);
-                               break;
-                       case eWeferences:
-                               cprintf("References: ");
-                               k = num_tokens(mptr, '|');
-                               for (j=0; j<k; ++j) {
-                                       extract_token(buf, mptr, j, '|', sizeof buf);
-                                       cprintf("<%s>", buf);
-                                       if (j == (k-1)) {
-                                               cprintf("%s", nl);
-                                       }
-                                       else {
-                                               cprintf(" ");
-                                       }
-                               }
-                               break;
-                       case eReplyTo:
-                               hptr = mptr;
-                               while ((*hptr != '\0') && isspace(*hptr))
-                                       hptr ++;
-                               if (!IsEmptyStr(hptr))
-                                       cprintf("Reply-To: %s%s", mptr, nl);
-                               break;
-
-                       case eExclusiveID:
-                       case eJournal:
-                       case eMesageText:
-                       case eBig_message:
-                       case eOriginalRoom:
-                       case eErrorMsg:
-                       case eSuppressIdx:
-                       case eExtnotify:
-                       case eVltMsgNum:
-                               /* these don't map to mime message headers. */
-                               break;
-                       }
-                       if (mptr != mpptr) {
-                               free (mptr);
-                       }
-               }
-       }
-       if (subject_found == 0) {
-               cprintf("Subject: (no subject)%s", nl);
-       }
-}
-
-
-void Dump_RFC822HeadersBody(
-       struct CtdlMessage *TheMessage,
-       int headers_only,       /* eschew the message body? */
-       int flags,              /* should the bessage be exported clean? */
-       const char *nl, int nlen)
-{
-       cit_uint8_t prev_ch;
-       int eoh = 0;
-       const char *StartOfText = StrBufNOTNULL;
-       char outbuf[1024];
-       int outlen = 0;
-       int nllen = strlen(nl);
-       char *mptr;
-       int lfSent = 0;
-
-       mptr = TheMessage->cm_fields[eMesageText];
-
-       prev_ch = '\0';
-       while (*mptr != '\0') {
-               if (*mptr == '\r') {
-                       /* do nothing */
-               }
-               else {
-                       if ((!eoh) &&
-                           (*mptr == '\n'))
-                       {
-                               eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
-                               if (!eoh)
-                                       eoh = *(mptr+1) == '\n';
-                               if (eoh)
-                               {
-                                       StartOfText = mptr;
-                                       StartOfText = strchr(StartOfText, '\n');
-                                       StartOfText = strchr(StartOfText, '\n');
-                               }
-                       }
-                       if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
-                           ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
-                           ((headers_only != HEADERS_NONE) && 
-                            (headers_only != HEADERS_ONLY))
-                       ) {
-                               if (*mptr == '\n') {
-                                       memcpy(&outbuf[outlen], nl, nllen);
-                                       outlen += nllen;
-                                       outbuf[outlen] = '\0';
-                               }
-                               else {
-                                       outbuf[outlen++] = *mptr;
-                               }
-                       }
-               }
-               if (flags & ESC_DOT) {
-                       if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
-                               outbuf[outlen++] = '.';
-                       }
-                       prev_ch = *mptr;
-               }
-               ++mptr;
-               if (outlen > 1000) {
-                       if (client_write(outbuf, outlen) == -1) {
-                               syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
-                               return;
-                       }
-                       lfSent =  (outbuf[outlen - 1] == '\n');
-                       outlen = 0;
-               }
-       }
-       if (outlen > 0) {
-               client_write(outbuf, outlen);
-               lfSent =  (outbuf[outlen - 1] == '\n');
-       }
-       if (!lfSent)
-               client_write(nl, nlen);
-}
-
-
-/* If the format type on disk is 1 (fixed-format), then we want
- * everything to be output completely literally ... regardless of
- * what message transfer format is in use.
- */
-void DumpFormatFixed(
-       struct CtdlMessage *TheMessage,
-       int mode,               /* how would you like that message? */
-       const char *nl, int nllen)
-{
-       cit_uint8_t ch;
-       char buf[SIZ];
-       int buflen;
-       int xlline = 0;
-       char *mptr;
-
-       mptr = TheMessage->cm_fields[eMesageText];
-       
-       if (mode == MT_MIME) {
-               cprintf("Content-type: text/plain\n\n");
-       }
-       *buf = '\0';
-       buflen = 0;
-       while (ch = *mptr++, ch > 0) {
-               if (ch == '\n')
-                       ch = '\r';
-
-               if ((buflen > 250) && (!xlline)){
-                       int tbuflen;
-                       tbuflen = buflen;
-
-                       while ((buflen > 0) && 
-                              (!isspace(buf[buflen])))
-                               buflen --;
-                       if (buflen == 0) {
-                               xlline = 1;
-                       }
-                       else {
-                               mptr -= tbuflen - buflen;
-                               buf[buflen] = '\0';
-                               ch = '\r';
-                       }
-               }
-
-               /* if we reach the outer bounds of our buffer, abort without respect for what we purge. */
-               if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
-                       ch = '\r';
-               }
-
-               if (ch == '\r') {
-                       memcpy (&buf[buflen], nl, nllen);
-                       buflen += nllen;
-                       buf[buflen] = '\0';
-
-                       if (client_write(buf, buflen) == -1) {
-                               syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
-                               return;
-                       }
-                       *buf = '\0';
-                       buflen = 0;
-                       xlline = 0;
-               } else {
-                       buf[buflen] = ch;
-                       buflen++;
-               }
-       }
-       buf[buflen] = '\0';
-       if (!IsEmptyStr(buf)) {
-               cprintf("%s%s", buf, nl);
-       }
-}
-
-
-/*
- * Get a message off disk.  (returns om_* values found in msgbase.h)
- */
-int CtdlOutputPreLoadedMsg(
-               struct CtdlMessage *TheMessage,
-               int mode,               /* how would you like that message? */
-               int headers_only,       /* eschew the message body? */
-               int do_proto,           /* do Citadel protocol responses? */
-               int crlf,               /* Use CRLF newlines instead of LF? */
-               int flags               /* should the bessage be exported clean? */
-) {
-       int i;
-       const char *nl; /* newline string */
-       int nlen;
-       struct ma_info ma;
-
-       /* Buffers needed for RFC822 translation.  These are all filled
-        * using functions that are bounds-checked, and therefore we can
-        * make them substantially smaller than SIZ.
-        */
-       char suser[1024];
-       char luser[1024];
-       char fuser[1024];
-       char snode[1024];
-       char mid[1024];
-
-       syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
-                  ((TheMessage == NULL) ? "NULL" : "not null"),
-                  mode, headers_only, do_proto, crlf
-       );
-
-       strcpy(mid, "unknown");
-       nl = (crlf ? "\r\n" : "\n");
-       nlen = crlf ? 2 : 1;
-
-       if (!CM_IsValidMsg(TheMessage)) {
-               syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
-               return(om_no_such_msg);
-       }
-
-       /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
-        * Pad it with spaces in order to avoid changing the RFC822 length of the message.
-        */
-       if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
-               memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
-       }
-               
-       /* Are we downloading a MIME component? */
-       if (mode == MT_DOWNLOAD) {
-               if (TheMessage->cm_format_type != FMT_RFC822) {
-                       if (do_proto)
-                               cprintf("%d This is not a MIME message.\n",
-                               ERROR + ILLEGAL_VALUE);
-               } else if (CC->download_fp != NULL) {
-                       if (do_proto) cprintf(
-                               "%d You already have a download open.\n",
-                               ERROR + RESOURCE_BUSY);
-               } else {
-                       /* Parse the message text component */
-                       mime_parser(CM_RANGE(TheMessage, eMesageText),
-                                   *mime_download, NULL, NULL, NULL, 0);
-                       /* If there's no file open by this time, the requested
-                        * section wasn't found, so print an error
-                        */
-                       if (CC->download_fp == NULL) {
-                               if (do_proto) cprintf(
-                                       "%d Section %s not found.\n",
-                                       ERROR + FILE_NOT_FOUND,
-                                       CC->download_desired_section);
-                       }
-               }
-               return((CC->download_fp != NULL) ? om_ok : om_mime_error);
-       }
-
-       // MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
-       // in a single server operation instead of opening a download file.
-       if (mode == MT_SPEW_SECTION) {
-               if (TheMessage->cm_format_type != FMT_RFC822) {
-                       if (do_proto)
-                               cprintf("%d This is not a MIME message.\n",
-                               ERROR + ILLEGAL_VALUE);
-               }
-               else {
-                       // Locate and parse the component specified by the caller
-                       int found_it = 0;
-                       mime_parser(CM_RANGE(TheMessage, eMesageText), *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
-
-                       // If section wasn't found, print an error
-                       if (!found_it) {
-                               if (do_proto) {
-                                       cprintf( "%d Section %s not found.\n", ERROR + FILE_NOT_FOUND, CC->download_desired_section);
-                               }
-                       }
-               }
-               return((CC->download_fp != NULL) ? om_ok : om_mime_error);
-       }
-
-       // now for the user-mode message reading loops
-       if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
-
-       // Does the caller want to skip the headers?
-       if (headers_only == HEADERS_NONE) goto START_TEXT;
-
-       // Tell the client which format type we're using.
-       if ( (mode == MT_CITADEL) && (do_proto) ) {
-               cprintf("type=%d\n", TheMessage->cm_format_type);       // Tell the client which format type we're using.
-       }
-
-       // nhdr=yes means that we're only displaying headers, no body
-       if ( (TheMessage->cm_anon_type == MES_ANONONLY)
-          && ((mode == MT_CITADEL) || (mode == MT_MIME))
-          && (do_proto)
-          ) {
-               cprintf("nhdr=yes\n");
-       }
-
-       if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
-               OutputCtdlMsgHeaders(TheMessage, do_proto);
-       }
-
-       // begin header processing loop for RFC822 transfer format
-       strcpy(suser, "");
-       strcpy(luser, "");
-       strcpy(fuser, "");
-       strcpy(snode, "");
-       if (mode == MT_RFC822) 
-               OutputRFC822MsgHeaders(
-                       TheMessage,
-                       flags,
-                       nl, nlen,
-                       mid, sizeof(mid),
-                       suser, sizeof(suser),
-                       luser, sizeof(luser),
-                       fuser, sizeof(fuser),
-                       snode, sizeof(snode)
-                       );
-
-
-       for (i=0; !IsEmptyStr(&suser[i]); ++i) {
-               suser[i] = tolower(suser[i]);
-               if (!isalnum(suser[i])) suser[i]='_';
-       }
-
-       if (mode == MT_RFC822) {
-               /* Construct a fun message id */
-               cprintf("Message-ID: <%s", mid);
-               if (strchr(mid, '@')==NULL) {
-                       cprintf("@%s", snode);
-               }
-               cprintf(">%s", nl);
-
-               if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
-                       cprintf("From: \"----\" <x@x.org>%s", nl);
-               }
-               else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
-                       cprintf("From: \"anonymous\" <x@x.org>%s", nl);
-               }
-               else if (!IsEmptyStr(fuser)) {
-                       cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
-               }
-               else {
-                       cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
-               }
-
-               /* Blank line signifying RFC822 end-of-headers */
-               if (TheMessage->cm_format_type != FMT_RFC822) {
-                       cprintf("%s", nl);
-               }
-       }
-
-       // end header processing loop ... at this point, we're in the text
-START_TEXT:
-       if (headers_only == HEADERS_FAST) goto DONE;
-
-       // Tell the client about the MIME parts in this message
-       if (TheMessage->cm_format_type == FMT_RFC822) {
-               if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
-                       memset(&ma, 0, sizeof(struct ma_info));
-                       mime_parser(CM_RANGE(TheMessage, eMesageText),
-                               (do_proto ? *list_this_part : NULL),
-                               (do_proto ? *list_this_pref : NULL),
-                               (do_proto ? *list_this_suff : NULL),
-                               (void *)&ma, 1);
-               }
-               else if (mode == MT_RFC822) {   // unparsed RFC822 dump
-                       Dump_RFC822HeadersBody(
-                               TheMessage,
-                               headers_only,
-                               flags,
-                               nl, nlen);
-                       goto DONE;
-               }
-       }
-
-       if (headers_only == HEADERS_ONLY) {
-               goto DONE;
-       }
-
-       // signify start of msg text
-       if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
-               if (do_proto) cprintf("text\n");
-       }
-
-       if (TheMessage->cm_format_type == FMT_FIXED) 
-               DumpFormatFixed(
-                       TheMessage,
-                       mode,           // how would you like that message?
-                       nl, nlen);
-
-       // If the message on disk is format 0 (Citadel vari-format), we
-       // output using the formatter at 80 columns.  This is the final output
-       // form if the transfer format is RFC822, but if the transfer format
-       // is Citadel proprietary, it'll still work, because the indentation
-       // for new paragraphs is correct and the client will reformat the
-       // message to the reader's screen width.
-       //
-       if (TheMessage->cm_format_type == FMT_CITADEL) {
-               if (mode == MT_MIME) {
-                       cprintf("Content-type: text/x-citadel-variformat\n\n");
-               }
-               memfmout(TheMessage->cm_fields[eMesageText], nl);
-       }
-
-       // If the message on disk is format 4 (MIME), we've gotta hand it
-       // off to the MIME parser.  The client has already been told that
-       // this message is format 1 (fixed format), so the callback function
-       // we use will display those parts as-is.
-       //
-       if (TheMessage->cm_format_type == FMT_RFC822) {
-               memset(&ma, 0, sizeof(struct ma_info));
-
-               if (mode == MT_MIME) {
-                       ma.use_fo_hooks = 0;
-                       strcpy(ma.chosen_part, "1");
-                       ma.chosen_pref = 9999;
-                       ma.dont_decode = CC->msg4_dont_decode;
-                       mime_parser(CM_RANGE(TheMessage, eMesageText),
-                                   *choose_preferred, *fixed_output_pre,
-                                   *fixed_output_post, (void *)&ma, 1);
-                       mime_parser(CM_RANGE(TheMessage, eMesageText),
-                                   *output_preferred, NULL, NULL, (void *)&ma, 1);
-               }
-               else {
-                       ma.use_fo_hooks = 1;
-                       mime_parser(CM_RANGE(TheMessage, eMesageText),
-                                   *fixed_output, *fixed_output_pre,
-                                   *fixed_output_post, (void *)&ma, 0);
-               }
-
-       }
-
-DONE:  /* now we're done */
-       if (do_proto) cprintf("000\n");
-       return(om_ok);
-}
-
-// Save one or more message pointers into a specified room
-// (Returns 0 for success, nonzero for failure)
-// roomname may be NULL to use the current room
-//
-// Note that the 'supplied_msg' field may be set to NULL, in which case
-// the message will be fetched from disk, by number, if we need to perform
-// replication checks.  This adds an additional database read, so if the
-// caller already has the message in memory then it should be supplied.  (Obviously
-// this mode of operation only works if we're saving a single message.)
-//
-int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
-                       int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
-) {
-       int i, j, unique;
-       char hold_rm[ROOMNAMELEN];
-       struct cdbdata *cdbfr;
-       int num_msgs;
-       long *msglist;
-       long highest_msg = 0L;
-
-       long msgid = 0;
-       struct CtdlMessage *msg = NULL;
-
-       long *msgs_to_be_merged = NULL;
-       int num_msgs_to_be_merged = 0;
-
-       syslog(LOG_DEBUG,
-               "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
-               roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
-       );
-
-       strcpy(hold_rm, CC->room.QRname);
-
-       /* Sanity checks */
-       if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
-       if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
-       if (num_newmsgs > 1) supplied_msg = NULL;
-
-       /* Now the regular stuff */
-       if (CtdlGetRoomLock(&CC->room,
-          ((roomname != NULL) ? roomname : CC->room.QRname) )
-          != 0) {
-               syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
-               return(ERROR + ROOM_NOT_FOUND);
-       }
-
-
-       msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
-       num_msgs_to_be_merged = 0;
-
-
-       cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
-       if (cdbfr == NULL) {
-               msglist = NULL;
-               num_msgs = 0;
-       } else {
-               msglist = (long *) cdbfr->ptr;
-               cdbfr->ptr = NULL;      /* CtdlSaveMsgPointerInRoom() now owns this memory */
-               num_msgs = cdbfr->len / sizeof(long);
-               cdb_free(cdbfr);
-       }
-
-
-       /* Create a list of msgid's which were supplied by the caller, but do
-        * not already exist in the target room.  It is absolutely taboo to
-        * have more than one reference to the same message in a room.
-        */
-       for (i=0; i<num_newmsgs; ++i) {
-               unique = 1;
-               if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
-                       if (msglist[j] == newmsgidlist[i]) {
-                               unique = 0;
-                       }
-               }
-               if (unique) {
-                       msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
-               }
-       }
-
-       syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
-
-       /*
-        * Now merge the new messages
-        */
-       msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
-       if (msglist == NULL) {
-               syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
-               free(msgs_to_be_merged);
-               return (ERROR + INTERNAL_ERROR);
-       }
-       memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
-       num_msgs += num_msgs_to_be_merged;
-
-       /* Sort the message list, so all the msgid's are in order */
-       num_msgs = sort_msglist(msglist, num_msgs);
-
-       /* Determine the highest message number */
-       highest_msg = msglist[num_msgs - 1];
-
-       /* Write it back to disk. */
-       cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
-                 msglist, (int)(num_msgs * sizeof(long)));
-
-       /* Free up the memory we used. */
-       free(msglist);
-
-       /* Update the highest-message pointer and unlock the room. */
-       CC->room.QRhighest = highest_msg;
-       CtdlPutRoomLock(&CC->room);
-
-       /* Perform replication checks if necessary */
-       if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
-               syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
-
-               for (i=0; i<num_msgs_to_be_merged; ++i) {
-                       msgid = msgs_to_be_merged[i];
-       
-                       if (supplied_msg != NULL) {
-                               msg = supplied_msg;
-                       }
-                       else {
-                               msg = CtdlFetchMessage(msgid, 0);
-                       }
-       
-                       if (msg != NULL) {
-                               ReplicationChecks(msg);
-               
-                               /* If the message has an Exclusive ID, index that... */
-                               if (!CM_IsEmpty(msg, eExclusiveID)) {
-                                       index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
-                               }
-
-                               /* Free up the memory we may have allocated */
-                               if (msg != supplied_msg) {
-                                       CM_Free(msg);
-                               }
-                       }
-       
-               }
-       }
-
-       else {
-               syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
-       }
-
-       /* Submit this room for processing by hooks */
-       int total_roomhook_errors = PerformRoomHooks(&CC->room);
-       if (total_roomhook_errors) {
-               syslog(LOG_WARNING, "msgbase: room hooks returned %d errors", total_roomhook_errors);
-       }
-
-       /* Go back to the room we were in before we wandered here... */
-       CtdlGetRoom(&CC->room, hold_rm);
-
-       /* Bump the reference count for all messages which were merged */
-       if (!suppress_refcount_adj) {
-               AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
-       }
-
-       /* Free up memory... */
-       if (msgs_to_be_merged != NULL) {
-               free(msgs_to_be_merged);
-       }
-
-       /* Return success. */
-       return (0);
-}
-
-
-/*
- * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
- * a single message.
- */
-int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
-                            int do_repl_check, struct CtdlMessage *supplied_msg)
-{
-       return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
-}
-
-
-/*
- * Message base operation to save a new message to the message store
- * (returns new message number)
- *
- * This is the back end for CtdlSubmitMsg() and should not be directly
- * called by server-side modules.
- *
- */
-long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
-       long retval;
-       struct ser_ret smr;
-       int is_bigmsg = 0;
-       char *holdM = NULL;
-       long holdMLen = 0;
-
-       /*
-        * If the message is big, set its body aside for storage elsewhere
-        * and we hide the message body from the serializer
-        */
-       if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG) {
-               is_bigmsg = 1;
-               holdM = msg->cm_fields[eMesageText];
-               msg->cm_fields[eMesageText] = NULL;
-               holdMLen = msg->cm_lengths[eMesageText];
-               msg->cm_lengths[eMesageText] = 0;
-       }
-
-       /* Serialize our data structure for storage in the database */  
-       CtdlSerializeMessage(&smr, msg);
-
-       if (is_bigmsg) {
-               /* put the message body back into the message */
-               msg->cm_fields[eMesageText] = holdM;
-               msg->cm_lengths[eMesageText] = holdMLen;
-       }
-
-       if (smr.len == 0) {
-               if (Reply) {
-                       cprintf("%d Unable to serialize message\n",
-                               ERROR + INTERNAL_ERROR);
-               }
-               else {
-                       syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
-
-               }
-               return (-1L);
-       }
-
-       /* Write our little bundle of joy into the message base */
-       retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len);
-       if (retval < 0) {
-               syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
-       }
-       else {
-               if (is_bigmsg) {
-                       retval = cdb_store(CDB_BIGMSGS,
-                                          &msgid,
-                                          (int)sizeof(long),
-                                          holdM,
-                                          (holdMLen + 1)
-                               );
-                       if (retval < 0) {
-                               syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
-                       }
-               }
-       }
-
-       /* Free the memory we used for the serialized message */
-       free(smr.ser);
-
-       return(retval);
-}
-
-
-long send_message(struct CtdlMessage *msg) {
-       long newmsgid;
-       long retval;
-       char msgidbuf[256];
-       long msgidbuflen;
-
-       /* Get a new message number */
-       newmsgid = get_new_message_number();
-
-       /* Generate an ID if we don't have one already */
-       if (CM_IsEmpty(msg, emessageId)) {
-               msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
-                                      (long unsigned int) time(NULL),
-                                      (long unsigned int) newmsgid,
-                                      CtdlGetConfigStr("c_fqdn")
-                       );
-
-               CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
-       }
-
-       retval = CtdlSaveThisMessage(msg, newmsgid, 1);
-
-       if (retval == 0) {
-               retval = newmsgid;
-       }
-
-       /* Return the *local* message ID to the caller
-        * (even if we're storing an incoming network message)
-        */
-       return(retval);
-}
-
-
-/*
- * Serialize a struct CtdlMessage into the format used on disk.
- * 
- * This function loads up a "struct ser_ret" (defined in server.h) which
- * contains the length of the serialized message and a pointer to the
- * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
- */
-void CtdlSerializeMessage(struct ser_ret *ret,         /* return values */
-                         struct CtdlMessage *msg)      /* unserialized msg */
-{
-       size_t wlen;
-       int i;
-
-       /*
-        * Check for valid message format
-        */
-       if (CM_IsValidMsg(msg) == 0) {
-               syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
-               ret->len = 0;
-               ret->ser = NULL;
-               return;
-       }
-
-       ret->len = 3;
-       for (i=0; i < NDiskFields; ++i)
-               if (msg->cm_fields[FieldOrder[i]] != NULL)
-                       ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
-
-       ret->ser = malloc(ret->len);
-       if (ret->ser == NULL) {
-               syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
-               ret->len = 0;
-               ret->ser = NULL;
-               return;
-       }
-
-       ret->ser[0] = 0xFF;
-       ret->ser[1] = msg->cm_anon_type;
-       ret->ser[2] = msg->cm_format_type;
-       wlen = 3;
-
-       for (i=0; i < NDiskFields; ++i) {
-               if (msg->cm_fields[FieldOrder[i]] != NULL) {
-                       ret->ser[wlen++] = (char)FieldOrder[i];
-
-                       memcpy(&ret->ser[wlen],
-                              msg->cm_fields[FieldOrder[i]],
-                              msg->cm_lengths[FieldOrder[i]] + 1);
-
-                       wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
-               }
-       }
-
-       if (ret->len != wlen) {
-               syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
-       }
-
-       return;
-}
-
-
-/*
- * Check to see if any messages already exist in the current room which
- * carry the same Exclusive ID as this one.  If any are found, delete them.
- */
-void ReplicationChecks(struct CtdlMessage *msg) {
-       long old_msgnum = (-1L);
-
-       if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
-
-       syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
-
-       /* No exclusive id?  Don't do anything. */
-       if (msg == NULL) return;
-       if (CM_IsEmpty(msg, eExclusiveID)) return;
-
-       /*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
-         msg->cm_fields[eExclusiveID], CC->room.QRname);*/
-
-       old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
-       if (old_msgnum > 0L) {
-               syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
-               CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
-       }
-}
-
-
-/*
- * Save a message to disk and submit it into the delivery system.
- */
-long CtdlSubmitMsg(struct CtdlMessage *msg,    /* message to save */
-                  struct recptypes *recps,     /* recipients (if mail) */
-                  const char *force            /* force a particular room? */
-) {
-       char hold_rm[ROOMNAMELEN];
-       char actual_rm[ROOMNAMELEN];
-       char force_room[ROOMNAMELEN];
-       char content_type[SIZ];                 /* We have to learn this */
-       char recipient[SIZ];
-       char bounce_to[1024];
-       const char *room;
-       long newmsgid;
-       const char *mptr = NULL;
-       struct ctdluser userbuf;
-       int a, i;
-       struct MetaData smi;
-       char *collected_addresses = NULL;
-       struct addresses_to_be_filed *aptr = NULL;
-       StrBuf *saved_rfc822_version = NULL;
-       int qualified_for_journaling = 0;
-
-       syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
-       if (CM_IsValidMsg(msg) == 0) return(-1);        /* self check */
-
-       /* If this message has no timestamp, we take the liberty of
-        * giving it one, right now.
-        */
-       if (CM_IsEmpty(msg, eTimestamp)) {
-               CM_SetFieldLONG(msg, eTimestamp, time(NULL));
-       }
-
-       /* If this message has no path, we generate one.
-        */
-       if (CM_IsEmpty(msg, eMessagePath)) {
-               if (!CM_IsEmpty(msg, eAuthor)) {
-                       CM_CopyField(msg, eMessagePath, eAuthor);
-                       for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
-                               if (isspace(msg->cm_fields[eMessagePath][a])) {
-                                       msg->cm_fields[eMessagePath][a] = ' ';
-                               }
-                       }
-               }
-               else {
-                       CM_SetField(msg, eMessagePath, HKEY("unknown"));
-               }
-       }
-
-       if (force == NULL) {
-               force_room[0] = '\0';
-       }
-       else {
-               strcpy(force_room, force);
-       }
-
-       /* Learn about what's inside, because it's what's inside that counts */
-       if (CM_IsEmpty(msg, eMesageText)) {
-               syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
-               return(-2);
-       }
-
-       switch (msg->cm_format_type) {
-       case 0:
-               strcpy(content_type, "text/x-citadel-variformat");
-               break;
-       case 1:
-               strcpy(content_type, "text/plain");
-               break;
-       case 4:
-               strcpy(content_type, "text/plain");
-               mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
-               if (mptr != NULL) {
-                       char *aptr;
-                       safestrncpy(content_type, &mptr[13], sizeof content_type);
-                       striplt(content_type);
-                       aptr = content_type;
-                       while (!IsEmptyStr(aptr)) {
-                               if ((*aptr == ';')
-                                   || (*aptr == ' ')
-                                   || (*aptr == 13)
-                                   || (*aptr == 10)) {
-                                       *aptr = 0;
-                               }
-                               else aptr++;
-                       }
-               }
-       }
-
-       /* Goto the correct room */
-       room = (recps) ? CC->room.QRname : SENTITEMS;
-       syslog(LOG_DEBUG, "msgbase: selected room %s", room);
-       strcpy(hold_rm, CC->room.QRname);
-       strcpy(actual_rm, CC->room.QRname);
-       if (recps != NULL) {
-               strcpy(actual_rm, SENTITEMS);
-       }
-
-       /* If the user is a twit, move to the twit room for posting */
-       if (TWITDETECT) {
-               if (CC->user.axlevel == AxProbU) {
-                       strcpy(hold_rm, actual_rm);
-                       strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
-                       syslog(LOG_DEBUG, "msgbase: diverting to twit room");
-               }
-       }
-
-       /* ...or if this message is destined for Aide> then go there. */
-       if (!IsEmptyStr(force_room)) {
-               strcpy(actual_rm, force_room);
-       }
-
-       syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
-       if (strcasecmp(actual_rm, CC->room.QRname)) {
-               CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
-       }
-
-       /*
-        * If this message has no O (room) field, generate one.
-        */
-       if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
-               CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
-       }
-
-       /* Perform "before save" hooks (aborting if any return nonzero) */
-       syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
-       if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
-
-       /*
-        * If this message has an Exclusive ID, and the room is replication
-        * checking enabled, then do replication checks.
-        */
-       if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
-               ReplicationChecks(msg);
-       }
-
-       /* Save it to disk */
-       syslog(LOG_DEBUG, "msgbase: saving to disk");
-       newmsgid = send_message(msg);
-       if (newmsgid <= 0L) return(-5);
-
-       /* Write a supplemental message info record.  This doesn't have to
-        * be a critical section because nobody else knows about this message
-        * yet.
-        */
-       syslog(LOG_DEBUG, "msgbase: creating metadata record");
-       memset(&smi, 0, sizeof(struct MetaData));
-       smi.meta_msgnum = newmsgid;
-       smi.meta_refcount = 0;
-       safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
-
-       /*
-        * Measure how big this message will be when rendered as RFC822.
-        * We do this for two reasons:
-        * 1. We need the RFC822 length for the new metadata record, so the
-        *    POP and IMAP services don't have to calculate message lengths
-        *    while the user is waiting (multiplied by potentially hundreds
-        *    or thousands of messages).
-        * 2. If journaling is enabled, we will need an RFC822 version of the
-        *    message to attach to the journalized copy.
-        */
-       if (CC->redirect_buffer != NULL) {
-               syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
-               abort();
-       }
-       CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
-       CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
-       smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
-       saved_rfc822_version = CC->redirect_buffer;
-       CC->redirect_buffer = NULL;
-
-       PutMetaData(&smi);
-
-       /* Now figure out where to store the pointers */
-       syslog(LOG_DEBUG, "msgbase: storing pointers");
-
-       /* If this is being done by the networker delivering a private
-        * message, we want to BYPASS saving the sender's copy (because there
-        * is no local sender; it would otherwise go to the Trashcan).
-        */
-       if ((!CC->internal_pgm) || (recps == NULL)) {
-               if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
-                       syslog(LOG_ERR, "msgbase: ERROR saving message pointer %ld in %s", newmsgid, actual_rm);
-                       CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
-               }
-       }
-
-       /* For internet mail, drop a copy in the outbound queue room */
-       if ((recps != NULL) && (recps->num_internet > 0)) {
-               CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
-       }
-
-       /* If other rooms are specified, drop them there too. */
-       if ((recps != NULL) && (recps->num_room > 0)) {
-               for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
-                       extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
-                       syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
-                       CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
-               }
-       }
-
-       /* Bump this user's messages posted counter. */
-       syslog(LOG_DEBUG, "msgbase: updating user");
-       CtdlLockGetCurrentUser();
-       CC->user.posted = CC->user.posted + 1;
-       CtdlPutCurrentUserLock();
-
-       /* Decide where bounces need to be delivered */
-       if ((recps != NULL) && (recps->bounce_to == NULL)) {
-               if (CC->logged_in) {
-                       strcpy(bounce_to, CC->user.fullname);
-               }
-               else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
-                       strcpy(bounce_to, msg->cm_fields[eAuthor]);
-               }
-               recps->bounce_to = bounce_to;
-       }
-               
-       CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
-
-       /* If this is private, local mail, make a copy in the
-        * recipient's mailbox and bump the reference count.
-        */
-       if ((recps != NULL) && (recps->num_local > 0)) {
-               char *pch;
-               int ntokens;
-
-               pch = recps->recp_local;
-               recps->recp_local = recipient;
-               ntokens = num_tokens(pch, '|');
-               for (i=0; i<ntokens; ++i) {
-                       extract_token(recipient, pch, i, '|', sizeof recipient);
-                       syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
-                       if (CtdlGetUser(&userbuf, recipient) == 0) {
-                               CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
-                               CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
-                               CtdlBumpNewMailCounter(userbuf.usernum);
-                               PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
-                       }
-                       else {
-                               syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
-                               CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
-                       }
-               }
-               recps->recp_local = pch;
-       }
-
-       /* Perform "after save" hooks */
-       syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
-
-       PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
-       CM_FlushField(msg, eVltMsgNum);
-
-       /* Go back to the room we started from */
-       syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
-       if (strcasecmp(hold_rm, CC->room.QRname)) {
-               CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
-       }
-
-       /*
-        * Any addresses to harvest for someone's address book?
-        */
-       if ( (CC->logged_in) && (recps != NULL) ) {
-               collected_addresses = harvest_collected_addresses(msg);
-       }
-
-       if (collected_addresses != NULL) {
-               aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
-               CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
-               aptr->roomname = strdup(actual_rm);
-               aptr->collected_addresses = collected_addresses;
-               begin_critical_section(S_ATBF);
-               aptr->next = atbf;
-               atbf = aptr;
-               end_critical_section(S_ATBF);
-       }
-
-       /*
-        * Determine whether this message qualifies for journaling.
-        */
-       if (!CM_IsEmpty(msg, eJournal)) {
-               qualified_for_journaling = 0;
-       }
-       else {
-               if (recps == NULL) {
-                       qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
-               }
-               else if (recps->num_local + recps->num_internet > 0) {
-                       qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
-               }
-               else {
-                       qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
-               }
-       }
-
-       /*
-        * Do we have to perform journaling?  If so, hand off the saved
-        * RFC822 version will be handed off to the journaler for background
-        * submit.  Otherwise, we have to free the memory ourselves.
-        */
-       if (saved_rfc822_version != NULL) {
-               if (qualified_for_journaling) {
-                       JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
-               }
-               else {
-                       FreeStrBuf(&saved_rfc822_version);
-               }
-       }
-
-       if ((recps != NULL) && (recps->bounce_to == bounce_to))
-               recps->bounce_to = NULL;
-
-       /* Done. */
-       return(newmsgid);
-}
-
-
-/*
- * Convenience function for generating small administrative messages.
- */
-long quickie_message(const char *from,
-                    const char *fromaddr,
-                    const char *to,
-                    char *room,
-                    const char *text, 
-                    int format_type,
-                    const char *subject)
-{
-       struct CtdlMessage *msg;
-       struct recptypes *recp = NULL;
-
-       msg = malloc(sizeof(struct CtdlMessage));
-       memset(msg, 0, sizeof(struct CtdlMessage));
-       msg->cm_magic = CTDLMESSAGE_MAGIC;
-       msg->cm_anon_type = MES_NORMAL;
-       msg->cm_format_type = format_type;
-
-       if (!IsEmptyStr(from)) {
-               CM_SetField(msg, eAuthor, from, -1);
-       }
-       else if (!IsEmptyStr(fromaddr)) {
-               char *pAt;
-               CM_SetField(msg, eAuthor, fromaddr, -1);
-               pAt = strchr(msg->cm_fields[eAuthor], '@');
-               if (pAt != NULL) {
-                       CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
-               }
-       }
-       else {
-               msg->cm_fields[eAuthor] = strdup("Citadel");
-       }
-
-       if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr, -1);
-       if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room, -1);
-       if (!IsEmptyStr(to)) {
-               CM_SetField(msg, eRecipient, to, -1);
-               recp = validate_recipients(to, NULL, 0);
-       }
-       if (!IsEmptyStr(subject)) {
-               CM_SetField(msg, eMsgSubject, subject, -1);
-       }
-       if (!IsEmptyStr(text)) {
-               CM_SetField(msg, eMesageText, text, -1);
-       }
-
-       long msgnum = CtdlSubmitMsg(msg, recp, room);
-       CM_Free(msg);
-       if (recp != NULL) free_recipients(recp);
-       return msgnum;
-}
-
-
-/*
- * Back end function used by CtdlMakeMessage() and similar functions
- */
-StrBuf *CtdlReadMessageBodyBuf(char *terminator,       // token signalling EOT
-                              long tlen,
-                              size_t maxlen,           // maximum message length
-                              StrBuf *exist,           // if non-null, append to it; exist is ALWAYS freed
-                              int crlf                 // CRLF newlines instead of LF
-) {
-       StrBuf *Message;
-       StrBuf *LineBuf;
-       int flushing = 0;
-       int finished = 0;
-       int dotdot = 0;
-
-       LineBuf = NewStrBufPlain(NULL, SIZ);
-       if (exist == NULL) {
-               Message = NewStrBufPlain(NULL, 4 * SIZ);
-       }
-       else {
-               Message = NewStrBufDup(exist);
-       }
-
-       /* Do we need to change leading ".." to "." for SMTP escaping? */
-       if ((tlen == 1) && (*terminator == '.')) {
-               dotdot = 1;
-       }
-
-       /* read in the lines of message text one by one */
-       do {
-               if (CtdlClientGetLine(LineBuf) < 0) {
-                       finished = 1;
-               }
-               if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
-                       finished = 1;
-               }
-               if ( (!flushing) && (!finished) ) {
-                       if (crlf) {
-                               StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
-                       }
-                       else {
-                               StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
-                       }
-                       
-                       /* Unescape SMTP-style input of two dots at the beginning of the line */
-                       if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
-                               StrBufCutLeft(LineBuf, 1);
-                       }
-                       StrBufAppendBuf(Message, LineBuf, 0);
-               }
-
-               /* if we've hit the max msg length, flush the rest */
-               if (StrLength(Message) >= maxlen) {
-                       flushing = 1;
-               }
-
-       } while (!finished);
-       FreeStrBuf(&LineBuf);
-
-       if (flushing) {
-               syslog(LOG_ERR, "msgbase: exceeded maximum message length of %ld - message was truncated", maxlen);
-       }
-
-       return Message;
-}
-
-
-// Back end function used by CtdlMakeMessage() and similar functions
-char *CtdlReadMessageBody(char *terminator,    // token signalling EOT
-                         long tlen,
-                         size_t maxlen,        // maximum message length
-                         StrBuf *exist,        // if non-null, append to it; exist is ALWAYS freed
-                         int crlf              // CRLF newlines instead of LF
-) {
-       StrBuf *Message;
-
-       Message = CtdlReadMessageBodyBuf(terminator,
-                                        tlen,
-                                        maxlen,
-                                        exist,
-                                        crlf
-       );
-       if (Message == NULL) {
-               return NULL;
-       }
-       else {
-               return SmashStrBuf(&Message);
-       }
-}
-
-
-struct CtdlMessage *CtdlMakeMessage(
-        struct ctdluser *author,        /* author's user structure */
-        char *recipient,                /* NULL if it's not mail */
-        char *recp_cc,                 /* NULL if it's not mail */
-        char *room,                     /* room where it's going */
-        int type,                       /* see MES_ types in header file */
-        int format_type,                /* variformat, plain text, MIME... */
-        char *fake_name,                /* who we're masquerading as */
-       char *my_email,                 /* which of my email addresses to use (empty is ok) */
-        char *subject,                  /* Subject (optional) */
-       char *supplied_euid,            /* ...or NULL if this is irrelevant */
-        char *preformatted_text,        /* ...or NULL to read text from client */
-       char *references                /* Thread references */
-) {
-       return CtdlMakeMessageLen(
-               author, /* author's user structure */
-               recipient,              /* NULL if it's not mail */
-               (recipient)?strlen(recipient) : 0,
-               recp_cc,                        /* NULL if it's not mail */
-               (recp_cc)?strlen(recp_cc): 0,
-               room,                   /* room where it's going */
-               (room)?strlen(room): 0,
-               type,                   /* see MES_ types in header file */
-               format_type,            /* variformat, plain text, MIME... */
-               fake_name,              /* who we're masquerading as */
-               (fake_name)?strlen(fake_name): 0,
-               my_email,                       /* which of my email addresses to use (empty is ok) */
-               (my_email)?strlen(my_email): 0,
-               subject,                        /* Subject (optional) */
-               (subject)?strlen(subject): 0,
-               supplied_euid,          /* ...or NULL if this is irrelevant */
-               (supplied_euid)?strlen(supplied_euid):0,
-               preformatted_text,      /* ...or NULL to read text from client */
-               (preformatted_text)?strlen(preformatted_text) : 0,
-               references,             /* Thread references */
-               (references)?strlen(references):0);
-
-}
-
-
-/*
- * Build a binary message to be saved on disk.
- * (NOTE: if you supply 'preformatted_text', the buffer you give it
- * will become part of the message.  This means you are no longer
- * responsible for managing that memory -- it will be freed along with
- * the rest of the fields when CM_Free() is called.)
- */
-struct CtdlMessage *CtdlMakeMessageLen(
-       struct ctdluser *author,        /* author's user structure */
-       char *recipient,                /* NULL if it's not mail */
-       long rcplen,
-       char *recp_cc,                  /* NULL if it's not mail */
-       long cclen,
-       char *room,                     /* room where it's going */
-       long roomlen,
-       int type,                       /* see MES_ types in header file */
-       int format_type,                /* variformat, plain text, MIME... */
-       char *fake_name,                /* who we're masquerading as */
-       long fnlen,
-       char *my_email,                 /* which of my email addresses to use (empty is ok) */
-       long myelen,
-       char *subject,                  /* Subject (optional) */
-       long subjlen,
-       char *supplied_euid,            /* ...or NULL if this is irrelevant */
-       long euidlen,
-       char *preformatted_text,        /* ...or NULL to read text from client */
-       long textlen,
-       char *references,               /* Thread references */
-       long reflen
-) {
-       long blen;
-       char buf[1024];
-       struct CtdlMessage *msg;
-       StrBuf *FakeAuthor;
-       StrBuf *FakeEncAuthor = NULL;
-
-       msg = malloc(sizeof(struct CtdlMessage));
-       memset(msg, 0, sizeof(struct CtdlMessage));
-       msg->cm_magic = CTDLMESSAGE_MAGIC;
-       msg->cm_anon_type = type;
-       msg->cm_format_type = format_type;
-
-       if (recipient != NULL) rcplen = striplt(recipient);
-       if (recp_cc != NULL) cclen = striplt(recp_cc);
-
-       /* Path or Return-Path */
-       if (myelen > 0) {
-               CM_SetField(msg, eMessagePath, my_email, myelen);
-       }
-       else if (!IsEmptyStr(author->fullname)) {
-               CM_SetField(msg, eMessagePath, author->fullname, -1);
-       }
-       convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
-
-       blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
-       CM_SetField(msg, eTimestamp, buf, blen);
-
-       if (fnlen > 0) {
-               FakeAuthor = NewStrBufPlain (fake_name, fnlen);
-       }
-       else {
-               FakeAuthor = NewStrBufPlain (author->fullname, -1);
-       }
-       StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
-       CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
-       FreeStrBuf(&FakeAuthor);
-
-       if (!!IsEmptyStr(CC->room.QRname)) {
-               if (CC->room.QRflags & QR_MAILBOX) {            /* room */
-                       CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11], -1);
-               }
-               else {
-                       CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
-               }
-       }
-
-       if (rcplen > 0) {
-               CM_SetField(msg, eRecipient, recipient, rcplen);
-       }
-       if (cclen > 0) {
-               CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
-       }
-
-       if (myelen > 0) {
-               CM_SetField(msg, erFc822Addr, my_email, myelen);
-       }
-       else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
-               CM_SetField(msg, erFc822Addr, CC->cs_inet_email, -1);
-       }
-
-       if (subject != NULL) {
-               long length;
-               length = striplt(subject);
-               if (length > 0) {
-                       long i;
-                       long IsAscii;
-                       IsAscii = -1;
-                       i = 0;
-                       while ((subject[i] != '\0') &&
-                              (IsAscii = isascii(subject[i]) != 0 ))
-                               i++;
-                       if (IsAscii != 0)
-                               CM_SetField(msg, eMsgSubject, subject, subjlen);
-                       else /* ok, we've got utf8 in the string. */
-                       {
-                               char *rfc2047Subj;
-                               rfc2047Subj = rfc2047encode(subject, length);
-                               CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
-                       }
-
-               }
-       }
-
-       if (euidlen > 0) {
-               CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
-       }
-
-       if (reflen > 0) {
-               CM_SetField(msg, eWeferences, references, reflen);
-       }
-
-       if (preformatted_text != NULL) {
-               CM_SetField(msg, eMesageText, preformatted_text, textlen);
-       }
-       else {
-               StrBuf *MsgBody;
-               MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
-               if (MsgBody != NULL) {
-                       CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
-               }
-       }
-
-       return(msg);
-}
-
-
-/*
- * API function to delete messages which match a set of criteria
- * (returns the actual number of messages deleted)
- */
-int CtdlDeleteMessages(const char *room_name,  // which room
-                      long *dmsgnums,          // array of msg numbers to be deleted
-                      int num_dmsgnums,        // number of msgs to be deleted, or 0 for "any"
-                      char *content_type       // or "" for any.  regular expressions expected.
-) {
-       struct ctdlroom qrbuf;
-       struct cdbdata *cdbfr;
-       long *msglist = NULL;
-       long *dellist = NULL;
-       int num_msgs = 0;
-       int i, j;
-       int num_deleted = 0;
-       int delete_this;
-       struct MetaData smi;
-       regex_t re;
-       regmatch_t pm;
-       int need_to_free_re = 0;
-
-       if (content_type) if (!IsEmptyStr(content_type)) {
-                       regcomp(&re, content_type, 0);
-                       need_to_free_re = 1;
-               }
-       syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
-
-       /* get room record, obtaining a lock... */
-       if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
-               syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
-               if (need_to_free_re) regfree(&re);
-               return(0);      /* room not found */
-       }
-       cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
-
-       if (cdbfr != NULL) {
-               dellist = malloc(cdbfr->len);
-               msglist = (long *) cdbfr->ptr;
-               cdbfr->ptr = NULL;      /* CtdlDeleteMessages() now owns this memory */
-               num_msgs = cdbfr->len / sizeof(long);
-               cdb_free(cdbfr);
-       }
-       if (num_msgs > 0) {
-               int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
-               int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
-               int have_more_del = 1;
-
-               num_msgs = sort_msglist(msglist, num_msgs);
-               if (num_dmsgnums > 1)
-                       num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
-/*
-               {
-                       StrBuf *dbg = NewStrBuf();
-                       for (i = 0; i < num_dmsgnums; i++)
-                               StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
-                       syslog(LOG_DEBUG, "msgbase: Deleting before: %s", ChrPtr(dbg));
-                       FreeStrBuf(&dbg);
-               }
-*/
-               i = 0; j = 0;
-               while ((i < num_msgs) && (have_more_del)) {
-                       delete_this = 0x00;
-
-                       /* Set/clear a bit for each criterion */
-
-                       /* 0 messages in the list or a null list means that we are
-                        * interested in deleting any messages which meet the other criteria.
-                        */
-                       if (have_delmsgs) {
-                               delete_this |= 0x01;
-                       }
-                       else {
-                               while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
-
-                               if (i >= num_msgs)
-                                       continue;
-
-                               if (msglist[i] == dmsgnums[j]) {
-                                       delete_this |= 0x01;
-                               }
-                               j++;
-                               have_more_del = (j < num_dmsgnums);
-                       }
-
-                       if (have_contenttype) {
-                               GetMetaData(&smi, msglist[i]);
-                               if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
-                                       delete_this |= 0x02;
-                               }
-                       } else {
-                               delete_this |= 0x02;
-                       }
-
-                       /* Delete message only if all bits are set */
-                       if (delete_this == 0x03) {
-                               dellist[num_deleted++] = msglist[i];
-                               msglist[i] = 0L;
-                       }
-                       i++;
-               }
-/*
-               {
-                       StrBuf *dbg = NewStrBuf();
-                       for (i = 0; i < num_deleted; i++)
-                               StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
-                       syslog(LOG_DEBUG, "msgbase: Deleting: %s", ChrPtr(dbg));
-                       FreeStrBuf(&dbg);
-               }
-*/
-               num_msgs = sort_msglist(msglist, num_msgs);
-               cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
-                         msglist, (int)(num_msgs * sizeof(long)));
-
-               if (num_msgs > 0)
-                       qrbuf.QRhighest = msglist[num_msgs - 1];
-               else
-                       qrbuf.QRhighest = 0;
-       }
-       CtdlPutRoomLock(&qrbuf);
-
-       /* Go through the messages we pulled out of the index, and decrement
-        * their reference counts by 1.  If this is the only room the message
-        * was in, the reference count will reach zero and the message will
-        * automatically be deleted from the database.  We do this in a
-        * separate pass because there might be plug-in hooks getting called,
-        * and we don't want that happening during an S_ROOMS critical
-        * section.
-        */
-       if (num_deleted) {
-               for (i=0; i<num_deleted; ++i) {
-                       PerformDeleteHooks(qrbuf.QRname, dellist[i]);
-               }
-               AdjRefCountList(dellist, num_deleted, -1);
-       }
-       /* Now free the memory we used, and go away. */
-       if (msglist != NULL) free(msglist);
-       if (dellist != NULL) free(dellist);
-       syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
-       if (need_to_free_re) regfree(&re);
-       return (num_deleted);
-}
-
-
-/*
- * GetMetaData()  -  Get the supplementary record for a message
- */
-void GetMetaData(struct MetaData *smibuf, long msgnum)
-{
-       struct cdbdata *cdbsmi;
-       long TheIndex;
-
-       memset(smibuf, 0, sizeof(struct MetaData));
-       smibuf->meta_msgnum = msgnum;
-       smibuf->meta_refcount = 1;      /* Default reference count is 1 */
-
-       /* Use the negative of the message number for its supp record index */
-       TheIndex = (0L - msgnum);
-
-       cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
-       if (cdbsmi == NULL) {
-               return;                 /* record not found; leave it alone */
-       }
-       memcpy(smibuf, cdbsmi->ptr,
-              ((cdbsmi->len > sizeof(struct MetaData)) ?
-               sizeof(struct MetaData) : cdbsmi->len)
-       );
-       cdb_free(cdbsmi);
-       return;
-}
-
-
-/*
- * PutMetaData()  -  (re)write supplementary record for a message
- */
-void PutMetaData(struct MetaData *smibuf)
-{
-       long TheIndex;
-
-       /* Use the negative of the message number for the metadata db index */
-       TheIndex = (0L - smibuf->meta_msgnum);
-
-       cdb_store(CDB_MSGMAIN,
-                 &TheIndex, (int)sizeof(long),
-                 smibuf, (int)sizeof(struct MetaData)
-       );
-}
-
-
-/*
- * Convenience function to process a big block of AdjRefCount() operations
- */
-void AdjRefCountList(long *msgnum, long nmsg, int incr)
-{
-       long i;
-
-       for (i = 0; i < nmsg; i++) {
-               AdjRefCount(msgnum[i], incr);
-       }
-}
-
-
-/*
- * AdjRefCount - adjust the reference count for a message.  We need to delete from disk any message whose reference count reaches zero.
- */
-void AdjRefCount(long msgnum, int incr)
-{
-       struct MetaData smi;
-       long delnum;
-
-       /* This is a *tight* critical section; please keep it that way, as
-        * it may get called while nested in other critical sections.  
-        * Complicating this any further will surely cause deadlock!
-        */
-       begin_critical_section(S_SUPPMSGMAIN);
-       GetMetaData(&smi, msgnum);
-       smi.meta_refcount += incr;
-       PutMetaData(&smi);
-       end_critical_section(S_SUPPMSGMAIN);
-       syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
-
-       /* If the reference count is now zero, delete both the message and its metadata record.
-        */
-       if (smi.meta_refcount == 0) {
-               syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
-               
-               /* Call delete hooks with NULL room to show it has gone altogether */
-               PerformDeleteHooks(NULL, msgnum);
-
-               /* Remove from message base */
-               delnum = msgnum;
-               cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
-               cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
-
-               /* Remove metadata record */
-               delnum = (0L - msgnum);
-               cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
-       }
-}
-
-
-/*
- * Write a generic object to this room
- *
- * Returns the message number of the written object, in case you need it.
- */
-long CtdlWriteObject(char *req_room,                   /* Room to stuff it in */
-                    char *content_type,                /* MIME type of this object */
-                    char *raw_message,                 /* Data to be written */
-                    off_t raw_length,                  /* Size of raw_message */
-                    struct ctdluser *is_mailbox,       /* Mailbox room? */
-                    int is_binary,                     /* Is encoding necessary? */
-                    unsigned int flags                 /* Internal save flags */
-) {
-       struct ctdlroom qrbuf;
-       char roomname[ROOMNAMELEN];
-       struct CtdlMessage *msg;
-       StrBuf *encoded_message = NULL;
-
-       if (is_mailbox != NULL) {
-               CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
-       }
-       else {
-               safestrncpy(roomname, req_room, sizeof(roomname));
-       }
-
-       syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
-
-       if (is_binary) {
-               encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
-       }
-       else {
-               encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
-       }
-
-       StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
-       StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
-       StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
-
-       if (is_binary) {
-               StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
-       }
-       else {
-               StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
-       }
-
-       if (is_binary) {
-               StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
-       }
-       else {
-               StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
-       }
-
-       syslog(LOG_DEBUG, "msgbase: allocating");
-       msg = malloc(sizeof(struct CtdlMessage));
-       memset(msg, 0, sizeof(struct CtdlMessage));
-       msg->cm_magic = CTDLMESSAGE_MAGIC;
-       msg->cm_anon_type = MES_NORMAL;
-       msg->cm_format_type = 4;
-       CM_SetField(msg, eAuthor, CC->user.fullname, -1);
-       CM_SetField(msg, eOriginalRoom, req_room, -1);
-       msg->cm_flags = flags;
-       
-       CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
-
-       /* Create the requested room if we have to. */
-       if (CtdlGetRoom(&qrbuf, roomname) != 0) {
-               CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
-       }
-
-       /* Now write the data */
-       long new_msgnum = CtdlSubmitMsg(msg, NULL, roomname);
-       CM_Free(msg);
-       return new_msgnum;
-}
-
-
-/************************************************************************/
-/*                      MODULE INITIALIZATION                           */
-/************************************************************************/
-
-CTDL_MODULE_INIT(msgbase)
-{
-       if (!threading) {
-               FillMsgKeyLookupTable();
-       }
-
-        /* return our module id for the log */
-       return "msgbase";
-}
diff --git a/citadel/msgbase.h b/citadel/msgbase.h
deleted file mode 100644 (file)
index 053e43d..0000000
+++ /dev/null
@@ -1,229 +0,0 @@
-
-#ifndef MSGBASE_H
-#define MSGBASE_H
-
-enum {
-       MSGS_ALL,
-       MSGS_OLD,
-       MSGS_NEW,
-       MSGS_FIRST,
-       MSGS_LAST,
-       MSGS_GT,
-       MSGS_EQ,
-       MSGS_SEARCH,
-       MSGS_LT
-};
-
-enum {
-       MSG_HDRS_BRIEF = 0,
-       MSG_HDRS_ALL = 1,
-       MSG_HDRS_EUID = 4,
-       MSG_HDRS_BRIEFFILTER = 8,
-       MSG_HDRS_THREADS = 9
-};
-
-/*
- * Possible return codes from CtdlOutputMsg()
- */
-enum {
-       om_ok,
-       om_not_logged_in,
-       om_no_such_msg,
-       om_mime_error,
-       om_access_denied
-};
-
-/*
- * Values of "headers_only" when calling message output routines
- */
-#define HEADERS_ALL    0       /* Headers and body */
-#define        HEADERS_ONLY    1       /* Headers only */
-#define        HEADERS_NONE    2       /* Body only */
-#define HEADERS_FAST   3       /* Headers only with no MIME info */
-
-
-struct ma_info {
-       int is_ma;              /* Set to 1 if we are using this stuff */
-       int freeze;             /* Freeze the replacement chain because we're
-                                * digging through a subsection */
-       int did_print;          /* One alternative has been displayed */
-       char chosen_part[128];  /* Which part of a m/a did we choose? */
-       int chosen_pref;        /* Chosen part preference level (lower is better) */
-       int use_fo_hooks;       /* Use fixed output hooks */
-       int dont_decode;        /* should we call the decoder or not? */
-};
-
-
-struct repl {                  /* Info for replication checking */
-       char exclusive_id[SIZ];
-       time_t highest;
-};
-
-
-/*
- * This is a list of "harvested" email addresses that we might want to
- * stick into someone's address book.  But we defer this operaiton so
- * it can be done asynchronously.
- */
-struct addresses_to_be_filed {
-       struct addresses_to_be_filed *next;
-       char *roomname;
-       char *collected_addresses;
-};
-
-extern struct addresses_to_be_filed *atbf;
-
-int GetFieldFromMnemonic(eMsgField *f, const char* c);
-void memfmout (char *mptr, const char *nl);
-void output_mime_parts(char *);
-long send_message (struct CtdlMessage *);
-void loadtroom (void);
-long CtdlSubmitMsg(struct CtdlMessage *, struct recptypes *, const char *);
-long quickie_message(const char *from, const char *fromaddr, const char *to, char *room, const char *text, int format_type, const char *subject);
-void GetMetaData(struct MetaData *, long);
-void PutMetaData(struct MetaData *);
-void AdjRefCount(long, int);
-void TDAP_AdjRefCount(long, int);
-int TDAP_ProcessAdjRefCountQueue(void);
-void simple_listing(long, void *);
-int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template);
-typedef void (*ForEachMsgCallback)(long MsgNumber, void *UserData);
-int CtdlForEachMessage(int mode,
-                       long ref,
-                       char *searchstring,
-                       char *content_type,
-                       struct CtdlMessage *compare,
-                        ForEachMsgCallback CallBack,
-                       void *userdata);
-int CtdlDeleteMessages(const char *, long *, int, char *);
-long CtdlWriteObject(char *req_room,                   /* Room to stuff it in */
-                       char *content_type,             /* MIME type of this object */
-                       char *raw_message,              /* Data to be written */
-                       off_t raw_length,               /* Size of raw_message */
-                       struct ctdluser *is_mailbox,    /* Mailbox room? */
-                       int is_binary,                  /* Is encoding necessary? */
-                       unsigned int flags              /* Internal save flags */
-);
-struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body);
-struct CtdlMessage * CM_Duplicate
-                       (struct CtdlMessage *OrgMsg);
-int  CM_IsEmpty        (struct CtdlMessage *Msg, eMsgField which);
-void CM_SetField       (struct CtdlMessage *Msg, eMsgField which, const char *buf, long length);
-void CM_SetFieldLONG   (struct CtdlMessage *Msg, eMsgField which, long lvalue);
-void CM_CopyField      (struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy);
-void CM_CutFieldAt     (struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen);
-void CM_FlushField     (struct CtdlMessage *Msg, eMsgField which);
-void CM_Flush          (struct CtdlMessage *Msg);
-void CM_SetAsField     (struct CtdlMessage *Msg, eMsgField which, char **buf, long length);
-void CM_SetAsFieldSB   (struct CtdlMessage *Msg, eMsgField which, StrBuf **buf);
-void CM_GetAsField     (struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen);
-void CM_PrependToField (struct CtdlMessage *Msg, eMsgField which, const char *buf, long length);
-
-void CM_Free           (struct CtdlMessage *msg);
-void CM_FreeContents   (struct CtdlMessage *msg);
-int  CM_IsValidMsg     (struct CtdlMessage *msg);
-
-#define CM_KEY(Message, Which) Message->cm_fields[Which], Message->cm_lengths[Which]
-#define CM_RANGE(Message, Which) Message->cm_fields[Which], \
-               Message->cm_fields[Which] + Message->cm_lengths[Which]
-
-void CtdlSerializeMessage(struct ser_ret *, struct CtdlMessage *);
-struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length);
-void ReplicationChecks(struct CtdlMessage *);
-int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
-                             int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
-);
-int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, struct CtdlMessage *msg);
-long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply);
-char *CtdlReadMessageBody(char *terminator, long tlen, size_t maxlen, StrBuf *exist, int crlf);
-StrBuf *CtdlReadMessageBodyBuf(
-               char *terminator,       /* token signalling EOT */
-               long tlen,
-               size_t maxlen,          /* maximum message length */
-               StrBuf *exist,          /* if non-null, append to it; exist is ALWAYS freed  */
-               int crlf                /* CRLF newlines instead of LF */
-);
-
-int CtdlOutputMsg(long msg_num,                /* message number (local) to fetch */
-               int mode,               /* how would you like that message? */
-               int headers_only,       /* eschew the message body? */
-               int do_proto,           /* do Citadel protocol responses? */
-               int crlf,               /* 0=LF, 1=CRLF */
-               char *section,          /* output a message/rfc822 section */
-               int flags,              /* should the bessage be exported clean? */
-               char **Author,          /* if you want to know the author of the message... */
-               char **Address,         /* if you want to know the sender address of the message... */
-               char **MessageID        /* if you want to know the Message-ID of the message... */
-);
-
-/* Flags which may be passed to CtdlOutputMsg() and CtdlOutputPreLoadedMsg() */
-#define QP_EADDR       (1<<0)          /* quoted-printable encode email addresses */
-#define CRLF           (1<<1)
-#define ESC_DOT                (1<<2)          /* output a line containing only "." as ".." instead */
-#define SUPPRESS_ENV_TO        (1<<3)          /* suppress Envelope-to: header (warning: destructive!) */
-
-int CtdlOutputPreLoadedMsg(struct CtdlMessage *,
-                          int mode,            /* how would you like that message? */
-                          int headers_only,    /* eschew the message body? */
-                          int do_proto,        /* do Citadel protocol responses? */
-                          int crlf,            /* 0=LF, 1=CRLF */
-                          int flags            /* should the bessage be exported clean? */
-);
-
-
-/* values for which_set */
-enum {
-       ctdlsetseen_seen,
-       ctdlsetseen_answered
-};
-
-void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
-       int target_setting, int which_set,
-       struct ctdluser *which_user, struct ctdlroom *which_room
-);
-
-void CtdlGetSeen(char *buf, int which_set);
-
-
-struct CtdlMessage *CtdlMakeMessage(
-        struct ctdluser *author,        /* author's user structure */
-        char *recipient,                /* NULL if it's not mail */
-        char *recp_cc,                 /* NULL if it's not mail */
-        char *room,                     /* room where it's going */
-        int type,                       /* see MES_ types in header file */
-        int format_type,                /* variformat, plain text, MIME... */
-        char *fake_name,                /* who we're masquerading as */
-       char *my_email,                 /* which of my email addresses to use (empty is ok) */
-        char *subject,                  /* Subject (optional) */
-       char *supplied_euid,            /* ...or NULL if this is irrelevant */
-        char *preformatted_text,        /* ...or NULL to read text from client */
-       char *references                /* Thread references */
-);
-
-struct CtdlMessage *CtdlMakeMessageLen(
-       struct ctdluser *author,        /* author's user structure */
-       char *recipient,                /* NULL if it's not mail */
-       long rcplen,
-       char *recp_cc,                  /* NULL if it's not mail */
-       long cclen,
-       char *room,                     /* room where it's going */
-       long roomlen,
-       int type,                       /* see MES_ types in header file */
-       int format_type,                /* variformat, plain text, MIME... */
-       char *fake_name,                /* who we're masquerading as */
-       long fnlen,
-       char *my_email,                 /* which of my email addresses to use (empty is ok) */
-       long myelen,
-       char *subject,                  /* Subject (optional) */
-       long subjlen,
-       char *supplied_euid,            /* ...or NULL if this is irrelevant */
-       long euidlen,
-       char *preformatted_text,        /* ...or NULL to read text from client */
-       long textlen,
-       char *references,               /* Thread references */
-       long reflen
-);
-
-void AdjRefCountList(long *msgnum, long nmsg, int incr);
-
-#endif /* MSGBASE_H */
diff --git a/citadel/netconfig.c b/citadel/netconfig.c
deleted file mode 100644 (file)
index ab47412..0000000
+++ /dev/null
@@ -1,192 +0,0 @@
-// This module handles loading, saving, and parsing of room network configurations.
-//
-// Copyright (c) 2000-2021 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include "sysdep.h"
-#include <stdio.h>
-#include <sys/types.h>
-#include <dirent.h>
-
-#ifdef HAVE_SYSCALL_H
-# include <syscall.h>
-#else 
-# if HAVE_SYS_SYSCALL_H
-#  include <sys/syscall.h>
-# endif
-#endif
-#include <dirent.h>
-#include <assert.h>
-
-#include <libcitadel.h>
-
-#include "ctdl_module.h"
-#include "serv_extensions.h"
-#include "config.h"
-
-
-// Create a config key for a room's netconfig entry
-void netcfg_keyname(char *keybuf, long roomnum) {
-       if (!keybuf) return;
-       sprintf(keybuf, "c_netconfig_%010ld", roomnum);
-}
-
-
-// Given a room number and a textual netconfig, convert to base64 and write to the configdb
-void SaveRoomNetConfigFile(long roomnum, const char *raw_netconfig) {
-       char keyname[25];
-       char *enc;
-       int enc_len;
-       int len;
-
-       len = strlen(raw_netconfig);
-       netcfg_keyname(keyname, roomnum);
-       enc = malloc(len * 2);
-
-       if (enc) {
-               enc_len = CtdlEncodeBase64(enc, raw_netconfig, len, 0);
-               if ((enc_len > 1) && (enc[enc_len-2] == 13)) enc[enc_len-2] = 0;
-               if ((enc_len > 0) && (enc[enc_len-1] == 10)) enc[enc_len-1] = 0;
-               enc[enc_len] = 0;
-               syslog(LOG_DEBUG, "netconfig: writing key '%s' (length=%d)", keyname, enc_len);
-               CtdlSetConfigStr(keyname, enc);
-               free(enc);
-       }
-}
-
-
-// Given a room number, attempt to load the netconfig configdb entry for that room.
-// If it returns NULL, there is no netconfig.
-// Otherwise the caller owns the returned memory and is responsible for freeing it.
-char *LoadRoomNetConfigFile(long roomnum) {
-       char keyname[25];
-       char *encoded_netconfig = NULL;
-       char *decoded_netconfig = NULL;
-
-       netcfg_keyname(keyname, roomnum);
-       encoded_netconfig = CtdlGetConfigStr(keyname);
-       if (!encoded_netconfig) return NULL;
-
-       decoded_netconfig = malloc(strlen(encoded_netconfig));  // yeah, way bigger than it needs to be, but safe
-       CtdlDecodeBase64(decoded_netconfig, encoded_netconfig, strlen(encoded_netconfig));
-       return decoded_netconfig;
-}
-
-
-//-----------------------------------------------------------------------------
-//              Per room network configs : exchange with client                
-//-----------------------------------------------------------------------------
-void cmd_gnet(char *argbuf) {
-       if ( (CC->room.QRflags & QR_MAILBOX) && (CC->user.usernum == atol(CC->room.QRname)) ) {
-               // users can edit the netconfigs for their own mailbox rooms
-       }
-       else if (CtdlAccessCheck(ac_room_aide)) return;
-       
-       cprintf("%d Network settings for room #%ld <%s>\n", LISTING_FOLLOWS, CC->room.QRnumber, CC->room.QRname);
-
-       char *c = LoadRoomNetConfigFile(CC->room.QRnumber);
-       if (c) {
-               int len = strlen(c);
-               client_write(c, len);                   // Can't use cprintf() here, it has a limit of 1024 bytes
-               if (c[len] != '\n') {
-                       client_write(HKEY("\n"));
-               }
-               free(c);
-       }
-       cprintf("000\n");
-}
-
-
-void cmd_snet(char *argbuf) {
-       StrBuf *Line = NULL;
-       StrBuf *TheConfig = NULL;
-       int rc;
-
-       unbuffer_output();
-       Line = NewStrBuf();
-       TheConfig = NewStrBuf();
-       cprintf("%d send new netconfig now\n", SEND_LISTING);
-
-       while (rc = CtdlClientGetLine(Line), (rc >= 0)) {
-               if ((rc == 3) && (strcmp(ChrPtr(Line), "000") == 0))
-                       break;
-
-               StrBufAppendBuf(TheConfig, Line, 0);
-               StrBufAppendBufPlain(TheConfig, HKEY("\n"), 0);
-       }
-       FreeStrBuf(&Line);
-
-       begin_critical_section(S_NETCONFIGS);
-       SaveRoomNetConfigFile(CC->room.QRnumber, ChrPtr(TheConfig));
-       end_critical_section(S_NETCONFIGS);
-       FreeStrBuf(&TheConfig);
-}
-
-
-// Convert any legacy configuration files in the "netconfigs" directory
-void convert_legacy_netcfg_files(void) {
-       DIR *dh = NULL;
-       struct dirent *dit = NULL;
-       char filename[PATH_MAX];
-       long roomnum;
-       FILE *fp;
-       long len;
-       char *v;
-
-       dh = opendir(ctdl_netcfg_dir);
-       if (!dh) return;
-
-       syslog(LOG_INFO, "netconfig: legacy netconfig files exist - converting them!");
-
-       while (dit = readdir(dh), dit != NULL) {        // yes, we use the non-reentrant version; we're not in threaded mode yet
-               roomnum = atol(dit->d_name);
-               if (roomnum > 0) {
-                       snprintf(filename, sizeof filename, "%s/%ld", ctdl_netcfg_dir, roomnum);
-                       fp = fopen(filename, "r");
-                       if (fp) {
-                               fseek(fp, 0L, SEEK_END);
-                               len = ftell(fp);
-                               if (len > 0) {
-                                       v = malloc(len);
-                                       if (v) {
-                                               rewind(fp);
-                                               if (fread(v, len, 1, fp)) {
-                                                       SaveRoomNetConfigFile(roomnum, v);
-                                                       unlink(filename);
-                                               }
-                                               free(v);
-                                       }
-                               }
-                               else {
-                                       unlink(filename);       // zero length netconfig, just delete it
-                               }
-                               fclose(fp);
-                       }
-               }
-       }
-
-       closedir(dh);
-       rmdir(ctdl_netcfg_dir);
-}
-
-
-/*
- * Module entry point
- */
-CTDL_MODULE_INIT(netconfig)
-{
-       if (!threading)
-       {
-               convert_legacy_netcfg_files();
-               CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
-               CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
-       }
-       return "netconfig";
-}
diff --git a/citadel/parsedate.c b/citadel/parsedate.c
deleted file mode 100644 (file)
index af4f055..0000000
+++ /dev/null
@@ -1,2379 +0,0 @@
-/* A Bison parser, made by GNU Bison 3.0.4.  */
-
-/* Bison implementation for Yacc-like parsers in C
-
-   Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc.
-
-   This program is free software: you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation, either version 3 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
-
-/* As a special exception, you may create a larger work that contains
-   part or all of the Bison parser skeleton and distribute that work
-   under terms of your choice, so long as that work isn't itself a
-   parser generator using the skeleton or a modified version thereof
-   as a parser skeleton.  Alternatively, if you modify or redistribute
-   the parser skeleton itself, you may (at your option) remove this
-   special exception, which will cause the skeleton and the resulting
-   Bison output files to be licensed under the GNU General Public
-   License without this special exception.
-
-   This special exception was added by the Free Software Foundation in
-   version 2.2 of Bison.  */
-
-/* C LALR(1) parser skeleton written by Richard Stallman, by
-   simplifying the original so-called "semantic" parser.  */
-
-/* All symbols defined below should begin with yy or YY, to avoid
-   infringing on user name space.  This should be done even for local
-   variables, as they might otherwise be expanded by user macros.
-   There are some unavoidable exceptions within include files to
-   define necessary library symbols; they are noted "INFRINGES ON
-   USER NAME SPACE" below.  */
-
-/* Identify Bison output.  */
-#define YYBISON 1
-
-/* Bison version.  */
-#define YYBISON_VERSION "3.0.4"
-
-/* Skeleton name.  */
-#define YYSKELETON_NAME "yacc.c"
-
-/* Pure parsers.  */
-#define YYPURE 0
-
-/* Push parsers.  */
-#define YYPUSH 0
-
-/* Pull parsers.  */
-#define YYPULL 1
-
-
-
-
-/* Copy the first part of user declarations.  */
-#line 1 "parsedate.y" /* yacc.c:339  */
-
-/* $Revision$
-**
-**  Originally written by Steven M. Bellovin <smb@research.att.com> while
-**  at the University of North Carolina at Chapel Hill.  Later tweaked by
-**  a couple of people on Usenet.  Completely overhauled by Rich $alz
-**  <rsalz@osf.org> and Jim Berets <jberets@bbn.com> in August, 1990.
-**  Further revised (removed obsolete constructs and cleaned up timezone
-**  names) in August, 1991, by Rich.  Paul Eggert <eggert@twinsun.com>
-**  helped in September, 1992.  Art Cancro <ajc@citadel.org> cleaned
-**  it up for ANSI C in December, 1999.
-**
-**  This grammar has six shift/reduce conflicts.
-**
-**  This code is in the public domain and has no copyright.
-*/
-/* SUPPRESS 530 *//* Empty body for statement */
-/* SUPPRESS 593 on yyerrlab *//* Label was not used */
-/* SUPPRESS 593 on yynewstate *//* Label was not used */
-/* SUPPRESS 595 on yypvt *//* Automatic variable may be used before set */
-
-#include "sysdep.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <ctype.h>
-#include <time.h>
-#if HAVE_STRING_H
-# if !STDC_HEADERS && HAVE_MEMORY_H
-#  include <memory.h>
-# endif
-# include <string.h>
-#endif
-#if HAVE_STRINGS_H
-# include <strings.h>
-#endif
-
-#include "parsedate.h"
-
-int date_lex(void);
-
-#define yyparse                date_parse
-#define yylex          date_lex
-#define yyerror                date_error
-
-
-    /* See the LeapYears table in Convert. */
-#define EPOCH          1970
-#define END_OF_TIME    2038
-    /* Constants for general time calculations. */
-#define DST_OFFSET     1
-#define SECSPERDAY     (24L * 60L * 60L)
-    /* Readability for TABLE stuff. */
-#define HOUR(x)                (x * 60)
-
-#define LPAREN         '('
-#define RPAREN         ')'
-#define IS7BIT(x)      ((unsigned int)(x) < 0200)
-
-#define SIZEOF(array)  ((int)(sizeof array / sizeof array[0]))
-#define ENDOF(array)   (&array[SIZEOF(array)])
-
-
-/*
-**  An entry in the lexical lookup table.
-*/
-typedef struct _TABLE {
-    char       *name;
-    int                type;
-    time_t     value;
-} TABLE;
-
-/*
-**  Daylight-savings mode:  on, off, or not yet known.
-*/
-typedef enum _DSTMODE {
-    DSTon, DSToff, DSTmaybe
-} DSTMODE;
-
-/*
-**  Meridian:  am, pm, or 24-hour style.
-*/
-typedef enum _MERIDIAN {
-    MERam, MERpm, MER24
-} MERIDIAN;
-
-
-/*
-**  Global variables.  We could get rid of most of them by using a yacc
-**  union, but this is more efficient.  (This routine predates the
-**  yacc %union construct.)
-*/
-static const char      *yyInput;
-static DSTMODE yyDSTmode;
-static int     yyHaveDate;
-static int     yyHaveRel;
-static int     yyHaveTime;
-static time_t  yyTimezone;
-static time_t  yyDay;
-static time_t  yyHour;
-static time_t  yyMinutes;
-static time_t  yyMonth;
-static time_t  yySeconds;
-static time_t  yyYear;
-static MERIDIAN        yyMeridian;
-static time_t  yyRelMonth;
-static time_t  yyRelSeconds;
-
-
-static void            date_error(char *);
-
-#line 179 "y.tab.c" /* yacc.c:339  */
-
-# ifndef YY_NULLPTR
-#  if defined __cplusplus && 201103L <= __cplusplus
-#   define YY_NULLPTR nullptr
-#  else
-#   define YY_NULLPTR 0
-#  endif
-# endif
-
-/* Enabling verbose error messages.  */
-#ifdef YYERROR_VERBOSE
-# undef YYERROR_VERBOSE
-# define YYERROR_VERBOSE 1
-#else
-# define YYERROR_VERBOSE 0
-#endif
-
-
-/* Debug traces.  */
-#ifndef YYDEBUG
-# define YYDEBUG 0
-#endif
-#if YYDEBUG
-extern int yydebug;
-#endif
-
-/* Token type.  */
-#ifndef YYTOKENTYPE
-# define YYTOKENTYPE
-  enum yytokentype
-  {
-    tDAY = 258,
-    tDAYZONE = 259,
-    tMERIDIAN = 260,
-    tMONTH = 261,
-    tMONTH_UNIT = 262,
-    tSEC_UNIT = 263,
-    tSNUMBER = 264,
-    tUNUMBER = 265,
-    tZONE = 266
-  };
-#endif
-/* Tokens.  */
-#define tDAY 258
-#define tDAYZONE 259
-#define tMERIDIAN 260
-#define tMONTH 261
-#define tMONTH_UNIT 262
-#define tSEC_UNIT 263
-#define tSNUMBER 264
-#define tUNUMBER 265
-#define tZONE 266
-
-/* Value type.  */
-#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
-
-union YYSTYPE
-{
-#line 114 "parsedate.y" /* yacc.c:355  */
-
-    time_t             Number;
-    enum _MERIDIAN     Meridian;
-
-#line 243 "y.tab.c" /* yacc.c:355  */
-};
-
-typedef union YYSTYPE YYSTYPE;
-# define YYSTYPE_IS_TRIVIAL 1
-# define YYSTYPE_IS_DECLARED 1
-#endif
-
-
-extern YYSTYPE yylval;
-
-int yyparse (void);
-
-
-
-/* Copy the second part of user declarations.  */
-
-#line 260 "y.tab.c" /* yacc.c:358  */
-
-#ifdef short
-# undef short
-#endif
-
-#ifdef YYTYPE_UINT8
-typedef YYTYPE_UINT8 yytype_uint8;
-#else
-typedef unsigned char yytype_uint8;
-#endif
-
-#ifdef YYTYPE_INT8
-typedef YYTYPE_INT8 yytype_int8;
-#else
-typedef signed char yytype_int8;
-#endif
-
-#ifdef YYTYPE_UINT16
-typedef YYTYPE_UINT16 yytype_uint16;
-#else
-typedef unsigned short int yytype_uint16;
-#endif
-
-#ifdef YYTYPE_INT16
-typedef YYTYPE_INT16 yytype_int16;
-#else
-typedef short int yytype_int16;
-#endif
-
-#ifndef YYSIZE_T
-# ifdef __SIZE_TYPE__
-#  define YYSIZE_T __SIZE_TYPE__
-# elif defined size_t
-#  define YYSIZE_T size_t
-# elif ! defined YYSIZE_T
-#  include <stddef.h> /* INFRINGES ON USER NAME SPACE */
-#  define YYSIZE_T size_t
-# else
-#  define YYSIZE_T unsigned int
-# endif
-#endif
-
-#define YYSIZE_MAXIMUM ((YYSIZE_T) -1)
-
-#ifndef YY_
-# if defined YYENABLE_NLS && YYENABLE_NLS
-#  if ENABLE_NLS
-#   include <libintl.h> /* INFRINGES ON USER NAME SPACE */
-#   define YY_(Msgid) dgettext ("bison-runtime", Msgid)
-#  endif
-# endif
-# ifndef YY_
-#  define YY_(Msgid) Msgid
-# endif
-#endif
-
-#ifndef YY_ATTRIBUTE
-# if (defined __GNUC__                                               \
-      && (2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__)))  \
-     || defined __SUNPRO_C && 0x5110 <= __SUNPRO_C
-#  define YY_ATTRIBUTE(Spec) __attribute__(Spec)
-# else
-#  define YY_ATTRIBUTE(Spec) /* empty */
-# endif
-#endif
-
-#ifndef YY_ATTRIBUTE_PURE
-# define YY_ATTRIBUTE_PURE   YY_ATTRIBUTE ((__pure__))
-#endif
-
-#ifndef YY_ATTRIBUTE_UNUSED
-# define YY_ATTRIBUTE_UNUSED YY_ATTRIBUTE ((__unused__))
-#endif
-
-#if !defined _Noreturn \
-     && (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112)
-# if defined _MSC_VER && 1200 <= _MSC_VER
-#  define _Noreturn __declspec (noreturn)
-# else
-#  define _Noreturn YY_ATTRIBUTE ((__noreturn__))
-# endif
-#endif
-
-/* Suppress unused-variable warnings by "using" E.  */
-#if ! defined lint || defined __GNUC__
-# define YYUSE(E) ((void) (E))
-#else
-# define YYUSE(E) /* empty */
-#endif
-
-#if defined __GNUC__ && 407 <= __GNUC__ * 100 + __GNUC_MINOR__
-/* Suppress an incorrect diagnostic about yylval being uninitialized.  */
-# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
-    _Pragma ("GCC diagnostic push") \
-    _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\
-    _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
-# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
-    _Pragma ("GCC diagnostic pop")
-#else
-# define YY_INITIAL_VALUE(Value) Value
-#endif
-#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
-# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
-# define YY_IGNORE_MAYBE_UNINITIALIZED_END
-#endif
-#ifndef YY_INITIAL_VALUE
-# define YY_INITIAL_VALUE(Value) /* Nothing. */
-#endif
-
-
-#if ! defined yyoverflow || YYERROR_VERBOSE
-
-/* The parser invokes alloca or malloc; define the necessary symbols.  */
-
-# ifdef YYSTACK_USE_ALLOCA
-#  if YYSTACK_USE_ALLOCA
-#   ifdef __GNUC__
-#    define YYSTACK_ALLOC __builtin_alloca
-#   elif defined __BUILTIN_VA_ARG_INCR
-#    include <alloca.h> /* INFRINGES ON USER NAME SPACE */
-#   elif defined _AIX
-#    define YYSTACK_ALLOC __alloca
-#   elif defined _MSC_VER
-#    include <malloc.h> /* INFRINGES ON USER NAME SPACE */
-#    define alloca _alloca
-#   else
-#    define YYSTACK_ALLOC alloca
-#    if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
-#     include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
-      /* Use EXIT_SUCCESS as a witness for stdlib.h.  */
-#     ifndef EXIT_SUCCESS
-#      define EXIT_SUCCESS 0
-#     endif
-#    endif
-#   endif
-#  endif
-# endif
-
-# ifdef YYSTACK_ALLOC
-   /* Pacify GCC's 'empty if-body' warning.  */
-#  define YYSTACK_FREE(Ptr) do { /* empty */; } while (0)
-#  ifndef YYSTACK_ALLOC_MAXIMUM
-    /* The OS might guarantee only one guard page at the bottom of the stack,
-       and a page size can be as small as 4096 bytes.  So we cannot safely
-       invoke alloca (N) if N exceeds 4096.  Use a slightly smaller number
-       to allow for a few compiler-allocated temporary stack slots.  */
-#   define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
-#  endif
-# else
-#  define YYSTACK_ALLOC YYMALLOC
-#  define YYSTACK_FREE YYFREE
-#  ifndef YYSTACK_ALLOC_MAXIMUM
-#   define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
-#  endif
-#  if (defined __cplusplus && ! defined EXIT_SUCCESS \
-       && ! ((defined YYMALLOC || defined malloc) \
-             && (defined YYFREE || defined free)))
-#   include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
-#   ifndef EXIT_SUCCESS
-#    define EXIT_SUCCESS 0
-#   endif
-#  endif
-#  ifndef YYMALLOC
-#   define YYMALLOC malloc
-#   if ! defined malloc && ! defined EXIT_SUCCESS
-void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
-#   endif
-#  endif
-#  ifndef YYFREE
-#   define YYFREE free
-#   if ! defined free && ! defined EXIT_SUCCESS
-void free (void *); /* INFRINGES ON USER NAME SPACE */
-#   endif
-#  endif
-# endif
-#endif /* ! defined yyoverflow || YYERROR_VERBOSE */
-
-
-#if (! defined yyoverflow \
-     && (! defined __cplusplus \
-         || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
-
-/* A type that is properly aligned for any stack member.  */
-union yyalloc
-{
-  yytype_int16 yyss_alloc;
-  YYSTYPE yyvs_alloc;
-};
-
-/* The size of the maximum gap between one aligned stack and the next.  */
-# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1)
-
-/* The size of an array large to enough to hold all stacks, each with
-   N elements.  */
-# define YYSTACK_BYTES(N) \
-     ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \
-      + YYSTACK_GAP_MAXIMUM)
-
-# define YYCOPY_NEEDED 1
-
-/* Relocate STACK from its old location to the new one.  The
-   local variables YYSIZE and YYSTACKSIZE give the old and new number of
-   elements in the stack, and YYPTR gives the new location of the
-   stack.  Advance YYPTR to a properly aligned location for the next
-   stack.  */
-# define YYSTACK_RELOCATE(Stack_alloc, Stack)                           \
-    do                                                                  \
-      {                                                                 \
-        YYSIZE_T yynewbytes;                                            \
-        YYCOPY (&yyptr->Stack_alloc, Stack, yysize);                    \
-        Stack = &yyptr->Stack_alloc;                                    \
-        yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \
-        yyptr += yynewbytes / sizeof (*yyptr);                          \
-      }                                                                 \
-    while (0)
-
-#endif
-
-#if defined YYCOPY_NEEDED && YYCOPY_NEEDED
-/* Copy COUNT objects from SRC to DST.  The source and destination do
-   not overlap.  */
-# ifndef YYCOPY
-#  if defined __GNUC__ && 1 < __GNUC__
-#   define YYCOPY(Dst, Src, Count) \
-      __builtin_memcpy (Dst, Src, (Count) * sizeof (*(Src)))
-#  else
-#   define YYCOPY(Dst, Src, Count)              \
-      do                                        \
-        {                                       \
-          YYSIZE_T yyi;                         \
-          for (yyi = 0; yyi < (Count); yyi++)   \
-            (Dst)[yyi] = (Src)[yyi];            \
-        }                                       \
-      while (0)
-#  endif
-# endif
-#endif /* !YYCOPY_NEEDED */
-
-/* YYFINAL -- State number of the termination state.  */
-#define YYFINAL  2
-/* YYLAST -- Last index in YYTABLE.  */
-#define YYLAST   40
-
-/* YYNTOKENS -- Number of terminals.  */
-#define YYNTOKENS  15
-/* YYNNTS -- Number of nonterminals.  */
-#define YYNNTS  9
-/* YYNRULES -- Number of rules.  */
-#define YYNRULES  30
-/* YYNSTATES -- Number of states.  */
-#define YYNSTATES  44
-
-/* YYTRANSLATE[YYX] -- Symbol number corresponding to YYX as returned
-   by yylex, with out-of-bounds checking.  */
-#define YYUNDEFTOK  2
-#define YYMAXUTOK   266
-
-#define YYTRANSLATE(YYX)                                                \
-  ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK)
-
-/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM
-   as returned by yylex, without out-of-bounds checking.  */
-static const yytype_uint8 yytranslate[] =
-{
-       0,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,    14,     2,     2,    13,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,    12,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,     2,     2,     1,     2,     3,     4,
-       5,     6,     7,     8,     9,    10,    11
-};
-
-#if YYDEBUG
-  /* YYRLINE[YYN] -- Source line where rule number YYN was defined.  */
-static const yytype_uint16 yyrline[] =
-{
-       0,   128,   128,   129,   132,   141,   145,   148,   153,   165,
-     171,   178,   184,   194,   198,   202,   210,   216,   237,   241,
-     253,   257,   262,   266,   271,   278,   281,   284,   287,   292,
-     295
-};
-#endif
-
-#if YYDEBUG || YYERROR_VERBOSE || 0
-/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
-   First, the terminals, then, starting at YYNTOKENS, nonterminals.  */
-static const char *const yytname[] =
-{
-  "$end", "error", "$undefined", "tDAY", "tDAYZONE", "tMERIDIAN",
-  "tMONTH", "tMONTH_UNIT", "tSEC_UNIT", "tSNUMBER", "tUNUMBER", "tZONE",
-  "':'", "'/'", "','", "$accept", "spec", "item", "time", "zone",
-  "numzone", "date", "rel", "o_merid", YY_NULLPTR
-};
-#endif
-
-# ifdef YYPRINT
-/* YYTOKNUM[NUM] -- (External) token number corresponding to the
-   (internal) symbol number NUM (which must be that of a token).  */
-static const yytype_uint16 yytoknum[] =
-{
-       0,   256,   257,   258,   259,   260,   261,   262,   263,   264,
-     265,   266,    58,    47,    44
-};
-# endif
-
-#define YYPACT_NINF -29
-
-#define yypact_value_is_default(Yystate) \
-  (!!((Yystate) == (-29)))
-
-#define YYTABLE_NINF -1
-
-#define yytable_value_is_error(Yytable_value) \
-  0
-
-  /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
-     STATE-NUM.  */
-static const yytype_int8 yypact[] =
-{
-     -29,     1,   -29,   -11,    11,    20,    12,   -29,     4,   -29,
-     -29,    13,    16,   -29,   -29,   -29,    21,   -29,   -29,    22,
-      23,   -29,   -29,   -29,     5,   -29,   -29,    28,    25,   -29,
-      17,    24,   -29,    26,   -29,    29,   -29,   -29,    30,   -29,
-       0,   -29,   -29,   -29
-};
-
-  /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
-     Performed when YYTABLE does not specify something else to do.  Zero
-     means the default is an error.  */
-static const yytype_uint8 yydefact[] =
-{
-       2,     0,     1,     0,     0,     0,    29,     3,     4,     6,
-       7,     0,    20,    27,    25,    30,    22,    28,    26,     0,
-       0,     8,    14,    17,    13,     5,    16,     0,     0,    23,
-      29,    18,    15,     0,    21,     0,    10,     9,     0,    24,
-      29,    19,    12,    11
-};
-
-  /* YYPGOTO[NTERM-NUM].  */
-static const yytype_int8 yypgoto[] =
-{
-     -29,   -29,   -29,   -29,   -29,   -24,   -29,   -29,   -28
-};
-
-  /* YYDEFGOTO[NTERM-NUM].  */
-static const yytype_int8 yydefgoto[] =
-{
-      -1,     1,     7,     8,    25,    26,     9,    10,    21
-};
-
-  /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM.  If
-     positive, shift that token.  If negative, reduce the rule whose
-     number is the opposite.  If YYTABLE_NINF, syntax error.  */
-static const yytype_uint8 yytable[] =
-{
-      32,     2,    37,    11,     3,    15,    36,     4,    22,    23,
-       5,     6,    43,    23,    23,    24,    42,    15,    16,    17,
-      18,    12,    15,    27,    19,    20,    23,    13,    14,    35,
-      28,    29,    30,    31,    33,    34,    39,    38,     0,    40,
-      41
-};
-
-static const yytype_int8 yycheck[] =
-{
-      24,     0,    30,    14,     3,     5,    30,     6,     4,     9,
-       9,    10,    40,     9,     9,    11,    40,     5,     6,     7,
-       8,    10,     5,    10,    12,    13,     9,     7,     8,    12,
-      14,    10,    10,    10,     6,    10,    10,    13,    -1,    10,
-      10
-};
-
-  /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
-     symbol of state STATE-NUM.  */
-static const yytype_uint8 yystos[] =
-{
-       0,    16,     0,     3,     6,     9,    10,    17,    18,    21,
-      22,    14,    10,     7,     8,     5,     6,     7,     8,    12,
-      13,    23,     4,     9,    11,    19,    20,    10,    14,    10,
-      10,    10,    20,     6,    10,    12,    20,    23,    13,    10,
-      10,    10,    20,    23
-};
-
-  /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives.  */
-static const yytype_uint8 yyr1[] =
-{
-       0,    15,    16,    16,    17,    17,    17,    17,    18,    18,
-      18,    18,    18,    19,    19,    19,    19,    20,    21,    21,
-      21,    21,    21,    21,    21,    22,    22,    22,    22,    23,
-      23
-};
-
-  /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN.  */
-static const yytype_uint8 yyr2[] =
-{
-       0,     2,     0,     2,     1,     2,     1,     1,     2,     4,
-       4,     6,     6,     1,     1,     2,     1,     1,     3,     5,
-       2,     4,     2,     3,     5,     2,     2,     2,     2,     0,
-       1
-};
-
-
-#define yyerrok         (yyerrstatus = 0)
-#define yyclearin       (yychar = YYEMPTY)
-#define YYEMPTY         (-2)
-#define YYEOF           0
-
-#define YYACCEPT        goto yyacceptlab
-#define YYABORT         goto yyabortlab
-#define YYERROR         goto yyerrorlab
-
-
-#define YYRECOVERING()  (!!yyerrstatus)
-
-#define YYBACKUP(Token, Value)                                  \
-do                                                              \
-  if (yychar == YYEMPTY)                                        \
-    {                                                           \
-      yychar = (Token);                                         \
-      yylval = (Value);                                         \
-      YYPOPSTACK (yylen);                                       \
-      yystate = *yyssp;                                         \
-      goto yybackup;                                            \
-    }                                                           \
-  else                                                          \
-    {                                                           \
-      yyerror (YY_("syntax error: cannot back up")); \
-      YYERROR;                                                  \
-    }                                                           \
-while (0)
-
-/* Error token number */
-#define YYTERROR        1
-#define YYERRCODE       256
-
-
-
-/* Enable debugging if requested.  */
-#if YYDEBUG
-
-# ifndef YYFPRINTF
-#  include <stdio.h> /* INFRINGES ON USER NAME SPACE */
-#  define YYFPRINTF fprintf
-# endif
-
-# define YYDPRINTF(Args)                        \
-do {                                            \
-  if (yydebug)                                  \
-    YYFPRINTF Args;                             \
-} while (0)
-
-/* This macro is provided for backward compatibility. */
-#ifndef YY_LOCATION_PRINT
-# define YY_LOCATION_PRINT(File, Loc) ((void) 0)
-#endif
-
-
-# define YY_SYMBOL_PRINT(Title, Type, Value, Location)                    \
-do {                                                                      \
-  if (yydebug)                                                            \
-    {                                                                     \
-      YYFPRINTF (stderr, "%s ", Title);                                   \
-      yy_symbol_print (stderr,                                            \
-                  Type, Value); \
-      YYFPRINTF (stderr, "\n");                                           \
-    }                                                                     \
-} while (0)
-
-
-/*----------------------------------------.
-| Print this symbol's value on YYOUTPUT.  |
-`----------------------------------------*/
-
-static void
-yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep)
-{
-  FILE *yyo = yyoutput;
-  YYUSE (yyo);
-  if (!yyvaluep)
-    return;
-# ifdef YYPRINT
-  if (yytype < YYNTOKENS)
-    YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep);
-# endif
-  YYUSE (yytype);
-}
-
-
-/*--------------------------------.
-| Print this symbol on YYOUTPUT.  |
-`--------------------------------*/
-
-static void
-yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep)
-{
-  YYFPRINTF (yyoutput, "%s %s (",
-             yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]);
-
-  yy_symbol_value_print (yyoutput, yytype, yyvaluep);
-  YYFPRINTF (yyoutput, ")");
-}
-
-/*------------------------------------------------------------------.
-| yy_stack_print -- Print the state stack from its BOTTOM up to its |
-| TOP (included).                                                   |
-`------------------------------------------------------------------*/
-
-static void
-yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop)
-{
-  YYFPRINTF (stderr, "Stack now");
-  for (; yybottom <= yytop; yybottom++)
-    {
-      int yybot = *yybottom;
-      YYFPRINTF (stderr, " %d", yybot);
-    }
-  YYFPRINTF (stderr, "\n");
-}
-
-# define YY_STACK_PRINT(Bottom, Top)                            \
-do {                                                            \
-  if (yydebug)                                                  \
-    yy_stack_print ((Bottom), (Top));                           \
-} while (0)
-
-
-/*------------------------------------------------.
-| Report that the YYRULE is going to be reduced.  |
-`------------------------------------------------*/
-
-static void
-yy_reduce_print (yytype_int16 *yyssp, YYSTYPE *yyvsp, int yyrule)
-{
-  unsigned long int yylno = yyrline[yyrule];
-  int yynrhs = yyr2[yyrule];
-  int yyi;
-  YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n",
-             yyrule - 1, yylno);
-  /* The symbols being reduced.  */
-  for (yyi = 0; yyi < yynrhs; yyi++)
-    {
-      YYFPRINTF (stderr, "   $%d = ", yyi + 1);
-      yy_symbol_print (stderr,
-                       yystos[yyssp[yyi + 1 - yynrhs]],
-                       &(yyvsp[(yyi + 1) - (yynrhs)])
-                                              );
-      YYFPRINTF (stderr, "\n");
-    }
-}
-
-# define YY_REDUCE_PRINT(Rule)          \
-do {                                    \
-  if (yydebug)                          \
-    yy_reduce_print (yyssp, yyvsp, Rule); \
-} while (0)
-
-/* Nonzero means print parse trace.  It is left uninitialized so that
-   multiple parsers can coexist.  */
-int yydebug;
-#else /* !YYDEBUG */
-# define YYDPRINTF(Args)
-# define YY_SYMBOL_PRINT(Title, Type, Value, Location)
-# define YY_STACK_PRINT(Bottom, Top)
-# define YY_REDUCE_PRINT(Rule)
-#endif /* !YYDEBUG */
-
-
-/* YYINITDEPTH -- initial size of the parser's stacks.  */
-#ifndef YYINITDEPTH
-# define YYINITDEPTH 200
-#endif
-
-/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
-   if the built-in stack extension method is used).
-
-   Do not make this value too large; the results are undefined if
-   YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
-   evaluated with infinite-precision integer arithmetic.  */
-
-#ifndef YYMAXDEPTH
-# define YYMAXDEPTH 10000
-#endif
-
-
-#if YYERROR_VERBOSE
-
-# ifndef yystrlen
-#  if defined __GLIBC__ && defined _STRING_H
-#   define yystrlen strlen
-#  else
-/* Return the length of YYSTR.  */
-static YYSIZE_T
-yystrlen (const char *yystr)
-{
-  YYSIZE_T yylen;
-  for (yylen = 0; yystr[yylen]; yylen++)
-    continue;
-  return yylen;
-}
-#  endif
-# endif
-
-# ifndef yystpcpy
-#  if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE
-#   define yystpcpy stpcpy
-#  else
-/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in
-   YYDEST.  */
-static char *
-yystpcpy (char *yydest, const char *yysrc)
-{
-  char *yyd = yydest;
-  const char *yys = yysrc;
-
-  while ((*yyd++ = *yys++) != '\0')
-    continue;
-
-  return yyd - 1;
-}
-#  endif
-# endif
-
-# ifndef yytnamerr
-/* Copy to YYRES the contents of YYSTR after stripping away unnecessary
-   quotes and backslashes, so that it's suitable for yyerror.  The
-   heuristic is that double-quoting is unnecessary unless the string
-   contains an apostrophe, a comma, or backslash (other than
-   backslash-backslash).  YYSTR is taken from yytname.  If YYRES is
-   null, do not copy; instead, return the length of what the result
-   would have been.  */
-static YYSIZE_T
-yytnamerr (char *yyres, const char *yystr)
-{
-  if (*yystr == '"')
-    {
-      YYSIZE_T yyn = 0;
-      char const *yyp = yystr;
-
-      for (;;)
-        switch (*++yyp)
-          {
-          case '\'':
-          case ',':
-            goto do_not_strip_quotes;
-
-          case '\\':
-            if (*++yyp != '\\')
-              goto do_not_strip_quotes;
-            /* Fall through.  */
-          default:
-            if (yyres)
-              yyres[yyn] = *yyp;
-            yyn++;
-            break;
-
-          case '"':
-            if (yyres)
-              yyres[yyn] = '\0';
-            return yyn;
-          }
-    do_not_strip_quotes: ;
-    }
-
-  if (! yyres)
-    return yystrlen (yystr);
-
-  return yystpcpy (yyres, yystr) - yyres;
-}
-# endif
-
-/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message
-   about the unexpected token YYTOKEN for the state stack whose top is
-   YYSSP.
-
-   Return 0 if *YYMSG was successfully written.  Return 1 if *YYMSG is
-   not large enough to hold the message.  In that case, also set
-   *YYMSG_ALLOC to the required number of bytes.  Return 2 if the
-   required number of bytes is too large to store.  */
-static int
-yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg,
-                yytype_int16 *yyssp, int yytoken)
-{
-  YYSIZE_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]);
-  YYSIZE_T yysize = yysize0;
-  enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 };
-  /* Internationalized format string. */
-  const char *yyformat = YY_NULLPTR;
-  /* Arguments of yyformat. */
-  char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM];
-  /* Number of reported tokens (one for the "unexpected", one per
-     "expected"). */
-  int yycount = 0;
-
-  /* There are many possibilities here to consider:
-     - If this state is a consistent state with a default action, then
-       the only way this function was invoked is if the default action
-       is an error action.  In that case, don't check for expected
-       tokens because there are none.
-     - The only way there can be no lookahead present (in yychar) is if
-       this state is a consistent state with a default action.  Thus,
-       detecting the absence of a lookahead is sufficient to determine
-       that there is no unexpected or expected token to report.  In that
-       case, just report a simple "syntax error".
-     - Don't assume there isn't a lookahead just because this state is a
-       consistent state with a default action.  There might have been a
-       previous inconsistent state, consistent state with a non-default
-       action, or user semantic action that manipulated yychar.
-     - Of course, the expected token list depends on states to have
-       correct lookahead information, and it depends on the parser not
-       to perform extra reductions after fetching a lookahead from the
-       scanner and before detecting a syntax error.  Thus, state merging
-       (from LALR or IELR) and default reductions corrupt the expected
-       token list.  However, the list is correct for canonical LR with
-       one exception: it will still contain any token that will not be
-       accepted due to an error action in a later state.
-  */
-  if (yytoken != YYEMPTY)
-    {
-      int yyn = yypact[*yyssp];
-      yyarg[yycount++] = yytname[yytoken];
-      if (!yypact_value_is_default (yyn))
-        {
-          /* Start YYX at -YYN if negative to avoid negative indexes in
-             YYCHECK.  In other words, skip the first -YYN actions for
-             this state because they are default actions.  */
-          int yyxbegin = yyn < 0 ? -yyn : 0;
-          /* Stay within bounds of both yycheck and yytname.  */
-          int yychecklim = YYLAST - yyn + 1;
-          int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
-          int yyx;
-
-          for (yyx = yyxbegin; yyx < yyxend; ++yyx)
-            if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR
-                && !yytable_value_is_error (yytable[yyx + yyn]))
-              {
-                if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM)
-                  {
-                    yycount = 1;
-                    yysize = yysize0;
-                    break;
-                  }
-                yyarg[yycount++] = yytname[yyx];
-                {
-                  YYSIZE_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]);
-                  if (! (yysize <= yysize1
-                         && yysize1 <= YYSTACK_ALLOC_MAXIMUM))
-                    return 2;
-                  yysize = yysize1;
-                }
-              }
-        }
-    }
-
-  switch (yycount)
-    {
-# define YYCASE_(N, S)                      \
-      case N:                               \
-        yyformat = S;                       \
-      break
-      YYCASE_(0, YY_("syntax error"));
-      YYCASE_(1, YY_("syntax error, unexpected %s"));
-      YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s"));
-      YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s"));
-      YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s"));
-      YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"));
-# undef YYCASE_
-    }
-
-  {
-    YYSIZE_T yysize1 = yysize + yystrlen (yyformat);
-    if (! (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM))
-      return 2;
-    yysize = yysize1;
-  }
-
-  if (*yymsg_alloc < yysize)
-    {
-      *yymsg_alloc = 2 * yysize;
-      if (! (yysize <= *yymsg_alloc
-             && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM))
-        *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM;
-      return 1;
-    }
-
-  /* Avoid sprintf, as that infringes on the user's name space.
-     Don't have undefined behavior even if the translation
-     produced a string with the wrong number of "%s"s.  */
-  {
-    char *yyp = *yymsg;
-    int yyi = 0;
-    while ((*yyp = *yyformat) != '\0')
-      if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount)
-        {
-          yyp += yytnamerr (yyp, yyarg[yyi++]);
-          yyformat += 2;
-        }
-      else
-        {
-          yyp++;
-          yyformat++;
-        }
-  }
-  return 0;
-}
-#endif /* YYERROR_VERBOSE */
-
-/*-----------------------------------------------.
-| Release the memory associated to this symbol.  |
-`-----------------------------------------------*/
-
-static void
-yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep)
-{
-  YYUSE (yyvaluep);
-  if (!yymsg)
-    yymsg = "Deleting";
-  YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp);
-
-  YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
-  YYUSE (yytype);
-  YY_IGNORE_MAYBE_UNINITIALIZED_END
-}
-
-
-
-
-/* The lookahead symbol.  */
-int yychar;
-
-/* The semantic value of the lookahead symbol.  */
-YYSTYPE yylval;
-/* Number of syntax errors so far.  */
-int yynerrs;
-
-
-/*----------.
-| yyparse.  |
-`----------*/
-
-int
-yyparse (void)
-{
-    int yystate;
-    /* Number of tokens to shift before error messages enabled.  */
-    int yyerrstatus;
-
-    /* The stacks and their tools:
-       'yyss': related to states.
-       'yyvs': related to semantic values.
-
-       Refer to the stacks through separate pointers, to allow yyoverflow
-       to reallocate them elsewhere.  */
-
-    /* The state stack.  */
-    yytype_int16 yyssa[YYINITDEPTH];
-    yytype_int16 *yyss;
-    yytype_int16 *yyssp;
-
-    /* The semantic value stack.  */
-    YYSTYPE yyvsa[YYINITDEPTH];
-    YYSTYPE *yyvs;
-    YYSTYPE *yyvsp;
-
-    YYSIZE_T yystacksize;
-
-  int yyn;
-  int yyresult;
-  /* Lookahead token as an internal (translated) token number.  */
-  int yytoken = 0;
-  /* The variables used to return semantic value and location from the
-     action routines.  */
-  YYSTYPE yyval;
-
-#if YYERROR_VERBOSE
-  /* Buffer for error messages, and its allocated size.  */
-  char yymsgbuf[128];
-  char *yymsg = yymsgbuf;
-  YYSIZE_T yymsg_alloc = sizeof yymsgbuf;
-#endif
-
-#define YYPOPSTACK(N)   (yyvsp -= (N), yyssp -= (N))
-
-  /* The number of symbols on the RHS of the reduced rule.
-     Keep to zero when no symbol should be popped.  */
-  int yylen = 0;
-
-  yyssp = yyss = yyssa;
-  yyvsp = yyvs = yyvsa;
-  yystacksize = YYINITDEPTH;
-
-  YYDPRINTF ((stderr, "Starting parse\n"));
-
-  yystate = 0;
-  yyerrstatus = 0;
-  yynerrs = 0;
-  yychar = YYEMPTY; /* Cause a token to be read.  */
-  goto yysetstate;
-
-/*------------------------------------------------------------.
-| yynewstate -- Push a new state, which is found in yystate.  |
-`------------------------------------------------------------*/
- yynewstate:
-  /* In all cases, when you get here, the value and location stacks
-     have just been pushed.  So pushing a state here evens the stacks.  */
-  yyssp++;
-
- yysetstate:
-  *yyssp = yystate;
-
-  if (yyss + yystacksize - 1 <= yyssp)
-    {
-      /* Get the current used size of the three stacks, in elements.  */
-      YYSIZE_T yysize = yyssp - yyss + 1;
-
-#ifdef yyoverflow
-      {
-        /* Give user a chance to reallocate the stack.  Use copies of
-           these so that the &'s don't force the real ones into
-           memory.  */
-        YYSTYPE *yyvs1 = yyvs;
-        yytype_int16 *yyss1 = yyss;
-
-        /* Each stack pointer address is followed by the size of the
-           data in use in that stack, in bytes.  This used to be a
-           conditional around just the two extra args, but that might
-           be undefined if yyoverflow is a macro.  */
-        yyoverflow (YY_("memory exhausted"),
-                    &yyss1, yysize * sizeof (*yyssp),
-                    &yyvs1, yysize * sizeof (*yyvsp),
-                    &yystacksize);
-
-        yyss = yyss1;
-        yyvs = yyvs1;
-      }
-#else /* no yyoverflow */
-# ifndef YYSTACK_RELOCATE
-      goto yyexhaustedlab;
-# else
-      /* Extend the stack our own way.  */
-      if (YYMAXDEPTH <= yystacksize)
-        goto yyexhaustedlab;
-      yystacksize *= 2;
-      if (YYMAXDEPTH < yystacksize)
-        yystacksize = YYMAXDEPTH;
-
-      {
-        yytype_int16 *yyss1 = yyss;
-        union yyalloc *yyptr =
-          (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize));
-        if (! yyptr)
-          goto yyexhaustedlab;
-        YYSTACK_RELOCATE (yyss_alloc, yyss);
-        YYSTACK_RELOCATE (yyvs_alloc, yyvs);
-#  undef YYSTACK_RELOCATE
-        if (yyss1 != yyssa)
-          YYSTACK_FREE (yyss1);
-      }
-# endif
-#endif /* no yyoverflow */
-
-      yyssp = yyss + yysize - 1;
-      yyvsp = yyvs + yysize - 1;
-
-      YYDPRINTF ((stderr, "Stack size increased to %lu\n",
-                  (unsigned long int) yystacksize));
-
-      if (yyss + yystacksize - 1 <= yyssp)
-        YYABORT;
-    }
-
-  YYDPRINTF ((stderr, "Entering state %d\n", yystate));
-
-  if (yystate == YYFINAL)
-    YYACCEPT;
-
-  goto yybackup;
-
-/*-----------.
-| yybackup.  |
-`-----------*/
-yybackup:
-
-  /* Do appropriate processing given the current state.  Read a
-     lookahead token if we need one and don't already have one.  */
-
-  /* First try to decide what to do without reference to lookahead token.  */
-  yyn = yypact[yystate];
-  if (yypact_value_is_default (yyn))
-    goto yydefault;
-
-  /* Not known => get a lookahead token if don't already have one.  */
-
-  /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol.  */
-  if (yychar == YYEMPTY)
-    {
-      YYDPRINTF ((stderr, "Reading a token: "));
-      yychar = yylex ();
-    }
-
-  if (yychar <= YYEOF)
-    {
-      yychar = yytoken = YYEOF;
-      YYDPRINTF ((stderr, "Now at end of input.\n"));
-    }
-  else
-    {
-      yytoken = YYTRANSLATE (yychar);
-      YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
-    }
-
-  /* If the proper action on seeing token YYTOKEN is to reduce or to
-     detect an error, take that action.  */
-  yyn += yytoken;
-  if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
-    goto yydefault;
-  yyn = yytable[yyn];
-  if (yyn <= 0)
-    {
-      if (yytable_value_is_error (yyn))
-        goto yyerrlab;
-      yyn = -yyn;
-      goto yyreduce;
-    }
-
-  /* Count tokens shifted since error; after three, turn off error
-     status.  */
-  if (yyerrstatus)
-    yyerrstatus--;
-
-  /* Shift the lookahead token.  */
-  YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
-
-  /* Discard the shifted token.  */
-  yychar = YYEMPTY;
-
-  yystate = yyn;
-  YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
-  *++yyvsp = yylval;
-  YY_IGNORE_MAYBE_UNINITIALIZED_END
-
-  goto yynewstate;
-
-
-/*-----------------------------------------------------------.
-| yydefault -- do the default action for the current state.  |
-`-----------------------------------------------------------*/
-yydefault:
-  yyn = yydefact[yystate];
-  if (yyn == 0)
-    goto yyerrlab;
-  goto yyreduce;
-
-
-/*-----------------------------.
-| yyreduce -- Do a reduction.  |
-`-----------------------------*/
-yyreduce:
-  /* yyn is the number of a rule to reduce with.  */
-  yylen = yyr2[yyn];
-
-  /* If YYLEN is nonzero, implement the default value of the action:
-     '$$ = $1'.
-
-     Otherwise, the following line sets YYVAL to garbage.
-     This behavior is undocumented and Bison
-     users should not rely upon it.  Assigning to YYVAL
-     unconditionally makes the parser a bit smaller, and it avoids a
-     GCC warning that YYVAL may be used uninitialized.  */
-  yyval = yyvsp[1-yylen];
-
-
-  YY_REDUCE_PRINT (yyn);
-  switch (yyn)
-    {
-        case 4:
-#line 132 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyHaveTime++;
-#ifdef lint
-           /* I am compulsive about lint natterings... */
-           if (yyHaveTime == -1) {
-               YYERROR;
-           }
-#endif /* lint */
-       }
-#line 1366 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 5:
-#line 141 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyHaveTime++;
-           yyTimezone = (yyvsp[0].Number);
-       }
-#line 1375 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 6:
-#line 145 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyHaveDate++;
-       }
-#line 1383 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 7:
-#line 148 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyHaveRel = 1;
-       }
-#line 1391 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 8:
-#line 153 "parsedate.y" /* yacc.c:1646  */
-    {
-           if ((yyvsp[-1].Number) < 100) {
-               yyHour = (yyvsp[-1].Number);
-               yyMinutes = 0;
-           }
-           else {
-               yyHour = (yyvsp[-1].Number) / 100;
-               yyMinutes = (yyvsp[-1].Number) % 100;
-           }
-           yySeconds = 0;
-           yyMeridian = (yyvsp[0].Meridian);
-       }
-#line 1408 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 9:
-#line 165 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyHour = (yyvsp[-3].Number);
-           yyMinutes = (yyvsp[-1].Number);
-           yySeconds = 0;
-           yyMeridian = (yyvsp[0].Meridian);
-       }
-#line 1419 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 10:
-#line 171 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyHour = (yyvsp[-3].Number);
-           yyMinutes = (yyvsp[-1].Number);
-           yyTimezone = (yyvsp[0].Number);
-           yyMeridian = MER24;
-           yyDSTmode = DSToff;
-       }
-#line 1431 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 11:
-#line 178 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyHour = (yyvsp[-5].Number);
-           yyMinutes = (yyvsp[-3].Number);
-           yySeconds = (yyvsp[-1].Number);
-           yyMeridian = (yyvsp[0].Meridian);
-       }
-#line 1442 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 12:
-#line 184 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyHour = (yyvsp[-5].Number);
-           yyMinutes = (yyvsp[-3].Number);
-           yySeconds = (yyvsp[-1].Number);
-           yyTimezone = (yyvsp[0].Number);
-           yyMeridian = MER24;
-           yyDSTmode = DSToff;
-       }
-#line 1455 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 13:
-#line 194 "parsedate.y" /* yacc.c:1646  */
-    {
-           (yyval.Number) = (yyvsp[0].Number);
-           yyDSTmode = DSToff;
-       }
-#line 1464 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 14:
-#line 198 "parsedate.y" /* yacc.c:1646  */
-    {
-           (yyval.Number) = (yyvsp[0].Number);
-           yyDSTmode = DSTon;
-       }
-#line 1473 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 15:
-#line 202 "parsedate.y" /* yacc.c:1646  */
-    {
-           /* Only allow "GMT+300" and "GMT-0800" */
-           if ((yyvsp[-1].Number) != 0) {
-               YYABORT;
-           }
-           (yyval.Number) = (yyvsp[0].Number);
-           yyDSTmode = DSToff;
-       }
-#line 1486 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 16:
-#line 210 "parsedate.y" /* yacc.c:1646  */
-    {
-           (yyval.Number) = (yyvsp[0].Number);
-           yyDSTmode = DSToff;
-       }
-#line 1495 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 17:
-#line 216 "parsedate.y" /* yacc.c:1646  */
-    {
-           int         i;
-
-           /* Unix and GMT and numeric timezones -- a little confusing. */
-           if ((yyvsp[0].Number) < 0) {
-               /* Don't work with negative modulus. */
-               (yyvsp[0].Number) = -(yyvsp[0].Number);
-               if ((yyvsp[0].Number) > 9999 || (i = (yyvsp[0].Number) % 100) >= 60) {
-                   YYABORT;
-               }
-               (yyval.Number) = ((yyvsp[0].Number) / 100) * 60 + i;
-           }
-           else {
-               if ((yyvsp[0].Number) > 9999 || (i = (yyvsp[0].Number) % 100) >= 60) {
-                   YYABORT;
-               }
-               (yyval.Number) = -(((yyvsp[0].Number) / 100) * 60 + i);
-           }
-       }
-#line 1519 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 18:
-#line 237 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyMonth = (yyvsp[-2].Number);
-           yyDay = (yyvsp[0].Number);
-       }
-#line 1528 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 19:
-#line 241 "parsedate.y" /* yacc.c:1646  */
-    {
-           if ((yyvsp[-4].Number) > 100) {
-               yyYear = (yyvsp[-4].Number);
-               yyMonth = (yyvsp[-2].Number);
-               yyDay = (yyvsp[0].Number);
-           }
-           else {
-               yyMonth = (yyvsp[-4].Number);
-               yyDay = (yyvsp[-2].Number);
-               yyYear = (yyvsp[0].Number);
-           }
-       }
-#line 1545 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 20:
-#line 253 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyMonth = (yyvsp[-1].Number);
-           yyDay = (yyvsp[0].Number);
-       }
-#line 1554 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 21:
-#line 257 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyMonth = (yyvsp[-3].Number);
-           yyDay = (yyvsp[-2].Number);
-           yyYear = (yyvsp[0].Number);
-       }
-#line 1564 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 22:
-#line 262 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyDay = (yyvsp[-1].Number);
-           yyMonth = (yyvsp[0].Number);
-       }
-#line 1573 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 23:
-#line 266 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyDay = (yyvsp[-2].Number);
-           yyMonth = (yyvsp[-1].Number);
-           yyYear = (yyvsp[0].Number);
-       }
-#line 1583 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 24:
-#line 271 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyDay = (yyvsp[-2].Number);
-           yyMonth = (yyvsp[-1].Number);
-           yyYear = (yyvsp[0].Number);
-       }
-#line 1593 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 25:
-#line 278 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyRelSeconds += (yyvsp[-1].Number) * (yyvsp[0].Number);
-       }
-#line 1601 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 26:
-#line 281 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyRelSeconds += (yyvsp[-1].Number) * (yyvsp[0].Number);
-       }
-#line 1609 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 27:
-#line 284 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyRelMonth += (yyvsp[-1].Number) * (yyvsp[0].Number);
-       }
-#line 1617 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 28:
-#line 287 "parsedate.y" /* yacc.c:1646  */
-    {
-           yyRelMonth += (yyvsp[-1].Number) * (yyvsp[0].Number);
-       }
-#line 1625 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 29:
-#line 292 "parsedate.y" /* yacc.c:1646  */
-    {
-           (yyval.Meridian) = MER24;
-       }
-#line 1633 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-  case 30:
-#line 295 "parsedate.y" /* yacc.c:1646  */
-    {
-           (yyval.Meridian) = (yyvsp[0].Meridian);
-       }
-#line 1641 "y.tab.c" /* yacc.c:1646  */
-    break;
-
-
-#line 1645 "y.tab.c" /* yacc.c:1646  */
-      default: break;
-    }
-  /* User semantic actions sometimes alter yychar, and that requires
-     that yytoken be updated with the new translation.  We take the
-     approach of translating immediately before every use of yytoken.
-     One alternative is translating here after every semantic action,
-     but that translation would be missed if the semantic action invokes
-     YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or
-     if it invokes YYBACKUP.  In the case of YYABORT or YYACCEPT, an
-     incorrect destructor might then be invoked immediately.  In the
-     case of YYERROR or YYBACKUP, subsequent parser actions might lead
-     to an incorrect destructor call or verbose syntax error message
-     before the lookahead is translated.  */
-  YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
-
-  YYPOPSTACK (yylen);
-  yylen = 0;
-  YY_STACK_PRINT (yyss, yyssp);
-
-  *++yyvsp = yyval;
-
-  /* Now 'shift' the result of the reduction.  Determine what state
-     that goes to, based on the state we popped back to and the rule
-     number reduced by.  */
-
-  yyn = yyr1[yyn];
-
-  yystate = yypgoto[yyn - YYNTOKENS] + *yyssp;
-  if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp)
-    yystate = yytable[yystate];
-  else
-    yystate = yydefgoto[yyn - YYNTOKENS];
-
-  goto yynewstate;
-
-
-/*--------------------------------------.
-| yyerrlab -- here on detecting error.  |
-`--------------------------------------*/
-yyerrlab:
-  /* Make sure we have latest lookahead translation.  See comments at
-     user semantic actions for why this is necessary.  */
-  yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar);
-
-  /* If not already recovering from an error, report this error.  */
-  if (!yyerrstatus)
-    {
-      ++yynerrs;
-#if ! YYERROR_VERBOSE
-      yyerror (YY_("syntax error"));
-#else
-# define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \
-                                        yyssp, yytoken)
-      {
-        char const *yymsgp = YY_("syntax error");
-        int yysyntax_error_status;
-        yysyntax_error_status = YYSYNTAX_ERROR;
-        if (yysyntax_error_status == 0)
-          yymsgp = yymsg;
-        else if (yysyntax_error_status == 1)
-          {
-            if (yymsg != yymsgbuf)
-              YYSTACK_FREE (yymsg);
-            yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc);
-            if (!yymsg)
-              {
-                yymsg = yymsgbuf;
-                yymsg_alloc = sizeof yymsgbuf;
-                yysyntax_error_status = 2;
-              }
-            else
-              {
-                yysyntax_error_status = YYSYNTAX_ERROR;
-                yymsgp = yymsg;
-              }
-          }
-        yyerror (yymsgp);
-        if (yysyntax_error_status == 2)
-          goto yyexhaustedlab;
-      }
-# undef YYSYNTAX_ERROR
-#endif
-    }
-
-
-
-  if (yyerrstatus == 3)
-    {
-      /* If just tried and failed to reuse lookahead token after an
-         error, discard it.  */
-
-      if (yychar <= YYEOF)
-        {
-          /* Return failure if at end of input.  */
-          if (yychar == YYEOF)
-            YYABORT;
-        }
-      else
-        {
-          yydestruct ("Error: discarding",
-                      yytoken, &yylval);
-          yychar = YYEMPTY;
-        }
-    }
-
-  /* Else will try to reuse lookahead token after shifting the error
-     token.  */
-  goto yyerrlab1;
-
-
-/*---------------------------------------------------.
-| yyerrorlab -- error raised explicitly by YYERROR.  |
-`---------------------------------------------------*/
-yyerrorlab:
-
-  /* Pacify compilers like GCC when the user code never invokes
-     YYERROR and the label yyerrorlab therefore never appears in user
-     code.  */
-  if (/*CONSTCOND*/ 0)
-     goto yyerrorlab;
-
-  /* Do not reclaim the symbols of the rule whose action triggered
-     this YYERROR.  */
-  YYPOPSTACK (yylen);
-  yylen = 0;
-  YY_STACK_PRINT (yyss, yyssp);
-  yystate = *yyssp;
-  goto yyerrlab1;
-
-
-/*-------------------------------------------------------------.
-| yyerrlab1 -- common code for both syntax error and YYERROR.  |
-`-------------------------------------------------------------*/
-yyerrlab1:
-  yyerrstatus = 3;      /* Each real token shifted decrements this.  */
-
-  for (;;)
-    {
-      yyn = yypact[yystate];
-      if (!yypact_value_is_default (yyn))
-        {
-          yyn += YYTERROR;
-          if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR)
-            {
-              yyn = yytable[yyn];
-              if (0 < yyn)
-                break;
-            }
-        }
-
-      /* Pop the current state because it cannot handle the error token.  */
-      if (yyssp == yyss)
-        YYABORT;
-
-
-      yydestruct ("Error: popping",
-                  yystos[yystate], yyvsp);
-      YYPOPSTACK (1);
-      yystate = *yyssp;
-      YY_STACK_PRINT (yyss, yyssp);
-    }
-
-  YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
-  *++yyvsp = yylval;
-  YY_IGNORE_MAYBE_UNINITIALIZED_END
-
-
-  /* Shift the error token.  */
-  YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp);
-
-  yystate = yyn;
-  goto yynewstate;
-
-
-/*-------------------------------------.
-| yyacceptlab -- YYACCEPT comes here.  |
-`-------------------------------------*/
-yyacceptlab:
-  yyresult = 0;
-  goto yyreturn;
-
-/*-----------------------------------.
-| yyabortlab -- YYABORT comes here.  |
-`-----------------------------------*/
-yyabortlab:
-  yyresult = 1;
-  goto yyreturn;
-
-#if !defined yyoverflow || YYERROR_VERBOSE
-/*-------------------------------------------------.
-| yyexhaustedlab -- memory exhaustion comes here.  |
-`-------------------------------------------------*/
-yyexhaustedlab:
-  yyerror (YY_("memory exhausted"));
-  yyresult = 2;
-  /* Fall through.  */
-#endif
-
-yyreturn:
-  if (yychar != YYEMPTY)
-    {
-      /* Make sure we have latest lookahead translation.  See comments at
-         user semantic actions for why this is necessary.  */
-      yytoken = YYTRANSLATE (yychar);
-      yydestruct ("Cleanup: discarding lookahead",
-                  yytoken, &yylval);
-    }
-  /* Do not reclaim the symbols of the rule whose action triggered
-     this YYABORT or YYACCEPT.  */
-  YYPOPSTACK (yylen);
-  YY_STACK_PRINT (yyss, yyssp);
-  while (yyssp != yyss)
-    {
-      yydestruct ("Cleanup: popping",
-                  yystos[*yyssp], yyvsp);
-      YYPOPSTACK (1);
-    }
-#ifndef yyoverflow
-  if (yyss != yyssa)
-    YYSTACK_FREE (yyss);
-#endif
-#if YYERROR_VERBOSE
-  if (yymsg != yymsgbuf)
-    YYSTACK_FREE (yymsg);
-#endif
-  return yyresult;
-}
-#line 300 "parsedate.y" /* yacc.c:1906  */
-
-
-/* Month and day table. */
-static TABLE   MonthDayTable[] = {
-    { "january",       tMONTH,  1 },
-    { "february",      tMONTH,  2 },
-    { "march",         tMONTH,  3 },
-    { "april",         tMONTH,  4 },
-    { "may",           tMONTH,  5 },
-    { "june",          tMONTH,  6 },
-    { "july",          tMONTH,  7 },
-    { "august",                tMONTH,  8 },
-    { "september",     tMONTH,  9 },
-    { "october",       tMONTH, 10 },
-    { "november",      tMONTH, 11 },
-    { "december",      tMONTH, 12 },
-       /* The value of the day isn't used... */
-    { "sunday",                tDAY, 0 },
-    { "monday",                tDAY, 0 },
-    { "tuesday",       tDAY, 0 },
-    { "wednesday",     tDAY, 0 },
-    { "thursday",      tDAY, 0 },
-    { "friday",                tDAY, 0 },
-    { "saturday",      tDAY, 0 },
-};
-
-/* Time units table. */
-static TABLE   UnitsTable[] = {
-    { "year",          tMONTH_UNIT,    12 },
-    { "month",         tMONTH_UNIT,    1 },
-    { "week",          tSEC_UNIT,      7L * 24 * 60 * 60 },
-    { "day",           tSEC_UNIT,      1L * 24 * 60 * 60 },
-    { "hour",          tSEC_UNIT,      60 * 60 },
-    { "minute",                tSEC_UNIT,      60 },
-    { "min",           tSEC_UNIT,      60 },
-    { "second",                tSEC_UNIT,      1 },
-    { "sec",           tSEC_UNIT,      1 },
-};
-
-/* Timezone table. */
-static TABLE   TimezoneTable[] = {
-    { "gmt",   tZONE,     HOUR( 0) },  /* Greenwich Mean */
-    { "ut",    tZONE,     HOUR( 0) },  /* Universal */
-    { "utc",   tZONE,     HOUR( 0) },  /* Universal Coordinated */
-    { "cut",   tZONE,     HOUR( 0) },  /* Coordinated Universal */
-    { "z",     tZONE,     HOUR( 0) },  /* Greenwich Mean */
-    { "wet",   tZONE,     HOUR( 0) },  /* Western European */
-    { "bst",   tDAYZONE,  HOUR( 0) },  /* British Summer */
-    { "nst",   tZONE,     HOUR(3)+30 }, /* Newfoundland Standard */
-    { "ndt",   tDAYZONE,  HOUR(3)+30 }, /* Newfoundland Daylight */
-    { "ast",   tZONE,     HOUR( 4) },  /* Atlantic Standard */
-    { "adt",   tDAYZONE,  HOUR( 4) },  /* Atlantic Daylight */
-    { "est",   tZONE,     HOUR( 5) },  /* Eastern Standard */
-    { "edt",   tDAYZONE,  HOUR( 5) },  /* Eastern Daylight */
-    { "cst",   tZONE,     HOUR( 6) },  /* Central Standard */
-    { "cdt",   tDAYZONE,  HOUR( 6) },  /* Central Daylight */
-    { "mst",   tZONE,     HOUR( 7) },  /* Mountain Standard */
-    { "mdt",   tDAYZONE,  HOUR( 7) },  /* Mountain Daylight */
-    { "pst",   tZONE,     HOUR( 8) },  /* Pacific Standard */
-    { "pdt",   tDAYZONE,  HOUR( 8) },  /* Pacific Daylight */
-    { "yst",   tZONE,     HOUR( 9) },  /* Yukon Standard */
-    { "ydt",   tDAYZONE,  HOUR( 9) },  /* Yukon Daylight */
-    { "akst",  tZONE,     HOUR( 9) },  /* Alaska Standard */
-    { "akdt",  tDAYZONE,  HOUR( 9) },  /* Alaska Daylight */
-    { "hst",   tZONE,     HOUR(10) },  /* Hawaii Standard */
-    { "hast",  tZONE,     HOUR(10) },  /* Hawaii-Aleutian Standard */
-    { "hadt",  tDAYZONE,  HOUR(10) },  /* Hawaii-Aleutian Daylight */
-    { "ces",   tDAYZONE,  -HOUR(1) },  /* Central European Summer */
-    { "cest",  tDAYZONE,  -HOUR(1) },  /* Central European Summer */
-    { "mez",   tZONE,     -HOUR(1) },  /* Middle European */
-    { "mezt",  tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
-    { "cet",   tZONE,     -HOUR(1) },  /* Central European */
-    { "met",   tZONE,     -HOUR(1) },  /* Middle European */
-    { "eet",   tZONE,     -HOUR(2) },  /* Eastern Europe */
-    { "msk",   tZONE,     -HOUR(3) },  /* Moscow Winter */
-    { "msd",   tDAYZONE,  -HOUR(3) },  /* Moscow Summer */
-    { "wast",  tZONE,     -HOUR(8) },  /* West Australian Standard */
-    { "wadt",  tDAYZONE,  -HOUR(8) },  /* West Australian Daylight */
-    { "hkt",   tZONE,     -HOUR(8) },  /* Hong Kong */
-    { "cct",   tZONE,     -HOUR(8) },  /* China Coast */
-    { "jst",   tZONE,     -HOUR(9) },  /* Japan Standard */
-    { "kst",   tZONE,     -HOUR(9) },  /* Korean Standard */
-    { "kdt",   tZONE,     -HOUR(9) },  /* Korean Daylight */
-    { "cast",  tZONE,     -(HOUR(9)+30) }, /* Central Australian Standard */
-    { "cadt",  tDAYZONE,  -(HOUR(9)+30) }, /* Central Australian Daylight */
-    { "east",  tZONE,     -HOUR(10) }, /* Eastern Australian Standard */
-    { "eadt",  tDAYZONE,  -HOUR(10) }, /* Eastern Australian Daylight */
-    { "nzst",  tZONE,     -HOUR(12) }, /* New Zealand Standard */
-    { "nzdt",  tDAYZONE,  -HOUR(12) }, /* New Zealand Daylight */
-
-    /* For completeness we include the following entries. */
-#if 0
-
-    /* Duplicate names.  Either they conflict with a zone listed above
-     * (which is either more likely to be seen or just been in circulation
-     * longer), or they conflict with another zone in this section and
-     * we could not reasonably choose one over the other. */
-    { "fst",   tZONE,     HOUR( 2) },  /* Fernando De Noronha Standard */
-    { "fdt",   tDAYZONE,  HOUR( 2) },  /* Fernando De Noronha Daylight */
-    { "bst",   tZONE,     HOUR( 3) },  /* Brazil Standard */
-    { "est",   tZONE,     HOUR( 3) },  /* Eastern Standard (Brazil) */
-    { "edt",   tDAYZONE,  HOUR( 3) },  /* Eastern Daylight (Brazil) */
-    { "wst",   tZONE,     HOUR( 4) },  /* Western Standard (Brazil) */
-    { "wdt",   tDAYZONE,  HOUR( 4) },  /* Western Daylight (Brazil) */
-    { "cst",   tZONE,     HOUR( 5) },  /* Chile Standard */
-    { "cdt",   tDAYZONE,  HOUR( 5) },  /* Chile Daylight */
-    { "ast",   tZONE,     HOUR( 5) },  /* Acre Standard */
-    { "adt",   tDAYZONE,  HOUR( 5) },  /* Acre Daylight */
-    { "cst",   tZONE,     HOUR( 5) },  /* Cuba Standard */
-    { "cdt",   tDAYZONE,  HOUR( 5) },  /* Cuba Daylight */
-    { "est",   tZONE,     HOUR( 6) },  /* Easter Island Standard */
-    { "edt",   tDAYZONE,  HOUR( 6) },  /* Easter Island Daylight */
-    { "sst",   tZONE,     HOUR(11) },  /* Samoa Standard */
-    { "ist",   tZONE,     -HOUR(2) },  /* Israel Standard */
-    { "idt",   tDAYZONE,  -HOUR(2) },  /* Israel Daylight */
-    { "idt",   tDAYZONE,  -(HOUR(3)+30) }, /* Iran Daylight */
-    { "ist",   tZONE,     -(HOUR(3)+30) }, /* Iran Standard */
-    { "cst",    tZONE,     -HOUR(8) }, /* China Standard */
-    { "cdt",    tDAYZONE,  -HOUR(8) }, /* China Daylight */
-    { "sst",    tZONE,     -HOUR(8) }, /* Singapore Standard */
-
-    /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
-    { "gst",   tZONE,     HOUR( 3) },  /* Greenland Standard */
-    { "wat",   tZONE,     -HOUR(1) },  /* West Africa */
-    { "at",    tZONE,     HOUR( 2) },  /* Azores */
-    { "gst",   tZONE,     -HOUR(10) }, /* Guam Standard */
-    { "nft",   tZONE,     HOUR(3)+30 }, /* Newfoundland */
-    { "idlw",  tZONE,     HOUR(12) },  /* International Date Line West */
-    { "mewt",  tZONE,     -HOUR(1) },  /* Middle European Winter */
-    { "mest",  tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
-    { "swt",   tZONE,     -HOUR(1) },  /* Swedish Winter */
-    { "sst",   tDAYZONE,  -HOUR(1) },  /* Swedish Summer */
-    { "fwt",   tZONE,     -HOUR(1) },  /* French Winter */
-    { "fst",   tDAYZONE,  -HOUR(1) },  /* French Summer */
-    { "bt",    tZONE,     -HOUR(3) },  /* Baghdad */
-    { "it",    tZONE,     -(HOUR(3)+30) }, /* Iran */
-    { "zp4",   tZONE,     -HOUR(4) },  /* USSR Zone 3 */
-    { "zp5",   tZONE,     -HOUR(5) },  /* USSR Zone 4 */
-    { "ist",   tZONE,     -(HOUR(5)+30) }, /* Indian Standard */
-    { "zp6",   tZONE,     -HOUR(6) },  /* USSR Zone 5 */
-    { "nst",   tZONE,     -HOUR(7) },  /* North Sumatra */
-    { "sst",   tZONE,     -HOUR(7) },  /* South Sumatra */
-    { "jt",    tZONE,     -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
-    { "nzt",   tZONE,     -HOUR(12) }, /* New Zealand */
-    { "idle",  tZONE,     -HOUR(12) }, /* International Date Line East */
-    { "cat",   tZONE,     HOUR(10) },  /* -- expired 1967 */
-    { "nt",    tZONE,     HOUR(11) },  /* -- expired 1967 */
-    { "ahst",  tZONE,     HOUR(10) },  /* -- expired 1983 */
-    { "hdt",   tDAYZONE,  HOUR(10) },  /* -- expired 1986 */
-#endif /* 0 */
-};
-
-
-/* ARGSUSED */
-static void
-date_error(char *s)
-{
-    /* NOTREACHED */
-}
-
-
-static time_t
-ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
-{
-    if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 61)
-       return -1;
-    if (Meridian == MER24) {
-       if (Hours < 0 || Hours > 23)
-           return -1;
-    }
-    else {
-       if (Hours < 1 || Hours > 12)
-           return -1;
-       if (Hours == 12)
-           Hours = 0;
-       if (Meridian == MERpm)
-           Hours += 12;
-    }
-    return (Hours * 60L + Minutes) * 60L + Seconds;
-}
-
-
-static time_t
-Convert(time_t Month, time_t Day, time_t Year,
-       time_t Hours, time_t Minutes, time_t Seconds,
-       MERIDIAN Meridian, DSTMODE dst)
-{
-    static int DaysNormal[13] = {
-       0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
-    };
-    static int DaysLeap[13] = {
-       0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
-    };
-    static int LeapYears[] = {
-       1972, 1976, 1980, 1984, 1988, 1992, 1996,
-       2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
-    };
-    register int       *yp;
-    register int       *mp;
-    register time_t    Julian;
-    register int       i;
-    time_t             tod;
-
-    if (Year < 0)
-       Year = -Year;
-    if (Year < 100)
-       Year += 1900;
-    if (Year < EPOCH)
-       Year += 100;
-    for (mp = DaysNormal, yp = LeapYears; yp < ENDOF(LeapYears); yp++)
-       if (Year == *yp) {
-           mp = DaysLeap;
-           break;
-       }
-    if (Year < EPOCH || Year > END_OF_TIME
-     || Month < 1 || Month > 12
-     /* NOSTRICT *//* conversion from long may lose accuracy */
-     || Day < 1 || Day > mp[(int)Month])
-       return -1;
-
-    Julian = Day - 1 + (Year - EPOCH) * 365;
-    for (yp = LeapYears; yp < ENDOF(LeapYears); yp++, Julian++)
-       if (Year <= *yp)
-           break;
-    for (i = 1; i < Month; i++)
-       Julian += *++mp;
-    Julian *= SECSPERDAY;
-    Julian += yyTimezone * 60L;
-    if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
-       return -1;
-    Julian += tod;
-    tod = Julian;
-    if (dst == DSTon || (dst == DSTmaybe && localtime(&tod)->tm_isdst))
-       Julian -= DST_OFFSET * 60L * 60L;
-    return Julian;
-}
-
-
-static time_t
-DSTcorrect(time_t Start, time_t Future)
-{
-    time_t     StartDay;
-    time_t     FutureDay;
-
-    StartDay = (localtime(&Start)->tm_hour + 1) % 24;
-    FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
-    return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60L * 60L;
-}
-
-
-static time_t
-RelativeMonth(time_t Start, time_t RelMonth)
-{
-    struct tm  *tm;
-    time_t     Month;
-    time_t     Year;
-
-    tm = localtime(&Start);
-    Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
-    Year = Month / 12;
-    Month = Month % 12 + 1;
-    return DSTcorrect(Start,
-           Convert(Month, (time_t)tm->tm_mday, Year,
-               (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
-               MER24, DSTmaybe));
-}
-
-
-static int
-LookupWord(char *buff, register int length)
-{
-    register char      *p;
-    register char      *q;
-    register TABLE     *tp;
-    register int       c;
-
-    p = buff;
-    c = p[0];
-
-    /* See if we have an abbreviation for a month. */
-    if (length == 3 || (length == 4 && p[3] == '.'))
-       for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
-           q = tp->name;
-           if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
-               yylval.Number = tp->value;
-               return tp->type;
-           }
-       }
-    else
-       for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++)
-           if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
-               yylval.Number = tp->value;
-               return tp->type;
-           }
-
-    /* Try for a timezone. */
-    for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
-       if (c == tp->name[0] && p[1] == tp->name[1]
-        && strcmp(p, tp->name) == 0) {
-           yylval.Number = tp->value;
-           return tp->type;
-       }
-
-    /* Try the units table. */
-    for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
-       if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
-           yylval.Number = tp->value;
-           return tp->type;
-       }
-
-    /* Strip off any plural and try the units table again. */
-    if (--length > 0 && p[length] == 's') {
-       p[length] = '\0';
-       for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
-           if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
-               p[length] = 's';
-               yylval.Number = tp->value;
-               return tp->type;
-           }
-       p[length] = 's';
-    }
-    length++;
-
-    /* Drop out any periods. */
-    for (p = buff, q = (char*)buff; *q; q++)
-       if (*q != '.')
-           *p++ = *q;
-    *p = '\0';
-
-    /* Try the meridians. */
-    if (buff[1] == 'm' && buff[2] == '\0') {
-       if (buff[0] == 'a') {
-           yylval.Meridian = MERam;
-           return tMERIDIAN;
-       }
-       if (buff[0] == 'p') {
-           yylval.Meridian = MERpm;
-           return tMERIDIAN;
-       }
-    }
-
-    /* If we saw any periods, try the timezones again. */
-    if (p - buff != length) {
-       c = buff[0];
-       for (p = buff, tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
-           if (c == tp->name[0] && p[1] == tp->name[1]
-           && strcmp(p, tp->name) == 0) {
-               yylval.Number = tp->value;
-               return tp->type;
-           }
-    }
-
-    /* Unknown word -- assume GMT timezone. */
-    yylval.Number = 0;
-    return tZONE;
-}
-
-
-int
-date_lex(void)
-{
-    register char      c;
-    register char      *p;
-    char               buff[20];
-    register int       sign;
-    register int       i;
-    register int       nesting;
-
-    for ( ; ; ) {
-       /* Get first character after the whitespace. */
-       for ( ; ; ) {
-           while (isspace(*yyInput))
-               yyInput++;
-           c = *yyInput;
-
-           /* Ignore RFC 822 comments, typically time zone names. */
-           if (c != LPAREN)
-               break;
-           for (nesting = 1; (c = *++yyInput) != RPAREN || --nesting; )
-               if (c == LPAREN)
-                   nesting++;
-               else if (!IS7BIT(c) || c == '\0' || c == '\r'
-                    || (c == '\\' && ((c = *++yyInput) == '\0' || !IS7BIT(c))))
-                   /* Lexical error: bad comment. */
-                   return '?';
-           yyInput++;
-       }
-
-       /* A number? */
-       if (isdigit(c) || c == '-' || c == '+') {
-           if (c == '-' || c == '+') {
-               sign = c == '-' ? -1 : 1;
-               yyInput++;
-               if (!isdigit(*yyInput))
-                   /* Skip the plus or minus sign. */
-                   continue;
-           }
-           else
-               sign = 0;
-           for (i = 0; (c = *yyInput++) != '\0' && isdigit(c); )
-               i = 10 * i + c - '0';
-           yyInput--;
-           yylval.Number = sign < 0 ? -i : i;
-           return sign ? tSNUMBER : tUNUMBER;
-       }
-
-       /* A word? */
-       if (isalpha(c)) {
-           for (p = buff; (c = *yyInput++) == '.' || isalpha(c); )
-               if (p < &buff[sizeof buff - 1])
-                   *p++ = isupper(c) ? tolower(c) : c;
-           *p = '\0';
-           yyInput--;
-           return LookupWord(buff, p - buff);
-       }
-
-       return *yyInput++;
-    }
-}
-
-
-time_t
-parsedate(const char *p)
-{
-    extern int         date_parse(void);
-    time_t             Start;
-
-    yyInput = p; /* well, its supposed to be const... */
-
-    yyYear = 0;
-    yyMonth = 0;
-    yyDay = 0;
-    yyTimezone = 0;
-    yyDSTmode = DSTmaybe;
-    yyHour = 0;
-    yyMinutes = 0;
-    yySeconds = 0;
-    yyMeridian = MER24;
-    yyRelSeconds = 0;
-    yyRelMonth = 0;
-    yyHaveDate = 0;
-    yyHaveRel = 0;
-    yyHaveTime = 0;
-
-    if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
-       return -1;
-
-    if (yyHaveDate || yyHaveTime) {
-       Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
-                   yyMeridian, yyDSTmode);
-       if (Start < 0)
-           return -1;
-    }
-    else
-       return -1;
-
-    Start += yyRelSeconds;
-    if (yyRelMonth)
-       Start += RelativeMonth(Start, yyRelMonth);
-
-    /* Have to do *something* with a legitimate -1 so it's distinguishable
-     * from the error return value.  (Alternately could set errno on error.) */
-    return Start == -1 ? 0 : Start;
-}
-
-
-#ifdef TEST
-
-#if YYDEBUG
-extern int     yydebug;
-#endif /* YYDEBUG */
-
-/* ARGSUSED */
-int
-main(int ac, char *av[])
-{
-    char       buff[128];
-    time_t     d;
-
-#if YYDEBUG
-    yydebug = 1;
-#endif /* YYDEBUG */
-
-    (void)printf("Enter date, or blank line to exit.\n\t> ");
-    for ( ; ; ) {
-       (void)printf("\t> ");
-       (void)fflush(stdout);
-       if (fgets(buff, sizeof buff, stdin) == NULL || buff[0] == '\n')
-           break;
-#if YYDEBUG
-       if (strcmp(buff, "yydebug") == 0) {
-           yydebug = !yydebug;
-           printf("yydebug = %s\n", yydebug ? "on" : "off");
-           continue;
-       }
-#endif /* YYDEBUG */
-       d = parsedate(buff, (TIMEINFO *)NULL);
-       if (d == -1)
-           (void)printf("Bad format - couldn't convert.\n");
-       else
-           (void)printf("%s", ctime(&d));
-    }
-
-    exit(0);
-    /* NOTREACHED */
-}
-#endif /* TEST */
diff --git a/citadel/parsedate.h b/citadel/parsedate.h
deleted file mode 100644 (file)
index 345e071..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-
-time_t parsedate(const char *);
diff --git a/citadel/parsedate.y b/citadel/parsedate.y
deleted file mode 100644 (file)
index d6022d3..0000000
+++ /dev/null
@@ -1,806 +0,0 @@
-%{
-/* $Revision$
-**
-**  Originally written by Steven M. Bellovin <smb@research.att.com> while
-**  at the University of North Carolina at Chapel Hill.  Later tweaked by
-**  a couple of people on Usenet.  Completely overhauled by Rich $alz
-**  <rsalz@osf.org> and Jim Berets <jberets@bbn.com> in August, 1990.
-**  Further revised (removed obsolete constructs and cleaned up timezone
-**  names) in August, 1991, by Rich.  Paul Eggert <eggert@twinsun.com>
-**  helped in September, 1992.  Art Cancro <ajc@citadel.org> cleaned
-**  it up for ANSI C in December, 1999.
-**
-**  This grammar has six shift/reduce conflicts.
-**
-**  This code is in the public domain and has no copyright.
-*/
-/* SUPPRESS 530 *//* Empty body for statement */
-/* SUPPRESS 593 on yyerrlab *//* Label was not used */
-/* SUPPRESS 593 on yynewstate *//* Label was not used */
-/* SUPPRESS 595 on yypvt *//* Automatic variable may be used before set */
-
-#include "sysdep.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <ctype.h>
-#include <time.h>
-#if HAVE_STRING_H
-# if !STDC_HEADERS && HAVE_MEMORY_H
-#  include <memory.h>
-# endif
-# include <string.h>
-#endif
-#if HAVE_STRINGS_H
-# include <strings.h>
-#endif
-
-#include "parsedate.h"
-
-int date_lex(void);
-
-#define yyparse                date_parse
-#define yylex          date_lex
-#define yyerror                date_error
-
-
-    /* See the LeapYears table in Convert. */
-#define EPOCH          1970
-#define END_OF_TIME    2038
-    /* Constants for general time calculations. */
-#define DST_OFFSET     1
-#define SECSPERDAY     (24L * 60L * 60L)
-    /* Readability for TABLE stuff. */
-#define HOUR(x)                (x * 60)
-
-#define LPAREN         '('
-#define RPAREN         ')'
-#define IS7BIT(x)      ((unsigned int)(x) < 0200)
-
-#define SIZEOF(array)  ((int)(sizeof array / sizeof array[0]))
-#define ENDOF(array)   (&array[SIZEOF(array)])
-
-
-/*
-**  An entry in the lexical lookup table.
-*/
-typedef struct _TABLE {
-    char       *name;
-    int                type;
-    time_t     value;
-} TABLE;
-
-/*
-**  Daylight-savings mode:  on, off, or not yet known.
-*/
-typedef enum _DSTMODE {
-    DSTon, DSToff, DSTmaybe
-} DSTMODE;
-
-/*
-**  Meridian:  am, pm, or 24-hour style.
-*/
-typedef enum _MERIDIAN {
-    MERam, MERpm, MER24
-} MERIDIAN;
-
-
-/*
-**  Global variables.  We could get rid of most of them by using a yacc
-**  union, but this is more efficient.  (This routine predates the
-**  yacc %union construct.)
-*/
-static const char      *yyInput;
-static DSTMODE yyDSTmode;
-static int     yyHaveDate;
-static int     yyHaveRel;
-static int     yyHaveTime;
-static time_t  yyTimezone;
-static time_t  yyDay;
-static time_t  yyHour;
-static time_t  yyMinutes;
-static time_t  yyMonth;
-static time_t  yySeconds;
-static time_t  yyYear;
-static MERIDIAN        yyMeridian;
-static time_t  yyRelMonth;
-static time_t  yyRelSeconds;
-
-
-static void            date_error(char *);
-%}
-
-%union {
-    time_t             Number;
-    enum _MERIDIAN     Meridian;
-}
-
-%token tDAY tDAYZONE tMERIDIAN tMONTH tMONTH_UNIT tSEC_UNIT tSNUMBER
-%token tUNUMBER tZONE
-
-%type  <Number>        tDAYZONE tMONTH tMONTH_UNIT tSEC_UNIT
-%type  <Number>        tSNUMBER tUNUMBER tZONE numzone zone
-%type  <Meridian>      tMERIDIAN o_merid
-
-%%
-
-spec   : /* NULL */
-       | spec item
-       ;
-
-item   : time {
-           yyHaveTime++;
-#ifdef lint
-           /* I am compulsive about lint natterings... */
-           if (yyHaveTime == -1) {
-               YYERROR;
-           }
-#endif /* lint */
-       }
-       | time zone {
-           yyHaveTime++;
-           yyTimezone = $2;
-       }
-       | date {
-           yyHaveDate++;
-       }
-       | rel {
-           yyHaveRel = 1;
-       }
-       ;
-
-time   : tUNUMBER o_merid {
-           if ($1 < 100) {
-               yyHour = $1;
-               yyMinutes = 0;
-           }
-           else {
-               yyHour = $1 / 100;
-               yyMinutes = $1 % 100;
-           }
-           yySeconds = 0;
-           yyMeridian = $2;
-       }
-       | tUNUMBER ':' tUNUMBER o_merid {
-           yyHour = $1;
-           yyMinutes = $3;
-           yySeconds = 0;
-           yyMeridian = $4;
-       }
-       | tUNUMBER ':' tUNUMBER numzone {
-           yyHour = $1;
-           yyMinutes = $3;
-           yyTimezone = $4;
-           yyMeridian = MER24;
-           yyDSTmode = DSToff;
-       }
-       | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
-           yyHour = $1;
-           yyMinutes = $3;
-           yySeconds = $5;
-           yyMeridian = $6;
-       }
-       | tUNUMBER ':' tUNUMBER ':' tUNUMBER numzone {
-           yyHour = $1;
-           yyMinutes = $3;
-           yySeconds = $5;
-           yyTimezone = $6;
-           yyMeridian = MER24;
-           yyDSTmode = DSToff;
-       }
-       ;
-
-zone   : tZONE {
-           $$ = $1;
-           yyDSTmode = DSToff;
-       }
-       | tDAYZONE {
-           $$ = $1;
-           yyDSTmode = DSTon;
-       }
-       | tZONE numzone {
-           /* Only allow "GMT+300" and "GMT-0800" */
-           if ($1 != 0) {
-               YYABORT;
-           }
-           $$ = $2;
-           yyDSTmode = DSToff;
-       }
-       | numzone {
-           $$ = $1;
-           yyDSTmode = DSToff;
-       }
-       ;
-
-numzone        : tSNUMBER {
-           int         i;
-
-           /* Unix and GMT and numeric timezones -- a little confusing. */
-           if ($1 < 0) {
-               /* Don't work with negative modulus. */
-               $1 = -$1;
-               if ($1 > 9999 || (i = $1 % 100) >= 60) {
-                   YYABORT;
-               }
-               $$ = ($1 / 100) * 60 + i;
-           }
-           else {
-               if ($1 > 9999 || (i = $1 % 100) >= 60) {
-                   YYABORT;
-               }
-               $$ = -(($1 / 100) * 60 + i);
-           }
-       }
-       ;
-
-date   : tUNUMBER '/' tUNUMBER {
-           yyMonth = $1;
-           yyDay = $3;
-       }
-       | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
-           if ($1 > 100) {
-               yyYear = $1;
-               yyMonth = $3;
-               yyDay = $5;
-           }
-           else {
-               yyMonth = $1;
-               yyDay = $3;
-               yyYear = $5;
-           }
-       }
-       | tMONTH tUNUMBER {
-           yyMonth = $1;
-           yyDay = $2;
-       }
-       | tMONTH tUNUMBER ',' tUNUMBER {
-           yyMonth = $1;
-           yyDay = $2;
-           yyYear = $4;
-       }
-       | tUNUMBER tMONTH {
-           yyDay = $1;
-           yyMonth = $2;
-       }
-       | tUNUMBER tMONTH tUNUMBER {
-           yyDay = $1;
-           yyMonth = $2;
-           yyYear = $3;
-       }
-       | tDAY ',' tUNUMBER tMONTH tUNUMBER {
-           yyDay = $3;
-           yyMonth = $4;
-           yyYear = $5;
-       }
-       ;
-
-rel    : tSNUMBER tSEC_UNIT {
-           yyRelSeconds += $1 * $2;
-       }
-       | tUNUMBER tSEC_UNIT {
-           yyRelSeconds += $1 * $2;
-       }
-       | tSNUMBER tMONTH_UNIT {
-           yyRelMonth += $1 * $2;
-       }
-       | tUNUMBER tMONTH_UNIT {
-           yyRelMonth += $1 * $2;
-       }
-       ;
-
-o_merid        : /* NULL */ {
-           $$ = MER24;
-       }
-       | tMERIDIAN {
-           $$ = $1;
-       }
-       ;
-
-%%
-
-/* Month and day table. */
-static TABLE   MonthDayTable[] = {
-    { "january",       tMONTH,  1 },
-    { "february",      tMONTH,  2 },
-    { "march",         tMONTH,  3 },
-    { "april",         tMONTH,  4 },
-    { "may",           tMONTH,  5 },
-    { "june",          tMONTH,  6 },
-    { "july",          tMONTH,  7 },
-    { "august",                tMONTH,  8 },
-    { "september",     tMONTH,  9 },
-    { "october",       tMONTH, 10 },
-    { "november",      tMONTH, 11 },
-    { "december",      tMONTH, 12 },
-       /* The value of the day isn't used... */
-    { "sunday",                tDAY, 0 },
-    { "monday",                tDAY, 0 },
-    { "tuesday",       tDAY, 0 },
-    { "wednesday",     tDAY, 0 },
-    { "thursday",      tDAY, 0 },
-    { "friday",                tDAY, 0 },
-    { "saturday",      tDAY, 0 },
-};
-
-/* Time units table. */
-static TABLE   UnitsTable[] = {
-    { "year",          tMONTH_UNIT,    12 },
-    { "month",         tMONTH_UNIT,    1 },
-    { "week",          tSEC_UNIT,      7L * 24 * 60 * 60 },
-    { "day",           tSEC_UNIT,      1L * 24 * 60 * 60 },
-    { "hour",          tSEC_UNIT,      60 * 60 },
-    { "minute",                tSEC_UNIT,      60 },
-    { "min",           tSEC_UNIT,      60 },
-    { "second",                tSEC_UNIT,      1 },
-    { "sec",           tSEC_UNIT,      1 },
-};
-
-/* Timezone table. */
-static TABLE   TimezoneTable[] = {
-    { "gmt",   tZONE,     HOUR( 0) },  /* Greenwich Mean */
-    { "ut",    tZONE,     HOUR( 0) },  /* Universal */
-    { "utc",   tZONE,     HOUR( 0) },  /* Universal Coordinated */
-    { "cut",   tZONE,     HOUR( 0) },  /* Coordinated Universal */
-    { "z",     tZONE,     HOUR( 0) },  /* Greenwich Mean */
-    { "wet",   tZONE,     HOUR( 0) },  /* Western European */
-    { "bst",   tDAYZONE,  HOUR( 0) },  /* British Summer */
-    { "nst",   tZONE,     HOUR(3)+30 }, /* Newfoundland Standard */
-    { "ndt",   tDAYZONE,  HOUR(3)+30 }, /* Newfoundland Daylight */
-    { "ast",   tZONE,     HOUR( 4) },  /* Atlantic Standard */
-    { "adt",   tDAYZONE,  HOUR( 4) },  /* Atlantic Daylight */
-    { "est",   tZONE,     HOUR( 5) },  /* Eastern Standard */
-    { "edt",   tDAYZONE,  HOUR( 5) },  /* Eastern Daylight */
-    { "cst",   tZONE,     HOUR( 6) },  /* Central Standard */
-    { "cdt",   tDAYZONE,  HOUR( 6) },  /* Central Daylight */
-    { "mst",   tZONE,     HOUR( 7) },  /* Mountain Standard */
-    { "mdt",   tDAYZONE,  HOUR( 7) },  /* Mountain Daylight */
-    { "pst",   tZONE,     HOUR( 8) },  /* Pacific Standard */
-    { "pdt",   tDAYZONE,  HOUR( 8) },  /* Pacific Daylight */
-    { "yst",   tZONE,     HOUR( 9) },  /* Yukon Standard */
-    { "ydt",   tDAYZONE,  HOUR( 9) },  /* Yukon Daylight */
-    { "akst",  tZONE,     HOUR( 9) },  /* Alaska Standard */
-    { "akdt",  tDAYZONE,  HOUR( 9) },  /* Alaska Daylight */
-    { "hst",   tZONE,     HOUR(10) },  /* Hawaii Standard */
-    { "hast",  tZONE,     HOUR(10) },  /* Hawaii-Aleutian Standard */
-    { "hadt",  tDAYZONE,  HOUR(10) },  /* Hawaii-Aleutian Daylight */
-    { "ces",   tDAYZONE,  -HOUR(1) },  /* Central European Summer */
-    { "cest",  tDAYZONE,  -HOUR(1) },  /* Central European Summer */
-    { "mez",   tZONE,     -HOUR(1) },  /* Middle European */
-    { "mezt",  tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
-    { "cet",   tZONE,     -HOUR(1) },  /* Central European */
-    { "met",   tZONE,     -HOUR(1) },  /* Middle European */
-    { "eet",   tZONE,     -HOUR(2) },  /* Eastern Europe */
-    { "msk",   tZONE,     -HOUR(3) },  /* Moscow Winter */
-    { "msd",   tDAYZONE,  -HOUR(3) },  /* Moscow Summer */
-    { "wast",  tZONE,     -HOUR(8) },  /* West Australian Standard */
-    { "wadt",  tDAYZONE,  -HOUR(8) },  /* West Australian Daylight */
-    { "hkt",   tZONE,     -HOUR(8) },  /* Hong Kong */
-    { "cct",   tZONE,     -HOUR(8) },  /* China Coast */
-    { "jst",   tZONE,     -HOUR(9) },  /* Japan Standard */
-    { "kst",   tZONE,     -HOUR(9) },  /* Korean Standard */
-    { "kdt",   tZONE,     -HOUR(9) },  /* Korean Daylight */
-    { "cast",  tZONE,     -(HOUR(9)+30) }, /* Central Australian Standard */
-    { "cadt",  tDAYZONE,  -(HOUR(9)+30) }, /* Central Australian Daylight */
-    { "east",  tZONE,     -HOUR(10) }, /* Eastern Australian Standard */
-    { "eadt",  tDAYZONE,  -HOUR(10) }, /* Eastern Australian Daylight */
-    { "nzst",  tZONE,     -HOUR(12) }, /* New Zealand Standard */
-    { "nzdt",  tDAYZONE,  -HOUR(12) }, /* New Zealand Daylight */
-
-    /* For completeness we include the following entries. */
-#if 0
-
-    /* Duplicate names.  Either they conflict with a zone listed above
-     * (which is either more likely to be seen or just been in circulation
-     * longer), or they conflict with another zone in this section and
-     * we could not reasonably choose one over the other. */
-    { "fst",   tZONE,     HOUR( 2) },  /* Fernando De Noronha Standard */
-    { "fdt",   tDAYZONE,  HOUR( 2) },  /* Fernando De Noronha Daylight */
-    { "bst",   tZONE,     HOUR( 3) },  /* Brazil Standard */
-    { "est",   tZONE,     HOUR( 3) },  /* Eastern Standard (Brazil) */
-    { "edt",   tDAYZONE,  HOUR( 3) },  /* Eastern Daylight (Brazil) */
-    { "wst",   tZONE,     HOUR( 4) },  /* Western Standard (Brazil) */
-    { "wdt",   tDAYZONE,  HOUR( 4) },  /* Western Daylight (Brazil) */
-    { "cst",   tZONE,     HOUR( 5) },  /* Chile Standard */
-    { "cdt",   tDAYZONE,  HOUR( 5) },  /* Chile Daylight */
-    { "ast",   tZONE,     HOUR( 5) },  /* Acre Standard */
-    { "adt",   tDAYZONE,  HOUR( 5) },  /* Acre Daylight */
-    { "cst",   tZONE,     HOUR( 5) },  /* Cuba Standard */
-    { "cdt",   tDAYZONE,  HOUR( 5) },  /* Cuba Daylight */
-    { "est",   tZONE,     HOUR( 6) },  /* Easter Island Standard */
-    { "edt",   tDAYZONE,  HOUR( 6) },  /* Easter Island Daylight */
-    { "sst",   tZONE,     HOUR(11) },  /* Samoa Standard */
-    { "ist",   tZONE,     -HOUR(2) },  /* Israel Standard */
-    { "idt",   tDAYZONE,  -HOUR(2) },  /* Israel Daylight */
-    { "idt",   tDAYZONE,  -(HOUR(3)+30) }, /* Iran Daylight */
-    { "ist",   tZONE,     -(HOUR(3)+30) }, /* Iran Standard */
-    { "cst",    tZONE,     -HOUR(8) }, /* China Standard */
-    { "cdt",    tDAYZONE,  -HOUR(8) }, /* China Daylight */
-    { "sst",    tZONE,     -HOUR(8) }, /* Singapore Standard */
-
-    /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
-    { "gst",   tZONE,     HOUR( 3) },  /* Greenland Standard */
-    { "wat",   tZONE,     -HOUR(1) },  /* West Africa */
-    { "at",    tZONE,     HOUR( 2) },  /* Azores */
-    { "gst",   tZONE,     -HOUR(10) }, /* Guam Standard */
-    { "nft",   tZONE,     HOUR(3)+30 }, /* Newfoundland */
-    { "idlw",  tZONE,     HOUR(12) },  /* International Date Line West */
-    { "mewt",  tZONE,     -HOUR(1) },  /* Middle European Winter */
-    { "mest",  tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
-    { "swt",   tZONE,     -HOUR(1) },  /* Swedish Winter */
-    { "sst",   tDAYZONE,  -HOUR(1) },  /* Swedish Summer */
-    { "fwt",   tZONE,     -HOUR(1) },  /* French Winter */
-    { "fst",   tDAYZONE,  -HOUR(1) },  /* French Summer */
-    { "bt",    tZONE,     -HOUR(3) },  /* Baghdad */
-    { "it",    tZONE,     -(HOUR(3)+30) }, /* Iran */
-    { "zp4",   tZONE,     -HOUR(4) },  /* USSR Zone 3 */
-    { "zp5",   tZONE,     -HOUR(5) },  /* USSR Zone 4 */
-    { "ist",   tZONE,     -(HOUR(5)+30) }, /* Indian Standard */
-    { "zp6",   tZONE,     -HOUR(6) },  /* USSR Zone 5 */
-    { "nst",   tZONE,     -HOUR(7) },  /* North Sumatra */
-    { "sst",   tZONE,     -HOUR(7) },  /* South Sumatra */
-    { "jt",    tZONE,     -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
-    { "nzt",   tZONE,     -HOUR(12) }, /* New Zealand */
-    { "idle",  tZONE,     -HOUR(12) }, /* International Date Line East */
-    { "cat",   tZONE,     HOUR(10) },  /* -- expired 1967 */
-    { "nt",    tZONE,     HOUR(11) },  /* -- expired 1967 */
-    { "ahst",  tZONE,     HOUR(10) },  /* -- expired 1983 */
-    { "hdt",   tDAYZONE,  HOUR(10) },  /* -- expired 1986 */
-#endif /* 0 */
-};
-
-
-/* ARGSUSED */
-static void
-date_error(char *s)
-{
-    /* NOTREACHED */
-}
-
-
-static time_t
-ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
-{
-    if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 61)
-       return -1;
-    if (Meridian == MER24) {
-       if (Hours < 0 || Hours > 23)
-           return -1;
-    }
-    else {
-       if (Hours < 1 || Hours > 12)
-           return -1;
-       if (Hours == 12)
-           Hours = 0;
-       if (Meridian == MERpm)
-           Hours += 12;
-    }
-    return (Hours * 60L + Minutes) * 60L + Seconds;
-}
-
-
-static time_t
-Convert(time_t Month, time_t Day, time_t Year,
-       time_t Hours, time_t Minutes, time_t Seconds,
-       MERIDIAN Meridian, DSTMODE dst)
-{
-    static int DaysNormal[13] = {
-       0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
-    };
-    static int DaysLeap[13] = {
-       0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
-    };
-    static int LeapYears[] = {
-       1972, 1976, 1980, 1984, 1988, 1992, 1996,
-       2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
-    };
-    register int       *yp;
-    register int       *mp;
-    register time_t    Julian;
-    register int       i;
-    time_t             tod;
-
-    if (Year < 0)
-       Year = -Year;
-    if (Year < 100)
-       Year += 1900;
-    if (Year < EPOCH)
-       Year += 100;
-    for (mp = DaysNormal, yp = LeapYears; yp < ENDOF(LeapYears); yp++)
-       if (Year == *yp) {
-           mp = DaysLeap;
-           break;
-       }
-    if (Year < EPOCH || Year > END_OF_TIME
-     || Month < 1 || Month > 12
-     /* NOSTRICT *//* conversion from long may lose accuracy */
-     || Day < 1 || Day > mp[(int)Month])
-       return -1;
-
-    Julian = Day - 1 + (Year - EPOCH) * 365;
-    for (yp = LeapYears; yp < ENDOF(LeapYears); yp++, Julian++)
-       if (Year <= *yp)
-           break;
-    for (i = 1; i < Month; i++)
-       Julian += *++mp;
-    Julian *= SECSPERDAY;
-    Julian += yyTimezone * 60L;
-    if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
-       return -1;
-    Julian += tod;
-    tod = Julian;
-    if (dst == DSTon || (dst == DSTmaybe && localtime(&tod)->tm_isdst))
-       Julian -= DST_OFFSET * 60L * 60L;
-    return Julian;
-}
-
-
-static time_t
-DSTcorrect(time_t Start, time_t Future)
-{
-    time_t     StartDay;
-    time_t     FutureDay;
-
-    StartDay = (localtime(&Start)->tm_hour + 1) % 24;
-    FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
-    return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60L * 60L;
-}
-
-
-static time_t
-RelativeMonth(time_t Start, time_t RelMonth)
-{
-    struct tm  *tm;
-    time_t     Month;
-    time_t     Year;
-
-    tm = localtime(&Start);
-    Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
-    Year = Month / 12;
-    Month = Month % 12 + 1;
-    return DSTcorrect(Start,
-           Convert(Month, (time_t)tm->tm_mday, Year,
-               (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
-               MER24, DSTmaybe));
-}
-
-
-static int
-LookupWord(char *buff, register int length)
-{
-    register char      *p;
-    register char      *q;
-    register TABLE     *tp;
-    register int       c;
-
-    p = buff;
-    c = p[0];
-
-    /* See if we have an abbreviation for a month. */
-    if (length == 3 || (length == 4 && p[3] == '.'))
-       for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
-           q = tp->name;
-           if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
-               yylval.Number = tp->value;
-               return tp->type;
-           }
-       }
-    else
-       for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++)
-           if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
-               yylval.Number = tp->value;
-               return tp->type;
-           }
-
-    /* Try for a timezone. */
-    for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
-       if (c == tp->name[0] && p[1] == tp->name[1]
-        && strcmp(p, tp->name) == 0) {
-           yylval.Number = tp->value;
-           return tp->type;
-       }
-
-    /* Try the units table. */
-    for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
-       if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
-           yylval.Number = tp->value;
-           return tp->type;
-       }
-
-    /* Strip off any plural and try the units table again. */
-    if (--length > 0 && p[length] == 's') {
-       p[length] = '\0';
-       for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
-           if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
-               p[length] = 's';
-               yylval.Number = tp->value;
-               return tp->type;
-           }
-       p[length] = 's';
-    }
-    length++;
-
-    /* Drop out any periods. */
-    for (p = buff, q = (char*)buff; *q; q++)
-       if (*q != '.')
-           *p++ = *q;
-    *p = '\0';
-
-    /* Try the meridians. */
-    if (buff[1] == 'm' && buff[2] == '\0') {
-       if (buff[0] == 'a') {
-           yylval.Meridian = MERam;
-           return tMERIDIAN;
-       }
-       if (buff[0] == 'p') {
-           yylval.Meridian = MERpm;
-           return tMERIDIAN;
-       }
-    }
-
-    /* If we saw any periods, try the timezones again. */
-    if (p - buff != length) {
-       c = buff[0];
-       for (p = buff, tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
-           if (c == tp->name[0] && p[1] == tp->name[1]
-           && strcmp(p, tp->name) == 0) {
-               yylval.Number = tp->value;
-               return tp->type;
-           }
-    }
-
-    /* Unknown word -- assume GMT timezone. */
-    yylval.Number = 0;
-    return tZONE;
-}
-
-
-int
-date_lex(void)
-{
-    register char      c;
-    register char      *p;
-    char               buff[20];
-    register int       sign;
-    register int       i;
-    register int       nesting;
-
-    for ( ; ; ) {
-       /* Get first character after the whitespace. */
-       for ( ; ; ) {
-           while (isspace(*yyInput))
-               yyInput++;
-           c = *yyInput;
-
-           /* Ignore RFC 822 comments, typically time zone names. */
-           if (c != LPAREN)
-               break;
-           for (nesting = 1; (c = *++yyInput) != RPAREN || --nesting; )
-               if (c == LPAREN)
-                   nesting++;
-               else if (!IS7BIT(c) || c == '\0' || c == '\r'
-                    || (c == '\\' && ((c = *++yyInput) == '\0' || !IS7BIT(c))))
-                   /* Lexical error: bad comment. */
-                   return '?';
-           yyInput++;
-       }
-
-       /* A number? */
-       if (isdigit(c) || c == '-' || c == '+') {
-           if (c == '-' || c == '+') {
-               sign = c == '-' ? -1 : 1;
-               yyInput++;
-               if (!isdigit(*yyInput))
-                   /* Skip the plus or minus sign. */
-                   continue;
-           }
-           else
-               sign = 0;
-           for (i = 0; (c = *yyInput++) != '\0' && isdigit(c); )
-               i = 10 * i + c - '0';
-           yyInput--;
-           yylval.Number = sign < 0 ? -i : i;
-           return sign ? tSNUMBER : tUNUMBER;
-       }
-
-       /* A word? */
-       if (isalpha(c)) {
-           for (p = buff; (c = *yyInput++) == '.' || isalpha(c); )
-               if (p < &buff[sizeof buff - 1])
-                   *p++ = isupper(c) ? tolower(c) : c;
-           *p = '\0';
-           yyInput--;
-           return LookupWord(buff, p - buff);
-       }
-
-       return *yyInput++;
-    }
-}
-
-
-time_t
-parsedate(const char *p)
-{
-    extern int         date_parse(void);
-    time_t             Start;
-
-    yyInput = p; /* well, its supposed to be const... */
-
-    yyYear = 0;
-    yyMonth = 0;
-    yyDay = 0;
-    yyTimezone = 0;
-    yyDSTmode = DSTmaybe;
-    yyHour = 0;
-    yyMinutes = 0;
-    yySeconds = 0;
-    yyMeridian = MER24;
-    yyRelSeconds = 0;
-    yyRelMonth = 0;
-    yyHaveDate = 0;
-    yyHaveRel = 0;
-    yyHaveTime = 0;
-
-    if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
-       return -1;
-
-    if (yyHaveDate || yyHaveTime) {
-       Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
-                   yyMeridian, yyDSTmode);
-       if (Start < 0)
-           return -1;
-    }
-    else
-       return -1;
-
-    Start += yyRelSeconds;
-    if (yyRelMonth)
-       Start += RelativeMonth(Start, yyRelMonth);
-
-    /* Have to do *something* with a legitimate -1 so it's distinguishable
-     * from the error return value.  (Alternately could set errno on error.) */
-    return Start == -1 ? 0 : Start;
-}
-
-
-#ifdef TEST
-
-#if YYDEBUG
-extern int     yydebug;
-#endif /* YYDEBUG */
-
-/* ARGSUSED */
-int
-main(int ac, char *av[])
-{
-    char       buff[128];
-    time_t     d;
-
-#if YYDEBUG
-    yydebug = 1;
-#endif /* YYDEBUG */
-
-    (void)printf("Enter date, or blank line to exit.\n\t> ");
-    for ( ; ; ) {
-       (void)printf("\t> ");
-       (void)fflush(stdout);
-       if (fgets(buff, sizeof buff, stdin) == NULL || buff[0] == '\n')
-           break;
-#if YYDEBUG
-       if (strcmp(buff, "yydebug") == 0) {
-           yydebug = !yydebug;
-           printf("yydebug = %s\n", yydebug ? "on" : "off");
-           continue;
-       }
-#endif /* YYDEBUG */
-       d = parsedate(buff, (TIMEINFO *)NULL);
-       if (d == -1)
-           (void)printf("Bad format - couldn't convert.\n");
-       else
-           (void)printf("%s", ctime(&d));
-    }
-
-    exit(0);
-    /* NOTREACHED */
-}
-#endif /* TEST */
diff --git a/citadel/room_ops.c b/citadel/room_ops.c
deleted file mode 100644 (file)
index 5b84fd3..0000000
+++ /dev/null
@@ -1,1189 +0,0 @@
-// Server functions which perform operations on room objects.
-//
-// Copyright (c) 1987-2022 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include <stdio.h>
-#include <libcitadel.h>
-
-#include "citserver.h"
-#include "ctdl_module.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "room_ops.h"
-
-struct floor *floorcache[MAXFLOORS];
-
-// Determine whether the currently logged in session has permission to read
-// messages in the current room.
-int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
-       if (    (!(CC->logged_in))
-               && (!(CC->internal_pgm))
-               && (!CtdlGetConfigInt("c_guest_logins"))
-       ) {
-               return(om_not_logged_in);
-       }
-       return(om_ok);
-}
-
-
-// Check to see whether we have permission to post a message in the current
-// room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
-// returns 0 on success.
-int CtdlDoIHavePermissionToPostInThisRoom(
-       char *errmsgbuf, 
-       size_t n, 
-       const char* RemoteIdentifier,
-       PostType PostPublic,
-       int is_reply
-) {
-       int ra;
-
-       if (!(CC->logged_in) && (PostPublic == POST_LOGGED_IN)) {
-               snprintf(errmsgbuf, n, "Not logged in.");
-               return (ERROR + NOT_LOGGED_IN);
-       }
-       else if (PostPublic == CHECK_EXIST) {
-               return (0);                                     // evaluate whether a recipient exists
-       }
-       else if (!(CC->logged_in)) {
-               if ((CC->room.QRflags & QR_READONLY)) {
-                       snprintf(errmsgbuf, n, "Not logged in.");
-                       return (ERROR + NOT_LOGGED_IN);
-               }
-               return (0);
-       }
-
-       if ((CC->user.axlevel < AxProbU) && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
-               snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
-               return (ERROR + HIGHER_ACCESS_REQUIRED);
-       }
-
-       CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
-
-       if (ra & UA_POSTALLOWED) {
-               strcpy(errmsgbuf, "OK to post or reply here");
-               return(0);
-       }
-
-       if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
-               // To be thorough, we ought to check to see if the message they are
-               // replying to is actually a valid one in this room, but unless this
-               // actually becomes a problem we'll go with high performance instead.
-               strcpy(errmsgbuf, "OK to reply here");
-               return(0);
-       }
-
-       if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
-               // Clarify what happened with a better error message
-               snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
-               return (ERROR + HIGHER_ACCESS_REQUIRED);
-       }
-
-       snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
-       return (ERROR + HIGHER_ACCESS_REQUIRED);
-
-}
-
-
-// Check whether the current user has permission to delete messages from
-// the current room (returns 1 for yes, 0 for no)
-int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
-       int ra;
-       CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
-       if (ra & UA_DELETEALLOWED) return(1);
-       return(0);
-}
-
-
-// Retrieve access control information for any user/room pair.
-// Yes, it has a couple of gotos.  If you don't like that, go die in a car fire.
-void CtdlRoomAccess(struct ctdlroom *roombuf, struct ctdluser *userbuf, int *result, int *view) {
-       int retval = 0;
-       visit vbuf;
-       int is_me = 0;
-       int is_guest = 0;
-
-       if (userbuf == &CC->user) {
-               is_me = 1;
-       }
-
-       if ((is_me) && (CtdlGetConfigInt("c_guest_logins")) && (!CC->logged_in)) {
-               is_guest = 1;
-       }
-
-       // for internal programs, always do everything
-       if (((CC->internal_pgm)) && (roombuf->QRflags & QR_INUSE)) {
-               retval = (UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED);
-               vbuf.v_view = 0;
-               goto SKIP_EVERYTHING;
-       }
-
-       // If guest mode is enabled, always grant access to the Lobby
-       if ((is_guest) && (!strcasecmp(roombuf->QRname, BASEROOM))) {
-               retval = (UA_KNOWN | UA_GOTOALLOWED);
-               vbuf.v_view = 0;
-               goto SKIP_EVERYTHING;
-       }
-
-       // Locate any applicable user/room relationships
-       if (is_guest) {
-               memset(&vbuf, 0, sizeof vbuf);
-       }
-       else {
-               CtdlGetRelationship(&vbuf, userbuf, roombuf);
-       }
-
-       // Force the properties of the Aide room
-       if (!strcasecmp(roombuf->QRname, CtdlGetConfigStr("c_aideroom"))) {
-               if (userbuf->axlevel >= AxAideU) {
-                       retval = UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED;
-               } else {
-                       retval = 0;
-               }
-               goto NEWMSG;
-       }
-
-       // If this is a public room, it's accessible...
-       if (    ((roombuf->QRflags & QR_PRIVATE) == 0) 
-               && ((roombuf->QRflags & QR_MAILBOX) == 0)
-       ) {
-               retval = retval | UA_KNOWN | UA_GOTOALLOWED;
-       }
-
-       // If this is a preferred users only room, check access level
-       if (roombuf->QRflags & QR_PREFONLY) {
-               if (userbuf->axlevel < AxPrefU) {
-                       retval = retval & ~UA_KNOWN & ~UA_GOTOALLOWED;
-               }
-       }
-
-       // For private rooms, check the generation number matchups
-       if (    (roombuf->QRflags & QR_PRIVATE) 
-               && ((roombuf->QRflags & QR_MAILBOX) == 0)
-       ) {
-
-               // An explicit match means the user belongs in this room
-               if (vbuf.v_flags & V_ACCESS) {
-                       retval = retval | UA_KNOWN | UA_GOTOALLOWED;
-               }
-               // Otherwise, check if this is a guess-name or passworded
-               // room.  If it is, a goto may at least be attempted
-               else if (       (roombuf->QRflags & QR_PRIVATE)
-                               || (roombuf->QRflags & QR_PASSWORDED)
-               ) {
-                       retval = retval & ~UA_KNOWN;
-                       retval = retval | UA_GOTOALLOWED;
-               }
-       }
-
-       // For mailbox rooms, also check the namespace.   Also, mailbox owners can delete their messages
-       if ( (roombuf->QRflags & QR_MAILBOX) && (atol(roombuf->QRname) != 0)) {
-               if (userbuf->usernum == atol(roombuf->QRname)) {
-                       retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED;
-               }
-               // An explicit match means the user belongs in this room
-               if (vbuf.v_flags & V_ACCESS) {
-                       retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED;
-               }
-       }
-
-       // For non-mailbox rooms...
-       else {
-               // User is allowed to post in the room unless:
-               // - User is not validated
-               // - User has no net privileges and it is a shared network room
-               // - It is a read-only room
-               // - It is a blog room (in which case we only allow replies to existing messages)
-               int post_allowed = 1;
-               int reply_allowed = 1;
-               if (userbuf->axlevel < AxProbU) {
-                       post_allowed = 0;
-                       reply_allowed = 0;
-               }
-               if ((userbuf->axlevel < AxNetU) && (roombuf->QRflags & QR_NETWORK)) {
-                       post_allowed = 0;
-                       reply_allowed = 0;
-               }
-               if (roombuf->QRflags & QR_READONLY) {
-                       post_allowed = 0;
-                       reply_allowed = 0;
-               }
-               if (roombuf->QRdefaultview == VIEW_BLOG) {
-                       post_allowed = 0;
-               }
-               if (post_allowed) {
-                       retval = retval | UA_POSTALLOWED | UA_REPLYALLOWED;
-               }
-               if (reply_allowed) {
-                       retval = retval | UA_REPLYALLOWED;
-               }
-
-               // If "collaborative deletion" is active for this room, any user who can post
-               // is also allowed to delete
-               if (roombuf->QRflags2 & QR2_COLLABDEL) {
-                       if (retval & UA_POSTALLOWED) {
-                               retval = retval | UA_DELETEALLOWED;
-                       }
-               }
-
-       }
-
-       // Check to see if the user has forgotten this room
-       if (vbuf.v_flags & V_FORGET) {
-               retval = retval & ~UA_KNOWN;
-               if (    ( ((roombuf->QRflags & QR_PRIVATE) == 0) 
-                       && ((roombuf->QRflags & QR_MAILBOX) == 0)
-               ) || (  (roombuf->QRflags & QR_MAILBOX) 
-                       && (atol(roombuf->QRname) == CC->user.usernum))
-               ) {
-                       retval = retval | UA_ZAPPED;
-               }
-       }
-
-       // If user is explicitly locked out of this room, deny everything
-       if (vbuf.v_flags & V_LOCKOUT) {
-               retval = retval & ~UA_KNOWN & ~UA_GOTOALLOWED & ~UA_POSTALLOWED & ~UA_REPLYALLOWED;
-       }
-
-       // Aides get access to all private rooms
-       if (    (userbuf->axlevel >= AxAideU)
-               && ((roombuf->QRflags & QR_MAILBOX) == 0)
-       ) {
-               if (vbuf.v_flags & V_FORGET) {
-                       retval = retval | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
-               }
-               else {
-                       retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
-               }
-       }
-
-       // Aides can gain access to mailboxes as well, but they don't show by default.
-       if (    (userbuf->axlevel >= AxAideU)
-               && (roombuf->QRflags & QR_MAILBOX)
-       ) {
-               retval = retval | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
-       }
-
-       // Aides and Room Aides have admin privileges
-       if (    (userbuf->axlevel >= AxAideU)
-               || (userbuf->usernum == roombuf->QRroomaide)
-       ) {
-               retval = retval | UA_ADMINALLOWED | UA_DELETEALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
-       }
-
-NEWMSG:        // By the way, we also check for the presence of new messages
-       if (is_msg_in_sequence_set(vbuf.v_seen, roombuf->QRhighest) == 0) {
-               retval = retval | UA_HASNEWMSGS;
-       }
-
-       // System rooms never show up in the list.
-       if (roombuf->QRflags2 & QR2_SYSTEM) {
-               retval = retval & ~UA_KNOWN;
-       }
-
-SKIP_EVERYTHING:
-       // Now give the caller the information it wants.
-       if (result != NULL) *result = retval;
-       if (view != NULL) *view = vbuf.v_view;
-}
-
-
-// Self-checking stuff for a room record read into memory
-void room_sanity_check(struct ctdlroom *qrbuf) {
-       // Mailbox rooms are always on the lowest floor
-       if (qrbuf->QRflags & QR_MAILBOX) {
-               qrbuf->QRfloor = 0;
-       }
-       // Listing order of 0 is illegal except for base rooms
-       if (qrbuf->QRorder == 0) {
-               if (    !(qrbuf->QRflags & QR_MAILBOX)
-                       && strncasecmp(qrbuf->QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)
-                       && strncasecmp(qrbuf->QRname, CtdlGetConfigStr("c_aideroom"), ROOMNAMELEN)
-               ) {
-                       qrbuf->QRorder = 64;
-               }
-       }
-}
-
-
-// CtdlGetRoom()  -  retrieve room data from disk
-int CtdlGetRoom(struct ctdlroom *qrbuf, const char *room_name) {
-       struct cdbdata *cdbqr;
-       char lowercase_name[ROOMNAMELEN];
-       char personal_lowercase_name[ROOMNAMELEN];
-       const char *sptr;
-       char *dptr, *eptr;
-
-       dptr = lowercase_name;
-       sptr = room_name;
-       eptr = (dptr + (sizeof lowercase_name - 1));
-       while (!IsEmptyStr(sptr) && (dptr < eptr)) {
-               *dptr = tolower(*sptr);
-               sptr++; dptr++;
-       }
-       *dptr = '\0';
-
-       memset(qrbuf, 0, sizeof(struct ctdlroom));
-
-       if (IsEmptyStr(lowercase_name)) {
-               return(1);                      // empty room name , not valid
-       }
-
-       // First, try the public namespace
-       cdbqr = cdb_fetch(CDB_ROOMS, lowercase_name, strlen(lowercase_name));
-
-       // If that didn't work, try the user's personal namespace
-       if (cdbqr == NULL) {
-               snprintf(personal_lowercase_name, sizeof personal_lowercase_name, "%010ld.%s", CC->user.usernum, lowercase_name);
-               cdbqr = cdb_fetch(CDB_ROOMS, personal_lowercase_name, strlen(personal_lowercase_name));
-       }
-       if (cdbqr != NULL) {
-               memcpy(qrbuf, cdbqr->ptr, ((cdbqr->len > sizeof(struct ctdlroom)) ?  sizeof(struct ctdlroom) : cdbqr->len));
-               cdb_free(cdbqr);
-               room_sanity_check(qrbuf);
-               return (0);
-       }
-       else {
-               return (1);
-       }
-}
-
-
-// CtdlGetRoomLock()  -  same as getroom() but locks the record (if supported)
-int CtdlGetRoomLock(struct ctdlroom *qrbuf, const char *room_name) {
-       register int retval;
-       retval = CtdlGetRoom(qrbuf, room_name);
-       if (retval == 0) begin_critical_section(S_ROOMS);
-       return(retval);
-}
-
-
-// b_putroom()  -  back end to putroom() and b_deleteroom()
-// (if the supplied buffer is NULL, delete the room record)
-void b_putroom(struct ctdlroom *qrbuf, char *room_name) {
-       char lowercase_name[ROOMNAMELEN];
-       char *aptr, *bptr;
-       long len;
-
-       aptr = room_name;
-       bptr = lowercase_name;
-       while (!IsEmptyStr(aptr)) {
-               *bptr = tolower(*aptr);
-               aptr++;
-               bptr++;
-       }
-       *bptr='\0';
-
-       len = bptr - lowercase_name;
-       if (qrbuf == NULL) {
-               cdb_delete(CDB_ROOMS, lowercase_name, len);
-       }
-       else {
-               time(&qrbuf->QRmtime);
-               cdb_store(CDB_ROOMS, lowercase_name, len, qrbuf, sizeof(struct ctdlroom));
-       }
-}
-
-
-// CtdlPutRoom()  -  store room data to disk
-void CtdlPutRoom(struct ctdlroom *qrbuf) {
-       b_putroom(qrbuf, qrbuf->QRname);
-}
-
-
-// b_deleteroom()  -  delete a room record from disk
-void b_deleteroom(char *room_name) {
-       b_putroom(NULL, room_name);
-}
-
-
-// CtdlPutRoomLock()  -  same as CtdlPutRoom() but unlocks the record (if supported)
-void CtdlPutRoomLock(struct ctdlroom *qrbuf) {
-       CtdlPutRoom(qrbuf);
-       end_critical_section(S_ROOMS);
-}
-
-
-// CtdlGetFloorByName()  -  retrieve the number of the named floor
-// return < 0 if not found else return floor number
-int CtdlGetFloorByName(const char *floor_name) {
-       int a;
-       struct floor *flbuf = NULL;
-
-       for (a = 0; a < MAXFLOORS; ++a) {
-               flbuf = CtdlGetCachedFloor(a);
-
-               // check to see if it already exists
-               if ((!strcasecmp(flbuf->f_name, floor_name)) && (flbuf->f_flags & F_INUSE)) {
-                       return a;
-               }
-       }
-       return -1;
-}
-
-
-// CtdlGetFloorByNameLock()  -  retrieve floor number for given floor and lock the floor list.
-int CtdlGetFloorByNameLock(const char *floor_name) {
-       begin_critical_section(S_FLOORTAB);
-       return CtdlGetFloorByName(floor_name);
-}
-
-
-/*
- * CtdlGetAvailableFloor()  -  Return number of first unused floor
- * return < 0 if none available
- */
-int CtdlGetAvailableFloor(void) {
-       int a;
-       struct floor *flbuf = NULL;
-
-       for (a = 0; a < MAXFLOORS; a++) {
-               flbuf = CtdlGetCachedFloor(a);
-
-               /* check to see if it already exists */
-               if ((flbuf->f_flags & F_INUSE) == 0) {
-                       return a;
-               }
-       }
-       return -1;
-}
-
-
-/*
- * CtdlGetFloor()  -  retrieve floor data from disk
- */
-void CtdlGetFloor(struct floor *flbuf, int floor_num) {
-       struct cdbdata *cdbfl;
-
-       memset(flbuf, 0, sizeof(struct floor));
-       cdbfl = cdb_fetch(CDB_FLOORTAB, &floor_num, sizeof(int));
-       if (cdbfl != NULL) {
-               memcpy(flbuf, cdbfl->ptr, ((cdbfl->len > sizeof(struct floor)) ?  sizeof(struct floor) : cdbfl->len));
-               cdb_free(cdbfl);
-       } else {
-               if (floor_num == 0) {
-                       safestrncpy(flbuf->f_name, "Main Floor", sizeof flbuf->f_name);
-                       flbuf->f_flags = F_INUSE;
-                       flbuf->f_ref_count = 3;
-               }
-       }
-}
-
-
-/*
- * lgetfloor()  -  same as CtdlGetFloor() but locks the record (if supported)
- */
-void lgetfloor(struct floor *flbuf, int floor_num) {
-       begin_critical_section(S_FLOORTAB);
-       CtdlGetFloor(flbuf, floor_num);
-}
-
-
-/*
- * CtdlGetCachedFloor()  -  Get floor record from *cache* (loads from disk if needed)
- *    
- * This is strictly a performance hack.
- */
-struct floor *CtdlGetCachedFloor(int floor_num) {
-       static int initialized = 0;
-       int i;
-       int fetch_new = 0;
-       struct floor *fl = NULL;
-
-       begin_critical_section(S_FLOORCACHE);
-       if (initialized == 0) {
-               for (i=0; i<MAXFLOORS; ++i) {
-                       floorcache[floor_num] = NULL;
-               }
-       initialized = 1;
-       }
-       if (floorcache[floor_num] == NULL) {
-               fetch_new = 1;
-       }
-       end_critical_section(S_FLOORCACHE);
-
-       if (fetch_new) {
-               fl = malloc(sizeof(struct floor));
-               CtdlGetFloor(fl, floor_num);
-               begin_critical_section(S_FLOORCACHE);
-               if (floorcache[floor_num] != NULL) {
-                       free(floorcache[floor_num]);
-               }
-               floorcache[floor_num] = fl;
-               end_critical_section(S_FLOORCACHE);
-       }
-
-       return(floorcache[floor_num]);
-}
-
-
-/*
- * CtdlPutFloor()  -  store floor data on disk
- */
-void CtdlPutFloor(struct floor *flbuf, int floor_num) {
-       /* If we've cached this, clear it out, 'cuz it's WRONG now! */
-       begin_critical_section(S_FLOORCACHE);
-       if (floorcache[floor_num] != NULL) {
-               free(floorcache[floor_num]);
-               floorcache[floor_num] = malloc(sizeof(struct floor));
-               memcpy(floorcache[floor_num], flbuf, sizeof(struct floor));
-       }
-       end_critical_section(S_FLOORCACHE);
-
-       cdb_store(CDB_FLOORTAB, &floor_num, sizeof(int),
-                 flbuf, sizeof(struct floor));
-}
-
-
-/*
- * CtdlPutFloorLock()  -  same as CtdlPutFloor() but unlocks the record (if supported)
- */
-void CtdlPutFloorLock(struct floor *flbuf, int floor_num) {
-       CtdlPutFloor(flbuf, floor_num);
-       end_critical_section(S_FLOORTAB);
-
-}
-
-
-/*
- * lputfloor()  -  same as CtdlPutFloor() but unlocks the record (if supported)
- */
-void lputfloor(struct floor *flbuf, int floor_num) {
-       CtdlPutFloorLock(flbuf, floor_num);
-}
-
-
-/* 
- * Iterate through the room table, performing a callback for each room.
- */
-void CtdlForEachRoom(ForEachRoomCallBack callback_func, void *in_data) {
-       struct ctdlroom qrbuf;
-       struct cdbdata *cdbqr;
-
-       cdb_rewind(CDB_ROOMS);
-
-       while (cdbqr = cdb_next_item(CDB_ROOMS), cdbqr != NULL) {
-               memset(&qrbuf, 0, sizeof(struct ctdlroom));
-               memcpy(&qrbuf, cdbqr->ptr, ((cdbqr->len > sizeof(struct ctdlroom)) ?  sizeof(struct ctdlroom) : cdbqr->len) );
-               cdb_free(cdbqr);
-               room_sanity_check(&qrbuf);
-               if (qrbuf.QRflags & QR_INUSE) {
-                       callback_func(&qrbuf, in_data);
-               }
-       }
-}
-
-
-/*
- * delete_msglist()  -  delete room message pointers
- */
-void delete_msglist(struct ctdlroom *whichroom) {
-        struct cdbdata *cdbml;
-
-       /* Make sure the msglist we're deleting actually exists, otherwise
-        * libdb will complain when we try to delete an invalid record
-        */
-        cdbml = cdb_fetch(CDB_MSGLISTS, &whichroom->QRnumber, sizeof(long));
-        if (cdbml != NULL) {
-               cdb_free(cdbml);
-
-               /* Go ahead and delete it */
-               cdb_delete(CDB_MSGLISTS, &whichroom->QRnumber, sizeof(long));
-       }
-}
-
-
-/*
- * Message pointer compare function for sort_msglist()
- */
-int sort_msglist_cmp(const void *m1, const void *m2) {
-       if ((*(const long *)m1) > (*(const long *)m2)) return(1);
-       if ((*(const long *)m1) < (*(const long *)m2)) return(-1);
-       return(0);
-}
-
-
-/*
- * sort message pointers
- * (returns new msg count)
- */
-int sort_msglist(long listptrs[], int oldcount) {
-       int numitems;
-       int i = 0;
-
-       numitems = oldcount;
-       if (numitems < 2) {
-               return (oldcount);
-       }
-
-       /* do the sort */
-       qsort(listptrs, numitems, sizeof(long), sort_msglist_cmp);
-
-       /* and yank any nulls */
-       while ((i < numitems) && (listptrs[i] == 0L)) i++;
-
-       if (i > 0) {
-               memmove(&listptrs[0], &listptrs[i], (sizeof(long) * (numitems - i)));
-               numitems-=i;
-       }
-
-       return (numitems);
-}
-
-
-/*
- * Determine whether a given room is non-editable.
- */
-int CtdlIsNonEditable(struct ctdlroom *qrbuf) {
-
-       /* Mail> rooms are non-editable */
-       if ( (qrbuf->QRflags & QR_MAILBOX) && (!strcasecmp(&qrbuf->QRname[11], MAILROOM)) ) {
-               return (1);
-       }
-
-       /* Everything else is editable */
-       return (0);
-}
-
-
-/*
- * Make the specified room the current room for this session.  No validation
- * or access control is done here -- the caller should make sure that the
- * specified room exists and is ok to access.
- */
-void CtdlUserGoto(char *where, int display_result, int transiently,
-               int *retmsgs, int *retnew, long *retoldest, long *retnewest)
-{
-       int a;
-       int new_messages = 0;
-       int old_messages = 0;
-       int total_messages = 0;
-       long oldest_message = 0;
-       long newest_message = 0;
-       int info = 0;
-       int rmailflag;
-       int raideflag;
-       int newmailcount = 0;
-       visit vbuf;
-       char truncated_roomname[ROOMNAMELEN];
-        struct cdbdata *cdbfr;
-       long *msglist = NULL;
-       int num_msgs = 0;
-       unsigned int original_v_flags;
-       int num_sets;
-       int s;
-       char setstr[128], lostr[64], histr[64];
-       long lo, hi;
-       int is_trash = 0;
-
-       /* If the supplied room name is NULL, the caller wants us to know that
-        * it has already copied the room record into CC->room, so
-        * we can skip the extra database fetch.
-        */
-       if (where != NULL) {
-               safestrncpy(CC->room.QRname, where, sizeof CC->room.QRname);
-               CtdlGetRoom(&CC->room, where);
-       }
-
-       /* Take care of all the formalities. */
-
-       begin_critical_section(S_USERS);
-       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
-       original_v_flags = vbuf.v_flags;
-
-       /* Know the room ... but not if it's the page log room, or if the
-        * caller specified that we're only entering this room transiently.
-        */
-       int add_room_to_known_list = 1;
-       if (transiently == 1) {
-               add_room_to_known_list = 0;
-       }
-       char *c_logpages = CtdlGetConfigStr("c_logpages");
-       if ( (c_logpages != NULL) && (!strcasecmp(CC->room.QRname, c_logpages)) ) {
-               add_room_to_known_list = 0;
-       }
-       if (add_room_to_known_list) {
-               vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
-               vbuf.v_flags = vbuf.v_flags | V_ACCESS;
-       }
-       
-       /* Only rewrite the database record if we changed something */
-       if (vbuf.v_flags != original_v_flags) {
-               CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
-       }
-       end_critical_section(S_USERS);
-
-       /* Check for new mail */
-       newmailcount = NewMailCount();
-
-       /* Set info to 1 if the room banner is new since our last visit.
-        * Some clients only want to display it when it changes.
-        */
-       if (CC->room.msgnum_info > vbuf.v_lastseen) {
-               info = 1;
-       }
-
-        cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
-        if (cdbfr != NULL) {
-               msglist = (long *) cdbfr->ptr;
-               cdbfr->ptr = NULL;      /* CtdlUserGoto() now owns this memory */
-               num_msgs = cdbfr->len / sizeof(long);
-               cdb_free(cdbfr);
-       }
-
-       total_messages = 0;
-       for (a=0; a<num_msgs; ++a) {
-               if (msglist[a] > 0L) ++total_messages;
-       }
-
-       if (total_messages > 0) {
-               oldest_message = msglist[0];
-               newest_message = msglist[num_msgs - 1];
-       }
-
-       num_sets = num_tokens(vbuf.v_seen, ',');
-       for (s=0; s<num_sets; ++s) {
-               extract_token(setstr, vbuf.v_seen, s, ',', sizeof setstr);
-
-               extract_token(lostr, setstr, 0, ':', sizeof lostr);
-               if (num_tokens(setstr, ':') >= 2) {
-                       extract_token(histr, setstr, 1, ':', sizeof histr);
-                       if (!strcmp(histr, "*")) {
-                               snprintf(histr, sizeof histr, "%ld", LONG_MAX);
-                       }
-               } 
-               else {
-                       strcpy(histr, lostr);
-               }
-               lo = atol(lostr);
-               hi = atol(histr);
-
-               for (a=0; a<num_msgs; ++a) if (msglist[a] > 0L) {
-                       if ((msglist[a] >= lo) && (msglist[a] <= hi)) {
-                               ++old_messages;
-                               msglist[a] = 0L;
-                       }
-               }
-       }
-       new_messages = total_messages - old_messages;
-
-       if (msglist != NULL) free(msglist);
-
-       if (CC->room.QRflags & QR_MAILBOX)
-               rmailflag = 1;
-       else
-               rmailflag = 0;
-
-       if ((CC->room.QRroomaide == CC->user.usernum) || (CC->user.axlevel >= AxAideU))
-               raideflag = 1;
-       else
-               raideflag = 0;
-
-       safestrncpy(truncated_roomname, CC->room.QRname, sizeof truncated_roomname);
-       if ( (CC->room.QRflags & QR_MAILBOX) && (atol(CC->room.QRname) == CC->user.usernum) ) {
-               safestrncpy(truncated_roomname, &truncated_roomname[11], sizeof truncated_roomname);
-       }
-
-       if (!strcasecmp(truncated_roomname, USERTRASHROOM)) {
-               is_trash = 1;
-       }
-
-       if (retmsgs != NULL) *retmsgs = total_messages;
-       if (retnew != NULL) *retnew = new_messages;
-       if (retoldest != NULL) *retoldest = oldest_message;
-       if (retnewest != NULL) *retnewest = newest_message;
-       syslog(LOG_DEBUG, "room_ops: %s : %d new of %d total messages, oldest=%ld, newest=%ld",
-                  CC->room.QRname, new_messages, total_messages, oldest_message, newest_message
-       );
-
-       CC->curr_view = (int)vbuf.v_view;
-
-       if (display_result) {
-               cprintf("%d%c%s|%d|%d|%d|%d|%ld|%ld|%d|%d|%d|%d|%d|%d|%d|%d|%ld|\n",
-                       CIT_OK, CtdlCheckExpress(),
-                       truncated_roomname,
-                       (int)new_messages,
-                       (int)total_messages,
-                       (int)info,
-                       (int)CC->room.QRflags,
-                       (long)CC->room.QRhighest,
-                       (long)vbuf.v_lastseen,
-                       (int)rmailflag,
-                       (int)raideflag,
-                       (int)newmailcount,
-                       (int)CC->room.QRfloor,
-                       (int)vbuf.v_view,
-                       (int)CC->room.QRdefaultview,
-                       (int)is_trash,
-                       (int)CC->room.QRflags2,
-                       (long)CC->room.QRmtime
-               );
-       }
-}
-
-
-/*
- * Handle some of the macro named rooms
- */
-void convert_room_name_macros(char *towhere, size_t maxlen) {
-       if (!strcasecmp(towhere, "_BASEROOM_")) {
-               safestrncpy(towhere, CtdlGetConfigStr("c_baseroom"), maxlen);
-       }
-       else if (!strcasecmp(towhere, "_MAIL_")) {
-               safestrncpy(towhere, MAILROOM, maxlen);
-       }
-       else if (!strcasecmp(towhere, "_TRASH_")) {
-               safestrncpy(towhere, USERTRASHROOM, maxlen);
-       }
-       else if (!strcasecmp(towhere, "_DRAFTS_")) {
-               safestrncpy(towhere, USERDRAFTROOM, maxlen);
-       }
-       else if (!strcasecmp(towhere, "_BITBUCKET_")) {
-               safestrncpy(towhere, CtdlGetConfigStr("c_twitroom"), maxlen);
-       }
-       else if (!strcasecmp(towhere, "_CALENDAR_")) {
-               safestrncpy(towhere, USERCALENDARROOM, maxlen);
-       }
-       else if (!strcasecmp(towhere, "_TASKS_")) {
-               safestrncpy(towhere, USERTASKSROOM, maxlen);
-       }
-       else if (!strcasecmp(towhere, "_CONTACTS_")) {
-               safestrncpy(towhere, USERCONTACTSROOM, maxlen);
-       }
-       else if (!strcasecmp(towhere, "_NOTES_")) {
-               safestrncpy(towhere, USERNOTESROOM, maxlen);
-       }
-}
-
-
-/*
- * Back end function to rename a room.
- * You can also specify which floor to move the room to, or specify -1 to
- * keep the room on the same floor it was on.
- *
- * If you are renaming a mailbox room, you must supply the namespace prefix
- * in *at least* the old name!
- */
-int CtdlRenameRoom(char *old_name, char *new_name, int new_floor) {
-       int old_floor = 0;
-       struct ctdlroom qrbuf;
-       struct ctdlroom qrtmp;
-       int ret = 0;
-       struct floor *fl;
-       struct floor flbuf;
-       long owner = 0L;
-       char actual_old_name[ROOMNAMELEN];
-
-       syslog(LOG_DEBUG, "room_ops: CtdlRenameRoom(%s, %s, %d)", old_name, new_name, new_floor);
-
-       if (new_floor >= 0) {
-               fl = CtdlGetCachedFloor(new_floor);
-               if ((fl->f_flags & F_INUSE) == 0) {
-                       return(crr_invalid_floor);
-               }
-       }
-
-       begin_critical_section(S_ROOMS);
-
-       if ( (CtdlGetRoom(&qrtmp, new_name) == 0) && (strcasecmp(new_name, old_name)) ) {
-               ret = crr_already_exists;
-       }
-
-       else if (CtdlGetRoom(&qrbuf, old_name) != 0) {
-               ret = crr_room_not_found;
-       }
-
-       else if ( (CC->user.axlevel < AxAideU) && (!CC->internal_pgm)
-                 && (CC->user.usernum != qrbuf.QRroomaide)
-                 && ( (((qrbuf.QRflags & QR_MAILBOX) == 0) || (atol(qrbuf.QRname) != CC->user.usernum))) )  {
-               ret = crr_access_denied;
-       }
-
-       else if (CtdlIsNonEditable(&qrbuf)) {
-               ret = crr_noneditable;
-       }
-
-       else {
-               /* Rename it */
-               safestrncpy(actual_old_name, qrbuf.QRname, sizeof actual_old_name);
-               if (qrbuf.QRflags & QR_MAILBOX) {
-                       owner = atol(qrbuf.QRname);
-               }
-               if ( (owner > 0L) && (atol(new_name) == 0L) ) {
-                       snprintf(qrbuf.QRname, sizeof(qrbuf.QRname),
-                                       "%010ld.%s", owner, new_name);
-               }
-               else {
-                       safestrncpy(qrbuf.QRname, new_name,
-                                               sizeof(qrbuf.QRname));
-               }
-
-               /* Reject change of floor for baseroom/aideroom */
-               if (!strncasecmp(old_name, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN) ||
-                   !strncasecmp(old_name, CtdlGetConfigStr("c_aideroom"), ROOMNAMELEN))
-               {
-                       new_floor = 0;
-               }
-
-               /* Take care of floor stuff */
-               old_floor = qrbuf.QRfloor;
-               if (new_floor < 0) {
-                       new_floor = old_floor;
-               }
-               qrbuf.QRfloor = new_floor;
-               CtdlPutRoom(&qrbuf);
-
-               begin_critical_section(S_CONFIG);
-       
-               /* If baseroom/aideroom name changes, update config */
-               if (!strncasecmp(old_name, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
-                       CtdlSetConfigStr("c_baseroom", new_name);
-               }
-               if (!strncasecmp(old_name, CtdlGetConfigStr("c_aideroom"), ROOMNAMELEN)) {
-                       CtdlSetConfigStr("c_aideroom", new_name);
-               }
-       
-               end_critical_section(S_CONFIG);
-       
-               /* If the room name changed, then there are now two room
-                * records, so we have to delete the old one.
-                */
-               if (strcasecmp(new_name, old_name)) {
-                       b_deleteroom(actual_old_name);
-               }
-
-               ret = crr_ok;
-       }
-
-       end_critical_section(S_ROOMS);
-
-       /* Adjust the floor reference counts if necessary */
-       if (new_floor != old_floor) {
-               lgetfloor(&flbuf, old_floor);
-               --flbuf.f_ref_count;
-               lputfloor(&flbuf, old_floor);
-               syslog(LOG_DEBUG, "room_ops: reference count for floor %d is now %d", old_floor, flbuf.f_ref_count);
-               lgetfloor(&flbuf, new_floor);
-               ++flbuf.f_ref_count;
-               lputfloor(&flbuf, new_floor);
-               syslog(LOG_DEBUG, "room_ops: reference count for floor %d is now %d", new_floor, flbuf.f_ref_count);
-       }
-
-       /* ...and everybody say "YATTA!" */     
-       return(ret);
-}
-
-
-/*
- * Asynchronously schedule a room for deletion.  By placing the room into an invalid private namespace,
- * the room will appear deleted to the user(s), but the session doesn't need to block while waiting for
- * database operations to complete.  Instead, the room gets purged when THE DREADED AUTO-PURGER makes
- * its next run.  Aren't we so clever?!!
- */
-void CtdlScheduleRoomForDeletion(struct ctdlroom *qrbuf) {
-       char old_name[ROOMNAMELEN];
-       static int seq = 0;
-
-       syslog(LOG_NOTICE, "room_ops: scheduling room <%s> for deletion", qrbuf->QRname);
-
-       safestrncpy(old_name, qrbuf->QRname, sizeof old_name);
-       CtdlGetRoom(qrbuf, qrbuf->QRname);
-
-       /* Turn the room into a private mailbox owned by a user who doesn't
-        * exist.  This will immediately make the room invisible to everyone,
-        * and qualify the room for purging.
-        */
-       snprintf(qrbuf->QRname, sizeof qrbuf->QRname, "9999999999.%08lx.%04d.%s",
-               time(NULL),
-               ++seq,
-               old_name
-       );
-       qrbuf->QRflags |= QR_MAILBOX;
-       time(&qrbuf->QRgen);    /* Use a timestamp as the new generation number  */
-       CtdlPutRoom(qrbuf);
-       b_deleteroom(old_name);
-}
-
-
-/*
- * Back end processing to delete a room and everything associated with it
- * (This one is synchronous and should only get called by THE DREADED
- * AUTO-PURGER in serv_expire.c.  All user-facing code should call
- * the asynchronous schedule_room_for_deletion() instead.)
- */
-void CtdlDeleteRoom(struct ctdlroom *qrbuf) {
-       struct floor flbuf;
-       char configdbkeyname[25];
-
-       syslog(LOG_NOTICE, "room_ops: deleting room <%s>", qrbuf->QRname);
-
-       // Delete the room's network configdb entry
-       netcfg_keyname(configdbkeyname, qrbuf->QRnumber);
-       CtdlDelConfig(configdbkeyname);
-
-       // Delete the messages in the room
-       // (Careful: this opens an S_ROOMS critical section!)
-       CtdlDeleteMessages(qrbuf->QRname, NULL, 0, "");
-
-       // Flag the room record as not in use
-       CtdlGetRoomLock(qrbuf, qrbuf->QRname);
-       qrbuf->QRflags = 0;
-       CtdlPutRoomLock(qrbuf);
-
-       // then decrement the reference count for the floor
-       lgetfloor(&flbuf, (int) (qrbuf->QRfloor));
-       flbuf.f_ref_count = flbuf.f_ref_count - 1;
-       lputfloor(&flbuf, (int) (qrbuf->QRfloor));
-
-       // Delete the room record from the database!
-       b_deleteroom(qrbuf->QRname);
-}
-
-
-/*
- * Check access control for deleting a room
- */
-int CtdlDoIHavePermissionToDeleteThisRoom(struct ctdlroom *qr) {
-
-       if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
-               return(0);
-       }
-
-       if (CtdlIsNonEditable(qr)) {
-               return(0);
-       }
-
-       /*
-        * For mailboxes, check stuff
-        */
-       if (qr->QRflags & QR_MAILBOX) {
-
-               if (strlen(qr->QRname) < 12) return(0); /* bad name */
-
-               if (atol(qr->QRname) != CC->user.usernum) {
-                       return(0);      /* not my room */
-               }
-
-               /* Can't delete your Mail> room */
-               if (!strcasecmp(&qr->QRname[11], MAILROOM)) return(0);
-
-               /* Otherwise it's ok */
-               return(1);
-       }
-
-       /*
-        * For normal rooms, just check for admin or room admin status.
-        */
-       return(is_room_aide());
-}
-
-
-/*
- * Internal code to create a new room (returns room flags)
- *
- * Room types:  0=public, 1=hidden, 2=passworded, 3=invitation-only,
- *              4=mailbox, 5=mailbox, but caller supplies namespace
- */
-unsigned CtdlCreateRoom(char *new_room_name,
-                    int new_room_type,
-                    char *new_room_pass,
-                    int new_room_floor,
-                    int really_create,
-                    int avoid_access,
-                    int new_room_view)
-{
-       struct ctdlroom qrbuf;
-       struct floor flbuf;
-       visit vbuf;
-
-       syslog(LOG_DEBUG, "room_ops: CtdlCreateRoom(name=%s, type=%d, view=%d)", new_room_name, new_room_type, new_room_view);
-
-       if (CtdlGetRoom(&qrbuf, new_room_name) == 0) {
-               syslog(LOG_DEBUG, "room_ops: cannot create room <%s> - already exists", new_room_name);
-               return(0);
-       }
-
-       memset(&qrbuf, 0, sizeof(struct ctdlroom));
-       safestrncpy(qrbuf.QRpasswd, new_room_pass, sizeof qrbuf.QRpasswd);
-       qrbuf.QRflags = QR_INUSE;
-       if (new_room_type > 0)
-               qrbuf.QRflags = (qrbuf.QRflags | QR_PRIVATE);
-       if (new_room_type == 1)
-               qrbuf.QRflags = (qrbuf.QRflags | QR_GUESSNAME);
-       if (new_room_type == 2)
-               qrbuf.QRflags = (qrbuf.QRflags | QR_PASSWORDED);
-       if ( (new_room_type == 4) || (new_room_type == 5) ) {
-               qrbuf.QRflags = (qrbuf.QRflags | QR_MAILBOX);
-               /* qrbuf.QRflags2 |= QR2_SUBJECTREQ; */
-       }
-
-       /* If the user is requesting a personal room, set up the room
-        * name accordingly (prepend the user number)
-        */
-       if (new_room_type == 4) {
-               CtdlMailboxName(qrbuf.QRname, sizeof qrbuf.QRname, &CC->user, new_room_name);
-       }
-       else {
-               safestrncpy(qrbuf.QRname, new_room_name, sizeof qrbuf.QRname);
-       }
-
-       /* If the room is private, and the system administrator has elected
-        * to automatically grant room admin privileges, do so now.
-        */
-       if ((qrbuf.QRflags & QR_PRIVATE) && (CREATAIDE == 1)) {
-               qrbuf.QRroomaide = CC->user.usernum;
-       }
-       /* Blog owners automatically become room admins of their blogs.
-        * (In the future we will offer a site-wide configuration setting to suppress this behavior.)
-        */
-       else if (new_room_view == VIEW_BLOG) {
-               qrbuf.QRroomaide = CC->user.usernum;
-       }
-       /* Otherwise, set the room admin to undefined.
-        */
-       else {
-               qrbuf.QRroomaide = (-1L);
-       }
-
-       /* 
-        * If the caller is only interested in testing whether this will work,
-        * return now without creating the room.
-        */
-       if (!really_create) return (qrbuf.QRflags);
-
-       qrbuf.QRnumber = get_new_room_number();
-       qrbuf.QRhighest = 0L;   /* No messages in this room yet */
-       time(&qrbuf.QRgen);     /* Use a timestamp as the generation number */
-       qrbuf.QRfloor = new_room_floor;
-       qrbuf.QRdefaultview = new_room_view;
-
-       /* save what we just did... */
-       CtdlPutRoom(&qrbuf);
-
-       /* bump the reference count on whatever floor the room is on */
-       lgetfloor(&flbuf, (int) qrbuf.QRfloor);
-       flbuf.f_ref_count = flbuf.f_ref_count + 1;
-       lputfloor(&flbuf, (int) qrbuf.QRfloor);
-
-       /* Grant the creator access to the room unless the avoid_access
-        * parameter was specified.
-        */
-       if ( (CC->logged_in) && (avoid_access == 0) ) {
-               CtdlGetRelationship(&vbuf, &CC->user, &qrbuf);
-               vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
-               vbuf.v_flags = vbuf.v_flags | V_ACCESS;
-               CtdlSetRelationship(&vbuf, &CC->user, &qrbuf);
-       }
-
-       /* resume our happy day */
-       return (qrbuf.QRflags);
-}
diff --git a/citadel/room_ops.h b/citadel/room_ops.h
deleted file mode 100644 (file)
index d72300b..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-int is_known (struct ctdlroom *roombuf, int roomnum, struct ctdluser *userbuf);
-int has_newmsgs (struct ctdlroom *roombuf, int roomnum, struct ctdluser *userbuf);
-int is_zapped (struct ctdlroom *roombuf, int roomnum, struct ctdluser *userbuf);
-void b_putroom(struct ctdlroom *qrbuf, char *room_name);
-void b_deleteroom(char *);
-void lgetfloor (struct floor *flbuf, int floor_num);
-void lputfloor (struct floor *flbuf, int floor_num);
-int sort_msglist (long int *listptrs, int oldcount);
-void list_roomname(struct ctdlroom *qrbuf, int ra, int current_view, int default_view);
-void convert_room_name_macros(char *towhere, size_t maxlen);
-
-typedef enum _POST_TYPE{
-       POST_LOGGED_IN,
-       POST_EXTERNAL,
-       CHECK_EXIST,
-       POST_LMTP
-} PostType;
-
-int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, 
-       size_t n, 
-       const char* RemoteIdentifier,
-       PostType PostPublic,
-       int is_reply
-);
-int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void);
-int CtdlDoIHavePermissionToReadMessagesInThisRoom(void);
diff --git a/citadel/scripts/mk_svn_revision.sh b/citadel/scripts/mk_svn_revision.sh
deleted file mode 100755 (executable)
index 9a68d27..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/bin/sh
-#
-# Script to generate svn_revision.c
-#
-
-ECHO=/usr/bin/printf
-
-
-SCRIPT_DIR=`dirname $0`
-SRC_DIR=`dirname $SCRIPT_DIR`
-CUR_DIR=`pwd`
-C_FILE="$CUR_DIR/svn_revision.c"
-H_FILE="$CUR_DIR/svn_revision.h"
-
-# determine if this code base came from subversion.
-if test -d $SRC_DIR/.svn  ; then
-       echo "have subversion repository"
-       SVNVERSION=`which svnversion`
-       if test -x $SVNVERSION  ; then
-               echo "have svnversion at $SVNVERSION"
-               BUILD=`svnversion -n .`
-               echo "This code base svn-revision: $BUILD"
-               CAN_BUILD_SVN_REVISION="yes"
-       fi
-else 
-    if test -d $SRC_DIR/../.git  ; then
-       echo "have Git repository."
-       BUILD=`/usr/bin/env git log -1 --pretty=%h . `
-       echo "This code base git-revision: $BUILD"
-       CAN_BUILD_SVN_REVISION="yes"
-    else
-       if test -f $C_FILE; then
-           exit
-       fi
-    fi
-fi
-
-if [ "$CAN_BUILD_SVN_REVISION" = "yes" ] ; then
-
-cat <<EOF > $C_FILE
-/*
- * Subversion / GIT revision functions
- *
- * Autogenerated at make/release time
- *
- * Do not modify this file
- *
- */
-const char *svn_revision (void)
-{
-       const char *SVN_Version = "$BUILD";
-       return SVN_Version;
-}
-EOF
-
-elif test ! -f $C_FILE  ; then
-
-cat <<EOF > $C_FILE
-/*
- * Subversion / GIT revision functions
- *
- * Autogenerated at make time
- *
- * There should have been one with your source distribution
- *
- * Do not modify this file
- *
- */
-const char *svn_revision (void)
-{
-       const char *SVN_Version = "(unknown)";
-       return SVN_Version;
-}
-EOF
-
-fi
diff --git a/citadel/serv_extensions.c b/citadel/serv_extensions.c
deleted file mode 100644 (file)
index 6197d28..0000000
+++ /dev/null
@@ -1,909 +0,0 @@
-/*
- * Citadel Extension Loader
- * Originally written by Brian Costello <btx@calyx.net>
- *
- * Copyright (c) 1987-2021 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <libcitadel.h>
-#include "sysdep_decls.h"
-#include "modules/crypto/serv_crypto.h"        /* Needed until a universal crypto startup hook is implimented for CtdlStartTLS */
-#include "serv_extensions.h"
-#include "ctdl_module.h"
-#include "config.h"
-
-
-/*
- * Structure defentitions for hook tables
- */
-
-typedef struct FixedOutputHook FixedOutputHook;
-struct FixedOutputHook {
-       FixedOutputHook *next;
-       char content_type[64];
-       void (*h_function_pointer) (char *, int);
-};
-FixedOutputHook *FixedOutputTable = NULL;
-
-
-/*
- * SessionFunctionHook extensions are used for any type of hook for which
- * the context in which it's being called (which is determined by the event
- * type) will make it obvious for the hook function to know where to look for
- * pertinent data.
- */
-typedef struct SessionFunctionHook SessionFunctionHook;
-struct SessionFunctionHook {
-       SessionFunctionHook *next;
-       int Priority;
-       void (*h_function_pointer) (void);
-       int eventtype;
-};
-SessionFunctionHook *SessionHookTable = NULL;
-
-
-/*
- * UserFunctionHook extensions are used for any type of hook which implements
- * an operation on a user or username (potentially) other than the one
- * operating the current session.
- */
-typedef struct UserFunctionHook UserFunctionHook;
-struct UserFunctionHook {
-       UserFunctionHook *next;
-       void (*h_function_pointer) (struct ctdluser *usbuf);
-       int eventtype;
-};
-UserFunctionHook *UserHookTable = NULL;
-
-
-/*
- * MessageFunctionHook extensions are used for hooks which implement handlers
- * for various types of message operations (save, read, etc.)
- */
-typedef struct MessageFunctionHook MessageFunctionHook;
-struct MessageFunctionHook {
-       MessageFunctionHook *next;
-       int (*h_function_pointer) (struct CtdlMessage *msg, struct recptypes *recps);
-       int eventtype;
-};
-MessageFunctionHook *MessageHookTable = NULL;
-
-
-/*
- * DeleteFunctionHook extensions are used for hooks which get called when a
- * message is about to be deleted.
- */
-typedef struct DeleteFunctionHook DeleteFunctionHook;
-struct DeleteFunctionHook {
-       DeleteFunctionHook *next;
-       void (*h_function_pointer) (char *target_room, long msgnum);
-};
-DeleteFunctionHook *DeleteHookTable = NULL;
-
-
-/*
- * ExpressMessageFunctionHook extensions are used for hooks which implement
- * the sending of an instant message through various channels.  Any function
- * registered should return the number of recipients to whom the message was
- * successfully transmitted.
- */
-typedef struct XmsgFunctionHook XmsgFunctionHook;
-struct XmsgFunctionHook {
-       XmsgFunctionHook *next;
-       int (*h_function_pointer) (char *, char *, char *, char *);
-       int order;
-};
-XmsgFunctionHook *XmsgHookTable = NULL;
-
-
-/*
- * RoomFunctionHook extensions are used for hooks which impliment room
- * processing functions when new messages are added.
- */
-typedef struct RoomFunctionHook RoomFunctionHook;
-struct RoomFunctionHook {
-       RoomFunctionHook *next;
-       int (*fcn_ptr) (struct ctdlroom *);
-};
-RoomFunctionHook *RoomHookTable = NULL;
-
-
-typedef struct SearchFunctionHook SearchFunctionHook;
-struct SearchFunctionHook {
-       SearchFunctionHook *next;
-       void (*fcn_ptr) (int *, long **, const char *);
-       char *name;
-};
-SearchFunctionHook *SearchFunctionHookTable = NULL;
-
-ServiceFunctionHook *ServiceHookTable = NULL;
-
-typedef struct ProtoFunctionHook ProtoFunctionHook;
-struct ProtoFunctionHook {
-       void (*handler) (char *cmdbuf);
-       const char *cmd;
-       const char *desc;
-};
-
-HashList *ProtoHookList = NULL;
-
-
-#define ERR_PORT (1 << 1)
-
-
-static StrBuf *portlist = NULL;
-static StrBuf *errormessages = NULL;
-
-
-long   DetailErrorFlags;
-ConstStr Empty = {HKEY("")};
-char *ErrSubject = "Startup Problems";
-ConstStr ErrGeneral[] = {
-       {HKEY("Citadel had trouble on starting up. ")},
-       {HKEY(" This means, Citadel won't be the service provider for a specific service you configured it to.\n\n"
-             "If you don't want Citadel to provide these services, turn them off in WebCit via: ")},
-       {HKEY("To make both ways actualy take place restart the citserver with \"sendcommand down\"\n\n"
-             "The errors returned by the system were:\n")},
-       {HKEY("You can recheck the above if you follow this faq item:\n"
-             "http://www.citadel.org/doku.php?id=faq:mastering_your_os:net#netstat")}
-};
-
-ConstStr ErrPortShort = { HKEY("We couldn't bind all ports you configured to be provided by Citadel Server.\n")};
-ConstStr ErrPortWhere = { HKEY("\"Admin->System Preferences->Network\".\n\nThe failed ports and sockets are: ")};
-ConstStr ErrPortHint  = { HKEY("If you want Citadel to provide you with that functionality, "
-                              "check the output of \"netstat -lnp\" on Linux, or \"netstat -na\" on BSD"
-                              " and disable the program that binds these ports.\n")};
-
-
-void LogPrintMessages(long err)
-{
-       StrBuf *Message;
-       StrBuf *List, *DetailList;
-       ConstStr *Short, *Where, *Hint; 
-
-       
-       Message = NewStrBufPlain(NULL, StrLength(portlist) + StrLength(errormessages));
-       
-       DetailErrorFlags = DetailErrorFlags & ~err;
-
-       switch (err)
-       {
-       case ERR_PORT:
-               Short = &ErrPortShort;
-               Where = &ErrPortWhere;
-               Hint  = &ErrPortHint;
-               List  = portlist;
-               DetailList = errormessages;
-               break;
-       default:
-               Short = &Empty;
-               Where = &Empty;
-               Hint  = &Empty;
-               List  = NULL;
-               DetailList = NULL;
-       }
-
-       StrBufAppendBufPlain(Message, CKEY(ErrGeneral[0]), 0);
-       StrBufAppendBufPlain(Message, CKEY(*Short), 0); 
-       StrBufAppendBufPlain(Message, CKEY(ErrGeneral[1]), 0);
-       StrBufAppendBufPlain(Message, CKEY(*Where), 0);
-       StrBufAppendBuf(Message, List, 0);
-       StrBufAppendBufPlain(Message, HKEY("\n\n"), 0);
-       StrBufAppendBufPlain(Message, CKEY(*Hint), 0);
-       StrBufAppendBufPlain(Message, HKEY("\n\n"), 0);
-       StrBufAppendBufPlain(Message, CKEY(ErrGeneral[2]), 0);
-       StrBufAppendBuf(Message, DetailList, 0);
-       StrBufAppendBufPlain(Message, HKEY("\n\n"), 0);
-       StrBufAppendBufPlain(Message, CKEY(ErrGeneral[3]), 0);
-
-       syslog(LOG_ERR, "extensions: %s", ChrPtr(Message));
-       syslog(LOG_ERR, "extensions: %s", ErrSubject);
-       quickie_message("Citadel", NULL, NULL, AIDEROOM, ChrPtr(Message), FMT_FIXED, ErrSubject);
-
-       FreeStrBuf(&Message);
-       FreeStrBuf(&List);
-       FreeStrBuf(&DetailList);
-}
-
-
-void AddPortError(char *Port, char *ErrorMessage)
-{
-       long len;
-
-       DetailErrorFlags |= ERR_PORT;
-
-       len = StrLength(errormessages);
-       if (len > 0) StrBufAppendBufPlain(errormessages, HKEY("; "), 0);
-       else errormessages = NewStrBuf();
-       StrBufAppendBufPlain(errormessages, ErrorMessage, -1, 0);
-
-
-       len = StrLength(portlist);
-       if (len > 0) StrBufAppendBufPlain(portlist, HKEY(";"), 0);
-       else portlist = NewStrBuf();
-       StrBufAppendBufPlain(portlist, Port, -1, 0);
-}
-
-
-int DLoader_Exec_Cmd(char *cmdbuf)
-{
-       void *vP;
-       ProtoFunctionHook *p;
-
-       if (GetHash(ProtoHookList, cmdbuf, 4, &vP) && (vP != NULL)) {
-               p = (ProtoFunctionHook*) vP;
-               p->handler(&cmdbuf[5]);
-               return 1;
-       }
-       return 0;
-}
-
-
-void CtdlRegisterProtoHook(void (*handler) (char *), char *cmd, char *desc)
-{
-       ProtoFunctionHook *p;
-
-       if (ProtoHookList == NULL)
-               ProtoHookList = NewHash (1, FourHash);
-
-
-       p = (ProtoFunctionHook *)
-               malloc(sizeof(ProtoFunctionHook));
-
-       if (p == NULL) {
-               fprintf(stderr, "can't malloc new ProtoFunctionHook\n");
-               exit(EXIT_FAILURE);
-       }
-       p->handler = handler;
-       p->cmd = cmd;
-       p->desc = desc;
-
-       Put(ProtoHookList, cmd, 4, p, NULL);
-       syslog(LOG_DEBUG, "extensions: registered server command %s (%s)", cmd, desc);
-}
-
-
-void CtdlRegisterSessionHook(void (*fcn_ptr) (void), int EventType, int Priority)
-{
-       SessionFunctionHook *newfcn;
-
-       newfcn = (SessionFunctionHook *)
-           malloc(sizeof(SessionFunctionHook));
-       newfcn->Priority = Priority;
-       newfcn->h_function_pointer = fcn_ptr;
-       newfcn->eventtype = EventType;
-
-       SessionFunctionHook **pfcn;
-       pfcn = &SessionHookTable;
-       while ((*pfcn != NULL) && 
-              ((*pfcn)->Priority < newfcn->Priority) &&
-              ((*pfcn)->next != NULL))
-               pfcn = &(*pfcn)->next;
-               
-       newfcn->next = *pfcn;
-       *pfcn = newfcn;
-       
-       syslog(LOG_DEBUG, "extensions: registered a new session function (type %d Priority %d)", EventType, Priority);
-}
-
-
-void CtdlUnregisterSessionHook(void (*fcn_ptr) (void), int EventType)
-{
-       SessionFunctionHook *cur, *p, *last;
-       last = NULL;
-       cur = SessionHookTable;
-       while  (cur != NULL) {
-               if ((fcn_ptr == cur->h_function_pointer) &&
-                   (EventType == cur->eventtype))
-               {
-                       syslog(LOG_DEBUG, "extensions: unregistered session function (type %d)", EventType);
-                       p = cur->next;
-
-                       free(cur);
-                       cur = NULL;
-
-                       if (last != NULL)
-                               last->next = p;
-                       else 
-                               SessionHookTable = p;
-                       cur = p;
-               }
-               else {
-                       last = cur;
-                       cur = cur->next;
-               }
-       }
-}
-
-
-void CtdlRegisterUserHook(void (*fcn_ptr) (ctdluser *), int EventType)
-{
-
-       UserFunctionHook *newfcn;
-
-       newfcn = (UserFunctionHook *)
-           malloc(sizeof(UserFunctionHook));
-       newfcn->next = UserHookTable;
-       newfcn->h_function_pointer = fcn_ptr;
-       newfcn->eventtype = EventType;
-       UserHookTable = newfcn;
-
-       syslog(LOG_DEBUG, "extensions: registered a new user function (type %d)",
-                  EventType);
-}
-
-
-void CtdlUnregisterUserHook(void (*fcn_ptr) (struct ctdluser *), int EventType)
-{
-       UserFunctionHook *cur, *p, *last;
-       last = NULL;
-       cur = UserHookTable;
-       while (cur != NULL) {
-               if ((fcn_ptr == cur->h_function_pointer) &&
-                   (EventType == cur->eventtype))
-               {
-                       syslog(LOG_DEBUG, "extensions: unregistered user function (type %d)", EventType);
-                       p = cur->next;
-
-                       free(cur);
-                       cur = NULL;
-
-                       if (last != NULL)
-                               last->next = p;
-                       else 
-                               UserHookTable = p;
-                       cur = p;
-               }
-               else {
-                       last = cur;
-                       cur = cur->next;
-               }
-       }
-}
-
-
-void CtdlRegisterMessageHook(int (*handler)(struct CtdlMessage *, struct recptypes *), int EventType)
-{
-
-       MessageFunctionHook *newfcn;
-
-       newfcn = (MessageFunctionHook *)
-           malloc(sizeof(MessageFunctionHook));
-       newfcn->next = MessageHookTable;
-       newfcn->h_function_pointer = handler;
-       newfcn->eventtype = EventType;
-       MessageHookTable = newfcn;
-
-       syslog(LOG_DEBUG, "extensions: registered a new message function (type %d)", EventType);
-}
-
-
-void CtdlUnregisterMessageHook(int (*handler)(struct CtdlMessage *, struct recptypes *), int EventType)
-{
-       MessageFunctionHook *cur, *p, *last;
-       last = NULL;
-       cur = MessageHookTable;
-       while (cur != NULL) {
-               if ((handler == cur->h_function_pointer) &&
-                   (EventType == cur->eventtype))
-               {
-                       syslog(LOG_DEBUG, "extensions: unregistered message function (type %d)", EventType);
-                       p = cur->next;
-                       free(cur);
-                       cur = NULL;
-
-                       if (last != NULL)
-                               last->next = p;
-                       else 
-                               MessageHookTable = p;
-                       cur = p;
-               }
-               else {
-                       last = cur;
-                       cur = cur->next;
-               }
-       }
-}
-
-
-void CtdlRegisterRoomHook(int (*fcn_ptr)(struct ctdlroom *))
-{
-       RoomFunctionHook *newfcn;
-
-       newfcn = (RoomFunctionHook *)
-           malloc(sizeof(RoomFunctionHook));
-       newfcn->next = RoomHookTable;
-       newfcn->fcn_ptr = fcn_ptr;
-       RoomHookTable = newfcn;
-
-       syslog(LOG_DEBUG, "extensions: registered a new room function");
-}
-
-
-void CtdlUnregisterRoomHook(int (*fcn_ptr)(struct ctdlroom *))
-{
-       RoomFunctionHook *cur, *p, *last;
-       last = NULL;
-       cur = RoomHookTable;
-       while (cur != NULL)
-       {
-               if (fcn_ptr == cur->fcn_ptr) {
-                       syslog(LOG_DEBUG, "extensions: unregistered room function");
-                       p = cur->next;
-
-                       free(cur);
-                       cur = NULL;
-
-                       if (last != NULL)
-                               last->next = p;
-                       else 
-                               RoomHookTable = p;
-                       cur = p;
-               }
-               else {
-                       last = cur;
-                       cur = cur->next;
-               }
-       }
-}
-
-
-void CtdlRegisterDeleteHook(void (*handler)(char *, long) )
-{
-       DeleteFunctionHook *newfcn;
-
-       newfcn = (DeleteFunctionHook *)
-           malloc(sizeof(DeleteFunctionHook));
-       newfcn->next = DeleteHookTable;
-       newfcn->h_function_pointer = handler;
-       DeleteHookTable = newfcn;
-
-       syslog(LOG_DEBUG, "extensions: registered a new delete function");
-}
-
-
-void CtdlUnregisterDeleteHook(void (*handler)(char *, long) )
-{
-       DeleteFunctionHook *cur, *p, *last;
-
-       last = NULL;
-       cur = DeleteHookTable;
-       while (cur != NULL) {
-               if (handler == cur->h_function_pointer )
-               {
-                       syslog(LOG_DEBUG, "extensions: unregistered delete function");
-                       p = cur->next;
-                       free(cur);
-
-                       if (last != NULL)
-                               last->next = p;
-                       else
-                               DeleteHookTable = p;
-
-                       cur = p;
-               }
-               else {
-                       last = cur;
-                       cur = cur->next;
-               }
-       }
-}
-
-
-void CtdlRegisterFixedOutputHook(char *content_type, void (*handler)(char *, int) )
-{
-       FixedOutputHook *newfcn;
-
-       newfcn = (FixedOutputHook *)
-           malloc(sizeof(FixedOutputHook));
-       newfcn->next = FixedOutputTable;
-       newfcn->h_function_pointer = handler;
-       safestrncpy(newfcn->content_type, content_type, sizeof newfcn->content_type);
-       FixedOutputTable = newfcn;
-
-       syslog(LOG_DEBUG, "extensions: registered a new fixed output function for %s", newfcn->content_type);
-}
-
-
-void CtdlUnregisterFixedOutputHook(char *content_type)
-{
-       FixedOutputHook *cur, *p, *last;
-
-       last = NULL;
-       cur = FixedOutputTable;
-       while (cur != NULL) {
-               /* This will also remove duplicates if any */
-               if (!strcasecmp(content_type, cur->content_type)) {
-                       syslog(LOG_DEBUG, "extensions: unregistered fixed output function for %s", content_type);
-                       p = cur->next;
-                       free(cur);
-
-                       if (last != NULL)
-                               last->next = p;
-                       else
-                               FixedOutputTable = p;
-                       
-                       cur = p;
-               }
-               else
-               {
-                       last = cur;
-                       cur = cur->next;
-               }
-       }
-}
-
-
-/* returns nonzero if we found a hook and used it */
-int PerformFixedOutputHooks(char *content_type, char *content, int content_length)
-{
-       FixedOutputHook *fcn;
-
-       for (fcn = FixedOutputTable; fcn != NULL; fcn = fcn->next) {
-               if (!strcasecmp(content_type, fcn->content_type)) {
-                       (*fcn->h_function_pointer) (content, content_length);
-                       return(1);
-               }
-       }
-       return(0);
-}
-
-
-void CtdlRegisterXmsgHook(int (*fcn_ptr) (char *, char *, char *, char *), int order)
-{
-
-       XmsgFunctionHook *newfcn;
-
-       newfcn = (XmsgFunctionHook *) malloc(sizeof(XmsgFunctionHook));
-       newfcn->next = XmsgHookTable;
-       newfcn->order = order;
-       newfcn->h_function_pointer = fcn_ptr;
-       XmsgHookTable = newfcn;
-       syslog(LOG_DEBUG, "extensions: registered a new x-msg function (priority %d)", order);
-}
-
-
-void CtdlUnregisterXmsgHook(int (*fcn_ptr) (char *, char *, char *, char *), int order)
-{
-       XmsgFunctionHook *cur, *p, *last;
-
-       last = NULL;
-       cur = XmsgHookTable;
-       while (cur != NULL) {
-               /* This will also remove duplicates if any */
-               if (fcn_ptr == cur->h_function_pointer &&
-                   order == cur->order) {
-                       syslog(LOG_DEBUG, "extensions: unregistered x-msg function (priority %d)", order);
-                       p = cur->next;
-                       free(cur);
-
-                       if (last != NULL) {
-                               last->next = p;
-                       }
-                       else {
-                               XmsgHookTable = p;
-                       }
-                       cur = p;
-               }
-               else {
-                       last = cur;
-                       cur = cur->next;
-               }
-       }
-}
-
-
-void CtdlRegisterServiceHook(int tcp_port,
-                            char *sockpath,
-                            void (*h_greeting_function) (void),
-                            void (*h_command_function) (void),
-                            void (*h_async_function) (void),
-                            const char *ServiceName)
-{
-       ServiceFunctionHook *newfcn;
-       char *message;
-
-       newfcn = (ServiceFunctionHook *) malloc(sizeof(ServiceFunctionHook));
-       message = (char*) malloc (SIZ + SIZ);
-       
-       newfcn->next = ServiceHookTable;
-       newfcn->tcp_port = tcp_port;
-       newfcn->sockpath = sockpath;
-       newfcn->h_greeting_function = h_greeting_function;
-       newfcn->h_command_function = h_command_function;
-       newfcn->h_async_function = h_async_function;
-       newfcn->ServiceName = ServiceName;
-
-       if (sockpath != NULL) {
-               newfcn->msock = ctdl_uds_server(sockpath, CtdlGetConfigInt("c_maxsessions"));
-               snprintf(message, SIZ, "extensions: unix domain socket '%s': ", sockpath);
-       }
-       else if (tcp_port <= 0) {       /* port -1 to disable */
-               syslog(LOG_INFO, "extensions: service %s has been manually disabled, skipping", ServiceName);
-               free (message);
-               free(newfcn);
-               return;
-       }
-       else {
-               newfcn->msock = ctdl_tcp_server(CtdlGetConfigStr("c_ip_addr"), tcp_port, CtdlGetConfigInt("c_maxsessions"));
-               snprintf(message, SIZ, "extensions: TCP port %s:%d: (%s) ", 
-                        CtdlGetConfigStr("c_ip_addr"), tcp_port, ServiceName);
-       }
-
-       if (newfcn->msock > 0) {
-               ServiceHookTable = newfcn;
-               strcat(message, "registered.");
-               syslog(LOG_INFO, "%s", message);
-       }
-       else {
-               AddPortError(message, "failed");
-               strcat(message, "FAILED.");
-               syslog(LOG_ERR, "%s", message);
-               free(newfcn);
-       }
-       free(message);
-}
-
-
-void CtdlUnregisterServiceHook(int tcp_port, char *sockpath,
-                       void (*h_greeting_function) (void),
-                       void (*h_command_function) (void),
-                       void (*h_async_function) (void)
-                       )
-{
-       ServiceFunctionHook *cur, *p, *last;
-
-       last = NULL;
-       cur = ServiceHookTable;
-       while (cur != NULL) {
-               /* This will also remove duplicates if any */
-               if (h_greeting_function == cur->h_greeting_function &&
-                   h_command_function == cur->h_command_function &&
-                   h_async_function == cur->h_async_function &&
-                   tcp_port == cur->tcp_port && 
-                   !(sockpath && cur->sockpath && strcmp(sockpath, cur->sockpath)) )
-               {
-                       if (cur->msock > 0)
-                               close(cur->msock);
-                       if (sockpath) {
-                               syslog(LOG_INFO, "extensions: closed UNIX domain socket %s", sockpath);
-                               unlink(sockpath);
-                       } else if (tcp_port) {
-                               syslog(LOG_INFO, "extensions: closed TCP port %d", tcp_port);
-                       } else {
-                               syslog(LOG_INFO, "extensions: unregistered service \"%s\"", cur->ServiceName);
-                       }
-                       p = cur->next;
-                       free(cur);
-                       if (last != NULL)
-                               last->next = p;
-                       else
-                               ServiceHookTable = p;
-                       cur = p;
-               }
-               else {
-                       last = cur;
-                       cur = cur->next;
-               }
-       }
-}
-
-
-void CtdlShutdownServiceHooks(void)
-{
-       /* sort of a duplicate of close_masters() but called earlier */
-       ServiceFunctionHook *cur;
-
-       cur = ServiceHookTable;
-       while (cur != NULL) 
-       {
-               if (cur->msock != -1)
-               {
-                       close(cur->msock);
-                       cur->msock = -1;
-                       if (cur->sockpath != NULL){
-                               syslog(LOG_INFO, "extensions: [%s] Closed UNIX domain socket %s", cur->ServiceName, cur->sockpath);
-                               unlink(cur->sockpath);
-                       } else {
-                               syslog(LOG_INFO, "extensions: [%s] closing service", cur->ServiceName);
-                       }
-               }
-               cur = cur->next;
-       }
-}
-
-
-void CtdlRegisterSearchFuncHook(void (*fcn_ptr)(int *, long **, const char *), char *name)
-{
-       SearchFunctionHook *newfcn;
-
-       if (!name || !fcn_ptr) {
-               return;
-       }
-       
-       newfcn = (SearchFunctionHook *)
-           malloc(sizeof(SearchFunctionHook));
-       newfcn->next = SearchFunctionHookTable;
-       newfcn->name = name;
-       newfcn->fcn_ptr = fcn_ptr;
-       SearchFunctionHookTable = newfcn;
-
-       syslog(LOG_DEBUG, "extensions: registered a new search function (%s)", name);
-}
-
-void CtdlUnregisterSearchFuncHook(void (*fcn_ptr)(int *, long **, const char *), char *name)
-{
-       SearchFunctionHook *cur, *p, *last;
-       
-       last = NULL;
-       cur = SearchFunctionHookTable;
-       while (cur != NULL) {
-               if (fcn_ptr &&
-                   (cur->fcn_ptr == fcn_ptr) &&
-                   name && !strcmp(name, cur->name))
-               {
-                       syslog(LOG_DEBUG, "extensions: unregistered search function(%s)", name);
-                       p = cur->next;
-                       free (cur);
-                       if (last != NULL)
-                               last->next = p;
-                       else
-                               SearchFunctionHookTable = p;
-                       cur = p;
-               }
-               else {
-                       last = cur;
-                       cur = cur->next;
-               }
-       }
-}
-
-
-void CtdlModuleDoSearch(int *num_msgs, long **search_msgs, const char *search_string, const char *func_name)
-{
-       SearchFunctionHook *fcn = NULL;
-
-       for (fcn = SearchFunctionHookTable; fcn != NULL; fcn = fcn->next) {
-               if (!func_name || !strcmp(func_name, fcn->name)) {
-                       (*fcn->fcn_ptr) (num_msgs, search_msgs, search_string);
-                       return;
-               }
-       }
-       *num_msgs = 0;
-}
-
-
-void PerformSessionHooks(int EventType)
-{
-       SessionFunctionHook *fcn = NULL;
-
-       for (fcn = SessionHookTable; fcn != NULL; fcn = fcn->next) {
-               if (fcn->eventtype == EventType) {
-                       if (EventType == EVT_TIMER) {
-                               pthread_setspecific(MyConKey, NULL);    /* for every hook */
-                       }
-                       (*fcn->h_function_pointer) ();
-               }
-       }
-}
-
-void PerformUserHooks(ctdluser *usbuf, int EventType)
-{
-       UserFunctionHook *fcn = NULL;
-
-       for (fcn = UserHookTable; fcn != NULL; fcn = fcn->next) {
-               if (fcn->eventtype == EventType) {
-                       (*fcn->h_function_pointer) (usbuf);
-               }
-       }
-}
-
-int PerformMessageHooks(struct CtdlMessage *msg, struct recptypes *recps, int EventType)
-{
-       MessageFunctionHook *fcn = NULL;
-       int total_retval = 0;
-
-       /* Other code may elect to protect this message from server-side
-        * handlers; if this is the case, don't do anything.
-        */
-       if (msg->cm_flags & CM_SKIP_HOOKS) {
-               return(0);
-       }
-
-       /* Otherwise, run all the hooks appropriate to this event type.
-        */
-       for (fcn = MessageHookTable; fcn != NULL; fcn = fcn->next) {
-               if (fcn->eventtype == EventType) {
-                       total_retval = total_retval + (*fcn->h_function_pointer) (msg, recps);
-               }
-       }
-
-       /* Return the sum of the return codes from the hook functions.  If
-        * this is an EVT_BEFORESAVE event, a nonzero return code will cause
-        * the save operation to abort.
-        */
-       return total_retval;
-}
-
-
-int PerformRoomHooks(struct ctdlroom *target_room)
-{
-       RoomFunctionHook *fcn;
-       int total_retval = 0;
-
-       syslog(LOG_DEBUG, "extensions: performing room hooks for <%s>", target_room->QRname);
-
-       for (fcn = RoomHookTable; fcn != NULL; fcn = fcn->next) {
-               total_retval = total_retval + (*fcn->fcn_ptr) (target_room);
-       }
-
-       /* Return the sum of the return codes from the hook functions.
-        */
-       return total_retval;
-}
-
-
-void PerformDeleteHooks(char *room, long msgnum)
-{
-       DeleteFunctionHook *fcn;
-
-       for (fcn = DeleteHookTable; fcn != NULL; fcn = fcn->next) {
-               (*fcn->h_function_pointer) (room, msgnum);
-       }
-}
-
-
-int PerformXmsgHooks(char *sender, char *sender_email, char *recp, char *msg)
-{
-       XmsgFunctionHook *fcn;
-       int total_sent = 0;
-       int p;
-
-       for (p=0; p<MAX_XMSG_PRI; ++p) {
-               for (fcn = XmsgHookTable; fcn != NULL; fcn = fcn->next) {
-                       if (fcn->order == p) {
-                               total_sent +=
-                                       (*fcn->h_function_pointer)
-                                               (sender, sender_email, recp, msg);
-                       }
-               }
-               /* Break out of the loop if a higher-priority function
-                * successfully delivered the message.  This prevents duplicate
-                * deliveries to local users simultaneously signed onto
-                * remote services.
-                */
-               if (total_sent) break;
-       }
-       return total_sent;
-}
-
-
-/*
- * Dirty hack until we impliment a hook mechanism for this
- */
-void CtdlModuleStartCryptoMsgs(char *ok_response, char *nosup_response, char *error_response)
-{
-#ifdef HAVE_OPENSSL
-       CtdlStartTLS (ok_response, nosup_response, error_response);
-#endif
-}
-
-
-CTDL_MODULE_INIT(modules)
-{
-       if (!threading) {
-       }
-       return "modules";
-}
diff --git a/citadel/serv_extensions.h b/citadel/serv_extensions.h
deleted file mode 100644 (file)
index c8308cf..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-
-#ifndef SERV_EXTENSIONS_H
-#define SERV_EXTENSIONS_H
-
-#include "server.h"
-
-/*
- * ServiceFunctionHook extensions are used for hooks which implement various
- * protocols (either on TCP or on unix domain sockets) directly in the Citadel server.
- */
-typedef struct ServiceFunctionHook ServiceFunctionHook;
-struct ServiceFunctionHook {
-       ServiceFunctionHook *next;
-       int tcp_port;
-       char *sockpath;
-       void (*h_greeting_function) (void) ;
-       void (*h_command_function) (void) ;
-       void (*h_async_function) (void) ;
-       int msock;
-       const char* ServiceName; /* this is just for debugging and logging purposes. */
-};
-extern ServiceFunctionHook *ServiceHookTable;
-
-void initialize_server_extensions(void);
-int DLoader_Exec_Cmd(char *cmdbuf);
-char *Dynamic_Module_Init(void);
-
-void PerformSessionHooks(int EventType);
-
-void PerformUserHooks(struct ctdluser *usbuf, int EventType);
-
-int PerformXmsgHooks(char *, char *, char *, char *);
-
-int PerformMessageHooks(struct CtdlMessage *, struct recptypes *recps, int EventType);
-
-int PerformRoomHooks(struct ctdlroom *);
-
-void PerformDeleteHooks(char *, long);
-
-
-
-
-
-int PerformFixedOutputHooks(char *, char *, int);
-
-void netcfg_keyname(char *keybuf, long roomnum);
-
-#endif /* SERV_EXTENSIONS_H */
diff --git a/citadel/serv_vcard.h b/citadel/serv_vcard.h
deleted file mode 100644 (file)
index 5e4b68f..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-void extract_inet_email_addrs(char *, size_t, char *, size_t, struct vCard *v, int local_addrs_only);
-struct vCard *vcard_get_user(struct ctdluser *u);
-
-enum {
-       V2L_WRITE,
-       V2L_DELETE
-};
-
diff --git a/citadel/server.h b/citadel/server.h
deleted file mode 100644 (file)
index aa038f4..0000000
+++ /dev/null
@@ -1,331 +0,0 @@
-/* 
- * Main declarations file for the Citadel server
- *
- * Copyright (c) 1987-2022 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#ifndef SERVER_H
-#define SERVER_H
-
-#ifdef __GNUC__
-#define INLINE __inline__
-#else
-#define INLINE
-#endif
-
-#include "citadel.h"
-#ifdef HAVE_OPENSSL
-#define OPENSSL_NO_KRB5                /* work around redhat b0rken ssl headers */
-#include <openssl/ssl.h>
-#endif
-
-/*
- * New format for a message in memory
- */
-struct CtdlMessage {
-       int cm_magic;                   /* Self-check (NOT SAVED TO DISK) */
-       char cm_anon_type;              /* Anonymous or author-visible */
-       char cm_format_type;            /* Format type */
-       char *cm_fields[256];           /* Data fields */
-       long cm_lengths[256];           /* size of datafields */
-       unsigned int cm_flags;          /* How to handle (NOT SAVED TO DISK) */
-};
-
-#define        CTDLMESSAGE_MAGIC               0x159d
-#define        CM_SKIP_HOOKS   0x01            /* Don't run server-side handlers */
-
-
-/* Data structure returned by validate_recipients() */
-struct recptypes {
-       int recptypes_magic;
-        int num_local;
-        int num_internet;
-       int num_room;
-        int num_error;
-       char *errormsg;
-       char *recp_local;
-       char *recp_internet;
-       char *recp_room;
-       char *recp_orgroom;
-       char *display_recp;
-       char *bounce_to;
-       char *envelope_from;
-       char *sending_room;
-};
-
-#define RECPTYPES_MAGIC 0xfeeb
-
-#define CTDLEXIT_SHUTDOWN      0       // Normal shutdown; do NOT auto-restart
-
-/*
- * Exit codes 101 through 109 are used for conditions in which
- * we deliberately do NOT want the service to automatically
- * restart.
- */
-#define CTDLEXIT_CONFIG                101     // Could not read system configuration
-#define CTDLEXIT_HOME          103     // Citadel home directory not found
-#define CTDLEXIT_DB            105     // Unable to initialize database
-#define CTDLEXIT_LIBCITADEL    106     // Incorrect version of libcitadel
-#define CTDL_EXIT_UNSUP_AUTH   107     // Unsupported auth mode configured
-#define CTDLEXIT_UNUSER                108     // Could not determine uid to run as
-#define CTDLEXIT_CRYPTO                109     // Problem initializing SSL or TLS
-
-/*
- * Reasons why a session would be terminated (set CC->kill_me to these values)
- */
-enum {
-       KILLME_NOT,
-       KILLME_UNKNOWN,
-       KILLME_CLIENT_LOGGED_OUT,
-       KILLME_IDLE,
-       KILLME_CLIENT_DISCONNECTED,
-       KILLME_AUTHFAILED,
-       KILLME_SERVER_SHUTTING_DOWN,
-       KILLME_MAX_SESSIONS_EXCEEDED,
-       KILLME_ADMIN_TERMINATE,
-       KILLME_SELECT_INTERRUPTED,
-       KILLME_SELECT_FAILED,
-       KILLME_WRITE_FAILED,
-       KILLME_SIMULATION_WORKER,
-       KILLME_NOLOGIN,
-       KILLME_NO_CRYPTO,
-       KILLME_READSTRING_FAILED,
-       KILLME_MALLOC_FAILED,
-       KILLME_QUOTA,
-       KILLME_READ_FAILED,
-       KILLME_SPAMMER,
-       KILLME_XML_PARSER
-};
-
-
-#define CS_STEALTH     1       /* stealth mode */
-#define CS_CHAT                2       /* chat mode */
-#define CS_POSTING     4       /* Posting */
-
-extern int ScheduledShutdown;
-extern uid_t ctdluid;
-extern int sanity_diag_mode;
-
-struct ExpressMessage {
-       struct ExpressMessage *next;
-       time_t timestamp;       /* When this message was sent */
-       unsigned flags;         /* Special instructions */
-       char sender[256];       /* Name of sending user */
-       char sender_email[256]; /* Email or JID of sending user */
-       char *text;             /* Message text (if applicable) */
-};
-
-#define EM_BROADCAST   1       /* Broadcast message */
-#define EM_GO_AWAY     2       /* Server requests client log off */
-#define EM_CHAT                4       /* Server requests client enter chat */
-
-/*
- * Various things we need to lock and unlock
- */
-enum {
-       S_USERS,
-       S_ROOMS,
-       S_SESSION_TABLE,
-       S_FLOORTAB,
-       S_CHATQUEUE,
-       S_CONTROL,
-       S_NETDB,
-       S_SUPPMSGMAIN,
-       S_CONFIG,
-       S_HOUSEKEEPING,
-       S_DIRECTORY,
-       S_NETCONFIGS,
-       S_FLOORCACHE,
-       S_ATBF,
-       S_JOURNAL_QUEUE,
-       S_CHKPWD,
-       S_LOG,
-       S_NETSPOOL,
-       S_XMPP_QUEUE,
-       S_SCHEDULE_LIST,
-       S_SINGLE_USER,
-       S_LDAP,
-       S_IM_LOGS,
-       S_OPENSSL,
-       MAX_SEMAPHORES
-};
-
-
-/*
- * message transfer formats
- */
-enum {
-       MT_CITADEL,             /* Citadel proprietary */
-       MT_RFC822,              /* RFC822 */
-       MT_MIME,                /* MIME-formatted message */
-       MT_DOWNLOAD,            /* Download a component */
-       MT_SPEW_SECTION         /* Download a component in a single operation */
-};
-
-/*
- * Message format types in the database
- */
-#define FMT_CITADEL    0       /* Citadel vari-format (proprietary) */
-#define FMT_FIXED      1       /* Fixed format (proprietary)        */
-#define FMT_RFC822     4       /* Standard (headers are in M field) */
-
-
-/*
- * Citadel DataBases (define one for each cdb we need to open)
- */
-enum {
-       CDB_MSGMAIN,            /* message base                  */
-       CDB_USERS,              /* user file                     */
-       CDB_ROOMS,              /* room index                    */
-       CDB_FLOORTAB,           /* floor index                   */
-       CDB_MSGLISTS,           /* room message lists            */
-       CDB_VISIT,              /* user/room relationships       */
-       CDB_DIRECTORY,          /* address book directory        */
-       CDB_USETABLE,           /* network use table             */
-       CDB_BIGMSGS,            /* larger message bodies         */
-       CDB_FULLTEXT,           /* full text search index        */
-       CDB_EUIDINDEX,          /* locate msgs by EUID           */
-       CDB_USERSBYNUMBER,      /* index of users by number      */
-       CDB_EXTAUTH,            /* associates OpenIDs with users */
-       CDB_CONFIG,             /* system configuration database */
-       MAXCDB                  /* total number of CDB's defined */
-};
-
-struct cdbdata {
-       size_t len;
-       char *ptr;
-};
-
-
-/*
- * Event types can't be enum'ed, because they must remain consistent between
- * builds (to allow for binary modules built somewhere else)
- */
-#define EVT_STOP       0       /* Session is terminating */
-#define EVT_START      1       /* Session is starting */
-#define EVT_LOGIN      2       /* A user is logging in */
-#define EVT_NEWROOM    3       /* Changing rooms */
-#define EVT_LOGOUT     4       /* A user is logging out */
-#define EVT_SETPASS    5       /* Setting or changing password */
-#define EVT_CMD                6       /* Called after each server command */
-#define EVT_RWHO       7       /* An RWHO command is being executed */
-#define EVT_ASYNC      8       /* Doing asynchronous messages */
-#define EVT_STEALTH    9       /* Entering stealth mode */
-#define EVT_UNSTEALTH  10      /* Exiting stealth mode */
-
-#define EVT_TIMER      50      /* Timer events are called once per minute
-                                  and are not tied to any session */
-#define EVT_HOUSE      51      /* as needed houskeeping stuff */
-#define EVT_SHUTDOWN   52      /* Server is shutting down */
-
-#define EVT_PURGEUSER  100     /* Deleting a user */
-#define EVT_NEWUSER    102     /* Creating a user */
-
-#define EVT_BEFORESAVE 201
-#define EVT_AFTERSAVE  202
-#define EVT_SMTPSCAN   203     /* called before submitting a msg from SMTP */
-#define EVT_AFTERUSRMBOXSAVE 204 /* called afte a message was saved into a users inbox */
-/* Priority levels for paging functions (lower is better) */
-enum {
-       XMSG_PRI_LOCAL,         /* Other users on -this- server */
-       XMSG_PRI_REMOTE,        /* Other users on a Citadel network (future) */
-       XMSG_PRI_FOREIGN,       /* Contacts on foreign instant message hosts */
-       MAX_XMSG_PRI
-};
-
-
-/* Defines the relationship of a user to a particular room */
-typedef struct __visit {
-       long v_roomnum;
-       long v_roomgen;
-       long v_usernum;
-       long v_lastseen;
-       unsigned int v_flags;
-       char v_seen[SIZ];
-       char v_answered[SIZ];
-       int v_view;
-} visit;
-
-#define V_FORGET       1       /* User has zapped this room        */
-#define V_LOCKOUT      2       /* User is locked out of this room  */
-#define V_ACCESS       4       /* Access is granted to this room   */
-
-
-/* Supplementary data for a message on disk
- * These are kept separate from the message itself for one of two reasons:
- * 1. Either their values may change at some point after initial save, or
- * 2. They are merely caches of data which exist somewhere else, for speed.
- * DO NOT PUT BIG DATA IN HERE ... we need this struct to be tiny for lots of quick r/w
- */
-struct MetaData {
-       long meta_msgnum;               /* Message number in *local* message base */
-       int meta_refcount;              /* Number of rooms pointing to this msg */
-       char meta_content_type[64];     /* Cached MIME content-type */
-       long meta_rfc822_length;        /* Cache of RFC822-translated msg length */
-};
-
-
-/* Calls to AdjRefCount() are queued and deferred, so the user doesn't
- * have to wait for various disk-intensive operations to complete synchronously.
- * This is the record format.
- */
-struct arcq {
-       long arcq_msgnum;               /* Message number being adjusted */
-       int arcq_delta;                 /* Adjustment ( usually 1 or -1 ) */
-};
-
-
-/*
- * Serialization routines use this struct to return a pointer and a length
- */
-struct ser_ret {
-       size_t len;
-       unsigned char *ser;
-};
-
-
-/*
- * The S_USETABLE database is used in several modules now, so we define its format here.
- */
-struct UseTable {
-       char ut_msgid[SIZ];
-       time_t ut_timestamp;
-};
-
-
-/*
- * These one-byte field headers are found in the Citadel message store.
- */
-typedef enum _MsgField {
-       eAuthor       = 'A',
-       eBig_message  = 'B',
-       eExclusiveID  = 'E',
-       erFc822Addr   = 'F',
-       emessageId    = 'I',
-       eJournal      = 'J',
-       eReplyTo      = 'K',
-       eListID       = 'L',
-       eMesageText   = 'M',
-       eOriginalRoom = 'O',
-       eMessagePath  = 'P',
-       eRecipient    = 'R',
-       eTimestamp    = 'T',
-       eMsgSubject   = 'U',
-       eenVelopeTo   = 'V',
-       eWeferences   = 'W',
-       eCarbonCopY   = 'Y',
-       eErrorMsg     = '0',
-       eSuppressIdx  = '1',
-       eExtnotify    = '2',
-       eVltMsgNum    = '3'
-} eMsgField;
-
-#endif /* SERVER_H */
diff --git a/citadel/server/citadel.h b/citadel/server/citadel.h
new file mode 100644 (file)
index 0000000..5e80d65
--- /dev/null
@@ -0,0 +1,187 @@
+// Main Citadel header file
+//
+// Copyright (c) 1987-2021 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+/* system customizations are in sysconfig.h */
+
+#ifndef CITADEL_H
+#define CITADEL_H
+/* #include <dmalloc.h> uncomment if using dmalloc */
+
+#include "sysdep.h"
+#include <limits.h>
+#include "sysconfig.h"
+#include "typesize.h"
+#include "ipcdef.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Text description of this software
+ * (We used to define this ourselves, but why bother when the build tools do it for us?)
+ */
+#define CITADEL        PACKAGE_STRING
+
+#define REV_LEVEL 951          // This version
+#define REV_MIN                591             // Oldest compatible database
+#define EXPORT_REV_MIN 931             // Oldest compatible export files
+#define LIBCITADEL_MIN 931             // Minimum required version of libcitadel
+#define SERVER_TYPE    0               // zero for stock Citadel; other developers please obtain SERVER_TYPE codes for your implementations
+
+#ifdef LIBCITADEL_VERSION_NUMBER
+#if LIBCITADEL_VERSION_NUMBER < LIBCITADEL_MIN
+#error libcitadel is too old.  Please upgrade it before continuing.
+#endif
+#endif
+
+/*
+ * This is the user name and password for the default administrator account
+ * that is created when Citadel Server is started with an empty database.
+ */
+#define DEFAULT_ADMIN_USERNAME "admin"
+#define DEFAULT_ADMIN_PASSWORD "citadel"
+
+/* Various length constants */
+
+#define ROOMNAMELEN    128             /* The size of a roomname string */
+#define USERNAME_SIZE  64              /* The size of a username string */
+#define MAX_EDITORS    5               /* number of external editors supported ; must be at least 1 */
+
+/*
+ * Message expiration policy stuff
+ */
+typedef struct ExpirePolicy ExpirePolicy;
+struct ExpirePolicy {
+       int expire_mode;
+       int expire_value;
+};
+
+#define EXPIRE_NEXTLEVEL       0       // Inherit expiration policy
+#define EXPIRE_MANUAL          1       // Don't expire messages at all
+#define EXPIRE_NUMMSGS         2       // Keep only latest n messages
+#define EXPIRE_AGE             3       // Expire messages after n days
+
+
+/*
+ * User records.
+ */
+typedef struct ctdluser ctdluser;
+struct ctdluser {                      // User record
+       int version;                    // Citadel version which created this record
+       uid_t uid;                      // Associate with a unix account?
+       char password[32];              // password
+       unsigned flags;                 // See US_ flags below
+       long timescalled;               // Total number of logins
+       long posted;                    // Number of messages ever submitted
+       cit_uint8_t axlevel;            // Access level
+       long usernum;                   // User number (never recycled)
+       time_t lastcall;                // Date/time of most recent login
+       int USuserpurge;                // Purge time (in days) for user
+       char fullname[64];              // Display name (primary identifier)
+       long msgnum_bio;                // msgnum of user's profile (bio)
+       long msgnum_pic;                // msgnum of user's avatar (photo)
+       char emailaddrs[512];           // Internet email addresses
+       long msgnum_inboxrules;         // msgnum of user's inbox filtering rules
+       long lastproc_inboxrules;       // msgnum of last message filtered
+};
+
+
+/* Bits which may appear in MMflags.
+ */
+#define MM_VALID       4               // New users need validating
+
+/*
+ * Room records.
+ */
+typedef struct ctdlroom ctdlroom;
+struct ctdlroom {
+       char QRname[ROOMNAMELEN];       // Name of room
+       char QRpasswd[10];              // Only valid if it's a private rm
+       long QRroomaide;                // User number of room aide
+       long QRhighest;                 // Highest message NUMBER in room
+       time_t QRgen;                   // Generation number of room
+       unsigned QRflags;               // See flag values below
+       char QRdirname[15];             // Directory name, if applicable
+       long msgnum_info;               // msgnum of room banner (info file)
+       char QRfloor;                   // Which floor this room is on
+       time_t QRmtime;                 // Date/time of last post
+       struct ExpirePolicy QRep;       // Message expiration policy
+       long QRnumber;                  // Globally unique room number
+       char QRorder;                   // Sort key for room listing order
+       unsigned QRflags2;              // Additional flags
+       int QRdefaultview;              // How to display the contents
+       long msgnum_pic;                // msgnum of room picture or icon
+};
+
+/* Private rooms are always flagged with QR_PRIVATE.  If neither QR_PASSWORDED
+ * or QR_GUESSNAME is set, then it is invitation-only.  Passworded rooms are
+ * flagged with both QR_PRIVATE and QR_PASSWORDED while guess-name rooms are
+ * flagged with both QR_PRIVATE and QR_GUESSNAME.  NEVER set all three flags.
+ */
+
+/*
+ * Miscellaneous
+ */
+#define MES_NORMAL     65              // Normal message
+#define MES_ANONONLY   66              // "****" header
+#define MES_ANONOPT    67              // "Anonymous" header
+
+/****************************************************************************/
+
+/*
+ * Floor record.  The floor number is implicit in its location in the file.
+ */
+typedef struct floor floor;
+struct floor {
+       unsigned short f_flags;         // flags
+       char f_name[256];               // name of floor
+       int f_ref_count;                // reference count
+       struct ExpirePolicy f_ep;       // default expiration policy
+};
+
+#define F_INUSE                1               // floor is in use
+
+
+/*
+ * Values used internally for function call returns, etc.
+ */
+#define NEWREGISTER    0               // new user to register
+#define REREGISTER     1               // existing user reregistering
+
+/* number of items which may be handled by the CONF command */
+#define NUM_CONFIGS 71
+
+#define TRACE  syslog(LOG_DEBUG, "\033[7m  Checkpoint: %s : %d  \033[0m", __FILE__, __LINE__)
+
+#ifndef LONG_MAX
+#define LONG_MAX 2147483647L
+#endif
+
+/*
+ * Authentication modes
+ */
+#define AUTHMODE_NATIVE                0       // Native (self-contained or "black box")
+#define AUTHMODE_HOST          1       // Authenticate against the host OS user database
+#define AUTHMODE_LDAP          2       // Authenticate using LDAP server with RFC 2307 schema
+#define AUTHMODE_LDAP_AD       3       // Authenticate using LDAP server with Active Directory schema
+
+#ifdef __cplusplus
+}
+#endif
+
+#if __GNUC__ >= 8
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-truncation"
+#endif
+
+#endif /* CITADEL_H */
diff --git a/citadel/server/citadel_dirs.c b/citadel/server/citadel_dirs.c
new file mode 100644 (file)
index 0000000..0560477
--- /dev/null
@@ -0,0 +1,59 @@
+// citadel_dirs.c : calculate pathnames for various files used in the Citadel system
+//
+// Copyright (c) 1987-2021 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or disclosure
+// is subject to the terms of the GNU General Public License, version 3.
+// The program is distributed without any warranty, expressed or implied.
+
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <time.h>
+#include <errno.h>
+#include <syslog.h>
+#include <libcitadel.h>
+#include "citadel.h"
+#include "citadel_dirs.h"
+
+// Generate an associated file name for a room
+size_t assoc_file_name(char *buf, size_t n, struct ctdlroom *qrbuf, const char *prefix) {
+       return snprintf(buf, n, "%s%ld", prefix, qrbuf->QRnumber);
+}
+
+
+int create_dir(char *which, long ACCESS, long UID, long GID) {
+       int rv;
+       rv = mkdir(which, ACCESS);
+       if ((rv == -1) && (errno != EEXIST)) {
+               syslog(LOG_ERR,
+                      "failed to create directory %s: %s",
+                      which,
+                      strerror(errno));
+               return rv;
+       }
+       rv = chmod(which, ACCESS);
+       if (rv == -1) {
+               syslog(LOG_ERR, "failed to set permissions for directory %s: %s", which, strerror(errno));
+               return rv;
+       }
+       rv = chown(which, UID, GID);
+       if (rv == -1) {
+               syslog(LOG_ERR, "failed to set owner for directory %s: %s", which, strerror(errno));
+               return rv;
+       }
+       return rv;
+}
+
+
+int create_run_directories(long UID, long GID) {
+       int rv = 0;
+       rv += create_dir(ctdl_message_dir   , S_IRUSR|S_IWUSR|S_IXUSR, UID, -1);
+       rv += create_dir(ctdl_file_dir      , S_IRUSR|S_IWUSR|S_IXUSR, UID, -1);
+       rv += create_dir(ctdl_key_dir       , S_IRUSR|S_IWUSR|S_IXUSR, UID, -1);
+       rv += create_dir(ctdl_run_dir       , S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH, UID, GID);
+       return rv;
+}
diff --git a/citadel/server/citadel_dirs.h b/citadel/server/citadel_dirs.h
new file mode 100644 (file)
index 0000000..ad422bf
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef __CITADEL_DIRS_H
+#define __CITADEL_DIRS_H
+
+#include <limits.h>
+
+/* Fixed directory names (some of these are obsolete and used only for migration) */
+#define ctdl_home_directory    "."
+#define ctdl_db_dir            "data"
+#define ctdl_file_dir          "files"
+#define ctdl_shared_dir                "."
+#define ctdl_image_dir         "images"
+#define ctdl_info_dir          "info"
+#define ctdl_key_dir           "keys"
+#define ctdl_message_dir       "messages"
+#define ctdl_usrpic_dir                "userpics"
+#define ctdl_autoetc_dir       "."
+#define ctdl_run_dir           "."
+#define ctdl_netcfg_dir                "netconfigs"
+#define ctdl_bbsbase_dir       "."
+#define ctdl_sbin_dir          "."
+#define ctdl_bin_dir           "."
+#define ctdl_utilbin_dir       "."
+
+/* Fixed file names (some of these are obsolete and used only for migration) */
+#define file_citadel_config            "citadel.config"
+#define file_lmtp_socket               "lmtp.socket"
+#define file_lmtp_unfiltered_socket    "lmtp-unfiltered.socket"
+#define file_arcq                      "refcount_adjustments.dat"
+#define file_citadel_socket            "citadel.socket"
+#define file_citadel_admin_socket      "citadel-admin.socket"
+#define file_pid_file                  "/var/run/citserver.pid"
+#define file_pid_paniclog              "panic.log"
+#define file_crpt_file_key             "keys/citadel.key"
+#define file_crpt_file_cer             "keys/citadel.cer"
+#define file_chkpwd                    "chkpwd"
+#define file_guesstimezone             "guesstimezone.sh"
+
+
+/* externs */
+extern int create_run_directories(long UID, long GUID);
+extern size_t assoc_file_name(char *buf, size_t n, struct ctdlroom *qrbuf, const char *prefix);
+extern FILE *create_digest_file(struct ctdlroom *room, int forceCreate);
+extern void remove_digest_file(struct ctdlroom *room);
+
+#endif /* __CITADEL_DIRS_H */
diff --git a/citadel/server/citadel_ldap.h b/citadel/server/citadel_ldap.h
new file mode 100644 (file)
index 0000000..926fbde
--- /dev/null
@@ -0,0 +1,17 @@
+// Configuration for LDAP authentication.  Most of this stuff gets pulled out of our site config file.
+//
+// Copyright (c) 1987-2017 by the citadel.org team
+//
+//  This program is open source software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License version 3.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+
+int CtdlTryUserLDAP(char *username, char *found_dn, int found_dn_size, char *fullname, int fullname_size, uid_t *found_uid);
+int CtdlTryPasswordLDAP(char *user_dn, const char *password);
+int Ctdl_LDAP_to_vCard(char *ldap_dn, struct vCard *v);
+int extract_email_addresses_from_ldap(char *ldap_dn, char *emailaddrs);
+void CtdlSynchronizeUsersFromLDAP(void);
diff --git a/citadel/server/citserver.c b/citadel/server/citserver.c
new file mode 100644 (file)
index 0000000..5c668a7
--- /dev/null
@@ -0,0 +1,198 @@
+// Main source module for the Citadel server
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or disclosure
+// is subject to the terms of the GNU General Public License, version 3.
+// The program is distributed without any warranty, expressed or implied.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include "sysdep.h"
+#include <time.h>
+#include <libcitadel.h>
+
+#include "ctdl_module.h"
+#include "housekeeping.h"
+#include "locate_host.h"
+#include "citserver.h"
+#include "user_ops.h"
+#include "control.h"
+#include "config.h"
+
+char *unique_session_numbers;
+int ScheduledShutdown = 0;
+time_t server_startup_time;
+int panic_fd;
+int openid_level_supported = 0;
+
+
+// We need pseudo-random numbers for a few things.  Seed generously.
+void seed_random_number_generator(void) {
+       FILE *urandom;
+       struct timeval tv;
+       unsigned int seed;
+
+       syslog(LOG_INFO, "Seeding the pseudo-random number generator...");
+       urandom = fopen("/dev/urandom", "r");
+       if (urandom != NULL) {
+               if (fread(&seed, sizeof seed, 1, urandom) == -1) {
+                       syslog(LOG_ERR, "citserver: failed to read random seed: %m");
+               }
+               fclose(urandom);
+       }
+       else {
+               gettimeofday(&tv, NULL);
+               seed = tv.tv_usec;
+       }
+       srand(seed);
+       srandom(seed);
+}
+
+
+// Various things that need to be initialized at startup
+void master_startup(void) {
+       struct ctdlroom qrbuf;
+       struct passwd *pw;
+       gid_t gid;
+
+       syslog(LOG_DEBUG, "master_startup() started");
+       time(&server_startup_time);
+
+       syslog(LOG_INFO, "Checking directory access");
+       if ((pw = getpwuid(ctdluid)) == NULL) {
+               gid = getgid();
+       }
+       else {
+               gid = pw->pw_gid;
+       }
+
+       if (create_run_directories(CTDLUID, gid) != 0) {
+               syslog(LOG_ERR, "citserver: failed to access and create directories");
+               exit(1);
+       }
+       syslog(LOG_INFO, "Opening databases");
+       open_databases();
+
+       // Load site-specific configuration
+       seed_random_number_generator();                                 // must be done before config system
+       syslog(LOG_INFO, "Initializing configuration system");
+       initialize_config_system();
+       validate_config();
+       migrate_legacy_control_record();
+
+       // If we have an existing database that is older than version 928, reindex the user records.
+       // Unfortunately we cannot do this in serv_upgrade.c because it needs to happen VERY early during startup.
+       int existing_db = CtdlGetConfigInt("MM_hosted_upgrade_level");
+       if ( (existing_db > 0) && (existing_db < 928) ) {
+               ForEachUser(reindex_user_928, NULL);
+       }
+
+       // Check floor reference counts
+       check_ref_counts();
+
+       syslog(LOG_INFO, "Creating base rooms (if necessary)");
+       CtdlCreateRoom(CtdlGetConfigStr("c_baseroom"), 0, "", 0, 1, 0, VIEW_BBS);
+       CtdlCreateRoom(AIDEROOM, 3, "", 0, 1, 0, VIEW_BBS);
+       CtdlCreateRoom(SYSCONFIGROOM, 3, "", 0, 1, 0, VIEW_BBS);
+       CtdlCreateRoom(CtdlGetConfigStr("c_twitroom"), 0, "", 0, 1, 0, VIEW_BBS);
+
+       // The "Local System Configuration" room doesn't need to be visible
+       if (CtdlGetRoomLock(&qrbuf, SYSCONFIGROOM) == 0) {
+               qrbuf.QRflags2 |= QR2_SYSTEM;
+               CtdlPutRoomLock(&qrbuf);
+       }
+
+       // Aide needs to be public postable, else we're not RFC conformant.
+       if (CtdlGetRoomLock(&qrbuf, AIDEROOM) == 0) {
+               qrbuf.QRflags2 |= QR2_SMTP_PUBLIC;
+               CtdlPutRoomLock(&qrbuf);
+       }
+
+       syslog(LOG_DEBUG, "master_startup() finished");
+}
+
+
+// Cleanup routine to be called when the server is shutting down.  Returns the needed exit code.
+int master_cleanup(int exitcode) {
+       static int already_cleaning_up = 0;
+
+       if (already_cleaning_up) {
+               while (1) {
+                       usleep(1000000);
+               }
+       }
+       already_cleaning_up = 1;
+
+       // Do system-dependent stuff
+       sysdep_master_cleanup();
+
+       // Close the configuration system
+       shutdown_config_system();
+
+       // Close databases
+       syslog(LOG_INFO, "citserver: closing databases");
+       close_databases();
+
+       // If the operator requested a halt but not an exit, halt here.
+       if (shutdown_and_halt) {
+               syslog(LOG_ERR, "citserver: Halting server without exiting.");
+               fflush(stdout);
+               fflush(stderr);
+               while (1) {
+                       sleep(32767);
+               }
+       }
+
+       // Now go away.
+       syslog(LOG_ERR, "citserver: Exiting with status %d", exitcode);
+       fflush(stdout);
+       fflush(stderr);
+
+       if (restart_server != 0) {
+               exitcode = 1;
+       }
+       else if ((running_as_daemon != 0) && ((exitcode == 0))) {
+               exitcode = CTDLEXIT_SHUTDOWN;
+       }
+       return (exitcode);
+}
+
+
+// returns an asterisk if there are any instant messages waiting, space otherwise.
+char CtdlCheckExpress(void) {
+       if (CC->FirstExpressMessage == NULL) {
+               return (' ');
+       }
+       else {
+               return ('*');
+       }
+}
+
+
+void citproto_begin_session() {
+       if (CC->nologin == 1) {
+               cprintf("%d Too many users are already online (maximum is %d)\n",
+                       ERROR + MAX_SESSIONS_EXCEEDED, CtdlGetConfigInt("c_maxsessions")
+               );
+               CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED;
+       }
+       else {
+               cprintf("%d %s Citadel server ready.\n", CIT_OK, CtdlGetConfigStr("c_fqdn"));
+               CC->can_receive_im = 1;
+       }
+}
+
+
+void citproto_begin_admin_session() {
+       CC->internal_pgm = 1;
+       cprintf("%d %s Citadel server ADMIN CONNECTION ready.\n", CIT_OK, CtdlGetConfigStr("c_fqdn"));
+}
+
+
+// This loop performs all asynchronous functions.
+void do_async_loop(void) {
+       PerformSessionHooks(EVT_ASYNC);
+}
diff --git a/citadel/server/citserver.h b/citadel/server/citserver.h
new file mode 100644 (file)
index 0000000..effe955
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright (c) 1987-2019 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+/* I fucking hate const and want it to die. */
+#pragma GCC diagnostic ignored "-Wcast-qual"
+
+#include "serv_extensions.h"
+#include "context.h"
+#include "ctdl_module.h"
+
+/* Simple linked list structures ... used in a bunch of different places. */
+typedef struct RoomProcList RoomProcList;
+struct RoomProcList {
+        struct RoomProcList *next;
+        char name[ROOMNAMELEN];
+        char lcname[ROOMNAMELEN];
+       long namelen;
+       long lastsent;
+       long key;
+       long QRNum;
+};
+struct UserProcList {
+       struct UserProcList *next;
+       char user[USERNAME_SIZE];
+};
+
+#define CTDLUSERIP      (IsEmptyStr(CC->cs_addr) ?  CC->cs_clientinfo: CC->cs_addr)
+
+void master_startup (void);
+int master_cleanup (int exitcode);
+void set_wtmpsupp (char *newtext);
+void set_wtmpsupp_to_current_room(void);
+void do_command_loop(void);
+void do_async_loop(void);
+void begin_session(struct CitContext *con);
+void citproto_begin_session(void);
+void citproto_begin_admin_session(void);
+void help_subst (char *strbuf, char *source, char *dest);
+char CtdlCheckExpress(void);
+
+extern int panic_fd;
+extern time_t server_startup_time;
+extern int openid_level_supported;
diff --git a/citadel/server/clientsocket.c b/citadel/server/clientsocket.c
new file mode 100644 (file)
index 0000000..a88c614
--- /dev/null
@@ -0,0 +1,270 @@
+// This module handles client-side sockets opened by the Citadel server (for
+// the client side of Internet protocols, etc.)   It does _not_ handle client
+// sockets for the Citadel client; for that you must look in ipc_c_tcp.c
+// (which, uncoincidentally, bears a striking similarity to this file).
+//
+// Copyright (c) 1987-2017 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or disclosure
+// is subject to the terms of the GNU General Public License, version 3.
+// The program is distributed without any warranty, expressed or implied.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <stdio.h>
+#ifdef __FreeBSD__
+#include <sys/socket.h>
+#endif
+#include <libcitadel.h>
+#include "ctdl_module.h"
+#include "clientsocket.h"
+
+int sock_connect(char *host, char *service) {
+       struct in6_addr serveraddr;
+       struct addrinfo hints;
+       struct addrinfo *res = NULL;
+       struct addrinfo *ai = NULL;
+       int rc = (-1);
+       int sock = (-1);
+
+       if ((host == NULL) || IsEmptyStr(host))
+               return (-1);
+       if ((service == NULL) || IsEmptyStr(service))
+               return (-1);
+
+       memset(&hints, 0x00, sizeof(hints));
+       hints.ai_flags = AI_NUMERICSERV;
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = SOCK_STREAM;
+
+       // Handle numeric IPv4 and IPv6 addresses
+       rc = inet_pton(AF_INET, host, &serveraddr);
+       if (rc == 1) {                                          // dotted quad
+               hints.ai_family = AF_INET;
+               hints.ai_flags |= AI_NUMERICHOST;
+       }
+       else {
+               rc = inet_pton(AF_INET6, host, &serveraddr);
+               if (rc == 1) {                                  // IPv6 address
+                       hints.ai_family = AF_INET6;
+                       hints.ai_flags |= AI_NUMERICHOST;
+               }
+       }
+
+       // Begin the connection process
+       rc = getaddrinfo(host, service, &hints, &res);
+       if (rc != 0) {
+               syslog(LOG_ERR, "%s: %s", host, gai_strerror(rc));
+               return(-1);
+       }
+
+       // Try all available addresses until we connect to one or until we run out.
+       for (ai = res; ai != NULL; ai = ai->ai_next) {
+               sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+               if (sock < 0) {
+                       syslog(LOG_ERR, "%s: %m", host);
+                       freeaddrinfo(res);
+                       return(-1);
+               }
+               rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
+               if (rc >= 0) {
+                        freeaddrinfo(res);
+                       return(sock);
+               }
+               else {
+                       syslog(LOG_ERR, "%s: %m", host);
+                       close(sock);
+               }
+       }
+       freeaddrinfo(res);
+       return(-1);
+}
+
+
+// Read data from the client socket.
+//
+// sock                socket fd to read from
+// buf         buffer to read into 
+// bytes       number of bytes to read
+// timeout     Number of seconds to wait before timing out
+//
+// Possible return values:
+//      1       Requested number of bytes has been read.
+//      0       Request timed out.
+//     -1      Connection is broken, or other error.
+int socket_read_blob(int *Socket, StrBuf *Target, int bytes, int timeout) {
+       const char *Error;
+       int retval = 0;
+
+       retval = StrBufReadBLOBBuffered(Target, CC->SBuf.Buf, &CC->SBuf.ReadWritePointer, Socket, 1, bytes, O_TERM, &Error); 
+       if (retval < 0) {
+               syslog(LOG_ERR, "clientsocket: socket_read_blob() failed: %s", Error);
+       }
+       return retval;
+}
+
+
+int CtdlSockGetLine(int *sock, StrBuf *Target, int nSec) {
+       const char *Error;
+       int rc;
+
+       FlushStrBuf(Target);
+       rc = StrBufTCP_read_buffered_line_fast(Target,
+                                              CC->SBuf.Buf,
+                                              &CC->SBuf.ReadWritePointer,
+                                              sock, nSec, 1, &Error);
+       if ((rc < 0) && (Error != NULL)) {
+               syslog(LOG_ERR, "clientsocket: CtdlSockGetLine() failed: %s", Error);
+       }
+       return rc;
+}
+
+
+// client_getln()   ...   Get a LF-terminated line of text from the client.
+int sock_getln(int *sock, char *buf, int bufsize) {
+       int i, retval;
+       const char *pCh;
+
+       FlushStrBuf(CC->sMigrateBuf);
+       retval = CtdlSockGetLine(sock, CC->sMigrateBuf, 5);
+
+       i = StrLength(CC->sMigrateBuf);
+       pCh = ChrPtr(CC->sMigrateBuf);
+
+       memcpy(buf, pCh, i + 1);
+
+       FlushStrBuf(CC->sMigrateBuf);
+       if (retval < 0) {
+               safestrncpy(&buf[i], "000", bufsize - i);
+               i += 3;
+       }
+       return i;
+}
+
+
+// sock_write() - send binary to server.
+// Returns the number of bytes written, or -1 for error.
+int sock_write(int *sock, const char *buf, int nbytes) {
+       return sock_write_timeout(sock, buf, nbytes, 50);
+}
+
+
+int sock_write_timeout(int *sock, const char *buf, int nbytes, int timeout) {
+       int nSuccessLess = 0;
+       int bytes_written = 0;
+       int retval;
+       fd_set rfds;
+        int fdflags;
+       int IsNonBlock;
+       struct timeval tv;
+       int selectresolution = 100;
+
+       fdflags = fcntl(*sock, F_GETFL);
+       IsNonBlock = (fdflags & O_NONBLOCK) == O_NONBLOCK;
+
+       while ((nSuccessLess < timeout) && (*sock != -1) && (bytes_written < nbytes)) {
+               if (IsNonBlock) {
+                       tv.tv_sec = selectresolution;
+                       tv.tv_usec = 0;
+                       
+                       FD_ZERO(&rfds);
+                       FD_SET(*sock, &rfds);
+                       if (select(*sock + 1, NULL, &rfds, NULL, &tv) == -1) {
+                               close (*sock);
+                               *sock = -1;
+                               return -1;
+                       }
+               }
+               if (IsNonBlock && !  FD_ISSET(*sock, &rfds)) {
+                       nSuccessLess ++;
+                       continue;
+               }
+               retval = write(*sock, &buf[bytes_written],
+                              nbytes - bytes_written);
+               if (retval < 1) {
+                       sock_close(*sock);
+                       *sock = -1;
+                       return (-1);
+               }
+               bytes_written = bytes_written + retval;
+               if (IsNonBlock && (bytes_written == nbytes)){
+                       tv.tv_sec = selectresolution;
+                       tv.tv_usec = 0;
+                       
+                       FD_ZERO(&rfds);
+                       FD_SET(*sock, &rfds);
+                       if (select(*sock + 1, NULL, &rfds, NULL, &tv) == -1) {
+                               close (*sock);
+                               *sock = -1;
+                               return -1;
+                       }
+               }
+       }
+       return (bytes_written);
+}
+
+
+// client_getln()   ...   Get a LF-terminated line of text from the client.
+int sock_getln_err(int *sock, char *buf, int bufsize, int *rc, int nSec) {
+       int i, retval;
+       const char *pCh;
+
+       FlushStrBuf(CC->sMigrateBuf);
+       *rc = retval = CtdlSockGetLine(sock, CC->sMigrateBuf, nSec);
+
+       i = StrLength(CC->sMigrateBuf);
+       pCh = ChrPtr(CC->sMigrateBuf);
+
+       memcpy(buf, pCh, i + 1);
+
+       FlushStrBuf(CC->sMigrateBuf);
+       if (retval < 0) {
+               safestrncpy(&buf[i], "000", bufsize - i);
+               i += 3;
+       }
+       return i;
+}
+
+
+// Multiline version of sock_gets() ... this is a convenience function for
+// client side protocol implementations.  It only returns the first line of
+// a multiline response, discarding the rest.
+int ml_sock_gets(int *sock, char *buf, int nSec) {
+       int rc = 0;
+       char bigbuf[1024];
+       int g;
+
+       g = sock_getln_err(sock, buf, SIZ, &rc, nSec);
+       if (rc < 0)
+               return rc;
+       if (g < 4)
+               return (g);
+       if (buf[3] != '-')
+               return (g);
+
+       do {
+               g = sock_getln_err(sock, bigbuf, SIZ, &rc, nSec);
+               if (rc < 0)
+                       return rc;
+               if (g < 0)
+                       return (g);
+       } while ((g >= 4) && (bigbuf[3] == '-'));
+
+       return (strlen(buf));
+}
+
+
+// sock_puts() - send line to server - implemented in terms of serv_write()
+// Returns the number of bytes written, or -1 for error.
+int sock_puts(int *sock, char *buf) {
+       int i, j;
+
+       i = sock_write(sock, buf, strlen(buf));
+       if (i < 0)
+               return (i);
+       j = sock_write(sock, "\n", 1);
+       if (j < 0)
+               return (j);
+       return (i + j);
+}
diff --git a/citadel/server/clientsocket.h b/citadel/server/clientsocket.h
new file mode 100644 (file)
index 0000000..cc275e1
--- /dev/null
@@ -0,0 +1,32 @@
+// Header file for TCP client socket library
+//
+// Copyright (c) 1987-2012 by the citadel.org team
+//
+//  This program is open source software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License version 3.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+
+int sock_connect(char *host, char *service);
+int sock_write(int *sock, const char *buf, int nbytes);
+int sock_write_timeout(int *sock, const char *buf, int nbytes, int timeout);
+int ml_sock_gets(int *sock, char *buf, int nSec);
+int sock_getln(int *sock, char *buf, int bufsize);
+int CtdlSockGetLine(int *sock, StrBuf *Target, int nSec);
+int sock_puts(int *sock, char *buf);
+int socket_read_blob(int *Socket, StrBuf * Target, int bytes, int timeout);
+
+
+/* 
+ * This looks dumb, but it's being done for future portability
+ */
+#define sock_close(sock)               close(sock)
+#define sock_shutdown(sock, how)       shutdown(sock, how)
+
+/* 
+ * Default timeout for client sessions
+ */
+#define CLIENT_TIMEOUT         600
diff --git a/citadel/server/config.c b/citadel/server/config.c
new file mode 100644 (file)
index 0000000..86077a0
--- /dev/null
@@ -0,0 +1,485 @@
+// Read and write the system configuration database
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or disclosure
+// is subject to the terms of the GNU General Public License, version 3.
+// The program is distributed without any warranty, expressed or implied.
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <crypt.h>
+#include <sys/utsname.h>
+#include <libcitadel.h>
+#include <assert.h>
+#include "config.h"
+#include "ctdl_module.h"
+
+long config_msgnum = 0;
+HashList *ctdlconfig = NULL;   // new configuration
+
+
+void config_warn_if_port_unset(char *key, int default_port) {
+       int p = CtdlGetConfigInt(key);
+       if ((p < -1) || (p == 0) || (p > UINT16_MAX)) {
+               syslog(LOG_ERR, "config: setting %s is not -1 (disabled) or a valid TCP port - setting to default %d", key, default_port);
+               CtdlSetConfigInt(key, default_port);
+       }
+}
+
+
+void config_warn_if_empty(char *key) {
+       if (IsEmptyStr(CtdlGetConfigStr(key))) {
+               syslog(LOG_ERR, "config: setting %s is empty, but must not - check your config!", key);
+       }
+}
+
+
+void validate_config(void) {
+
+       // these shouldn't be empty
+       config_warn_if_empty("c_fqdn");
+       config_warn_if_empty("c_baseroom");
+       config_warn_if_empty("c_aideroom");
+       config_warn_if_empty("c_twitroom");
+       config_warn_if_empty("c_nodename");
+
+       // Sanity check for port bindings
+       config_warn_if_port_unset("c_smtp_port",        25);
+       config_warn_if_port_unset("c_pop3_port",        110);
+       config_warn_if_port_unset("c_imap_port",        143);
+       config_warn_if_port_unset("c_msa_port",         587);
+       config_warn_if_port_unset("c_port_number",      504);
+       config_warn_if_port_unset("c_smtps_port",       465);
+       config_warn_if_port_unset("c_pop3s_port",       995);
+       config_warn_if_port_unset("c_imaps_port",       993);
+       config_warn_if_port_unset("c_pftcpdict_port",   -1);
+       config_warn_if_port_unset("c_xmpp_c2s_port",    5222);
+       config_warn_if_port_unset("c_xmpp_s2s_port",    5269);
+       config_warn_if_port_unset("c_nntp_port",        119);
+       config_warn_if_port_unset("c_nntps_port",       563);
+
+       if (getpwuid(ctdluid) == NULL) {
+               syslog(LOG_ERR, "config: uid (%d) does not exist ... citserver will run as root", ctdluid);
+       }
+}
+
+
+// The "host key" is a 100-byte random string that is used to do some persistent hashing.
+// It must remain consistent throughout the lifetime of this Citadel installation
+void generate_host_key(void) {
+       syslog(LOG_INFO, "config: generating a host key");
+       int len = 0;
+       char host_key[128];
+
+       while (len < 100) {
+               do {
+                       host_key[len] = random() % 0x7F;
+               } while (!isalnum(host_key[len]));
+               host_key[++len] = 0;
+       }
+       CtdlSetConfigStr("host_key", host_key);
+}
+
+
+// Put some sane default values into our configuration.  Some will be overridden when we run setup.
+void brand_new_installation_set_defaults(void) {
+
+       struct utsname my_utsname;
+       struct hostent *he;
+       char detected_hostname[256];
+
+       // Determine our host name, in case we need to use it as a default
+       uname(&my_utsname);
+
+       // set some sample/default values in place of blanks...
+       extract_token(detected_hostname, my_utsname.nodename, 0, '.', sizeof detected_hostname);
+       CtdlSetConfigStr("c_nodename", detected_hostname);
+
+       if ((he = gethostbyname(my_utsname.nodename)) != NULL) {
+               CtdlSetConfigStr("c_fqdn", he->h_name);
+       }
+       else {
+               CtdlSetConfigStr("c_fqdn", my_utsname.nodename);
+       }
+
+       CtdlSetConfigStr("c_humannode",         "Citadel Server");
+       CtdlSetConfigInt("c_initax",            4);
+       CtdlSetConfigStr("c_moreprompt",        "<more>");
+       CtdlSetConfigStr("c_twitroom",          "Trashcan");
+       CtdlSetConfigStr("c_baseroom",          BASEROOM);
+       CtdlSetConfigStr("c_aideroom",          "Aide");
+       CtdlSetConfigInt("c_sleeping",          900);
+
+       if (CtdlGetConfigInt("c_createax") == 0) {
+               CtdlSetConfigInt("c_createax", 3);
+       }
+
+       // Default port numbers for various services
+       CtdlSetConfigInt("c_port_number",       504);
+       CtdlSetConfigInt("c_smtp_port",         25);
+       CtdlSetConfigInt("c_pop3_port",         110);
+       CtdlSetConfigInt("c_imap_port",         143);
+       CtdlSetConfigInt("c_msa_port",          587);
+       CtdlSetConfigInt("c_smtps_port",        465);
+       CtdlSetConfigInt("c_pop3s_port",        995);
+       CtdlSetConfigInt("c_imaps_port",        993);
+       CtdlSetConfigInt("c_pftcpdict_port",    -1);
+       CtdlSetConfigInt("c_xmpp_c2s_port",     5222);
+       CtdlSetConfigInt("c_xmpp_s2s_port",     5269);
+       CtdlSetConfigInt("c_nntp_port",         119);
+       CtdlSetConfigInt("c_nntps_port",        563);
+
+       // Prevent the "new installation, set defaults" behavior from occurring again
+       CtdlSetConfigLong("c_config_created_or_migrated", (long)time(NULL));
+}
+
+
+// Migrate a supplied legacy configuration to the new in-db format.
+// No individual site should ever have to do this more than once.
+void migrate_legacy_config(struct legacy_config *lconfig) {
+       CtdlSetConfigStr(       "c_nodename"            ,       lconfig->c_nodename             );
+       CtdlSetConfigStr(       "c_fqdn"                ,       lconfig->c_fqdn                 );
+       CtdlSetConfigStr(       "c_humannode"           ,       lconfig->c_humannode            );
+       CtdlSetConfigInt(       "c_creataide"           ,       lconfig->c_creataide            );
+       CtdlSetConfigInt(       "c_sleeping"            ,       lconfig->c_sleeping             );
+       CtdlSetConfigInt(       "c_initax"              ,       lconfig->c_initax               );
+       CtdlSetConfigInt(       "c_regiscall"           ,       lconfig->c_regiscall            );
+       CtdlSetConfigInt(       "c_twitdetect"          ,       lconfig->c_twitdetect           );
+       CtdlSetConfigStr(       "c_twitroom"            ,       lconfig->c_twitroom             );
+       CtdlSetConfigStr(       "c_moreprompt"          ,       lconfig->c_moreprompt           );
+       CtdlSetConfigInt(       "c_restrict"            ,       lconfig->c_restrict             );
+       CtdlSetConfigStr(       "c_site_location"       ,       lconfig->c_site_location        );
+       CtdlSetConfigStr(       "c_sysadm"              ,       lconfig->c_sysadm               );
+       CtdlSetConfigInt(       "c_maxsessions"         ,       lconfig->c_maxsessions          );
+       CtdlSetConfigStr(       "c_ip_addr"             ,       lconfig->c_ip_addr              );
+       CtdlSetConfigInt(       "c_port_number"         ,       lconfig->c_port_number          );
+       CtdlSetConfigInt(       "c_ep_mode"             ,       lconfig->c_ep.expire_mode       );
+       CtdlSetConfigInt(       "c_ep_value"            ,       lconfig->c_ep.expire_value      );
+       CtdlSetConfigInt(       "c_userpurge"           ,       lconfig->c_userpurge            );
+       CtdlSetConfigInt(       "c_roompurge"           ,       lconfig->c_roompurge            );
+       CtdlSetConfigStr(       "c_logpages"            ,       lconfig->c_logpages             );
+       CtdlSetConfigInt(       "c_createax"            ,       lconfig->c_createax             );
+       CtdlSetConfigLong(      "c_maxmsglen"           ,       lconfig->c_maxmsglen            );
+       CtdlSetConfigInt(       "c_min_workers"         ,       lconfig->c_min_workers          );
+       CtdlSetConfigInt(       "c_max_workers"         ,       lconfig->c_max_workers          );
+       CtdlSetConfigInt(       "c_pop3_port"           ,       lconfig->c_pop3_port            );
+       CtdlSetConfigInt(       "c_smtp_port"           ,       lconfig->c_smtp_port            );
+       CtdlSetConfigInt(       "c_rfc822_strict_from"  ,       lconfig->c_rfc822_strict_from   );
+       CtdlSetConfigInt(       "c_aide_zap"            ,       lconfig->c_aide_zap             );
+       CtdlSetConfigInt(       "c_imap_port"           ,       lconfig->c_imap_port            );
+       CtdlSetConfigLong(      "c_net_freq"            ,       lconfig->c_net_freq             );
+       CtdlSetConfigInt(       "c_disable_newu"        ,       lconfig->c_disable_newu         );
+       CtdlSetConfigInt(       "c_enable_fulltext"     ,       lconfig->c_enable_fulltext      );
+       CtdlSetConfigStr(       "c_baseroom"            ,       lconfig->c_baseroom             );
+       CtdlSetConfigStr(       "c_aideroom"            ,       lconfig->c_aideroom             );
+       CtdlSetConfigInt(       "c_purge_hour"          ,       lconfig->c_purge_hour           );
+       CtdlSetConfigInt(       "c_mbxep_mode"          ,       lconfig->c_mbxep.expire_mode    );
+       CtdlSetConfigInt(       "c_mbxep_value"         ,       lconfig->c_mbxep.expire_value   );
+       CtdlSetConfigStr(       "c_ldap_host"           ,       lconfig->c_ldap_host            );
+       CtdlSetConfigInt(       "c_ldap_port"           ,       lconfig->c_ldap_port            );
+       CtdlSetConfigStr(       "c_ldap_base_dn"        ,       lconfig->c_ldap_base_dn         );
+       CtdlSetConfigStr(       "c_ldap_bind_dn"        ,       lconfig->c_ldap_bind_dn         );
+       CtdlSetConfigStr(       "c_ldap_bind_pw"        ,       lconfig->c_ldap_bind_pw         );
+       CtdlSetConfigInt(       "c_msa_port"            ,       lconfig->c_msa_port             );
+       CtdlSetConfigInt(       "c_imaps_port"          ,       lconfig->c_imaps_port           );
+       CtdlSetConfigInt(       "c_pop3s_port"          ,       lconfig->c_pop3s_port           );
+       CtdlSetConfigInt(       "c_smtps_port"          ,       lconfig->c_smtps_port           );
+       CtdlSetConfigInt(       "c_auto_cull"           ,       lconfig->c_auto_cull            );
+       CtdlSetConfigInt(       "c_allow_spoofing"      ,       lconfig->c_allow_spoofing       );
+       CtdlSetConfigInt(       "c_journal_email"       ,       lconfig->c_journal_email        );
+       CtdlSetConfigInt(       "c_journal_pubmsgs"     ,       lconfig->c_journal_pubmsgs      );
+       CtdlSetConfigStr(       "c_journal_dest"        ,       lconfig->c_journal_dest         );
+       CtdlSetConfigStr(       "c_default_cal_zone"    ,       lconfig->c_default_cal_zone     );
+       CtdlSetConfigInt(       "c_pftcpdict_port"      ,       lconfig->c_pftcpdict_port       );
+       CtdlSetConfigInt(       "c_auth_mode"           ,       lconfig->c_auth_mode            );
+       CtdlSetConfigInt(       "c_rbl_at_greeting"     ,       lconfig->c_rbl_at_greeting      );
+       CtdlSetConfigStr(       "c_pager_program"       ,       lconfig->c_pager_program        );
+       CtdlSetConfigInt(       "c_imap_keep_from"      ,       lconfig->c_imap_keep_from       );
+       CtdlSetConfigInt(       "c_xmpp_c2s_port"       ,       lconfig->c_xmpp_c2s_port        );
+       CtdlSetConfigInt(       "c_xmpp_s2s_port"       ,       lconfig->c_xmpp_s2s_port        );
+       CtdlSetConfigLong(      "c_pop3_fetch"          ,       lconfig->c_pop3_fetch           );
+       CtdlSetConfigLong(      "c_pop3_fastest"        ,       lconfig->c_pop3_fastest         );
+       CtdlSetConfigInt(       "c_spam_flag_only"      ,       lconfig->c_spam_flag_only       );
+       CtdlSetConfigInt(       "c_guest_logins"        ,       lconfig->c_guest_logins         );
+       CtdlSetConfigInt(       "c_nntp_port"           ,       lconfig->c_nntp_port            );
+       CtdlSetConfigInt(       "c_nntps_port"          ,       lconfig->c_nntps_port           );
+}
+
+
+// Called during the initialization of Citadel server.
+// It verifies the system's integrity and reads citadel.config into memory.
+void initialize_config_system(void) {
+       FILE *cfp;
+       int rv;
+       struct legacy_config lconfig;   // legacy configuration
+       ctdlconfig = NewHash(1, NULL);  // set up the real config system
+
+       // Ensure that we are linked to the correct version of libcitadel
+       if (libcitadel_version_number() < LIBCITADEL_VERSION_NUMBER) {
+               fprintf(stderr, "You are running libcitadel version %d\n", libcitadel_version_number());
+               fprintf(stderr, "citserver was compiled against version %d\n", LIBCITADEL_VERSION_NUMBER);
+               exit(CTDLEXIT_LIBCITADEL);
+       }
+
+       memset(&lconfig, 0, sizeof(struct legacy_config));
+       cfp = fopen(file_citadel_config, "rb");
+       if (cfp != NULL) {
+               if (CtdlGetConfigLong("c_config_created_or_migrated") > 0) {
+                       fprintf(stderr, "Citadel Server found BOTH legacy and new configurations present.\n");
+                       fprintf(stderr, "Exiting to prevent data corruption.\n");
+                       exit(CTDLEXIT_CONFIG);
+               }
+               rv = fread((char *) &lconfig, sizeof(struct legacy_config), 1, cfp);
+               if (rv != 1) {
+                       fprintf(stderr, 
+                               "Warning: Found a legacy config file %s has unexpected size. \n",
+                               file_citadel_config
+                       );
+               }
+
+               migrate_legacy_config(&lconfig);
+
+               fclose(cfp);
+               if (unlink(file_citadel_config) != 0) {
+                       fprintf(stderr, "Unable to remove legacy config file %s after migrating it.\n", file_citadel_config);
+                       fprintf(stderr, "Exiting to prevent data corruption.\n");
+                       exit(CTDLEXIT_CONFIG);
+               }
+
+               // Prevent migration/initialization from happening again.
+               CtdlSetConfigLong("c_config_created_or_migrated", (long)time(NULL));
+
+       }
+
+       // New installation?  Set up configuration
+       if (CtdlGetConfigLong("c_config_created_or_migrated") <= 0) {
+               brand_new_installation_set_defaults();
+       }
+
+        // Default maximum message length is 10 megabytes.  This is site
+       // configurable.  Also check to make sure the limit has not been
+       // set below 8192 bytes.
+        if (CtdlGetConfigLong("c_maxmsglen") <= 0)     CtdlSetConfigLong("c_maxmsglen", 10485760);
+        if (CtdlGetConfigLong("c_maxmsglen") < 8192)   CtdlSetConfigLong("c_maxmsglen", 8192);
+
+       // Default lower and upper limits on number of worker threads
+       if (CtdlGetConfigInt("c_min_workers") < 5)      CtdlSetConfigInt("c_min_workers", 5);   // min
+       if (CtdlGetConfigInt("c_max_workers") == 0)     CtdlSetConfigInt("c_max_workers", 256); // default max
+       if (CtdlGetConfigInt("c_max_workers") < CtdlGetConfigInt("c_min_workers")) {
+               CtdlSetConfigInt("c_max_workers", CtdlGetConfigInt("c_min_workers"));           // max >= min
+       }
+
+       // Networking more than once every five minutes just isn't sane
+       if (CtdlGetConfigLong("c_net_freq") == 0)       CtdlSetConfigLong("c_net_freq", 3600);  // once per hour default
+       if (CtdlGetConfigLong("c_net_freq") < 300)      CtdlSetConfigLong("c_net_freq", 300);   // minimum 5 minutes
+
+       // Same goes for POP3
+       if (CtdlGetConfigLong("c_pop3_fetch") == 0)     CtdlSetConfigLong("c_pop3_fetch", 3600);        // once per hour default
+       if (CtdlGetConfigLong("c_pop3_fetch") < 300)    CtdlSetConfigLong("c_pop3_fetch", 300);         // 5 minutes min
+       if (CtdlGetConfigLong("c_pop3_fastest") == 0)   CtdlSetConfigLong("c_pop3_fastest", 3600);      // once per hour default
+       if (CtdlGetConfigLong("c_pop3_fastest") < 300)  CtdlSetConfigLong("c_pop3_fastest", 300);       // 5 minutes min
+
+       // LDAP sync frequency
+       if (CtdlGetConfigLong("c_ldap_sync_freq") == 0) CtdlSetConfigLong("c_ldap_sync_freq", 300);     // every 5 minutes default
+
+       // "create new user" only works with native authentication mode
+       if (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) {
+               CtdlSetConfigInt("c_disable_newu", 1);
+       }
+
+       // If we do not have a host key, generate one now, and save it.
+       if (IsEmptyStr(CtdlGetConfigStr("host_key"))) {
+               generate_host_key();
+       }
+}
+
+
+// Called when Citadel server is shutting down.
+// Clears out the config hash table.
+void shutdown_config_system(void) {
+       DeleteHash(&ctdlconfig);
+}
+
+
+// Set a system config value.  Simple key/value here.
+void CtdlSetConfigStr(char *key, char *value) {
+       int key_len = strlen(key);
+       int value_len = strlen(value);
+
+       // Save it in memory
+       Put(ctdlconfig, key, key_len, strdup(value), NULL);
+
+       // Also write it to the config database
+
+       int dbv_size = key_len + value_len + 2;
+       char *dbv = malloc(dbv_size);
+       strcpy(dbv, key);
+       strcpy(&dbv[key_len + 1], value);
+       cdb_store(CDB_CONFIG, key, key_len, dbv, dbv_size);
+       free(dbv);
+}
+
+
+// Set a numeric system config value (long integer)
+void CtdlSetConfigLong(char *key, long value) {
+       char longstr[256];
+       sprintf(longstr, "%ld", value);
+       CtdlSetConfigStr(key, longstr);
+}
+
+
+// Set a numeric system config value (integer)
+void CtdlSetConfigInt(char *key, int value) {
+       char intstr[256];
+       sprintf(intstr, "%d", value);
+       CtdlSetConfigStr(key, intstr);
+}
+
+
+// Delete a system config value.
+void CtdlDelConfig(char *key) {
+       int key_len = strlen(key);
+
+       if (IsEmptyStr(key)) return;
+
+       // Delete from the database.
+       cdb_delete(CDB_CONFIG, key, key_len);
+
+       // Delete from the in-memory cache
+       HashPos *Pos = GetNewHashPos(ctdlconfig, 1);
+       if (GetHashPosFromKey(ctdlconfig, key, key_len, Pos)) {
+               DeleteEntryFromHash(ctdlconfig, Pos);
+       }
+       DeleteHashPos(&Pos);
+
+       assert(Pos == NULL);    // no memory leaks allowed
+}
+
+
+// Fetch a system config value.  Caller does *not* own the returned value and may not alter it.
+char *CtdlGetConfigStr(char *key) {
+       char *value = NULL;
+       struct cdbdata *cdb;
+       int key_len = strlen(key);
+
+       if (IsEmptyStr(key)) return(NULL);
+
+       // First look in memory
+       if (GetHash(ctdlconfig, key, key_len, (void *)&value)) {
+               return value;
+       }
+
+       // Then look in the database.
+       cdb = cdb_fetch(CDB_CONFIG, key, key_len);
+       if (cdb == NULL) {      // nope, not there either.
+               return(NULL);
+       }
+
+       // Got it.  Save it in memory for the next fetch.
+       value = strdup(cdb->ptr + key_len + 1);         // The key was stored there too; skip past it
+       cdb_free(cdb);
+       Put(ctdlconfig, key, key_len, value, NULL);
+       return value;
+}
+
+
+// Fetch a system config value - integer
+int CtdlGetConfigInt(char *key) {
+       char *s = CtdlGetConfigStr(key);
+       if (s) return atoi(s);
+       return 0;
+}
+
+
+// Fetch a system config value - long integer
+long CtdlGetConfigLong(char *key) {
+       char *s = CtdlGetConfigStr(key);
+       if (s) return atol(s);
+       return 0;
+}
+
+
+void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
+       config_msgnum = msgnum;
+}
+
+
+// This is for fetching longer configuration sets which are stored in the message base.
+char *CtdlGetSysConfig(char *sysconfname) {
+       char hold_rm[ROOMNAMELEN];
+       long msgnum = -1;
+       char *conf;
+       struct CtdlMessage *msg;
+       char buf[SIZ];
+       
+       strcpy(hold_rm, CC->room.QRname);
+       if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
+               CtdlGetRoom(&CC->room, hold_rm);
+               return NULL;
+       }
+
+       // The new way: hunt for the message number in the config database
+       msgnum = CtdlGetConfigLong(sysconfname);
+
+       // Legacy format: hunt through the local system configuration room for a message with a matching MIME type
+       if (msgnum <= 0) {
+               begin_critical_section(S_CONFIG);
+               config_msgnum = -1;
+               CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL, CtdlGetSysConfigBackend, NULL);
+               msgnum = config_msgnum;
+               end_critical_section(S_CONFIG);
+               if (msgnum > 0) {
+                       CtdlSetConfigLong(sysconfname, msgnum);         // store it the new way so we don't have to do this again
+               }
+       }
+
+       if (msgnum <= 0) {
+               conf = NULL;
+       }
+       else {
+               msg = CtdlFetchMessage(msgnum, 1);
+               if (msg != NULL) {
+                       conf = strdup(msg->cm_fields[eMesageText]);
+                       CM_Free(msg);
+               }
+               else {
+                       conf = NULL;
+               }
+       }
+
+       CtdlGetRoom(&CC->room, hold_rm);
+
+       if (conf != NULL) {             // Strip the MIME headers, leaving only the content
+               do {
+                       extract_token(buf, conf, 0, '\n', sizeof buf);
+                       strcpy(conf, &conf[strlen(buf)+1]);
+               } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
+       }
+
+       return(conf);
+}
+
+
+// This is for storing longer configuration sets which are stored in the message base.
+void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
+       long old_msgnum = -1;
+       long new_msgnum = -1;
+
+       // Search for the previous copy of this config item, so we can delete it
+       old_msgnum = CtdlGetConfigLong(sysconfname);
+
+       // Go ahead and save it, and write the new msgnum to the config database so we can find it again
+       new_msgnum = CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 0);
+       if (new_msgnum > 0) {
+               CtdlSetConfigLong(sysconfname, new_msgnum);
+
+               // Now delete the old copy
+               if (old_msgnum > 0) {
+                       CtdlDeleteMessages(SYSCONFIGROOM, &old_msgnum, 1, "");
+               }
+       }
+}
diff --git a/citadel/server/config.h b/citadel/server/config.h
new file mode 100644 (file)
index 0000000..23f144a
--- /dev/null
@@ -0,0 +1,113 @@
+// Copyright (c) 1987-2016 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "serv_extensions.h"
+#include "citadel_dirs.h"
+
+// This is the format of the legacy config file.  Do not attempt to do anything with it other
+// than migrate it into the new format.  Seriously -- DO NOT CHANGE IT.  The only purpose of this
+// struct is to represent the OLD configuration format.
+struct legacy_config {
+       char c_nodename[16];            // short name of this node on a Citadel network
+       char c_fqdn[64];                // this site's fully qualified domain name
+       char c_humannode[21];           // human-readable site name
+       char c_niu_7[16];
+       uid_t c_niu_6;
+       char c_creataide;               // 1 = creating a room auto-grants room aide privileges
+       int c_sleeping;                 // watchdog timer (seconds)
+       char c_initax;                  // initial access level for new users
+       char c_regiscall;               // after c_regiscall logins user will be asked to register
+       char c_twitdetect;              // automatically move messages from problem users to trashcan
+       char c_twitroom[ROOMNAMELEN];   // name of trashcan
+       char c_moreprompt[80];          // paginator prompt
+       char c_restrict;                // require per-user permission to send Internet mail
+       long c_niu_1;
+       char c_site_location[32];       // geographic location of this Citadel site
+       char c_sysadm[26];              // name of system administrator
+       char c_niu_2[15];
+       int c_niu_3;
+       int c_maxsessions;              // maximum number of concurrent sessions allowed
+       char c_ip_addr[20];             // bind address for listening sockets
+       int c_port_number;              // port number for Citadel protocol (usually 504)
+       int c_niu_4;
+       struct ExpirePolicy c_ep;       // default expire policy for the entire site
+       int c_userpurge;                // user purge time (in days)
+       int c_roompurge;                // room purge time (in days)
+       char c_logpages[ROOMNAMELEN];
+       char c_createax;
+       long c_maxmsglen;
+       int c_min_workers;
+       int c_max_workers;
+       int c_pop3_port;
+       int c_smtp_port;
+       int c_rfc822_strict_from;
+       int c_aide_zap;
+       int c_imap_port;
+       time_t c_net_freq;
+       char c_disable_newu;
+       char c_enable_fulltext;
+       char c_baseroom[ROOMNAMELEN];
+       char c_aideroom[ROOMNAMELEN];
+       int c_purge_hour;
+       struct ExpirePolicy c_mbxep;
+       char c_ldap_host[128];
+       int c_ldap_port;
+       char c_ldap_base_dn[256];
+       char c_ldap_bind_dn[256];
+       char c_ldap_bind_pw[256];
+       int c_msa_port;
+       int c_imaps_port;
+       int c_pop3s_port;
+       int c_smtps_port;
+       char c_auto_cull;
+       char c_niu_5;
+       char c_allow_spoofing;
+       char c_journal_email;
+       char c_journal_pubmsgs;
+       char c_journal_dest[128];
+       char c_default_cal_zone[128];
+       int c_pftcpdict_port;
+       int c_niu_9;
+       int c_auth_mode;
+       char c_niu_8[256];
+       int c_niu_10;
+       char c_niu_11[256];
+       char c_niu_12[256];
+       char c_rbl_at_greeting;
+       char c_master_user[32];
+       char c_master_pass[32];
+       char c_pager_program[256];
+       char c_imap_keep_from;
+       int c_xmpp_c2s_port;
+       int c_xmpp_s2s_port;
+       time_t c_pop3_fetch;
+       time_t c_pop3_fastest;
+       int c_spam_flag_only;
+       int c_guest_logins;
+       int c_nntp_port;
+       int c_nntps_port;
+};
+
+
+void initialize_config_system(void);
+void shutdown_config_system(void);
+void put_config(void);
+void CtdlSetConfigStr(char *, char *);
+char *CtdlGetConfigStr(char *);
+int CtdlGetConfigInt(char *);
+long CtdlGetConfigLong(char *);
+void CtdlSetConfigInt(char *key, int value);
+void CtdlSetConfigLong(char *key, long value);
+void CtdlDelConfig(char *key);
+
+char *CtdlGetSysConfig(char *sysconfname);
+void CtdlPutSysConfig(char *sysconfname, char *sysconfdata);
+void validate_config(void);
+void netcfg_keyname(char *, long);
diff --git a/citadel/server/context.c b/citadel/server/context.c
new file mode 100644 (file)
index 0000000..bca317d
--- /dev/null
@@ -0,0 +1,686 @@
+//
+// Citadel context management stuff.
+// Here's where we (hopefully) have all the code that manipulates contexts.
+//
+// Copyright (c) 1987-2020 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or disclosure
+// is subject to the terms of the GNU General Public License, version 3.
+// The program is distributed without any warranty, expressed or implied.
+
+#include "ctdl_module.h"
+#include "serv_extensions.h"
+#include "citserver.h"
+#include "user_ops.h"
+#include "locate_host.h"
+#include "context.h"
+#include "control.h"
+#include "config.h"
+
+pthread_key_t MyConKey;                                /* TSD key for MyContext() */
+CitContext masterCC;
+CitContext *ContextList = NULL;
+time_t last_purge = 0;                         /* Last dead session purge */
+int num_sessions = 0;                          /* Current number of sessions */
+int next_pid = 0;
+
+/* Flag for single user mode */
+static int want_single_user = 0;
+
+/* Try to go single user */
+
+int CtdlTrySingleUser(void) {
+       int can_do = 0;
+       
+       begin_critical_section(S_SINGLE_USER);
+       if (want_single_user)
+               can_do = 0;
+       else
+       {
+               can_do = 1;
+               want_single_user = 1;
+       }
+       end_critical_section(S_SINGLE_USER);
+       return can_do;
+}
+
+
+void CtdlEndSingleUser(void) {
+       begin_critical_section(S_SINGLE_USER);
+       want_single_user = 0;
+       end_critical_section(S_SINGLE_USER);
+}
+
+
+int CtdlWantSingleUser(void) {
+       return want_single_user;
+}
+
+
+int CtdlIsSingleUser(void) {
+       if (want_single_user) {
+               /* check for only one context here */
+               if (num_sessions == 1)
+                       return 1;
+       }
+       return 0;
+}
+
+
+/*
+ * Locate a context by its session number and terminate it if the user is able.
+ * User can NOT terminate their current session.
+ * User CAN terminate any other session that has them logged in.
+ * Aide CAN terminate any session except the current one.
+ */
+int CtdlTerminateOtherSession (int session_num) {
+       int ret = 0;
+       CitContext *ccptr;
+       int aide;
+
+       if (session_num == CC->cs_pid) return TERM_NOTALLOWED;
+
+       aide = ( (CC->user.axlevel >= AxAideU) || (CC->internal_pgm) ) ;
+
+       syslog(LOG_DEBUG, "context: locating session to kill");
+       begin_critical_section(S_SESSION_TABLE);
+       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+               if (session_num == ccptr->cs_pid) {
+                       ret |= TERM_FOUND;
+                       if ((ccptr->user.usernum == CC->user.usernum) || aide) {
+                               ret |= TERM_ALLOWED;
+                       }
+                       break;
+               }
+       }
+
+       if (((ret & TERM_FOUND) != 0) && ((ret & TERM_ALLOWED) != 0))
+       {
+               if (ccptr->user.usernum == CC->user.usernum)
+                       ccptr->kill_me = KILLME_ADMIN_TERMINATE;
+               else
+                       ccptr->kill_me = KILLME_IDLE;
+               end_critical_section(S_SESSION_TABLE);
+       }
+       else
+               end_critical_section(S_SESSION_TABLE);
+
+       return ret;
+}
+
+
+/*
+ * Check to see if the user who we just sent mail to is logged in.  If yes,
+ * bump the 'new mail' counter for their session.  That enables them to
+ * receive a new mail notification without having to hit the database.
+ */
+void BumpNewMailCounter(long which_user) {
+       CtdlBumpNewMailCounter(which_user);
+}
+
+
+void CtdlBumpNewMailCounter(long which_user) {
+       CitContext *ptr;
+
+       begin_critical_section(S_SESSION_TABLE);
+
+       for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
+               if (ptr->user.usernum == which_user) {
+                       ptr->newmail += 1;
+               }
+       }
+
+       end_critical_section(S_SESSION_TABLE);
+}
+
+
+/*
+ * Check to see if a user is currently logged in
+ * Take care with what you do as a result of this test.
+ * The user may not have been logged in when this function was called BUT
+ * because of threading the user might be logged in before you test the result.
+ */
+int CtdlIsUserLoggedIn(char *user_name) {
+       CitContext *cptr;
+       int ret = 0;
+
+       begin_critical_section (S_SESSION_TABLE);
+       for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
+               if (!strcasecmp(cptr->user.fullname, user_name)) {
+                       ret = 1;
+                       break;
+               }
+       }
+       end_critical_section(S_SESSION_TABLE);
+       return ret;
+}
+
+
+/*
+ * Check to see if a user is currently logged in.
+ * Basically same as CtdlIsUserLoggedIn() but uses the user number instead.
+ * Take care with what you do as a result of this test.
+ * The user may not have been logged in when this function was called BUT
+ * because of threading the user might be logged in before you test the result.
+ */
+int CtdlIsUserLoggedInByNum (long usernum) {
+       CitContext *cptr;
+       int ret = 0;
+
+       begin_critical_section(S_SESSION_TABLE);
+       for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
+               if (cptr->user.usernum == usernum) {
+                       ret = 1;
+               }
+       }
+       end_critical_section(S_SESSION_TABLE);
+       return ret;
+}
+
+
+/*
+ * Return a pointer to the CitContext structure bound to the thread which
+ * called this function.  If there's no such binding (for example, if it's
+ * called by the housekeeper thread) then a generic 'master' CC is returned.
+ *
+ * This function is used *VERY* frequently and must be kept small.
+ */
+CitContext *MyContext(void) {
+       register CitContext *c;
+       return ((c = (CitContext *) pthread_getspecific(MyConKey), c == NULL) ? &masterCC : c);
+}
+
+
+/*
+ * Terminate idle sessions.  This function pounds through the session table
+ * comparing the current time to each session's time-of-last-command.  If an
+ * idle session is found it is terminated, then the search restarts at the
+ * beginning because the pointer to our place in the list becomes invalid.
+ */
+void terminate_idle_sessions(void) {
+       CitContext *ccptr;
+       time_t now;
+       int killed = 0;
+       int longrunners = 0;
+
+       now = time(NULL);
+       begin_critical_section(S_SESSION_TABLE);
+       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+               if (
+                       (ccptr != CC)
+                       && (CtdlGetConfigLong("c_sleeping") > 0)
+                       && (now - (ccptr->lastcmd) > CtdlGetConfigLong("c_sleeping"))
+               ) {
+                       if (!ccptr->dont_term) {
+                               ccptr->kill_me = KILLME_IDLE;
+                               ++killed;
+                       }
+                       else {
+                               ++longrunners;
+                       }
+               }
+       }
+       end_critical_section(S_SESSION_TABLE);
+       if (killed > 0) {
+               syslog(LOG_INFO, "context: scheduled %d idle sessions for termination", killed);
+       }
+       if (longrunners > 0) {
+               syslog(LOG_INFO, "context: did not terminate %d protected idle sessions", longrunners);
+       }
+}
+
+
+/*
+ * During shutdown, close the sockets of any sessions still connected.
+ */
+void terminate_all_sessions(void) {
+       CitContext *ccptr;
+       int killed = 0;
+
+       begin_critical_section(S_SESSION_TABLE);
+       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+               if (ccptr->client_socket != -1)
+               {
+                       syslog(LOG_INFO, "context: terminate_all_sessions() is murdering %s CC[%d]", ccptr->curr_user, ccptr->cs_pid);
+                       close(ccptr->client_socket);
+                       ccptr->client_socket = -1;
+                       killed++;
+               }
+       }
+       end_critical_section(S_SESSION_TABLE);
+       if (killed > 0) {
+               syslog(LOG_INFO, "context: flushed %d stuck sessions", killed);
+       }
+}
+
+
+/*
+ * Terminate a session.
+ */
+void RemoveContext (CitContext *con) {
+       const char *c;
+       if (con == NULL) {
+               syslog(LOG_ERR, "context: RemoveContext() called with NULL, this should not happen");
+               return;
+       }
+       c = con->ServiceName;
+       if (c == NULL) {
+               c = "WTF?";
+       }
+       syslog(LOG_DEBUG, "context: RemoveContext(%s) session %d", c, con->cs_pid);
+
+       /* Run any cleanup routines registered by loadable modules.
+        * Note: We have to "become_session()" because the cleanup functions
+        *       might make references to "CC" assuming it's the right one.
+        */
+       become_session(con);
+       CtdlUserLogout();
+       PerformSessionHooks(EVT_STOP);
+       client_close();                         /* If the client is still connected, blow 'em away. */
+       become_session(NULL);
+
+       syslog(LOG_INFO, "context: [%3d]SRV[%s] Session ended.", con->cs_pid, c);
+
+       /* 
+        * If the client is still connected, blow 'em away. 
+        * if the socket is 0 or -1, its already gone or was never there.
+        */
+       if (con->client_socket > 0) {
+               syslog(LOG_INFO, "context: closing socket %d", con->client_socket);
+               close(con->client_socket);
+       }
+
+       /* If using AUTHMODE_LDAP, free the DN */
+       if (con->ldap_dn) {
+               free(con->ldap_dn);
+               con->ldap_dn = NULL;
+       }
+       FreeStrBuf(&con->StatusMessage);
+       FreeStrBuf(&con->MigrateBuf);
+       FreeStrBuf(&con->RecvBuf.Buf);
+       if (con->cached_msglist) {
+               free(con->cached_msglist);
+       }
+
+       syslog(LOG_DEBUG, "context: done with RemoveContext()");
+}
+
+
+/*
+ * Initialize a new context and place it in the list.  The session number
+ * used to be the PID (which is why it's called cs_pid), but that was when we
+ * had one process per session.  Now we just assign them sequentially, starting
+ * at 1 (don't change it to 0 because masterCC uses 0).
+ */
+CitContext *CreateNewContext(void) {
+       CitContext *me;
+
+       me = (CitContext *) malloc(sizeof(CitContext));
+       if (me == NULL) {
+               syslog(LOG_ERR, "citserver: malloc() failed: %m");
+               return NULL;
+       }
+       memset(me, 0, sizeof(CitContext));
+
+       /* Give the context a name. Hopefully makes it easier to track */
+       strcpy (me->user.fullname, "SYS_notauth");
+       
+       /* The new context will be created already in the CON_EXECUTING state
+        * in order to prevent another thread from grabbing it while it's
+        * being set up.
+        */
+       me->state = CON_EXECUTING;
+       /*
+        * Generate a unique session number and insert this context into
+        * the list.
+        */
+       me->MigrateBuf = NewStrBuf();
+
+       me->RecvBuf.Buf = NewStrBuf();
+
+       me->lastcmd = time(NULL);       /* set lastcmd to now to prevent idle timer infanticide TODO: if we have a valid IO, use that to set the timer. */
+
+
+       begin_critical_section(S_SESSION_TABLE);
+       me->cs_pid = ++next_pid;
+       me->prev = NULL;
+       me->next = ContextList;
+       ContextList = me;
+       if (me->next != NULL) {
+               me->next->prev = me;
+       }
+       ++num_sessions;
+       end_critical_section(S_SESSION_TABLE);
+       return (me);
+}
+
+
+/*
+ * Initialize a new context and place it in the list.  The session number
+ * used to be the PID (which is why it's called cs_pid), but that was when we
+ * had one process per session.  Now we just assign them sequentially, starting
+ * at 1 (don't change it to 0 because masterCC uses 0).
+ */
+CitContext *CloneContext(CitContext *CloneMe) {
+       CitContext *me;
+
+       me = (CitContext *) malloc(sizeof(CitContext));
+       if (me == NULL) {
+               syslog(LOG_ERR, "citserver: malloc() failed: %m");
+               return NULL;
+       }
+       memcpy(me, CloneMe, sizeof(CitContext));
+
+       memset(&me->RecvBuf, 0, sizeof(IOBuffer));
+       memset(&me->SendBuf, 0, sizeof(IOBuffer));
+       memset(&me->SBuf, 0, sizeof(IOBuffer));
+       me->MigrateBuf = NULL;
+       me->sMigrateBuf = NULL;
+       me->redirect_buffer = NULL;
+#ifdef HAVE_OPENSSL
+       me->ssl = NULL;
+#endif
+
+       me->download_fp = NULL;
+       me->upload_fp = NULL;
+       /// TODO: what about the room/user?
+       me->ma = NULL;
+       me->openid_data = NULL;
+       me->ldap_dn = NULL;
+       me->session_specific_data = NULL;
+       
+       me->CIT_ICAL = NULL;
+
+       me->cached_msglist = NULL;
+       me->download_fp = NULL;
+       me->upload_fp = NULL;
+       me->client_socket = 0;
+
+       me->MigrateBuf = NewStrBuf();
+       me->RecvBuf.Buf = NewStrBuf();
+       
+       begin_critical_section(S_SESSION_TABLE);
+       {
+               me->cs_pid = ++next_pid;
+               me->prev = NULL;
+               me->next = ContextList;
+               me->lastcmd = time(NULL);       /* set lastcmd to now to prevent idle timer infanticide */
+               ContextList = me;
+               if (me->next != NULL) {
+                       me->next->prev = me;
+               }
+               ++num_sessions;
+       }
+       end_critical_section(S_SESSION_TABLE);
+       return (me);
+}
+
+
+/*
+ * Return an array containing a copy of the context list.
+ * This allows worker threads to perform "for each context" operations without
+ * having to lock and traverse the live list.
+ */
+CitContext *CtdlGetContextArray(int *count) {
+       int nContexts, i;
+       CitContext *nptr, *cptr;
+       
+       nContexts = num_sessions;
+       nptr = malloc(sizeof(CitContext) * nContexts);
+       if (!nptr) {
+               *count = 0;
+               return NULL;
+       }
+       begin_critical_section(S_SESSION_TABLE);
+       for (cptr = ContextList, i=0; cptr != NULL && i < nContexts; cptr = cptr->next, i++) {
+               memcpy(&nptr[i], cptr, sizeof (CitContext));
+       }
+       end_critical_section (S_SESSION_TABLE);
+       
+       *count = i;
+       return nptr;
+}
+
+
+/*
+ * Back-end function for starting a session
+ */
+void begin_session(CitContext *con) {
+       /* 
+        * Initialize some variables specific to our context.
+        */
+       con->logged_in = 0;
+       con->internal_pgm = 0;
+       con->download_fp = NULL;
+       con->upload_fp = NULL;
+       con->cached_msglist = NULL;
+       con->cached_num_msgs = 0;
+       con->FirstExpressMessage = NULL;
+       time(&con->lastcmd);
+       time(&con->lastidle);
+       strcpy(con->lastcmdname, "    ");
+       strcpy(con->cs_clientname, "(unknown)");
+       strcpy(con->curr_user, NLI);
+       *con->cs_clientinfo = '\0';
+       safestrncpy(con->cs_host, CtdlGetConfigStr("c_fqdn"), sizeof con->cs_host);
+       safestrncpy(con->cs_addr, "", sizeof con->cs_addr);
+       con->cs_UDSclientUID = -1;
+       con->cs_host[sizeof con->cs_host - 1] = 0;
+       if (!CC->is_local_client) {
+               locate_host(con->cs_host, sizeof con->cs_host,
+                       con->cs_addr, sizeof con->cs_addr,
+                       con->client_socket
+               );
+       }
+       else {
+               con->cs_host[0] = 0;
+               con->cs_addr[0] = 0;
+#ifdef HAVE_STRUCT_UCRED
+               {
+                       /* as http://www.wsinnovations.com/softeng/articles/uds.html told us... */
+                       struct ucred credentials;
+                       socklen_t ucred_length = sizeof(struct ucred);
+                       
+                       /*fill in the user data structure */
+                       if(getsockopt(con->client_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &ucred_length)) {
+                               syslog(LOG_ERR, "context: could obtain credentials from unix domain socket");
+                               
+                       }
+                       else {          
+                               /* the process ID of the process on the other side of the socket */
+                               /* credentials.pid; */
+                               
+                               /* the effective UID of the process on the other side of the socket  */
+                               con->cs_UDSclientUID = credentials.uid;
+                               
+                               /* the effective primary GID of the process on the other side of the socket */
+                               /* credentials.gid; */
+                               
+                               /* To get supplemental groups, we will have to look them up in our account
+                                  database, after a reverse lookup on the UID to get the account name.
+                                  We can take this opportunity to check to see if this is a legit account.
+                               */
+                               snprintf(con->cs_clientinfo, sizeof(con->cs_clientinfo),
+                                        "PID: "F_PID_T"; UID: "F_UID_T"; GID: "F_XPID_T" ", 
+                                        credentials.pid,
+                                        credentials.uid,
+                                        credentials.gid);
+                       }
+               }
+#endif
+       }
+       con->cs_flags = 0;
+
+       con->nologin = 0;
+       if (((CtdlGetConfigInt("c_maxsessions") > 0)&&(num_sessions > CtdlGetConfigInt("c_maxsessions"))) || CtdlWantSingleUser()) {
+               con->nologin = 1;
+       }
+
+       syslog(LOG_INFO, "context: session (%s) started from %s (%s) uid=%d",
+               con->ServiceName, con->cs_host, con->cs_addr, con->cs_UDSclientUID
+       );
+
+       /* Run any session startup routines registered by loadable modules */
+       PerformSessionHooks(EVT_START);
+}
+
+
+/*
+ * This function fills in a context and its user field correctly
+ * Then creates/loads that user
+ */
+void CtdlFillSystemContext(CitContext *context, char *name) {
+       char sysname[SIZ];
+       long len;
+
+       memset(context, 0, sizeof(CitContext));
+       context->internal_pgm = 1;
+       context->cs_pid = 0;
+       strcpy (sysname, "SYS_");
+       strcat (sysname, name);
+       len = strlen(sysname);
+       memcpy(context->curr_user, sysname, len + 1);
+       context->client_socket = (-1);
+       context->state = CON_SYS;
+       context->ServiceName = name;
+
+       /* internal_create_user has the side effect of loading the user regardless of whether they
+        * already existed or needed to be created
+        */
+       internal_create_user(sysname, &(context->user), -1) ;
+       
+       /* Check to see if the system user needs upgrading */
+       if (context->user.usernum == 0) {       /* old system user with number 0, upgrade it */
+               context->user.usernum = get_new_user_number();
+               syslog(LOG_INFO, "context: upgrading system user \"%s\" from user number 0 to user number %ld", context->user.fullname, context->user.usernum);
+               /* add user to the database */
+               CtdlPutUser(&(context->user));
+               cdb_store(CDB_USERSBYNUMBER, &(context->user.usernum), sizeof(long), context->user.fullname, strlen(context->user.fullname)+1);
+       }
+}
+
+
+/*
+ * Cleanup any contexts that are left lying around
+ */
+void context_cleanup(void) {
+       CitContext *ptr = NULL;
+       CitContext *rem = NULL;
+
+       /*
+        * Clean up the contexts.
+        * There are no threads so no critical_section stuff is needed.
+        */
+       ptr = ContextList;
+       
+       /* We need to update the ContextList because some modules may want to itterate it
+        * Question is should we NULL it before iterating here or should we just keep updating it
+        * as we remove items?
+        *
+        * Answer is to NULL it first to prevent modules from doing any actions on the list at all
+        */
+       ContextList=NULL;
+       while (ptr != NULL){
+               /* Remove the session from the active list */
+               rem = ptr->next;
+               --num_sessions;
+
+               syslog(LOG_DEBUG, "context: context_cleanup() purging session %d", ptr->cs_pid);
+               RemoveContext(ptr);
+               free (ptr);
+               ptr = rem;
+       }
+}
+
+
+/*
+ * Purge all sessions which have the 'kill_me' flag set.
+ * This function has code to prevent it from running more than once every
+ * few seconds, because running it after every single unbind would waste a lot
+ * of CPU time and keep the context list locked too much.  To force it to run
+ * anyway, set "force" to nonzero.
+ */
+void dead_session_purge(int force) {
+       CitContext *ptr, *ptr2;         /* general-purpose utility pointer */
+       CitContext *rem = NULL;         /* list of sessions to be destroyed */
+       
+       if (force == 0) {
+               if ( (time(NULL) - last_purge) < 5 ) {
+                       return; /* Too soon, go away */
+               }
+       }
+       time(&last_purge);
+
+       if (try_critical_section(S_SESSION_TABLE))
+               return;
+               
+       ptr = ContextList;
+       while (ptr) {
+               ptr2 = ptr;
+               ptr = ptr->next;
+               
+               if ( (ptr2->state == CON_IDLE) && (ptr2->kill_me) ) {
+                       /* Remove the session from the active list */
+                       if (ptr2->prev) {
+                               ptr2->prev->next = ptr2->next;
+                       }
+                       else {
+                               ContextList = ptr2->next;
+                       }
+                       if (ptr2->next) {
+                               ptr2->next->prev = ptr2->prev;
+                       }
+
+                       --num_sessions;
+                       /* And put it on our to-be-destroyed list */
+                       ptr2->next = rem;
+                       rem = ptr2;
+               }
+       }
+       end_critical_section(S_SESSION_TABLE);
+
+       /* Now that we no longer have the session list locked, we can take
+        * our time and destroy any sessions on the to-be-killed list, which
+        * is allocated privately on this thread's stack.
+        */
+       while (rem != NULL) {
+               syslog(LOG_DEBUG, "context: dead_session_purge() purging session %d, reason=%d", rem->cs_pid, rem->kill_me);
+               RemoveContext(rem);
+               ptr = rem;
+               rem = rem->next;
+               free(ptr);
+       }
+}
+
+
+/*
+ * masterCC is the context we use when not attached to a session.  This
+ * function initializes it.
+ */
+void InitializeMasterCC(void) {
+       memset(&masterCC, 0, sizeof(struct CitContext));
+       masterCC.internal_pgm = 1;
+       masterCC.cs_pid = 0;
+}
+
+
+/*
+ * Set the "async waiting" flag for a session, if applicable
+ */
+void set_async_waiting(struct CitContext *ccptr) {
+       syslog(LOG_DEBUG, "context: setting async_waiting flag for session %d", ccptr->cs_pid);
+       if (ccptr->is_async) {
+               ccptr->async_waiting++;
+               if (ccptr->state == CON_IDLE) {
+                       ccptr->state = CON_READY;
+               }
+       }
+}
+
+
+CTDL_MODULE_INIT(session)
+{
+       if (!threading) {
+       }
+       return "session";
+}
diff --git a/citadel/server/context.h b/citadel/server/context.h
new file mode 100644 (file)
index 0000000..e76d7ac
--- /dev/null
@@ -0,0 +1,179 @@
+
+#ifndef CONTEXT_H
+#define CONTEXT_H
+
+#include <stdarg.h>
+#include "sysdep.h"
+#include "server.h"
+#include "sysdep_decls.h"
+#include "threads.h"
+
+
+/*
+ * Values for CitContext.state
+ * 
+ * A session that is doing nothing is in CON_IDLE state.  When activity
+ * is detected on the socket, it goes to CON_READY, indicating that it
+ * needs to have a worker thread bound to it.  When a thread binds to
+ * the session, it goes to CON_EXECUTING and does its thing.  When the
+ * transaction is finished, the thread sets it back to CON_IDLE and lets
+ * it go.
+ */
+typedef enum __CCState {
+       CON_IDLE,               /* This context is doing nothing */
+       CON_GREETING,           /* This context needs to output its greeting */
+       CON_STARTING,           /* This context is outputting its greeting */
+       CON_READY,              /* This context needs attention */
+       CON_EXECUTING,          /* This context is bound to a thread */
+       CON_SYS                 /* This is a system context and mustn't be purged */
+} CCState;
+
+#ifndef __CIT_CONTEXT__
+#define __CIT_CONTEXT__
+typedef struct CitContext CitContext;
+#endif
+
+/*
+ * This structure keeps track of all information relating to a running 
+ * session on the server.  We keep one of these for each session.
+ */
+struct CitContext {
+       CitContext *prev;       /* Link to previous session in list */
+       CitContext *next;       /* Link to next session in the list */
+
+       int cs_pid;             /* session ID */
+       int dont_term;          /* for special activities like artv so we don't get killed */
+       double created;         /* time of birth */
+       time_t lastcmd;         /* time of last command executed */
+       time_t lastidle;        /* For computing idle time */
+       CCState state;          /* thread state (see CON_ values below) */
+       int kill_me;            /* Set to nonzero to flag for termination */
+
+       IOBuffer SendBuf,       /* Our write Buffer */
+               RecvBuf,        /* Our block buffered read buffer */
+               SBuf;           /* Our block buffered read buffer for clients */
+
+       StrBuf *MigrateBuf;     /* Our block buffered read buffer */
+       StrBuf *sMigrateBuf;    /* Our block buffered read buffer */
+
+       int client_socket;
+       int is_local_client;    /* set to 1 if client is running on the same host */
+       /* Redirect this session's output to a memory buffer? */
+       StrBuf *redirect_buffer;                /* the buffer */
+       StrBuf *StatusMessage;
+#ifdef HAVE_OPENSSL
+       SSL *ssl;
+       int redirect_ssl;
+#endif
+
+       char curr_user[USERNAME_SIZE];  /* name of current user */
+       int logged_in;          /* logged in? */
+       int internal_pgm;       /* authenticated as internal program? */
+       int nologin;            /* not allowed to log in */
+       int curr_view;          /* The view type for the current user/room */
+
+       time_t previous_login;  /* Date/time of previous login */
+       char lastcmdname[5];    /* name of last command executed */
+       unsigned cs_flags;      /* miscellaneous flags */
+       int is_async;           /* Nonzero if client accepts async msgs */
+       int async_waiting;      /* Nonzero if there are async msgs waiting */
+       int input_waiting;      /* Nonzero if there is client input waiting */
+       int can_receive_im;     /* Session is capable of receiving instant messages */
+
+       /* Client information */
+       int cs_clientdev;       /* client developer ID */
+       int cs_clienttyp;       /* client type code */
+       int cs_clientver;       /* client version number */
+       char cs_clientinfo[256];/* if its a unix domain socket, some info for logging. */
+       uid_t cs_UDSclientUID;  /* the uid of the client when talking via UDS */
+       char cs_clientname[32]; /* name of client software */
+       char cs_host[64];       /* host logged in from */
+       char cs_addr[64];       /* address logged in from */
+
+       /* The Internet type of thing */
+       char cs_principal_id[256];              /* User principal identity for XMPP, ActivityPub, etc. */
+       char cs_inet_email[128];                /* Return address of outbound Internet mail */
+       char cs_inet_other_emails[1024];        /* User's other valid Internet email addresses */
+       char cs_inet_fn[128];                   /* Friendly-name of outbound Internet mail */
+
+       FILE *download_fp;      /* Fields relating to file transfer */
+       size_t download_fp_total;
+       char download_desired_section[128];
+       FILE *upload_fp;
+       char upl_file[256];
+       char upl_path[PATH_MAX];
+       char upl_comment[256];
+       char upl_filedir[PATH_MAX];
+       char upl_mimetype[64];
+
+       struct ctdluser user;   /* Database record buffers */
+       struct ctdlroom room;
+
+       /* A linked list of all instant messages sent to us. */
+       struct ExpressMessage *FirstExpressMessage;
+       int disable_exp;        /* Set to 1 to disable incoming pages */
+       int newmail;            /* Other sessions increment this */
+
+       /* Preferred MIME formats */
+       char preferred_formats[256];
+       int msg4_dont_decode;
+
+       /* Dynamically allocated session data */
+       void *session_specific_data;            /* Used by individual protocol modules */
+       struct cit_ical *CIT_ICAL;              /* calendaring data */
+       struct ma_info *ma;                     /* multipart/alternative data */
+       const char *ServiceName;                /* readable purpose of this session */
+       long tcp_port;
+       void *openid_data;                      /* Data stored by the OpenID module */
+       char *ldap_dn;                          /* DN of user when using AUTHMODE_LDAP */
+
+       void (*h_command_function) (void) ;     /* service command function */
+       void (*h_async_function) (void) ;       /* do async msgs function */
+       void (*h_greeting_function) (void) ;    /* greeting function for session startup */
+
+       long *cached_msglist;                   /* results of the previous CtdlForEachMessage() */
+       int cached_num_msgs;
+
+       char vcard_updated_by_ldap;             /* !0 iff ldap changed the vcard, treat as aide update */
+};
+
+#define CC MyContext()
+
+extern pthread_key_t MyConKey;                 /* TSD key for MyContext() */
+extern int num_sessions;
+extern CitContext masterCC;
+extern CitContext *ContextList;
+
+CitContext *MyContext (void);
+void RemoveContext (struct CitContext *);
+CitContext *CreateNewContext (void);
+void context_cleanup(void);
+void kill_session (int session_to_kill);
+void InitializeMasterCC(void);
+void dead_session_purge(int force);
+void set_async_waiting(struct CitContext *ccptr);
+
+CitContext *CloneContext(CitContext *CloneMe);
+
+/* forcibly close and flush fd's on shutdown */
+void terminate_all_sessions(void);
+
+/* Deprecated, user CtdlBumpNewMailCounter() instead */
+void BumpNewMailCounter(long) __attribute__ ((deprecated));
+
+void terminate_idle_sessions(void);
+int CtdlTerminateOtherSession (int session_num);
+/* bits returned by CtdlTerminateOtherSession */
+#define TERM_FOUND     0x01
+#define TERM_ALLOWED   0x02
+#define TERM_KILLED    0x03
+#define TERM_NOTALLOWED -1
+
+/*
+ * Bind a thread to a context.  (It's inline merely to speed things up.)
+ */
+static INLINE void become_session(CitContext *which_con) {
+       pthread_setspecific(MyConKey, (void *)which_con );
+}
+
+#endif /* CONTEXT_H */
diff --git a/citadel/server/control.c b/citadel/server/control.c
new file mode 100644 (file)
index 0000000..4efe823
--- /dev/null
@@ -0,0 +1,809 @@
+//
+// This module handles states which are global to the entire server.
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or disclosure
+// is subject to the terms of the GNU General Public License, version 3.
+// The program is distributed without any warranty, expressed or implied.
+
+#include <stdio.h>
+#include <sys/file.h>
+#include <libcitadel.h>
+
+#include "ctdl_module.h"
+#include "config.h"
+#include "citserver.h"
+#include "user_ops.h"
+
+long control_highest_user = 0;
+
+/*
+ * This is the legacy "control record" format for the message base.  If found
+ * on disk, its contents will be migrated into the system configuration.  Never
+ * change this.
+ */
+struct legacy_ctrl_format {
+       long MMhighest;                 /* highest message number in file   */
+       unsigned MMflags;               /* Global system flags              */
+       long MMnextuser;                /* highest user number on system    */
+       long MMnextroom;                /* highest room number on system    */
+       int MM_hosted_upgrade_level;    /* Server-hosted upgrade level      */
+       int MM_fulltext_wordbreaker;    /* ID of wordbreaker in use         */
+       long MMfulltext;                /* highest message number indexed   */
+       int MMdbversion;                /* Version of Berkeley DB used on previous server run */
+};
+
+
+/*
+ * data that gets passed back and forth between control_find_highest() and its caller
+ */
+struct cfh {
+       long highest_roomnum_found;
+       long highest_msgnum_found;
+};
+
+
+/*
+ * Callback to get highest room number when rebuilding message base metadata
+ *
+ * sanity_diag_mode (can be set by -s flag at startup) may be:
+ * 0 = attempt to fix inconsistencies
+ * 1 = show inconsistencies but don't repair them, exit after complete
+ * 2 = show inconsistencies but don't repair them, continue execution
+ */
+void control_find_highest(struct ctdlroom *qrbuf, void *data) {
+       struct cfh *cfh = (struct cfh *)data;
+       struct cdbdata *cdbfr;
+       long *msglist;
+       int num_msgs=0;
+       int c;
+
+       if (qrbuf->QRnumber > cfh->highest_roomnum_found) {
+               cfh->highest_roomnum_found = qrbuf->QRnumber;
+       }
+
+       /* Load the message list */
+       cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
+       if (cdbfr != NULL) {
+               msglist = (long *) cdbfr->ptr;
+               num_msgs = cdbfr->len / sizeof(long);
+       }
+       else {
+               return; /* No messages at all?  No further action. */
+       }
+
+       if (num_msgs > 0) {
+               for (c=0; c<num_msgs; c++) {
+                       if (msglist[c] > cfh->highest_msgnum_found) {
+                               cfh->highest_msgnum_found = msglist[c];
+                       }
+               }
+       }
+
+       cdb_free(cdbfr);
+}
+
+
+/*
+ * Callback to get highest user number.
+ */
+void control_find_user(char *username, void *out_data) {
+       struct ctdluser EachUser;
+
+       if (CtdlGetUser(&EachUser, username) != 0) {
+               return;
+       }
+
+       if (EachUser.usernum > CtdlGetConfigLong("MMnextuser")) {
+               syslog(LOG_DEBUG, "control: fixing MMnextuser %ld > %ld , found in %s",
+                       EachUser.usernum, CtdlGetConfigLong("MMnextuser"), EachUser.fullname
+               );
+               if (!sanity_diag_mode) {
+                       CtdlSetConfigLong("MMnextuser", EachUser.usernum);
+               }
+       }
+}
+
+
+/*
+ * If we have a legacy format control record on disk, import it.
+ */
+void migrate_legacy_control_record(void) {
+       FILE *fp = NULL;
+       struct legacy_ctrl_format c;
+       memset(&c, 0, sizeof(c));
+
+       fp = fopen("citadel.control", "rb+");
+       if (fp != NULL) {
+               syslog(LOG_INFO, "control: legacy format record found -- importing to db");
+               fread(&c, sizeof(struct legacy_ctrl_format), 1, fp);
+               
+               CtdlSetConfigLong(      "MMhighest",                    c.MMhighest);
+               CtdlSetConfigInt(       "MMflags",                      c.MMflags);
+               CtdlSetConfigLong(      "MMnextuser",                   c.MMnextuser);
+               CtdlSetConfigLong(      "MMnextroom",                   c.MMnextroom);
+               CtdlSetConfigInt(       "MM_hosted_upgrade_level",      c.MM_hosted_upgrade_level);
+               CtdlSetConfigInt(       "MM_fulltext_wordbreaker",      c.MM_fulltext_wordbreaker);
+               CtdlSetConfigLong(      "MMfulltext",                   c.MMfulltext);
+
+               fclose(fp);
+               if (unlink("citadel.control") != 0) {
+                       fprintf(stderr, "Unable to remove legacy control record after migrating it.\n");
+                       fprintf(stderr, "Exiting to prevent data corruption.\n");
+                       exit(CTDLEXIT_CONFIG);
+               }
+       }
+}
+
+
+/*
+ * check_control   -  check the control record has sensible values for message, user and room numbers
+ */
+void check_control(void) {
+
+       syslog(LOG_INFO, "control: sanity checking the recorded highest message and room numbers");
+       struct cfh cfh;
+       memset(&cfh, 0, sizeof(struct cfh));
+       CtdlForEachRoom(control_find_highest, &cfh);
+
+       if (cfh.highest_roomnum_found > CtdlGetConfigLong("MMnextroom")) {
+               syslog(LOG_DEBUG, "control: fixing MMnextroom %ld > %ld", cfh.highest_roomnum_found, CtdlGetConfigLong("MMnextroom"));
+               if (!sanity_diag_mode) {
+                       CtdlSetConfigLong("MMnextroom", cfh.highest_roomnum_found);
+               }
+       }
+
+       if (cfh.highest_msgnum_found > CtdlGetConfigLong("MMhighest")) {
+               syslog(LOG_DEBUG, "control: fixing MMhighest %ld > %ld", cfh.highest_msgnum_found, CtdlGetConfigLong("MMhighest"));
+               if (!sanity_diag_mode) {
+                       CtdlSetConfigLong("MMhighest", cfh.highest_msgnum_found);
+               }
+       }
+
+       syslog(LOG_INFO, "control: sanity checking the recorded highest user number");
+       ForEachUser(control_find_user, NULL);
+
+       syslog(LOG_INFO, "control: sanity checks complete");
+
+       if (sanity_diag_mode == 1) {
+               syslog(LOG_INFO, "control: sanity check diagnostic mode is active - exiting now");
+               abort();
+       }
+}
+
+
+/*
+ * get_new_message_number()  -  Obtain a new, unique ID to be used for a message.
+ */
+long get_new_message_number(void)
+{
+       long retval = 0L;
+       begin_critical_section(S_CONTROL);
+       retval = CtdlGetConfigLong("MMhighest");
+       ++retval;
+       CtdlSetConfigLong("MMhighest", retval);
+       end_critical_section(S_CONTROL);
+       return(retval);
+}
+
+
+/*
+ * CtdlGetCurrentMessageNumber()  -  Obtain the current highest message number in the system
+ * This provides a quick way to initialize a variable that might be used to indicate
+ * messages that should not be processed.   For example, an inbox rules script will use this
+ * to record determine that messages older than this should not be processed.
+ *
+ * (Why is this function here?  Can't we just go straight to the config variable it fetches?)
+ */
+long CtdlGetCurrentMessageNumber(void)
+{
+       return CtdlGetConfigLong("MMhighest");
+}
+
+
+/*
+ * get_new_user_number()  -  Obtain a new, unique ID to be used for a user.
+ */
+long get_new_user_number(void)
+{
+       long retval = 0L;
+       begin_critical_section(S_CONTROL);
+       retval = CtdlGetConfigLong("MMnextuser");
+       ++retval;
+       CtdlSetConfigLong("MMnextuser", retval);
+       end_critical_section(S_CONTROL);
+       return(retval);
+}
+
+
+/*
+ * get_new_room_number()  -  Obtain a new, unique ID to be used for a room.
+ */
+long get_new_room_number(void)
+{
+       long retval = 0L;
+       begin_critical_section(S_CONTROL);
+       retval = CtdlGetConfigLong("MMnextroom");
+       ++retval;
+       CtdlSetConfigLong("MMnextroom", retval);
+       end_critical_section(S_CONTROL);
+       return(retval);
+}
+
+
+/*
+ * Helper function for cmd_conf() to handle boolean values
+ */
+int confbool(char *v)
+{
+       if (IsEmptyStr(v)) return(0);
+       if (atoi(v) != 0) return(1);
+       return(0);
+}
+
+
+/* 
+ * Get or set global configuration options
+ */
+void cmd_conf(char *argbuf)
+{
+       char cmd[16];
+       char buf[1024];
+       int a, i;
+       long ii;
+       char *confptr;
+       char confname[128];
+
+       if (CtdlAccessCheck(ac_aide)) return;
+
+       extract_token(cmd, argbuf, 0, '|', sizeof cmd);
+
+       // CONF GET - retrieve system configuration in legacy format
+       // This is deprecated; please do not add fields or change their order.
+       if (!strcasecmp(cmd, "GET")) {
+               cprintf("%d Configuration...\n", LISTING_FOLLOWS);
+               cprintf("%s\n",         CtdlGetConfigStr("c_nodename"));
+               cprintf("%s\n",         CtdlGetConfigStr("c_fqdn"));
+               cprintf("%s\n",         CtdlGetConfigStr("c_humannode"));
+               cprintf("xxx\n"); /* placeholder -- field no longer in use */
+               cprintf("%d\n",         CtdlGetConfigInt("c_creataide"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_sleeping"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_initax"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_regiscall"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_twitdetect"));
+               cprintf("%s\n",         CtdlGetConfigStr("c_twitroom"));
+               cprintf("%s\n",         CtdlGetConfigStr("c_moreprompt"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_restrict"));
+               cprintf("%s\n",         CtdlGetConfigStr("c_site_location"));
+               cprintf("%s\n",         CtdlGetConfigStr("c_sysadm"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_maxsessions"));
+               cprintf("xxx\n"); /* placeholder -- field no longer in use */
+               cprintf("%d\n",         CtdlGetConfigInt("c_userpurge"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_roompurge"));
+               cprintf("%s\n",         CtdlGetConfigStr("c_logpages"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_createax"));
+               cprintf("%ld\n",        CtdlGetConfigLong("c_maxmsglen"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_min_workers"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_max_workers"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_pop3_port"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_smtp_port"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_rfc822_strict_from"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_aide_zap"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_imap_port"));
+               cprintf("%ld\n",        CtdlGetConfigLong("c_net_freq"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_disable_newu"));
+               cprintf("1\n"); /* niu */
+               cprintf("%d\n",         CtdlGetConfigInt("c_purge_hour"));
+               cprintf("%s\n",         CtdlGetConfigStr("c_ldap_host"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_ldap_port"));
+               cprintf("%s\n",         CtdlGetConfigStr("c_ldap_base_dn"));
+               cprintf("%s\n",         CtdlGetConfigStr("c_ldap_bind_dn"));
+               cprintf("%s\n",         CtdlGetConfigStr("c_ldap_bind_pw"));
+               cprintf("%s\n",         CtdlGetConfigStr("c_ip_addr"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_msa_port"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_imaps_port"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_pop3s_port"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_smtps_port"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_enable_fulltext"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_auto_cull"));
+               cprintf("1\n");
+               cprintf("%d\n",         CtdlGetConfigInt("c_allow_spoofing"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_journal_email"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_journal_pubmsgs"));
+               cprintf("%s\n",         CtdlGetConfigStr("c_journal_dest"));
+               cprintf("%s\n",         CtdlGetConfigStr("c_default_cal_zone"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_pftcpdict_port"));
+               cprintf("0\n");
+               cprintf("%d\n",         CtdlGetConfigInt("c_auth_mode"));
+               cprintf("\n");
+               cprintf("\n");
+               cprintf("\n");
+               cprintf("\n");
+               cprintf("%d\n",         CtdlGetConfigInt("c_rbl_at_greeting"));
+               cprintf("\n");
+               cprintf("\n");
+               cprintf("%s\n",         CtdlGetConfigStr("c_pager_program"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_imap_keep_from"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_xmpp_c2s_port"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_xmpp_s2s_port"));
+               cprintf("%ld\n",        CtdlGetConfigLong("c_pop3_fetch"));
+               cprintf("%ld\n",        CtdlGetConfigLong("c_pop3_fastest"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_spam_flag_only"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_guest_logins"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_port_number"));
+               cprintf("%d\n",         ctdluid);
+               cprintf("%d\n",         CtdlGetConfigInt("c_nntp_port"));
+               cprintf("%d\n",         CtdlGetConfigInt("c_nntps_port"));
+               cprintf("000\n");
+       }
+
+       // CONF SET - set system configuration in legacy format
+       // This is deprecated; please do not add fields or change their order.
+       else if (!strcasecmp(cmd, "SET")) {
+               unbuffer_output();
+               cprintf("%d Send configuration...\n", SEND_LISTING);
+               a = 0;
+               while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
+                       switch (a) {
+                       case 0:
+                               CtdlSetConfigStr("c_nodename", buf);
+                               break;
+                       case 1:
+                               CtdlSetConfigStr("c_fqdn", buf);
+                               break;
+                       case 2:
+                               CtdlSetConfigStr("c_humannode", buf);
+                               break;
+                       case 3:
+                               /* placeholder -- field no longer in use */
+                               break;
+                       case 4:
+                               CtdlSetConfigInt("c_creataide", confbool(buf));
+                               break;
+                       case 5:
+                               CtdlSetConfigInt("c_sleeping", atoi(buf));
+                               break;
+                       case 6:
+                               i = atoi(buf);
+                               if (i < 1) i = 1;
+                               if (i > 6) i = 6;
+                               CtdlSetConfigInt("c_initax", i);
+                               break;
+                       case 7:
+                               CtdlSetConfigInt("c_regiscall", confbool(buf));
+                               break;
+                       case 8:
+                               CtdlSetConfigInt("c_twitdetect", confbool(buf));
+                               break;
+                       case 9:
+                               CtdlSetConfigStr("c_twitroom", buf);
+                               break;
+                       case 10:
+                               CtdlSetConfigStr("c_moreprompt", buf);
+                               break;
+                       case 11:
+                               CtdlSetConfigInt("c_restrict", confbool(buf));
+                               break;
+                       case 12:
+                               CtdlSetConfigStr("c_site_location", buf);
+                               break;
+                       case 13:
+                               CtdlSetConfigStr("c_sysadm", buf);
+                               break;
+                       case 14:
+                               i = atoi(buf);
+                               if (i < 0) i = 0;
+                               CtdlSetConfigInt("c_maxsessions", i);
+                               break;
+                       case 15:
+                               /* placeholder -- field no longer in use */
+                               break;
+                       case 16:
+                               CtdlSetConfigInt("c_userpurge", atoi(buf));
+                               break;
+                       case 17:
+                               CtdlSetConfigInt("c_roompurge", atoi(buf));
+                               break;
+                       case 18:
+                               CtdlSetConfigStr("c_logpages", buf);
+                               break;
+                       case 19:
+                               i = atoi(buf);
+                               if (i < 1) i = 1;
+                               if (i > 6) i = 6;
+                               CtdlSetConfigInt("c_createax", i);
+                               break;
+                       case 20:
+                               ii = atol(buf);
+                               if (ii >= 8192) {
+                                       CtdlSetConfigLong("c_maxmsglen", ii);
+                               }
+                               break;
+                       case 21:
+                               i = atoi(buf);
+                               if (i >= 3) {                                   // minimum value
+                                       CtdlSetConfigInt("c_min_workers", i);
+                               }
+                               break;
+                       case 22:
+                               i = atoi(buf);
+                               if (i >= CtdlGetConfigInt("c_min_workers")) {   // max must be >= min
+                                       CtdlSetConfigInt("c_max_workers", i);
+                               }
+                               break;
+                       case 23:
+                               CtdlSetConfigInt("c_pop3_port", atoi(buf));
+                               break;
+                       case 24:
+                               CtdlSetConfigInt("c_smtp_port", atoi(buf));
+                               break;
+                       case 25:
+                               CtdlSetConfigInt("c_rfc822_strict_from", atoi(buf));
+                               break;
+                       case 26:
+                               CtdlSetConfigInt("c_aide_zap", confbool(buf));
+                               break;
+                       case 27:
+                               CtdlSetConfigInt("c_imap_port", atoi(buf));
+                               break;
+                       case 28:
+                               CtdlSetConfigLong("c_net_freq", atol(buf));
+                               break;
+                       case 29:
+                               CtdlSetConfigInt("c_disable_newu", confbool(buf));
+                               break;
+                       case 30:
+                               /* niu */
+                               break;
+                       case 31:
+                               i = atoi(buf);
+                               if ((i >= 0) && (i <= 23)) {
+                                       CtdlSetConfigInt("c_purge_hour", i);
+                               }
+                               break;
+                       case 32:
+                               CtdlSetConfigStr("c_ldap_host", buf);
+                               break;
+                       case 33:
+                               CtdlSetConfigInt("c_ldap_port", atoi(buf));
+                               break;
+                       case 34:
+                               CtdlSetConfigStr("c_ldap_base_dn", buf);
+                               break;
+                       case 35:
+                               CtdlSetConfigStr("c_ldap_bind_dn", buf);
+                               break;
+                       case 36:
+                               CtdlSetConfigStr("c_ldap_bind_pw", buf);
+                               break;
+                       case 37:
+                               CtdlSetConfigStr("c_ip_addr", buf);
+                               break;
+                       case 38:
+                               CtdlSetConfigInt("c_msa_port", atoi(buf));
+                               break;
+                       case 39:
+                               CtdlSetConfigInt("c_imaps_port", atoi(buf));
+                               break;
+                       case 40:
+                               CtdlSetConfigInt("c_pop3s_port", atoi(buf));
+                               break;
+                       case 41:
+                               CtdlSetConfigInt("c_smtps_port", atoi(buf));
+                               break;
+                       case 42:
+                               CtdlSetConfigInt("c_enable_fulltext", confbool(buf));
+                               break;
+                       case 43:
+                               CtdlSetConfigInt("c_auto_cull", confbool(buf));
+                               break;
+                       case 44:
+                               /* niu */
+                               break;
+                       case 45:
+                               CtdlSetConfigInt("c_allow_spoofing", confbool(buf));
+                               break;
+                       case 46:
+                               CtdlSetConfigInt("c_journal_email", confbool(buf));
+                               break;
+                       case 47:
+                               CtdlSetConfigInt("c_journal_pubmsgs", confbool(buf));
+                               break;
+                       case 48:
+                               CtdlSetConfigStr("c_journal_dest", buf);
+                               break;
+                       case 49:
+                               CtdlSetConfigStr("c_default_cal_zone", buf);
+                               break;
+                       case 50:
+                               CtdlSetConfigInt("c_pftcpdict_port", atoi(buf));
+                               break;
+                       case 51:
+                               /* niu */
+                               break;
+                       case 52:
+                               CtdlSetConfigInt("c_auth_mode", atoi(buf));
+                               break;
+                       case 53:
+                               /* niu */
+                               break;
+                       case 54:
+                               /* niu */
+                               break;
+                       case 55:
+                               /* niu */
+                               break;
+                       case 56:
+                               /* niu */
+                               break;
+                       case 57:
+                               CtdlSetConfigInt("c_rbl_at_greeting", confbool(buf));
+                               break;
+                       case 58:
+                               /* niu */
+                               break;
+                       case 59:
+                               /* niu */
+                               break;
+                       case 60:
+                               CtdlSetConfigStr("c_pager_program", buf);
+                               break;
+                       case 61:
+                               CtdlSetConfigInt("c_imap_keep_from", confbool(buf));
+                               break;
+                       case 62:
+                               CtdlSetConfigInt("c_xmpp_c2s_port", atoi(buf));
+                               break;
+                       case 63:
+                               CtdlSetConfigInt("c_xmpp_s2s_port", atoi(buf));
+                               break;
+                       case 64:
+                               CtdlSetConfigLong("c_pop3_fetch", atol(buf));
+                               break;
+                       case 65:
+                               CtdlSetConfigLong("c_pop3_fastest", atol(buf));
+                               break;
+                       case 66:
+                               CtdlSetConfigInt("c_spam_flag_only", confbool(buf));
+                               break;
+                       case 67:
+                               CtdlSetConfigInt("c_guest_logins", confbool(buf));
+                               break;
+                       case 68:
+                               CtdlSetConfigInt("c_port_number", atoi(buf));
+                               break;
+                       case 69:
+                               /* niu */
+                               break;
+                       case 70:
+                               CtdlSetConfigInt("c_nntp_port", atoi(buf));
+                               break;
+                       case 71:
+                               CtdlSetConfigInt("c_nntps_port", atoi(buf));
+                               break;
+                       }
+                       ++a;
+               }
+               snprintf(buf, sizeof buf,
+                       "The global system configuration has been edited by %s.\n",
+                        (CC->logged_in ? CC->curr_user : "an administrator")
+               );
+               CtdlAideMessage(buf, "Citadel Configuration Manager Message");
+
+               if (!IsEmptyStr(CtdlGetConfigStr("c_logpages")))
+                       CtdlCreateRoom(CtdlGetConfigStr("c_logpages"), 3, "", 0, 1, 1, VIEW_BBS);
+
+               /* If full text indexing has been disabled, invalidate the
+                * index so it doesn't try to use it later.
+                */
+               if (CtdlGetConfigInt("c_enable_fulltext") == 0) {
+                       CtdlSetConfigInt("MM_fulltext_wordbreaker", 0);
+               }
+       }
+
+       // CONF GETSYS - retrieve arbitrary system configuration stanzas stored in the message base
+       else if (!strcasecmp(cmd, "GETSYS")) {
+               extract_token(confname, argbuf, 1, '|', sizeof confname);
+               confptr = CtdlGetSysConfig(confname);
+               if (confptr != NULL) {
+                       long len; 
+
+                       len = strlen(confptr);
+                       cprintf("%d %s\n", LISTING_FOLLOWS, confname);
+                       client_write(confptr, len);
+                       if ((len > 0) && (confptr[len - 1] != 10))
+                               client_write("\n", 1);
+                       cprintf("000\n");
+                       free(confptr);
+               } else {
+                       cprintf("%d No such configuration.\n",
+                               ERROR + ILLEGAL_VALUE);
+               }
+       }
+
+       // CONF PUTSYS - store arbitrary system configuration stanzas in the message base
+       else if (!strcasecmp(cmd, "PUTSYS")) {
+               extract_token(confname, argbuf, 1, '|', sizeof confname);
+               unbuffer_output();
+               cprintf("%d %s\n", SEND_LISTING, confname);
+               confptr = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
+               CtdlPutSysConfig(confname, confptr);
+               free(confptr);
+       }
+
+       // CONF GETVAL - retrieve configuration variables from the database
+       // CONF LOADVAL - same thing but can handle variables bigger than 1 KB (deprecated and probably safe to remove)
+       else if ( (!strcasecmp(cmd, "GETVAL")) || (!strcasecmp(cmd, "LOADVAL")) ) {
+               extract_token(confname, argbuf, 1, '|', sizeof confname);
+               char *v = CtdlGetConfigStr(confname);
+               if ( (v) && (!strcasecmp(cmd, "GETVAL")) ) {
+                       cprintf("%d %s|\n", CIT_OK, v);
+               }
+               else if ( (v) && (!strcasecmp(cmd, "LOADVAL")) ) {
+                       cprintf("%d %d\n", BINARY_FOLLOWS, (int)strlen(v));
+                       client_write(v, strlen(v));
+               }
+               else {
+                       cprintf("%d |\n", ERROR);
+               }
+       }
+
+       // CONF PUTVAL - store configuration variables in the database
+       else if (!strcasecmp(cmd, "PUTVAL")) {
+               if (num_tokens(argbuf, '|') < 3) {
+                       cprintf("%d name and value required\n", ERROR);
+               }
+               else {
+                       extract_token(confname, argbuf, 1, '|', sizeof confname);
+                       extract_token(buf, argbuf, 2, '|', sizeof buf);
+                       CtdlSetConfigStr(confname, buf);
+                       cprintf("%d setting '%s' to '%s'\n", CIT_OK, confname, buf);
+               }
+       }
+
+       // CONF STOREVAL - store configuration variables in the database bigger than 1 KB (deprecated and probably safe to remove)
+       else if (!strcasecmp(cmd, "STOREVAL")) {
+               if (num_tokens(argbuf, '|') < 3) {
+                       cprintf("%d name and length required\n", ERROR);
+               }
+               else {
+                       extract_token(confname, argbuf, 1, '|', sizeof confname);
+                       int bytes = extract_int(argbuf, 2);
+                       char *valbuf = malloc(bytes + 1);
+                       cprintf("%d %d\n", SEND_BINARY, bytes);
+                       client_read(valbuf, bytes);
+                       valbuf[bytes+1] = 0;
+                       CtdlSetConfigStr(confname, valbuf);
+                       free(valbuf);
+               }
+       }
+
+       // CONF LISTVAL - list configuration variables in the database and their values
+       else if (!strcasecmp(cmd, "LISTVAL")) {
+               struct cdbdata *cdbcfg;
+               int keylen = 0;
+               char *key = NULL;
+               char *value = NULL;
+       
+               cprintf("%d all configuration variables\n", LISTING_FOLLOWS);
+               cdb_rewind(CDB_CONFIG);
+               while (cdbcfg = cdb_next_item(CDB_CONFIG), cdbcfg != NULL) {
+                       if (cdbcfg->len < 1020) {
+                               keylen = strlen(cdbcfg->ptr);
+                               key = cdbcfg->ptr;
+                               value = cdbcfg->ptr + keylen + 1;
+                               cprintf("%s|%s\n", key, value);
+                       }
+                       cdb_free(cdbcfg);
+               }
+               cprintf("000\n");
+       }
+
+       else {
+               cprintf("%d Illegal option(s) specified.\n", ERROR + ILLEGAL_VALUE);
+       }
+}
+
+
+typedef struct __ConfType {
+       ConstStr Name;
+       long Type;
+}ConfType;
+
+ConfType CfgNames[] = {
+       { {HKEY("localhost") },    0},
+       { {HKEY("directory") },    0},
+       { {HKEY("smarthost") },    2},
+       { {HKEY("fallbackhost") }, 2},
+       { {HKEY("rbl") },          3},
+       { {HKEY("spamassassin") }, 3},
+       { {HKEY("masqdomain") },   1},
+       { {HKEY("clamav") },       3},
+       { {HKEY("notify") },       3},
+       { {NULL, 0}, 0}
+};
+
+HashList *CfgNameHash = NULL;
+void cmd_gvdn(char *argbuf)
+{
+       const ConfType *pCfg;
+       char *confptr;
+       long min = atol(argbuf);
+       const char *Pos = NULL;
+       const char *PPos = NULL;
+       const char *HKey;
+       long HKLen;
+       StrBuf *Line;
+       StrBuf *Config;
+       StrBuf *Cfg;
+       StrBuf *CfgToken;
+       HashList *List;
+       HashPos *It;
+       void *vptr;
+       
+       List = NewHash(1, NULL);
+       Cfg = NewStrBufPlain(CtdlGetConfigStr("c_fqdn"), -1);
+       Put(List, SKEY(Cfg), Cfg, HFreeStrBuf);
+       Cfg = NULL;
+
+       confptr = CtdlGetSysConfig(INTERNETCFG);
+       Config = NewStrBufPlain(confptr, -1);
+       free(confptr);
+
+       Line = NewStrBufPlain(NULL, StrLength(Config));
+       CfgToken = NewStrBufPlain(NULL, StrLength(Config));
+       while (StrBufSipLine(Line, Config, &Pos))
+       {
+               if (Cfg == NULL)
+                       Cfg = NewStrBufPlain(NULL, StrLength(Line));
+               PPos = NULL;
+               StrBufExtract_NextToken(Cfg, Line, &PPos, '|');
+               StrBufExtract_NextToken(CfgToken, Line, &PPos, '|');
+               if (GetHash(CfgNameHash, SKEY(CfgToken), &vptr) &&
+                   (vptr != NULL))
+               {
+                       pCfg = (ConfType *) vptr;
+                       if (pCfg->Type <= min)
+                       {
+                               Put(List, SKEY(Cfg), Cfg, HFreeStrBuf);
+                               Cfg = NULL;
+                       }
+               }
+       }
+
+       cprintf("%d Valid Domains\n", LISTING_FOLLOWS);
+       It = GetNewHashPos(List, 1);
+       while (GetNextHashPos(List, It, &HKLen, &HKey, &vptr))
+       {
+               cputbuf(vptr);
+               cprintf("\n");
+       }
+       cprintf("000\n");
+
+       DeleteHashPos(&It);
+       DeleteHash(&List);
+       FreeStrBuf(&Cfg);
+       FreeStrBuf(&Line);
+       FreeStrBuf(&CfgToken);
+       FreeStrBuf(&Config);
+}
+
+/*****************************************************************************/
+/*                      MODULE INITIALIZATION STUFF                          */
+/*****************************************************************************/
+
+char *ctdl_module_init_control(void) {
+       if (!threading) {
+               int i;
+
+               CfgNameHash = NewHash(1, NULL);
+               for (i = 0; CfgNames[i].Name.Key != NULL; i++)
+                       Put(CfgNameHash, CKEY(CfgNames[i].Name), &CfgNames[i], reference_free_handler);
+
+               CtdlRegisterProtoHook(cmd_gvdn, "GVDN", "get valid domain names");
+               CtdlRegisterProtoHook(cmd_conf, "CONF", "get/set system configuration");
+       }
+       /* return our id for the log */
+       return "control";
+}
diff --git a/citadel/server/control.h b/citadel/server/control.h
new file mode 100644 (file)
index 0000000..649d3fd
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 1987-2015 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+void get_control (void);
+void put_control (void);
+void check_control(void);
+long int get_new_message_number (void);
+long int get_new_user_number (void);
+long int get_new_room_number (void);
+void migrate_legacy_control_record(void);
diff --git a/citadel/server/ctdl_module.h b/citadel/server/ctdl_module.h
new file mode 100644 (file)
index 0000000..6156d0c
--- /dev/null
@@ -0,0 +1,329 @@
+
+#ifndef CTDL_MODULE_H
+#define CTDL_MODULE_H
+
+#include "sysdep.h"
+
+#ifdef HAVE_GC
+#define GC_THREADS
+#define GC_REDIRECT_TO_LOCAL
+#include <gc/gc_local_alloc.h>
+#else
+#define GC_MALLOC malloc
+#define GC_MALLOC_ATOMIC malloc
+#define GC_FREE free
+#define GC_REALLOC realloc
+#endif
+
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <syslog.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#include <limits.h>
+
+#include <libcitadel.h>
+
+#include "server.h"
+#include "sysdep_decls.h"
+#include "msgbase.h"
+#include "threads.h"
+#include "citadel_dirs.h"
+#include "context.h"
+
+extern int threading;
+
+
+/*
+ * define macros for module init stuff
+ */
+#define CTDL_MODULE_INIT(module_name) char *ctdl_module_##module_name##_init (int threading)
+
+#define CTDL_INIT_CALL(module_name) ctdl_module_##module_name##_init (threading)
+
+#define CTDL_MODULE_UPGRADE(module_name) char *ctdl_module_##module_name##_upgrade (void)
+
+#define CTDL_UPGRADE_CALL(module_name) ctdl_module_##module_name##_upgrade ()
+
+#define CtdlAideMessage(TEXT, SUBJECT)         \
+       quickie_message(                        \
+               "Citadel",                      \
+               NULL,                           \
+               NULL,                           \
+               AIDEROOM,                       \
+               TEXT,                           \
+               FMT_CITADEL,                    \
+               SUBJECT) 
+
+/*
+ * Hook functions available to modules.
+ */
+/* Priorities for  */
+#define PRIO_QUEUE 500
+#define PRIO_AGGR 1000
+#define PRIO_SEND 1500
+#define PRIO_CLEANUP 2000
+/* Priorities for EVT_HOUSE */
+#define PRIO_HOUSE 3000
+/* Priorities for EVT_LOGIN */
+#define PRIO_CREATE 10000
+/* Priorities for EVT_LOGOUT */
+#define PRIO_LOGOUT 15000
+/* Priorities for EVT_LOGIN */
+#define PRIO_LOGIN 20000
+/* Priorities for EVT_START */
+#define PRIO_START 25000
+/* Priorities for EVT_STOP */
+#define PRIO_STOP 30000
+/* Priorities for EVT_ASYNC */
+#define PRIO_ASYNC 35000
+/* Priorities for EVT_SHUTDOWN */
+#define PRIO_SHUTDOWN 40000
+/* Priorities for EVT_UNSTEALTH */
+#define PRIO_UNSTEALTH 45000
+/* Priorities for EVT_STEALTH */
+#define PRIO_STEALTH 50000
+
+
+void CtdlRegisterSessionHook(void (*fcn_ptr)(void), int EventType, int Priority);
+void CtdlUnregisterSessionHook(void (*fcn_ptr)(void), int EventType);
+void CtdlShutdownServiceHooks(void);
+
+void CtdlRegisterUserHook(void (*fcn_ptr)(struct ctdluser *), int EventType);
+void CtdlUnregisterUserHook(void (*fcn_ptr)(struct ctdluser *), int EventType);
+
+void CtdlRegisterXmsgHook(int (*fcn_ptr)(char *, char *, char *, char *), int order);
+void CtdlUnregisterXmsgHook(int (*fcn_ptr)(char *, char *, char *, char *), int order);
+
+void CtdlRegisterMessageHook(int (*handler)(struct CtdlMessage *, struct recptypes *), int EventType);
+void CtdlUnregisterMessageHook(int (*handler)(struct CtdlMessage *, struct recptypes *), int EventType);
+
+void CtdlRegisterRoomHook(int (*fcn_ptr)(struct ctdlroom *) );
+void CtdlUnregisterRoomHook(int (*fnc_ptr)(struct ctdlroom *) );
+
+void CtdlRegisterDeleteHook(void (*handler)(char *, long) );
+void CtdlUnregisterDeleteHook(void (*handler)(char *, long) );
+
+void CtdlRegisterCleanupHook(void (*fcn_ptr)(void));
+void CtdlUnregisterCleanupHook(void (*fcn_ptr)(void));
+
+void CtdlRegisterEVCleanupHook(void (*fcn_ptr)(void));
+void CtdlUnregisterEVCleanupHook(void (*fcn_ptr)(void));
+
+void CtdlRegisterProtoHook(void (*handler)(char *), char *cmd, char *desc);
+
+void CtdlRegisterServiceHook(int tcp_port,
+                            char *sockpath,
+                            void (*h_greeting_function) (void),
+                            void (*h_command_function) (void),
+                            void (*h_async_function) (void),
+                            const char *ServiceName
+);
+void CtdlUnregisterServiceHook(int tcp_port,
+                       char *sockpath,
+                        void (*h_greeting_function) (void),
+                        void (*h_command_function) (void),
+                        void (*h_async_function) (void)
+);
+
+void CtdlRegisterFixedOutputHook(char *content_type, void (*output_function) (char *supplied_data, int len));
+void CtdlUnRegisterFixedOutputHook(char *content_type);
+void CtdlRegisterMaintenanceThread(char *name, void *(*thread_proc) (void *arg));
+void CtdlRegisterSearchFuncHook(void (*fcn_ptr)(int *, long **, const char *), char *name);
+
+/*
+ * if you say a) (which may take a while)
+ * don't forget to say b)
+ */
+void CtdlDisableHouseKeeping(void);
+void CtdlEnableHouseKeeping(void);
+
+/* TODODRW: This needs to be changed into a hook type interface
+ * for now we have this horrible hack
+ */
+void CtdlModuleStartCryptoMsgs(char *ok_response, char *nosup_response, char *error_response);
+
+/* return the current context list as an array and do it in a safe manner
+ * The returned data is a copy so only reading is useful
+ * The number of contexts is returned in count.
+ * Beware, this does not copy any of the data pointed to by the context.
+ * This means that you can not rely on things like the redirect buffer being valid.
+ * You must free the returned pointer when done.
+ */
+struct CitContext *CtdlGetContextArray (int *count);
+void CtdlFillSystemContext(struct CitContext *context, char *name);
+int CtdlTrySingleUser(void);
+void CtdlEndSingleUser(void);
+int CtdlWantSingleUser(void);
+int CtdlIsSingleUser(void);
+
+
+int CtdlIsUserLoggedIn (char *user_name);
+int CtdlIsUserLoggedInByNum (long usernum);
+void CtdlBumpNewMailCounter(long which_user);
+
+
+/*
+ * CtdlGetCurrentMessageNumber()  -  Obtain the current highest message number in the system
+ * This provides a quick way to initialize a variable that might be used to indicate
+ * messages that should not be processed.  For example, a new inbox script will use this
+ * to record determine that messages older than this should not be processed.
+ * This function is defined in control.c
+ */
+long CtdlGetCurrentMessageNumber(void);
+
+
+
+/*
+ * Expose various room operation functions from room_ops.c to the modules API
+ */
+
+unsigned CtdlCreateRoom(char *new_room_name,
+                       int new_room_type,
+                       char *new_room_pass,
+                       int new_room_floor,
+                       int really_create,
+                       int avoid_access,
+                       int new_room_view);
+int CtdlGetRoom(struct ctdlroom *qrbuf, const char *room_name);
+int CtdlGetRoomLock(struct ctdlroom *qrbuf, const char *room_name);
+int CtdlDoIHavePermissionToDeleteThisRoom(struct ctdlroom *qr);
+void CtdlRoomAccess(struct ctdlroom *roombuf, struct ctdluser *userbuf, int *result, int *view);
+void CtdlPutRoomLock(struct ctdlroom *qrbuf);
+typedef void (*ForEachRoomCallBack)(struct ctdlroom *EachRoom, void *out_data);
+void CtdlForEachRoom(ForEachRoomCallBack CB, void *in_data);
+char *LoadRoomNetConfigFile(long roomnum);
+void SaveRoomNetConfigFile(long roomnum, const char *raw_netconfig);
+void CtdlDeleteRoom(struct ctdlroom *qrbuf);
+int CtdlRenameRoom(char *old_name, char *new_name, int new_floor);
+void CtdlUserGoto (char *where, int display_result, int transiently, int *msgs, int *new, long *oldest, long *newest);
+struct floor *CtdlGetCachedFloor(int floor_num);
+void CtdlScheduleRoomForDeletion(struct ctdlroom *qrbuf);
+void CtdlGetFloor (struct floor *flbuf, int floor_num);
+void CtdlPutFloor (struct floor *flbuf, int floor_num);
+void CtdlPutFloorLock(struct floor *flbuf, int floor_num);
+int CtdlGetFloorByName(const char *floor_name);
+int CtdlGetFloorByNameLock(const char *floor_name);
+int CtdlGetAvailableFloor(void);
+int CtdlIsNonEditable(struct ctdlroom *qrbuf);
+void CtdlPutRoom(struct ctdlroom *);
+
+/*
+ * Possible return values for CtdlRenameRoom()
+ */
+enum {
+       crr_ok,                         /* success */
+       crr_room_not_found,             /* room not found */
+       crr_already_exists,             /* new name already exists */
+       crr_noneditable,                /* cannot edit this room */
+       crr_invalid_floor,              /* target floor does not exist */
+       crr_access_denied               /* not allowed to edit this room */
+};
+
+
+
+/*
+ * API declarations from citserver.h
+ */
+int CtdlAccessCheck(int);
+/* 'required access level' values which may be passed to CtdlAccessCheck()
+ */
+enum {
+       ac_none,
+       ac_logged_in_or_guest,
+       ac_logged_in,
+       ac_room_aide,
+       ac_aide,
+       ac_internal,
+};
+
+
+
+/*
+ * API declarations from serv_extensions.h
+ */
+void CtdlModuleDoSearch(int *num_msgs, long **search_msgs, const char *search_string, const char *func_name);
+
+#define NODENAME               CtdlGetConfigStr("c_nodename")
+#define FQDN                   CtdlGetConfigStr("c_fqdn")
+#define CTDLUID                        ctdluid
+#define CREATAIDE              CtdlGetConfigInt("c_creataide")
+#define REGISCALL              CtdlGetConfigInt("c_regiscall")
+#define TWITDETECT             CtdlGetConfigInt("c_twitdetect")
+#define TWITROOM               CtdlGetConfigStr("c_twitroom")
+#define RESTRICT_INTERNET      CtdlGetConfigInt("c_restrict")
+
+#define CtdlREGISTERRoomCfgType(a, p, uniq, nSegs, s, d) RegisterRoomCfgType(#a, sizeof(#a) - 1, a, p, uniq, nSegs, s, d);
+
+
+
+/*
+ * Expose API calls from user_ops.c
+ */
+int CtdlGetUser(struct ctdluser *usbuf, char *name);
+int CtdlGetUserLen(struct ctdluser *usbuf, const char *name, long len);
+int CtdlGetUserLock(struct ctdluser *usbuf, char *name);
+void CtdlPutUser(struct ctdluser *usbuf);
+void CtdlPutUserLock(struct ctdluser *usbuf);
+int CtdlLockGetCurrentUser(void);
+void CtdlPutCurrentUserLock(void);
+int CtdlGetUserByNumber(struct ctdluser *usbuf, long number);
+void CtdlGetRelationship(visit *vbuf, struct ctdluser *rel_user, struct ctdlroom *rel_room);
+void CtdlSetRelationship(visit *newvisit, struct ctdluser *rel_user, struct ctdlroom *rel_room);
+void CtdlMailboxName(char *buf, size_t n, const struct ctdluser *who, const char *prefix);
+int CtdlLoginExistingUser(const char *username);
+
+/*
+ * Values which may be returned by CtdlLoginExistingUser()
+ */
+enum {
+       pass_ok,
+       pass_already_logged_in,
+       pass_no_user,
+       pass_internal_error,
+       pass_wrong_password
+};
+
+int CtdlTryPassword(const char *password, long len);
+/*
+ * Values which may be returned by CtdlTryPassword()
+ */
+enum {
+       login_ok,
+       login_already_logged_in,
+       login_too_many_users,
+       login_not_found
+};
+
+void CtdlUserLogout(void);
+
+/*
+ * Expose API calls from msgbase.c
+ */
+
+
+/*
+ * Expose API calls from euidindex.c
+ */
+long CtdlLocateMessageByEuid(char *euid, struct ctdlroom *qrbuf);
+
+
+/*
+ * Expose API calls from modules/openid/serv_openid_rp.c in order to turn it into a generic external authentication driver
+ */
+int attach_extauth(struct ctdluser *who, StrBuf *claimed_id);
+
+#endif /* CTDL_MODULE_H */
diff --git a/citadel/server/database.c b/citadel/server/database.c
new file mode 100644 (file)
index 0000000..eb4dae5
--- /dev/null
@@ -0,0 +1,811 @@
+// This is a data store backend for the Citadel server which uses Berkeley DB.
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+/*****************************************************************************
+       Tunable configuration parameters for the Berkeley DB back end
+ *****************************************************************************/
+
+/* Citadel will checkpoint the db at the end of every session, but only if
+ * the specified number of kilobytes has been written, or if the specified
+ * number of minutes has passed, since the last checkpoint.
+ */
+#define MAX_CHECKPOINT_KBYTES  256
+#define MAX_CHECKPOINT_MINUTES 15
+
+/*****************************************************************************/
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <zlib.h>
+#include <db.h>
+
+#if DB_VERSION_MAJOR < 5
+#error Citadel requires Berkeley DB v5.0 or newer.  Please upgrade.
+#endif
+
+#include <libcitadel.h>
+#include "ctdl_module.h"
+#include "control.h"
+#include "citserver.h"
+#include "config.h"
+
+static DB *dbp[MAXCDB];                /* One DB handle for each Citadel database */
+static DB_ENV *dbenv;          /* The DB environment (global) */
+
+
+void cdb_abort(void) {
+       syslog(LOG_DEBUG, "db: citserver is stopping in order to prevent data loss. uid=%d gid=%d euid=%d egid=%d",
+               getuid(), getgid(), geteuid(), getegid()
+       );
+       exit(CTDLEXIT_DB);
+}
+
+
+/* Verbose logging callback */
+void cdb_verbose_log(const DB_ENV *dbenv, const char *msg, const char *what_is_this) {
+       if (!IsEmptyStr(msg)) {
+               syslog(LOG_DEBUG, "db: %s", msg);
+       }
+}
+
+
+/* Verbose logging callback */
+void cdb_verbose_err(const DB_ENV *dbenv, const char *errpfx, const char *msg) {
+       syslog(LOG_ERR, "db: %s", msg);
+}
+
+
+/* wrapper for txn_abort() that logs/aborts on error */
+static void txabort(DB_TXN *tid) {
+       int ret;
+
+       ret = tid->abort(tid);
+
+       if (ret) {
+               syslog(LOG_ERR, "db: txn_abort: %s", db_strerror(ret));
+               cdb_abort();
+       }
+}
+
+
+/* wrapper for txn_commit() that logs/aborts on error */
+static void txcommit(DB_TXN *tid) {
+       int ret;
+
+       ret = tid->commit(tid, 0);
+
+       if (ret) {
+               syslog(LOG_ERR, "db: txn_commit: %s", db_strerror(ret));
+               cdb_abort();
+       }
+}
+
+
+/* wrapper for txn_begin() that logs/aborts on error */
+static void txbegin(DB_TXN **tid) {
+       int ret;
+
+       ret = dbenv->txn_begin(dbenv, NULL, tid, 0);
+
+       if (ret) {
+               syslog(LOG_ERR, "db: txn_begin: %s", db_strerror(ret));
+               cdb_abort();
+       }
+}
+
+
+/* panic callback */
+static void dbpanic(DB_ENV * env, int errval) {
+       syslog(LOG_ERR, "db: PANIC: %s", db_strerror(errval));
+}
+
+
+static void cclose(DBC * cursor) {
+       int ret;
+
+       if ((ret = cursor->c_close(cursor))) {
+               syslog(LOG_ERR, "db: c_close: %s", db_strerror(ret));
+               cdb_abort();
+       }
+}
+
+
+static void bailIfCursor(DBC ** cursors, const char *msg) {
+       int i;
+
+       for (i = 0; i < MAXCDB; i++)
+               if (cursors[i] != NULL) {
+                       syslog(LOG_ERR, "db: cursor still in progress on cdb %02x: %s", i, msg);
+                       cdb_abort();
+               }
+}
+
+
+void cdb_check_handles(void) {
+       bailIfCursor(TSD->cursors, "in check_handles");
+
+       if (TSD->tid != NULL) {
+               syslog(LOG_ERR, "db: transaction still in progress!");
+               cdb_abort();
+       }
+}
+
+
+/*
+ * Request a checkpoint of the database.  Called once per minute by the thread manager.
+ */
+void cdb_checkpoint(void) {
+       int ret;
+
+       syslog(LOG_DEBUG, "db: -- checkpoint --");
+       ret = dbenv->txn_checkpoint(dbenv, MAX_CHECKPOINT_KBYTES, MAX_CHECKPOINT_MINUTES, 0);
+
+       if (ret != 0) {
+               syslog(LOG_ERR, "db: cdb_checkpoint() txn_checkpoint: %s", db_strerror(ret));
+               cdb_abort();
+       }
+
+       /* After a successful checkpoint, we can cull the unused logs */
+       if (CtdlGetConfigInt("c_auto_cull")) {
+               ret = dbenv->log_set_config(dbenv, DB_LOG_AUTO_REMOVE, 1);
+       }
+       else {
+               ret = dbenv->log_set_config(dbenv, DB_LOG_AUTO_REMOVE, 0);
+       }
+}
+
+
+/*
+ * Open the various databases we'll be using.  Any database which
+ * does not exist should be created.  Note that we don't need a
+ * critical section here, because there aren't any active threads
+ * manipulating the database yet.
+ */
+void open_databases(void) {
+       int ret;
+       int i;
+       char dbfilename[32];
+       u_int32_t flags = 0;
+       int dbversion_major, dbversion_minor, dbversion_patch;
+
+       syslog(LOG_DEBUG, "db: open_databases() starting");
+       syslog(LOG_DEBUG, "db: Compiled libdb: %s", DB_VERSION_STRING);
+       syslog(LOG_DEBUG, "db:   Linked libdb: %s", db_version(&dbversion_major, &dbversion_minor, &dbversion_patch));
+       syslog(LOG_DEBUG, "db:    Linked zlib: %s", zlibVersion());
+
+       /*
+        * Silently try to create the database subdirectory.  If it's already there, no problem.
+        */
+       if ((mkdir(ctdl_db_dir, 0700) != 0) && (errno != EEXIST)) {
+               syslog(LOG_ERR, "db: unable to create database directory [%s]: %m", ctdl_db_dir);
+       }
+       if (chmod(ctdl_db_dir, 0700) != 0) {
+               syslog(LOG_ERR, "db: unable to set database directory permissions [%s]: %m", ctdl_db_dir);
+       }
+       if (chown(ctdl_db_dir, CTDLUID, (-1)) != 0) {
+               syslog(LOG_ERR, "db: unable to set the owner for [%s]: %m", ctdl_db_dir);
+       }
+       syslog(LOG_DEBUG, "db: Setting up DB environment");
+       // db_env_set_func_yield((int (*)(u_long,  u_long))sched_yield);
+       ret = db_env_create(&dbenv, 0);
+       if (ret) {
+               syslog(LOG_ERR, "db: db_env_create: %s", db_strerror(ret));
+               syslog(LOG_ERR, "db: exit code %d", ret);
+               exit(CTDLEXIT_DB);
+       }
+       dbenv->set_errpfx(dbenv, "citserver");
+       dbenv->set_paniccall(dbenv, dbpanic);
+       dbenv->set_errcall(dbenv, cdb_verbose_err);
+       dbenv->set_errpfx(dbenv, "ctdl");
+       dbenv->set_msgcall(dbenv, cdb_verbose_log);
+       dbenv->set_verbose(dbenv, DB_VERB_DEADLOCK, 1);
+       dbenv->set_verbose(dbenv, DB_VERB_RECOVERY, 1);
+
+       /*
+        * We want to specify the shared memory buffer pool cachesize,
+        * but everything else is the default.
+        */
+       ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0);
+       if (ret) {
+               syslog(LOG_ERR, "db: set_cachesize: %s", db_strerror(ret));
+               dbenv->close(dbenv, 0);
+               syslog(LOG_ERR, "db: exit code %d", ret);
+               exit(CTDLEXIT_DB);
+       }
+
+       if ((ret = dbenv->set_lk_detect(dbenv, DB_LOCK_DEFAULT))) {
+               syslog(LOG_ERR, "db: set_lk_detect: %s", db_strerror(ret));
+               dbenv->close(dbenv, 0);
+               syslog(LOG_ERR, "db: exit code %d", ret);
+               exit(CTDLEXIT_DB);
+       }
+
+       flags = DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_INIT_TXN | DB_INIT_LOCK | DB_THREAD | DB_INIT_LOG;
+       syslog(LOG_DEBUG, "db: dbenv->open(dbenv, %s, %d, 0)", ctdl_db_dir, flags);
+       ret = dbenv->open(dbenv, ctdl_db_dir, flags, 0);                                // try opening the database cleanly
+       if (ret == DB_RUNRECOVERY) {
+               syslog(LOG_ERR, "db: dbenv->open: %s", db_strerror(ret));
+               syslog(LOG_ERR, "db: attempting recovery...");
+               flags |= DB_RECOVER;
+               ret = dbenv->open(dbenv, ctdl_db_dir, flags, 0);                        // try recovery
+       }
+       if (ret == DB_RUNRECOVERY) {
+               syslog(LOG_ERR, "db: dbenv->open: %s", db_strerror(ret));
+               syslog(LOG_ERR, "db: attempting catastrophic recovery...");
+               flags &= ~DB_RECOVER;
+               flags |= DB_RECOVER_FATAL;
+               ret = dbenv->open(dbenv, ctdl_db_dir, flags, 0);                        // try catastrophic recovery
+       }
+       if (ret) {
+               syslog(LOG_ERR, "db: dbenv->open: %s", db_strerror(ret));
+               dbenv->close(dbenv, 0);
+               syslog(LOG_ERR, "db: exit code %d", ret);
+               exit(CTDLEXIT_DB);
+       }
+
+       syslog(LOG_INFO, "db: mounting databases");
+       for (i = 0; i < MAXCDB; ++i) {
+               ret = db_create(&dbp[i], dbenv, 0);                                     // Create a database handle
+               if (ret) {
+                       syslog(LOG_ERR, "db: db_create: %s", db_strerror(ret));
+                       syslog(LOG_ERR, "db: exit code %d", ret);
+                       exit(CTDLEXIT_DB);
+               }
+
+               snprintf(dbfilename, sizeof dbfilename, "cdb.%02x", i);                 // table names by number
+               ret = dbp[i]->open(dbp[i], NULL, dbfilename, NULL, DB_BTREE, DB_CREATE | DB_AUTO_COMMIT | DB_THREAD, 0600);
+               if (ret) {
+                       syslog(LOG_ERR, "db: db_open[%02x]: %s", i, db_strerror(ret));
+                       if (ret == ENOMEM) {
+                               syslog(LOG_ERR, "db: You may need to tune your database; please read http://www.citadel.org/doku.php?id=faq:troubleshooting:out_of_lock_entries for more information.");
+                       }
+                       syslog(LOG_ERR, "db: exit code %d", ret);
+                       exit(CTDLEXIT_DB);
+               }
+       }
+}
+
+
+/*
+ * Make sure we own all the files, because in a few milliseconds we're going to drop root privs.
+ */
+void cdb_chmod_data(void) {
+       DIR *dp;
+       struct dirent *d;
+       char filename[PATH_MAX];
+
+       dp = opendir(ctdl_db_dir);
+       if (dp != NULL) {
+               while (d = readdir(dp), d != NULL) {
+                       if (d->d_name[0] != '.') {
+                               snprintf(filename, sizeof filename, "%s/%s", ctdl_db_dir, d->d_name);
+                               syslog(LOG_DEBUG, "db: chmod(%s, 0600) returned %d", filename, chmod(filename, 0600));
+                               syslog(LOG_DEBUG, "db: chown(%s, CTDLUID, -1) returned %d",
+                                       filename, chown(filename, CTDLUID, (-1))
+                               );
+                       }
+               }
+               closedir(dp);
+       }
+}
+
+
+/*
+ * Close all of the db database files we've opened.  This can be done
+ * in a loop, since it's just a bunch of closes.
+ */
+void close_databases(void) {
+       int i;
+       int ret;
+
+       syslog(LOG_INFO, "db: performing final checkpoint");
+       if ((ret = dbenv->txn_checkpoint(dbenv, 0, 0, 0))) {
+               syslog(LOG_ERR, "db: txn_checkpoint: %s", db_strerror(ret));
+       }
+
+       syslog(LOG_INFO, "db: flushing the database logs");
+       if ((ret = dbenv->log_flush(dbenv, NULL))) {
+               syslog(LOG_ERR, "db: log_flush: %s", db_strerror(ret));
+       }
+
+       /* close the tables */
+       syslog(LOG_INFO, "db: closing databases");
+       for (i = 0; i < MAXCDB; ++i) {
+               syslog(LOG_INFO, "db: closing database %02x", i);
+               ret = dbp[i]->close(dbp[i], 0);
+               if (ret) {
+                       syslog(LOG_ERR, "db: db_close: %s", db_strerror(ret));
+               }
+
+       }
+
+       // This seemed nifty at the time but did anyone really look at it?
+       // #ifdef DB_STAT_ALL
+       // /* print some statistics... */
+       // dbenv->lock_stat_print(dbenv, DB_STAT_ALL);
+       // #endif
+
+       /* Close the handle. */
+       ret = dbenv->close(dbenv, 0);
+       if (ret) {
+               syslog(LOG_ERR, "db: DBENV->close: %s", db_strerror(ret));
+       }
+}
+
+
+/*
+ * Decompress a database item if it was compressed on disk
+ */
+void cdb_decompress_if_necessary(struct cdbdata *cdb) {
+       static int magic = COMPRESS_MAGIC;
+
+       if ((cdb == NULL) || (cdb->ptr == NULL) || (cdb->len < sizeof(magic)) || (memcmp(cdb->ptr, &magic, sizeof(magic)))) {
+               return;
+       }
+
+       /* At this point we know we're looking at a compressed item. */
+
+       struct CtdlCompressHeader zheader;
+       char *uncompressed_data;
+       char *compressed_data;
+       uLongf destLen, sourceLen;
+       size_t cplen;
+
+       memset(&zheader, 0, sizeof(struct CtdlCompressHeader));
+       cplen = sizeof(struct CtdlCompressHeader);
+       if (sizeof(struct CtdlCompressHeader) > cdb->len) {
+               cplen = cdb->len;
+       }
+       memcpy(&zheader, cdb->ptr, cplen);
+
+       compressed_data = cdb->ptr;
+       compressed_data += sizeof(struct CtdlCompressHeader);
+
+       sourceLen = (uLongf) zheader.compressed_len;
+       destLen = (uLongf) zheader.uncompressed_len;
+       uncompressed_data = malloc(zheader.uncompressed_len);
+
+       if (uncompress((Bytef *) uncompressed_data,
+                      (uLongf *) & destLen, (const Bytef *) compressed_data, (uLong) sourceLen) != Z_OK) {
+               syslog(LOG_ERR, "db: uncompress() error");
+               cdb_abort();
+       }
+
+       free(cdb->ptr);
+       cdb->len = (size_t) destLen;
+       cdb->ptr = uncompressed_data;
+}
+
+
+/*
+ * Store a piece of data.  Returns 0 if the operation was successful.  If a
+ * key already exists it should be overwritten.
+ */
+int cdb_store(int cdb, const void *ckey, int ckeylen, void *cdata, int cdatalen) {
+
+       DBT dkey, ddata;
+       DB_TXN *tid = NULL;
+       int ret = 0;
+       struct CtdlCompressHeader zheader;
+       char *compressed_data = NULL;
+       int compressing = 0;
+       size_t buffer_len = 0;
+       uLongf destLen = 0;
+
+       memset(&dkey, 0, sizeof(DBT));
+       memset(&ddata, 0, sizeof(DBT));
+       dkey.size = ckeylen;
+       dkey.data = (void *) ckey;
+       ddata.size = cdatalen;
+       ddata.data = cdata;
+
+       /* Only compress Visit and UseTable records.  Everything else is uncompressed. */
+       if ((cdb == CDB_VISIT) || (cdb == CDB_USETABLE)) {
+               compressing = 1;
+               zheader.magic = COMPRESS_MAGIC;
+               zheader.uncompressed_len = cdatalen;
+               buffer_len = ((cdatalen * 101) / 100) + 100 + sizeof(struct CtdlCompressHeader);
+               destLen = (uLongf) buffer_len;
+               compressed_data = malloc(buffer_len);
+               if (compress2((Bytef *) (compressed_data + sizeof(struct CtdlCompressHeader)),
+                             &destLen, (Bytef *) cdata, (uLongf) cdatalen, 1) != Z_OK) {
+                       syslog(LOG_ERR, "db: compress2() error");
+                       cdb_abort();
+               }
+               zheader.compressed_len = (size_t) destLen;
+               memcpy(compressed_data, &zheader, sizeof(struct CtdlCompressHeader));
+               ddata.size = (size_t) (sizeof(struct CtdlCompressHeader) + zheader.compressed_len);
+               ddata.data = compressed_data;
+       }
+
+       if (TSD->tid != NULL) {
+               ret = dbp[cdb]->put(dbp[cdb],   // db
+                                   TSD->tid,   // transaction ID
+                                   &dkey,      // key
+                                   &ddata,     // data
+                                   0           // flags
+               );
+               if (ret) {
+                       syslog(LOG_ERR, "db: cdb_store(%d): %s", cdb, db_strerror(ret));
+                       cdb_abort();
+               }
+               if (compressing) {
+                       free(compressed_data);
+               }
+               return ret;
+       } else {
+               bailIfCursor(TSD->cursors, "attempt to write during r/o cursor");
+
+             retry:
+               txbegin(&tid);
+
+               if ((ret = dbp[cdb]->put(dbp[cdb],      // db
+                                        tid,           // transaction ID
+                                        &dkey,         // key
+                                        &ddata,        // data
+                                        0))) {         // flags
+                       if (ret == DB_LOCK_DEADLOCK) {
+                               txabort(tid);
+                               goto retry;
+                       } else {
+                               syslog(LOG_ERR, "db: cdb_store(%d): %s", cdb, db_strerror(ret));
+                               cdb_abort();
+                       }
+               } else {
+                       txcommit(tid);
+                       if (compressing) {
+                               free(compressed_data);
+                       }
+                       return ret;
+               }
+       }
+       return ret;
+}
+
+
+/*
+ * Delete a piece of data.  Returns 0 if the operation was successful.
+ */
+int cdb_delete(int cdb, void *key, int keylen) {
+       DBT dkey;
+       DB_TXN *tid;
+       int ret;
+
+       memset(&dkey, 0, sizeof dkey);
+       dkey.size = keylen;
+       dkey.data = key;
+
+       if (TSD->tid != NULL) {
+               ret = dbp[cdb]->del(dbp[cdb], TSD->tid, &dkey, 0);
+               if (ret) {
+                       syslog(LOG_ERR, "db: cdb_delete(%d): %s", cdb, db_strerror(ret));
+                       if (ret != DB_NOTFOUND) {
+                               cdb_abort();
+                       }
+               }
+       } else {
+               bailIfCursor(TSD->cursors, "attempt to delete during r/o cursor");
+
+             retry:
+               txbegin(&tid);
+
+               if ((ret = dbp[cdb]->del(dbp[cdb], tid, &dkey, 0)) && ret != DB_NOTFOUND) {
+                       if (ret == DB_LOCK_DEADLOCK) {
+                               txabort(tid);
+                               goto retry;
+                       } else {
+                               syslog(LOG_ERR, "db: cdb_delete(%d): %s", cdb, db_strerror(ret));
+                               cdb_abort();
+                       }
+               } else {
+                       txcommit(tid);
+               }
+       }
+       return ret;
+}
+
+
+static DBC *localcursor(int cdb) {
+       int ret;
+       DBC *curs;
+
+       if (TSD->cursors[cdb] == NULL) {
+               ret = dbp[cdb]->cursor(dbp[cdb], TSD->tid, &curs, 0);
+       }
+       else {
+               ret = TSD->cursors[cdb]->c_dup(TSD->cursors[cdb], &curs, DB_POSITION);
+       }
+
+       if (ret) {
+               syslog(LOG_ERR, "db: localcursor: %s", db_strerror(ret));
+               cdb_abort();
+       }
+
+       return curs;
+}
+
+
+/*
+ * Fetch a piece of data.  If not found, returns NULL.  Otherwise, it returns
+ * a struct cdbdata which it is the caller's responsibility to free later on
+ * using the cdb_free() routine.
+ */
+struct cdbdata *cdb_fetch(int cdb, const void *key, int keylen) {
+
+       if (keylen == 0) {              // key length zero is impossible
+               return(NULL);
+       }
+
+       struct cdbdata *tempcdb;
+       DBT dkey, dret;
+       int ret;
+
+       memset(&dkey, 0, sizeof(DBT));
+       dkey.size = keylen;
+       dkey.data = (void *) key;
+
+       if (TSD->tid != NULL) {
+               memset(&dret, 0, sizeof(DBT));
+               dret.flags = DB_DBT_MALLOC;
+               ret = dbp[cdb]->get(dbp[cdb], TSD->tid, &dkey, &dret, 0);
+       }
+       else {
+               DBC *curs;
+
+               do {
+                       memset(&dret, 0, sizeof(DBT));
+                       dret.flags = DB_DBT_MALLOC;
+                       curs = localcursor(cdb);
+                       ret = curs->c_get(curs, &dkey, &dret, DB_SET);
+                       cclose(curs);
+               } while (ret == DB_LOCK_DEADLOCK);
+       }
+
+       if ((ret != 0) && (ret != DB_NOTFOUND)) {
+               syslog(LOG_ERR, "db: cdb_fetch(%d): %s", cdb, db_strerror(ret));
+               cdb_abort();
+       }
+
+       if (ret != 0) {
+               return NULL;
+       }
+
+       tempcdb = (struct cdbdata *) malloc(sizeof(struct cdbdata));
+       if (tempcdb == NULL) {
+               syslog(LOG_ERR, "db: cdb_fetch() cannot allocate memory for tempcdb: %m");
+               cdb_abort();
+               return NULL;    /* make it easier for static analysis... */
+       }
+       else {
+               tempcdb->len = dret.size;
+               tempcdb->ptr = dret.data;
+               cdb_decompress_if_necessary(tempcdb);
+               return (tempcdb);
+       }
+}
+
+
+/*
+ * Free a cdbdata item.
+ *
+ * Note that we only free the 'ptr' portion if it is not NULL.  This allows
+ * other code to assume ownership of that memory simply by storing the
+ * pointer elsewhere and then setting 'ptr' to NULL.  cdb_free() will then
+ * avoid freeing it.
+ */
+void cdb_free(struct cdbdata *cdb) {
+       if (cdb->ptr) {
+               free(cdb->ptr);
+       }
+       free(cdb);
+}
+
+
+void cdb_close_cursor(int cdb) {
+       if (TSD->cursors[cdb] != NULL) {
+               cclose(TSD->cursors[cdb]);
+       }
+
+       TSD->cursors[cdb] = NULL;
+}
+
+
+/* 
+ * Prepare for a sequential search of an entire database.
+ * (There is guaranteed to be no more than one traversal in
+ * progress per thread at any given time.)
+ */
+void cdb_rewind(int cdb) {
+       int ret = 0;
+
+       if (TSD->cursors[cdb] != NULL) {
+               syslog(LOG_ERR, "db: cdb_rewind: must close cursor on database %d before reopening", cdb);
+               cdb_abort();
+               /* cclose(TSD->cursors[cdb]); */
+       }
+
+       /*
+        * Now initialize the cursor
+        */
+       ret = dbp[cdb]->cursor(dbp[cdb], TSD->tid, &TSD->cursors[cdb], 0);
+       if (ret) {
+               syslog(LOG_ERR, "db: cdb_rewind: db_cursor: %s", db_strerror(ret));
+               cdb_abort();
+       }
+}
+
+
+/*
+ * Fetch the next item in a sequential search.  Returns a pointer to a 
+ * cdbdata structure, or NULL if we've hit the end.
+ */
+struct cdbdata *cdb_next_item(int cdb) {
+       DBT key, data;
+       struct cdbdata *cdbret;
+       int ret = 0;
+
+       /* Initialize the key/data pair so the flags aren't set. */
+       memset(&key, 0, sizeof(key));
+       memset(&data, 0, sizeof(data));
+       data.flags = DB_DBT_MALLOC;
+
+       ret = TSD->cursors[cdb]->c_get(TSD->cursors[cdb], &key, &data, DB_NEXT);
+
+       if (ret) {
+               if (ret != DB_NOTFOUND) {
+                       syslog(LOG_ERR, "db: cdb_next_item(%d): %s", cdb, db_strerror(ret));
+                       cdb_abort();
+               }
+               cdb_close_cursor(cdb);
+               return NULL;    /* presumably, end of file */
+       }
+
+       cdbret = (struct cdbdata *) malloc(sizeof(struct cdbdata));
+       cdbret->len = data.size;
+       cdbret->ptr = data.data;
+       cdb_decompress_if_necessary(cdbret);
+
+       return (cdbret);
+}
+
+
+/*
+ * Transaction-based stuff.  I'm writing this as I bake cookies...
+ */
+void cdb_begin_transaction(void) {
+       bailIfCursor(TSD->cursors, "can't begin transaction during r/o cursor");
+
+       if (TSD->tid != NULL) {
+               syslog(LOG_ERR, "db: cdb_begin_transaction: ERROR: nested transaction");
+               cdb_abort();
+       }
+
+       txbegin(&TSD->tid);
+}
+
+
+void cdb_end_transaction(void) {
+       int i;
+
+       for (i = 0; i < MAXCDB; i++) {
+               if (TSD->cursors[i] != NULL) {
+                       syslog(LOG_WARNING, "db: cdb_end_transaction: WARNING: cursor %d still open at transaction end", i);
+                       cclose(TSD->cursors[i]);
+                       TSD->cursors[i] = NULL;
+               }
+       }
+
+       if (TSD->tid == NULL) {
+               syslog(LOG_ERR, "db: cdb_end_transaction: ERROR: txcommit(NULL) !!");
+               cdb_abort();
+       }
+       else {
+               txcommit(TSD->tid);
+       }
+
+       TSD->tid = NULL;
+}
+
+
+/*
+ * Truncate (delete every record)
+ */
+void cdb_trunc(int cdb) {
+       /* DB_TXN *tid; */
+       int ret;
+       u_int32_t count;
+
+       if (TSD->tid != NULL) {
+               syslog(LOG_ERR, "db: cdb_trunc must not be called in a transaction.");
+               cdb_abort();
+       }
+       else {
+               bailIfCursor(TSD->cursors, "attempt to write during r/o cursor");
+
+             retry:
+               /* txbegin(&tid); */
+
+               if ((ret = dbp[cdb]->truncate(dbp[cdb], /* db */
+                                             NULL,     /* transaction ID */
+                                             &count,   /* #rows deleted */
+                                             0))) {    /* flags */
+                       if (ret == DB_LOCK_DEADLOCK) {
+                               /* txabort(tid); */
+                               goto retry;
+                       } else {
+                               syslog(LOG_ERR, "db: cdb_truncate(%d): %s", cdb, db_strerror(ret));
+                               if (ret == ENOMEM) {
+                                       syslog(LOG_ERR, "db: You may need to tune your database; please read http://www.citadel.org/doku.php?id=faq:troubleshooting:out_of_lock_entries for more information.");
+                               }
+                               exit(CTDLEXIT_DB);
+                       }
+               }
+               else {
+                       /* txcommit(tid); */
+               }
+       }
+}
+
+
+// compact (defragment) the database, possibly returning space back to the underlying filesystem
+void cdb_compact(void) {
+       int ret;
+       int i;
+
+       syslog(LOG_DEBUG, "db: cdb_compact() started");
+       for (i = 0; i < MAXCDB; i++) {
+               syslog(LOG_DEBUG, "db: compacting database %d", i);
+               ret = dbp[i]->compact(dbp[i], NULL, NULL, NULL, NULL, DB_FREE_SPACE, NULL);
+               if (ret) {
+                       syslog(LOG_ERR, "db: compact: %s", db_strerror(ret));
+               }
+       }
+       syslog(LOG_DEBUG, "db: cdb_compact() finished");
+}
+
+
+// Has an item already been seen (is it in the CDB_USETABLE) ?
+// Returns 0 if it hasn't, 1 if it has
+// In either case, writes the item to the database for next time.
+int CheckIfAlreadySeen(StrBuf *guid) {
+       int found = 0;
+       struct UseTable ut;
+       struct cdbdata *cdbut;
+
+       syslog(LOG_DEBUG, "db: CheckIfAlreadySeen(%s)", ChrPtr(guid));
+       cdbut = cdb_fetch(CDB_USETABLE, SKEY(guid));
+       if (cdbut != NULL) {
+               found = 1;
+               cdb_free(cdbut);
+       }
+
+       /* (Re)write the record, to update the timestamp.  Zeroing it out makes it compress better. */
+       memset(&ut, 0, sizeof(struct UseTable));
+       memcpy(ut.ut_msgid, SKEY(guid));
+       ut.ut_timestamp = time(NULL);
+       cdb_store(CDB_USETABLE, SKEY(guid), &ut, sizeof(struct UseTable));
+       return (found);
+}
+
+
+char *ctdl_module_init_database() {
+       if (!threading) {
+               // nothing to do here
+       }
+
+       /* return our module id for the log */
+       return "database";
+}
diff --git a/citadel/server/database.h b/citadel/server/database.h
new file mode 100644 (file)
index 0000000..b9b76b7
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 1987-2017 by the citadel.org team
+ *
+ *  This program is open source software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 3.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#ifndef DATABASE_H
+#define DATABASE_H
+
+
+void open_databases (void);
+void close_databases (void);
+int cdb_store (int cdb, const void *key, int keylen, void *data, int datalen);
+int cdb_delete (int cdb, void *key, int keylen);
+struct cdbdata *cdb_fetch (int cdb, const void *key, int keylen);
+void cdb_free (struct cdbdata *cdb);
+void cdb_rewind (int cdb);
+struct cdbdata *cdb_next_item (int cdb);
+void cdb_close_cursor(int cdb);
+void cdb_begin_transaction(void);
+void cdb_end_transaction(void);
+void cdb_allocate_tsd(void);
+void cdb_free_tsd(void);
+void cdb_check_handles(void);
+void cdb_trunc(int cdb);
+void *checkpoint_thread(void *arg);
+void cdb_chmod_data(void);
+void cdb_checkpoint(void);
+void check_handles(void *arg);
+void cdb_cull_logs(void);
+void cdb_compact(void);
+
+
+/*
+ * Database records beginning with this magic number are assumed to
+ * be compressed.  In the event that a database record actually begins with
+ * this magic number, we *must* compress it whether we want to or not,
+ * because the fetch function will try to uncompress it anyway.
+ * 
+ * (No need to #ifdef this stuff; it compiles ok even if zlib is not present
+ * and doesn't declare anything so it won't bloat the code)
+ */
+#define COMPRESS_MAGIC 0xc0ffeeee
+
+struct CtdlCompressHeader {
+       int magic;
+       size_t uncompressed_len;
+       size_t compressed_len;
+};
+
+int CheckIfAlreadySeen(StrBuf *guid);
+
+#endif /* DATABASE_H */
+
diff --git a/citadel/server/default_timezone.c b/citadel/server/default_timezone.c
new file mode 100644 (file)
index 0000000..6ce5c10
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 1987-2018 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include <libical/ical.h>
+#include <libcitadel.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "sysdep_decls.h"
+#include "support.h"
+#include "config.h"
+#include "default_timezone.h"
+#include "ctdl_module.h"
+
+
+/*
+ * Figure out which time zone needs to be used for timestamps that are
+ * not UTC and do not have a time zone specified.
+ */
+icaltimezone *get_default_icaltimezone(void) {
+
+        icaltimezone *zone = NULL;
+       char *default_zone_name = CtdlGetConfigStr("c_default_cal_zone");
+
+        if (!zone) {
+                zone = icaltimezone_get_builtin_timezone(default_zone_name);
+        }
+        if (!zone) {
+               syslog(LOG_ERR, "ical: Unable to load '%s' time zone.  Defaulting to UTC.", default_zone_name);
+                zone = icaltimezone_get_utc_timezone();
+       }
+       if (!zone) {
+               syslog(LOG_ERR, "ical: unable to load UTC time zone!");
+       }
+
+        return zone;
+}
diff --git a/citadel/server/default_timezone.h b/citadel/server/default_timezone.h
new file mode 100644 (file)
index 0000000..869c70d
--- /dev/null
@@ -0,0 +1 @@
+icaltimezone *get_default_icaltimezone(void);
diff --git a/citadel/server/domain.c b/citadel/server/domain.c
new file mode 100644 (file)
index 0000000..79461f5
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * DNS lookup for SMTP sender
+ *
+ * Copyright (c) 1987-2021 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "sysdep.h"
+#include <stdio.h>
+#include <syslog.h>
+#ifdef HAVE_RESOLV_H
+#include <arpa/nameser.h>
+#ifdef HAVE_ARPA_NAMESER_COMPAT_H
+#include <arpa/nameser_compat.h>
+#endif
+#ifdef __FreeBSD__
+#include <netinet/in.h>
+#endif
+#include <resolv.h>
+#endif
+#include <libcitadel.h>
+#include "sysdep_decls.h"
+#include "citadel.h"
+#include "domain.h"
+#include "internet_addressing.h"
+
+
+/*
+ * get_hosts() checks the Internet configuration for various types of
+ * entries and returns them in the same format as getmx() does -- fill the
+ * buffer with a delimited list of hosts and return the number of hosts.
+ * 
+ * This is used to fetch MX smarthosts, SpamAssassin hosts, etc.
+ */
+int get_hosts(char *mxbuf, char *rectype) {
+       int config_lines;
+       int i;
+       char buf[256];
+       char host[256], type[256];
+       int total_smarthosts = 0;
+
+       if (inetcfg == NULL) return(0);
+       strcpy(mxbuf, "");
+
+       config_lines = num_tokens(inetcfg, '\n');
+       for (i=0; i<config_lines; ++i) {
+               extract_token(buf, inetcfg, i, '\n', sizeof buf);
+               extract_token(host, buf, 0, '|', sizeof host);
+               extract_token(type, buf, 1, '|', sizeof type);
+
+               if (!strcasecmp(type, rectype)) {
+                       strcat(mxbuf, host);
+                       strcat(mxbuf, "|");
+                       ++total_smarthosts;
+               }
+       }
+
+       return(total_smarthosts);
+}
+
+
+/*
+ * Compare the preference of two MX records.  First check by the actual
+ * number listed in the MX record.  If they're identical, randomize the
+ * result.
+ */
+int mx_compare_pref(const void *mx1, const void *mx2) {
+       int pref1;
+       int pref2;
+
+       pref1 = ((const struct mx *)mx1)->pref;
+       pref2 = ((const struct mx *)mx2)->pref;
+
+       if (pref1 > pref2) {
+               return(1);
+       }
+       else if (pref1 < pref2) {
+               return(0);
+       }
+       else {
+               return(rand() % 2);
+       }
+}
+
+
+/* 
+ * getmx()
+ *
+ * Return one or more MX's for a mail destination.
+ *
+ * Upon success, it fills 'mxbuf' with one or more MX hosts, separated by
+ * vertical bar characters, and returns the number of hosts as its return
+ * value.  If no MX's are found, it returns 0.
+ *
+ */
+int getmx(char *mxbuf, char *dest) {
+
+#ifdef HAVE_RESOLV_H
+       union {
+                       u_char bytes[1024];
+                       HEADER header;
+    } answer;
+#endif
+
+       int ret;
+       unsigned char *startptr, *endptr, *ptr;
+       char expanded_buf[1024];
+       unsigned short pref, type;
+       int n = 0;
+       int qdcount;
+       Array *mxrecords = NULL;
+       struct mx mx;
+
+       // If we're configured to send all mail to a smart-host, then our job here is really easy -- just return those.
+       n = get_hosts(mxbuf, "smarthost");
+       if (n > 0) {
+               return(n);
+       }
+
+       mxrecords = array_new(sizeof(struct mx));
+
+       // No smart-host?  Look up the best MX for a site.  Make a call to the resolver library.
+       ret = res_query(dest, C_IN, T_MX, (unsigned char *)answer.bytes, sizeof(answer));
+
+       if (ret < 0) {
+               mx.pref = 0;
+               strcpy(mx.host, dest);
+               array_append(mxrecords, &mx);
+       }
+       else {
+               if (ret > sizeof(answer)) {             // If we had to truncate, shrink the number to avoid fireworks
+                       ret = sizeof(answer);
+               }
+
+               startptr = &answer.bytes[0];            // start and end of buffer
+               endptr = &answer.bytes[ret];
+               ptr = startptr + HFIXEDSZ;              // advance past header
+       
+               for (qdcount = ntohs(answer.header.qdcount); qdcount--; ptr += ret + QFIXEDSZ) {
+                       if ((ret = dn_skipname(ptr, endptr)) < 0) {
+                               syslog(LOG_DEBUG, "domain: dn_skipname error");
+                               return(0);
+                       }
+               }
+       
+               while(1) {
+                       memset(expanded_buf, 0, sizeof(expanded_buf));
+                       ret = dn_expand(startptr, endptr, ptr, expanded_buf, sizeof(expanded_buf));
+                       if (ret < 0) break;
+                       ptr += ret;
+       
+                       GETSHORT(type, ptr);
+                       ptr += INT16SZ + INT32SZ;
+                       GETSHORT(n, ptr);
+       
+                       if (type != T_MX) {
+                               ptr += n;
+                       }
+       
+                       else {
+                               GETSHORT(pref, ptr);
+                               ret = dn_expand(startptr, endptr, ptr, expanded_buf, sizeof(expanded_buf));
+                               ptr += ret;
+       
+                               mx.pref = pref;
+                               strcpy(mx.host, expanded_buf);
+                               array_append(mxrecords, &mx);
+                       }
+               }
+       }
+
+       // Sort the MX records by preference
+       if (array_len(mxrecords) > 1) {
+               array_sort(mxrecords, mx_compare_pref);
+       }
+
+       int num_mxrecs = array_len(mxrecords);
+       strcpy(mxbuf, "");
+       for (n=0; n<num_mxrecs; ++n) {
+               strcat(mxbuf, ((struct mx *)array_get_element_at(mxrecords, n))->host);
+               strcat(mxbuf, "|");
+       }
+       array_free(mxrecords);
+
+       // Append any fallback smart hosts we have configured.
+       num_mxrecs += get_hosts(&mxbuf[strlen(mxbuf)], "fallbackhost");
+       return(num_mxrecs);
+}
diff --git a/citadel/server/domain.h b/citadel/server/domain.h
new file mode 100644 (file)
index 0000000..ba36759
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 1987-2012 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+struct mx {
+       int pref;
+       char host[1024];
+};
+
+int getmx(char *mxbuf, char *dest);
+int get_hosts(char *mxbuf, char *rectype);
+
+
+/* HP/UX has old include files...these are from arpa/nameser.h */
+
+#include "typesize.h"
+
+#ifndef HFIXEDSZ
+#define HFIXEDSZ       12              /* I hope! */
+#endif
+#ifndef INT16SZ
+#define        INT16SZ         sizeof(cit_int16_t)
+#endif
+#ifndef INT32SZ
+#define INT32SZ                sizeof(cit_int32_t)
+#endif
diff --git a/citadel/server/euidindex.c b/citadel/server/euidindex.c
new file mode 100644 (file)
index 0000000..0e8daf9
--- /dev/null
@@ -0,0 +1,213 @@
+// Index messages by EUID per room.
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "sysdep.h"
+#include <stdio.h>
+#include <libcitadel.h>
+
+#include "citserver.h"
+#include "room_ops.h"
+
+// The structure of an euidindex record *key* is:
+//
+// |----room_number----|----------EUID-------------|
+//    (sizeof long)       (actual length of euid)
+//
+//
+// The structure of an euidindex record *value* is:
+//
+// |-----msg_number----|----room_number----|----------EUID-------------|
+//    (sizeof long)       (sizeof long)       (actual length of euid)
+
+// Return nonzero if the supplied room is one which should have
+// an EUID index.
+int DoesThisRoomNeedEuidIndexing(struct ctdlroom *qrbuf) {
+
+       switch(qrbuf->QRdefaultview) {
+               case VIEW_BBS:          return(0);
+               case VIEW_MAILBOX:      return(0);
+               case VIEW_ADDRESSBOOK:  return(1);
+               case VIEW_DRAFTS:       return(0);
+               case VIEW_CALENDAR:     return(1);
+               case VIEW_TASKS:        return(1);
+               case VIEW_NOTES:        return(1);
+               case VIEW_WIKI:         return(1);
+               case VIEW_BLOG:         return(1);
+       }
+       
+       return(0);
+}
+
+
+// Locate a message in a given room with a given euid, and return
+// its message number.
+long locate_message_by_euid(char *euid, struct ctdlroom *qrbuf) {
+       return CtdlLocateMessageByEuid (euid, qrbuf);
+}
+
+
+long CtdlLocateMessageByEuid(char *euid, struct ctdlroom *qrbuf) {
+       char *key;
+       int key_len;
+       struct cdbdata *cdb_euid;
+       long msgnum = (-1L);
+
+       syslog(LOG_DEBUG, "euidindex: searching for EUID <%s> in <%s>", euid, qrbuf->QRname);
+
+       key_len = strlen(euid) + sizeof(long) + 1;
+       key = malloc(key_len);
+       memcpy(key, &qrbuf->QRnumber, sizeof(long));
+       strcpy(&key[sizeof(long)], euid);
+
+       cdb_euid = cdb_fetch(CDB_EUIDINDEX, key, key_len);
+       free(key);
+
+       if (cdb_euid == NULL) {
+               msgnum = (-1L);
+       }
+       else {
+               // The first (sizeof long) of the record is what we're looking for.  Throw away the rest.
+               memcpy(&msgnum, cdb_euid->ptr, sizeof(long));
+               cdb_free(cdb_euid);
+       }
+       syslog(LOG_DEBUG, "euidindex: returning msgnum = %ld", msgnum);
+       return(msgnum);
+}
+
+
+// Store the euid index for a message, which has presumably just been
+// stored in this room by the caller.
+void index_message_by_euid(char *euid, struct ctdlroom *qrbuf, long msgnum) {
+       char *key;
+       int key_len;
+       char *data;
+       int data_len;
+
+       syslog(LOG_DEBUG, "euidindex: indexing message #%ld <%s> in <%s>", msgnum, euid, qrbuf->QRname);
+
+       key_len = strlen(euid) + sizeof(long) + 1;
+       key = malloc(key_len);
+       memcpy(key, &qrbuf->QRnumber, sizeof(long));
+       strcpy(&key[sizeof(long)], euid);
+
+       data_len = sizeof(long) + key_len;
+       data = malloc(data_len);
+
+       memcpy(data, &msgnum, sizeof(long));
+       memcpy(&data[sizeof(long)], key, key_len);
+
+       cdb_store(CDB_EUIDINDEX, key, key_len, data, data_len);
+       free(key);
+       free(data);
+}
+
+
+// Called by rebuild_euid_index_for_room() to index one message.
+void rebuild_euid_index_for_msg(long msgnum, void *userdata) {
+       struct CtdlMessage *msg = NULL;
+
+       msg = CtdlFetchMessage(msgnum, 0);
+       if (msg == NULL) return;
+       if (!CM_IsEmpty(msg, eExclusiveID)) {
+               index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgnum);
+       }
+       CM_Free(msg);
+}
+
+
+void rebuild_euid_index_for_room(struct ctdlroom *qrbuf, void *data) {
+       static struct RoomProcList *rplist = NULL;
+       struct RoomProcList *ptr;
+       struct ctdlroom qr;
+
+       // Lazy programming here.  Call this function as a CtdlForEachRoom backend
+       // in order to queue up the room names, or call it with a null room
+       // to make it do the processing.
+       if (qrbuf != NULL) {
+               ptr = (struct RoomProcList *)
+                       malloc(sizeof (struct RoomProcList));
+               if (ptr == NULL) return;
+
+               safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
+               ptr->next = rplist;
+               rplist = ptr;
+               return;
+       }
+
+       while (rplist != NULL) {
+               if (CtdlGetRoom(&qr, rplist->name) == 0) {
+                       if (DoesThisRoomNeedEuidIndexing(&qr)) {
+                               syslog(LOG_DEBUG,
+                                       "euidindex: rebuilding EUID index for <%s>",
+                                       rplist->name);
+                               CtdlUserGoto(rplist->name, 0, 0, NULL, NULL, NULL, NULL);
+                               CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, rebuild_euid_index_for_msg, NULL);
+                       }
+               }
+               ptr = rplist;
+               rplist = rplist->next;
+               free(ptr);
+       }
+}
+
+
+// Globally rebuild the EUID indices in every room.
+void rebuild_euid_index(void) {
+       cdb_trunc(CDB_EUIDINDEX);                               // delete the old indices
+       CtdlForEachRoom(rebuild_euid_index_for_room, NULL);     // enumerate room names
+       rebuild_euid_index_for_room(NULL, NULL);                // and index them
+}
+
+
+// Server command to fetch a message number given an euid.
+void cmd_euid(char *cmdbuf) {
+       char euid[256];
+       long msgnum;
+        struct cdbdata *cdbfr;
+        long *msglist = NULL;
+        int num_msgs = 0;
+       int i;
+
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+       extract_token(euid, cmdbuf, 0, '|', sizeof euid);
+       msgnum = CtdlLocateMessageByEuid(euid, &CC->room);
+       if (msgnum <= 0L) {
+               cprintf("%d not found\n", ERROR + MESSAGE_NOT_FOUND);
+               return;
+       }
+
+        cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+       if (cdbfr != NULL) {
+                num_msgs = cdbfr->len / sizeof(long);
+                msglist = (long *) cdbfr->ptr;
+                for (i = 0; i < num_msgs; ++i) {
+                        if (msglist[i] == msgnum) {
+                               cdb_free(cdbfr);
+                               cprintf("%d %ld\n", CIT_OK, msgnum);
+                               return;
+                       }
+               }
+                cdb_free(cdbfr);
+       }
+
+       cprintf("%d not found\n", ERROR + MESSAGE_NOT_FOUND);
+}
+
+
+char *ctdl_module_init_euidindex(void) {
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_euid, "EUID", "Perform operations on Extended IDs for messages");
+       }
+       /* return our id for the log */
+       return "euidindex";
+}
diff --git a/citadel/server/euidindex.h b/citadel/server/euidindex.h
new file mode 100644 (file)
index 0000000..e30f79f
--- /dev/null
@@ -0,0 +1,9 @@
+/*
+ * Index messages by EUID per room.
+ */
+
+int DoesThisRoomNeedEuidIndexing(struct ctdlroom *qrbuf);
+/* locate_message_by_euid is deprecated. Use CtdlLocateMessageByEuid instead */
+long locate_message_by_euid(char *euid, struct ctdlroom *qrbuf) __attribute__ ((deprecated));
+void index_message_by_euid(char *euid, struct ctdlroom *qrbuf, long msgnum);
+void rebuild_euid_index(void);
diff --git a/citadel/server/genstamp.c b/citadel/server/genstamp.c
new file mode 100644 (file)
index 0000000..e8d5dd8
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Function to generate RFC822-compliant textual time/date stamp
+ */
+
+#include "sysdep.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include "genstamp.h"
+
+static char *months[] = {
+       "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+       "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static char *weekdays[] = {
+       "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+/*
+ * Supplied with a unix timestamp, generate an RFC822-compliant textual
+ * time and date stamp.
+ */
+long datestring(char *buf, size_t n, time_t xtime, int which_format) {
+       struct tm t;
+
+       long offset;
+       char offsign;
+
+       localtime_r(&xtime, &t);
+
+       /* Convert "seconds west of GMT" to "hours/minutes offset" */
+#ifdef HAVE_STRUCT_TM_TM_GMTOFF
+       offset = t.tm_gmtoff;
+#else
+       offset = timezone;
+#endif
+       if (offset > 0) {
+               offsign = '+';
+       }
+       else {
+               offset = 0L - offset;
+               offsign = '-';
+       }
+       offset = ( (offset / 3600) * 100 ) + ( offset % 60 );
+
+       switch(which_format) {
+
+               case DATESTRING_RFC822:
+                       return snprintf(
+                               buf, n,
+                               "%s, %02d %s %04d %02d:%02d:%02d %c%04ld",
+                               weekdays[t.tm_wday],
+                               t.tm_mday,
+                               months[t.tm_mon],
+                               t.tm_year + 1900,
+                               t.tm_hour,
+                               t.tm_min,
+                               t.tm_sec,
+                               offsign, offset
+                               );
+               break;
+
+               case DATESTRING_IMAP:
+                       return snprintf(
+                               buf, n,
+                               "%02d-%s-%04d %02d:%02d:%02d %c%04ld",
+                               t.tm_mday,
+                               months[t.tm_mon],
+                               t.tm_year + 1900,
+                               t.tm_hour,
+                               t.tm_min,
+                               t.tm_sec,
+                               offsign, offset
+                               );
+               break;
+
+       }
+       return 0;
+}
diff --git a/citadel/server/genstamp.h b/citadel/server/genstamp.h
new file mode 100644 (file)
index 0000000..ffe0d9c
--- /dev/null
@@ -0,0 +1,7 @@
+
+long datestring(char *buf, size_t n, time_t xtime, int which_format);
+
+enum {
+       DATESTRING_RFC822,
+       DATESTRING_IMAP
+};
diff --git a/citadel/server/housekeeping.c b/citadel/server/housekeeping.c
new file mode 100644 (file)
index 0000000..54cfe11
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * This file contains miscellaneous housekeeping tasks.
+ *
+ * Copyright (c) 1987-2021 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <libcitadel.h>
+
+#include "ctdl_module.h"
+#include "serv_extensions.h"
+#include "room_ops.h"
+#include "internet_addressing.h"
+#include "config.h"
+#include "journaling.h"
+#include "citadel_ldap.h"
+
+void check_sched_shutdown(void) {
+       if ((ScheduledShutdown == 1) && (ContextList == NULL)) {
+               syslog(LOG_NOTICE, "housekeeping: scheduled shutdown initiating");
+               server_shutting_down = 1;
+       }
+}
+
+
+/*
+ * Check (and fix) floor reference counts.  This doesn't need to be done
+ * very often, since the counts should remain correct during normal operation.
+ */
+void check_ref_counts_backend(struct ctdlroom *qrbuf, void *data) {
+
+       int *new_refcounts;
+
+       new_refcounts = (int *) data;
+
+       ++new_refcounts[(int)qrbuf->QRfloor];
+}
+
+
+void check_ref_counts(void) {
+       struct floor flbuf;
+       int a;
+
+       int new_refcounts[MAXFLOORS];
+
+       syslog(LOG_DEBUG, "housekeeping: checking floor reference counts");
+       for (a=0; a<MAXFLOORS; ++a) {
+               new_refcounts[a] = 0;
+       }
+
+       cdb_begin_transaction();
+       CtdlForEachRoom(check_ref_counts_backend, (void *)new_refcounts );
+       cdb_end_transaction();
+
+       for (a=0; a<MAXFLOORS; ++a) {
+               lgetfloor(&flbuf, a);
+               flbuf.f_ref_count = new_refcounts[a];
+               if (new_refcounts[a] > 0) {
+                       flbuf.f_flags = flbuf.f_flags | QR_INUSE;
+               }
+               else {
+                       flbuf.f_flags = flbuf.f_flags & ~QR_INUSE;
+               }
+               lputfloor(&flbuf, a);
+               syslog(LOG_DEBUG, "housekeeping: floor %d has %d rooms", a, new_refcounts[a]);
+       }
+}
+
+
+/*
+ * Provide hints as to whether we have any memory leaks
+ */
+void keep_an_eye_on_memory_usage(void) {
+       static void *original_brk = NULL;
+       if (!original_brk) original_brk = sbrk(0);      // Remember the original program break so we can test for leaks
+       syslog(LOG_DEBUG, "original_brk=%lx, current_brk=%lx, addl=%ld", (long)original_brk, (long)sbrk(0), (long)(sbrk(0)-original_brk));      // FIXME not so noisy please
+}
+
+
+/*
+ * This is the housekeeping loop.  Worker threads come through here after
+ * processing client requests but before jumping back into the pool.  We
+ * only allow housekeeping to execute once per minute, and we only allow one
+ * instance to run at a time.
+ */
+static int housekeeping_in_progress = 0;
+static int housekeeping_disabled = 0;
+static time_t last_timer = 0L;
+
+void do_housekeeping(void) {
+       int do_housekeeping_now = 0;
+       int do_perminute_housekeeping_now = 0;
+       time_t now;
+
+       if (housekeeping_disabled) {
+               return;
+       }
+
+       /*
+        * We do it this way instead of wrapping the whole loop in an
+        * S_HOUSEKEEPING critical section because it eliminates the need to
+        * potentially have multiple concurrent mutexes in progress.
+        */
+       begin_critical_section(S_HOUSEKEEPING);
+       if (housekeeping_in_progress == 0) {
+               do_housekeeping_now = 1;
+               housekeeping_in_progress = 1;
+       }
+       end_critical_section(S_HOUSEKEEPING);
+
+       now = time(NULL);
+       if ( (do_housekeeping_now == 0) && (!CtdlIsSingleUser()) ) {
+               if ( (now - last_timer) > (time_t)300 ) {
+                       syslog(LOG_WARNING,
+                               "housekeeping: WARNING: housekeeping loop has not run for %ld minutes.  Is something stuck?",
+                               ((now - last_timer) / 60)
+                       );
+               }
+               return;
+       }
+
+       /*
+        * Ok, at this point we've made the decision to run the housekeeping
+        * loop.  Everything below this point is real work.
+        */
+
+       if ( (now - last_timer) > (time_t)60 ) {
+               do_perminute_housekeeping_now = 1;
+               last_timer = time(NULL);
+       }
+
+       /* First, do the "as often as needed" stuff... */
+       JournalRunQueue();
+       PerformSessionHooks(EVT_HOUSE);
+
+       /* Then, do the "once per minute" stuff... */
+       if (do_perminute_housekeeping_now) {
+               cdb_check_handles();
+               PerformSessionHooks(EVT_TIMER);         // Run all registered TIMER hooks
+
+               // LDAP sync isn't in a module so we can put it here
+               static time_t last_ldap_sync = 0L;
+               if ( (now - last_ldap_sync) > (time_t)CtdlGetConfigLong("c_ldap_sync_freq") ) {
+                       CtdlSynchronizeUsersFromLDAP();
+                       last_ldap_sync = time(NULL);
+               }
+
+       keep_an_eye_on_memory_usage();
+       }
+
+       /*
+        * All done.
+        */
+       begin_critical_section(S_HOUSEKEEPING);
+       housekeeping_in_progress = 0;
+       end_critical_section(S_HOUSEKEEPING);
+}
+
+
+void CtdlDisableHouseKeeping(void) {
+       syslog(LOG_INFO, "housekeeping: trying to disable");
+       while ( (!housekeeping_disabled) && (!server_shutting_down) && (!housekeeping_in_progress) ) {
+
+               if (housekeeping_in_progress) {
+                       sleep(1);
+               }
+               else {
+                       begin_critical_section(S_HOUSEKEEPING);
+                       if (!housekeeping_in_progress) {
+                               housekeeping_disabled = 1;
+                       }
+                       end_critical_section(S_HOUSEKEEPING);
+               }
+       }
+       syslog(LOG_INFO, "housekeeping: disabled now");
+}
+
+
+void CtdlEnableHouseKeeping(void) {
+       begin_critical_section(S_HOUSEKEEPING);
+       housekeeping_in_progress = 0;
+       end_critical_section(S_HOUSEKEEPING);
+}
diff --git a/citadel/server/housekeeping.h b/citadel/server/housekeeping.h
new file mode 100644 (file)
index 0000000..90ee12a
--- /dev/null
@@ -0,0 +1,3 @@
+void check_sched_shutdown(void);
+void check_ref_counts(void);
+void do_housekeeping(void);
diff --git a/citadel/server/internet_addressing.c b/citadel/server/internet_addressing.c
new file mode 100644 (file)
index 0000000..59e078f
--- /dev/null
@@ -0,0 +1,1720 @@
+// This file contains functions which handle the mapping of Internet addresses
+// to users on the Citadel system.
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "citadel.h"
+#include "server.h"
+#include "sysdep_decls.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "msgbase.h"
+#include "internet_addressing.h"
+#include "user_ops.h"
+#include "room_ops.h"
+#include "parsedate.h"
+#include "database.h"
+#include "ctdl_module.h"
+#ifdef HAVE_ICONV
+#include <iconv.h>
+
+// This is the non-define version in case it is needed for debugging
+#if 0
+inline void FindNextEnd (char *bptr, char *end)
+{
+       /* Find the next ?Q? */
+       end = strchr(bptr + 2, '?');
+       if (end == NULL) return NULL;
+       if (((*(end + 1) == 'B') || (*(end + 1) == 'Q')) && 
+           (*(end + 2) == '?')) {
+               /* skip on to the end of the cluster, the next ?= */
+               end = strstr(end + 3, "?=");
+       }
+       else
+               /* sort of half valid encoding, try to find an end. */
+               end = strstr(bptr, "?=");
+}
+#endif
+
+#define FindNextEnd(bptr, end) { \
+       end = strchr(bptr + 2, '?'); \
+       if (end != NULL) { \
+               if (((*(end + 1) == 'B') || (*(end + 1) == 'Q')) && (*(end + 2) == '?')) { \
+                       end = strstr(end + 3, "?="); \
+               } else end = strstr(bptr, "?="); \
+       } \
+}
+
+// Handle subjects with RFC2047 encoding such as:
+// =?koi8-r?B?78bP0s3Mxc7JxSDXz9rE1dvO2c3JINvB0sHNySDP?=
+void utf8ify_rfc822_string(char *buf) {
+       char *start, *end, *next, *nextend, *ptr;
+       char newbuf[1024];
+       char charset[128];
+       char encoding[16];
+       char istr[1024];
+       iconv_t ic = (iconv_t)(-1) ;
+       char *ibuf;                     // Buffer of characters to be converted
+       char *obuf;                     // Buffer for converted characters
+       size_t ibuflen;                 // Length of input buffer
+       size_t obuflen;                 // Length of output buffer
+       char *isav;                     // Saved pointer to input buffer
+       char *osav;                     // Saved pointer to output buffer
+       int passes = 0;
+       int i, len, delta;
+       int illegal_non_rfc2047_encoding = 0;
+
+       // Sometimes, badly formed messages contain strings which were simply
+       // written out directly in some foreign character set instead of
+       // using RFC2047 encoding.  This is illegal but we will attempt to
+       // handle it anyway by converting from a user-specified default
+       // charset to UTF-8 if we see any nonprintable characters.
+       len = strlen(buf);
+       for (i=0; i<len; ++i) {
+               if ((buf[i] < 32) || (buf[i] > 126)) {
+                       illegal_non_rfc2047_encoding = 1;
+                       i = len;        // take a shortcut, it won't be more than one.
+               }
+       }
+       if (illegal_non_rfc2047_encoding) {
+               const char *default_header_charset = "iso-8859-1";
+               if ( (strcasecmp(default_header_charset, "UTF-8")) && (strcasecmp(default_header_charset, "us-ascii")) ) {
+                       ctdl_iconv_open("UTF-8", default_header_charset, &ic);
+                       if (ic != (iconv_t)(-1) ) {
+                               ibuf = malloc(1024);
+                               isav = ibuf;
+                               safestrncpy(ibuf, buf, 1024);
+                               ibuflen = strlen(ibuf);
+                               obuflen = 1024;
+                               obuf = (char *) malloc(obuflen);
+                               osav = obuf;
+                               iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
+                               osav[1024-obuflen] = 0;
+                               strcpy(buf, osav);
+                               free(osav);
+                               iconv_close(ic);
+                               free(isav);
+                       }
+               }
+       }
+
+       // pre evaluate the first pair
+       nextend = end = NULL;
+       len = strlen(buf);
+       start = strstr(buf, "=?");
+       if (start != NULL) 
+               FindNextEnd (start, end);
+
+       while ((start != NULL) && (end != NULL)) {
+               next = strstr(end, "=?");
+               if (next != NULL)
+                       FindNextEnd(next, nextend);
+               if (nextend == NULL)
+                       next = NULL;
+
+               // did we find two partitions
+               if ((next != NULL) && ((next - end) > 2)) {
+                       ptr = end + 2;
+                       while ((ptr < next) && 
+                              (isspace(*ptr) ||
+                               (*ptr == '\r') ||
+                               (*ptr == '\n') || 
+                               (*ptr == '\t')))
+                               ptr ++;
+                       // did we find a gab just filled with blanks?
+                       if (ptr == next) {
+                               memmove(end + 2, next, len - (next - start));
+
+                               // now terminate the gab at the end
+                               delta = (next - end) - 2;
+                               len -= delta;
+                               buf[len] = '\0';
+
+                               // move next to its new location.
+                               next -= delta;
+                               nextend -= delta;
+                       }
+               }
+               // our next-pair is our new first pair now.
+               start = next;
+               end = nextend;
+       }
+
+       // Now we handle foreign character sets properly encoded in RFC2047 format.
+       start = strstr(buf, "=?");
+       FindNextEnd((start != NULL)? start : buf, end);
+       while (start != NULL && end != NULL && end > start) {
+               extract_token(charset, start, 1, '?', sizeof charset);
+               extract_token(encoding, start, 2, '?', sizeof encoding);
+               extract_token(istr, start, 3, '?', sizeof istr);
+
+               ibuf = malloc(1024);
+               isav = ibuf;
+               if (!strcasecmp(encoding, "B")) {       // base64
+                       ibuflen = CtdlDecodeBase64(ibuf, istr, strlen(istr));
+               }
+               else if (!strcasecmp(encoding, "Q")) {  // quoted-printable
+                       size_t len;
+                       unsigned long pos;
+                       
+                       len = strlen(istr);
+                       pos = 0;
+                       while (pos < len) {
+                               if (istr[pos] == '_') istr[pos] = ' ';
+                               pos++;
+                       }
+                       ibuflen = CtdlDecodeQuotedPrintable(ibuf, istr, len);
+               }
+               else {
+                       strcpy(ibuf, istr);             // unknown encoding
+                       ibuflen = strlen(istr);
+               }
+
+               ctdl_iconv_open("UTF-8", charset, &ic);
+               if (ic != (iconv_t)(-1) ) {
+                       obuflen = 1024;
+                       obuf = (char *) malloc(obuflen);
+                       osav = obuf;
+                       iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
+                       osav[1024-obuflen] = 0;
+
+                       end = start;
+                       end++;
+                       strcpy(start, "");
+                       remove_token(end, 0, '?');
+                       remove_token(end, 0, '?');
+                       remove_token(end, 0, '?');
+                       remove_token(end, 0, '?');
+                       strcpy(end, &end[1]);
+
+                       snprintf(newbuf, sizeof newbuf, "%s%s%s", buf, osav, end);
+                       strcpy(buf, newbuf);
+                       free(osav);
+                       iconv_close(ic);
+               }
+               else {
+                       end = start;
+                       end++;
+                       strcpy(start, "");
+                       remove_token(end, 0, '?');
+                       remove_token(end, 0, '?');
+                       remove_token(end, 0, '?');
+                       remove_token(end, 0, '?');
+                       strcpy(end, &end[1]);
+
+                       snprintf(newbuf, sizeof newbuf, "%s(unreadable)%s", buf, end);
+                       strcpy(buf, newbuf);
+               }
+
+               free(isav);
+
+               // Since spammers will go to all sorts of absurd lengths to get their
+               // messages through, there are LOTS of corrupt headers out there.
+               // So, prevent a really badly formed RFC2047 header from throwing
+               // this function into an infinite loop.
+               ++passes;
+               if (passes > 20) return;
+
+               start = strstr(buf, "=?");
+               FindNextEnd((start != NULL)? start : buf, end);
+       }
+
+}
+#else
+inline void utf8ify_rfc822_string(char *a){};
+
+#endif
+
+
+char *inetcfg = NULL;
+
+// Return nonzero if the supplied name is an alias for this host.
+int CtdlHostAlias(char *fqdn) {
+       int config_lines;
+       int i;
+       char buf[256];
+       char host[256], type[256];
+       int found = 0;
+
+       if (fqdn == NULL)                                       return(hostalias_nomatch);
+       if (IsEmptyStr(fqdn))                                   return(hostalias_nomatch);
+       if (!strcasecmp(fqdn, "localhost"))                     return(hostalias_localhost);
+       if (!strcasecmp(fqdn, CtdlGetConfigStr("c_fqdn")))      return(hostalias_localhost);
+       if (!strcasecmp(fqdn, CtdlGetConfigStr("c_nodename")))  return(hostalias_localhost);
+       if (inetcfg == NULL)                                    return(hostalias_nomatch);
+
+       config_lines = num_tokens(inetcfg, '\n');
+       for (i=0; i<config_lines; ++i) {
+               extract_token(buf, inetcfg, i, '\n', sizeof buf);
+               extract_token(host, buf, 0, '|', sizeof host);
+               extract_token(type, buf, 1, '|', sizeof type);
+
+               found = 0;
+
+               // Process these in a specific order, in case there are multiple matches.
+               // We want localhost to override masq, for example.
+
+               if ( (!strcasecmp(type, "masqdomain")) && (!strcasecmp(fqdn, host))) {
+                       found = hostalias_masq;
+               }
+
+               if ( (!strcasecmp(type, "localhost")) && (!strcasecmp(fqdn, host))) {
+                       found = hostalias_localhost;
+               }
+
+               // "directory" used to be a distributed version of "localhost" but they're both the same now
+               if ( (!strcasecmp(type, "directory")) && (!strcasecmp(fqdn, host))) {
+                       found = hostalias_localhost;
+               }
+
+               if (found) return(found);
+       }
+       return(hostalias_nomatch);
+}
+
+
+// Determine whether a given Internet address belongs to the current user
+int CtdlIsMe(char *addr, int addr_buf_len) {
+       struct recptypes *recp;
+       int i;
+
+       recp = validate_recipients(addr, NULL, 0);
+       if (recp == NULL) return(0);
+
+       if (recp->num_local == 0) {
+               free_recipients(recp);
+               return(0);
+       }
+
+       for (i=0; i<recp->num_local; ++i) {
+               extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
+               if (!strcasecmp(addr, CC->user.fullname)) {
+                       free_recipients(recp);
+                       return(1);
+               }
+       }
+
+       free_recipients(recp);
+       return(0);
+}
+
+
+// If the last item in a list of recipients was truncated to a partial address,
+// remove it completely in order to avoid choking library functions.
+void sanitize_truncated_recipient(char *str) {
+       if (!str) return;
+       if (num_tokens(str, ',') < 2) return;
+
+       int len = strlen(str);
+       if (len < 900) return;
+       if (len > 998) str[998] = 0;
+
+       char *cptr = strrchr(str, ',');
+       if (!cptr) return;
+
+       char *lptr = strchr(cptr, '<');
+       char *rptr = strchr(cptr, '>');
+
+       if ( (lptr) && (rptr) && (rptr > lptr) ) return;
+
+       *cptr = 0;
+}
+
+
+// This function is self explanatory.
+// (What can I say, I'm in a weird mood today...)
+void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name) {
+       char *ptr;
+       if (!name) return;
+
+       for (ptr=name; *ptr; ++ptr) {
+               while ( (isspace(*ptr)) && (*(ptr+1)=='@') ) {
+                       strcpy(ptr, ptr+1);
+                       if (ptr > name) --ptr;
+               }
+               while ( (*ptr=='@') && (*(ptr+1)!=0) && (isspace(*(ptr+1))) ) {
+                       strcpy(ptr+1, ptr+2);
+               }
+       }
+}
+
+
+// values that can be returned by expand_aliases()
+enum {
+       EA_ERROR,               // Can't send message due to bad address
+       EA_MULTIPLE,            // Alias expanded into multiple recipients -- run me again!
+       EA_LOCAL,               // Local message, do no network processing
+       EA_INTERNET,            // Convert msg and send as Internet mail
+       EA_SKIP                 // This recipient has been invalidated -- skip it!
+};
+
+
+// Process alias and routing info for email addresses
+int expand_aliases(char *name, char *aliases) {
+       int a;
+       char aaa[SIZ];
+       int at = 0;
+
+       if (aliases) {
+               int num_aliases = num_tokens(aliases, '\n');
+               for (a=0; a<num_aliases; ++a) {
+                       extract_token(aaa, aliases, a, '\n', sizeof aaa);
+                       char *bar = strchr(aaa, '|');
+                       if (bar) {
+                               bar[0] = 0;
+                               ++bar;
+                               striplt(aaa);
+                               striplt(bar);
+                               if ( (!IsEmptyStr(aaa)) && (!strcasecmp(name, aaa)) ) {
+                                       syslog(LOG_DEBUG, "internet_addressing: global alias <%s> to <%s>", name, bar);
+                                       strcpy(name, bar);
+                               }
+                       }
+               }
+               if (strchr(name, ',')) {
+                       return(EA_MULTIPLE);
+               }
+       }
+
+       char original_name[256];                                // Now go for the regular aliases
+       safestrncpy(original_name, name, sizeof original_name);
+
+       // should these checks still be here, or maybe move them to split_recps() ?
+       striplt(name);
+       remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
+       stripallbut(name, '<', '>');
+
+       // Hit the email address directory
+       if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
+               strcpy(name, aaa);
+       }
+
+       if (strcasecmp(original_name, name)) {
+               syslog(LOG_INFO, "internet_addressing: directory alias <%s> to <%s>", original_name, name);
+       }
+
+       // Change "user @ xxx" to "user" if xxx is an alias for this host
+       for (a=0; name[a] != '\0'; ++a) {
+               if (name[a] == '@') {
+                       if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
+                               name[a] = 0;
+                               syslog(LOG_DEBUG, "internet_addressing: host is local, recipient is <%s>", name);
+                               break;
+                       }
+               }
+       }
+
+       // Is this a local or remote recipient?
+       at = haschar(name, '@');
+       if (at == 0) {
+               return(EA_LOCAL);                       // no @'s = local address
+       }
+       else if (at == 1) {
+               return(EA_INTERNET);                    // one @ = internet address
+       }
+       else {
+               return(EA_ERROR);                       // more than one @ = badly formed address
+       }
+}
+
+
+// Return a supplied list of email addresses as an array, removing superfluous information and syntax.
+// If an existing Array is supplied as "append_to" it will do so; otherwise a new Array is allocated.
+Array *split_recps(char *addresses, Array *append_to) {
+
+       if (IsEmptyStr(addresses)) {            // nothing supplied, nothing returned
+               return(NULL);
+       }
+
+       // Copy the supplied address list into our own memory space, because we are going to modify it.
+       char *a = strdup(addresses);
+       if (a == NULL) {
+               syslog(LOG_ERR, "internet_addressing: malloc() failed: %m");
+               return(NULL);
+       }
+
+       // Strip out anything in double quotes
+       char *l = NULL;
+       char *r = NULL;
+       do {
+               l = strchr(a, '\"');
+               r = strrchr(a, '\"');
+               if (r > l) {
+                       strcpy(l, r+1);
+               }
+       } while (r > l);
+
+       // Transform all qualifying delimiters to commas
+       char *t;
+       for (t=a; t[0]; ++t) {
+               if ((t[0]==';') || (t[0]=='|')) {
+                       t[0]=',';
+               }
+       }
+
+       // Tokenize the recipients into an array.  No single recipient should be larger than 256 bytes.
+       Array *recipients_array = NULL;
+       if (append_to) {
+               recipients_array = append_to;                   // Append to an existing array of recipients
+       }
+       else {
+               recipients_array = array_new(256);              // This is a new array of recipients
+       }
+
+       int num_addresses = num_tokens(a, ',');
+       int i;
+       for (i=0; i<num_addresses; ++i) {
+               char this_address[256];
+               extract_token(this_address, a, i, ',', sizeof this_address);
+               striplt(this_address);                          // strip leading and trailing whitespace
+               stripout(this_address, '(', ')');               // remove any portion in parentheses
+               stripallbut(this_address, '<', '>');            // if angle brackets are present, keep only what is inside them
+               if (!IsEmptyStr(this_address)) {
+                       array_append(recipients_array, this_address);
+               }
+       }
+
+       free(a);                                                // We don't need this buffer anymore.
+       return(recipients_array);                               // Return the completed array to the caller.
+}
+
+
+// Validate recipients, count delivery types and errors, and handle aliasing
+//
+// Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses 
+// were specified, or the number of addresses found invalid.
+//
+// Caller needs to free the result using free_recipients()
+//
+struct recptypes *validate_recipients(char *supplied_recipients, const char *RemoteIdentifier, int Flags) {
+       struct recptypes *ret;
+       char *recipients = NULL;
+       char append[SIZ];
+       long len;
+       int mailtype;
+       int invalid;
+       struct ctdluser tempUS;
+       struct ctdlroom original_room;
+       int err = 0;
+       char errmsg[SIZ];
+       char *org_recp;
+       char this_recp[256];
+
+       ret = (struct recptypes *) malloc(sizeof(struct recptypes));                    // Initialize
+       if (ret == NULL) return(NULL);
+       memset(ret, 0, sizeof(struct recptypes));                                       // set all values to null/zero
+
+       if (supplied_recipients == NULL) {
+               recipients = strdup("");
+       }
+       else {
+               recipients = strdup(supplied_recipients);
+       }
+
+       len = strlen(recipients) + 1024;                                                // allocate memory
+       ret->errormsg = malloc(len);
+       ret->recp_local = malloc(len);
+       ret->recp_internet = malloc(len);
+       ret->recp_room = malloc(len);
+       ret->display_recp = malloc(len);
+       ret->recp_orgroom = malloc(len);
+
+       ret->errormsg[0] = 0;
+       ret->recp_local[0] = 0;
+       ret->recp_internet[0] = 0;
+       ret->recp_room[0] = 0;
+       ret->recp_orgroom[0] = 0;
+       ret->display_recp[0] = 0;
+       ret->recptypes_magic = RECPTYPES_MAGIC;
+
+       Array *recp_array = split_recps(supplied_recipients, NULL);
+
+       char *aliases = CtdlGetSysConfig(GLOBAL_ALIASES);                               // First hit the Global Alias Table
+
+       int r;
+       for (r=0; (recp_array && r<array_len(recp_array)); ++r) {
+               org_recp = (char *)array_get_element_at(recp_array, r);
+               strncpy(this_recp, org_recp, sizeof this_recp);
+
+               int i;
+               for (i=0; i<3; ++i) {                                           // pass three times through the aliaser
+                       mailtype = expand_aliases(this_recp, aliases);
+       
+                       // If an alias expanded to multiple recipients, strip off those recipients and append them
+                       // to the end of the array.  This loop will hit those again when it gets there.
+                       if (mailtype == EA_MULTIPLE) {
+                               recp_array = split_recps(this_recp, recp_array);
+                       }
+               }
+
+               // This loop searches for duplicate recipients in the final list and marks them to be skipped.
+               int j;
+               for (j=0; j<r; ++j) {
+                       if (!strcasecmp(this_recp, (char *)array_get_element_at(recp_array, j) )) {
+                               mailtype = EA_SKIP;
+                       }
+               }
+
+               syslog(LOG_DEBUG, "Recipient #%d of type %d is <%s>", r, mailtype, this_recp);
+               invalid = 0;
+               errmsg[0] = 0;
+               switch(mailtype) {
+               case EA_LOCAL:                                  // There are several types of "local" recipients.
+
+                       // Old BBS conventions require mail to "sysop" to go somewhere.  Send it to the admin room.
+                       if (!strcasecmp(this_recp, "sysop")) {
+                               ++ret->num_room;
+                               strcpy(this_recp, CtdlGetConfigStr("c_aideroom"));
+                               if (!IsEmptyStr(ret->recp_room)) {
+                                       strcat(ret->recp_room, "|");
+                               }
+                               strcat(ret->recp_room, this_recp);
+                       }
+
+                       // This handles rooms which can receive posts via email.
+                       else if (!strncasecmp(this_recp, "room_", 5)) {
+                               original_room = CC->room;                               // Remember where we parked
+
+                               char mail_to_room[ROOMNAMELEN];
+                               char *m;
+                               strncpy(mail_to_room, &this_recp[5], sizeof mail_to_room);
+                               for (m = mail_to_room; *m; ++m) {
+                                       if (m[0] == '_') m[0]=' ';
+                               }
+                               if (!CtdlGetRoom(&CC->room, mail_to_room)) {            // Find the room they asked for
+
+                                       err = CtdlDoIHavePermissionToPostInThisRoom(    // check for write permissions to room
+                                               errmsg, 
+                                               sizeof errmsg, 
+                                               Flags,
+                                               0                                       // 0 means "this is not a reply"
+                                       );
+                                       if (err) {
+                                               ++ret->num_error;
+                                               invalid = 1;
+                                       } 
+                                       else {
+                                               ++ret->num_room;
+                                               if (!IsEmptyStr(ret->recp_room)) {
+                                                       strcat(ret->recp_room, "|");
+                                               }
+                                               strcat(ret->recp_room, CC->room.QRname);
+       
+                                               if (!IsEmptyStr(ret->recp_orgroom)) {
+                                                       strcat(ret->recp_orgroom, "|");
+                                               }
+                                               strcat(ret->recp_orgroom, this_recp);
+       
+                                       }
+                               }
+                               else {                                                  // no such room exists
+                                       ++ret->num_error;
+                                       invalid = 1;
+                               }
+                                               
+                               // Restore this session's original room location.
+                               CC->room = original_room;
+
+                       }
+
+                       // This handles the most common case, which is mail to a user's inbox.
+                       else if (CtdlGetUser(&tempUS, this_recp) == 0) {
+                               ++ret->num_local;
+                               strcpy(this_recp, tempUS.fullname);
+                               if (!IsEmptyStr(ret->recp_local)) {
+                                       strcat(ret->recp_local, "|");
+                               }
+                               strcat(ret->recp_local, this_recp);
+                       }
+
+                       // No match for this recipient
+                       else {
+                               ++ret->num_error;
+                               invalid = 1;
+                       }
+                       break;
+               case EA_INTERNET:
+                       // Yes, you're reading this correctly: if the target domain points back to the local system,
+                       // the address is invalid.  That's because if the address were valid, we would have
+                       // already translated it to a local address by now.
+                       if (IsDirectory(this_recp, 0)) {
+                               ++ret->num_error;
+                               invalid = 1;
+                       }
+                       else {
+                               ++ret->num_internet;
+                               if (!IsEmptyStr(ret->recp_internet)) {
+                                       strcat(ret->recp_internet, "|");
+                               }
+                               strcat(ret->recp_internet, this_recp);
+                       }
+                       break;
+               case EA_MULTIPLE:
+               case EA_SKIP:
+                       // no action required, anything in this slot has already been processed elsewhere
+                       break;
+               case EA_ERROR:
+                       ++ret->num_error;
+                       invalid = 1;
+                       break;
+               }
+               if (invalid) {
+                       if (IsEmptyStr(errmsg)) {
+                               snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
+                       }
+                       else {
+                               snprintf(append, sizeof append, "%s", errmsg);
+                       }
+                       if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
+                               if (!IsEmptyStr(ret->errormsg)) {
+                                       strcat(ret->errormsg, "; ");
+                               }
+                               strcat(ret->errormsg, append);
+                       }
+               }
+               else {
+                       if (IsEmptyStr(ret->display_recp)) {
+                               strcpy(append, this_recp);
+                       }
+                       else {
+                               snprintf(append, sizeof append, ", %s", this_recp);
+                       }
+                       if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
+                               strcat(ret->display_recp, append);
+                       }
+               }
+       }
+
+       if (aliases != NULL) {          // ok, we're done with the global alias list now
+               free(aliases);
+       }
+
+       if ( (ret->num_local + ret->num_internet + ret->num_room + ret->num_error) == 0) {
+               ret->num_error = (-1);
+               strcpy(ret->errormsg, "No recipients specified.");
+       }
+
+       syslog(LOG_DEBUG, "internet_addressing: validate_recipients() = %d local, %d room, %d SMTP, %d error",
+               ret->num_local, ret->num_room, ret->num_internet, ret->num_error
+       );
+
+       free(recipients);
+       if (recp_array) {
+               array_free(recp_array);
+       }
+
+       return(ret);
+}
+
+
+// Destructor for recptypes
+void free_recipients(struct recptypes *valid) {
+
+       if (valid == NULL) {
+               return;
+       }
+
+       if (valid->recptypes_magic != RECPTYPES_MAGIC) {
+               syslog(LOG_ERR, "internet_addressing: attempt to call free_recipients() on some other data type!");
+               abort();
+       }
+
+       if (valid->errormsg != NULL)            free(valid->errormsg);
+       if (valid->recp_local != NULL)          free(valid->recp_local);
+       if (valid->recp_internet != NULL)       free(valid->recp_internet);
+       if (valid->recp_room != NULL)           free(valid->recp_room);
+       if (valid->recp_orgroom != NULL)        free(valid->recp_orgroom);
+       if (valid->display_recp != NULL)        free(valid->display_recp);
+       if (valid->bounce_to != NULL)           free(valid->bounce_to);
+       if (valid->envelope_from != NULL)       free(valid->envelope_from);
+       if (valid->sending_room != NULL)        free(valid->sending_room);
+       free(valid);
+}
+
+
+char *qp_encode_email_addrs(char *source) {
+       char *user, *node, *name;
+       const char headerStr[] = "=?UTF-8?Q?";
+       char *Encoded;
+       char *EncodedName;
+       char *nPtr;
+       int need_to_encode = 0;
+       long SourceLen;
+       long EncodedMaxLen;
+       long nColons = 0;
+       long *AddrPtr;
+       long *AddrUtf8;
+       long nAddrPtrMax = 50;
+       long nmax;
+       int InQuotes = 0;
+       int i, n;
+
+       if (source == NULL) return source;
+       if (IsEmptyStr(source)) return source;
+       syslog(LOG_DEBUG, "internet_addressing: qp_encode_email_addrs <%s>", source);
+
+       AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
+       AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
+       memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
+       *AddrPtr = 0;
+       i = 0;
+       while (!IsEmptyStr (&source[i])) {
+               if (nColons >= nAddrPtrMax){
+                       long *ptr;
+
+                       ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
+                       memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
+                       free (AddrPtr), AddrPtr = ptr;
+
+                       ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
+                       memset(&ptr[nAddrPtrMax], 0, sizeof (long) * nAddrPtrMax);
+
+                       memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
+                       free (AddrUtf8), AddrUtf8 = ptr;
+                       nAddrPtrMax *= 2;                               
+               }
+               if (((unsigned char) source[i] < 32) || ((unsigned char) source[i] > 126)) {
+                       need_to_encode = 1;
+                       AddrUtf8[nColons] = 1;
+               }
+               if (source[i] == '"') {
+                       InQuotes = !InQuotes;
+               }
+               if (!InQuotes && source[i] == ',') {
+                       AddrPtr[nColons] = i;
+                       nColons++;
+               }
+               i++;
+       }
+       if (need_to_encode == 0) {
+               free(AddrPtr);
+               free(AddrUtf8);
+               return source;
+       }
+
+       SourceLen = i;
+       EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
+       Encoded = (char*) malloc (EncodedMaxLen);
+
+       for (i = 0; i < nColons; i++) {
+               source[AddrPtr[i]++] = '\0';
+       }
+       // TODO: if libidn, this might get larger
+       user = malloc(SourceLen + 1);
+       node = malloc(SourceLen + 1);
+       name = malloc(SourceLen + 1);
+
+       nPtr = Encoded;
+       *nPtr = '\0';
+       for (i = 0; i < nColons && nPtr != NULL; i++) {
+               nmax = EncodedMaxLen - (nPtr - Encoded);
+               if (AddrUtf8[i]) {
+                       process_rfc822_addr(&source[AddrPtr[i]], user, node, name);
+                       // TODO: libIDN here !
+                       if (IsEmptyStr(name)) {
+                               n = snprintf(nPtr, nmax, (i==0)?"%s@%s" : ",%s@%s", user, node);
+                       }
+                       else {
+                               EncodedName = rfc2047encode(name, strlen(name));                        
+                               n = snprintf(nPtr, nmax, (i==0)?"%s <%s@%s>" : ",%s <%s@%s>", EncodedName, user, node);
+                               free(EncodedName);
+                       }
+               }
+               else { 
+                       n = snprintf(nPtr, nmax, (i==0)?"%s" : ",%s", &source[AddrPtr[i]]);
+               }
+               if (n > 0 )
+                       nPtr += n;
+               else { 
+                       char *ptr, *nnPtr;
+                       ptr = (char*) malloc(EncodedMaxLen * 2);
+                       memcpy(ptr, Encoded, EncodedMaxLen);
+                       nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
+                       free(Encoded), Encoded = ptr;
+                       EncodedMaxLen *= 2;
+                       i--; // do it once more with properly lengthened buffer
+               }
+       }
+       for (i = 0; i < nColons; i++)
+               source[--AddrPtr[i]] = ',';
+
+       free(user);
+       free(node);
+       free(name);
+       free(AddrUtf8);
+       free(AddrPtr);
+       return Encoded;
+}
+
+
+// Unfold a multi-line field into a single line, removing multi-whitespaces
+void unfold_rfc822_field(char **field, char **FieldEnd) 
+{
+       int quote = 0;
+       char *pField = *field;
+       char *sField;
+       char *pFieldEnd = *FieldEnd;
+
+       while (isspace(*pField))
+               pField++;
+       // remove leading/trailing whitespace
+       ;
+
+       while (isspace(*pFieldEnd))
+               pFieldEnd --;
+
+       *FieldEnd = pFieldEnd;
+       // convert non-space whitespace to spaces, and remove double blanks
+       for (sField = *field = pField; 
+            sField < pFieldEnd; 
+            pField++, sField++)
+       {
+               if ((*sField=='\r') || (*sField=='\n'))
+               {
+                       int offset = 1;
+                       while ( ( (*(sField + offset) == '\r') || (*(sField + offset) == '\n' )) && (sField + offset < pFieldEnd) ) {
+                               offset ++;
+                       }
+                       sField += offset;
+                       *pField = *sField;
+               }
+               else {
+                       if (*sField=='\"') quote = 1 - quote;
+                       if (!quote) {
+                               if (isspace(*sField)) {
+                                       *pField = ' ';
+                                       pField++;
+                                       sField++;
+                                       
+                                       while ((sField < pFieldEnd) && 
+                                              isspace(*sField))
+                                               sField++;
+                                       *pField = *sField;
+                               }
+                               else *pField = *sField;
+                       }
+                       else *pField = *sField;
+               }
+       }
+       *pField = '\0';
+       *FieldEnd = pField - 1;
+}
+
+
+// Split an RFC822-style address into userid, host, and full name
+//
+// Note: This still handles obsolete address syntaxes such as user%node@node and ...node!user
+//       We should probably remove that.
+void process_rfc822_addr(const char *rfc822, char *user, char *node, char *name) {
+       int a;
+
+       strcpy(user, "");
+       strcpy(node, CtdlGetConfigStr("c_fqdn"));
+       strcpy(name, "");
+
+       if (rfc822 == NULL) return;
+
+       // extract full name - first, it's From minus <userid>
+       strcpy(name, rfc822);
+       stripout(name, '<', '>');
+
+       // strip anything to the left of a bang
+       while ((!IsEmptyStr(name)) && (haschar(name, '!') > 0))
+               strcpy(name, &name[1]);
+
+       // and anything to the right of a @ or %
+       for (a = 0; name[a] != '\0'; ++a) {
+               if (name[a] == '@') {
+                       name[a] = 0;
+                       break;
+               }
+               if (name[a] == '%') {
+                       name[a] = 0;
+                       break;
+               }
+       }
+
+       // but if there are parentheses, that changes the rules...
+       if ((haschar(rfc822, '(') == 1) && (haschar(rfc822, ')') == 1)) {
+               strcpy(name, rfc822);
+               stripallbut(name, '(', ')');
+       }
+
+       // but if there are a set of quotes, that supersedes everything
+       if (haschar(rfc822, 34) == 2) {
+               strcpy(name, rfc822);
+               while ((!IsEmptyStr(name)) && (name[0] != 34)) {
+                       strcpy(&name[0], &name[1]);
+               }
+               strcpy(&name[0], &name[1]);
+               for (a = 0; name[a] != '\0'; ++a)
+                       if (name[a] == 34) {
+                               name[a] = 0;
+                               break;
+                       }
+       }
+       // extract user id
+       strcpy(user, rfc822);
+
+       // first get rid of anything in parens
+       stripout(user, '(', ')');
+
+       // if there's a set of angle brackets, strip it down to that
+       if ((haschar(user, '<') == 1) && (haschar(user, '>') == 1)) {
+               stripallbut(user, '<', '>');
+       }
+
+       // strip anything to the left of a bang
+       while ((!IsEmptyStr(user)) && (haschar(user, '!') > 0))
+               strcpy(user, &user[1]);
+
+       // and anything to the right of a @ or %
+       for (a = 0; user[a] != '\0'; ++a) {
+               if (user[a] == '@') {
+                       user[a] = 0;
+                       break;
+               }
+               if (user[a] == '%') {
+                       user[a] = 0;
+                       break;
+               }
+       }
+
+
+       // extract node name
+       strcpy(node, rfc822);
+
+       // first get rid of anything in parens
+       stripout(node, '(', ')');
+
+       // if there's a set of angle brackets, strip it down to that
+       if ((haschar(node, '<') == 1) && (haschar(node, '>') == 1)) {
+               stripallbut(node, '<', '>');
+       }
+
+       // If no node specified, tack ours on instead
+       if (
+               (haschar(node, '@')==0)
+               && (haschar(node, '%')==0)
+               && (haschar(node, '!')==0)
+       ) {
+               strcpy(node, CtdlGetConfigStr("c_nodename"));
+       }
+       else {
+
+               // strip anything to the left of a @
+               while ((!IsEmptyStr(node)) && (haschar(node, '@') > 0))
+                       strcpy(node, &node[1]);
+       
+               // strip anything to the left of a %
+               while ((!IsEmptyStr(node)) && (haschar(node, '%') > 0))
+                       strcpy(node, &node[1]);
+       
+               // reduce multiple system bang paths to node!user
+               while ((!IsEmptyStr(node)) && (haschar(node, '!') > 1))
+                       strcpy(node, &node[1]);
+       
+               // now get rid of the user portion of a node!user string
+               for (a = 0; node[a] != '\0'; ++a)
+                       if (node[a] == '!') {
+                               node[a] = 0;
+                               break;
+                       }
+       }
+
+       // strip leading and trailing spaces in all strings
+       striplt(user);
+       striplt(node);
+       striplt(name);
+
+       // If we processed a string that had the address in angle brackets
+       // but no name outside the brackets, we now have an empty name.  In
+       // this case, use the user portion of the address as the name.
+       if ((IsEmptyStr(name)) && (!IsEmptyStr(user))) {
+               strcpy(name, user);
+       }
+}
+
+
+// convert_field() is a helper function for convert_internet_message().
+// Given start/end positions for an rfc822 field, it converts it to a Citadel
+// field if it wants to, and unfolds it if necessary.
+//
+// Returns 1 if the field was converted and inserted into the Citadel message
+// structure, implying that the source field should be removed from the
+// message text.
+int convert_field(struct CtdlMessage *msg, const char *beg, const char *end) {
+       char *key, *value, *valueend;
+       long len;
+       const char *pos;
+       int i;
+       const char *colonpos = NULL;
+       int processed = 0;
+       char user[1024];
+       char node[1024];
+       char name[1024];
+       char addr[1024];
+       time_t parsed_date;
+       long valuelen;
+
+       for (pos = end; pos >= beg; pos--) {
+               if (*pos == ':') colonpos = pos;
+       }
+
+       if (colonpos == NULL) return(0);        /* no colon? not a valid header line */
+
+       len = end - beg;
+       key = malloc(len + 2);
+       memcpy(key, beg, len + 1);
+       key[len] = '\0';
+       valueend = key + len;
+       * ( key + (colonpos - beg) ) = '\0';
+       value = &key[(colonpos - beg) + 1];
+       // printf("Header: [%s]\nValue: [%s]\n", key, value);
+       unfold_rfc822_field(&value, &valueend);
+       valuelen = valueend - value + 1;
+       // printf("UnfoldedValue: [%s]\n", value);
+
+       // Here's the big rfc822-to-citadel loop.
+
+       // Date/time is converted into a unix timestamp.  If the conversion
+       // fails, we replace it with the time the message arrived locally.
+       if (!strcasecmp(key, "Date")) {
+               parsed_date = parsedate(value);
+               if (parsed_date < 0L) parsed_date = time(NULL);
+
+               if (CM_IsEmpty(msg, eTimestamp))
+                       CM_SetFieldLONG(msg, eTimestamp, parsed_date);
+               processed = 1;
+       }
+
+       else if (!strcasecmp(key, "From")) {
+               process_rfc822_addr(value, user, node, name);
+               syslog(LOG_DEBUG, "internet_addressing: converted to <%s@%s> (%s)", user, node, name);
+               snprintf(addr, sizeof(addr), "%s@%s", user, node);
+               if (CM_IsEmpty(msg, eAuthor) && !IsEmptyStr(name)) {
+                       CM_SetField(msg, eAuthor, name, -1);
+               }
+               if (CM_IsEmpty(msg, erFc822Addr) && !IsEmptyStr(addr)) {
+                       CM_SetField(msg, erFc822Addr, addr, -1);
+               }
+               processed = 1;
+       }
+
+       else if (!strcasecmp(key, "Subject")) {
+               if (CM_IsEmpty(msg, eMsgSubject))
+                       CM_SetField(msg, eMsgSubject, value, valuelen);
+               processed = 1;
+       }
+
+       else if (!strcasecmp(key, "List-ID")) {
+               if (CM_IsEmpty(msg, eListID))
+                       CM_SetField(msg, eListID, value, valuelen);
+               processed = 1;
+       }
+
+       else if (!strcasecmp(key, "To")) {
+               if (CM_IsEmpty(msg, eRecipient))
+                       CM_SetField(msg, eRecipient, value, valuelen);
+               processed = 1;
+       }
+
+       else if (!strcasecmp(key, "CC")) {
+               if (CM_IsEmpty(msg, eCarbonCopY))
+                       CM_SetField(msg, eCarbonCopY, value, valuelen);
+               processed = 1;
+       }
+
+       else if (!strcasecmp(key, "Message-ID")) {
+               if (!CM_IsEmpty(msg, emessageId)) {
+                       syslog(LOG_WARNING, "internet_addressing: duplicate message id");
+               }
+               else {
+                       char *pValue;
+                       long pValueLen;
+
+                       pValue = value;
+                       pValueLen = valuelen;
+                       // Strip angle brackets
+                       while (haschar(pValue, '<') > 0) {
+                               pValue ++;
+                               pValueLen --;
+                       }
+
+                       for (i = 0; i <= pValueLen; ++i)
+                               if (pValue[i] == '>') {
+                                       pValueLen = i;
+                                       break;
+                               }
+
+                       CM_SetField(msg, emessageId, pValue, pValueLen);
+               }
+
+               processed = 1;
+       }
+
+       else if (!strcasecmp(key, "Return-Path")) {
+               if (CM_IsEmpty(msg, eMessagePath))
+                       CM_SetField(msg, eMessagePath, value, valuelen);
+               processed = 1;
+       }
+
+       else if (!strcasecmp(key, "Envelope-To")) {
+               if (CM_IsEmpty(msg, eenVelopeTo))
+                       CM_SetField(msg, eenVelopeTo, value, valuelen);
+               processed = 1;
+       }
+
+       else if (!strcasecmp(key, "References")) {
+               CM_SetField(msg, eWeferences, value, valuelen);
+               processed = 1;
+       }
+
+       else if (!strcasecmp(key, "Reply-To")) {
+               CM_SetField(msg, eReplyTo, value, valuelen);
+               processed = 1;
+       }
+
+       else if (!strcasecmp(key, "In-reply-to")) {
+               if (CM_IsEmpty(msg, eWeferences)) // References: supersedes In-reply-to:
+                       CM_SetField(msg, eWeferences, value, valuelen);
+               processed = 1;
+       }
+
+
+
+       // Clean up and move on.
+       free(key);      // Don't free 'value', it's actually the same buffer
+       return processed;
+}
+
+
+// Convert RFC822 references format (References) to Citadel references format (Weferences)
+void convert_references_to_wefewences(char *str) {
+       int bracket_nesting = 0;
+       char *ptr = str;
+       char *moveptr = NULL;
+       char ch;
+
+       while(*ptr) {
+               ch = *ptr;
+               if (ch == '>') {
+                       --bracket_nesting;
+                       if (bracket_nesting < 0) bracket_nesting = 0;
+               }
+               if ((ch == '>') && (bracket_nesting == 0) && (*(ptr+1)) && (ptr>str) ) {
+                       *ptr = '|';
+                       ++ptr;
+               }
+               else if (bracket_nesting > 0) {
+                       ++ptr;
+               }
+               else {
+                       moveptr = ptr;
+                       while (*moveptr) {
+                               *moveptr = *(moveptr+1);
+                               ++moveptr;
+                       }
+               }
+               if (ch == '<') ++bracket_nesting;
+       }
+
+}
+
+
+// Convert an RFC822 message (headers + body) to a CtdlMessage structure.
+// NOTE: the supplied buffer becomes part of the CtdlMessage structure, and
+// will be deallocated when CM_Free() is called.  Therefore, the
+// supplied buffer should be DEREFERENCED.  It should not be freed or used
+// again.
+struct CtdlMessage *convert_internet_message(char *rfc822) {
+       StrBuf *RFCBuf = NewStrBufPlain(rfc822, -1);
+       free (rfc822);
+       return convert_internet_message_buf(&RFCBuf);
+}
+
+
+struct CtdlMessage *convert_internet_message_buf(StrBuf **rfc822)
+{
+       struct CtdlMessage *msg;
+       const char *pos, *beg, *end, *totalend;
+       int done, alldone = 0;
+       int converted;
+       StrBuf *OtherHeaders;
+
+       msg = malloc(sizeof(struct CtdlMessage));
+       if (msg == NULL) return msg;
+
+       memset(msg, 0, sizeof(struct CtdlMessage));
+       msg->cm_magic = CTDLMESSAGE_MAGIC;      // self check
+       msg->cm_anon_type = 0;                  // never anonymous
+       msg->cm_format_type = FMT_RFC822;       // internet message
+
+       pos = ChrPtr(*rfc822);
+       totalend = pos + StrLength(*rfc822);
+       done = 0;
+       OtherHeaders = NewStrBufPlain(NULL, StrLength(*rfc822));
+
+       while (!alldone) {
+
+               /* Locate beginning and end of field, keeping in mind that
+                * some fields might be multiline
+                */
+               end = beg = pos;
+
+               while ((end < totalend) && 
+                      (end == beg) && 
+                      (done == 0) ) 
+               {
+
+                       if ( (*pos=='\n') && ((*(pos+1))!=0x20) && ((*(pos+1))!=0x09) )
+                       {
+                               end = pos;
+                       }
+
+                       /* done with headers? */
+                       if ((*pos=='\n') &&
+                           ( (*(pos+1)=='\n') ||
+                             (*(pos+1)=='\r')) ) 
+                       {
+                               alldone = 1;
+                       }
+
+                       if (pos >= (totalend - 1) )
+                       {
+                               end = pos;
+                               done = 1;
+                       }
+
+                       ++pos;
+
+               }
+
+               /* At this point we have a field.  Are we interested in it? */
+               converted = convert_field(msg, beg, end);
+
+               /* Strip the field out of the RFC822 header if we used it */
+               if (!converted) {
+                       StrBufAppendBufPlain(OtherHeaders, beg, end - beg, 0);
+                       StrBufAppendBufPlain(OtherHeaders, HKEY("\n"), 0);
+               }
+
+               /* If we've hit the end of the message, bail out */
+               if (pos >= totalend)
+                       alldone = 1;
+       }
+       StrBufAppendBufPlain(OtherHeaders, HKEY("\n"), 0);
+       if (pos < totalend)
+               StrBufAppendBufPlain(OtherHeaders, pos, totalend - pos, 0);
+       FreeStrBuf(rfc822);
+       CM_SetAsFieldSB(msg, eMesageText, &OtherHeaders);
+
+       /* Follow-up sanity checks... */
+
+       /* If there's no timestamp on this message, set it to now. */
+       if (CM_IsEmpty(msg, eTimestamp)) {
+               CM_SetFieldLONG(msg, eTimestamp, time(NULL));
+       }
+
+       /* If a W (references, or rather, Wefewences) field is present, we
+        * have to convert it from RFC822 format to Citadel format.
+        */
+       if (!CM_IsEmpty(msg, eWeferences)) {
+               /// todo: API!
+               convert_references_to_wefewences(msg->cm_fields[eWeferences]);
+       }
+
+       return msg;
+}
+
+
+/*
+ * Look for a particular header field in an RFC822 message text.  If the
+ * requested field is found, it is unfolded (if necessary) and returned to
+ * the caller.  The field name is stripped out, leaving only its contents.
+ * The caller is responsible for freeing the returned buffer.  If the requested
+ * field is not present, or anything else goes wrong, it returns NULL.
+ */
+char *rfc822_fetch_field(const char *rfc822, const char *fieldname) {
+       char *fieldbuf = NULL;
+       const char *end_of_headers;
+       const char *field_start;
+       const char *ptr;
+       char *cont;
+       char fieldhdr[SIZ];
+
+       /* Should never happen, but sometimes we get stupid */
+       if (rfc822 == NULL) return(NULL);
+       if (fieldname == NULL) return(NULL);
+
+       snprintf(fieldhdr, sizeof fieldhdr, "%s:", fieldname);
+
+       /* Locate the end of the headers, so we don't run past that point */
+       end_of_headers = cbmstrcasestr(rfc822, "\n\r\n");
+       if (end_of_headers == NULL) {
+               end_of_headers = cbmstrcasestr(rfc822, "\n\n");
+       }
+       if (end_of_headers == NULL) return (NULL);
+
+       field_start = cbmstrcasestr(rfc822, fieldhdr);
+       if (field_start == NULL) return(NULL);
+       if (field_start > end_of_headers) return(NULL);
+
+       fieldbuf = malloc(SIZ);
+       strcpy(fieldbuf, "");
+
+       ptr = field_start;
+       ptr = cmemreadline(ptr, fieldbuf, SIZ-strlen(fieldbuf) );
+       while ( (isspace(ptr[0])) && (ptr < end_of_headers) ) {
+               strcat(fieldbuf, " ");
+               cont = &fieldbuf[strlen(fieldbuf)];
+               ptr = cmemreadline(ptr, cont, SIZ-strlen(fieldbuf) );
+               striplt(cont);
+       }
+
+       strcpy(fieldbuf, &fieldbuf[strlen(fieldhdr)]);
+       striplt(fieldbuf);
+
+       return(fieldbuf);
+}
+
+
+/*****************************************************************************
+ *                      DIRECTORY MANAGEMENT FUNCTIONS                       *
+ *****************************************************************************/
+
+/*
+ * Generate the index key for an Internet e-mail address to be looked up
+ * in the database.
+ */
+void directory_key(char *key, char *addr) {
+       int i;
+       int keylen = 0;
+
+       for (i=0; !IsEmptyStr(&addr[i]); ++i) {
+               if (!isspace(addr[i])) {
+                       key[keylen++] = tolower(addr[i]);
+               }
+       }
+       key[keylen++] = 0;
+
+       syslog(LOG_DEBUG, "internet_addressing: directory key is <%s>", key);
+}
+
+
+/*
+ * Return nonzero if the supplied address is in one of "our" domains
+ */
+int IsDirectory(char *addr, int allow_masq_domains) {
+       char domain[256];
+       int h;
+
+       extract_token(domain, addr, 1, '@', sizeof domain);
+       striplt(domain);
+
+       h = CtdlHostAlias(domain);
+
+       if ( (h == hostalias_masq) && allow_masq_domains)
+               return(1);
+       
+       if (h == hostalias_localhost) {
+               return(1);
+       }
+       else {
+               return(0);
+       }
+}
+
+
+/*
+ * Add an Internet e-mail address to the directory for a user
+ */
+int CtdlDirectoryAddUser(char *internet_addr, char *citadel_addr) {
+       char key[SIZ];
+
+       if (IsDirectory(internet_addr, 0) == 0) {
+               return 0;
+       }
+       syslog(LOG_DEBUG, "internet_addressing: create directory entry: %s --> %s", internet_addr, citadel_addr);
+       directory_key(key, internet_addr);
+       cdb_store(CDB_DIRECTORY, key, strlen(key), citadel_addr, strlen(citadel_addr)+1 );
+       return 1;
+}
+
+
+/*
+ * Delete an Internet e-mail address from the directory.
+ *
+ * (NOTE: we don't actually use or need the citadel_addr variable; it's merely
+ * here because the callback API expects to be able to send it.)
+ */
+int CtdlDirectoryDelUser(char *internet_addr, char *citadel_addr) {
+       char key[SIZ];
+       
+       syslog(LOG_DEBUG, "internet_addressing: delete directory entry: %s --> %s", internet_addr, citadel_addr);
+       directory_key(key, internet_addr);
+       return cdb_delete(CDB_DIRECTORY, key, strlen(key) ) == 0;
+}
+
+
+/*
+ * Look up an Internet e-mail address in the directory.
+ * On success: returns 0, and Citadel address stored in 'target'
+ * On failure: returns nonzero
+ */
+int CtdlDirectoryLookup(char *target, char *internet_addr, size_t targbuflen) {
+       struct cdbdata *cdbrec;
+       char key[SIZ];
+
+       /* Dump it in there unchanged, just for kicks */
+       if (target != NULL) {
+               safestrncpy(target, internet_addr, targbuflen);
+       }
+
+       /* Only do lookups for addresses with hostnames in them */
+       if (num_tokens(internet_addr, '@') != 2) return(-1);
+
+       /* Only do lookups for domains in the directory */
+       if (IsDirectory(internet_addr, 0) == 0) return(-1);
+
+       directory_key(key, internet_addr);
+       cdbrec = cdb_fetch(CDB_DIRECTORY, key, strlen(key) );
+       if (cdbrec != NULL) {
+               if (target != NULL) {
+                       safestrncpy(target, cdbrec->ptr, targbuflen);
+               }
+               cdb_free(cdbrec);
+               return(0);
+       }
+
+       return(-1);
+}
+
+
+/*
+ * Harvest any email addresses that someone might want to have in their
+ * "collected addresses" book.
+ */
+char *harvest_collected_addresses(struct CtdlMessage *msg) {
+       char *coll = NULL;
+       char addr[256];
+       char user[256], node[256], name[256];
+       int is_harvestable;
+       int i, j, h;
+       eMsgField field = 0;
+
+       if (msg == NULL) return(NULL);
+
+       is_harvestable = 1;
+       strcpy(addr, "");       
+       if (!CM_IsEmpty(msg, eAuthor)) {
+               strcat(addr, msg->cm_fields[eAuthor]);
+       }
+       if (!CM_IsEmpty(msg, erFc822Addr)) {
+               strcat(addr, " <");
+               strcat(addr, msg->cm_fields[erFc822Addr]);
+               strcat(addr, ">");
+               if (IsDirectory(msg->cm_fields[erFc822Addr], 0)) {
+                       is_harvestable = 0;
+               }
+       }
+
+       if (is_harvestable) {
+               coll = strdup(addr);
+       }
+       else {
+               coll = strdup("");
+       }
+
+       if (coll == NULL) return(NULL);
+
+       /* Scan both the R (To) and Y (CC) fields */
+       for (i = 0; i < 2; ++i) {
+               if (i == 0) field = eRecipient;
+               if (i == 1) field = eCarbonCopY;
+
+               if (!CM_IsEmpty(msg, field)) {
+                       for (j=0; j<num_tokens(msg->cm_fields[field], ','); ++j) {
+                               extract_token(addr, msg->cm_fields[field], j, ',', sizeof addr);
+                               if (strstr(addr, "=?") != NULL)
+                                       utf8ify_rfc822_string(addr);
+                               process_rfc822_addr(addr, user, node, name);
+                               h = CtdlHostAlias(node);
+                               if (h != hostalias_localhost) {
+                                       coll = realloc(coll, strlen(coll) + strlen(addr) + 4);
+                                       if (coll == NULL) return(NULL);
+                                       if (!IsEmptyStr(coll)) {
+                                               strcat(coll, ",");
+                                       }
+                                       striplt(addr);
+                                       strcat(coll, addr);
+                               }
+                       }
+               }
+       }
+
+       if (IsEmptyStr(coll)) {
+               free(coll);
+               return(NULL);
+       }
+       return(coll);
+}
+
+
+/*
+ * Helper function for CtdlRebuildDirectoryIndex()
+ */
+void CtdlRebuildDirectoryIndex_backend(char *username, void *data) {
+
+       int j = 0;
+       struct ctdluser usbuf;
+
+       if (CtdlGetUser(&usbuf, username) != 0) {
+               return;
+       }
+
+       if ( (!IsEmptyStr(usbuf.fullname)) && (!IsEmptyStr(usbuf.emailaddrs)) ) {
+               for (j=0; j<num_tokens(usbuf.emailaddrs, '|'); ++j) {
+                       char one_email[512];
+                       extract_token(one_email, usbuf.emailaddrs, j, '|', sizeof one_email);
+                       CtdlDirectoryAddUser(one_email, usbuf.fullname);
+               }
+       }
+}
+
+
+/*
+ * Initialize the directory database (erasing anything already there)
+ */
+void CtdlRebuildDirectoryIndex(void) {
+       syslog(LOG_INFO, "internet_addressing: rebuilding email address directory index");
+       cdb_trunc(CDB_DIRECTORY);
+       ForEachUser(CtdlRebuildDirectoryIndex_backend, NULL);
+}
+
+
+// Configure Internet email addresses for a user account, updating the Directory Index in the process
+void CtdlSetEmailAddressesForUser(char *requested_user, char *new_emailaddrs) {
+       struct ctdluser usbuf;
+       int i;
+       char buf[SIZ];
+
+       if (CtdlGetUserLock(&usbuf, requested_user) != 0) {     // We can lock because the DirectoryIndex functions don't lock.
+               return;                                         // Silently fail here if the specified user does not exist.
+       }
+
+       syslog(LOG_DEBUG, "internet_addressing: setting email addresses for <%s> to <%s>", usbuf.fullname, new_emailaddrs);
+
+       // Delete all of the existing directory index records for the user (easier this way)
+       for (i=0; i<num_tokens(usbuf.emailaddrs, '|'); ++i) {
+               extract_token(buf, usbuf.emailaddrs, i, '|', sizeof buf);
+               CtdlDirectoryDelUser(buf, requested_user);
+       }
+
+       strcpy(usbuf.emailaddrs, new_emailaddrs);               // make it official.
+
+       // Index all of the new email addresses (they've already been sanitized)
+       for (i=0; i<num_tokens(usbuf.emailaddrs, '|'); ++i) {
+               extract_token(buf, usbuf.emailaddrs, i, '|', sizeof buf);
+               CtdlDirectoryAddUser(buf, requested_user);
+       }
+
+       CtdlPutUserLock(&usbuf);
+}
+
+
+/*
+ * Auto-generate an Internet email address for a user account
+ */
+void AutoGenerateEmailAddressForUser(struct ctdluser *user) {
+       char synthetic_email_addr[1024];
+       int i, j;
+       int u = 0;
+
+       for (i=0; u==0; ++i) {
+               if (i == 0) {
+                       // first try just converting the user name to lowercase and replacing spaces with underscores
+                       snprintf(synthetic_email_addr, sizeof synthetic_email_addr, "%s@%s", user->fullname, CtdlGetConfigStr("c_fqdn"));
+                       for (j=0; ((synthetic_email_addr[j] != '\0')&&(synthetic_email_addr[j] != '@')); j++) {
+                               synthetic_email_addr[j] = tolower(synthetic_email_addr[j]);
+                               if (!isalnum(synthetic_email_addr[j])) {
+                                       synthetic_email_addr[j] = '_';
+                               }
+                       }
+               }
+               else if (i == 1) {
+                       // then try 'ctdl' followed by the user number
+                       snprintf(synthetic_email_addr, sizeof synthetic_email_addr, "ctdl%08lx@%s", user->usernum, CtdlGetConfigStr("c_fqdn"));
+               }
+               else if (i > 1) {
+                       // oof.  just keep trying other numbers until we find one
+                       snprintf(synthetic_email_addr, sizeof synthetic_email_addr, "ctdl%08x@%s", i, CtdlGetConfigStr("c_fqdn"));
+               }
+               u = CtdlDirectoryLookup(NULL, synthetic_email_addr, 0);
+               syslog(LOG_DEBUG, "user_ops: address <%s> lookup returned <%d>", synthetic_email_addr, u);
+       }
+
+       CtdlSetEmailAddressesForUser(user->fullname, synthetic_email_addr);
+       strncpy(CC->user.emailaddrs, synthetic_email_addr, sizeof(user->emailaddrs));
+       syslog(LOG_DEBUG, "user_ops: auto-generated email address <%s> for <%s>", synthetic_email_addr, user->fullname);
+}
+
+
+// Determine whether the supplied email address is subscribed to the supplied room's mailing list service.
+int is_email_subscribed_to_list(char *email, char *room_name) {
+       struct ctdlroom room;
+       long roomnum;
+       char *roomnetconfig;
+       int found_it = 0;
+
+       if (CtdlGetRoom(&room, room_name)) {
+               return(0);                                      // room not found, so definitely not subscribed
+       }
+
+       // If this room has the QR2_SMTP_PUBLIC flag set, anyone may email a post to this room, even non-subscribers.
+       if (room.QRflags2 & QR2_SMTP_PUBLIC) {
+               return(1);
+       }
+
+       roomnum = room.QRnumber;
+       roomnetconfig = LoadRoomNetConfigFile(roomnum);
+       if (roomnetconfig == NULL) {
+               return(0);
+       }
+
+       // We're going to do a very sloppy match here and simply search for the specified email address
+       // anywhere in the room's netconfig.  If you don't like this, fix it yourself.
+       if (bmstrcasestr(roomnetconfig, email)) {
+               found_it = 1;
+       }
+       else {
+               found_it = 0;
+       }
+
+       free(roomnetconfig);
+       return(found_it);
+}
diff --git a/citadel/server/internet_addressing.h b/citadel/server/internet_addressing.h
new file mode 100644 (file)
index 0000000..4430335
--- /dev/null
@@ -0,0 +1,35 @@
+
+#include "server.h"
+#include "ctdl_module.h"
+
+struct recptypes *validate_recipients(char *recipients, const char *RemoteIdentifier, int Flags);
+void free_recipients(struct recptypes *);
+void process_rfc822_addr(const char *rfc822, char *user, char *node, char *name);
+char *rfc822_fetch_field(const char *rfc822, const char *fieldname);
+void sanitize_truncated_recipient(char *str);
+char *qp_encode_email_addrs(char *source);
+int alias (char *name);
+int IsDirectory(char *addr, int allow_masq_domains);
+void CtdlRebuildDirectoryIndex(void);
+int CtdlDirectoryAddUser(char *internet_addr, char *citadel_addr);
+int CtdlDirectoryDelUser(char *internet_addr, char *citadel_addr);
+int CtdlDirectoryLookup(char *target, char *internet_addr, size_t targbuflen);
+void CtdlSetEmailAddressesForUser(char *requested_user, char *new_emailaddrs);
+void AutoGenerateEmailAddressForUser(struct ctdluser *user);
+struct CtdlMessage *convert_internet_message(char *rfc822);
+struct CtdlMessage *convert_internet_message_buf(StrBuf **rfc822);
+int CtdlIsMe(char *addr, int addr_buf_len);
+int CtdlHostAlias(char *fqdn);
+char *harvest_collected_addresses(struct CtdlMessage *msg);
+int is_email_subscribed_to_list(char *email, char *room_name);
+
+/* 
+ * Values that can be returned by CtdlHostAlias()
+ */
+enum {
+       hostalias_nomatch,
+       hostalias_localhost,
+       hostalias_masq
+};
+
+extern char *inetcfg;
diff --git a/citadel/server/ipcdef.h b/citadel/server/ipcdef.h
new file mode 100644 (file)
index 0000000..23897e4
--- /dev/null
@@ -0,0 +1,90 @@
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LISTING_FOLLOWS                100
+#define CIT_OK                 200
+#define MORE_DATA              300
+#define SEND_LISTING           400
+#define ERROR                  500
+#define BINARY_FOLLOWS         600
+#define SEND_BINARY            700
+#define START_CHAT_MODE                800
+
+#define INTERNAL_ERROR         10
+#define TOO_BIG                        11
+#define ILLEGAL_VALUE          12
+#define NOT_LOGGED_IN          20
+#define CMD_NOT_SUPPORTED      30
+#define SERVER_SHUTTING_DOWN   31
+#define PASSWORD_REQUIRED      40
+#define ALREADY_LOGGED_IN      41
+#define USERNAME_REQUIRED      42
+#define HIGHER_ACCESS_REQUIRED 50
+#define MAX_SESSIONS_EXCEEDED  51
+#define RESOURCE_BUSY          52
+#define RESOURCE_NOT_OPEN      53
+#define NOT_HERE               60
+#define INVALID_FLOOR_OPERATION        61
+#define NO_SUCH_USER           70
+#define FILE_NOT_FOUND         71
+#define ROOM_NOT_FOUND         72
+#define NO_SUCH_SYSTEM         73
+#define ALREADY_EXISTS         74
+#define MESSAGE_NOT_FOUND      75
+
+#define ASYNC_MSG              900
+#define ASYNC_GEXP             02
+
+#define QR_PERMANENT   1               /* Room does not purge              */
+#define QR_INUSE       2               /* Set if in use, clear if avail    */
+#define QR_PRIVATE     4               /* Set for any type of private room */
+#define QR_PASSWORDED  8               /* Set if there's a password too    */
+#define QR_GUESSNAME   16              /* Set if it's a guessname room     */
+#define QR_DIRECTORY   32              /* Directory room                   */
+#define QR_UPLOAD      64              /* Allowed to upload                */
+#define QR_DOWNLOAD    128             /* Allowed to download              */
+#define QR_VISDIR      256             /* Visible directory                */
+#define QR_ANONONLY    512             /* Anonymous-Only room              */
+#define QR_ANONOPT     1024            /* Anonymous-Option room            */
+#define QR_NETWORK     2048            /* Shared network room              */
+#define QR_PREFONLY    4096            /* Preferred status needed to enter */
+#define QR_READONLY    8192            /* Aide status required to post     */
+#define QR_MAILBOX     16384           /* Set if this is a private mailbox */
+
+#define QR2_SYSTEM     1               /* System room; hide by default     */
+#define QR2_SELFLIST   2               /* Self-service mailing list mgmt   */
+#define QR2_COLLABDEL  4               /* Anyone who can post can delete   */
+#define QR2_SUBJECTREQ 8               /* Subject strongly recommended */
+#define QR2_SMTP_PUBLIC        16              /* Listservice Subscribers may post */
+
+#define US_NEEDVALID   1               /* User needs to be validated       */
+#define US_EXTEDIT     2               /* Always use external editor       */
+#define US_PERM                4               /* Permanent user                   */
+#define US_LASTOLD     16              /* Print last old message with new  */
+#define US_EXPERT      32              /* Experienced user                 */
+#define US_UNLISTED    64              /* Unlisted userlog entry           */
+#define US_NOPROMPT    128             /* Don't prompt after each message  */
+#define US_PROMPTCTL   256             /* <N>ext & <S>top work at prompt   */
+#define US_DISAPPEAR   512             /* Use "disappearing msg prompts"   */
+#define US_REGIS       1024            /* Registered user                  */
+#define US_PAGINATOR   2048            /* Pause after each screen of text  */
+#define US_INTERNET    4096            /* Internet mail privileges         */
+#define US_FLOORS      8192            /* User wants to see floors         */
+#define US_COLOR       16384           /* User wants ANSI color support    */
+#define US_USER_SET    (US_LASTOLD | US_EXPERT | US_UNLISTED | \
+                       US_NOPROMPT | US_DISAPPEAR | US_PAGINATOR | \
+                       US_FLOORS | US_COLOR | US_PROMPTCTL | US_EXTEDIT)
+
+#define UA_KNOWN                2      /* Room appears in a 'known rooms' list */
+#define UA_GOTOALLOWED          4      /* User may goto this room if specified by exact name */
+#define UA_HASNEWMSGS           8      /* Unread messages exist in this room */
+#define UA_ZAPPED               16     /* User has forgotten (zapped) this room */
+#define UA_POSTALLOWED         32      /* User may post top-level messages here */
+#define UA_ADMINALLOWED                64      /* Aide or Room Aide rights exist here */
+#define UA_DELETEALLOWED       128     /* User is allowed to delete messages from this room */
+#define UA_REPLYALLOWED                256     /* User is allowed to reply to existing messages here */
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/citadel/server/journaling.c b/citadel/server/journaling.c
new file mode 100644 (file)
index 0000000..939ec3b
--- /dev/null
@@ -0,0 +1,255 @@
+/*
+ * Message journaling functions.
+ *
+ * Copyright (c) 1987-2020 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <libcitadel.h>
+#include "ctdl_module.h"
+#include "citserver.h"
+#include "config.h"
+#include "user_ops.h"
+#include "serv_vcard.h"                        /* Needed for vcard_getuser and extract_inet_email_addrs */
+#include "internet_addressing.h"
+#include "journaling.h"
+
+struct jnlq *jnlq = NULL;      /* journal queue */
+
+/*
+ * Hand off a copy of a message to be journalized.
+ */
+void JournalBackgroundSubmit(struct CtdlMessage *msg,
+                       StrBuf *saved_rfc822_version,
+                       struct recptypes *recps) {
+
+       struct jnlq *jptr = NULL;
+
+       /* Avoid double journaling! */
+       if (!CM_IsEmpty(msg, eJournal)) {
+               FreeStrBuf(&saved_rfc822_version);
+               return;
+       }
+
+       jptr = (struct jnlq *)malloc(sizeof(struct jnlq));
+       if (jptr == NULL) {
+               FreeStrBuf(&saved_rfc822_version);
+               return;
+       }
+       memset(jptr, 0, sizeof(struct jnlq));
+       if (recps != NULL) memcpy(&jptr->recps, recps, sizeof(struct recptypes));
+       if (!CM_IsEmpty(msg, eAuthor)) jptr->from = strdup(msg->cm_fields[eAuthor]);
+       if (!CM_IsEmpty(msg, erFc822Addr)) jptr->rfca = strdup(msg->cm_fields[erFc822Addr]);
+       if (!CM_IsEmpty(msg, eMsgSubject)) jptr->subj = strdup(msg->cm_fields[eMsgSubject]);
+       if (!CM_IsEmpty(msg, emessageId)) jptr->msgn = strdup(msg->cm_fields[emessageId]);
+       jptr->rfc822 = SmashStrBuf(&saved_rfc822_version);
+
+       /* Add to the queue */
+       begin_critical_section(S_JOURNAL_QUEUE);
+       jptr->next = jnlq;
+       jnlq = jptr;
+       end_critical_section(S_JOURNAL_QUEUE);
+}
+
+
+/*
+ * Convert a local user name to an internet email address for the journal
+ * FIXME - grab the user's Internet email address from the user record, not from vCard !!!!
+ */
+void local_to_inetemail(char *inetemail, char *localuser, size_t inetemail_len) {
+       struct ctdluser us;
+       struct vCard *v;
+
+       strcpy(inetemail, "");
+       if (CtdlGetUser(&us, localuser) != 0) {
+               return;
+       }
+
+       v = vcard_get_user(&us);
+       if (v == NULL) {
+               return;
+       }
+
+       extract_inet_email_addrs(inetemail, inetemail_len, NULL, 0, v, 1);
+       vcard_free(v);
+}
+
+
+/*
+ * Called by JournalRunQueue() to send an individual message.
+ */
+void JournalRunQueueMsg(struct jnlq *jmsg) {
+
+       struct CtdlMessage *journal_msg = NULL;
+       struct recptypes *journal_recps = NULL;
+       StrBuf *message_text = NULL;
+       char mime_boundary[256];
+       long mblen;
+       long rfc822len;
+       char recipient[256];
+       char inetemail[256];
+       static int seq = 0;
+       int i;
+
+       if (jmsg == NULL)
+               return;
+       journal_recps = validate_recipients(CtdlGetConfigStr("c_journal_dest"), NULL, 0);
+       if (journal_recps != NULL) {
+
+               if (  (journal_recps->num_local > 0)
+                  || (journal_recps->num_internet > 0)
+                  || (journal_recps->num_room > 0)
+               ) {
+
+                       /*
+                        * Construct journal message.
+                        * Note that we are transferring ownership of some of the memory here.
+                        */
+                       journal_msg = malloc(sizeof(struct CtdlMessage));
+                       memset(journal_msg, 0, sizeof(struct CtdlMessage));
+                       journal_msg->cm_magic = CTDLMESSAGE_MAGIC;
+                       journal_msg->cm_anon_type = MES_NORMAL;
+                       journal_msg->cm_format_type = FMT_RFC822;
+                       CM_SetField(journal_msg, eJournal, HKEY("is journal"));
+
+                       if (!IsEmptyStr(jmsg->from)) {
+                               CM_SetField(journal_msg, eAuthor, jmsg->from, -1);
+                       }
+
+                       if (!IsEmptyStr(jmsg->rfca)) {
+                               CM_SetField(journal_msg, erFc822Addr, jmsg->rfca, -1);
+                       }
+
+                       if (!IsEmptyStr(jmsg->subj)) {
+                               CM_SetField(journal_msg, eMsgSubject, jmsg->subj, -1);
+                       }
+
+                       mblen = snprintf(mime_boundary, sizeof(mime_boundary),
+                                        "--Citadel-Journal-%08lx-%04x--", time(NULL), ++seq);
+
+                       if (!IsEmptyStr(jmsg->rfc822)) {
+                               rfc822len = strlen(jmsg->rfc822);
+                       }
+                       else {
+                               rfc822len = 0;
+                       }
+
+                       message_text = NewStrBufPlain(NULL, rfc822len + sizeof(struct recptypes) + 1024);
+
+                       /*
+                        * Here is where we begin to compose the journalized message.
+                        * (The "ExJournalReport" header is consumed by some email retention services which assume the journaling agent is Exchange.)
+                        */
+                       StrBufAppendBufPlain(
+                               message_text, 
+                               HKEY("Content-type: multipart/mixed; boundary=\""),
+                               0
+                       );
+
+                       StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
+
+                       StrBufAppendBufPlain(
+                               message_text, 
+                               HKEY("\"\r\n"
+                                    "Content-Identifier: ExJournalReport\r\n"
+                                    "MIME-Version: 1.0\r\n"
+                                    "\n"
+                                    "--"),
+                               0
+                       );
+
+                       StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
+
+                       StrBufAppendBufPlain(
+                               message_text, 
+                               HKEY("\r\n"
+                                    "Content-type: text/plain\r\n"
+                                    "\r\n"
+                                    "Sender: "), 0);
+
+                       if (CM_IsEmpty(journal_msg, eAuthor))
+                               StrBufAppendBufPlain(
+                                       message_text, 
+                                       journal_msg->cm_fields[eAuthor], -1, 0);
+                       else
+                               StrBufAppendBufPlain(
+                                       message_text, 
+                                       HKEY("(null)"), 0);
+
+                       if (!CM_IsEmpty(journal_msg, erFc822Addr)) {
+                               StrBufAppendPrintf(message_text, " <%s>",
+                                                  journal_msg->cm_fields[erFc822Addr]);
+                       }
+
+                       StrBufAppendBufPlain(message_text, HKEY("\r\nMessage-ID: <"), 0);
+                       StrBufAppendBufPlain(message_text, jmsg->msgn, -1, 0);
+                       StrBufAppendBufPlain(message_text, HKEY(">\r\nRecipients:\r\n"), 0);
+
+                       if (jmsg->recps.num_local > 0) {
+                               for (i=0; i<jmsg->recps.num_local; ++i) {
+                                       extract_token(recipient, jmsg->recps.recp_local, i, '|', sizeof recipient);
+                                       local_to_inetemail(inetemail, recipient, sizeof inetemail);
+                                       StrBufAppendPrintf(message_text, "      %s <%s>\r\n", recipient, inetemail);
+                               }
+                       }
+
+                       if (jmsg->recps.num_internet > 0) {
+                               for (i=0; i<jmsg->recps.num_internet; ++i) {
+                                       extract_token(recipient, jmsg->recps.recp_internet, i, '|', sizeof recipient);
+                                       StrBufAppendPrintf(message_text, "      %s\r\n", recipient);
+                               }
+                       }
+
+                       StrBufAppendBufPlain(message_text, HKEY("\r\n" "--"), 0);
+                       StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
+                       StrBufAppendBufPlain(message_text, HKEY("\r\nContent-type: message/rfc822\r\n\r\n"), 0);
+                       StrBufAppendBufPlain(message_text, jmsg->rfc822, rfc822len, 0);
+                       StrBufAppendBufPlain(message_text, HKEY("--"), 0);
+                       StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
+                       StrBufAppendBufPlain(message_text, HKEY("--\r\n"), 0);
+
+                       CM_SetAsFieldSB(journal_msg, eMesageText, &message_text);
+                       free(jmsg->rfc822);
+                       free(jmsg->msgn);
+                       jmsg->rfc822 = NULL;
+                       jmsg->msgn = NULL;
+                       
+                       /* Submit journal message */
+                       CtdlSubmitMsg(journal_msg, journal_recps, "");
+                       CM_Free(journal_msg);
+               }
+
+               free_recipients(journal_recps);
+       }
+
+       /* We are responsible for freeing this memory. */
+       free(jmsg);
+}
+
+
+/*
+ * Run the queue.
+ */
+void JournalRunQueue(void) {
+       struct jnlq *jptr = NULL;
+
+       while (jnlq != NULL) {
+               begin_critical_section(S_JOURNAL_QUEUE);
+               if (jnlq != NULL) {
+                       jptr = jnlq;
+                       jnlq = jnlq->next;
+               }
+               end_critical_section(S_JOURNAL_QUEUE);
+               JournalRunQueueMsg(jptr);
+       }
+}
+
+
diff --git a/citadel/server/journaling.h b/citadel/server/journaling.h
new file mode 100644 (file)
index 0000000..4a66781
--- /dev/null
@@ -0,0 +1,16 @@
+struct jnlq {
+       struct jnlq *next;
+       struct recptypes recps;
+       char *from;
+       char *node;
+       char *rfca;
+       char *subj;
+       char *msgn;
+       char *rfc822;
+};
+
+void JournalBackgroundSubmit(struct CtdlMessage *msg,
+                        StrBuf *saved_rfc822_version,
+                        struct recptypes *recps);
+void JournalRunQueueMsg(struct jnlq *jmsg);
+void JournalRunQueue(void);
diff --git a/citadel/server/ldap.c b/citadel/server/ldap.c
new file mode 100644 (file)
index 0000000..3a16117
--- /dev/null
@@ -0,0 +1,637 @@
+// These functions implement the portions of AUTHMODE_LDAP and AUTHMODE_LDAP_AD which
+// actually speak to the LDAP server.
+//
+// Copyright (c) 2011-2022 by the citadel.org development team.
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// ldapsearch -D uid=admin,cn=users,cn=compat,dc=demo1,dc=freeipa,dc=org -w Secret123 -h ipa.demo1.freeipa.org
+
+int ctdl_require_ldap_version = 3;
+
+#define _GNU_SOURCE            // Needed to suppress warning about vasprintf() when running on Linux/Linux
+#include <stdio.h>
+#include <libcitadel.h>
+#include "citserver.h"
+#include "citadel_ldap.h"
+#include "ctdl_module.h"
+#include "user_ops.h"
+#include "internet_addressing.h"
+#include "config.h"
+#include <ldap.h>
+
+
+// These functions are deprecated and until we change them to the new API we need these declarations to get around compiler warnings.
+int ldap_simple_bind_s(LDAP *, const char *, const char *);
+int ldap_unbind(LDAP *);
+
+
+// Utility function, supply a search result and get back the fullname (display name, common name, etc) from the first result
+//
+// POSIX schema:       the display name will be found in "cn" (common name)
+// Active Directory:   the display name will be found in "displayName"
+//
+void derive_fullname_from_ldap_result(char *fullname, int fullname_size, LDAP *ldserver, LDAPMessage *search_result) {
+       struct berval **values;
+
+       if (fullname == NULL) return;
+       if (search_result == NULL) return;
+       if (ldserver == NULL) return;
+
+       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
+               values = ldap_get_values_len(ldserver, search_result, "displayName");
+               if (values) {
+                       if (ldap_count_values_len(values) > 0) {
+                               safestrncpy(fullname, values[0]->bv_val, fullname_size);
+                               syslog(LOG_DEBUG, "ldap: displayName = %s", fullname);
+                       }
+                       ldap_value_free_len(values);
+               }
+       }
+       else {
+               values = ldap_get_values_len(ldserver, search_result, "cn");
+               if (values) {
+                       if (ldap_count_values_len(values) > 0) {
+                               safestrncpy(fullname, values[0]->bv_val, fullname_size);
+                               syslog(LOG_DEBUG, "ldap: cn = %s", fullname);
+                       }
+                       ldap_value_free_len(values);
+               }
+       }
+}
+
+
+// Utility function, supply a search result and get back the uid from the first result
+//
+// POSIX schema:       numeric user id will be in the "uidNumber" attribute
+// Active Directory:   we make a uid hashed from "objectGUID"
+//
+uid_t derive_uid_from_ldap(LDAP *ldserver, LDAPMessage *entry) {
+       struct berval **values;
+       uid_t uid = (-1);
+
+       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
+               values = ldap_get_values_len(ldserver, entry, "objectGUID");
+               if (values) {
+                       if (ldap_count_values_len(values) > 0) {
+                               uid = abs(HashLittle(values[0]->bv_val, values[0]->bv_len));
+                       }
+                       ldap_value_free_len(values);
+               }
+       }
+       else {
+               values = ldap_get_values_len(ldserver, entry, "uidNumber");
+               if (values) {
+                       if (ldap_count_values_len(values) > 0) {
+                               uid = atoi(values[0]->bv_val);
+                       }
+                       ldap_value_free_len(values);
+               }
+       }
+
+       syslog(LOG_DEBUG, "ldap: uid = %d", uid);
+       return(uid);
+}
+
+
+// Wrapper function for ldap_initialize() that consistently fills in the correct fields
+int ctdl_ldap_initialize(LDAP **ld) {
+
+       char server_url[256];
+       int ret;
+
+       snprintf(server_url, sizeof server_url, "ldap://%s:%d", CtdlGetConfigStr("c_ldap_host"), CtdlGetConfigInt("c_ldap_port"));
+       syslog(LOG_DEBUG, "ldap: initializing server %s", server_url);
+       ret = ldap_initialize(ld, server_url);
+       if (ret != LDAP_SUCCESS) {
+               syslog(LOG_ERR, "ldap: could not connect to %s : %m", server_url);
+               *ld = NULL;
+               return(errno);
+       }
+
+       return(ret);
+}
+
+
+// Bind to the LDAP server and return a working handle
+LDAP *ctdl_ldap_bind(void) {
+       LDAP *ldserver = NULL;
+       int i;
+
+       if (ctdl_ldap_initialize(&ldserver) != LDAP_SUCCESS) {
+               return(NULL);
+       }
+
+       ldap_set_option(ldserver, LDAP_OPT_PROTOCOL_VERSION, &ctdl_require_ldap_version);
+       ldap_set_option(ldserver, LDAP_OPT_REFERRALS, (void *)LDAP_OPT_OFF);
+
+       striplt(CtdlGetConfigStr("c_ldap_bind_dn"));
+       striplt(CtdlGetConfigStr("c_ldap_bind_pw"));
+       i = ldap_simple_bind_s(ldserver,
+               (!IsEmptyStr(CtdlGetConfigStr("c_ldap_bind_dn")) ? CtdlGetConfigStr("c_ldap_bind_dn") : NULL),
+               (!IsEmptyStr(CtdlGetConfigStr("c_ldap_bind_pw")) ? CtdlGetConfigStr("c_ldap_bind_pw") : NULL)
+       );
+       if (i != LDAP_SUCCESS) {
+               syslog(LOG_ERR, "ldap: Cannot bind: %s (%d)", ldap_err2string(i), i);
+               return(NULL);
+       }
+
+       return(ldserver);
+}
+
+
+// Look up a user in the directory to see if this is an account that can be authenticated
+//
+// POSIX schema:       Search all "inetOrgPerson" objects with "uid" set to the supplied username
+// Active Directory:   Look for an account with "sAMAccountName" set to the supplied username
+//
+int CtdlTryUserLDAP(char *username, char *found_dn, int found_dn_size, char *fullname, int fullname_size, uid_t *uid) {
+       LDAP *ldserver = NULL;
+       LDAPMessage *search_result = NULL;
+       LDAPMessage *entry = NULL;
+       char searchstring[1024];
+       struct timeval tv;
+       char *user_dn = NULL;
+
+       ldserver = ctdl_ldap_bind();
+       if (!ldserver) return(-1);
+
+       if (fullname) safestrncpy(fullname, username, fullname_size);
+       tv.tv_sec = 10;
+       tv.tv_usec = 0;
+
+       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
+               snprintf(searchstring, sizeof(searchstring), "(sAMAccountName=%s)", username);
+       }
+       else {
+               snprintf(searchstring, sizeof(searchstring), "(&(objectclass=inetOrgPerson)(uid=%s))", username);
+       }
+
+       syslog(LOG_DEBUG, "ldap: search: %s", searchstring);
+       syslog(LOG_DEBUG, "ldap: search results: %s", ldap_err2string(ldap_search_ext_s(
+               ldserver,                                       // ld
+               CtdlGetConfigStr("c_ldap_base_dn"),             // base
+               LDAP_SCOPE_SUBTREE,                             // scope
+               searchstring,                                   // filter
+               NULL,                                           // attrs (all attributes)
+               0,                                              // attrsonly (attrs + values)
+               NULL,                                           // serverctrls (none)
+               NULL,                                           // clientctrls (none)
+               &tv,                                            // timeout
+               1,                                              // sizelimit (1 result max)
+               &search_result                                  // put the result here
+       )));
+
+       // Ignore the return value of ldap_search_ext_s().  Sometimes it returns an error even when
+       // the search succeeds.  Instead, we check to see whether search_result is still NULL.
+       if (search_result == NULL) {
+               syslog(LOG_DEBUG, "ldap: zero search results were returned");
+               ldap_unbind(ldserver);
+               return(2);
+       }
+
+       // At this point we've got at least one result from our query.  If there are multiple
+       // results, we still only look at the first one.
+       entry = ldap_first_entry(ldserver, search_result);
+       if (entry) {
+
+               user_dn = ldap_get_dn(ldserver, entry);
+               if (user_dn) {
+                       syslog(LOG_DEBUG, "ldap: dn = %s", user_dn);
+               }
+
+               derive_fullname_from_ldap_result(fullname, fullname_size, ldserver, search_result);
+               *uid = derive_uid_from_ldap(ldserver, search_result);
+       }
+
+       // free the results
+       ldap_msgfree(search_result);
+
+       // unbind so we can go back in as the authenticating user
+       ldap_unbind(ldserver);
+
+       if (!user_dn) {
+               syslog(LOG_DEBUG, "ldap: No such user was found.");
+               return(4);
+       }
+
+       if (found_dn) safestrncpy(found_dn, user_dn, found_dn_size);
+       ldap_memfree(user_dn);
+       return(0);
+}
+
+
+// This is an extension of CtdlTryPassword() which gets called when using LDAP authentication.
+int CtdlTryPasswordLDAP(char *user_dn, const char *password) {
+       LDAP *ldserver = NULL;
+       int i = (-1);
+
+       if (IsEmptyStr(password)) {
+               syslog(LOG_DEBUG, "ldap: empty passwords are not permitted");
+               return(1);
+       }
+
+       syslog(LOG_DEBUG, "ldap: trying to bind as %s", user_dn);
+       i = ctdl_ldap_initialize(&ldserver);
+       if (i == LDAP_SUCCESS) {
+               ldap_set_option(ldserver, LDAP_OPT_PROTOCOL_VERSION, &ctdl_require_ldap_version);
+               i = ldap_simple_bind_s(ldserver, user_dn, password);
+               if (i == LDAP_SUCCESS) {
+                       syslog(LOG_DEBUG, "ldap: bind succeeded");
+               }
+               else {
+                       syslog(LOG_DEBUG, "ldap: Cannot bind: %s (%d)", ldap_err2string(i), i);
+               }
+               ldap_set_option(ldserver, LDAP_OPT_REFERRALS, (void *)LDAP_OPT_OFF);
+               ldap_unbind(ldserver);
+       }
+
+       if (i == LDAP_SUCCESS) {
+               return(0);
+       }
+
+       return(1);
+}
+
+
+// set multiple properties
+// returns nonzero only if property changed.
+int vcard_set_props_iff_different(struct vCard *v, char *propname, int numvals, char **vals) {
+       int i;
+       char *oldval = "";
+       for (i=0; i<numvals; i++) {
+               oldval = vcard_get_prop(v, propname, 0, i, 0);
+               if (oldval == NULL) break;
+               if (strcmp(vals[i],oldval)) break;
+       }
+       if (i != numvals) {
+               syslog(LOG_DEBUG, "ldap: vcard property %s, element %d of %d changed from %s to %s", propname, i, numvals, oldval, vals[i]);
+               for (i=0; i<numvals; i++) {
+                       vcard_set_prop(v,propname,vals[i],(i==0) ? 0 : 1);
+               }
+               return 1;
+       }
+       return 0;
+}
+
+
+// set one property
+// returns nonzero only if property changed.
+int vcard_set_one_prop_iff_different(struct vCard *v,char *propname, char *newfmt, ...) {
+       va_list args;
+       char *newvalue;
+       int did_change = 0;
+       va_start(args,newfmt);
+       if (vasprintf(&newvalue, newfmt, args) < 0) {
+               syslog(LOG_ERR, "ldap: out of memory");
+               return 0;
+       }
+       did_change = vcard_set_props_iff_different(v, propname, 1, &newvalue);
+       va_end(args);
+       free(newvalue);
+       return did_change;
+}
+
+
+// Learn LDAP attributes and stuff them into the vCard.
+// Returns nonzero if we changed anything.
+int Ctdl_LDAP_to_vCard(char *ldap_dn, struct vCard *v) {
+       int changed_something = 0;
+       LDAP *ldserver = NULL;
+       struct timeval tv;
+       LDAPMessage *search_result = NULL;
+       LDAPMessage *entry = NULL;
+       struct berval **givenName;
+       struct berval **sn;
+       struct berval **cn;
+       struct berval **initials;
+       struct berval **o;
+       struct berval **street;
+       struct berval **l;
+       struct berval **st;
+       struct berval **postalCode;
+       struct berval **telephoneNumber;
+       struct berval **mobile;
+       struct berval **homePhone;
+       struct berval **facsimileTelephoneNumber;
+       struct berval **mail;
+       struct berval **uid;
+       struct berval **homeDirectory;
+       struct berval **uidNumber;
+       struct berval **loginShell;
+       struct berval **gidNumber;
+       struct berval **c;
+       struct berval **title;
+       struct berval **uuid;
+       char *attrs[] = { "*","+",NULL};
+
+       if (!ldap_dn) return(0);
+       if (!v) return(0);
+
+       ldserver = ctdl_ldap_bind();
+       if (!ldserver) return(-1);
+
+       tv.tv_sec = 10;
+       tv.tv_usec = 0;
+
+       syslog(LOG_DEBUG, "ldap: search: %s", ldap_dn);
+       syslog(LOG_DEBUG, "ldap: search results: %s", ldap_err2string(ldap_search_ext_s(
+               ldserver,                               // ld
+               ldap_dn,                                // base
+               LDAP_SCOPE_SUBTREE,                     // scope
+               NULL,                                   // filter
+               attrs,                                  // attrs (all attributes)
+               0,                                      // attrsonly (attrs + values)
+               NULL,                                   // serverctrls (none)
+               NULL,                                   // clientctrls (none)
+               &tv,                                    // timeout
+               1,                                      // sizelimit (1 result max)
+               &search_result                          // res
+       )));
+       
+       // Ignore the return value of ldap_search_ext_s().  Sometimes it returns an error even when
+       // the search succeeds.  Instead, we check to see whether search_result is still NULL.
+       if (search_result == NULL) {
+               syslog(LOG_DEBUG, "ldap: zero search results were returned");
+               ldap_unbind(ldserver);
+               return(0);
+       }
+
+       // At this point we've got at least one result from our query.  If there are multiple
+       // results, we still only look at the first one.
+       entry = ldap_first_entry(ldserver, search_result);
+       if (entry) {
+               syslog(LOG_DEBUG, "ldap: search got user details for vcard.");
+               givenName                       = ldap_get_values_len(ldserver, search_result, "givenName");
+               sn                              = ldap_get_values_len(ldserver, search_result, "sn");
+               cn                              = ldap_get_values_len(ldserver, search_result, "cn");
+               initials                        = ldap_get_values_len(ldserver, search_result, "initials");
+               title                           = ldap_get_values_len(ldserver, search_result, "title");
+               o                               = ldap_get_values_len(ldserver, search_result, "o");
+               street                          = ldap_get_values_len(ldserver, search_result, "street");
+               l                               = ldap_get_values_len(ldserver, search_result, "l");
+               st                              = ldap_get_values_len(ldserver, search_result, "st");
+               postalCode                      = ldap_get_values_len(ldserver, search_result, "postalCode");
+               telephoneNumber                 = ldap_get_values_len(ldserver, search_result, "telephoneNumber");
+               mobile                          = ldap_get_values_len(ldserver, search_result, "mobile");
+               homePhone                       = ldap_get_values_len(ldserver, search_result, "homePhone");
+               facsimileTelephoneNumber        = ldap_get_values_len(ldserver, search_result, "facsimileTelephoneNumber");
+               mail                            = ldap_get_values_len(ldserver, search_result, "mail");
+               uid                             = ldap_get_values_len(ldserver, search_result, "uid");
+               homeDirectory                   = ldap_get_values_len(ldserver, search_result, "homeDirectory");
+               uidNumber                       = ldap_get_values_len(ldserver, search_result, "uidNumber");
+               loginShell                      = ldap_get_values_len(ldserver, search_result, "loginShell");
+               gidNumber                       = ldap_get_values_len(ldserver, search_result, "gidNumber");
+               c                               = ldap_get_values_len(ldserver, search_result, "c");
+               uuid                            = ldap_get_values_len(ldserver, search_result, "entryUUID");
+
+               if (street && l && st && postalCode && c) changed_something |= vcard_set_one_prop_iff_different(v,"adr",";;%s;%s;%s;%s;%s",street[0]->bv_val,l[0]->bv_val,st[0]->bv_val,postalCode[0]->bv_val,c[0]->bv_val);
+               if (telephoneNumber) changed_something |= vcard_set_one_prop_iff_different(v,"tel;work","%s",telephoneNumber[0]->bv_val);
+               if (facsimileTelephoneNumber) changed_something |= vcard_set_one_prop_iff_different(v,"tel;fax","%s",facsimileTelephoneNumber[0]->bv_val);
+               if (mobile) changed_something |= vcard_set_one_prop_iff_different(v,"tel;cell","%s",mobile[0]->bv_val);
+               if (homePhone) changed_something |= vcard_set_one_prop_iff_different(v,"tel;home","%s",homePhone[0]->bv_val);
+               if (givenName && sn) {
+                       if (initials) {
+                               changed_something |= vcard_set_one_prop_iff_different(v,"n","%s;%s;%s",sn[0]->bv_val,givenName[0]->bv_val,initials[0]->bv_val);
+                       }
+                       else {
+                               changed_something |= vcard_set_one_prop_iff_different(v,"n","%s;%s",sn[0]->bv_val,givenName[0]->bv_val);
+                       }
+               }
+
+               // FIXME we need a new way to do this.
+               //if (mail) {
+                       //changed_something |= vcard_set_props_iff_different(v,"email;internet",ldap_count_values_len(mail),mail);
+               //}
+
+               if (uuid) changed_something |= vcard_set_one_prop_iff_different(v,"X-uuid","%s",uuid[0]->bv_val);
+               if (o) changed_something |= vcard_set_one_prop_iff_different(v,"org","%s",o[0]->bv_val);
+               if (cn) changed_something |= vcard_set_one_prop_iff_different(v,"fn","%s",cn[0]->bv_val);
+               if (title) changed_something |= vcard_set_one_prop_iff_different(v,"title","%s",title[0]->bv_val);
+               
+               if (givenName)                  ldap_value_free_len(givenName);
+               if (initials)                   ldap_value_free_len(initials);
+               if (sn)                         ldap_value_free_len(sn);
+               if (cn)                         ldap_value_free_len(cn);
+               if (o)                          ldap_value_free_len(o);
+               if (street)                     ldap_value_free_len(street);
+               if (l)                          ldap_value_free_len(l);
+               if (st)                         ldap_value_free_len(st);
+               if (postalCode)                 ldap_value_free_len(postalCode);
+               if (telephoneNumber)            ldap_value_free_len(telephoneNumber);
+               if (mobile)                     ldap_value_free_len(mobile);
+               if (homePhone)                  ldap_value_free_len(homePhone);
+               if (facsimileTelephoneNumber)   ldap_value_free_len(facsimileTelephoneNumber);
+               if (mail)                       ldap_value_free_len(mail);
+               if (uid)                        ldap_value_free_len(uid);
+               if (homeDirectory)              ldap_value_free_len(homeDirectory);
+               if (uidNumber)                  ldap_value_free_len(uidNumber);
+               if (loginShell)                 ldap_value_free_len(loginShell);
+               if (gidNumber)                  ldap_value_free_len(gidNumber);
+               if (c)                          ldap_value_free_len(c);
+               if (title)                      ldap_value_free_len(title);
+               if (uuid)                       ldap_value_free_len(uuid);
+       }
+       // free the results
+       ldap_msgfree(search_result);
+
+       // unbind so we can go back in as the authenticating user
+       ldap_unbind(ldserver);
+       return(changed_something);      // tell the caller whether we made any changes
+}
+
+
+// Extract a user's Internet email addresses from LDAP.
+// Returns zero if we got a valid set of addresses; nonzero for error.
+int extract_email_addresses_from_ldap(char *ldap_dn, char *emailaddrs) {
+       LDAP *ldserver = NULL;
+       struct timeval tv;
+       LDAPMessage *search_result = NULL;
+       LDAPMessage *entry = NULL;
+       struct berval **mail;
+       char *attrs[] = { "*","+",NULL };
+
+       if (!ldap_dn) return(1);
+       if (!emailaddrs) return(1);
+
+       ldserver = ctdl_ldap_bind();
+       if (!ldserver) return(-1);
+
+       tv.tv_sec = 10;
+       tv.tv_usec = 0;
+
+       syslog(LOG_DEBUG, "ldap: search: %s", ldap_dn);
+       syslog(LOG_DEBUG, "ldap: search results: %s", ldap_err2string(ldap_search_ext_s(
+               ldserver,                               // ld
+               ldap_dn,                                // base
+               LDAP_SCOPE_SUBTREE,                     // scope
+               NULL,                                   // filter
+               attrs,                                  // attrs (all attributes)
+               0,                                      // attrsonly (attrs + values)
+               NULL,                                   // serverctrls (none)
+               NULL,                                   // clientctrls (none)
+               &tv,                                    // timeout
+               1,                                      // sizelimit (1 result max)
+               &search_result                          // res
+       )));
+       
+       // Ignore the return value of ldap_search_ext_s().  Sometimes it returns an error even when
+       // the search succeeds.  Instead, we check to see whether search_result is still NULL.
+       if (search_result == NULL) {
+               syslog(LOG_DEBUG, "ldap: zero search results were returned");
+               ldap_unbind(ldserver);
+               return(4);
+       }
+
+       // At this point we've got at least one result from our query.
+       // If there are multiple results, we still only look at the first one.
+       emailaddrs[0] = 0;                                                              // clear out any previous results
+       entry = ldap_first_entry(ldserver, search_result);
+       if (entry) {
+               syslog(LOG_DEBUG, "ldap: search got user details");
+               mail = ldap_get_values_len(ldserver, search_result, "mail");
+               if (mail) {
+                       int q;
+                       for (q=0; q<ldap_count_values_len(mail); ++q) {
+                               if (IsDirectory(mail[q]->bv_val, 0)) {
+                                       if ((strlen(emailaddrs) + mail[q]->bv_len + 2) > 512) {
+                                               syslog(LOG_ERR, "ldap: can't fit all email addresses into user record");
+                                       }
+                                       else {
+                                               if (!IsEmptyStr(emailaddrs)) {
+                                                       strcat(emailaddrs, "|");
+                                               }
+                                               strcat(emailaddrs, mail[q]->bv_val);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       // free the results
+       ldap_msgfree(search_result);
+
+       // unbind so we can go back in as the authenticating user
+       ldap_unbind(ldserver);
+       return(0);
+}
+
+
+// Remember that a particular user exists in the Citadel database.
+// As we scan the LDAP tree we will remove users from this list when we find them.
+// At the end of the scan, any users remaining in this list are stale and should be deleted.
+void ldap_note_user_in_citadel(char *username, void *data) {
+       return;
+}
+
+
+// Scan LDAP for users and populate Citadel's user database with everyone
+//
+// POSIX schema:       All objects of class "inetOrgPerson"
+// Active Directory:   Objects that are class "user" and class "person" but NOT class "computer"
+//
+void CtdlSynchronizeUsersFromLDAP(void) {
+       LDAP *ldserver = NULL;
+       LDAPMessage *search_result = NULL;
+       LDAPMessage *entry = NULL;
+       char *user_dn = NULL;
+       char searchstring[1024];
+       struct timeval tv;
+
+       if ((CtdlGetConfigInt("c_auth_mode") != AUTHMODE_LDAP) && (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_LDAP_AD)) {
+               return;                                         // If this site is not running LDAP, stop here.
+       }
+
+       syslog(LOG_INFO, "ldap: synchronizing Citadel user database from LDAP");
+
+       // first, scan the existing Citadel user list
+       // ForEachUser(ldap_note_user_in_citadel, NULL);        // FIXME finish this
+
+       ldserver = ctdl_ldap_bind();
+       if (!ldserver) return;
+
+       tv.tv_sec = 10;
+       tv.tv_usec = 0;
+
+       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
+                       snprintf(searchstring, sizeof(searchstring), "(&(objectClass=user)(objectClass=person)(!(objectClass=computer)))");
+       }
+       else {
+                       snprintf(searchstring, sizeof(searchstring), "(objectClass=inetOrgPerson)");
+       }
+
+       syslog(LOG_DEBUG, "ldap: search: %s", searchstring);
+       syslog(LOG_DEBUG, "ldap: search results: %s", ldap_err2string(ldap_search_ext_s(
+               ldserver,                                       // ld
+               CtdlGetConfigStr("c_ldap_base_dn"),             // base
+               LDAP_SCOPE_SUBTREE,                             // scope
+               searchstring,                                   // filter
+               NULL,                                           // attrs (all attributes)
+               0,                                              // attrsonly (attrs + values)
+               NULL,                                           // serverctrls (none)
+               NULL,                                           // clientctrls (none)
+               &tv,                                            // timeout
+               INT_MAX,                                        // sizelimit (max)
+               &search_result                                  // put the result here
+       )));
+
+       // Ignore the return value of ldap_search_ext_s().  Sometimes it returns an error even when
+       // the search succeeds.  Instead, we check to see whether search_result is still NULL.
+       if (search_result == NULL) {
+               syslog(LOG_DEBUG, "ldap: zero search results were returned");
+               ldap_unbind(ldserver);
+               return;
+       }
+
+       syslog(LOG_DEBUG, "ldap: %d entries returned", ldap_count_entries(ldserver, search_result));
+       for (entry=ldap_first_entry(ldserver, search_result); entry!=NULL; entry=ldap_next_entry(ldserver, entry)) {
+               user_dn = ldap_get_dn(ldserver, entry);
+               if (user_dn) {
+                       syslog(LOG_DEBUG, "ldap: found %s", user_dn);
+
+                       int fullname_size = 256;
+                       char fullname[256] = { 0 } ;
+                       uid_t uid = (-1);
+                       char new_emailaddrs[512] = { 0 } ;
+
+                       uid = derive_uid_from_ldap(ldserver, entry);
+                       derive_fullname_from_ldap_result(fullname, fullname_size, ldserver, entry);
+                       syslog(LOG_DEBUG, "ldap: display name: <%s> , uid = <%d>", fullname, uid);
+
+                       // now create or update the user
+                       int found_user;
+                       struct ctdluser usbuf;
+
+                       found_user = getuserbyuid(&usbuf, uid);
+                       if (found_user != 0) {
+                               create_user(fullname, CREATE_USER_DO_NOT_BECOME_USER, uid);
+                               found_user = getuserbyuid(&usbuf, uid);
+                               strcpy(fullname, usbuf.fullname);
+                       }
+
+                       if (found_user == 0) {          // user record exists
+                                                       // now update the account email addresses if necessary
+                               if (CtdlGetConfigInt("c_ldap_sync_email_addrs") > 0) {
+                                       if (extract_email_addresses_from_ldap(user_dn, new_emailaddrs) == 0) {
+                                               if (strcmp(usbuf.emailaddrs, new_emailaddrs)) {                         // update only if changed
+                                                       CtdlSetEmailAddressesForUser(usbuf.fullname, new_emailaddrs);
+                                               }
+                                       }
+                               }
+                       }
+                       ldap_memfree(user_dn);
+               }
+       }
+
+       // free the results
+       ldap_msgfree(search_result);
+
+       // unbind so we can go back in as the authenticating user
+       ldap_unbind(ldserver);
+}
diff --git a/citadel/server/locate_host.c b/citadel/server/locate_host.c
new file mode 100644 (file)
index 0000000..64879ee
--- /dev/null
@@ -0,0 +1,362 @@
+/*
+ * Functions which handle hostname/address lookups and resolution
+ *
+ * Copyright (c) 1987-2019 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "sysdep.h"
+#include <string.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <libcitadel.h>
+
+#include "context.h"
+#ifdef HAVE_RESOLV_H
+#include <arpa/nameser.h>
+#ifdef HAVE_ARPA_NAMESER_COMPAT_H
+#include <arpa/nameser_compat.h>
+#endif
+#include <resolv.h>
+#endif
+
+#include "domain.h"
+#include "locate_host.h"
+
+/* START: some missing macros on OpenBSD 3.9 */
+#ifndef NS_CMPRSFLGS
+#define NS_CMPRSFLGS   0xc0
+#endif
+#if !defined(NS_MAXCDNAME) && defined (MAXCDNAME)
+#define NS_MAXCDNAME MAXCDNAME
+#endif
+#if !defined(NS_INT16SZ) && defined(INT16SZ)
+#define NS_INT16SZ INT16SZ
+#define NS_INT32SZ INT32SZ
+#endif
+#ifndef NS_GET16
+#  define NS_GET16 GETSHORT
+#endif
+/* END: some missing macros on OpenBSD 3.9 */
+
+
+/*
+ * Given an open client socket, return the host name and IP address at the other end.
+ * (IPv4 and IPv6 compatible)
+ */
+void locate_host(char *tbuf, size_t n, char *abuf, size_t na, int client_socket)
+{
+       struct sockaddr_in6 clientaddr;
+       unsigned int addrlen = sizeof(clientaddr);
+
+       tbuf[0] = 0;
+       abuf[0] = 0;
+
+       getpeername(client_socket, (struct sockaddr *)&clientaddr, &addrlen);
+       getnameinfo((struct sockaddr *)&clientaddr, addrlen, tbuf, n, NULL, 0, 0);
+       getnameinfo((struct sockaddr *)&clientaddr, addrlen, abuf, na, NULL, 0, NI_NUMERICHOST);
+
+       /* Convert IPv6-mapped IPv4 addresses back to traditional dotted quad.
+        *
+        * Other code here, such as the RBL check, will expect IPv4 addresses to be represented
+        * as dotted-quad, even if they come in over a hybrid IPv6/IPv4 socket.
+        */
+       if ( (strlen(abuf) > 7) && (!strncasecmp(abuf, "::ffff:", 7)) ) {
+               if (!strcmp(abuf, tbuf)) strcpy(tbuf, &tbuf[7]);
+               strcpy(abuf, &abuf[7]);
+       }
+}
+
+
+/*
+ * RBL check written by Edward S. Marshall [http://rblcheck.sourceforge.net]
+ */
+#define RESULT_SIZE 4096 /* What is the longest result text we support? */
+int rblcheck_backend(char *domain, char *txtbuf, int txtbufsize) {
+       int a, b, c;
+       char *result = NULL;
+       u_char fixedans[ PACKETSZ ];
+       u_char *answer;
+       int need_to_free_answer = 0;
+       const u_char *cp;
+       u_char *rp;
+       const u_char *cend;
+       const u_char *rend;
+       int len;
+       char *p = NULL;
+       static int res_initted = 0;
+
+       if (!res_initted) {             /* only have to do this once */
+               res_init();
+               res_initted = 1;
+       }
+
+       /* Make our DNS query. */
+       answer = fixedans;
+       if (server_shutting_down) {
+               if (txtbuf != NULL) {
+                       snprintf(txtbuf, txtbufsize, "System shutting down");
+               }
+               return (1);
+       }
+       len = res_query(domain, C_IN, T_A, answer, PACKETSZ);
+
+       /* Was there a problem? If so, the domain doesn't exist. */
+       if (len == -1) {
+               if (txtbuf != NULL) {
+                       strcpy(txtbuf, "");
+               }
+               return(0);
+       }
+
+       if (len > PACKETSZ) {
+               answer = malloc(len);
+               need_to_free_answer = 1;
+               len = res_query(domain, C_IN, T_A, answer, len);
+               if( len == -1 ) {
+                       if (txtbuf != NULL) {
+                               snprintf(txtbuf, txtbufsize, "Message rejected due to known spammer source IP address");
+                       }
+                       if (need_to_free_answer) free(answer);
+                       return(1);
+               }
+       }
+       if (server_shutting_down) {
+               if (txtbuf != NULL) {
+                       snprintf(txtbuf, txtbufsize, "System shutting down");
+               }
+               if (need_to_free_answer) free(answer);
+               return (1);
+       }
+
+       result = (char *) malloc(RESULT_SIZE);
+       result[0] = '\0';
+
+       /* Make another DNS query for textual data; this shouldn't
+        * be a performance hit, since it'll now be cached at the
+        * nameserver we're using.
+        */
+       len = res_query(domain, C_IN, T_TXT, answer, PACKETSZ);
+       if (server_shutting_down) {
+               if (txtbuf != NULL) {
+                       snprintf(txtbuf, txtbufsize, "System shutting down");
+               }
+               if (need_to_free_answer) free(answer);
+               free(result);
+               return (1);
+       }
+
+       /* Just in case there's no TXT record... */
+       if (len ==(-1)) {
+               if (txtbuf != NULL) {
+                       snprintf(txtbuf, txtbufsize, "Message rejected due to known spammer source IP address");
+               }
+               if (need_to_free_answer) free(answer);
+               free(result);
+               return(1);
+       }
+
+       /* Skip the header and the address we queried. */
+       cp = answer + sizeof( HEADER );
+       while( *cp != '\0' ) {
+               a = *cp++;
+               while( a-- )
+                       cp++;
+       }
+
+       /* This seems to be a bit of magic data that we need to
+        * skip. I wish there were good online documentation
+        * for programming for libresolv, so I'd know what I'm
+        * skipping here. Anyone reading this, feel free to
+        * enlighten me.
+        */
+       cp += 1 + NS_INT16SZ + NS_INT32SZ;
+
+       /* Skip the type, class and ttl. */
+       cp += (NS_INT16SZ * 2) + NS_INT32SZ;
+
+       /* Get the length and end of the buffer. */
+       NS_GET16(c, cp);
+       cend = cp + c;
+
+       /* Iterate over any multiple answers we might have. In
+        * this context, it's unlikely, but anyway.
+        */
+       rp = (u_char *) result;
+       rend = (u_char *) result + RESULT_SIZE - 1;
+       while (cp < cend && rp < rend) {
+               a = *cp++;
+               if (a != 0) {
+                       for (b = a; b > 0 && cp < cend && rp < rend; b--) {
+                               if (*cp == '\n' || *cp == '"' || *cp == '\\') {
+                                       *rp++ = '\\';
+                               }
+                               *rp++ = *cp++;
+                       }
+               }
+       }
+       *rp = '\0';
+       if (txtbuf != NULL) {
+               long len;
+               len = snprintf(txtbuf, txtbufsize, "%s", result);
+       
+               /* Remove nonprintable characters */
+               for (p = txtbuf; *p != '\0'; p++) {
+                       if (!isprint(*p)) {
+                               memmove (p,
+                                        p + 1,
+                                        len - (p - txtbuf) - 1);
+                       }
+               }
+       }
+       if (need_to_free_answer) free(answer);
+       free(result);
+       return(1);
+}
+
+
+/*
+ * Check to see if the client host is on some sort of spam list (RBL)
+ * If spammer, returns nonzero and places reason in 'message_to_spammer'
+ */
+int rbl_check(char *cs_addr, char *message_to_spammer)
+{
+       char tbuf[256] = "";
+       int suffix_pos = 0;
+       int rbl;
+       int rc;
+       int num_rbl;
+       char rbl_domains[SIZ];
+       char txt_answer[1024];
+       struct timeval tx_start;
+       struct timeval tx_finish;
+
+       rc = 0;
+       strcpy(message_to_spammer, "ok");
+       gettimeofday(&tx_start, NULL);          /* start a stopwatch for performance timing */
+
+       if ((strchr(cs_addr, '.')) && (!strchr(cs_addr, ':'))) {
+               int a1, a2, a3, a4;
+
+               sscanf(cs_addr, "%d.%d.%d.%d", &a1, &a2, &a3, &a4);
+               snprintf(tbuf, sizeof tbuf, "%d.%d.%d.%d.", a4, a3, a2, a1);
+               suffix_pos = strlen(tbuf);
+       }
+       else if ((!strchr(cs_addr, '.')) && (strchr(cs_addr, ':'))) {
+               int num_colons = 0;
+               int i = 0;
+               char workbuf[sizeof tbuf];
+               char *ptr;
+
+               /* tedious code to expand and reverse an IPv6 address */
+               safestrncpy(tbuf, cs_addr, sizeof tbuf);
+               num_colons = haschar(tbuf, ':');
+               if ((num_colons < 2) || (num_colons > 7))
+                       goto finish_rbl;        /* badly formed address */
+
+               /* expand the "::" shorthand */
+               while (num_colons < 7) {
+                       ptr = strstr(tbuf, "::");
+                       if (!ptr)
+                               goto finish_rbl;                                /* badly formed address */
+
+                       ++ptr;
+                       strcpy(workbuf, ptr);
+                       strcpy(ptr, ":");
+                       strcat(ptr, workbuf);
+                       ++num_colons;
+               }
+
+               /* expand to 32 hex characters with no colons */
+               strcpy(workbuf, tbuf);
+               strcpy(tbuf, "00000000000000000000000000000000");
+               for (i=0; i<8; ++i) {
+                       char tokbuf[5];
+                       extract_token(tokbuf, workbuf, i, ':', sizeof tokbuf);
+                       memcpy(&tbuf[ (i*4) + (4-strlen(tokbuf)) ], tokbuf, strlen(tokbuf) );
+               }
+               if (strlen(tbuf) != 32) {
+                       goto finish_rbl;
+               }
+
+               /* now reverse it and add dots */
+               strcpy(workbuf, tbuf);
+               for (i=0; i<32; ++i) {
+                       tbuf[i*2] = workbuf[31-i];
+                       tbuf[(i*2)+1] = '.';
+               }
+               tbuf[64] = 0;
+               suffix_pos = 64;
+       }
+       else {
+               goto finish_rbl;        /* unknown address format */
+       }
+
+       /* See if we have any RBL domains configured */
+       num_rbl = get_hosts(rbl_domains, "rbl");
+       if (num_rbl < 1)
+       {
+               goto finish_rbl;
+       }
+
+       /* Try all configured RBL's */
+        for (rbl=0; rbl<num_rbl; ++rbl) {
+                extract_token(&tbuf[suffix_pos], rbl_domains, rbl, '|', (sizeof tbuf - suffix_pos));
+
+               if (rblcheck_backend(tbuf, txt_answer, sizeof txt_answer)) {
+                       strcpy(message_to_spammer, txt_answer);
+                       syslog(LOG_INFO, "RBL: %s %s", cs_addr, txt_answer);
+                       rc = 1;
+               }
+       }
+finish_rbl:
+       /* How long did this transaction take? */
+       gettimeofday(&tx_finish, NULL);
+
+       syslog(LOG_WARNING, "rbl: %s [%ld.%06ld] %s",
+               cs_addr,
+               ((tx_finish.tv_sec*1000000 + tx_finish.tv_usec) - (tx_start.tv_sec*1000000 + tx_start.tv_usec)) / 1000000,
+               ((tx_finish.tv_sec*1000000 + tx_finish.tv_usec) - (tx_start.tv_sec*1000000 + tx_start.tv_usec)) % 1000000,
+               (rc)?"found":"none found"
+       );
+
+       return rc;
+}
+                       
+
+/*
+ * Convert a host name to a dotted quad address. 
+ * Returns zero on success or nonzero on failure.
+ *
+ * FIXME this is obviously not IPv6 compatible.
+ */
+int hostname_to_dotted_quad(char *addr, char *host) {
+       struct hostent *ch;
+       const char *i;
+       int a1, a2, a3, a4;
+
+       ch = gethostbyname(host);
+       if (ch == NULL) {
+               strcpy(addr, "0.0.0.0");
+               return(1);
+       }
+
+       i = (const char *) ch->h_addr_list[0];
+       a1 = ((*i++) & 0xff);
+       a2 = ((*i++) & 0xff);
+       a3 = ((*i++) & 0xff);
+       a4 = ((*i++) & 0xff);
+       sprintf(addr, "%d.%d.%d.%d", a1, a2, a3, a4);
+       return(0);
+}
diff --git a/citadel/server/locate_host.h b/citadel/server/locate_host.h
new file mode 100644 (file)
index 0000000..68f113f
--- /dev/null
@@ -0,0 +1,4 @@
+void locate_host(char *tbuf, size_t n, char *abuf, size_t na, int client_socket);
+int rbl_check(char *cs_addr, char *message_to_spammer);
+int hostname_to_dotted_quad(char *addr, char *host);
+int rblcheck_backend(char *domain, char *txtbuf, int txtbufsize);
diff --git a/citadel/server/modules/autocompletion/.gitignore b/citadel/server/modules/autocompletion/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/autocompletion/serv_autocompletion.c b/citadel/server/modules/autocompletion/serv_autocompletion.c
new file mode 100644 (file)
index 0000000..732bd71
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * Autocompletion of email recipients, etc.
+ *
+ * Copyright (c) 1987-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+
+#include "../../ctdl_module.h"
+#include "serv_autocompletion.h"
+#include "../../config.h"
+
+
+/*
+ * Convert a structured name into a friendly name.  Caller must free the
+ * returned pointer.
+ */
+char *n_to_fn(char *value) {
+       char *nnn = NULL;
+       int i;
+
+       nnn = malloc(strlen(value) + 10);
+       strcpy(nnn, "");
+       extract_token(&nnn[strlen(nnn)] , value, 3, ';', 999);
+       strcat(nnn, " ");
+       extract_token(&nnn[strlen(nnn)] , value, 1, ';', 999);
+       strcat(nnn, " ");
+       extract_token(&nnn[strlen(nnn)] , value, 2, ';', 999);
+       strcat(nnn, " ");
+       extract_token(&nnn[strlen(nnn)] , value, 0, ';', 999);
+       strcat(nnn, " ");
+       extract_token(&nnn[strlen(nnn)] , value, 4, ';', 999);
+       strcat(nnn, " ");
+       for (i=0; i<strlen(nnn); ++i) {
+               if (!strncmp(&nnn[i], "  ", 2)) strcpy(&nnn[i], &nnn[i+1]);
+       }
+       striplt(nnn);
+       return(nnn);
+}
+
+
+
+
+/*
+ * Back end for cmd_auto()
+ */
+void hunt_for_autocomplete(long msgnum, char *search_string) {
+       struct CtdlMessage *msg;
+       struct vCard *v;
+       char *value = NULL;
+       char *value2 = NULL;
+       int i = 0;
+       char *nnn = NULL;
+
+       msg = CtdlFetchMessage(msgnum, 1);
+       if (msg == NULL) return;
+
+       v = vcard_load(msg->cm_fields[eMesageText]);
+       CM_Free(msg);
+
+       /*
+        * Try to match from a friendly name (the "fn" field).  If there is
+        * a match, return the entry in the form of:
+        *     Display Name <user@domain.org>
+        */
+       value = vcard_get_prop(v, "fn", 0, 0, 0);
+       if (value != NULL) if (bmstrcasestr(value, search_string)) {
+               value2 = vcard_get_prop(v, "email", 1, 0, 0);
+               if (value2 == NULL) value2 = "";
+               cprintf("%s <%s>\n", value, value2);
+               vcard_free(v);
+               return;
+       }
+
+       /*
+        * Try to match from a structured name (the "n" field).  If there is
+        * a match, return the entry in the form of:
+        *     Display Name <user@domain.org>
+        */
+       value = vcard_get_prop(v, "n", 0, 0, 0);
+       if (value != NULL) if (bmstrcasestr(value, search_string)) {
+
+               value2 = vcard_get_prop(v, "email", 1, 0, 0);
+               if (value2 == NULL) value2 = "";
+               nnn = n_to_fn(value);
+               cprintf("%s <%s>\n", nnn, value2);
+               free(nnn);
+               vcard_free(v);
+               return;
+       }
+
+       /*
+        * Try a partial match on all listed email addresses.
+        */
+       i = 0;
+       while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
+               if (bmstrcasestr(value, search_string)) {
+                       if (vcard_get_prop(v, "fn", 0, 0, 0)) {
+                               cprintf("%s <%s>\n", vcard_get_prop(v, "fn", 0, 0, 0), value);
+                       }
+                       else if (vcard_get_prop(v, "n", 0, 0, 0)) {
+                               nnn = n_to_fn(vcard_get_prop(v, "n", 0, 0, 0));
+                               cprintf("%s <%s>\n", nnn, value);
+                               free(nnn);
+                       
+                       }
+                       else {
+                               cprintf("%s\n", value);
+                       }
+                       vcard_free(v);
+                       return;
+               }
+       }
+
+       vcard_free(v);
+}
+
+
+
+/*
+ * Attempt to autocomplete an address based on a partial...
+ */
+void cmd_auto(char *argbuf) {
+       char hold_rm[ROOMNAMELEN];
+       char search_string[256];
+       long *msglist = NULL;
+       int num_msgs = 0;
+       long *fts_msgs = NULL;
+       int fts_num_msgs = 0;
+       struct cdbdata *cdbfr;
+       int r = 0;
+       int i = 0;
+       int j = 0;
+       int search_match = 0;
+       char *rooms_to_try[] = { USERCONTACTSROOM, ADDRESS_BOOK_ROOM };
+               
+       if (CtdlAccessCheck(ac_logged_in)) return;
+       extract_token(search_string, argbuf, 0, '|', sizeof search_string);
+       if (IsEmptyStr(search_string)) {
+               cprintf("%d You supplied an empty partial.\n",
+                       ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       strcpy(hold_rm, CC->room.QRname);       /* save current room */
+       cprintf("%d try these:\n", LISTING_FOLLOWS);
+
+       /*
+        * Gather up message pointers in rooms containing vCards
+        */
+       for (r=0; r < (sizeof(rooms_to_try) / sizeof(char *)); ++r) {
+               if (CtdlGetRoom(&CC->room, rooms_to_try[r]) == 0) {
+                       cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+                       if (cdbfr != NULL) {
+                               msglist = realloc(msglist, (num_msgs * sizeof(long)) + cdbfr->len + 1);
+                               memcpy(&msglist[num_msgs], cdbfr->ptr, cdbfr->len);
+                               num_msgs += (cdbfr->len / sizeof(long));
+                               cdb_free(cdbfr);
+                       }
+               }
+       }
+
+       /*
+        * Search-reduce the results if we have the full text index available
+        */
+       if (CtdlGetConfigInt("c_enable_fulltext")) {
+               CtdlModuleDoSearch(&fts_num_msgs, &fts_msgs, search_string, "fulltext");
+               if (fts_msgs) {
+                       for (i=0; i<num_msgs; ++i) {
+                               search_match = 0;
+                               for (j=0; j<fts_num_msgs; ++j) {
+                                       if (msglist[i] == fts_msgs[j]) {
+                                               search_match = 1;
+                                               j = fts_num_msgs + 1;   /* end the search */
+                                       }
+                               }
+                               if (!search_match) {
+                                       msglist[i] = 0;         /* invalidate this result */
+                               }
+                       }
+                       free(fts_msgs);
+               }
+               else {
+                       /* If no results, invalidate the whole list */
+                       free(msglist);
+                       msglist = NULL;
+                       num_msgs = 0;
+               }
+       }
+
+       /*
+        * Now output the ones that look interesting
+        */
+       if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
+               if (msglist[i] != 0) {
+                       hunt_for_autocomplete(msglist[i], search_string);
+               }
+       }
+       
+       cprintf("000\n");
+       if (strcmp(CC->room.QRname, hold_rm)) {
+               CtdlGetRoom(&CC->room, hold_rm);    /* return to saved room */
+       }
+
+       if (msglist) {
+               free(msglist);
+       }
+       
+}
+
+
+char *ctdl_module_init_autocompletion(void) {
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_auto, "AUTO", "Do recipient autocompletion");
+       }
+       /* return our module name for the log */
+       return "autocompletion";
+}
diff --git a/citadel/server/modules/autocompletion/serv_autocompletion.h b/citadel/server/modules/autocompletion/serv_autocompletion.h
new file mode 100644 (file)
index 0000000..9ef0caf
--- /dev/null
@@ -0,0 +1,13 @@
+/* 
+ * Copyright (c) 1987-2012 by the citadel.org team
+ *
+ *  This program is open source software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 3.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+char *serv_autocompletion_init(void);
diff --git a/citadel/server/modules/bio/.gitignore b/citadel/server/modules/bio/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/bio/serv_bio.c b/citadel/server/modules/bio/serv_bio.c
new file mode 100644 (file)
index 0000000..b973bb5
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * This module implements server commands related to the display and
+ * manipulation of user "bio" files.
+ *
+ * Copyright (c) 1987-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../ctdl_module.h"
+#include "../../config.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+
+/*
+ * Command to enter user bio (profile) in plain text.
+ * This is deprecated , or at least it will be when its replacement is written  :)
+ * I want commands to get/set bio in full MIME wonderfulness.
+ */
+void cmd_ebio(char *cmdbuf) {
+       char buf[SIZ];
+
+       unbuffer_output();
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+               return;
+       }
+
+       StrBuf *NewProfile = NewStrBufPlain("Content-type: text/plain; charset=UTF-8\nContent-transfer-encoding: 8bit\n\n", -1);
+
+       cprintf("%d Transmit user profile in plain text now.\n", SEND_LISTING);
+       while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
+               StrBufAppendBufPlain(NewProfile, buf, -1, 0);
+               StrBufAppendBufPlain(NewProfile, HKEY("\n"), 0);
+       }
+
+       /* we have read the new profile from the user , now save it */
+       long old_msgnum = CC->user.msgnum_bio;
+       char userconfigroomname[ROOMNAMELEN];
+       CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &CC->user, USERCONFIGROOM);
+       long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, ChrPtr(NewProfile), FMT_RFC822, "Profile submitted with EBIO command");
+       FreeStrBuf(&NewProfile);
+       CtdlGetUserLock(&CC->user, CC->curr_user);
+       CC->user.msgnum_bio = new_msgnum;
+       CtdlPutUserLock(&CC->user);
+       if (old_msgnum > 0) {
+               syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
+               CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
+       }
+}
+
+
+/*
+ * Command to read user bio (profile) in plain text.
+ * This is deprecated , or at least it will be when its replacement is written  :)
+ * I want commands to get/set bio in full MIME wonderfulness.
+ */
+void cmd_rbio(char *cmdbuf)
+{
+       struct ctdluser ruser;
+       char buf[SIZ];
+
+       extract_token(buf, cmdbuf, 0, '|', sizeof buf);
+       if (CtdlGetUser(&ruser, buf) != 0) {
+               cprintf("%d No such user.\n",ERROR + NO_SUCH_USER);
+               return;
+       }
+
+       cprintf("%d OK|%s|%ld|%d|%ld|%ld|%ld\n", LISTING_FOLLOWS,
+               ruser.fullname, ruser.usernum, ruser.axlevel,
+               (long)ruser.lastcall, ruser.timescalled, ruser.posted);
+
+       struct CtdlMessage *msg = CtdlFetchMessage(ruser.msgnum_bio, 1);
+       if (msg != NULL) {
+               CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_NONE, 0, 0, 0);
+               CM_Free(msg);
+       }
+       cprintf("000\n");
+}
+
+
+/*
+ * Import function called by import_old_bio_files() for a single user
+ */
+void import_one_bio_file(char *username, long usernum, char *path)
+{
+       syslog(LOG_DEBUG, "Import legacy bio for %s, usernum=%ld, filename=%s", username, usernum, path);
+
+       FILE *fp = fopen(path, "r");
+       if (!fp) return;
+
+       fseek(fp, 0, SEEK_END);
+       long data_length = ftell(fp);
+
+       if (data_length >= 1) {
+               rewind(fp);
+               char *unencoded_data = malloc(data_length);
+               if (unencoded_data) {
+                       fread(unencoded_data, data_length, 1, fp);
+                       char *encoded_data = malloc((data_length * 2) + 100);
+                       if (encoded_data) {
+                               sprintf(encoded_data, "Content-type: text/plain; charset=UTF-8\nContent-transfer-encoding: base64\n\n");
+                               CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
+
+                               char userconfigroomname[ROOMNAMELEN];
+                               struct ctdluser usbuf;
+
+                               if (CtdlGetUser(&usbuf, username) == 0) {       // no need to lock it , we are still initializing
+                                       long old_msgnum = usbuf.msgnum_bio;
+                                       CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &usbuf, USERCONFIGROOM);
+                                       long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, encoded_data, FMT_RFC822, "Profile imported from bio");
+                                       syslog(LOG_DEBUG, "Message %ld is now the profile for %s", new_msgnum, username);
+                                       usbuf.msgnum_bio = new_msgnum;
+                                       CtdlPutUser(&usbuf);
+                                       unlink(path);                           // delete the old file , it's in the database now
+                                       if (old_msgnum > 0) {
+                                               syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
+                                               CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
+                                       }
+                               }
+                               free(encoded_data);
+                       }
+                       free(unencoded_data);
+               }
+       }
+       fclose(fp);
+}
+
+
+/*
+ * Look for old-format "bio" files and import them into the message base
+ */
+void import_old_bio_files(void)
+{
+       DIR *filedir = NULL;
+       struct dirent *filedir_entry;
+       size_t d_namelen;
+       struct ctdluser usbuf;
+       long usernum = 0;
+       int d_type = 0;
+       struct stat s;
+       char path[PATH_MAX];
+
+
+       syslog(LOG_DEBUG, "Importing old style bio files into the message base");
+       filedir = opendir("bio");
+       if (filedir == NULL) {
+               return;
+       }
+       while ( (filedir_entry = readdir(filedir)) , (filedir_entry != NULL))
+       {
+#ifdef _DIRENT_HAVE_D_NAMLEN
+               d_namelen = filedir_entry->d_namlen;
+
+#else
+               d_namelen = strlen(filedir_entry->d_name);
+#endif
+
+#ifdef _DIRENT_HAVE_D_TYPE
+               d_type = filedir_entry->d_type;
+#else
+
+#ifndef DT_UNKNOWN
+#define DT_UNKNOWN     0
+#define DT_DIR         4
+#define DT_REG         8
+#define DT_LNK         10
+
+#define IFTODT(mode)   (((mode) & 0170000) >> 12)
+#define DTTOIF(dirtype)        ((dirtype) << 12)
+#endif
+               d_type = DT_UNKNOWN;
+#endif
+               if ((d_namelen == 1) && 
+                   (filedir_entry->d_name[0] == '.'))
+                       continue;
+
+               if ((d_namelen == 2) && 
+                   (filedir_entry->d_name[0] == '.') &&
+                   (filedir_entry->d_name[1] == '.'))
+                       continue;
+
+               snprintf(path, PATH_MAX, "bio/%s", filedir_entry->d_name);
+               if (d_type == DT_UNKNOWN) {
+                       if (lstat(path, &s) == 0) {
+                               d_type = IFTODT(s.st_mode);
+                       }
+               }
+               switch (d_type)
+               {
+               case DT_DIR:
+                       break;
+               case DT_LNK:
+               case DT_REG:
+                       usernum = atol(filedir_entry->d_name);
+                       if (CtdlGetUserByNumber(&usbuf, usernum) == 0) {
+                               import_one_bio_file(usbuf.fullname, usernum, path);
+                       }
+               }
+       }
+       closedir(filedir);
+       rmdir("bio");
+}
+
+
+
+char *ctdl_module_init_bio(void) {
+       if (!threading) {
+               import_old_bio_files();
+               CtdlRegisterProtoHook(cmd_ebio, "EBIO", "Enter your bio");
+               CtdlRegisterProtoHook(cmd_rbio, "RBIO", "Read a user's bio");
+       }
+       /* return our module name for the log */
+        return "bio";
+}
diff --git a/citadel/server/modules/blog/serv_blog.c b/citadel/server/modules/blog/serv_blog.c
new file mode 100644 (file)
index 0000000..e4cfca5
--- /dev/null
@@ -0,0 +1,80 @@
+// Support for blog rooms
+//
+// Copyright (c) 1999-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as published
+// by the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../serv_vcard.h"
+#include "../../citadel_ldap.h"
+#include "../../ctdl_module.h"
+
+// Pre-save hook for saving a message in a blog room.
+// (Do we want to only do this for top-level messages?)
+int blog_upload_beforesave(struct CtdlMessage *msg, struct recptypes *recp) {
+
+       // Only run this hook for blog rooms
+       if (CC->room.QRdefaultview != VIEW_BLOG) {
+               return(0);
+       }
+
+       // If the message doesn't have an EUID, give it one.
+       if (CM_IsEmpty(msg, eExclusiveID)) {
+               char uuid[SIZ];
+               generate_uuid(uuid);
+               CM_SetField(msg, eExclusiveID, uuid, strlen(uuid));
+       }
+
+       // We also want to define a maximum length, whether we generated it or not.
+       CM_CutFieldAt(msg, eExclusiveID, BLOG_EUIDBUF_SIZE - 1);
+       
+       // Now allow the save to complete.
+       return(0);
+}
+
+
+// Module initialization
+char *ctdl_module_init_blog(void) {
+       if (!threading) {
+               CtdlRegisterMessageHook(blog_upload_beforesave, EVT_BEFORESAVE);
+       }
+       
+       // return our module id for the log
+       return "blog";
+}
diff --git a/citadel/server/modules/calendar/.gitignore b/citadel/server/modules/calendar/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/calendar/serv_calendar.c b/citadel/server/modules/calendar/serv_calendar.c
new file mode 100644 (file)
index 0000000..6820458
--- /dev/null
@@ -0,0 +1,2564 @@
+/* 
+ * This module implements iCalendar object processing and the Calendar>
+ * room on a Citadel server.  It handles iCalendar objects using the
+ * iTIP protocol.  See RFCs 2445 and 2446.
+ *
+ * Copyright (c) 1987-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define PRODID "-//Citadel//NONSGML Citadel Calendar//EN"
+
+#include "../../ctdl_module.h"
+#include <libical/ical.h>
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_calendar.h"
+#include "../../room_ops.h"
+#include "../../euidindex.h"
+#include "../../default_timezone.h"
+#include "../../config.h"
+
+struct ical_respond_data {
+       char desired_partnum[SIZ];
+       icalcomponent *cal;
+};
+
+
+/*
+ * Utility function to create a new VCALENDAR component with some of the
+ * required fields already set the way we like them.
+ */
+icalcomponent *icalcomponent_new_citadel_vcalendar(void) {
+       icalcomponent *encaps;
+
+       encaps = icalcomponent_new_vcalendar();
+       if (encaps == NULL) {
+               syslog(LOG_ERR, "calendar: could not allocate component");
+               return NULL;
+       }
+
+       /* Set the Product ID */
+       icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
+
+       /* Set the Version Number */
+       icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
+
+       return(encaps);
+}
+
+
+/*
+ * Utility function to encapsulate a subcomponent into a full VCALENDAR
+ */
+icalcomponent *ical_encapsulate_subcomponent(icalcomponent *subcomp) {
+       icalcomponent *encaps;
+
+       /* If we're already looking at a full VCALENDAR component,
+        * don't bother ... just return itself.
+        */
+       if (icalcomponent_isa(subcomp) == ICAL_VCALENDAR_COMPONENT) {
+               return subcomp;
+       }
+
+       /* Encapsulate the VEVENT component into a complete VCALENDAR */
+       encaps = icalcomponent_new_citadel_vcalendar();
+       if (encaps == NULL) return NULL;
+
+       /* Encapsulate the subcomponent inside */
+       icalcomponent_add_component(encaps, subcomp);
+
+       /* Return the object we just created. */
+       return(encaps);
+}
+
+
+/*
+ * Write a calendar object into the specified user's calendar room.
+ * If the supplied user is NULL, this function writes the calendar object
+ * to the currently selected room.
+ */
+void ical_write_to_cal(struct ctdluser *u, icalcomponent *cal) {
+       char *ser = NULL;
+       long serlen;
+       icalcomponent *encaps = NULL;
+       struct CtdlMessage *msg = NULL;
+       icalcomponent *tmp=NULL;
+
+       if (cal == NULL) return;
+
+       /* If the supplied object is a subcomponent, encapsulate it in
+        * a full VCALENDAR component, and save that instead.
+        */
+       if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
+               tmp = icalcomponent_new_clone(cal);
+               encaps = ical_encapsulate_subcomponent(tmp);
+               ical_write_to_cal(u, encaps);
+               icalcomponent_free(tmp);
+               icalcomponent_free(encaps);
+               return;
+       }
+
+       ser = icalcomponent_as_ical_string_r(cal);
+       if (ser == NULL) return;
+
+       serlen = strlen(ser);
+
+       /* If the caller supplied a user, write to that user's default calendar room */
+       if (u) {
+               /* This handy API function does all the work for us. */
+               CtdlWriteObject(USERCALENDARROOM,       /* which room */
+                       "text/calendar",        /* MIME type */
+                       ser,                    /* data */
+                       serlen + 1,             /* length */
+                       u,                      /* which user */
+                       0,                      /* not binary */
+                       0                       /* no flags */
+               );
+       }
+
+       /* If the caller did not supply a user, write to the currently selected room */
+       if (!u) {
+               struct CitContext *CCC = CC;
+               StrBuf *MsgBody;
+
+               msg = malloc(sizeof(struct CtdlMessage));
+               memset(msg, 0, sizeof(struct CtdlMessage));
+               msg->cm_magic = CTDLMESSAGE_MAGIC;
+               msg->cm_anon_type = MES_NORMAL;
+               msg->cm_format_type = 4;
+               CM_SetField(msg, eAuthor, CCC->user.fullname, strlen(CCC->user.fullname));
+               CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
+
+               MsgBody = NewStrBufPlain(NULL, serlen + 100);
+               StrBufAppendBufPlain(MsgBody, HKEY("Content-type: text/calendar\r\n\r\n"), 0);
+               StrBufAppendBufPlain(MsgBody, ser, serlen, 0);
+
+               CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
+       
+               /* Now write the data */
+               CtdlSubmitMsg(msg, NULL, "");
+               CM_Free(msg);
+       }
+
+       /* In either case, now we can free the serialized calendar object */
+       free(ser);
+}
+
+
+/*
+ * Send a reply to a meeting invitation.
+ *
+ * 'request' is the invitation to reply to.
+ * 'action' is the string "accept" or "decline" or "tentative".
+ *
+ */
+void ical_send_a_reply(icalcomponent *request, char *action) {
+       icalcomponent *the_reply = NULL;
+       icalcomponent *vevent = NULL;
+       icalproperty *attendee = NULL;
+       char attendee_string[SIZ];
+       icalproperty *organizer = NULL;
+       char organizer_string[SIZ];
+       icalproperty *summary = NULL;
+       char summary_string[SIZ];
+       icalproperty *me_attend = NULL;
+       struct recptypes *recp = NULL;
+       icalparameter *partstat = NULL;
+       char *serialized_reply = NULL;
+       char *reply_message_text = NULL;
+       const char *ch;
+       struct CtdlMessage *msg = NULL;
+       struct recptypes *valid = NULL;
+
+       *organizer_string = '\0';
+       strcpy(summary_string, "Calendar item");
+
+       if (request == NULL) {
+               syslog(LOG_ERR, "calendar: trying to reply to NULL event");
+               return;
+       }
+
+       the_reply = icalcomponent_new_clone(request);
+       if (the_reply == NULL) {
+               syslog(LOG_ERR, "calendar: cannot clone request");
+               return;
+       }
+
+       /* Change the method from REQUEST to REPLY */
+       icalcomponent_set_method(the_reply, ICAL_METHOD_REPLY);
+
+       vevent = icalcomponent_get_first_component(the_reply, ICAL_VEVENT_COMPONENT);
+       if (vevent != NULL) {
+               /* Hunt for attendees, removing ones that aren't us.
+                * (Actually, remove them all, cloning our own one so we can
+                * re-insert it later)
+                */
+               while (attendee = icalcomponent_get_first_property(vevent,
+                   ICAL_ATTENDEE_PROPERTY), (attendee != NULL)
+               ) {
+                       ch = icalproperty_get_attendee(attendee);
+                       if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) {
+                               safestrncpy(attendee_string, ch + 7, sizeof (attendee_string));
+                               striplt(attendee_string);
+                               recp = validate_recipients(attendee_string, NULL, 0);
+                               if (recp != NULL) {
+                                       if (!strcasecmp(recp->recp_local, CC->user.fullname)) {
+                                               if (me_attend) icalproperty_free(me_attend);
+                                               me_attend = icalproperty_new_clone(attendee);
+                                       }
+                                       free_recipients(recp);
+                               }
+                       }
+
+                       /* Remove it... */
+                       icalcomponent_remove_property(vevent, attendee);
+                       icalproperty_free(attendee);
+               }
+
+               /* We found our own address in the attendee list. */
+               if (me_attend) {
+                       /* Change the partstat from NEEDS-ACTION to ACCEPT or DECLINE */
+                       icalproperty_remove_parameter_by_kind(me_attend, ICAL_PARTSTAT_PARAMETER);
+
+                       if (!strcasecmp(action, "accept")) {
+                               partstat = icalparameter_new_partstat(ICAL_PARTSTAT_ACCEPTED);
+                       }
+                       else if (!strcasecmp(action, "decline")) {
+                               partstat = icalparameter_new_partstat(ICAL_PARTSTAT_DECLINED);
+                       }
+                       else if (!strcasecmp(action, "tentative")) {
+                               partstat = icalparameter_new_partstat(ICAL_PARTSTAT_TENTATIVE);
+                       }
+
+                       if (partstat) icalproperty_add_parameter(me_attend, partstat);
+
+                       /* Now insert it back into the vevent. */
+                       icalcomponent_add_property(vevent, me_attend);
+               }
+
+               /* Figure out who to send this thing to */
+               organizer = icalcomponent_get_first_property(vevent, ICAL_ORGANIZER_PROPERTY);
+               if (organizer != NULL) {
+                       if (icalproperty_get_organizer(organizer)) {
+                               strcpy(organizer_string,
+                                       icalproperty_get_organizer(organizer) );
+                       }
+               }
+               if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
+                       strcpy(organizer_string, &organizer_string[7]);
+                       striplt(organizer_string);
+               } else {
+                       strcpy(organizer_string, "");
+               }
+
+               /* Extract the summary string -- we'll use it as the
+                * message subject for the reply
+                */
+               summary = icalcomponent_get_first_property(vevent, ICAL_SUMMARY_PROPERTY);
+               if (summary != NULL) {
+                       if (icalproperty_get_summary(summary)) {
+                               strcpy(summary_string,
+                                       icalproperty_get_summary(summary) );
+                       }
+               }
+       }
+
+       /* Now generate the reply message and send it out. */
+       serialized_reply = icalcomponent_as_ical_string_r(the_reply);
+       icalcomponent_free(the_reply);  /* don't need this anymore */
+       if (serialized_reply == NULL) return;
+
+       reply_message_text = malloc(strlen(serialized_reply) + SIZ);
+       if (reply_message_text != NULL) {
+               sprintf(reply_message_text,
+                       "Content-type: text/calendar; charset=\"utf-8\"\r\n\r\n%s\r\n",
+                       serialized_reply
+               );
+
+               msg = CtdlMakeMessage(&CC->user,
+                       organizer_string,       /* to */
+                       "",                     /* cc */
+                       CC->room.QRname, 0, FMT_RFC822,
+                       "",
+                       "",
+                       summary_string,         /* Use summary for subject */
+                       NULL,
+                       reply_message_text,
+                       NULL);
+       
+               if (msg != NULL) {
+                       valid = validate_recipients(organizer_string, NULL, 0);
+                       CtdlSubmitMsg(msg, valid, "");
+                       CM_Free(msg);
+                       free_recipients(valid);
+               }
+       }
+       free(serialized_reply);
+}
+
+
+/*
+ * Callback function for mime parser that hunts for calendar content types
+ * and turns them into calendar objects.  If something is found, it is placed
+ * in ird->cal, and the caller now owns that memory and is responsible for freeing it.
+ */
+void ical_locate_part(char *name, char *filename, char *partnum, char *disp,
+               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+               char *cbid, void *cbuserdata) {
+
+       struct ical_respond_data *ird = NULL;
+
+       ird = (struct ical_respond_data *) cbuserdata;
+
+       /* desired_partnum can be set to "_HUNT_" to have it just look for
+        * the first part with a content type of text/calendar.  Otherwise
+        * we have to only process the right one.
+        */
+       if (strcasecmp(ird->desired_partnum, "_HUNT_")) {
+               if (strcasecmp(partnum, ird->desired_partnum)) {
+                       return;
+               }
+       }
+
+       if (  (strcasecmp(cbtype, "text/calendar"))
+          && (strcasecmp(cbtype, "application/ics")) ) {
+               return;
+       }
+
+       if (ird->cal != NULL) {
+               icalcomponent_free(ird->cal);
+               ird->cal = NULL;
+       }
+
+       ird->cal = icalcomponent_new_from_string(content);
+}
+
+
+/*
+ * Respond to a meeting request.
+ */
+void ical_respond(long msgnum, char *partnum, char *action) {
+       struct CtdlMessage *msg = NULL;
+       struct ical_respond_data ird;
+
+       if (
+          (strcasecmp(action, "accept"))
+          && (strcasecmp(action, "decline"))
+       ) {
+               cprintf("%d Action must be 'accept' or 'decline'\n",
+                       ERROR + ILLEGAL_VALUE
+               );
+               return;
+       }
+
+       msg = CtdlFetchMessage(msgnum, 1);
+       if (msg == NULL) {
+               cprintf("%d Message %ld not found.\n",
+                       ERROR + ILLEGAL_VALUE,
+                       (long)msgnum
+               );
+               return;
+       }
+
+       memset(&ird, 0, sizeof ird);
+       strcpy(ird.desired_partnum, partnum);
+       mime_parser(CM_RANGE(msg, eMesageText),
+                   *ical_locate_part,          /* callback function */
+                   NULL, NULL,
+                   (void *) &ird,                      /* user data */
+                   0
+       );
+
+       /* We're done with the incoming message, because we now have a
+        * calendar object in memory.
+        */
+       CM_Free(msg);
+
+       /*
+        * Here is the real meat of this function.  Handle the event.
+        */
+       if (ird.cal != NULL) {
+               /* Save this in the user's calendar if necessary */
+               if (!strcasecmp(action, "accept")) {
+                       ical_write_to_cal(&CC->user, ird.cal);
+               }
+
+               /* Send a reply if necessary */
+               if (icalcomponent_get_method(ird.cal) == ICAL_METHOD_REQUEST) {
+                       ical_send_a_reply(ird.cal, action);
+               }
+
+               /* We used to delete the invitation after handling it.
+                * We don't do that anymore, but here is the code that handled it:
+                * CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
+                */
+
+               /* Free the memory we allocated and return a response. */
+               icalcomponent_free(ird.cal);
+               ird.cal = NULL;
+               cprintf("%d ok\n", CIT_OK);
+               return;
+       }
+       else {
+               cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
+               return;
+       }
+
+       /* should never get here */
+}
+
+
+/*
+ * Figure out the UID of the calendar event being referred to in a
+ * REPLY object.  This function is recursive.
+ */
+void ical_learn_uid_of_reply(char *uidbuf, icalcomponent *cal) {
+       icalcomponent *subcomponent;
+       icalproperty *p;
+
+       /* If this object is a REPLY, then extract the UID. */
+       if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
+               p = icalcomponent_get_first_property(cal, ICAL_UID_PROPERTY);
+               if (p != NULL) {
+                       strcpy(uidbuf, icalproperty_get_comment(p));
+               }
+       }
+
+       /* Otherwise, recurse through any VEVENT subcomponents.  We do NOT want the
+        * UID of the reply; we want the UID of the invitation being replied to.
+        */
+       for (subcomponent = icalcomponent_get_first_component(cal, ICAL_VEVENT_COMPONENT);
+           subcomponent != NULL;
+           subcomponent = icalcomponent_get_next_component(cal, ICAL_VEVENT_COMPONENT) ) {
+               ical_learn_uid_of_reply(uidbuf, subcomponent);
+       }
+}
+
+
+/*
+ * ical_update_my_calendar_with_reply() refers to this callback function; when we
+ * locate the message containing the calendar event we're replying to, this function
+ * gets called.  It basically just sticks the message number in a supplied buffer.
+ */
+void ical_hunt_for_event_to_update(long msgnum, void *data) {
+       long *msgnumptr;
+
+       msgnumptr = (long *) data;
+       *msgnumptr = msgnum;
+}
+
+
+struct original_event_container {
+       icalcomponent *c;
+};
+
+/*
+ * Callback function for mime parser that hunts for calendar content types
+ * and turns them into calendar objects (called by ical_update_my_calendar_with_reply()
+ * to fetch the object being updated)
+ */
+void ical_locate_original_event(char *name, char *filename, char *partnum, char *disp,
+               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+               char *cbid, void *cbuserdata) {
+
+       struct original_event_container *oec = NULL;
+
+       if (  (strcasecmp(cbtype, "text/calendar"))
+          && (strcasecmp(cbtype, "application/ics")) ) {
+               return;
+       }
+       oec = (struct original_event_container *) cbuserdata;
+       if (oec->c != NULL) {
+               icalcomponent_free(oec->c);
+       }
+       oec->c = icalcomponent_new_from_string(content);
+}
+
+
+/*
+ * Merge updated attendee information from a REPLY into an existing event.
+ */
+void ical_merge_attendee_reply(icalcomponent *event, icalcomponent *reply) {
+       icalcomponent *c;
+       icalproperty *e_attendee, *r_attendee;
+
+       /* First things first.  If we're not looking at a VEVENT component,
+        * recurse through subcomponents until we find one.
+        */
+       if (icalcomponent_isa(event) != ICAL_VEVENT_COMPONENT) {
+               for (c = icalcomponent_get_first_component(event, ICAL_VEVENT_COMPONENT);
+                   c != NULL;
+                   c = icalcomponent_get_next_component(event, ICAL_VEVENT_COMPONENT) ) {
+                       ical_merge_attendee_reply(c, reply);
+               }
+               return;
+       }
+
+       /* Now do the same thing with the reply.
+        */
+       if (icalcomponent_isa(reply) != ICAL_VEVENT_COMPONENT) {
+               for (c = icalcomponent_get_first_component(reply, ICAL_VEVENT_COMPONENT);
+                   c != NULL;
+                   c = icalcomponent_get_next_component(reply, ICAL_VEVENT_COMPONENT) ) {
+                       ical_merge_attendee_reply(event, c);
+               }
+               return;
+       }
+
+       /* Clone the reply, because we're going to rip its guts out. */
+       reply = icalcomponent_new_clone(reply);
+
+       /* At this point we're looking at the correct subcomponents.
+        * Iterate through the attendees looking for a match.
+        */
+STARTOVER:
+       for (e_attendee = icalcomponent_get_first_property(event, ICAL_ATTENDEE_PROPERTY);
+           e_attendee != NULL;
+           e_attendee = icalcomponent_get_next_property(event, ICAL_ATTENDEE_PROPERTY)) {
+
+               for (r_attendee = icalcomponent_get_first_property(reply, ICAL_ATTENDEE_PROPERTY);
+                   r_attendee != NULL;
+                   r_attendee = icalcomponent_get_next_property(reply, ICAL_ATTENDEE_PROPERTY)) {
+
+                       /* Check to see if these two attendees match...
+                        */
+                       const char *e, *r;
+                       e = icalproperty_get_attendee(e_attendee);
+                       r = icalproperty_get_attendee(r_attendee);
+
+                       if ((e != NULL) && 
+                           (r != NULL) && 
+                           !strcasecmp(e, r)) {
+                               /* ...and if they do, remove the attendee from the event
+                                * and replace it with the attendee from the reply.  (The
+                                * reply's copy will have the same address, but an updated
+                                * status.)
+                                */
+                               icalcomponent_remove_property(event, e_attendee);
+                               icalproperty_free(e_attendee);
+                               icalcomponent_remove_property(reply, r_attendee);
+                               icalcomponent_add_property(event, r_attendee);
+
+                               /* Since we diddled both sets of attendees, we have to start
+                                * the iteration over again.  This will not create an infinite
+                                * loop because we removed the attendee from the reply.  (That's
+                                * why we cloned the reply, and that's what we mean by "ripping
+                                * its guts out.")
+                                */
+                               goto STARTOVER;
+                       }
+       
+               }
+       }
+
+       /* Free the *clone* of the reply. */
+       icalcomponent_free(reply);
+}
+
+
+/*
+ * Handle an incoming RSVP (object with method==ICAL_METHOD_REPLY) for a
+ * calendar event.  The object has already been deserialized for us; all
+ * we have to do here is hunt for the event in our calendar, merge in the
+ * updated attendee status, and save it again.
+ *
+ * This function returns 0 on success, 1 if the event was not found in the
+ * user's calendar, or 2 if an internal error occurred.
+ */
+int ical_update_my_calendar_with_reply(icalcomponent *cal) {
+       char uid[SIZ];
+       char hold_rm[ROOMNAMELEN];
+       long msgnum_being_replaced = 0;
+       struct CtdlMessage *msg = NULL;
+       struct original_event_container oec;
+       icalcomponent *original_event;
+       char *serialized_event = NULL;
+       char roomname[ROOMNAMELEN];
+       char *message_text = NULL;
+
+       /* Figure out just what event it is we're dealing with */
+       strcpy(uid, "--==<< InVaLiD uId >>==--");
+       ical_learn_uid_of_reply(uid, cal);
+       syslog(LOG_DEBUG, "calendar: UID of event being replied to is <%s>", uid);
+
+       strcpy(hold_rm, CC->room.QRname);       /* save current room */
+
+       if (CtdlGetRoom(&CC->room, USERCALENDARROOM) != 0) {
+               CtdlGetRoom(&CC->room, hold_rm);
+               syslog(LOG_ERR, "calendar: cannot get user calendar room");
+               return(2);
+       }
+
+       /*
+        * Look in the EUID index for a message with
+        * the Citadel EUID set to the value we're looking for.  Since
+        * Citadel always sets the message EUID to the iCalendar UID of
+        * the event, this will work.
+        */
+       msgnum_being_replaced = CtdlLocateMessageByEuid(uid, &CC->room);
+
+       CtdlGetRoom(&CC->room, hold_rm);        /* return to saved room */
+
+       syslog(LOG_DEBUG, "calendar: msgnum_being_replaced == %ld", msgnum_being_replaced);
+       if (msgnum_being_replaced == 0) {
+               return(1);                      /* no calendar event found */
+       }
+
+       /* Now we know the ID of the message containing the event being updated.
+        * We don't actually have to delete it; that'll get taken care of by the
+        * server when we save another event with the same UID.  This just gives
+        * us the ability to load the event into memory so we can diddle the
+        * attendees.
+        */
+       msg = CtdlFetchMessage(msgnum_being_replaced, 1);
+       if (msg == NULL) {
+               return(2);                      /* internal error */
+       }
+       oec.c = NULL;
+       mime_parser(CM_RANGE(msg, eMesageText),
+                   *ical_locate_original_event,        /* callback function */
+                   NULL, NULL,
+                   &oec,                               /* user data */
+                   0
+       );
+       CM_Free(msg);
+
+       original_event = oec.c;
+       if (original_event == NULL) {
+               syslog(LOG_ERR, "calendar: original_component is NULL");
+               return(2);
+       }
+
+       /* Merge the attendee's updated status into the event */
+       ical_merge_attendee_reply(original_event, cal);
+
+       /* Serialize it */
+       serialized_event = icalcomponent_as_ical_string_r(original_event);
+       icalcomponent_free(original_event);     /* Don't need this anymore. */
+       if (serialized_event == NULL) return(2);
+
+       CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
+
+       message_text = malloc(strlen(serialized_event) + SIZ);
+       if (message_text != NULL) {
+               sprintf(message_text,
+                       "Content-type: text/calendar; charset=\"utf-8\"\r\n\r\n%s\r\n",
+                       serialized_event
+               );
+
+               msg = CtdlMakeMessage(&CC->user,
+                       "",                     /* No recipient */
+                       "",                     /* No recipient */
+                       roomname,
+                       0, FMT_RFC822,
+                       "",
+                       "",
+                       "",             /* no subject */
+                       NULL,
+                       message_text,
+                       NULL);
+       
+               if (msg != NULL) {
+                       CIT_ICAL->avoid_sending_invitations = 1;
+                       CtdlSubmitMsg(msg, NULL, roomname);
+                       CM_Free(msg);
+                       CIT_ICAL->avoid_sending_invitations = 0;
+               }
+       }
+       free(serialized_event);
+       return(0);
+}
+
+
+/*
+ * Handle an incoming RSVP for an event.  (This is the server subcommand part; it
+ * simply extracts the calendar object from the message, deserializes it, and
+ * passes it up to ical_update_my_calendar_with_reply() for processing.
+ */
+void ical_handle_rsvp(long msgnum, char *partnum, char *action) {
+       struct CtdlMessage *msg = NULL;
+       struct ical_respond_data ird;
+       int ret;
+
+       if (
+          (strcasecmp(action, "update"))
+          && (strcasecmp(action, "ignore"))
+       ) {
+               cprintf("%d Action must be 'update' or 'ignore'\n",
+                       ERROR + ILLEGAL_VALUE
+               );
+               return;
+       }
+
+       msg = CtdlFetchMessage(msgnum, 1);
+       if (msg == NULL) {
+               cprintf("%d Message %ld not found.\n",
+                       ERROR + ILLEGAL_VALUE,
+                       (long)msgnum
+               );
+               return;
+       }
+
+       memset(&ird, 0, sizeof ird);
+       strcpy(ird.desired_partnum, partnum);
+       mime_parser(CM_RANGE(msg, eMesageText),
+                   *ical_locate_part,          /* callback function */
+                   NULL, NULL,
+                   (void *) &ird,                      /* user data */
+                   0
+               );
+
+       /* We're done with the incoming message, because we now have a
+        * calendar object in memory.
+        */
+       CM_Free(msg);
+
+       /*
+        * Here is the real meat of this function.  Handle the event.
+        */
+       if (ird.cal != NULL) {
+               /* Update the user's calendar if necessary */
+               if (!strcasecmp(action, "update")) {
+                       ret = ical_update_my_calendar_with_reply(ird.cal);
+                       if (ret == 0) {
+                               cprintf("%d Your calendar has been updated with this reply.\n",
+                                       CIT_OK);
+                       }
+                       else if (ret == 1) {
+                               cprintf("%d This event does not exist in your calendar.\n",
+                                       ERROR + FILE_NOT_FOUND);
+                       }
+                       else {
+                               cprintf("%d An internal error occurred.\n",
+                                       ERROR + INTERNAL_ERROR);
+                       }
+               }
+               else {
+                       cprintf("%d This reply has been ignored.\n", CIT_OK);
+               }
+
+               /* Now that we've processed this message, we don't need it
+                * anymore.  So delete it.  (Don't do this anymore.)
+               CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
+                */
+
+               /* Free the memory we allocated and return a response. */
+               icalcomponent_free(ird.cal);
+               ird.cal = NULL;
+               return;
+       }
+       else {
+               cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
+               return;
+       }
+
+       /* should never get here */
+}
+
+
+/*
+ * Search for a property in both the top level and in a VEVENT subcomponent
+ */
+icalproperty *ical_ctdl_get_subprop(
+               icalcomponent *cal,
+               icalproperty_kind which_prop
+) {
+       icalproperty *p;
+       icalcomponent *c;
+
+       p = icalcomponent_get_first_property(cal, which_prop);
+       if (p == NULL) {
+               c = icalcomponent_get_first_component(cal,
+                                                       ICAL_VEVENT_COMPONENT);
+               if (c != NULL) {
+                       p = icalcomponent_get_first_property(c, which_prop);
+               }
+       }
+       return p;
+}
+
+
+/*
+ * Check to see if two events overlap.  Returns nonzero if they do.
+ * (This function is used in both Citadel and WebCit.  If you change it in
+ * one place, change it in the other.  Better yet, put it in a library.)
+ */
+int ical_ctdl_is_overlap(
+                       struct icaltimetype t1start,
+                       struct icaltimetype t1end,
+                       struct icaltimetype t2start,
+                       struct icaltimetype t2end
+) {
+       if (icaltime_is_null_time(t1start)) return(0);
+       if (icaltime_is_null_time(t2start)) return(0);
+
+       /* if either event lacks end time, assume end = start */
+       if (icaltime_is_null_time(t1end))
+               memcpy(&t1end, &t1start, sizeof(struct icaltimetype));
+       else {
+               if (t1end.is_date && icaltime_compare(t1start, t1end)) {
+                        /*
+                         * the end date is non-inclusive so adjust it by one
+                         * day because our test is inclusive, note that a day is
+                         * not too much because we are talking about all day
+                         * events
+                        * if start = end we assume that nevertheless the whole
+                        * day is meant
+                         */
+                       icaltime_adjust(&t1end, -1, 0, 0, 0);   
+               }
+       }
+
+       if (icaltime_is_null_time(t2end))
+               memcpy(&t2end, &t2start, sizeof(struct icaltimetype));
+       else {
+               if (t2end.is_date && icaltime_compare(t2start, t2end)) {
+                       icaltime_adjust(&t2end, -1, 0, 0, 0);   
+               }
+       }
+
+       /* First, check for all-day events */
+       if (t1start.is_date || t2start.is_date) {
+               /* If event 1 ends before event 2 starts, we're in the clear. */
+               if (icaltime_compare_date_only(t1end, t2start) < 0) return(0);
+
+               /* If event 2 ends before event 1 starts, we're also ok. */
+               if (icaltime_compare_date_only(t2end, t1start) < 0) return(0);
+
+               return(1);
+       }
+
+       /* syslog(LOG_DEBUG, "Comparing t1start %d:%d t1end %d:%d t2start %d:%d t2end %d:%d",
+               t1start.hour, t1start.minute, t1end.hour, t1end.minute,
+               t2start.hour, t2start.minute, t2end.hour, t2end.minute);
+       */
+
+       /* Now check for overlaps using date *and* time. */
+
+       /* If event 1 ends before event 2 starts, we're in the clear. */
+       if (icaltime_compare(t1end, t2start) <= 0) return(0);
+       /* syslog(LOG_DEBUG, "calendar: first passed"); */
+
+       /* If event 2 ends before event 1 starts, we're also ok. */
+       if (icaltime_compare(t2end, t1start) <= 0) return(0);
+       /* syslog(LOG_DEBUG, "calendar: second passed"); */
+
+       /* Otherwise, they overlap. */
+       return(1);
+}
+
+
+/* 
+ * Phase 6 of "hunt for conflicts"
+ * called by ical_conflicts_phase5()
+ *
+ * Now both the proposed and existing events have been boiled down to start and end times.
+ * Check for overlap and output any conflicts.
+ *
+ * Returns nonzero if a conflict was reported.  This allows the caller to stop iterating.
+ */
+int ical_conflicts_phase6(struct icaltimetype t1start,
+                       struct icaltimetype t1end,
+                       struct icaltimetype t2start,
+                       struct icaltimetype t2end,
+                       long existing_msgnum,
+                       char *conflict_event_uid,
+                       char *conflict_event_summary,
+                       char *compare_uid)
+{
+       int conflict_reported = 0;
+
+       /* debugging cruft *
+       time_t tt;
+       tt = icaltime_as_timet_with_zone(t1start, t1start.zone);
+       syslog(LOG_DEBUG, "PROPOSED START: %s", ctime(&tt));
+       tt = icaltime_as_timet_with_zone(t1end, t1end.zone);
+       syslog(LOG_DEBUG, "  PROPOSED END: %s", ctime(&tt));
+       tt = icaltime_as_timet_with_zone(t2start, t2start.zone);
+       syslog(LOG_DEBUG, "EXISTING START: %s", ctime(&tt));
+       tt = icaltime_as_timet_with_zone(t2end, t2end.zone);
+       syslog(LOG_DEBUG, "  EXISTING END: %s", ctime(&tt));
+       * debugging cruft */
+
+       /* compare and output */
+
+       if (ical_ctdl_is_overlap(t1start, t1end, t2start, t2end)) {
+               cprintf("%ld||%s|%s|%d|\n",
+                       existing_msgnum,
+                       conflict_event_uid,
+                       conflict_event_summary,
+                       (       (!IsEmptyStr(compare_uid)
+                               &&(!strcasecmp(compare_uid,
+                               conflict_event_uid))) ? 1 : 0
+                               )
+                       );
+               conflict_reported = 1;
+       }
+
+       return(conflict_reported);
+}
+
+
+/*
+ * Phase 5 of "hunt for conflicts"
+ * Called by ical_conflicts_phase4()
+ *
+ * We have the proposed event boiled down to start and end times.
+ * Now check it against an existing event. 
+ */
+void ical_conflicts_phase5(struct icaltimetype t1start,
+                       struct icaltimetype t1end,
+                       icalcomponent *existing_event,
+                       long existing_msgnum,
+                       char *compare_uid)
+{
+       char conflict_event_uid[SIZ];
+       char conflict_event_summary[SIZ];
+       struct icaltimetype t2start, t2end;
+       icalproperty *p;
+
+       /* recur variables */
+       icalproperty *rrule = NULL;
+       struct icalrecurrencetype recur;
+       icalrecur_iterator *ritr = NULL;
+       struct icaldurationtype dur;
+       int num_recur = 0;
+
+       /* initialization */
+       strcpy(conflict_event_uid, "");
+       strcpy(conflict_event_summary, "");
+       t2start = icaltime_null_time();
+       t2end = icaltime_null_time();
+
+       /* existing event stuff */
+       p = ical_ctdl_get_subprop(existing_event, ICAL_DTSTART_PROPERTY);
+       if (p == NULL) return;
+       if (p != NULL) t2start = icalproperty_get_dtstart(p);
+       if (icaltime_is_utc(t2start)) {
+               t2start.zone = icaltimezone_get_utc_timezone();
+       }
+       else {
+               t2start.zone = icalcomponent_get_timezone(existing_event,
+                       icalparameter_get_tzid(
+                               icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
+                       )
+               );
+               if (!t2start.zone) {
+                       t2start.zone = get_default_icaltimezone();
+               }
+       }
+
+       p = ical_ctdl_get_subprop(existing_event, ICAL_DTEND_PROPERTY);
+       if (p != NULL) {
+               t2end = icalproperty_get_dtend(p);
+
+               if (icaltime_is_utc(t2end)) {
+                       t2end.zone = icaltimezone_get_utc_timezone();
+               }
+               else {
+                       t2end.zone = icalcomponent_get_timezone(existing_event,
+                               icalparameter_get_tzid(
+                                       icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
+                               )
+                       );
+                       if (!t2end.zone) {
+                               t2end.zone = get_default_icaltimezone();
+                       }
+               }
+               dur = icaltime_subtract(t2end, t2start);
+       }
+       else {
+               memset (&dur, 0, sizeof(struct icaldurationtype));
+       }
+
+       rrule = ical_ctdl_get_subprop(existing_event, ICAL_RRULE_PROPERTY);
+       if (rrule) {
+               recur = icalproperty_get_rrule(rrule);
+               ritr = icalrecur_iterator_new(recur, t2start);
+       }
+
+       do {
+               p = ical_ctdl_get_subprop(existing_event, ICAL_UID_PROPERTY);
+               if (p != NULL) {
+                       strcpy(conflict_event_uid, icalproperty_get_comment(p));
+               }
+       
+               p = ical_ctdl_get_subprop(existing_event, ICAL_SUMMARY_PROPERTY);
+               if (p != NULL) {
+                       strcpy(conflict_event_summary, icalproperty_get_comment(p));
+               }
+       
+               if (ical_conflicts_phase6(t1start, t1end, t2start, t2end,
+                  existing_msgnum, conflict_event_uid, conflict_event_summary, compare_uid))
+               {
+                       num_recur = MAX_RECUR + 1;      /* force it out of scope, no need to continue */
+               }
+
+               if (rrule) {
+                       t2start = icalrecur_iterator_next(ritr);
+                       if (!icaltime_is_null_time(t2end)) {
+                               const icaltimezone *hold_zone = t2end.zone;
+                               t2end = icaltime_add(t2start, dur);
+                               t2end.zone = hold_zone;
+                       }
+                       ++num_recur;
+               }
+
+               if (icaltime_compare(t2start, t1end) < 0) {
+                       num_recur = MAX_RECUR + 1;      /* force it out of scope */
+               }
+
+       } while ( (rrule) && (!icaltime_is_null_time(t2start)) && (num_recur < MAX_RECUR) );
+       icalrecur_iterator_free(ritr);
+}
+
+
+/*
+ * Phase 4 of "hunt for conflicts"
+ * Called by ical_hunt_for_conflicts_backend()
+ *
+ * At this point we've got it boiled down to two icalcomponent events in memory.
+ * If they conflict, output something to the client.
+ */
+void ical_conflicts_phase4(icalcomponent *proposed_event,
+               icalcomponent *existing_event,
+               long existing_msgnum)
+{
+       struct icaltimetype t1start, t1end;
+       icalproperty *p;
+       char compare_uid[SIZ];
+
+       /* recur variables */
+       icalproperty *rrule = NULL;
+       struct icalrecurrencetype recur;
+       icalrecur_iterator *ritr = NULL;
+       struct icaldurationtype dur;
+       int num_recur = 0;
+
+       /* initialization */
+       t1end = icaltime_null_time();
+       *compare_uid = '\0';
+
+       /* proposed event stuff */
+
+       p = ical_ctdl_get_subprop(proposed_event, ICAL_DTSTART_PROPERTY);
+       if (p == NULL)
+               return;
+       else
+               t1start = icalproperty_get_dtstart(p);
+
+       if (icaltime_is_utc(t1start)) {
+               t1start.zone = icaltimezone_get_utc_timezone();
+       }
+       else {
+               t1start.zone = icalcomponent_get_timezone(proposed_event,
+                       icalparameter_get_tzid(
+                               icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
+                       )
+               );
+               if (!t1start.zone) {
+                       t1start.zone = get_default_icaltimezone();
+               }
+       }
+       
+       p = ical_ctdl_get_subprop(proposed_event, ICAL_DTEND_PROPERTY);
+       if (p != NULL) {
+               t1end = icalproperty_get_dtend(p);
+
+               if (icaltime_is_utc(t1end)) {
+                       t1end.zone = icaltimezone_get_utc_timezone();
+               }
+               else {
+                       t1end.zone = icalcomponent_get_timezone(proposed_event,
+                               icalparameter_get_tzid(
+                                       icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
+                               )
+                       );
+                       if (!t1end.zone) {
+                               t1end.zone = get_default_icaltimezone();
+                       }
+               }
+
+               dur = icaltime_subtract(t1end, t1start);
+       }
+       else {
+               memset (&dur, 0, sizeof(struct icaldurationtype));
+       }
+
+       rrule = ical_ctdl_get_subprop(proposed_event, ICAL_RRULE_PROPERTY);
+       if (rrule) {
+               recur = icalproperty_get_rrule(rrule);
+               ritr = icalrecur_iterator_new(recur, t1start);
+       }
+
+       p = ical_ctdl_get_subprop(proposed_event, ICAL_UID_PROPERTY);
+       if (p != NULL) {
+               strcpy(compare_uid, icalproperty_get_comment(p));
+       }
+
+       do {
+               ical_conflicts_phase5(t1start, t1end, existing_event, existing_msgnum, compare_uid);
+
+               if (rrule) {
+                       t1start = icalrecur_iterator_next(ritr);
+                       if (!icaltime_is_null_time(t1end)) {
+                               const icaltimezone *hold_zone = t1end.zone;
+                               t1end = icaltime_add(t1start, dur);
+                               t1end.zone = hold_zone;
+                       }
+                       ++num_recur;
+               }
+
+       } while ( (rrule) && (!icaltime_is_null_time(t1start)) && (num_recur < MAX_RECUR) );
+       icalrecur_iterator_free(ritr);
+}
+
+
+/*
+ * Phase 3 of "hunt for conflicts"
+ * Called by ical_hunt_for_conflicts()
+ */
+void ical_hunt_for_conflicts_backend(long msgnum, void *data) {
+       icalcomponent *proposed_event;
+       struct CtdlMessage *msg = NULL;
+       struct ical_respond_data ird;
+
+       proposed_event = (icalcomponent *)data;
+
+       msg = CtdlFetchMessage(msgnum, 1);
+       if (msg == NULL) return;
+       memset(&ird, 0, sizeof ird);
+       strcpy(ird.desired_partnum, "_HUNT_");
+       mime_parser(CM_RANGE(msg, eMesageText),
+                   *ical_locate_part,          /* callback function */
+                   NULL, NULL,
+                   (void *) &ird,                      /* user data */
+                   0
+       );
+       CM_Free(msg);
+
+       if (ird.cal == NULL) return;
+
+       ical_conflicts_phase4(proposed_event, ird.cal, msgnum);
+       icalcomponent_free(ird.cal);
+}
+
+
+/* 
+ * Phase 2 of "hunt for conflicts" operation.
+ * At this point we have a calendar object which represents the VEVENT that
+ * is proposed for addition to the calendar.  Now hunt through the user's
+ * calendar room, and output zero or more existing VEVENTs which conflict
+ * with this one.
+ */
+void ical_hunt_for_conflicts(icalcomponent *cal) {
+       char hold_rm[ROOMNAMELEN];
+
+       strcpy(hold_rm, CC->room.QRname);       /* save current room */
+
+       if (CtdlGetRoom(&CC->room, USERCALENDARROOM) != 0) {
+               CtdlGetRoom(&CC->room, hold_rm);
+               cprintf("%d You do not have a calendar.\n", ERROR + ROOM_NOT_FOUND);
+               return;
+       }
+
+       cprintf("%d Conflicting events:\n", LISTING_FOLLOWS);
+
+       CtdlForEachMessage(MSGS_ALL, 0, NULL,
+               NULL,
+               NULL,
+               ical_hunt_for_conflicts_backend,
+               (void *) cal
+       );
+
+       cprintf("000\n");
+       CtdlGetRoom(&CC->room, hold_rm);        /* return to saved room */
+
+}
+
+
+/*
+ * Hunt for conflicts (Phase 1 -- retrieve the object and call Phase 2)
+ */
+void ical_conflicts(long msgnum, char *partnum) {
+       struct CtdlMessage *msg = NULL;
+       struct ical_respond_data ird;
+
+       msg = CtdlFetchMessage(msgnum, 1);
+       if (msg == NULL) {
+               cprintf("%d Message %ld not found\n",
+                       ERROR + ILLEGAL_VALUE,
+                       (long)msgnum
+               );
+               return;
+       }
+
+       memset(&ird, 0, sizeof ird);
+       strcpy(ird.desired_partnum, partnum);
+       mime_parser(CM_RANGE(msg, eMesageText),
+                   *ical_locate_part,          /* callback function */
+                   NULL, NULL,
+                   (void *) &ird,                      /* user data */
+                   0
+               );
+
+       CM_Free(msg);
+
+       if (ird.cal != NULL) {
+               ical_hunt_for_conflicts(ird.cal);
+               icalcomponent_free(ird.cal);
+               return;
+       }
+
+       cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
+}
+
+
+/*
+ * Look for busy time in a VEVENT and add it to the supplied VFREEBUSY.
+ *
+ * fb                  The VFREEBUSY component to which we are appending
+ * top_level_cal       The top-level VCALENDAR component which contains a VEVENT to be added
+ */
+void ical_add_to_freebusy(icalcomponent *fb, icalcomponent *top_level_cal) {
+       icalcomponent *cal;
+       icalproperty *p;
+       icalvalue *v;
+       struct icalperiodtype this_event_period = icalperiodtype_null_period();
+       icaltimetype dtstart;
+       icaltimetype dtend;
+
+       /* recur variables */
+       icalproperty *rrule = NULL;
+       struct icalrecurrencetype recur;
+       icalrecur_iterator *ritr = NULL;
+       struct icaldurationtype dur;
+       int num_recur = 0;
+
+       if (!top_level_cal) return;
+
+       /* Find the VEVENT component containing an event */
+       cal = icalcomponent_get_first_component(top_level_cal, ICAL_VEVENT_COMPONENT);
+       if (!cal) return;
+
+       /* If this event is not opaque, the user isn't publishing it as
+        * busy time, so don't bother doing anything else.
+        */
+       p = icalcomponent_get_first_property(cal, ICAL_TRANSP_PROPERTY);
+       if (p != NULL) {
+               v = icalproperty_get_value(p);
+               if (v != NULL) {
+                       if (icalvalue_get_transp(v) != ICAL_TRANSP_OPAQUE) {
+                               return;
+                       }
+               }
+       }
+
+       /*
+        * Now begin calculating the event start and end times.
+        */
+       p = icalcomponent_get_first_property(cal, ICAL_DTSTART_PROPERTY);
+       if (!p) return;
+       dtstart = icalproperty_get_dtstart(p);
+
+       if (icaltime_is_utc(dtstart)) {
+               dtstart.zone = icaltimezone_get_utc_timezone();
+       }
+       else {
+               dtstart.zone = icalcomponent_get_timezone(top_level_cal,
+                       icalparameter_get_tzid(
+                               icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
+                       )
+               );
+               if (!dtstart.zone) {
+                       dtstart.zone = get_default_icaltimezone();
+               }
+       }
+
+       dtend = icalcomponent_get_dtend(cal);
+       if (!icaltime_is_null_time(dtend)) {
+               dur = icaltime_subtract(dtend, dtstart);
+       }
+       else {
+               memset (&dur, 0, sizeof(struct icaldurationtype));
+       }
+
+       /* Is a recurrence specified?  If so, get ready to process it... */
+       rrule = ical_ctdl_get_subprop(cal, ICAL_RRULE_PROPERTY);
+       if (rrule) {
+               recur = icalproperty_get_rrule(rrule);
+               ritr = icalrecur_iterator_new(recur, dtstart);
+       }
+
+       do {
+               /* Convert the DTSTART and DTEND properties to an icalperiod. */
+               this_event_period.start = dtstart;
+       
+               if (!icaltime_is_null_time(dtend)) {
+                       this_event_period.end = dtend;
+               }
+
+               /* Convert the timestamps to UTC.  It's ok to do this because we've already expanded
+                * recurrences and this data is never going to get used again.
+                */
+               this_event_period.start = icaltime_convert_to_zone(
+                       this_event_period.start,
+                       icaltimezone_get_utc_timezone()
+               );
+               this_event_period.end = icaltime_convert_to_zone(
+                       this_event_period.end,
+                       icaltimezone_get_utc_timezone()
+               );
+       
+               /* Now add it. */
+               icalcomponent_add_property(fb, icalproperty_new_freebusy(this_event_period));
+
+               /* Make sure the DTSTART property of the freebusy *list* is set to
+                * the DTSTART property of the *earliest event*.
+                */
+               p = icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY);
+               if (p == NULL) {
+                       icalcomponent_set_dtstart(fb, this_event_period.start);
+               }
+               else {
+                       if (icaltime_compare(this_event_period.start, icalcomponent_get_dtstart(fb)) < 0) {
+                               icalcomponent_set_dtstart(fb, this_event_period.start);
+                       }
+               }
+       
+               /* Make sure the DTEND property of the freebusy *list* is set to
+                * the DTEND property of the *latest event*.
+                */
+               p = icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY);
+               if (p == NULL) {
+                       icalcomponent_set_dtend(fb, this_event_period.end);
+               }
+               else {
+                       if (icaltime_compare(this_event_period.end, icalcomponent_get_dtend(fb)) > 0) {
+                               icalcomponent_set_dtend(fb, this_event_period.end);
+                       }
+               }
+
+               if (rrule) {
+                       dtstart = icalrecur_iterator_next(ritr);
+                       if (!icaltime_is_null_time(dtend)) {
+                               dtend = icaltime_add(dtstart, dur);
+                               dtend.zone = dtstart.zone;
+                       }
+                       ++num_recur;
+               }
+
+       } while ( (rrule) && (!icaltime_is_null_time(dtstart)) && (num_recur < MAX_RECUR) ) ;
+       icalrecur_iterator_free(ritr);
+}
+
+
+/*
+ * Backend for ical_freebusy()
+ *
+ * This function simply loads the messages in the user's calendar room,
+ * which contain VEVENTs, then strips them of all non-freebusy data, and
+ * adds them to the supplied VCALENDAR.
+ *
+ */
+void ical_freebusy_backend(long msgnum, void *data) {
+       icalcomponent *fb;
+       struct CtdlMessage *msg = NULL;
+       struct ical_respond_data ird;
+
+       fb = (icalcomponent *)data;             /* User-supplied data will be the VFREEBUSY component */
+
+       msg = CtdlFetchMessage(msgnum, 1);
+       if (msg == NULL) return;
+       memset(&ird, 0, sizeof ird);
+       strcpy(ird.desired_partnum, "_HUNT_");
+       mime_parser(CM_RANGE(msg, eMesageText),
+                   *ical_locate_part,          /* callback function */
+                   NULL, NULL,
+                   (void *) &ird,                      /* user data */
+                   0
+               );
+       CM_Free(msg);
+
+       if (ird.cal) {
+               ical_add_to_freebusy(fb, ird.cal);              /* Add VEVENT times to VFREEBUSY */
+               icalcomponent_free(ird.cal);
+       }
+}
+
+
+/*
+ * Grab another user's free/busy times
+ */
+void ical_freebusy(char *who) {
+       struct ctdluser usbuf;
+       char calendar_room_name[ROOMNAMELEN];
+       char hold_rm[ROOMNAMELEN];
+       char *serialized_request = NULL;
+       icalcomponent *encaps = NULL;
+       icalcomponent *fb = NULL;
+       int found_user = (-1);
+       struct recptypes *recp = NULL;
+       char buf[256];
+       char host[256];
+       char type[256];
+       int i = 0;
+       int config_lines = 0;
+
+       /* First try an exact match. */
+       found_user = CtdlGetUser(&usbuf, who);
+
+       /* If not found, try it as an unqualified email address. */
+       if (found_user != 0) {
+               strcpy(buf, who);
+               recp = validate_recipients(buf, NULL, 0);
+               syslog(LOG_DEBUG, "calendar: trying <%s>", buf);
+               if (recp != NULL) {
+                       if (recp->num_local == 1) {
+                               found_user = CtdlGetUser(&usbuf, recp->recp_local);
+                       }
+                       free_recipients(recp);
+               }
+       }
+
+       /* If still not found, try it as an address qualified with the
+        * primary FQDN of this Citadel node.
+        */
+       if (found_user != 0) {
+               snprintf(buf, sizeof buf, "%s@%s", who, CtdlGetConfigStr("c_fqdn"));
+               syslog(LOG_DEBUG, "calendar: trying <%s>", buf);
+               recp = validate_recipients(buf, NULL, 0);
+               if (recp != NULL) {
+                       if (recp->num_local == 1) {
+                               found_user = CtdlGetUser(&usbuf, recp->recp_local);
+                       }
+                       free_recipients(recp);
+               }
+       }
+
+       /* Still not found?  Try qualifying it with every domain we
+        * might have addresses in.
+        */
+       if (found_user != 0) {
+               config_lines = num_tokens(inetcfg, '\n');
+               for (i=0; ((i < config_lines) && (found_user != 0)); ++i) {
+                       extract_token(buf, inetcfg, i, '\n', sizeof buf);
+                       extract_token(host, buf, 0, '|', sizeof host);
+                       extract_token(type, buf, 1, '|', sizeof type);
+
+                       if ( (!strcasecmp(type, "localhost"))
+                          || (!strcasecmp(type, "directory")) ) {
+                               snprintf(buf, sizeof buf, "%s@%s", who, host);
+                               syslog(LOG_DEBUG, "calendar: trying <%s>", buf);
+                               recp = validate_recipients(buf, NULL, 0);
+                               if (recp != NULL) {
+                                       if (recp->num_local == 1) {
+                                               found_user = CtdlGetUser(&usbuf, recp->recp_local);
+                                       }
+                                       free_recipients(recp);
+                               }
+                       }
+               }
+       }
+
+       if (found_user != 0) {
+               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+               return;
+       }
+
+       CtdlMailboxName(calendar_room_name, sizeof calendar_room_name,
+               &usbuf, USERCALENDARROOM);
+
+       strcpy(hold_rm, CC->room.QRname);       /* save current room */
+
+       if (CtdlGetRoom(&CC->room, calendar_room_name) != 0) {
+               cprintf("%d Cannot open calendar\n", ERROR + ROOM_NOT_FOUND);
+               CtdlGetRoom(&CC->room, hold_rm);
+               return;
+       }
+
+       /* Create a VFREEBUSY subcomponent */
+       syslog(LOG_DEBUG, "calendar: creating VFREEBUSY component");
+       fb = icalcomponent_new_vfreebusy();
+       if (fb == NULL) {
+               cprintf("%d Internal error: cannot allocate memory.\n", ERROR + INTERNAL_ERROR);
+               CtdlGetRoom(&CC->room, hold_rm);
+               return;
+       }
+
+       /* Set the method to PUBLISH */
+       icalcomponent_set_method(fb, ICAL_METHOD_PUBLISH);
+
+       /* Set the DTSTAMP to right now. */
+       icalcomponent_set_dtstamp(fb, icaltime_from_timet_with_zone(time(NULL), 0, icaltimezone_get_utc_timezone()));
+
+       /* Add the user's email address as ORGANIZER */
+       sprintf(buf, "MAILTO:%s", who);
+       if (strchr(buf, '@') == NULL) {
+               strcat(buf, "@");
+               strcat(buf, CtdlGetConfigStr("c_fqdn"));
+       }
+       for (i=0; buf[i]; ++i) {
+               if (buf[i]==' ') buf[i] = '_';
+       }
+       icalcomponent_add_property(fb, icalproperty_new_organizer(buf));
+
+       /* Add busy time from events */
+       syslog(LOG_DEBUG, "calendar: adding busy time from events");
+       CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, ical_freebusy_backend, (void *)fb );
+
+       /* If values for DTSTART and DTEND are still not present, set them
+        * to yesterday and tomorrow as default values.
+        */
+       if (icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY) == NULL) {
+               icalcomponent_set_dtstart(fb, icaltime_from_timet_with_zone(time(NULL)-86400L, 0, icaltimezone_get_utc_timezone()));
+       }
+       if (icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY) == NULL) {
+               icalcomponent_set_dtend(fb, icaltime_from_timet_with_zone(time(NULL)+86400L, 0, icaltimezone_get_utc_timezone()));
+       }
+
+       /* Put the freebusy component into the calendar component */
+       syslog(LOG_DEBUG, "calendar: encapsulating");
+       encaps = ical_encapsulate_subcomponent(fb);
+       if (encaps == NULL) {
+               icalcomponent_free(fb);
+               cprintf("%d Internal error: cannot allocate memory.\n",
+                       ERROR + INTERNAL_ERROR);
+               CtdlGetRoom(&CC->room, hold_rm);
+               return;
+       }
+
+       /* Set the method to PUBLISH */
+       syslog(LOG_DEBUG, "calendar: setting method");
+       icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
+
+       /* Serialize it */
+       syslog(LOG_DEBUG, "calendar: serializing");
+       serialized_request = icalcomponent_as_ical_string_r(encaps);
+       icalcomponent_free(encaps);     /* Don't need this anymore. */
+
+       cprintf("%d Free/busy for %s\n", LISTING_FOLLOWS, usbuf.fullname);
+       if (serialized_request != NULL) {
+               client_write(serialized_request, strlen(serialized_request));
+               free(serialized_request);
+       }
+       cprintf("\n000\n");
+
+       /* Go back to the room from which we came... */
+       CtdlGetRoom(&CC->room, hold_rm);
+}
+
+
+/*
+ * Backend for ical_getics()
+ * 
+ * This is a ForEachMessage() callback function that searches the current room
+ * for calendar events and adds them each into one big calendar component.
+ */
+void ical_getics_backend(long msgnum, void *data) {
+       icalcomponent *encaps, *c;
+       struct CtdlMessage *msg = NULL;
+       struct ical_respond_data ird;
+
+       encaps = (icalcomponent *)data;
+       if (encaps == NULL) return;
+
+       /* Look for the calendar event... */
+
+       msg = CtdlFetchMessage(msgnum, 1);
+       if (msg == NULL) return;
+       memset(&ird, 0, sizeof ird);
+       strcpy(ird.desired_partnum, "_HUNT_");
+       mime_parser(CM_RANGE(msg, eMesageText),
+                   *ical_locate_part,          /* callback function */
+                   NULL, NULL,
+                   (void *) &ird,                      /* user data */
+                   0
+       );
+       CM_Free(msg);
+
+       if (ird.cal == NULL) return;
+
+       /* Here we go: put the VEVENT into the VCALENDAR.  We now no longer
+        * are responsible for "the_request"'s memory -- it will be freed
+        * when we free "encaps".
+        */
+
+       /* If the top-level component is *not* a VCALENDAR, we can drop it right
+        * in.  This will almost never happen.
+        */
+       if (icalcomponent_isa(ird.cal) != ICAL_VCALENDAR_COMPONENT) {
+               icalcomponent_add_component(encaps, ird.cal);
+       }
+       /*
+        * In the more likely event that we're looking at a VCALENDAR with the VEVENT
+        * and other components encapsulated inside, we have to extract them.
+        */
+       else {
+               for (c = icalcomponent_get_first_component(ird.cal, ICAL_ANY_COMPONENT);
+                   (c != NULL);
+                   c = icalcomponent_get_next_component(ird.cal, ICAL_ANY_COMPONENT)) {
+
+                       /* For VTIMEZONE components, suppress duplicates of the same tzid */
+
+                       if (icalcomponent_isa(c) == ICAL_VTIMEZONE_COMPONENT) {
+                               icalproperty *p = icalcomponent_get_first_property(c, ICAL_TZID_PROPERTY);
+                               if (p) {
+                                       const char *tzid = icalproperty_get_tzid(p);
+                                       if (!icalcomponent_get_timezone(encaps, tzid)) {
+                                               icalcomponent_add_component(encaps,
+                                                                       icalcomponent_new_clone(c));
+                                       }
+                               }
+                       }
+
+                       /* All other types of components can go in verbatim */
+                       else {
+                               icalcomponent_add_component(encaps, icalcomponent_new_clone(c));
+                       }
+               }
+               icalcomponent_free(ird.cal);
+       }
+}
+
+
+/*
+ * Retrieve all of the calendar items in the current room, and output them
+ * as a single icalendar object.
+ */
+void ical_getics(void)
+{
+       icalcomponent *encaps = NULL;
+       char *ser = NULL;
+
+       if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
+          &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
+               cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
+               return;         /* Not an iCalendar-centric room */
+       }
+
+       encaps = icalcomponent_new_vcalendar();
+       if (encaps == NULL) {
+               syslog(LOG_ERR, "calendar: could not allocate component!");
+               cprintf("%d Could not allocate memory\n", ERROR+INTERNAL_ERROR);
+               return;
+       }
+
+       cprintf("%d one big calendar\n", LISTING_FOLLOWS);
+
+       /* Set the Product ID */
+       icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
+
+       /* Set the Version Number */
+       icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
+
+       /* Set the method to PUBLISH */
+       icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
+
+       /* Now go through the room encapsulating all calendar items. */
+       CtdlForEachMessage(MSGS_ALL, 0, NULL,
+               NULL,
+               NULL,
+               ical_getics_backend,
+               (void *) encaps
+       );
+
+       ser = icalcomponent_as_ical_string_r(encaps);
+       icalcomponent_free(encaps);                     /* Don't need this anymore. */
+       client_write(ser, strlen(ser));
+       free(ser);
+       cprintf("\n000\n");
+}
+
+
+/*
+ * Helper callback function for ical_putics() to discover which TZID's we need.
+ * Simply put the tzid name string into a hash table.  After the callbacks are
+ * done we'll go through them and attach the ones that we have.
+ */
+void ical_putics_grabtzids(icalparameter *param, void *data)
+{
+       const char *tzid = icalparameter_get_tzid(param);
+       HashList *keys = (HashList *) data;
+       
+       if ( (keys) && (tzid) && (!IsEmptyStr(tzid)) ) {
+               Put(keys, tzid, strlen(tzid), strdup(tzid), NULL);
+       }
+}
+
+
+/*
+ * Delete all of the calendar items in the current room, and replace them
+ * with calendar items from a client-supplied data stream.
+ */
+void ical_putics(void)
+{
+       char *calstream = NULL;
+       icalcomponent *cal;
+       icalcomponent *c;
+       icalcomponent *encaps = NULL;
+       HashList *tzidlist = NULL;
+       HashPos *HashPos;
+       void *Value;
+       const char *Key;
+       long len;
+
+       /* Only allow this operation if we're in a room containing a calendar or tasks view */
+       if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
+          &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
+               cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
+               return;
+       }
+
+       /* Only allow this operation if we have permission to overwrite the existing calendar */
+       if (!CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) {
+               cprintf("%d Permission denied.\n", ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+       }
+
+       cprintf("%d Transmit data now\n", SEND_LISTING);
+       calstream = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
+       if (calstream == NULL) {
+               return;
+       }
+
+       cal = icalcomponent_new_from_string(calstream);
+       free(calstream);
+
+       /* We got our data stream -- now do something with it. */
+
+       /* Delete the existing messages in the room, because we are overwriting
+        * the entire calendar with an entire new (or updated) calendar.
+        * (Careful: this opens an S_ROOMS critical section!)
+        */
+       CtdlDeleteMessages(CC->room.QRname, NULL, 0, "");
+
+       /* If the top-level component is *not* a VCALENDAR, we can drop it right
+        * in.  This will almost never happen.
+        */
+       if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
+               ical_write_to_cal(NULL, cal);
+       }
+       /*
+        * In the more likely event that we're looking at a VCALENDAR with the VEVENT
+        * and other components encapsulated inside, we have to extract them.
+        */
+       else {
+               for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
+                   (c != NULL);
+                   c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
+
+                       /* Non-VTIMEZONE components each get written as individual messages.
+                        * But we also need to attach the relevant VTIMEZONE components to them.
+                        */
+                       if ( (icalcomponent_isa(c) != ICAL_VTIMEZONE_COMPONENT)
+                          && (encaps = icalcomponent_new_vcalendar()) ) {
+                               icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
+                               icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
+                               icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
+
+                               /* Attach any needed timezones here */
+                               tzidlist = NewHash(1, NULL);
+                               if (tzidlist) {
+                                       icalcomponent_foreach_tzid(c, ical_putics_grabtzids, tzidlist);
+                               }
+                               HashPos = GetNewHashPos(tzidlist, 0);
+
+                               while (GetNextHashPos(tzidlist, HashPos, &len, &Key, &Value)) {
+                                       syslog(LOG_DEBUG, "calendar: attaching timezone '%s'", (char*) Value);
+                                       icaltimezone *t = NULL;
+
+                                       /* First look for a timezone attached to the original calendar */
+                                       t = icalcomponent_get_timezone(cal, Value);
+
+                                       /* Try built-in tzdata if the right one wasn't attached */
+                                       if (!t) {
+                                               t = icaltimezone_get_builtin_timezone(Value);
+                                       }
+
+                                       /* I've got a valid timezone to attach. */
+                                       if (t) {
+                                               icalcomponent_add_component(encaps,
+                                                       icalcomponent_new_clone(
+                                                               icaltimezone_get_component(t)
+                                                       )
+                                               );
+                                       }
+
+                               }
+                               DeleteHashPos(&HashPos);
+                               DeleteHash(&tzidlist);
+
+                               /* Now attach the component itself (usually a VEVENT or VTODO) */
+                               icalcomponent_add_component(encaps, icalcomponent_new_clone(c));
+
+                               /* Write it to the message store */
+                               ical_write_to_cal(NULL, encaps);
+                               icalcomponent_free(encaps);
+                       }
+               }
+       }
+
+       icalcomponent_free(cal);
+}
+
+
+/*
+ * All Citadel calendar commands from the client come through here.
+ */
+void cmd_ical(char *argbuf)
+{
+       char subcmd[64];
+       long msgnum;
+       char partnum[256];
+       char action[256];
+       char who[256];
+
+       extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
+
+       /* Allow "test" and "freebusy" subcommands without logging in. */
+
+       if (!strcasecmp(subcmd, "test")) {
+               cprintf("%d This server supports calendaring\n", CIT_OK);
+               return;
+       }
+
+       if (!strcasecmp(subcmd, "freebusy")) {
+               extract_token(who, argbuf, 1, '|', sizeof who);
+               ical_freebusy(who);
+               return;
+       }
+
+       if (!strcasecmp(subcmd, "sgi")) {
+               CIT_ICAL->server_generated_invitations = (extract_int(argbuf, 1) ? 1 : 0) ;
+               cprintf("%d %d\n", CIT_OK, CIT_ICAL->server_generated_invitations);
+               return;
+       }
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       if (!strcasecmp(subcmd, "respond")) {
+               msgnum = extract_long(argbuf, 1);
+               extract_token(partnum, argbuf, 2, '|', sizeof partnum);
+               extract_token(action, argbuf, 3, '|', sizeof action);
+               ical_respond(msgnum, partnum, action);
+               return;
+       }
+
+       if (!strcasecmp(subcmd, "handle_rsvp")) {
+               msgnum = extract_long(argbuf, 1);
+               extract_token(partnum, argbuf, 2, '|', sizeof partnum);
+               extract_token(action, argbuf, 3, '|', sizeof action);
+               ical_handle_rsvp(msgnum, partnum, action);
+               return;
+       }
+
+       if (!strcasecmp(subcmd, "conflicts")) {
+               msgnum = extract_long(argbuf, 1);
+               extract_token(partnum, argbuf, 2, '|', sizeof partnum);
+               ical_conflicts(msgnum, partnum);
+               return;
+       }
+
+       if (!strcasecmp(subcmd, "getics")) {
+               ical_getics();
+               return;
+       }
+
+       if (!strcasecmp(subcmd, "putics")) {
+               ical_putics();
+               return;
+       }
+
+       cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
+}
+
+
+/*
+ * We don't know if the calendar room exists so we just create it at login
+ */
+void ical_CtdlCreateRoom(void)
+{
+       struct ctdlroom qr;
+       visit vbuf;
+
+       /* Create the calendar room if it doesn't already exist */
+       CtdlCreateRoom(USERCALENDARROOM, 4, "", 0, 1, 0, VIEW_CALENDAR);
+
+       /* Set expiration policy to manual; otherwise objects will be lost! */
+       if (CtdlGetRoomLock(&qr, USERCALENDARROOM)) {
+               syslog(LOG_ERR, "calendar: couldn't get the user calendar room");
+               return;
+       }
+       qr.QRep.expire_mode = EXPIRE_MANUAL;
+       qr.QRdefaultview = VIEW_CALENDAR;       /* 3 = calendar view */
+       CtdlPutRoomLock(&qr);
+
+       /* Set the view to a calendar view */
+       CtdlGetRelationship(&vbuf, &CC->user, &qr);
+       vbuf.v_view = VIEW_CALENDAR;
+       CtdlSetRelationship(&vbuf, &CC->user, &qr);
+
+       /* Create the tasks list room if it doesn't already exist */
+       CtdlCreateRoom(USERTASKSROOM, 4, "", 0, 1, 0, VIEW_TASKS);
+
+       /* Set expiration policy to manual; otherwise objects will be lost! */
+       if (CtdlGetRoomLock(&qr, USERTASKSROOM)) {
+               syslog(LOG_ERR, "calendar: couldn't get the user calendar room!");
+               return;
+       }
+       qr.QRep.expire_mode = EXPIRE_MANUAL;
+       qr.QRdefaultview = VIEW_TASKS;
+       CtdlPutRoomLock(&qr);
+
+       /* Set the view to a task list view */
+       CtdlGetRelationship(&vbuf, &CC->user, &qr);
+       vbuf.v_view = VIEW_TASKS;
+       CtdlSetRelationship(&vbuf, &CC->user, &qr);
+
+       /* Create the notes room if it doesn't already exist */
+       CtdlCreateRoom(USERNOTESROOM, 4, "", 0, 1, 0, VIEW_NOTES);
+
+       /* Set expiration policy to manual; otherwise objects will be lost! */
+       if (CtdlGetRoomLock(&qr, USERNOTESROOM)) {
+               syslog(LOG_ERR, "calendar: couldn't get the user calendar room!");
+               return;
+       }
+       qr.QRep.expire_mode = EXPIRE_MANUAL;
+       qr.QRdefaultview = VIEW_NOTES;
+       CtdlPutRoomLock(&qr);
+
+       /* Set the view to a notes view */
+       CtdlGetRelationship(&vbuf, &CC->user, &qr);
+       vbuf.v_view = VIEW_NOTES;
+       CtdlSetRelationship(&vbuf, &CC->user, &qr);
+
+       return;
+}
+
+
+/*
+ * ical_send_out_invitations() is called by ical_saving_vevent() when it finds a VEVENT.
+ *
+ * top_level_cal is the highest available level calendar object.
+ * cal is the subcomponent containing the VEVENT.
+ *
+ * Note: if you change the encapsulation code here, change it in WebCit's ical_encapsulate_subcomponent()
+ */
+void ical_send_out_invitations(icalcomponent *top_level_cal, icalcomponent *cal) {
+       icalcomponent *the_request = NULL;
+       char *serialized_request = NULL;
+       icalcomponent *encaps = NULL;
+       char *request_message_text = NULL;
+       struct CtdlMessage *msg = NULL;
+       struct recptypes *valid = NULL;
+       char attendees_string[SIZ];
+       int num_attendees = 0;
+       char this_attendee[256];
+       icalproperty *attendee = NULL;
+       char summary_string[SIZ];
+       icalproperty *summary = NULL;
+       size_t reqsize;
+       icalproperty *p;
+       struct icaltimetype t;
+       const icaltimezone *attached_zones[5] = { NULL, NULL, NULL, NULL, NULL };
+       int i;
+       const icaltimezone *z;
+       int num_zones_attached = 0;
+       int zone_already_attached;
+       icalparameter *tzidp = NULL;
+       const char *tzidc = NULL;
+
+       if (cal == NULL) {
+               syslog(LOG_ERR, "calendar: trying to reply to NULL event?");
+               return;
+       }
+
+       /* If this is a VCALENDAR component, look for a VEVENT subcomponent. */
+       if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
+               ical_send_out_invitations(top_level_cal,
+                       icalcomponent_get_first_component(
+                               cal, ICAL_VEVENT_COMPONENT
+                       )
+               );
+               return;
+       }
+
+       /* Clone the event */
+       the_request = icalcomponent_new_clone(cal);
+       if (the_request == NULL) {
+               syslog(LOG_ERR, "calendar: cannot clone calendar object");
+               return;
+       }
+
+       /* Extract the summary string -- we'll use it as the
+        * message subject for the request
+        */
+       strcpy(summary_string, "Meeting request");
+       summary = icalcomponent_get_first_property(the_request, ICAL_SUMMARY_PROPERTY);
+       if (summary != NULL) {
+               if (icalproperty_get_summary(summary)) {
+                       strcpy(summary_string,
+                               icalproperty_get_summary(summary) );
+               }
+       }
+
+       /* Determine who the recipients of this message are (the attendees) */
+       strcpy(attendees_string, "");
+       for (attendee = icalcomponent_get_first_property(the_request, ICAL_ATTENDEE_PROPERTY); attendee != NULL; attendee = icalcomponent_get_next_property(the_request, ICAL_ATTENDEE_PROPERTY)) {
+               const char *ch = icalproperty_get_attendee(attendee);
+               if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) {
+                       safestrncpy(this_attendee, ch + 7, sizeof(this_attendee));
+                       
+                       if (!CtdlIsMe(this_attendee, sizeof this_attendee)) {   /* don't send an invitation to myself! */
+                               snprintf(&attendees_string[strlen(attendees_string)],
+                                        sizeof(attendees_string) - strlen(attendees_string),
+                                        "%s, ",
+                                        this_attendee
+                                       );
+                               ++num_attendees;
+                       }
+               }
+       }
+
+       syslog(LOG_DEBUG, "calendar: <%d> attendees: <%s>", num_attendees, attendees_string);
+
+       /* If there are no attendees, there are no invitations to send, so...
+        * don't bother putting one together!  Punch out, Maverick!
+        */
+       if (num_attendees == 0) {
+               icalcomponent_free(the_request);
+               return;
+       }
+
+       /* Encapsulate the VEVENT component into a complete VCALENDAR */
+       encaps = icalcomponent_new_vcalendar();
+       if (encaps == NULL) {
+               syslog(LOG_ERR, "calendar: could not allocate component!");
+               icalcomponent_free(the_request);
+               return;
+       }
+
+       /* Set the Product ID */
+       icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
+
+       /* Set the Version Number */
+       icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
+
+       /* Set the method to REQUEST */
+       icalcomponent_set_method(encaps, ICAL_METHOD_REQUEST);
+
+       /* Look for properties containing timezone parameters, to see if we need to attach VTIMEZONEs */
+       for (p = icalcomponent_get_first_property(the_request, ICAL_ANY_PROPERTY);
+            p != NULL;
+            p = icalcomponent_get_next_property(the_request, ICAL_ANY_PROPERTY))
+       {
+               if ( (icalproperty_isa(p) == ICAL_COMPLETED_PROPERTY)
+                 || (icalproperty_isa(p) == ICAL_CREATED_PROPERTY)
+                 || (icalproperty_isa(p) == ICAL_DATEMAX_PROPERTY)
+                 || (icalproperty_isa(p) == ICAL_DATEMIN_PROPERTY)
+                 || (icalproperty_isa(p) == ICAL_DTEND_PROPERTY)
+                 || (icalproperty_isa(p) == ICAL_DTSTAMP_PROPERTY)
+                 || (icalproperty_isa(p) == ICAL_DTSTART_PROPERTY)
+                 || (icalproperty_isa(p) == ICAL_DUE_PROPERTY)
+                 || (icalproperty_isa(p) == ICAL_EXDATE_PROPERTY)
+                 || (icalproperty_isa(p) == ICAL_LASTMODIFIED_PROPERTY)
+                 || (icalproperty_isa(p) == ICAL_MAXDATE_PROPERTY)
+                 || (icalproperty_isa(p) == ICAL_MINDATE_PROPERTY)
+                 || (icalproperty_isa(p) == ICAL_RECURRENCEID_PROPERTY)
+               ) {
+                       t = icalproperty_get_dtstart(p);        // it's safe to use dtstart for all of them
+
+                       /* Determine the tzid in order for some of the conditions below to work */
+                       tzidp = icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER);
+                       if (tzidp) {
+                               tzidc = icalparameter_get_tzid(tzidp);
+                       }
+                       else {
+                               tzidc = NULL;
+                       }
+
+                       /* First see if there's a timezone attached to the data structure itself */
+                       if (icaltime_is_utc(t)) {
+                               z = icaltimezone_get_utc_timezone();
+                       }
+                       else {
+                               z = icaltime_get_timezone(t);
+                       }
+
+                       /* If not, try to determine the tzid from the parameter using attached zones */
+                       if ((!z) && (tzidc)) {
+                               z = icalcomponent_get_timezone(top_level_cal, tzidc);
+                       }
+
+                       /* Still no good?  Try our internal database */
+                       if ((!z) && (tzidc)) {
+                               z = icaltimezone_get_builtin_timezone_from_tzid(tzidc);
+                       }
+
+                       if (z) {
+                               /* We have a valid timezone.  Good.  Now we need to attach it. */
+
+                               zone_already_attached = 0;
+                               for (i=0; i<5; ++i) {
+                                       if (z == attached_zones[i]) {
+                                               /* We've already got this one, no need to attach another. */
+                                               ++zone_already_attached;
+                                       }
+                               }
+                               if ((!zone_already_attached) && (num_zones_attached < 5)) {
+                                       /* This is a new one, so attach it. */
+                                       attached_zones[num_zones_attached++] = z;
+                               }
+
+                               icalproperty_set_parameter(p, icalparameter_new_tzid(icaltimezone_get_tzid(z))
+                               );
+                       }
+               }
+       }
+
+       /* Encapsulate any timezones we need */
+       if (num_zones_attached > 0) for (i=0; i<num_zones_attached; ++i) {
+               icalcomponent *zc;
+               zc = icalcomponent_new_clone(icaltimezone_get_component(attached_zones[i]));
+               icalcomponent_add_component(encaps, zc);
+       }
+
+       /* Here we go: encapsulate the VEVENT into the VCALENDAR.  We now no longer
+        * are responsible for "the_request"'s memory -- it will be freed
+        * when we free "encaps".
+        */
+       icalcomponent_add_component(encaps, the_request);
+
+       /* Serialize it */
+       serialized_request = icalcomponent_as_ical_string_r(encaps);
+       icalcomponent_free(encaps);     /* Don't need this anymore. */
+       if (serialized_request == NULL) return;
+
+       reqsize = strlen(serialized_request) + SIZ;
+       request_message_text = malloc(reqsize);
+       if (request_message_text != NULL) {
+               snprintf(request_message_text, reqsize,
+                       "Content-type: text/calendar\r\n\r\n%s\r\n",
+                       serialized_request
+               );
+
+               msg = CtdlMakeMessage(
+                       &CC->user,
+                       NULL,                   /* No single recipient here */
+                       NULL,                   /* No single recipient here */
+                       CC->room.QRname,
+                       0,
+                       FMT_RFC822,
+                       NULL,
+                       NULL,
+                       summary_string,         /* Use summary for subject */
+                       NULL,
+                       request_message_text,
+                       NULL
+               );
+       
+               if (msg != NULL) {
+                       valid = validate_recipients(attendees_string, NULL, 0);
+                       CtdlSubmitMsg(msg, valid, "");
+                       CM_Free(msg);
+                       free_recipients(valid);
+               }
+       }
+       free(serialized_request);
+}
+
+
+/*
+ * When a calendar object is being saved, determine whether it's a VEVENT
+ * and the user saving it is the organizer.  If so, send out invitations
+ * to any listed attendees.
+ *
+ * This function is recursive.  The caller can simply supply the same object
+ * as both arguments.  When it recurses it will alter the second argument
+ * while holding on to the top level object.  This allows us to go back and
+ * grab things like time zones which might be attached.
+ *
+ */
+void ical_saving_vevent(icalcomponent *top_level_cal, icalcomponent *cal) {
+       icalcomponent *c;
+       icalproperty *organizer = NULL;
+       char organizer_string[SIZ];
+
+       syslog(LOG_DEBUG, "calendar: ical_saving_vevent() has been called");
+
+       /* Don't send out invitations unless the client wants us to. */
+       if (CIT_ICAL->server_generated_invitations == 0) {
+               return;
+       }
+
+       /* Don't send out invitations if we've been asked not to. */
+       if (CIT_ICAL->avoid_sending_invitations > 0) {
+               return;
+       }
+
+       strcpy(organizer_string, "");
+       /*
+        * The VEVENT subcomponent is the one we're interested in.
+        * Send out invitations if, and only if, this user is the Organizer.
+        */
+       if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
+               organizer = icalcomponent_get_first_property(cal, ICAL_ORGANIZER_PROPERTY);
+               if (organizer != NULL) {
+                       if (icalproperty_get_organizer(organizer)) {
+                               strcpy(organizer_string,
+                                       icalproperty_get_organizer(organizer));
+                       }
+               }
+               if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
+                       strcpy(organizer_string, &organizer_string[7]);
+                       striplt(organizer_string);
+                       /*
+                        * If the user saving the event is listed as the
+                        * organizer, then send out invitations.
+                        */
+                       if (CtdlIsMe(organizer_string, sizeof organizer_string)) {
+                               ical_send_out_invitations(top_level_cal, cal);
+                       }
+               }
+       }
+
+       /* If the component has subcomponents, recurse through them. */
+       for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
+           (c != NULL);
+           c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
+               /* Recursively process subcomponent */
+               ical_saving_vevent(top_level_cal, c);
+       }
+
+}
+
+
+/*
+ * Back end for ical_obj_beforesave()
+ * This hunts for the UID of the calendar event (becomes Citadel msg EUID),
+ * the summary of the event (becomes message subject),
+ * and the start time (becomes message date/time).
+ */
+void ical_obj_beforesave_backend(char *name, char *filename, char *partnum,
+               char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
+               char *encoding, char *cbid, void *cbuserdata)
+{
+       const char* pch;
+       icalcomponent *cal, *nested_event, *nested_todo, *whole_cal;
+       icalproperty *p;
+       char new_uid[256] = "";
+       struct CtdlMessage *msg = (struct CtdlMessage *) cbuserdata;
+
+       if (!msg) return;
+
+       /* We're only interested in calendar data. */
+       if (  (strcasecmp(cbtype, "text/calendar"))
+          && (strcasecmp(cbtype, "application/ics")) ) {
+               return;
+       }
+
+       /* Hunt for the UID and drop it in
+        * the "user data" pointer for the MIME parser.  When
+        * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
+        * to that string.
+        */
+       whole_cal = icalcomponent_new_from_string(content);
+       cal = whole_cal;
+       if (cal != NULL) {
+               if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
+                       nested_event = icalcomponent_get_first_component(
+                               cal, ICAL_VEVENT_COMPONENT);
+                       if (nested_event != NULL) {
+                               cal = nested_event;
+                       }
+                       else {
+                               nested_todo = icalcomponent_get_first_component(
+                                       cal, ICAL_VTODO_COMPONENT);
+                               if (nested_todo != NULL) {
+                                       cal = nested_todo;
+                               }
+                       }
+               }
+               
+               if (cal != NULL) {
+
+                       /* Set the message EUID to the iCalendar UID */
+
+                       p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
+                       if (p == NULL) {
+                               /* If there's no uid we must generate one */
+                               generate_uuid(new_uid);
+                               icalcomponent_add_property(cal, icalproperty_new_uid(new_uid));
+                               p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
+                       }
+                       if (p != NULL) {
+                               pch = icalproperty_get_comment(p);
+                               if (!IsEmptyStr(pch)) {
+                                       CM_SetField(msg, eExclusiveID, pch, strlen(pch));
+                                       syslog(LOG_DEBUG, "calendar: saving calendar UID <%s>", pch);
+                               }
+                       }
+
+                       /* Set the message subject to the iCalendar summary */
+
+                       p = ical_ctdl_get_subprop(cal, ICAL_SUMMARY_PROPERTY);
+                       if (p != NULL) {
+                               pch = icalproperty_get_comment(p);
+                               if (!IsEmptyStr(pch)) {
+                                       char *subj;
+
+                                       subj = rfc2047encode(pch, strlen(pch));
+                                       CM_SetAsField(msg, eMsgSubject, &subj, strlen(subj));
+                               }
+                       }
+
+                       /* Set the message date/time to the iCalendar start time */
+
+                       p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY);
+                       if (p != NULL) {
+                               time_t idtstart;
+                               idtstart = icaltime_as_timet(icalproperty_get_dtstart(p));
+                               if (idtstart > 0) {
+                                       CM_SetFieldLONG(msg, eTimestamp, idtstart);
+                               }
+                       }
+
+               }
+               icalcomponent_free(cal);
+               if (whole_cal != cal) {
+                       icalcomponent_free(whole_cal);
+               }
+       }
+}
+
+
+/*
+ * See if we need to prevent the object from being saved (we don't allow
+ * MIME types other than text/calendar in "calendar" or "tasks" rooms).
+ *
+ * If the message is being saved, we also set various message header fields
+ * using data found in the iCalendar object.
+ */
+int ical_obj_beforesave(struct CtdlMessage *msg, struct recptypes *recp)
+{
+       /* First determine if this is a calendar or tasks room */
+       if (  (CC->room.QRdefaultview != VIEW_CALENDAR)
+          && (CC->room.QRdefaultview != VIEW_TASKS)
+       ) {
+               return(0);              /* Not an iCalendar-centric room */
+       }
+
+       /* It must be an RFC822 message! */
+       if (msg->cm_format_type != 4) {
+               syslog(LOG_DEBUG, "calendar: rejecting non-RFC822 message");
+               return(1);              /* You tried to save a non-RFC822 message! */
+       }
+
+       if (CM_IsEmpty(msg, eMesageText)) {
+               return(1);              /* You tried to save a null message! */
+       }
+
+       /* Do all of our lovely back-end parsing */
+       mime_parser(CM_RANGE(msg, eMesageText),
+                   *ical_obj_beforesave_backend,
+                   NULL, NULL,
+                   (void *)msg,
+                   0
+               );
+
+       return(0);
+}
+
+
+/*
+ * Things we need to do after saving a calendar event.
+ */
+void ical_obj_aftersave_backend(char *name, char *filename, char *partnum,
+               char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
+               char *encoding, char *cbid, void *cbuserdata)
+{
+       icalcomponent *cal;
+
+       /* We're only interested in calendar items here. */
+       if (  (strcasecmp(cbtype, "text/calendar"))
+          && (strcasecmp(cbtype, "application/ics")) ) {
+               return;
+       }
+
+       /* Hunt for the UID and drop it in
+        * the "user data" pointer for the MIME parser.  When
+        * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
+        * to that string.
+        */
+       if (  (!strcasecmp(cbtype, "text/calendar"))
+          || (!strcasecmp(cbtype, "application/ics")) ) {
+               cal = icalcomponent_new_from_string(content);
+               if (cal != NULL) {
+                       ical_saving_vevent(cal, cal);
+                       icalcomponent_free(cal);
+               }
+       }
+}
+
+
+/* 
+ * Things we need to do after saving a calendar event.
+ * (This will start back end tasks such as automatic generation of invitations,
+ * if such actions are appropriate.)
+ */
+int ical_obj_aftersave(struct CtdlMessage *msg, struct recptypes *recp)
+{
+       char roomname[ROOMNAMELEN];
+
+       /*
+        * If this isn't the Calendar> room, no further action is necessary.
+        */
+
+       /* First determine if this is our room */
+       CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
+       if (strcasecmp(roomname, CC->room.QRname)) {
+               return(0);      /* Not the Calendar room -- don't do anything. */
+       }
+
+       /* It must be an RFC822 message! */
+       if (msg->cm_format_type != 4) return(1);
+
+       /* Reject null messages */
+       if (CM_IsEmpty(msg, eMesageText)) return(1);
+       
+       /* Now recurse through it looking for our icalendar data */
+       mime_parser(CM_RANGE(msg, eMesageText),
+                   *ical_obj_aftersave_backend,
+                   NULL, NULL,
+                   NULL,
+                   0
+               );
+
+       return(0);
+}
+
+
+void ical_session_startup(void) {
+       CIT_ICAL = malloc(sizeof(struct cit_ical));
+       memset(CIT_ICAL, 0, sizeof(struct cit_ical));
+}
+
+
+void ical_session_shutdown(void) {
+       free(CIT_ICAL);
+}
+
+
+/*
+ * Back end for ical_fixed_output()
+ */
+void ical_fixed_output_backend(icalcomponent *cal, int recursion_level) {
+       icalcomponent *c;
+       icalproperty *p;
+       char buf[256];
+       const char *ch;
+
+       p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY);
+       if (p != NULL) {
+               cprintf("%s\n", (const char *)icalproperty_get_comment(p));
+       }
+
+       p = icalcomponent_get_first_property(cal, ICAL_LOCATION_PROPERTY);
+       if (p != NULL) {
+               cprintf("%s\n", (const char *)icalproperty_get_comment(p));
+       }
+
+       p = icalcomponent_get_first_property(cal, ICAL_DESCRIPTION_PROPERTY);
+       if (p != NULL) {
+               cprintf("%s\n", (const char *)icalproperty_get_comment(p));
+       }
+
+       /* If the component has attendees, iterate through them. */
+       for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); (p != NULL); p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) {
+               ch =  icalproperty_get_attendee(p);
+               if ((ch != NULL) && 
+                   !strncasecmp(ch, "MAILTO:", 7)) {
+
+                       /* screen name or email address */
+                       safestrncpy(buf, ch + 7, sizeof(buf));
+                       striplt(buf);
+                       cprintf("%s ", buf);
+               }
+               cprintf("\n");
+       }
+
+       /* If the component has subcomponents, recurse through them. */
+       for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
+           (c != 0);
+           c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
+               /* Recursively process subcomponent */
+               ical_fixed_output_backend(c, recursion_level+1);
+       }
+}
+
+
+/*
+ * Function to output iCalendar data as plain text.  Nobody uses MSG0
+ * anymore, so really this is just so we expose the vCard data to the full
+ * text indexer.
+ */
+void ical_fixed_output(char *ptr, int len) {
+       icalcomponent *cal;
+       char *stringy_cal;
+
+       stringy_cal = malloc(len + 1);
+       safestrncpy(stringy_cal, ptr, len + 1);
+       cal = icalcomponent_new_from_string(stringy_cal);
+       free(stringy_cal);
+
+       if (cal == NULL) {
+               return;
+       }
+
+       ical_fixed_output_backend(cal, 0);
+
+       /* Free the memory we obtained from libical's constructor */
+       icalcomponent_free(cal);
+}
+
+
+/*
+ * Register this module with the Citadel server.
+ */
+char *ctdl_module_init_calendar(void) {
+       if (!threading) {
+
+               /* Tell libical to return errors instead of aborting if it gets bad data */
+
+#ifdef LIBICAL_ICAL_EXPORT     // cheap and sleazy way to detect libical >=2.0
+               icalerror_set_errors_are_fatal(0);
+#else
+               icalerror_errors_are_fatal = 0;
+#endif
+
+               /* Use our own application prefix in tzid's generated from system tzdata */
+               icaltimezone_set_tzid_prefix("/citadel.org/");
+
+               /* Initialize our hook functions */
+               CtdlRegisterMessageHook(ical_obj_beforesave, EVT_BEFORESAVE);
+               CtdlRegisterMessageHook(ical_obj_aftersave, EVT_AFTERSAVE);
+               CtdlRegisterSessionHook(ical_CtdlCreateRoom, EVT_LOGIN, PRIO_LOGIN + 1);
+               CtdlRegisterProtoHook(cmd_ical, "ICAL", "Citadel iCalendar commands");
+               CtdlRegisterSessionHook(ical_session_startup, EVT_START, PRIO_START + 1);
+               CtdlRegisterSessionHook(ical_session_shutdown, EVT_STOP, PRIO_STOP + 80);
+               CtdlRegisterFixedOutputHook("text/calendar", ical_fixed_output);
+               CtdlRegisterFixedOutputHook("application/ics", ical_fixed_output);
+       }
+
+       /* return our module name for the log */
+       return "calendar";
+}
diff --git a/citadel/server/modules/calendar/serv_calendar.h b/citadel/server/modules/calendar/serv_calendar.h
new file mode 100644 (file)
index 0000000..d7a149c
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * iCalendar implementation for Citadel
+ *
+ *
+ * Copyright (c) 1987-2012 by the citadel.org team
+ *
+ *  This program is open source software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 3.
+ *  
+ *  
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  
+ *  
+ *  
+ */
+
+/* 
+ * "server_generated_invitations" tells the Citadel server that the
+ * client wants invitations to be generated and sent out by the
+ * server.  Set to 1 to enable this functionality.
+ *
+ * "avoid_sending_invitations" is a server-internal variable.  It is
+ * set internally during certain transactions and cleared
+ * automatically.
+ */
+struct cit_ical {
+       int server_generated_invitations;
+        int avoid_sending_invitations;
+};
+
+#define CIT_ICAL CC->CIT_ICAL
+#define MAX_RECUR 1000
diff --git a/citadel/server/modules/checkpoint/.gitignore b/citadel/server/modules/checkpoint/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/checkpoint/serv_checkpoint.c b/citadel/server/modules/checkpoint/serv_checkpoint.c
new file mode 100644 (file)
index 0000000..090bfd6
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * checkpointing module for the database
+ *
+ * Copyright (c) 1987-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+
+#include <libcitadel.h>
+
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../msgbase.h"
+#include "../../sysdep_decls.h"
+#include "../../config.h"
+#include "../../threads.h"
+#include "../../ctdl_module.h"
+#include "../../context.h"
+
+char *ctdl_module_init_checkpoint(void) {
+       if (threading) {
+               CtdlRegisterSessionHook(cdb_checkpoint, EVT_TIMER, PRIO_CLEANUP + 10);
+       }
+       /* return our module name for the log */
+       return "checkpoint";
+}
diff --git a/citadel/server/modules/clamav/.gitignore b/citadel/server/modules/clamav/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/clamav/serv_virus.c b/citadel/server/modules/clamav/serv_virus.c
new file mode 100644 (file)
index 0000000..2913006
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * This module allows Citadel to use clamd to filter incoming messages
+ * arriving via SMTP.  For more information on clamd, visit
+ * http://clamav.net (the ClamAV project is not in any way
+ * affiliated with the Citadel project).
+ *
+ * Copyright (c) 1987-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define CLAMD_PORT       "3310"
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../domain.h"
+#include "../../clientsocket.h"
+#include "../../ctdl_module.h"
+
+
+/*
+ * Connect to the clamd server and scan a message.
+ */
+int clamd(struct CtdlMessage *msg, struct recptypes *recp) {
+       int sock = (-1);
+       int streamsock = (-1);
+       char clamhosts[SIZ];
+       int num_clamhosts;
+       char buf[SIZ];
+        char hostbuf[SIZ];
+        char portbuf[SIZ];
+       int is_virus = 0;
+       int clamhost;
+       StrBuf *msgtext;
+       CitContext *CCC;
+
+       /* See if we have any clamd hosts configured */
+       num_clamhosts = get_hosts(clamhosts, "clamav");
+       if (num_clamhosts < 1) {
+               return(0);
+       }
+
+       /* Try them one by one until we get a working one */
+        for (clamhost=0; clamhost<num_clamhosts; ++clamhost) {
+                extract_token(buf, clamhosts, clamhost, '|', sizeof buf);
+                syslog(LOG_INFO, "Connecting to clamd at <%s>\n", buf);
+
+                /* Assuming a host:port entry */ 
+                extract_token(hostbuf, buf, 0, ':', sizeof hostbuf);
+                if (extract_token(portbuf, buf, 1, ':', sizeof portbuf)==-1)
+                  /* Didn't specify a port so we'll try the psuedo-standard 3310 */
+                  sock = sock_connect(hostbuf, CLAMD_PORT);
+                else
+                  /* Port specified lets try connecting to it! */
+                  sock = sock_connect(hostbuf, portbuf);
+
+                if (sock >= 0) syslog(LOG_DEBUG, "Connected!\n");
+        }
+
+       if (sock < 0) {
+               /* If the service isn't running, just pass the mail
+                * through.  Potentially throwing away mails isn't good.
+                */
+               return(0);
+       }
+       CCC=CC;
+       CCC->SBuf.Buf = NewStrBuf();
+       CCC->sMigrateBuf = NewStrBuf();
+       CCC->SBuf.ReadWritePointer = NULL;
+
+       /* Command */
+       syslog(LOG_DEBUG, "Transmitting STREAM command\n");
+       sprintf(buf, "STREAM\r\n");
+       sock_write(&sock, buf, strlen(buf));
+
+       syslog(LOG_DEBUG, "Waiting for PORT number\n");
+        if (sock_getln(&sock, buf, sizeof buf) < 0) {
+                goto bail;
+        }
+
+        syslog(LOG_DEBUG, "<%s\n", buf);
+       if (strncasecmp(buf, "PORT", 4)!=0) {
+               goto bail;
+       }
+
+        /* Should have received a port number to connect to */
+       extract_token(portbuf, buf, 1, ' ', sizeof portbuf);
+
+       /* Attempt to establish connection to STREAM socket */
+        streamsock = sock_connect(hostbuf, portbuf);
+
+       if (streamsock < 0) {
+               /* If the service isn't running, just pass the mail
+                * through.  Potentially throwing away mails isn't good.
+                */
+               FreeStrBuf(&CCC->SBuf.Buf);
+               FreeStrBuf(&CCC->sMigrateBuf);
+               return(0);
+        }
+       else {
+               syslog(LOG_DEBUG, "STREAM socket connected!\n");
+       }
+
+
+       /* Message */
+       CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+       CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, 0);
+       msgtext = CC->redirect_buffer;
+       CC->redirect_buffer = NULL;
+
+       sock_write(&streamsock, SKEY(msgtext));
+       FreeStrBuf(&msgtext);
+
+       /* Close the streamsocket connection; this tells clamd
+        * that we're done.
+        */
+       if (streamsock != -1) {
+               close(streamsock);
+       }
+       
+       /* Response */
+       syslog(LOG_DEBUG, "Awaiting response\n");
+        if (sock_getln(&sock, buf, sizeof buf) < 0) {
+                goto bail;
+        }
+        syslog(LOG_DEBUG, "<%s\n", buf);
+       if (strncasecmp(buf, "stream: OK", 10)!=0) {
+               is_virus = 1;
+       }
+
+       if (is_virus) {
+               CM_SetField(msg, eErrorMsg, HKEY("message rejected by virus filter"));
+       }
+
+bail:  close(sock);
+       FreeStrBuf(&CCC->SBuf.Buf);
+       FreeStrBuf(&CCC->sMigrateBuf);
+       return(is_virus);
+}
+
+
+char *ctdl_module_init_virus(void) {
+       if (!threading) {
+               CtdlRegisterMessageHook(clamd, EVT_SMTPSCAN);
+       }
+       
+       /* return our module name for the log */
+        return "virus";
+}
diff --git a/citadel/server/modules/crypto/.gitignore b/citadel/server/modules/crypto/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/crypto/serv_crypto.c b/citadel/server/modules/crypto/serv_crypto.c
new file mode 100644 (file)
index 0000000..4f16092
--- /dev/null
@@ -0,0 +1,619 @@
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include "../../sysdep.h"
+
+#ifdef HAVE_OPENSSL
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#endif
+
+#include <time.h>
+
+#ifdef HAVE_PTHREAD_H
+#include <pthread.h>
+#endif
+
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#include <stdio.h>
+#include <libcitadel.h>
+#include "../../server.h"
+#include "serv_crypto.h"
+#include "../../sysdep_decls.h"
+#include "../../citadel.h"
+#include "../../config.h"
+#include "../../ctdl_module.h"
+
+#ifdef HAVE_OPENSSL
+
+SSL_CTX *ssl_ctx = NULL;               // This SSL context is used for all sessions.
+char *ssl_cipher_list = CIT_CIPHERS;
+
+// If a private key does not exist, generate one now.
+void generate_key(char *keyfilename) {
+       int ret = 0;
+       RSA *rsa = NULL;
+       BIGNUM *bne = NULL;
+       int bits = 2048;
+       unsigned long e = RSA_F4;
+       FILE *fp;
+
+       if (access(keyfilename, R_OK) == 0) {   // Already have one.
+               syslog(LOG_INFO, "crypto: %s exists and is readable", keyfilename);
+               return;
+       }
+
+       syslog(LOG_INFO, "crypto: generating RSA key pair");
+       // generate rsa key
+       bne = BN_new();
+       ret = BN_set_word(bne,e);
+       if (ret != 1) {
+               goto free_all;
+       }
+       rsa = RSA_new();
+       ret = RSA_generate_key_ex(rsa, bits, bne, NULL);
+       if (ret != 1) {
+               goto free_all;
+       }
+
+       // write the key file
+       fp = fopen(keyfilename, "w");
+       if (fp != NULL) {
+               chmod(keyfilename, 0600);
+               if (PEM_write_RSAPrivateKey(fp, // the file
+                                       rsa,    // the key
+                                       NULL,   // no enc
+                                       NULL,   // no passphrase
+                                       0,      // no passphrase
+                                       NULL,   // no callback
+                                       NULL    // no callback
+               ) != 1) {
+                       syslog(LOG_ERR, "crypto: cannot write key: %s", ERR_reason_error_string(ERR_get_error()));
+                       unlink(keyfilename);
+               }
+               fclose(fp);
+       }
+
+       // free the memory we used
+free_all:
+       RSA_free(rsa);
+       BN_free(bne);
+}
+
+
+// If a certificate does not exist, generate a self-signed one now.
+void generate_certificate(char *keyfilename, char *certfilename) {
+       RSA *private_key = NULL;
+       EVP_PKEY *public_key = NULL;
+       X509_REQ *certificate_signing_request = NULL;
+       X509_NAME *name = NULL;
+       X509 *certificate = NULL;
+       FILE *fp;
+
+       if (access(certfilename, R_OK) == 0) {                  // already have one.
+               syslog(LOG_INFO, "crypto: %s exists and is readable", certfilename);
+               return;
+       }
+
+       syslog(LOG_INFO, "crypto: generating a self-signed certificate");
+
+       // Read in our private key.
+       fp = fopen(keyfilename, "r");
+       if (fp) {
+               private_key = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
+               fclose(fp);
+       }
+
+       if (!private_key) {
+               syslog(LOG_ERR, "crypto: cannot read the private key");
+               return;
+       }
+
+       // Create a public key from the private key
+       public_key = EVP_PKEY_new();
+       if (!public_key) {
+               syslog(LOG_ERR, "crypto: cannot allocate public key");
+               RSA_free(private_key);
+               return;
+       }
+       EVP_PKEY_assign_RSA(public_key, private_key);
+
+       // Create a certificate signing request
+       certificate_signing_request = X509_REQ_new();
+       if (!certificate_signing_request) {
+               syslog(LOG_ERR, "crypto: cannot allocate certificate signing request");
+               RSA_free(private_key);
+               EVP_PKEY_free(public_key);
+               return;
+       }
+
+       // Assign the public key to the certificate signing request
+       X509_REQ_set_pubkey(certificate_signing_request, public_key);
+       X509_REQ_set_version(certificate_signing_request, 0L);
+
+       // Tell it who we are
+       name = X509_REQ_get_subject_name(certificate_signing_request);
+       X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned const char *)"ZZ", -1, -1, 0);
+       X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC, (unsigned const char *)"The World", -1, -1, 0);
+       X509_NAME_add_entry_by_txt(name, "L", MBSTRING_ASC, (unsigned const char *)"My Location", -1, -1, 0);
+       X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned const char *)"Generic certificate", -1, -1, 0);
+       X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, (unsigned const char *)"Citadel server", -1, -1, 0);
+       X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned const char *)"*", -1, -1, 0);
+       X509_REQ_set_subject_name(certificate_signing_request, name);
+
+       // Sign the CSR
+       if (!X509_REQ_sign(certificate_signing_request, public_key, EVP_md5())) {
+               syslog(LOG_ERR, "crypto: X509_REQ_sign(): error");
+               X509_REQ_free(certificate_signing_request);
+               RSA_free(private_key);
+               EVP_PKEY_free(public_key);
+               return;
+       }
+
+       // Generate the self-signed certificate
+       certificate = X509_new();
+       if (!certificate) {
+               syslog(LOG_ERR, "crypto: cannot allocate X.509 certificate");
+               X509_REQ_free(certificate_signing_request);
+               RSA_free(private_key);
+               EVP_PKEY_free(public_key);
+               return;
+       }
+
+       ASN1_INTEGER_set(X509_get_serialNumber(certificate), 0);
+       X509_set_issuer_name(certificate, X509_REQ_get_subject_name(certificate_signing_request));
+       X509_set_subject_name(certificate, X509_REQ_get_subject_name(certificate_signing_request));
+       X509_gmtime_adj(X509_get_notBefore(certificate), 0);
+       X509_gmtime_adj(X509_get_notAfter(certificate), (long)60*60*24*SIGN_DAYS);
+       X509_set_pubkey(certificate, public_key);
+       X509_REQ_free(certificate_signing_request);             // We're done with the CSR; free it
+
+       // Finally, sign the certificate with our private key.
+       if (!X509_sign(certificate, public_key, EVP_md5())) {
+               syslog(LOG_ERR, "crypto: X509_sign() error");
+               X509_free(certificate);
+               RSA_free(private_key);
+               EVP_PKEY_free(public_key);
+               return;
+       }
+
+       // Write the certificate to disk
+       fp = fopen(certfilename, "w");
+       if (fp != NULL) {
+               chmod(certfilename, 0600);
+               PEM_write_X509(fp, certificate);
+               fclose(fp);
+       }
+       else {
+               syslog(LOG_ERR, "crypto: %s: %m", certfilename);
+       }
+
+       X509_free(certificate);
+       EVP_PKEY_free(public_key);
+       // do not RSA_free(private_key); because it was freed by EVP_PKEY_free() above
+}
+
+
+// Set the private key and certificate chain for the global SSL Context.
+// This is called during initialization, and can be called again later if the certificate changes.
+void bind_to_key_and_certificate(void) {
+
+       SSL_CTX *old_ctx = NULL;
+       SSL_CTX *new_ctx = NULL;
+
+       const SSL_METHOD *method = SSLv23_server_method();
+       if (!method) {
+               syslog(LOG_ERR, "crypto: SSLv23_server_method() failed: %s", ERR_reason_error_string(ERR_get_error()));
+               return;
+       }
+
+       new_ctx = SSL_CTX_new(method);
+       if (!new_ctx) {
+               syslog(LOG_ERR, "crypto: SSL_CTX_new failed: %s", ERR_reason_error_string(ERR_get_error()));
+               return;
+       }
+
+       if (!(SSL_CTX_set_cipher_list(new_ctx, ssl_cipher_list))) {
+               syslog(LOG_ERR, "crypto: SSL_CTX_set_cipher_list failed: %s", ERR_reason_error_string(ERR_get_error()));
+               return;
+       }
+
+       syslog(LOG_DEBUG, "crypto: using certificate chain %s", file_crpt_file_cer);
+        if (!SSL_CTX_use_certificate_chain_file(new_ctx, file_crpt_file_cer)) {
+               syslog(LOG_ERR, "crypto: SSL_CTX_use_certificate_chain_file failed: %s", ERR_reason_error_string(ERR_get_error()));
+               return;
+       }
+
+       syslog(LOG_DEBUG, "crypto: using private key %s", file_crpt_file_key);
+        if (!SSL_CTX_use_PrivateKey_file(new_ctx, file_crpt_file_key, SSL_FILETYPE_PEM)) {
+               syslog(LOG_ERR, "crypto: SSL_CTX_use_PrivateKey_file failed: %s", ERR_reason_error_string(ERR_get_error()));
+               return;
+       }
+
+       old_ctx = ssl_ctx;
+       ssl_ctx = new_ctx;              // All future binds will use the new certificate
+
+       if (old_ctx != NULL) {
+               sleep(1);               // Hopefully wait until all in-progress binds to the old certificate have completed
+               SSL_CTX_free(old_ctx);
+       }
+}
+
+
+// Check the modification time of the key and certificate files.  Reload if either one changed.
+void update_key_and_cert_if_needed(void) {
+       static time_t previous_mtime = 0;
+       struct stat keystat;
+       struct stat certstat;
+
+       if (stat(file_crpt_file_key, &keystat) != 0) {
+               syslog(LOG_ERR, "%s: %s", file_crpt_file_key, strerror(errno));
+               return;
+       }
+       if (stat(file_crpt_file_cer, &certstat) != 0) {
+               syslog(LOG_ERR, "%s: %s", file_crpt_file_cer, strerror(errno));
+               return;
+       }
+
+       if ((keystat.st_mtime + certstat.st_mtime) != previous_mtime) {
+               begin_critical_section(S_OPENSSL);
+               bind_to_key_and_certificate();
+               end_critical_section(S_OPENSSL);
+               previous_mtime = keystat.st_mtime + certstat.st_mtime;
+       }
+}
+
+
+// Initialize the TLS subsystem.
+void init_ssl(void) {
+
+       // Initialize the OpenSSL library
+       SSL_library_init();
+       SSL_load_error_strings();
+
+       // Load (or generate) a key and certificate
+       mkdir(ctdl_key_dir, 0700);                                      // If the keys directory does not exist, create it
+       generate_key(file_crpt_file_key);                               // If a private key does not exist, create it
+       generate_certificate(file_crpt_file_key, file_crpt_file_cer);   // If a certificate does not exist, create it
+       bind_to_key_and_certificate();                                  // Load key and cert from disk, and bind to them.
+
+       // Register some Citadel protocol commands for dealing with encrypted sessions
+       CtdlRegisterProtoHook(cmd_stls, "STLS", "Start TLS session");
+       CtdlRegisterProtoHook(cmd_gtls, "GTLS", "Get TLS session status");
+       CtdlRegisterSessionHook(endtls, EVT_STOP, PRIO_STOP + 10);
+}
+
+
+// client_write_ssl() Send binary data to the client encrypted.
+void client_write_ssl(const char *buf, int nbytes) {
+       int retval;
+       int nremain;
+       char junk[1];
+
+       nremain = nbytes;
+
+       while (nremain > 0) {
+               if (SSL_want_write(CC->ssl)) {
+                       if ((SSL_read(CC->ssl, junk, 0)) < 1) {
+                               syslog(LOG_DEBUG, "crypto: SSL_read in client_write: %s", ERR_reason_error_string(ERR_get_error()));
+                       }
+               }
+               retval =
+                   SSL_write(CC->ssl, &buf[nbytes - nremain], nremain);
+               if (retval < 1) {
+                       long errval;
+
+                       errval = SSL_get_error(CC->ssl, retval);
+                       if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
+                               sleep(1);
+                               continue;
+                       }
+                       syslog(LOG_DEBUG, "crypto: SSL_write got error %ld, ret %d", errval, retval);
+                       if (retval == -1) {
+                               syslog(LOG_DEBUG, "crypto: errno is %d", errno);
+                       }
+                       endtls();
+                       client_write(&buf[nbytes - nremain], nremain);
+                       return;
+               }
+               nremain -= retval;
+       }
+}
+
+
+// read data from the encrypted layer.
+int client_read_sslbuffer(StrBuf *buf, int timeout) {
+       char sbuf[16384];       // OpenSSL communicates in 16k blocks, so let's speak its native tongue.
+       int rlen;
+       char junk[1];
+       SSL *pssl = CC->ssl;
+
+       if (pssl == NULL) return(-1);
+
+       while (1) {
+               if (SSL_want_read(pssl)) {
+                       if ((SSL_write(pssl, junk, 0)) < 1) {
+                               syslog(LOG_DEBUG, "crypto: SSL_write in client_read");
+                       }
+               }
+               rlen = SSL_read(pssl, sbuf, sizeof(sbuf));
+               if (rlen < 1) {
+                       long errval;
+
+                       errval = SSL_get_error(pssl, rlen);
+                       if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
+                               sleep(1);
+                               continue;
+                       }
+                       syslog(LOG_DEBUG, "crypto: SSL_read got error %ld", errval);
+                       endtls();
+                       return (-1);
+               }
+               StrBufAppendBufPlain(buf, sbuf, rlen, 0);
+               return rlen;
+       }
+       return (0);
+}
+
+
+int client_readline_sslbuffer(StrBuf *Line, StrBuf *IOBuf, const char **Pos, int timeout) {
+       const char *pos = NULL;
+       const char *pLF;
+       int len, rlen;
+       int nSuccessLess = 0;
+       const char *pch = NULL;
+       
+       if ((Line == NULL) || (Pos == NULL) || (IOBuf == NULL)) {
+               if (Pos != NULL) {
+                       *Pos = NULL;
+               }
+               return -1;
+       }
+
+       pos = *Pos;
+       if ((StrLength(IOBuf) > 0) && (pos != NULL) && (pos < ChrPtr(IOBuf) + StrLength(IOBuf))) {
+               pch = pos;
+               pch = strchr(pch, '\n');
+               
+               if (pch == NULL) {
+                       StrBufAppendBufPlain(Line, pos, StrLength(IOBuf) - (pos - ChrPtr(IOBuf)), 0);
+                       FlushStrBuf(IOBuf);
+                       *Pos = NULL;
+               }
+               else {
+                       int n = 0;
+                       if ((pch > ChrPtr(IOBuf)) && (*(pch - 1) == '\r')) {
+                               n = 1;
+                       }
+                       StrBufAppendBufPlain(Line, pos, (pch - pos - n), 0);
+
+                       if (StrLength(IOBuf) <= (pch - ChrPtr(IOBuf) + 1)) {
+                               FlushStrBuf(IOBuf);
+                               *Pos = NULL;
+                       }
+                       else {
+                               *Pos = pch + 1;
+                       }
+                       return StrLength(Line);
+               }
+       }
+
+       pLF = NULL;
+       while ((nSuccessLess < timeout) && (pLF == NULL) && (CC->ssl != NULL)) {
+
+               rlen = client_read_sslbuffer(IOBuf, timeout);
+               if (rlen < 1) {
+                       return -1;
+               }
+               else if (rlen > 0) {
+                       pLF = strchr(ChrPtr(IOBuf), '\n');
+               }
+       }
+       *Pos = NULL;
+       if (pLF != NULL) {
+               pos = ChrPtr(IOBuf);
+               len = pLF - pos;
+               if (len > 0 && (*(pLF - 1) == '\r') )
+                       len --;
+               StrBufAppendBufPlain(Line, pos, len, 0);
+               if (pLF + 1 >= ChrPtr(IOBuf) + StrLength(IOBuf)) {
+                       FlushStrBuf(IOBuf);
+               }
+               else  {
+                       *Pos = pLF + 1;
+               }
+               return StrLength(Line);
+       }
+       return -1;
+}
+
+
+int client_read_sslblob(StrBuf *Target, long bytes, int timeout) {
+       long baselen;
+       long RemainRead;
+       int retval = 0;
+
+       baselen = StrLength(Target);
+
+       if (StrLength(CC->RecvBuf.Buf) > 0) {
+               long RemainLen;
+               long TotalLen;
+               const char *pchs;
+
+               if (CC->RecvBuf.ReadWritePointer == NULL) {
+                       CC->RecvBuf.ReadWritePointer = ChrPtr(CC->RecvBuf.Buf);
+               }
+               pchs = ChrPtr(CC->RecvBuf.Buf);
+               TotalLen = StrLength(CC->RecvBuf.Buf);
+               RemainLen = TotalLen - (pchs - CC->RecvBuf.ReadWritePointer);
+               if (RemainLen > bytes) {
+                       RemainLen = bytes;
+               }
+               if (RemainLen > 0) {
+                       StrBufAppendBufPlain(Target, CC->RecvBuf.ReadWritePointer, RemainLen, 0);
+                       CC->RecvBuf.ReadWritePointer += RemainLen;
+               }
+               if ((ChrPtr(CC->RecvBuf.Buf) + StrLength(CC->RecvBuf.Buf)) <= CC->RecvBuf.ReadWritePointer) {
+                       CC->RecvBuf.ReadWritePointer = NULL;
+                       FlushStrBuf(CC->RecvBuf.Buf);
+               }
+       }
+
+       if (StrLength(Target) >= bytes + baselen) {
+               return 1;
+       }
+
+       CC->RecvBuf.ReadWritePointer = NULL;
+
+       while ((StrLength(Target) < bytes + baselen) && (retval >= 0)) {
+               retval = client_read_sslbuffer(CC->RecvBuf.Buf, timeout);
+               if (retval >= 0) {
+                       RemainRead = bytes - (StrLength (Target) - baselen);
+                       if (RemainRead < StrLength(CC->RecvBuf.Buf)) {
+                               StrBufAppendBufPlain(
+                                       Target, 
+                                       ChrPtr(CC->RecvBuf.Buf), 
+                                       RemainRead, 0);
+                               CC->RecvBuf.ReadWritePointer = ChrPtr(CC->RecvBuf.Buf) + RemainRead;
+                               break;
+                       }
+                       StrBufAppendBuf(Target, CC->RecvBuf.Buf, 0);
+                       FlushStrBuf(CC->RecvBuf.Buf);
+               }
+               else {
+                       FlushStrBuf(CC->RecvBuf.Buf);
+                       return -1;
+       
+               }
+       }
+       return 1;
+}
+
+
+// CtdlStartTLS() starts TLS encryption for the current session.  It
+// must be supplied with pre-generated strings for responses of "ok," "no
+// support for TLS," and "error" so that we can use this in any protocol.
+void CtdlStartTLS(char *ok_response, char *nosup_response, char *error_response) {
+       int retval, bits, alg_bits;
+
+       if (CC->redirect_ssl) {
+               syslog(LOG_ERR, "crypto: attempt to begin SSL on an already encrypted connection");
+               if (error_response != NULL) {
+                       cprintf("%s", error_response);
+               }
+               return;
+       }
+
+       if (!ssl_ctx) {
+               syslog(LOG_ERR, "crypto: SSL failed: ../../context.has not been initialized");
+               if (nosup_response != NULL) {
+                       cprintf("%s", nosup_response);
+               }
+               return;
+       }
+
+       update_key_and_cert_if_needed();                // did someone update the key or cert?  if so, re-bind them
+
+       if (!(CC->ssl = SSL_new(ssl_ctx))) {
+               syslog(LOG_ERR, "crypto: SSL_new failed: %s", ERR_reason_error_string(ERR_get_error()));
+               if (error_response != NULL) {
+                       cprintf("%s", error_response);
+               }
+               return;
+       }
+       if (!(SSL_set_fd(CC->ssl, CC->client_socket))) {
+               syslog(LOG_ERR, "crypto: SSL_set_fd failed: %s", ERR_reason_error_string(ERR_get_error()));
+               SSL_free(CC->ssl);
+               CC->ssl = NULL;
+               if (error_response != NULL) {
+                       cprintf("%s", error_response);
+               }
+               return;
+       }
+       if (ok_response != NULL) {
+               cprintf("%s", ok_response);
+       }
+       retval = SSL_accept(CC->ssl);
+       if (retval < 1) {
+               // Can't notify the client of an error here; they will
+               // discover the problem at the SSL layer and should
+               // revert to unencrypted communications.
+               syslog(LOG_ERR, "crypto: SSL_accept failed: %s", ERR_reason_error_string(ERR_get_error()));
+               SSL_free(CC->ssl);
+               CC->ssl = NULL;
+               return;
+       }
+       bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(CC->ssl), &alg_bits);
+       syslog(LOG_INFO, "crypto: TLS using %s on %s (%d of %d bits)",
+               SSL_CIPHER_get_name(SSL_get_current_cipher(CC->ssl)),
+               SSL_CIPHER_get_version(SSL_get_current_cipher(CC->ssl)),
+               bits, alg_bits
+       );
+       CC->redirect_ssl = 1;
+}
+
+
+// cmd_stls() starts TLS encryption for the current session
+void cmd_stls(char *params) {
+       char ok_response[SIZ];
+       char nosup_response[SIZ];
+       char error_response[SIZ];
+
+       unbuffer_output();
+
+       sprintf(ok_response, "%d Begin TLS negotiation now\n", CIT_OK);
+       sprintf(nosup_response, "%d TLS not supported here\n", ERROR + CMD_NOT_SUPPORTED);
+       sprintf(error_response, "%d TLS negotiation error\n", ERROR + INTERNAL_ERROR);
+
+       CtdlStartTLS(ok_response, nosup_response, error_response);
+}
+
+
+// cmd_gtls() returns status info about the TLS connection
+void cmd_gtls(char *params) {
+       int bits, alg_bits;
+
+       if (!CC->ssl || !CC->redirect_ssl) {
+               cprintf("%d Session is not encrypted.\n", ERROR);
+               return;
+       }
+       bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(CC->ssl), &alg_bits);
+       cprintf("%d %s|%s|%d|%d\n", CIT_OK,
+               SSL_CIPHER_get_version(SSL_get_current_cipher(CC->ssl)),
+               SSL_CIPHER_get_name(SSL_get_current_cipher(CC->ssl)),
+               alg_bits, bits);
+}
+
+
+// endtls() shuts down the TLS connection
+void endtls(void) {
+       if (!CC->ssl) {
+               CC->redirect_ssl = 0;
+               return;
+       }
+
+       syslog(LOG_INFO, "crypto: ending TLS on this session");
+       SSL_shutdown(CC->ssl);
+       SSL_free(CC->ssl);
+       CC->ssl = NULL;
+       CC->redirect_ssl = 0;
+}
+
+#endif // HAVE_OPENSSL
diff --git a/citadel/server/modules/crypto/serv_crypto.h b/citadel/server/modules/crypto/serv_crypto.h
new file mode 100644 (file)
index 0000000..b5ee85d
--- /dev/null
@@ -0,0 +1,23 @@
+
+/*
+ * Number of days for which self-signed certs are valid.
+ */
+#define SIGN_DAYS      1100    // Just over three years
+
+// Which ciphers will be offered; see https://www.openssl.org/docs/manmaster/man1/ciphers.html
+#define CIT_CIPHERS    "ALL:RC4+RSA:+SSLv2:+TLSv1:!MD5:@STRENGTH"
+
+#ifdef HAVE_OPENSSL
+#define OPENSSL_NO_KRB5                /* work around redhat b0rken ssl headers */
+void init_ssl(void);
+void client_write_ssl (const char *buf, int nbytes);
+int client_read_sslbuffer(StrBuf *buf, int timeout);
+int client_readline_sslbuffer(StrBuf *Target, StrBuf *Buffer, const char **Pos, int timeout);
+int client_read_sslblob(StrBuf *Target, long want_len, int timeout);
+void cmd_stls(char *params);
+void cmd_gtls(char *params);
+void endtls(void);
+void CtdlStartTLS(char *ok_response, char *nosup_response, char *error_response);
+extern SSL_CTX *ssl_ctx;  
+
+#endif
diff --git a/citadel/server/modules/ctdlproto/files.h b/citadel/server/modules/ctdlproto/files.h
new file mode 100644 (file)
index 0000000..080c83e
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef FILE_OPS_H
+#define FILE_OPS_H
+
+#include "context.h"
+
+void OpenCmdResult (char *, const char *);
+void abort_upl (CitContext *who);
+
+
+#endif /* FILE_OPS_H */
diff --git a/citadel/server/modules/ctdlproto/serv_ctdlproto.c b/citadel/server/modules/ctdlproto/serv_ctdlproto.c
new file mode 100644 (file)
index 0000000..ae267b5
--- /dev/null
@@ -0,0 +1,81 @@
+/* 
+ * Citadel protocol main dispatcher
+ *
+ * Copyright (c) 1987-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <libcitadel.h>
+
+#include "../../citserver.h"
+#include "../../ctdl_module.h"
+#include "../../config.h"
+/*
+ * This loop recognizes all server commands.
+ */
+void do_command_loop(void) {
+       struct CitContext *CCC = CC;
+       char cmdbuf[SIZ];
+       
+       time(&CCC->lastcmd);
+       memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
+       if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
+               syslog(LOG_INFO, "Citadel client disconnected: ending session.");
+               CCC->kill_me = KILLME_CLIENT_DISCONNECTED;
+               return;
+       }
+
+       /* Log the server command, but don't show passwords... */
+       if ( (strncasecmp(cmdbuf, "PASS", 4)) && (strncasecmp(cmdbuf, "SETP", 4)) ) {
+               syslog(LOG_DEBUG, "[%s(%ld)] %s",
+                       CCC->curr_user, CCC->user.usernum, cmdbuf
+               );
+       }
+       else {
+               syslog(LOG_DEBUG, "[%s(%ld)] <password command hidden from log>",
+                           CCC->curr_user, CCC->user.usernum
+               );
+       }
+
+       buffer_output();
+
+       /*
+        * Let other clients see the last command we executed, and
+        * update the idle time, but not NOOP, QNOP, PEXP, GEXP, RWHO, or TIME.
+        */
+       if ( (strncasecmp(cmdbuf, "NOOP", 4))
+          && (strncasecmp(cmdbuf, "QNOP", 4))
+          && (strncasecmp(cmdbuf, "PEXP", 4))
+          && (strncasecmp(cmdbuf, "GEXP", 4))
+          && (strncasecmp(cmdbuf, "RWHO", 4))
+          && (strncasecmp(cmdbuf, "TIME", 4)) ) {
+               strcpy(CCC->lastcmdname, "    ");
+               safestrncpy(CCC->lastcmdname, cmdbuf, sizeof(CCC->lastcmdname));
+               time(&CCC->lastidle);
+       }
+       
+       if ((strncasecmp(cmdbuf, "ENT0", 4))
+          && (strncasecmp(cmdbuf, "MESG", 4))
+          && (strncasecmp(cmdbuf, "MSGS", 4)))
+       {
+          CCC->cs_flags &= ~CS_POSTING;
+       }
+                  
+       if (!DLoader_Exec_Cmd(cmdbuf)) {
+               cprintf("%d Unrecognized or unsupported command.\n", ERROR + CMD_NOT_SUPPORTED);
+       }       
+
+       unbuffer_output();
+
+       /* Run any after-each-command routines registered by modules */
+       PerformSessionHooks(EVT_CMD);
+}
+
diff --git a/citadel/server/modules/ctdlproto/serv_file.c b/citadel/server/modules/ctdlproto/serv_file.c
new file mode 100644 (file)
index 0000000..d266578
--- /dev/null
@@ -0,0 +1,684 @@
+/* 
+ * Server functions which handle file transfers and room directories.
+ *
+ * Copyright (c) 1987-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <libcitadel.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "../../ctdl_module.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+
+
+/*
+ * Server command to delete a file from a room's directory
+ */
+void cmd_delf(char *filename)
+{
+       char pathname[64];
+       int a;
+
+       if (CtdlAccessCheck(ac_room_aide))
+               return;
+
+       if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
+               cprintf("%d No directory in this room.\n", ERROR + NOT_HERE);
+               return;
+       }
+
+       if (IsEmptyStr(filename)) {
+               cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
+               return;
+       }
+       for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
+               if ( (filename[a] == '/') || (filename[a] == '\\') ) {
+                       filename[a] = '_';
+               }
+       }
+       snprintf(pathname, sizeof pathname,
+                "%s/%s/%s",
+                ctdl_file_dir,
+                CC->room.QRdirname, filename
+       );
+       a = unlink(pathname);
+       if (a == 0) {
+               cprintf("%d File '%s' deleted.\n", CIT_OK, pathname);
+       }
+       else {
+               cprintf("%d File '%s' not found.\n", ERROR + FILE_NOT_FOUND, pathname);
+       }
+}
+
+
+/*
+ * move a file from one room directory to another
+ */
+void cmd_movf(char *cmdbuf)
+{
+       char filename[PATH_MAX];
+       char pathname[PATH_MAX];
+       char newpath[PATH_MAX];
+       char newroom[ROOMNAMELEN];
+       char buf[PATH_MAX];
+       int a;
+       struct ctdlroom qrbuf;
+
+       extract_token(filename, cmdbuf, 0, '|', sizeof filename);
+       extract_token(newroom, cmdbuf, 1, '|', sizeof newroom);
+
+       if (CtdlAccessCheck(ac_room_aide)) return;
+
+       if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
+               cprintf("%d No directory in this room.\n", ERROR + NOT_HERE);
+               return;
+       }
+
+       if (IsEmptyStr(filename)) {
+               cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
+               return;
+       }
+
+       for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
+               if ( (filename[a] == '/') || (filename[a] == '\\') ) {
+                       filename[a] = '_';
+               }
+       }
+       snprintf(pathname, sizeof pathname, "./files/%s/%s", CC->room.QRdirname, filename);
+       if (access(pathname, 0) != 0) {
+               cprintf("%d File '%s' not found.\n", ERROR + FILE_NOT_FOUND, pathname);
+               return;
+       }
+
+       if (CtdlGetRoom(&qrbuf, newroom) != 0) {
+               cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, newroom);
+               return;
+       }
+       if ((qrbuf.QRflags & QR_DIRECTORY) == 0) {
+               cprintf("%d '%s' is not a directory room.\n", ERROR + NOT_HERE, qrbuf.QRname);
+               return;
+       }
+       snprintf(newpath, sizeof newpath, "./files/%s/%s", qrbuf.QRdirname, filename);
+       if (link(pathname, newpath) != 0) {
+               cprintf("%d Couldn't move file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
+               return;
+       }
+       unlink(pathname);
+
+       /* this is a crude method of copying the file description */
+       snprintf(buf, sizeof buf, "cat ./files/%s/filedir |grep \"%s\" >>./files/%s/filedir", CC->room.QRdirname, filename, qrbuf.QRdirname);
+       system(buf);
+       cprintf("%d File '%s' has been moved.\n", CIT_OK, filename);
+}
+
+
+/*
+ * This code is common to all commands which open a file for downloading,
+ * regardless of whether it's a file from the directory, an image, a network
+ * spool file, a MIME attachment, etc.
+ * It examines the file and displays the OK result code and some information
+ * about the file.  NOTE: this stuff is Unix dependent.
+ */
+void OpenCmdResult(char *filename, const char *mime_type)
+{
+       struct stat statbuf;
+       time_t modtime;
+       long filesize;
+
+       fstat(fileno(CC->download_fp), &statbuf);
+       CC->download_fp_total = statbuf.st_size;
+       filesize = (long) statbuf.st_size;
+       modtime = (time_t) statbuf.st_mtime;
+
+       cprintf("%d %ld|%ld|%s|%s\n", CIT_OK, filesize, (long)modtime, filename, mime_type);
+}
+
+
+/*
+ * open a file for downloading
+ */
+void cmd_open(char *cmdbuf)
+{
+       char filename[256];
+       char pathname[PATH_MAX];
+       int a;
+
+       extract_token(filename, cmdbuf, 0, '|', sizeof filename);
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
+               cprintf("%d No directory in this room.\n", ERROR + NOT_HERE);
+               return;
+       }
+
+       if (IsEmptyStr(filename)) {
+               cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
+               return;
+       }
+       if (strstr(filename, "../") != NULL)
+       {
+               cprintf("%d syntax error.\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       if (CC->download_fp != NULL) {
+               cprintf("%d You already have a download file open.\n", ERROR + RESOURCE_BUSY);
+               return;
+       }
+
+       for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
+               if ( (filename[a] == '/') || (filename[a] == '\\') ) {
+                       filename[a] = '_';
+               }
+       }
+
+       snprintf(pathname, sizeof pathname, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, filename);
+       CC->download_fp = fopen(pathname, "r");
+
+       if (CC->download_fp == NULL) {
+               cprintf("%d cannot open %s: %s\n", ERROR + INTERNAL_ERROR, pathname, strerror(errno));
+               return;
+       }
+
+       OpenCmdResult(filename, "application/octet-stream");
+}
+
+
+/*
+ * open an image file
+ */
+void cmd_oimg(char *cmdbuf)
+{
+       char filename[PATH_MAX];
+       char pathname[PATH_MAX];
+       char MimeTestBuf[32];
+       int rv;
+
+       extract_token(filename, cmdbuf, 0, '|', sizeof filename);
+
+       if (IsEmptyStr(filename)) {
+               cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
+               return;
+       }
+
+       if (CC->download_fp != NULL) {
+               cprintf("%d You already have a download file open.\n", ERROR + RESOURCE_BUSY);
+               return;
+       }
+
+       CC->download_fp = fopen(pathname, "rb");
+       if (CC->download_fp == NULL) {
+               strcat(pathname, ".gif");
+               CC->download_fp = fopen(pathname, "rb");
+       }
+       if (CC->download_fp == NULL) {
+               cprintf("%d Cannot open %s: %s\n", ERROR + FILE_NOT_FOUND, pathname, strerror(errno));
+               return;
+       }
+       rv = fread(&MimeTestBuf[0], 1, 32, CC->download_fp);
+       if (rv == -1) {
+               cprintf("%d Cannot access %s: %s\n", ERROR + FILE_NOT_FOUND, pathname, strerror(errno));
+               return;
+       }
+
+       rewind (CC->download_fp);
+       OpenCmdResult(pathname, GuessMimeType(&MimeTestBuf[0], 32));
+}
+
+
+/*
+ * open a file for uploading
+ */
+void cmd_uopn(char *cmdbuf)
+{
+       int a;
+
+       extract_token(CC->upl_file, cmdbuf, 0, '|', sizeof CC->upl_file);
+       extract_token(CC->upl_mimetype, cmdbuf, 1, '|', sizeof CC->upl_mimetype);
+       extract_token(CC->upl_comment, cmdbuf, 2, '|', sizeof CC->upl_comment);
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
+               cprintf("%d No directory in this room.\n", ERROR + NOT_HERE);
+               return;
+       }
+
+       if (IsEmptyStr(CC->upl_file)) {
+               cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
+               return;
+       }
+
+       if (CC->upload_fp != NULL) {
+               cprintf("%d You already have a upload file open.\n", ERROR + RESOURCE_BUSY);
+               return;
+       }
+
+       for (a = 0; !IsEmptyStr(&CC->upl_file[a]); ++a) {
+               if ( (CC->upl_file[a] == '/') || (CC->upl_file[a] == '\\') ) {
+                       CC->upl_file[a] = '_';
+               }
+       }
+       snprintf(CC->upl_path, sizeof CC->upl_path, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, CC->upl_file);
+       snprintf(CC->upl_filedir, sizeof CC->upl_filedir, "%s/%s/filedir", ctdl_file_dir, CC->room.QRdirname);
+
+       CC->upload_fp = fopen(CC->upl_path, "r");
+       if (CC->upload_fp != NULL) {
+               fclose(CC->upload_fp);
+               CC->upload_fp = NULL;
+               cprintf("%d '%s' already exists\n", ERROR + ALREADY_EXISTS, CC->upl_path);
+               return;
+       }
+
+       CC->upload_fp = fopen(CC->upl_path, "wb");
+       if (CC->upload_fp == NULL) {
+               cprintf("%d Cannot open %s: %s\n", ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno));
+               return;
+       }
+       cprintf("%d Ok\n", CIT_OK);
+}
+
+
+/*
+ * open an image file for uploading
+ */
+void cmd_uimg(char *cmdbuf)
+{
+       int is_this_for_real;
+       char basenm[256];
+       int a;
+
+       if (num_parms(cmdbuf) < 2) {
+               cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       is_this_for_real = extract_int(cmdbuf, 0);
+       extract_token(CC->upl_mimetype, cmdbuf, 1, '|', sizeof CC->upl_mimetype);
+       extract_token(basenm, cmdbuf, 2, '|', sizeof basenm);
+       if (CC->upload_fp != NULL) {
+               cprintf("%d You already have an upload file open.\n", ERROR + RESOURCE_BUSY);
+               return;
+       }
+
+       strcpy(CC->upl_path, "");
+
+       for (a = 0; !IsEmptyStr(&basenm[a]); ++a) {
+               basenm[a] = tolower(basenm[a]);
+               if ( (basenm[a] == '/') || (basenm[a] == '\\') ) {
+                       basenm[a] = '_';
+               }
+       }
+
+       if (CC->user.axlevel >= AxAideU) {
+               snprintf(CC->upl_path, sizeof CC->upl_path, "%s/%s", ctdl_image_dir, basenm);
+       }
+
+       if (IsEmptyStr(CC->upl_path)) {
+               cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+               return;
+       }
+
+       if (is_this_for_real == 0) {
+               cprintf("%d Ok to send image\n", CIT_OK);
+               return;
+       }
+
+       CC->upload_fp = fopen(CC->upl_path, "wb");
+       if (CC->upload_fp == NULL) {
+               cprintf("%d Cannot open %s: %s\n", ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno));
+               return;
+       }
+       cprintf("%d Ok\n", CIT_OK);
+}
+
+
+/*
+ * close the download file
+ */
+void cmd_clos(char *cmdbuf)
+{
+       if (CC->download_fp == NULL) {
+               cprintf("%d You don't have a download file open.\n", ERROR + RESOURCE_NOT_OPEN);
+               return;
+       }
+
+       fclose(CC->download_fp);
+       CC->download_fp = NULL;
+       cprintf("%d Ok\n", CIT_OK);
+}
+
+
+/*
+ * abort an upload
+ */
+void abort_upl(CitContext *who)
+{
+       if (who->upload_fp != NULL) {
+               fclose(who->upload_fp);
+               who->upload_fp = NULL;
+               unlink(CC->upl_path);
+       }
+}
+
+
+/*
+ * close the upload file
+ */
+void cmd_ucls(char *cmd)
+{
+       FILE *fp;
+       char upload_notice[SIZ];
+
+       if (CC->upload_fp == NULL) {
+               cprintf("%d You don't have an upload file open.\n", ERROR + RESOURCE_NOT_OPEN);
+               return;
+       }
+
+       fclose(CC->upload_fp);
+       CC->upload_fp = NULL;
+
+       if (!strcasecmp(cmd, "1")) {
+               cprintf("%d File '%s' saved.\n", CIT_OK, CC->upl_path);
+               fp = fopen(CC->upl_filedir, "a");
+               if (fp == NULL) {
+                       fp = fopen(CC->upl_filedir, "w");
+               }
+               if (fp != NULL) {
+                       fprintf(fp, "%s %s %s\n", CC->upl_file, CC->upl_mimetype, CC->upl_comment);
+                       fclose(fp);
+               }
+
+               if ((CC->room.QRflags2 & QR2_NOUPLMSG) == 0) {
+                       /* put together an upload notice */
+                       snprintf(upload_notice, sizeof upload_notice,
+                                "NEW UPLOAD: '%s'\n %s\n%s\n",
+                                CC->upl_file, 
+                                CC->upl_comment, 
+                                CC->upl_mimetype
+                       );
+                       quickie_message(CC->curr_user, NULL, NULL, CC->room.QRname, upload_notice, 0, NULL);
+               }
+       } else {
+               abort_upl(CC);
+               cprintf("%d File '%s' aborted.\n", CIT_OK, CC->upl_path);
+       }
+}
+
+
+/*
+ * read from the download file
+ */
+void cmd_read(char *cmdbuf)
+{
+       long start_pos;
+       size_t bytes;
+       char buf[SIZ];
+       int rc;
+
+       /* The client will transmit its requested offset and byte count */
+       start_pos = extract_long(cmdbuf, 0);
+       bytes = extract_int(cmdbuf, 1);
+       if ((start_pos < 0) || (bytes <= 0)) {
+               cprintf("%d you have to specify a value > 0.\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       if (CC->download_fp == NULL) {
+               cprintf("%d You don't have a download file open.\n", ERROR + RESOURCE_NOT_OPEN);
+               return;
+       }
+
+       /* If necessary, reduce the byte count to the size of our buffer */
+       if (bytes > sizeof(buf)) {
+               bytes = sizeof(buf);
+       }
+
+       rc = fseek(CC->download_fp, start_pos, 0);
+       if (rc < 0) {
+               cprintf("%d your file is smaller than %ld.\n", ERROR + ILLEGAL_VALUE, start_pos);
+               syslog(LOG_ERR, "serv_file: your file %s is smaller than %ld [%s]", 
+                       CC->upl_path, 
+                       start_pos,
+                       strerror(errno)
+               );
+
+               return;
+       }
+       bytes = fread(buf, 1, bytes, CC->download_fp);
+       if (bytes > 0) {
+               /* Tell the client the actual byte count and transmit it */
+               cprintf("%d %d\n", BINARY_FOLLOWS, (int)bytes);
+               client_write(buf, bytes);
+       }
+       else {
+               cprintf("%d %s\n", ERROR, strerror(errno));
+       }
+}
+
+
+/*
+ * write to the upload file
+ */
+void cmd_writ(char *cmdbuf)
+{
+       struct CitContext *CCC = CC;
+       int bytes;
+       char *buf;
+       int rv;
+
+       unbuffer_output();
+
+       bytes = extract_int(cmdbuf, 0);
+
+       if (CCC->upload_fp == NULL) {
+               cprintf("%d You don't have an upload file open.\n", ERROR + RESOURCE_NOT_OPEN);
+               return;
+       }
+       if (bytes <= 0) {
+               cprintf("%d you have to specify a value > 0.\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       if (bytes > 100000) {
+               bytes = 100000;
+       }
+
+       cprintf("%d %d\n", SEND_BINARY, bytes);
+       buf = malloc(bytes + 1);
+       client_read(buf, bytes);
+       rv = fwrite(buf, bytes, 1, CCC->upload_fp);
+       if (rv == -1) {
+               syslog(LOG_ERR, "serv_file: %s", strerror(errno));
+       }
+       free(buf);
+}
+
+
+void files_logout_hook(void)
+{
+        CitContext *CCC = MyContext();
+
+       /*
+        * If there is a download in progress, abort it.
+        */
+       if (CCC->download_fp != NULL) {
+               fclose(CCC->download_fp);
+               CCC->download_fp = NULL;
+       }
+
+       /*
+        * If there is an upload in progress, abort it.
+        */
+       if (CCC->upload_fp != NULL) {
+               abort_upl(CCC);
+       }
+
+}
+
+
+/* 
+ * help_subst()  -  support routine for help file viewer
+ */
+void help_subst(char *strbuf, char *source, char *dest)
+{
+       char workbuf[SIZ];
+       int p;
+
+       while (p = pattern2(strbuf, source), (p >= 0)) {
+               strcpy(workbuf, &strbuf[p + strlen(source)]);
+               strcpy(&strbuf[p], dest);
+               strcat(strbuf, workbuf);
+       }
+}
+
+
+void do_help_subst(char *buffer)
+{
+       char buf2[16];
+
+       help_subst(buffer, "^nodename", CtdlGetConfigStr("c_nodename"));
+       help_subst(buffer, "^humannode", CtdlGetConfigStr("c_humannode"));
+       help_subst(buffer, "^fqdn", CtdlGetConfigStr("c_fqdn"));
+       help_subst(buffer, "^username", CC->user.fullname);
+       snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
+       help_subst(buffer, "^usernum", buf2);
+       help_subst(buffer, "^sysadm", CtdlGetConfigStr("c_sysadm"));
+       help_subst(buffer, "^variantname", CITADEL);
+       help_subst(buffer, "^maxsessions", CtdlGetConfigStr("c_maxsessions"));          // yes it's numeric but str is ok here
+       help_subst(buffer, "^bbsdir", ctdl_message_dir);
+}
+
+
+typedef const char *ccharp;
+/*
+ * display system messages or help
+ */
+void cmd_mesg(char *mname)
+{
+       FILE *mfp;
+       char targ[256];
+       char buf[256];
+       char buf2[256];
+       DIR *dp;
+       struct dirent *d;
+
+       extract_token(buf, mname, 0, '|', sizeof buf);
+
+       snprintf(buf2, sizeof buf2, "%s.%d.%d", buf, CC->cs_clientdev, CC->cs_clienttyp);
+
+       /* If the client requested "?" then produce a listing */
+       if (!strcmp(buf, "?")) {
+               cprintf("%d %s\n", LISTING_FOLLOWS, buf);
+               dp = opendir(ctdl_message_dir);
+               if (dp != NULL) {
+                       while (d = readdir(dp), d != NULL) {
+                               if (d->d_name[0] != '.') {
+                                       cprintf(" %s\n", d->d_name);
+                               }
+                       }
+                       closedir(dp);
+               }
+               cprintf("000\n");
+               return;
+       }
+
+       /* Otherwise, look for the requested file by name. */
+       snprintf(targ, sizeof targ, "%s/%s", ctdl_message_dir, buf);
+       mfp = fopen(targ, "r");
+       if (mfp==NULL) {
+               cprintf("%d Cannot open '%s': %s\n",
+                       ERROR + FILE_NOT_FOUND, targ, strerror(errno));
+               return;
+       }
+       cprintf("%d %s\n", LISTING_FOLLOWS, buf);
+
+       while (fgets(buf, (sizeof buf - 1), mfp) != NULL) {
+               buf[strlen(buf)-1] = 0;
+               do_help_subst(buf);
+               cprintf("%s\n",buf);
+       }
+
+       fclose(mfp);
+       cprintf("000\n");
+}
+
+
+/*
+ * enter system messages or help
+ */
+void cmd_emsg(char *mname)
+{
+       FILE *mfp;
+       char targ[256];
+       char buf[256];
+       int a;
+
+       unbuffer_output();
+
+       if (CtdlAccessCheck(ac_aide)) return;
+
+       extract_token(buf, mname, 0, '|', sizeof buf);
+       for (a=0; !IsEmptyStr(&buf[a]); ++a) {          /* security measure */
+               if (buf[a] == '/') buf[a] = '.';
+       }
+
+       if (IsEmptyStr(targ)) {
+               snprintf(targ, sizeof targ, "%s/%s", ctdl_message_dir, buf);
+       }
+
+       mfp = fopen(targ, "w");
+       if (mfp==NULL) {
+               cprintf("%d Cannot open '%s': %s\n",
+                       ERROR + INTERNAL_ERROR, targ, strerror(errno));
+               return;
+       }
+       cprintf("%d %s\n", SEND_LISTING, targ);
+
+       while (client_getln(buf, sizeof buf) >=0 && strcmp(buf, "000")) {
+               fprintf(mfp, "%s\n", buf);
+       }
+
+       fclose(mfp);
+}
+
+/*****************************************************************************/
+/*                      MODULE INITIALIZATION STUFF                          */
+/*****************************************************************************/
+
+char *ctdl_module_init_file_ops(void) {
+       if (!threading) {
+               CtdlRegisterSessionHook(files_logout_hook, EVT_LOGOUT, PRIO_LOGOUT + 8);
+               CtdlRegisterProtoHook(cmd_delf, "DELF", "Delete a file");
+               CtdlRegisterProtoHook(cmd_movf, "MOVF", "Move a file");
+               CtdlRegisterProtoHook(cmd_open, "OPEN", "Open a download file transfer");
+               CtdlRegisterProtoHook(cmd_clos, "CLOS", "Close a download file transfer");
+               CtdlRegisterProtoHook(cmd_uopn, "UOPN", "Open an upload file transfer");
+               CtdlRegisterProtoHook(cmd_ucls, "UCLS", "Close an upload file transfer");
+               CtdlRegisterProtoHook(cmd_read, "READ", "File transfer read operation");
+               CtdlRegisterProtoHook(cmd_writ, "WRIT", "File transfer write operation");
+               CtdlRegisterProtoHook(cmd_oimg, "OIMG", "Open an image file for download");
+               CtdlRegisterProtoHook(cmd_uimg, "UIMG", "Upload an image file");
+               CtdlRegisterProtoHook(cmd_mesg, "MESG", "fetch system banners");
+               CtdlRegisterProtoHook(cmd_emsg, "EMSG", "submit system banners");
+       }
+        /* return our Subversion id for the Log */
+       return "file_ops";
+}
diff --git a/citadel/server/modules/ctdlproto/serv_messages.c b/citadel/server/modules/ctdlproto/serv_messages.c
new file mode 100644 (file)
index 0000000..4036c9e
--- /dev/null
@@ -0,0 +1,837 @@
+// Message-related protocol commands for Citadel clients
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include <stdio.h>
+#include <libcitadel.h>
+#include "../../citserver.h"
+#include "../../ctdl_module.h"
+#include "../../internet_addressing.h"
+#include "../../user_ops.h"
+#include "../../room_ops.h"
+#include "../../config.h"
+
+extern char *msgkeys[];
+
+
+// Back end for the MSGS command: output message number only.
+void simple_listing(long msgnum, void *userdata) {
+       cprintf("%ld\n", msgnum);
+}
+
+
+// Back end for the MSGS command: output header summary.
+void headers_listing(long msgnum, void *userdata) {
+       struct CtdlMessage *msg;
+       int output_mode =  *(int *)userdata;
+
+       msg = CtdlFetchMessage(msgnum, 0);
+       if (msg == NULL) {
+               cprintf("%ld|0|||||||\n", msgnum);
+               return;
+       }
+
+       // change all vertical bars in the subject to hyphens so it doesn't screw up the protocol
+       if (!CM_IsEmpty(msg, eMsgSubject)) {
+               char *p;
+               for (p=msg->cm_fields[eMsgSubject]; *p; p++) {
+                       if (*p == '|') {
+                               *p = '-';
+                       }
+               }
+       }
+
+       // output all fields except the references hash
+       cprintf("%ld|%s|%s|%s|%s|%s",
+               msgnum,
+               (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"),
+               (!CM_IsEmpty(msg, eAuthor) ? msg->cm_fields[eAuthor] : ""),
+               CtdlGetConfigStr("c_nodename"),                                         // no more nodenames anymore
+               (!CM_IsEmpty(msg, erFc822Addr) ? msg->cm_fields[erFc822Addr] : ""),
+               (!CM_IsEmpty(msg, eMsgSubject) ? msg->cm_fields[eMsgSubject] : "")
+       );
+
+       if (output_mode == MSG_HDRS_THREADS) {          // field view with thread hashes
+
+               // output the references hash
+               cprintf ("|%d|", 
+                       (!CM_IsEmpty(msg, emessageId) ? HashLittle(msg->cm_fields[emessageId],strlen(msg->cm_fields[emessageId])) : 0)
+               );
+       
+               // output the references hash (yes it's ok that we're trashing the source buffer by doing this)
+               if (!CM_IsEmpty(msg, eWeferences)) {
+                       char *token;
+                       char *rest = msg->cm_fields[eWeferences];
+                       char *prev = rest;
+                       while((token = strtok_r(rest, "|", &rest))) {
+                               cprintf("%d%s", HashLittle(token,rest-prev-(*rest==0?0:1)), (*rest==0?"":","));
+                               prev = rest;
+                       }
+               }
+       
+               cprintf("|\n");
+       }
+
+       else {                                          // field view with no threads, subject extends out forever
+               cprintf("\n");
+       }
+
+       CM_Free(msg);
+}
+
+typedef struct _msg_filter {
+       HashList *Filter;
+       HashPos *p;
+       StrBuf *buffer;
+} msg_filter;
+
+
+void headers_brief_filter(long msgnum, void *userdata) {
+       long i, l;
+       struct CtdlMessage *msg;
+       msg_filter *flt = (msg_filter*) userdata;
+
+       l = GetCount(flt->Filter);
+       msg = CtdlFetchMessage(msgnum, 0);
+       StrBufPrintf(flt->buffer, "%ld", msgnum);
+       if (msg == NULL) {
+               for (i = 0; i < l; i++) {
+                       StrBufAppendBufPlain(flt->buffer, HKEY("|"), 0);
+               }
+       }
+       else {
+               const char *k;
+               long len;
+               void *v;
+               RewindHashPos(flt->Filter, flt->p, 0);
+               while (GetNextHashPos(flt->Filter, flt->p, &len, &k, &v)) {
+                       eMsgField f = (eMsgField) v;
+                       
+                       StrBufAppendBufPlain(flt->buffer, HKEY("|"), 0);
+                       if (!CM_IsEmpty(msg, f)) {
+                               StrBufAppendBufPlain(flt->buffer, CM_KEY(msg, f), 0);
+                       }
+               }
+       }
+       StrBufAppendBufPlain(flt->buffer, HKEY("\n"), 0);
+       cputbuf(flt->buffer);
+}
+
+// Back end for the MSGS command: output EUID header.
+void headers_euid(long msgnum, void *userdata) {
+       struct CtdlMessage *msg;
+
+       msg = CtdlFetchMessage(msgnum, 0);
+       if (msg == NULL) {
+               cprintf("%ld||\n", msgnum);
+               return;
+       }
+
+       cprintf("%ld|%s|%s\n", 
+               msgnum, 
+               (!CM_IsEmpty(msg, eExclusiveID) ? msg->cm_fields[eExclusiveID] : ""),
+               (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"));
+       CM_Free(msg);
+}
+
+
+// cmd_msgs()  -  get list of message #'s in this room
+//             implements the MSGS server command using CtdlForEachMessage()
+void cmd_msgs(char *cmdbuf) {
+       int mode = 0;
+       char which[16];
+       char buf[256];
+       char tfield[256];
+       char tvalue[256];
+       int cm_ref = 0;
+       int with_template = 0;
+       struct CtdlMessage *template = NULL;
+        msg_filter filt;
+       char search_string[1024];
+       ForEachMsgCallback CallBack;
+
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+       extract_token(which, cmdbuf, 0, '|', sizeof which);
+       cm_ref = extract_int(cmdbuf, 1);
+       extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
+       with_template = extract_int(cmdbuf, 2);
+       int output_mode = extract_int(cmdbuf, 3);
+       switch (output_mode) {
+               default:
+               case MSG_HDRS_BRIEF:
+                       CallBack = simple_listing;
+                       break;
+               case MSG_HDRS_ALL:
+               case MSG_HDRS_THREADS:
+                       CallBack = headers_listing;
+                       break;
+               case MSG_HDRS_EUID:
+                       CallBack = headers_euid;
+                       break;
+               case MSG_HDRS_BRIEFFILTER:
+                       with_template = 2;
+                       CallBack = headers_brief_filter;
+                       break;
+       }
+
+       strcat(which, "   ");
+       if (!strncasecmp(which, "OLD", 3))
+               mode = MSGS_OLD;
+       else if (!strncasecmp(which, "NEW", 3))
+               mode = MSGS_NEW;
+       else if (!strncasecmp(which, "FIRST", 5))
+               mode = MSGS_FIRST;
+       else if (!strncasecmp(which, "LAST", 4))
+               mode = MSGS_LAST;
+       else if (!strncasecmp(which, "GT", 2))
+               mode = MSGS_GT;
+       else if (!strncasecmp(which, "LT", 2))
+               mode = MSGS_LT;
+       else if (!strncasecmp(which, "SEARCH", 6))
+               mode = MSGS_SEARCH;
+       else
+               mode = MSGS_ALL;
+
+       if ( (mode == MSGS_SEARCH) && (!CtdlGetConfigInt("c_enable_fulltext")) ) {
+               cprintf("%d Full text index is not enabled on this server.\n",
+                       ERROR + CMD_NOT_SUPPORTED);
+               return;
+       }
+
+       if (with_template == 1) {
+               memset(buf, 0, 5);
+               unbuffer_output();
+               cprintf("%d Send template then receive message list\n",
+                       START_CHAT_MODE);
+               template = (struct CtdlMessage *)
+                       malloc(sizeof(struct CtdlMessage));
+               memset(template, 0, sizeof(struct CtdlMessage));
+               template->cm_magic = CTDLMESSAGE_MAGIC;
+               template->cm_anon_type = MES_NORMAL;
+
+               while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
+                       eMsgField f;
+                       long tValueLen;
+
+                       tValueLen = extract_token(tfield, buf, 0, '|', sizeof tfield);
+                       if ((tValueLen == 4) && GetFieldFromMnemonic(&f, tfield))
+                       {
+                               tValueLen = extract_token(tvalue, buf, 1, '|', sizeof tvalue);
+                               if (tValueLen >= 0) {
+                                       CM_SetField(template, f, tvalue, tValueLen);
+                               }
+                       }
+               }
+               buffer_output();
+       }
+       else if (with_template == 2) {
+               long i = 0;
+               memset(buf, 0, 5);
+               cprintf("%d Send list of headers\n",
+                       START_CHAT_MODE);
+               filt.Filter = NewHash(1, lFlathash);
+               filt.buffer = NewStrBufPlain(NULL, 1024);
+               while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
+                       eMsgField f;
+       
+                       if (GetFieldFromMnemonic(&f, buf))
+                       {
+                               Put(filt.Filter, LKEY(i), (void*)f, reference_free_handler);
+                               i++;
+                       }
+               }
+               filt.p = GetNewHashPos(filt.Filter, 0);
+               buffer_output();
+       }
+       else {
+               cprintf("%d  \n", LISTING_FOLLOWS);
+       }
+
+       if (with_template < 2) {
+               CtdlForEachMessage(mode,
+                                  ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
+                                  ( (mode == MSGS_SEARCH) ? search_string : NULL ),
+                                  NULL,
+                                  template,
+                                  CallBack,
+                                  &output_mode);
+               if (template != NULL) CM_Free(template);
+       }
+       else {
+               CtdlForEachMessage(mode,
+                                  ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
+                                  ( (mode == MSGS_SEARCH) ? search_string : NULL ),
+                                  NULL,
+                                  NULL,
+                                  CallBack,
+                                  &filt);
+               DeleteHashPos(&filt.p);
+               DeleteHash(&filt.Filter);
+               FreeStrBuf(&filt.buffer);
+               
+       }
+       cprintf("000\n");
+}
+
+
+/*
+ * display a message (mode 0 - Citadel proprietary)
+ */
+void cmd_msg0(char *cmdbuf)
+{
+       long msgid;
+       int headers_only = HEADERS_ALL;
+
+       msgid = extract_long(cmdbuf, 0);
+       headers_only = extract_int(cmdbuf, 1);
+
+       CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL, NULL);
+       return;
+}
+
+
+// display a message (mode 2 - RFC822)
+void cmd_msg2(char *cmdbuf) {
+       long msgid;
+       int headers_only = HEADERS_ALL;
+
+       msgid = extract_long(cmdbuf, 0);
+       headers_only = extract_int(cmdbuf, 1);
+
+       CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL, NULL);
+}
+
+
+// Display a message using MIME content types
+void cmd_msg4(char *cmdbuf) {
+       long msgid;
+       char section[64];
+
+       msgid = extract_long(cmdbuf, 0);
+       extract_token(section, cmdbuf, 1, '|', sizeof section);
+       CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL, NULL);
+}
+
+
+// Client tells us its preferred message format(s)
+void cmd_msgp(char *cmdbuf) {
+       if (!strcasecmp(cmdbuf, "dont_decode")) {
+               CC->msg4_dont_decode = 1;
+               cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
+       }
+       else {
+               safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
+               cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
+       }
+}
+
+
+// Open a component of a MIME message as a download file 
+void cmd_opna(char *cmdbuf) {
+       long msgid;
+       char desired_section[128];
+
+       msgid = extract_long(cmdbuf, 0);
+       extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
+       safestrncpy(CC->download_desired_section, desired_section,
+               sizeof CC->download_desired_section);
+       CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
+}                      
+
+
+// Open a component of a MIME message and transmit it all at once
+void cmd_dlat(char *cmdbuf) {
+       long msgid;
+       char desired_section[128];
+
+       msgid = extract_long(cmdbuf, 0);
+       extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
+       safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
+       CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
+}
+
+
+// message entry  -  mode 0 (normal)
+void cmd_ent0(char *entargs) {
+       int post = 0;
+       char recp[SIZ];
+       char cc[SIZ];
+       char bcc[SIZ];
+       char supplied_euid[128];
+       int anon_flag = 0;
+       int format_type = 0;
+       char newusername[256];
+       char newuseremail[256];
+       struct CtdlMessage *msg;
+       int anonymous = 0;
+       char errmsg[SIZ];
+       int err = 0;
+       struct recptypes *valid = NULL;
+       struct recptypes *valid_to = NULL;
+       struct recptypes *valid_cc = NULL;
+       struct recptypes *valid_bcc = NULL;
+       char subject[SIZ];
+       int subject_required = 0;
+       int do_confirm = 0;
+       long msgnum;
+       int i, j;
+       char buf[256];
+       int newuseremail_ok = 0;
+       char references[SIZ];
+       char *ptr;
+
+       unbuffer_output();
+
+       post = extract_int(entargs, 0);
+       extract_token(recp, entargs, 1, '|', sizeof recp);
+       anon_flag = extract_int(entargs, 2);
+       format_type = extract_int(entargs, 3);
+       extract_token(subject, entargs, 4, '|', sizeof subject);
+       extract_token(newusername, entargs, 5, '|', sizeof newusername);
+       do_confirm = extract_int(entargs, 6);
+       extract_token(cc, entargs, 7, '|', sizeof cc);
+       extract_token(bcc, entargs, 8, '|', sizeof bcc);
+       switch(CC->room.QRdefaultview) {
+       case VIEW_NOTES:
+       case VIEW_WIKI:
+               extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
+               break;
+       default:
+               supplied_euid[0] = 0;
+               break;
+       }
+       extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
+       extract_token(references, entargs, 11, '|', sizeof references);
+       for (ptr=references; *ptr != 0; ++ptr) {
+               if (*ptr == '!') *ptr = '|';
+       }
+
+       /* first check to make sure the request is valid. */
+
+       err = CtdlDoIHavePermissionToPostInThisRoom(
+               errmsg,
+               sizeof errmsg,
+               POST_LOGGED_IN,
+               (!IsEmptyStr(references))               // is this a reply?  or a top-level post?
+       );
+       if (err) {
+               cprintf("%d %s\n", err, errmsg);
+               return;
+       }
+
+       /* Check some other permission type things. */
+
+       if (IsEmptyStr(newusername)) {
+               strcpy(newusername, CC->user.fullname);
+       }
+       if (  (CC->user.axlevel < AxAideU)
+             && (strcasecmp(newusername, CC->user.fullname))
+             && (strcasecmp(newusername, CC->cs_inet_fn))
+       ) {     
+               cprintf("%d You don't have permission to author messages as '%s'.\n",
+                       ERROR + HIGHER_ACCESS_REQUIRED,
+                       newusername
+               );
+               return;
+       }
+
+       if (IsEmptyStr(newuseremail)) {
+               newuseremail_ok = 1;
+       }
+
+       if (!IsEmptyStr(newuseremail)) {
+               if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
+                       newuseremail_ok = 1;
+               }
+               else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
+                       j = num_tokens(CC->cs_inet_other_emails, '|');
+                       for (i=0; i<j; ++i) {
+                               extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
+                               if (!strcasecmp(newuseremail, buf)) {
+                                       newuseremail_ok = 1;
+                               }
+                       }
+               }
+       }
+
+       if (!newuseremail_ok) {
+               cprintf("%d You don't have permission to author messages as '%s'.\n",
+                       ERROR + HIGHER_ACCESS_REQUIRED,
+                       newuseremail
+                       );
+               return;
+       }
+
+       CC->cs_flags |= CS_POSTING;
+
+       // In mailbox rooms we have to behave a little differently --
+       // make sure the user has specified at least one recipient.  Then
+       // validate the recipient(s).  We do this for the Mail> room, as
+       // well as any room which has the "Mailbox" view set - unless it
+       // is the DRAFTS room which does not require recipients.
+
+       if ( (  ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
+               || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
+                    ) && (strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
+               if (CC->user.axlevel < AxProbU) {
+                       strcpy(recp, "sysop");
+                       strcpy(cc, "");
+                       strcpy(bcc, "");
+               }
+
+               valid_to = validate_recipients(recp, NULL, 0);
+               if (valid_to->num_error > 0) {
+                       cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
+                       free_recipients(valid_to);
+                       return;
+               }
+
+               valid_cc = validate_recipients(cc, NULL, 0);
+               if (valid_cc->num_error > 0) {
+                       cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
+                       free_recipients(valid_to);
+                       free_recipients(valid_cc);
+                       return;
+               }
+
+               valid_bcc = validate_recipients(bcc, NULL, 0);
+               if (valid_bcc->num_error > 0) {
+                       cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
+                       free_recipients(valid_to);
+                       free_recipients(valid_cc);
+                       free_recipients(valid_bcc);
+                       return;
+               }
+
+               // Recipient required, but none were specified
+               if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
+                       free_recipients(valid_to);
+                       free_recipients(valid_cc);
+                       free_recipients(valid_bcc);
+                       cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
+                       return;
+               }
+
+               if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
+                       if (CtdlCheckInternetMailPermission(&CC->user)==0) {
+                               cprintf("%d You do not have permission "
+                                       "to send Internet mail.\n",
+                                       ERROR + HIGHER_ACCESS_REQUIRED);
+                               free_recipients(valid_to);
+                               free_recipients(valid_cc);
+                               free_recipients(valid_bcc);
+                               return;
+                       }
+               }
+
+               if ( ( (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet) > 0) && (CC->user.axlevel < AxNetU) ) {
+                       cprintf("%d Higher access required for network mail.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+                       free_recipients(valid_to);
+                       free_recipients(valid_cc);
+                       free_recipients(valid_bcc);
+                       return;
+               }
+       
+               if ((RESTRICT_INTERNET == 1)
+                   && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
+                   && ((CC->user.flags & US_INTERNET) == 0)
+                   && (!CC->internal_pgm)) {
+                       cprintf("%d You don't have access to Internet mail.\n",
+                               ERROR + HIGHER_ACCESS_REQUIRED);
+                       free_recipients(valid_to);
+                       free_recipients(valid_cc);
+                       free_recipients(valid_bcc);
+                       return;
+               }
+
+       }
+
+       // Is this a room which has anonymous-only or anonymous-option?
+       anonymous = MES_NORMAL;
+       if (CC->room.QRflags & QR_ANONONLY) {
+               anonymous = MES_ANONONLY;
+       }
+       if (CC->room.QRflags & QR_ANONOPT) {
+               if (anon_flag == 1) {   // only if the user requested it
+                       anonymous = MES_ANONOPT;
+               }
+       }
+
+       if ((CC->room.QRflags & QR_MAILBOX) == 0) {
+               recp[0] = 0;
+       }
+
+       // Recommend to the client that the use of a message subject is
+       // strongly recommended in this room, if either the SUBJECTREQ flag
+       // is set, or if there is one or more Internet email recipients.
+
+       if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
+       if ((valid_to)  && (valid_to->num_internet > 0))        subject_required = 1;
+       if ((valid_cc)  && (valid_cc->num_internet > 0))        subject_required = 1;
+       if ((valid_bcc) && (valid_bcc->num_internet > 0))       subject_required = 1;
+
+       // If we're only checking the validity of the request, return success without creating the message.
+       if (post == 0) {
+               cprintf("%d %s|%d\n", CIT_OK,
+                       ((valid_to != NULL) ? valid_to->display_recp : ""), 
+                       subject_required);
+               free_recipients(valid_to);
+               free_recipients(valid_cc);
+               free_recipients(valid_bcc);
+               return;
+       }
+
+       // We don't need these anymore because we'll do it differently below
+       free_recipients(valid_to);
+       free_recipients(valid_cc);
+       free_recipients(valid_bcc);
+
+       // Read in the message from the client.
+       if (do_confirm) {
+               cprintf("%d send message\n", START_CHAT_MODE);
+       }
+       else {
+               cprintf("%d send message\n", SEND_LISTING);
+       }
+
+       msg = CtdlMakeMessage(&CC->user, recp, cc,
+                             CC->room.QRname, anonymous, format_type,
+                             newusername, newuseremail, subject,
+                             ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
+                             NULL, references);
+
+       // Put together one big recipients struct containing to/cc/bcc all in one.  This is for the envelope.
+       char *all_recps = malloc(SIZ * 3);
+       strcpy(all_recps, recp);
+       if (!IsEmptyStr(cc)) {
+               if (!IsEmptyStr(all_recps)) {
+                       strcat(all_recps, ",");
+               }
+               strcat(all_recps, cc);
+       }
+       if (!IsEmptyStr(bcc)) {
+               if (!IsEmptyStr(all_recps)) {
+                       strcat(all_recps, ",");
+               }
+               strcat(all_recps, bcc);
+       }
+       if (!IsEmptyStr(all_recps)) {
+               valid = validate_recipients(all_recps, NULL, 0);
+       }
+       else {
+               valid = NULL;
+       }
+       free(all_recps);
+
+       // posting into a mailing list room? set the envelope from 
+       // to the actual mail address so others get a valid reply-to-header.
+       if ((valid != NULL) && (valid->num_room == 1) && !IsEmptyStr(valid->recp_orgroom)) {
+               CM_SetField(msg, eenVelopeTo, valid->recp_orgroom, strlen(valid->recp_orgroom));
+       }
+
+       if (msg != NULL) {
+               msgnum = CtdlSubmitMsg(msg, valid, "");
+               if (do_confirm) {
+                       cprintf("%ld\n", msgnum);
+
+                       if (StrLength(CC->StatusMessage) > 0) {
+                               cprintf("%s\n", ChrPtr(CC->StatusMessage));
+                       }
+                       else if (msgnum >= 0L) {
+                               client_write(HKEY("Message accepted.\n"));
+                       }
+                       else {
+                               client_write(HKEY("Internal error.\n"));
+                       }
+
+                       if (!CM_IsEmpty(msg, eExclusiveID)) {
+                               cprintf("%s\n", msg->cm_fields[eExclusiveID]);
+                       } else {
+                               cprintf("\n");
+                       }
+                       cprintf("000\n");
+               }
+
+               CM_Free(msg);
+       }
+       if (valid != NULL) {
+               free_recipients(valid);
+       }
+       return;
+}
+
+
+// Delete message from current room
+void cmd_dele(char *args) {
+       int num_deleted;
+       int i;
+       char msgset[SIZ];
+       char msgtok[32];
+       long *msgs;
+       int num_msgs = 0;
+
+       extract_token(msgset, args, 0, '|', sizeof msgset);
+       num_msgs = num_tokens(msgset, ',');
+       if (num_msgs < 1) {
+               cprintf("%d Nothing to do.\n", CIT_OK);
+               return;
+       }
+
+       if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
+               cprintf("%d Higher access required.\n",
+                       ERROR + HIGHER_ACCESS_REQUIRED);
+               return;
+       }
+
+       // Build our message set to be moved/copied
+       msgs = malloc(num_msgs * sizeof(long));
+       for (i=0; i<num_msgs; ++i) {
+               extract_token(msgtok, msgset, i, ',', sizeof msgtok);
+               msgs[i] = atol(msgtok);
+       }
+
+       num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
+       free(msgs);
+
+       if (num_deleted) {
+               cprintf("%d %d message%s deleted.\n", CIT_OK,
+                       num_deleted, ((num_deleted != 1) ? "s" : ""));
+       } else {
+               cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
+       }
+}
+
+
+// move or copy a message to another room
+void cmd_move(char *args) {
+       char msgset[SIZ];
+       char msgtok[32];
+       long *msgs;
+       int num_msgs = 0;
+
+       char targ[ROOMNAMELEN];
+       struct ctdlroom qtemp;
+       int err;
+       int is_copy = 0;
+       int ra;
+       int permit = 0;
+       int i;
+
+       extract_token(msgset, args, 0, '|', sizeof msgset);
+       num_msgs = num_tokens(msgset, ',');
+       if (num_msgs < 1) {
+               cprintf("%d Nothing to do.\n", CIT_OK);
+               return;
+       }
+
+       extract_token(targ, args, 1, '|', sizeof targ);
+       convert_room_name_macros(targ, sizeof targ);
+       targ[ROOMNAMELEN - 1] = 0;
+       is_copy = extract_int(args, 2);
+
+       if (CtdlGetRoom(&qtemp, targ) != 0) {
+               cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
+               return;
+       }
+
+       if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
+               cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
+               return;
+       }
+
+       CtdlGetUser(&CC->user, CC->curr_user);
+       CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
+
+       // Check for permission to perform this operation.
+       // Remember: "CC->room" is source, "qtemp" is target.
+       permit = 0;
+
+       // Admins can move/copy
+       if (CC->user.axlevel >= AxAideU) permit = 1;
+
+       // Room aides can move/copy
+       if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
+
+       // Permit move/copy from personal rooms
+       if ((CC->room.QRflags & QR_MAILBOX)
+           && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
+
+       // Permit only copy from public to personal room
+       if (    (is_copy)
+               && (!(CC->room.QRflags & QR_MAILBOX))
+               && (qtemp.QRflags & QR_MAILBOX)
+       ) {
+               permit = 1;
+       }
+
+       // Permit message removal from collaborative delete rooms
+       if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
+
+       // Users allowed to post into the target room may move into it too.
+       if ((CC->room.QRflags & QR_MAILBOX) && 
+           (qtemp.QRflags & UA_POSTALLOWED))  permit = 1;
+
+       // User must have access to target room
+       if (!(ra & UA_KNOWN))  permit = 0;
+
+       if (!permit) {
+               cprintf("%d Higher access required.\n",
+                       ERROR + HIGHER_ACCESS_REQUIRED);
+               return;
+       }
+
+       // Build our message set to be moved/copied
+       msgs = malloc(num_msgs * sizeof(long));
+       for (i=0; i<num_msgs; ++i) {
+               extract_token(msgtok, msgset, i, ',', sizeof msgtok);
+               msgs[i] = atol(msgtok);
+       }
+
+       // Do the copy
+       err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
+       if (err != 0) {
+               cprintf("%d Cannot store message(s) in %s: error %d\n",
+                       err, targ, err);
+               free(msgs);
+               return;
+       }
+
+       // Now delete the message from the source room, if this is a 'move' rather than a 'copy' operation.
+       if (is_copy == 0) {
+               CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
+       }
+       free(msgs);
+
+       cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
+}
+
+
+/*****************************************************************************/
+/*                      MODULE INITIALIZATION STUFF                          */
+/*****************************************************************************/
+char *ctdl_module_init_ctdl_message(void) {
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
+               CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
+               CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
+               CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
+               CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
+               CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
+               CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
+               CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
+               CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
+               CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
+       }
+
+        /* return our Subversion id for the Log */
+       return "ctdl_message";
+}
diff --git a/citadel/server/modules/ctdlproto/serv_rooms.c b/citadel/server/modules/ctdlproto/serv_rooms.c
new file mode 100644 (file)
index 0000000..16c69b7
--- /dev/null
@@ -0,0 +1,1050 @@
+// Server functions which perform operations on room objects.
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>    /* for cmd_rdir to read contents of the directory */
+#include <libcitadel.h>
+
+#include "../../citserver.h"
+#include "../../ctdl_module.h"
+#include "../../room_ops.h"
+#include "../../config.h"
+
+// Back-back-end for all room listing commands
+void list_roomname(struct ctdlroom *qrbuf, int ra, int current_view, int default_view) {
+       char truncated_roomname[ROOMNAMELEN];
+
+       // For my own mailbox rooms, chop off the owner prefix
+       if ( (qrbuf->QRflags & QR_MAILBOX)
+            && (atol(qrbuf->QRname) == CC->user.usernum) ) {
+               safestrncpy(truncated_roomname, qrbuf->QRname, sizeof truncated_roomname);
+               safestrncpy(truncated_roomname, &truncated_roomname[11], sizeof truncated_roomname);
+               cprintf("%s", truncated_roomname);
+       }
+       // For all other rooms, just display the name in its entirety
+       else {
+               cprintf("%s", qrbuf->QRname);
+       }
+
+       /* ...and now the other parameters */
+       cprintf("|%u|%d|%d|%d|%d|%d|%d|%ld|\n",
+               qrbuf->QRflags,
+               (int) qrbuf->QRfloor,
+               (int) qrbuf->QRorder,
+               (int) qrbuf->QRflags2,
+               ra,
+               current_view,
+               default_view,
+               qrbuf->QRmtime
+       );
+}
+
+
+// cmd_lrms()   -  List all accessible rooms, known or forgotten
+void cmd_lrms_backend(struct ctdlroom *qrbuf, void *data) {
+       int FloorBeingSearched = (-1);
+       int ra;
+       int view;
+
+       FloorBeingSearched = *(int *)data;
+       CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
+
+       if ((( ra & (UA_KNOWN | UA_ZAPPED)))
+           && ((qrbuf->QRfloor == (FloorBeingSearched))
+               || ((FloorBeingSearched) < 0)))
+               list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
+}
+
+
+void cmd_lrms(char *argbuf) {
+       int FloorBeingSearched = (-1);
+       if (!IsEmptyStr(argbuf))
+               FloorBeingSearched = extract_int(argbuf, 0);
+
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+       CtdlGetUser(&CC->user, CC->curr_user);
+       cprintf("%d Accessible rooms:\n", LISTING_FOLLOWS);
+
+       CtdlForEachRoom(cmd_lrms_backend, &FloorBeingSearched);
+       cprintf("000\n");
+}
+
+
+// cmd_lkra()   -  List all known rooms
+void cmd_lkra_backend(struct ctdlroom *qrbuf, void *data) {
+       int FloorBeingSearched = (-1);
+       int ra;
+       int view;
+
+       FloorBeingSearched = *(int *)data;
+       CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
+
+       if ((( ra & (UA_KNOWN))) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
+               list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
+       }
+}
+
+
+void cmd_lkra(char *argbuf) {
+       int FloorBeingSearched = (-1);
+       if (!IsEmptyStr(argbuf)) {
+               FloorBeingSearched = extract_int(argbuf, 0);
+       }
+
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+       
+       CtdlGetUser(&CC->user, CC->curr_user);
+       cprintf("%d Known rooms:\n", LISTING_FOLLOWS);
+
+       CtdlForEachRoom(cmd_lkra_backend, &FloorBeingSearched);
+       cprintf("000\n");
+}
+
+
+void cmd_lprm_backend(struct ctdlroom *qrbuf, void *data) {
+       int FloorBeingSearched = (-1);
+       int ra;
+       int view;
+
+       FloorBeingSearched = *(int *)data;
+       CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
+
+       if (((qrbuf->QRflags & QR_PRIVATE) == 0) && ((qrbuf->QRflags & QR_MAILBOX) == 0) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
+               list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
+       }
+}
+
+
+void cmd_lprm(char *argbuf) {
+       int FloorBeingSearched = (-1);
+       if (!IsEmptyStr(argbuf)) {
+               FloorBeingSearched = extract_int(argbuf, 0);
+       }
+
+       cprintf("%d Public rooms:\n", LISTING_FOLLOWS);
+
+       CtdlForEachRoom(cmd_lprm_backend, &FloorBeingSearched);
+       cprintf("000\n");
+}
+
+
+// cmd_lkrn()   -  List all known rooms with new messages
+void cmd_lkrn_backend(struct ctdlroom *qrbuf, void *data) {
+       int FloorBeingSearched = (-1);
+       int ra;
+       int view;
+
+       FloorBeingSearched = *(int *)data;
+       CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
+
+       if ((ra & UA_KNOWN) && (ra & UA_HASNEWMSGS) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
+               list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
+       }
+}
+
+
+void cmd_lkrn(char *argbuf) {
+       int FloorBeingSearched = (-1);
+       if (!IsEmptyStr(argbuf)) {
+               FloorBeingSearched = extract_int(argbuf, 0);
+       }
+
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+       
+       CtdlGetUser(&CC->user, CC->curr_user);
+       cprintf("%d Rooms w/ new msgs:\n", LISTING_FOLLOWS);
+
+       CtdlForEachRoom(cmd_lkrn_backend, &FloorBeingSearched);
+       cprintf("000\n");
+}
+
+
+// cmd_lkro()   -  List all known rooms
+void cmd_lkro_backend(struct ctdlroom *qrbuf, void *data) {
+       int FloorBeingSearched = (-1);
+       int ra;
+       int view;
+
+       FloorBeingSearched = *(int *)data;
+       CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
+
+       if ((ra & UA_KNOWN) && ((ra & UA_HASNEWMSGS) == 0) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
+               list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
+       }
+}
+
+
+void cmd_lkro(char *argbuf) {
+       int FloorBeingSearched = (-1);
+       if (!IsEmptyStr(argbuf)) {
+               FloorBeingSearched = extract_int(argbuf, 0);
+       }
+
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+       
+       CtdlGetUser(&CC->user, CC->curr_user);
+       cprintf("%d Rooms w/o new msgs:\n", LISTING_FOLLOWS);
+
+       CtdlForEachRoom(cmd_lkro_backend, &FloorBeingSearched);
+       cprintf("000\n");
+}
+
+
+// cmd_lzrm()   -  List all forgotten rooms
+void cmd_lzrm_backend(struct ctdlroom *qrbuf, void *data) {
+       int FloorBeingSearched = (-1);
+       int ra;
+       int view;
+
+       FloorBeingSearched = *(int *)data;
+       CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
+
+       if ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
+               list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
+       }
+}
+
+
+void cmd_lzrm(char *argbuf) {
+       int FloorBeingSearched = (-1);
+       if (!IsEmptyStr(argbuf))
+               FloorBeingSearched = extract_int(argbuf, 0);
+
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+       
+       CtdlGetUser(&CC->user, CC->curr_user);
+       cprintf("%d Zapped rooms:\n", LISTING_FOLLOWS);
+
+       CtdlForEachRoom(cmd_lzrm_backend, &FloorBeingSearched);
+       cprintf("000\n");
+}
+
+
+// cmd_goto()  -  goto a new room
+void cmd_goto(char *gargs) {
+       struct CitContext *CCC = CC;
+       struct ctdlroom QRscratch;
+       int c;
+       int ok = 0;
+       int ra;
+       char augmented_roomname[ROOMNAMELEN];
+       char towhere[ROOMNAMELEN];
+       char password[32];
+       int transiently = 0;
+
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+       extract_token(towhere, gargs, 0, '|', sizeof towhere);
+       extract_token(password, gargs, 1, '|', sizeof password);
+       transiently = extract_int(gargs, 2);
+
+       CtdlGetUser(&CCC->user, CCC->curr_user);
+
+       // Handle some of the macro named rooms
+       convert_room_name_macros(towhere, sizeof towhere);
+
+       // First try a regular match
+       c = CtdlGetRoom(&QRscratch, towhere);
+
+       // Then try a mailbox name match
+       if (c != 0) {
+               CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CCC->user, towhere);
+               c = CtdlGetRoom(&QRscratch, augmented_roomname);
+               if (c == 0) {
+                       safestrncpy(towhere, augmented_roomname, sizeof towhere);
+               }
+       }
+
+       // And if the room was found...
+       if (c == 0) {
+               // Let internal programs go directly to any room.
+               if (CCC->internal_pgm) {
+                       memcpy(&CCC->room, &QRscratch, sizeof(struct ctdlroom));
+                       CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL);
+                       return;
+               }
+
+               // See if there is an existing user/room relationship
+               CtdlRoomAccess(&QRscratch, &CCC->user, &ra, NULL);
+
+               // normal clients have to pass through security
+               if (ra & UA_GOTOALLOWED) {
+                       ok = 1;
+               }
+
+               if (ok == 1) {
+                       if ((QRscratch.QRflags & QR_MAILBOX) &&
+                           ((ra & UA_GOTOALLOWED))) {
+                               memcpy(&CCC->room, &QRscratch, sizeof(struct ctdlroom));
+                               CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL);
+                               return;
+                       }
+                       else if ((QRscratch.QRflags & QR_PASSWORDED) &&
+                               ((ra & UA_KNOWN) == 0) &&
+                               (strcasecmp(QRscratch.QRpasswd, password)) &&
+                               (CCC->user.axlevel < AxAideU)
+                       ) {
+                               cprintf("%d wrong or missing passwd\n", ERROR + PASSWORD_REQUIRED);
+                               return;
+                       }
+                       else if ((QRscratch.QRflags & QR_PRIVATE) &&
+                                  ((QRscratch.QRflags & QR_PASSWORDED) == 0) &&
+                                  ((QRscratch.QRflags & QR_GUESSNAME) == 0) &&
+                                  ((ra & UA_KNOWN) == 0) &&
+                                  (CCC->user.axlevel < AxAideU)
+                                  ) {
+                               syslog(LOG_DEBUG, "rooms: failed to acquire private room");
+                       }
+                       else {
+                               memcpy(&CCC->room, &QRscratch, sizeof(struct ctdlroom));
+                               CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL);
+                               return;
+                       }
+               }
+       }
+
+       cprintf("%d room '%s' not found\n", ERROR + ROOM_NOT_FOUND, towhere);
+}
+
+
+void cmd_whok(char *cmdbuf) {
+       struct ctdluser temp;
+       struct cdbdata *cdbus;
+       int ra;
+
+       cprintf("%d Who knows room:\n", LISTING_FOLLOWS);
+       cdb_rewind(CDB_USERS);
+       while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
+               memset(&temp, 0, sizeof temp);
+               memcpy(&temp, cdbus->ptr, sizeof temp);
+               cdb_free(cdbus);
+
+               CtdlRoomAccess(&CC->room, &temp, &ra, NULL);
+               if ((!IsEmptyStr(temp.fullname)) && 
+                   (CC->room.QRflags & QR_INUSE) &&
+                   (ra & UA_KNOWN)
+                       )
+                       cprintf("%s\n", temp.fullname);
+       }
+       cprintf("000\n");
+}
+
+
+// RDIR command for room directory
+void cmd_rdir(char *cmdbuf) {
+       char buf[256];
+       char comment[256];
+       FILE *fd;
+       struct stat statbuf;
+       DIR *filedir = NULL;
+       struct dirent *filedir_entry;
+       int d_namelen;
+       char buf2[SIZ];
+       char mimebuf[64];
+       long len;
+       
+       if (CtdlAccessCheck(ac_logged_in)) return;
+       
+       CtdlGetRoom(&CC->room, CC->room.QRname);
+       CtdlGetUser(&CC->user, CC->curr_user);
+
+       if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
+               cprintf("%d not here.\n", ERROR + NOT_HERE);
+               return;
+       }
+       if (((CC->room.QRflags & QR_VISDIR) == 0)
+               && (CC->user.axlevel < AxAideU)
+               && (CC->user.usernum != CC->room.QRroomaide))
+       {
+               cprintf("%d not here.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+               return;
+       }
+
+       snprintf(buf, sizeof buf, "%s/%s", ctdl_file_dir, CC->room.QRdirname);
+       filedir = opendir (buf);
+       
+       if (filedir == NULL) {
+               cprintf("%d not here.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+               return;
+       }
+       cprintf("%d %s|%s/%s\n", LISTING_FOLLOWS, CtdlGetConfigStr("c_fqdn"), ctdl_file_dir, CC->room.QRdirname);
+       
+       snprintf(buf, sizeof buf, "%s/%s/filedir", ctdl_file_dir, CC->room.QRdirname);
+       fd = fopen(buf, "r");
+       if (fd == NULL) {
+               fd = fopen("/dev/null", "r");
+       }
+       while ((filedir_entry = readdir(filedir))) {
+               if (strcasecmp(filedir_entry->d_name, "filedir") && filedir_entry->d_name[0] != '.') {
+#ifdef _DIRENT_HAVE_D_NAMELEN
+                       d_namelen = filedir_entry->d_namlen;
+#else
+                       d_namelen = strlen(filedir_entry->d_name);
+#endif
+                       snprintf(buf, sizeof buf, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, filedir_entry->d_name);
+                       stat(buf, &statbuf);    /* stat the file */
+                       if (!(statbuf.st_mode & S_IFREG)) {
+                               snprintf(buf2, sizeof buf2,
+                                       "\"%s\" appears in the file directory for room \"%s\" but is not a regular file.  Directories, named pipes, sockets, etc. are not usable in Citadel room directories.\n",
+                                       buf, CC->room.QRname
+                               );
+                               CtdlAideMessage(buf2, "Unusable data found in room directory");
+                               continue;       /* not a useable file type so don't show it */
+                       }
+                       safestrncpy(comment, "", sizeof comment);
+                       fseek(fd, 0L, 0);       /* rewind descriptions file */
+                       /* Get the description from the descriptions file */
+                       while ((fgets(buf, sizeof buf, fd) != NULL) && (IsEmptyStr(comment))) {
+                               buf[strlen(buf) - 1] = 0;
+                               if ((!strncasecmp(buf, filedir_entry->d_name, d_namelen)) && (buf[d_namelen] == ' '))
+                                       safestrncpy(comment, &buf[d_namelen + 1], sizeof comment);
+                       }
+                       len = extract_token (mimebuf, comment, 0,' ', 64);
+                       if ((len <0) || strchr(mimebuf, '/') == NULL) {
+                               snprintf (mimebuf, 64, "application/octetstream");
+                               len = 0;
+                       }
+                       cprintf("%s|%ld|%s|%s\n", 
+                               filedir_entry->d_name, 
+                               (long)statbuf.st_size, 
+                               mimebuf, 
+                               &comment[len]);
+               }
+       }
+       fclose(fd);
+       closedir(filedir);
+       
+       cprintf("000\n");
+}
+
+
+// get room parameters (admin or room admin command)
+void cmd_getr(char *cmdbuf) {
+       if (CtdlAccessCheck(ac_room_aide)) return;
+
+       CtdlGetRoom(&CC->room, CC->room.QRname);
+       cprintf("%d%c%s|%s|%s|%d|%d|%d|%d|%d|\n",
+               CIT_OK,
+               CtdlCheckExpress(),
+               ((CC->room.QRflags & QR_MAILBOX) ?  &CC->room.QRname[11] : CC->room.QRname),
+               ((CC->room.QRflags & QR_PASSWORDED) ?  CC->room.QRpasswd : ""),
+               ((CC->room.QRflags & QR_DIRECTORY) ?  CC->room.QRdirname : ""),
+               CC->room.QRflags,
+               (int) CC->room.QRfloor,
+               (int) CC->room.QRorder,
+               CC->room.QRdefaultview,
+               CC->room.QRflags2
+       );
+}
+
+
+// set room parameters (admin or room admin command)
+void cmd_setr(char *args) {
+       char buf[256];
+       int new_order = 0;
+       int r;
+       int new_floor;
+       char new_name[ROOMNAMELEN];
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       if (num_parms(args) >= 6) {
+               new_floor = extract_int(args, 5);
+       }
+       else {
+               new_floor = (-1);       /* don't change the floor */
+       }
+
+       /* When is a new name more than just a new name?  When the old name
+        * has a namespace prefix.
+        */
+       if (CC->room.QRflags & QR_MAILBOX) {
+               sprintf(new_name, "%010ld.", atol(CC->room.QRname) );
+       }
+       else {
+               safestrncpy(new_name, "", sizeof new_name);
+       }
+       extract_token(&new_name[strlen(new_name)], args, 0, '|', (sizeof new_name - strlen(new_name)));
+
+       r = CtdlRenameRoom(CC->room.QRname, new_name, new_floor);
+
+       if (r == crr_room_not_found) {
+               cprintf("%d Internal error - room not found?\n", ERROR + INTERNAL_ERROR);
+       }
+       else if (r == crr_already_exists) {
+               cprintf("%d '%s' already exists.\n",
+                       ERROR + ALREADY_EXISTS, new_name);
+       }
+       else if (r == crr_noneditable) {
+               cprintf("%d Cannot edit this room.\n", ERROR + NOT_HERE);
+       }
+       else if (r == crr_invalid_floor) {
+               cprintf("%d Target floor does not exist.\n",
+                       ERROR + INVALID_FLOOR_OPERATION);
+       }
+       else if (r == crr_access_denied) {
+               cprintf("%d You do not have permission to edit '%s'\n",
+                       ERROR + HIGHER_ACCESS_REQUIRED,
+                       CC->room.QRname);
+       }
+       else if (r != crr_ok) {
+               cprintf("%d Error: CtdlRenameRoom() returned %d\n",
+                       ERROR + INTERNAL_ERROR, r);
+       }
+
+       if (r != crr_ok) {
+               return;
+       }
+
+       CtdlGetRoom(&CC->room, new_name);
+
+       /* Now we have to do a bunch of other stuff */
+
+       if (num_parms(args) >= 7) {
+               new_order = extract_int(args, 6);
+               if (new_order < 1)
+                       new_order = 1;
+               if (new_order > 127)
+                       new_order = 127;
+       }
+
+       CtdlGetRoomLock(&CC->room, CC->room.QRname);
+
+       /* Directory room */
+       extract_token(buf, args, 2, '|', sizeof buf);
+       buf[15] = 0;
+       safestrncpy(CC->room.QRdirname, buf,
+               sizeof CC->room.QRdirname);
+
+       /* Default view */
+       if (num_parms(args) >= 8) {
+               CC->room.QRdefaultview = extract_int(args, 7);
+       }
+
+       /* Second set of flags */
+       if (num_parms(args) >= 9) {
+               CC->room.QRflags2 = extract_int(args, 8);
+       }
+
+       /* Misc. flags */
+       CC->room.QRflags = (extract_int(args, 3) | QR_INUSE);
+       /* Clean up a client boo-boo: if the client set the room to
+        * guess-name or passworded, ensure that the private flag is
+        * also set.
+        */
+       if ((CC->room.QRflags & QR_GUESSNAME)
+           || (CC->room.QRflags & QR_PASSWORDED))
+               CC->room.QRflags |= QR_PRIVATE;
+
+       /* Some changes can't apply to BASEROOM */
+       if (!strncasecmp(CC->room.QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
+               CC->room.QRorder = 0;
+               CC->room.QRpasswd[0] = '\0';
+               CC->room.QRflags &= ~(QR_PRIVATE & QR_PASSWORDED &
+                       QR_GUESSNAME & QR_PREFONLY & QR_MAILBOX);
+               CC->room.QRflags |= QR_PERMANENT;
+       }
+       else {  
+               /* March order (doesn't apply to AIDEROOM) */
+               if (num_parms(args) >= 7)
+                       CC->room.QRorder = (char) new_order;
+               /* Room password */
+               extract_token(buf, args, 1, '|', sizeof buf);
+               buf[10] = 0;
+               safestrncpy(CC->room.QRpasswd, buf, sizeof CC->room.QRpasswd);
+               /* Kick everyone out if the client requested it
+                * (by changing the room's generation number)
+                */
+               if (extract_int(args, 4)) {
+                       time(&CC->room.QRgen);
+               }
+       }
+       /* Some changes can't apply to AIDEROOM */
+       if (!strncasecmp(CC->room.QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
+               CC->room.QRorder = 0;
+               CC->room.QRflags &= ~QR_MAILBOX;
+               CC->room.QRflags |= QR_PERMANENT;
+       }
+
+       /* Write the room record back to disk */
+       CtdlPutRoomLock(&CC->room);
+
+       /* Create a room directory if necessary */
+       if (CC->room.QRflags & QR_DIRECTORY) {
+               snprintf(buf, sizeof buf,"%s/%s", ctdl_file_dir, CC->room.QRdirname);
+               mkdir(buf, 0755);
+       }
+       snprintf(buf, sizeof buf, "The room \"%s\" has been edited by %s.\n",
+               CC->room.QRname,
+               (CC->logged_in ? CC->curr_user : "an administrator")
+       );
+       CtdlAideMessage(buf, "Room modification Message");
+       cprintf("%d Ok\n", CIT_OK);
+}
+
+
+// get the name of the room admin for this room
+void cmd_geta(char *cmdbuf) {
+       struct ctdluser usbuf;
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       if (CtdlGetUserByNumber(&usbuf, CC->room.QRroomaide) == 0) {
+               cprintf("%d %s\n", CIT_OK, usbuf.fullname);
+       }
+       else {
+               cprintf("%d \n", CIT_OK);
+       }
+}
+
+
+// set the room admin for this room
+void cmd_seta(char *new_ra) {
+       struct ctdluser usbuf;
+       long newu;
+       char buf[SIZ];
+       int post_notice;
+
+       if (CtdlAccessCheck(ac_room_aide)) return;
+
+       if (CtdlGetUser(&usbuf, new_ra) != 0) {
+               newu = (-1L);
+       }
+       else {
+               newu = usbuf.usernum;
+       }
+
+       CtdlGetRoomLock(&CC->room, CC->room.QRname);
+       post_notice = 0;
+       if (CC->room.QRroomaide != newu) {
+               post_notice = 1;
+       }
+       CC->room.QRroomaide = newu;
+       CtdlPutRoomLock(&CC->room);
+
+       // We have to post the change notice _after_ writing changes to 
+       // the room table, otherwise it would deadlock!
+       if (post_notice == 1) {
+               if (!IsEmptyStr(usbuf.fullname))
+                       snprintf(buf, sizeof buf,
+                               "%s is now the room admin for \"%s\".\n",
+                               usbuf.fullname, CC->room.QRname);
+               else
+                       snprintf(buf, sizeof buf,
+                               "There is now no room admin for \"%s\".\n",
+                               CC->room.QRname);
+               CtdlAideMessage(buf, "Admin Room Modification");
+       }
+       cprintf("%d Ok\n", CIT_OK);
+}
+
+
+// Retrieve info file for this room (this ought to be upgraded to handle non-plain-text)
+void cmd_rinf(char *argbuf) {
+       struct CtdlMessage *msg = CtdlFetchMessage(CC->room.msgnum_info, 1);
+       if (msg != NULL) {
+               cprintf("%d Info:\n", LISTING_FOLLOWS);
+               CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_NONE, 0, 0, 0);
+               CM_Free(msg);
+               cprintf("000\n");
+       }
+       else {
+               cprintf("%d No info file.\n", ERROR + FILE_NOT_FOUND);
+       }
+}
+
+
+// admin command: kill the current room
+void cmd_kill(char *argbuf) {
+       char deleted_room_name[ROOMNAMELEN];
+       char msg[SIZ];
+       int kill_ok;
+
+       kill_ok = extract_int(argbuf, 0);
+
+       if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->room) == 0) {
+               cprintf("%d Can't delete this room.\n", ERROR + NOT_HERE);
+               return;
+       }
+       if (kill_ok) {
+               if (CC->room.QRflags & QR_MAILBOX) {
+                       safestrncpy(deleted_room_name, &CC->room.QRname[11], sizeof deleted_room_name);
+               }
+               else {
+                       safestrncpy(deleted_room_name, CC->room.QRname, sizeof deleted_room_name);
+               }
+
+               /* Do the dirty work */
+               CtdlScheduleRoomForDeletion(&CC->room);
+
+               /* Return to the Lobby */
+               CtdlUserGoto(CtdlGetConfigStr("c_baseroom"), 0, 0, NULL, NULL, NULL, NULL);
+
+               /* tell the world what we did */
+               snprintf(msg, sizeof msg, "The room \"%s\" has been deleted by %s.\n",
+                        deleted_room_name,
+                       (CC->logged_in ? CC->curr_user : "an administrator")
+               );
+               CtdlAideMessage(msg, "Room Purger Message");
+               cprintf("%d '%s' deleted.\n", CIT_OK, deleted_room_name);
+       }
+       else {
+               cprintf("%d ok to delete.\n", CIT_OK);
+       }
+}
+
+
+// create a new room
+void cmd_cre8(char *args) {
+       int cre8_ok;
+       char new_room_name[ROOMNAMELEN];
+       int new_room_type;
+       char new_room_pass[32];
+       int new_room_floor;
+       int new_room_view;
+       char *notification_message = NULL;
+       unsigned newflags;
+       struct floor *fl;
+       int avoid_access = 0;
+
+       cre8_ok = extract_int(args, 0);
+       extract_token(new_room_name, args, 1, '|', sizeof new_room_name);
+       new_room_name[ROOMNAMELEN - 1] = 0;
+       new_room_type = extract_int(args, 2);
+       extract_token(new_room_pass, args, 3, '|', sizeof new_room_pass);
+       avoid_access = extract_int(args, 5);
+       new_room_view = extract_int(args, 6);
+       new_room_pass[9] = 0;
+       new_room_floor = 0;
+
+       if ((IsEmptyStr(new_room_name)) && (cre8_ok == 1)) {
+               cprintf("%d Invalid room name.\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       if (!strcasecmp(new_room_name, MAILROOM)) {
+               cprintf("%d '%s' already exists.\n",
+                       ERROR + ALREADY_EXISTS, new_room_name);
+               return;
+       }
+
+       if (num_parms(args) >= 5) {
+               fl = CtdlGetCachedFloor(extract_int(args, 4));
+               if (fl == NULL) {
+                       cprintf("%d Invalid floor number.\n",
+                               ERROR + INVALID_FLOOR_OPERATION);
+                       return;
+               }
+               else if ((fl->f_flags & F_INUSE) == 0) {
+                       cprintf("%d Invalid floor number.\n",
+                               ERROR + INVALID_FLOOR_OPERATION);
+                       return;
+               } else {
+                       new_room_floor = extract_int(args, 4);
+               }
+       }
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       if (CC->user.axlevel < CtdlGetConfigInt("c_createax") && !CC->internal_pgm) {
+               cprintf("%d You need higher access to create rooms.\n",
+                       ERROR + HIGHER_ACCESS_REQUIRED);
+               return;
+       }
+
+       if ((IsEmptyStr(new_room_name)) && (cre8_ok == 0)) {
+               cprintf("%d Ok to create rooms.\n", CIT_OK);
+               return;
+       }
+
+       if ((new_room_type < 0) || (new_room_type > 5)) {
+               cprintf("%d Invalid room type.\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       if (new_room_type == 5) {
+               if (CC->user.axlevel < AxAideU) {
+                       cprintf("%d Higher access required\n", 
+                               ERROR + HIGHER_ACCESS_REQUIRED);
+                       return;
+               }
+       }
+
+       /* Check to make sure the requested room name doesn't already exist */
+       newflags = CtdlCreateRoom(new_room_name,
+                               new_room_type, new_room_pass, new_room_floor,
+                               0, avoid_access, new_room_view);
+       if (newflags == 0) {
+               cprintf("%d '%s' already exists.\n",
+                       ERROR + ALREADY_EXISTS, new_room_name);
+               return;
+       }
+
+       if (cre8_ok == 0) {
+               cprintf("%d OK to create '%s'\n", CIT_OK, new_room_name);
+               return;
+       }
+
+       /* If we reach this point, the room needs to be created. */
+
+       newflags = CtdlCreateRoom(new_room_name,
+                          new_room_type, new_room_pass, new_room_floor, 1, 0,
+                          new_room_view);
+
+       /* post a message in Aide> describing the new room */
+       notification_message = malloc(1024);
+       snprintf(notification_message, 1024,
+               "A new room called \"%s\" has been created by %s%s%s%s%s%s\n",
+               new_room_name,
+               (CC->logged_in ? CC->curr_user : "an administrator"),
+               ((newflags & QR_MAILBOX) ? " [personal]" : ""),
+               ((newflags & QR_PRIVATE) ? " [private]" : ""),
+               ((newflags & QR_GUESSNAME) ? " [hidden]" : ""),
+               ((newflags & QR_PASSWORDED) ? " Password: " : ""),
+               ((newflags & QR_PASSWORDED) ? new_room_pass : "")
+       );
+       CtdlAideMessage(notification_message, "Room Creation Message");
+       free(notification_message);
+
+       cprintf("%d '%s' has been created.\n", CIT_OK, new_room_name);
+}
+
+
+// Upload the room banner text for this room.
+// This should be amended to handle content types other than plain text.
+void cmd_einf(char *ok) {                              /* enter info file for current room */
+       char buf[SIZ];
+       unbuffer_output();
+
+       if (CtdlAccessCheck(ac_room_aide)) return;
+
+       if (atoi(ok) == 0) {
+               cprintf("%d Ok.\n", CIT_OK);
+               return;
+       }
+
+       StrBuf *NewBanner = NewStrBufPlain("Content-type: text/plain; charset=UTF-8\nContent-transfer-encoding: 8bit\n\n", -1);
+
+       cprintf("%d Transmit new banner in plain text now.\n", SEND_LISTING);
+       while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
+               StrBufAppendBufPlain(NewBanner, buf, -1, 0);
+               StrBufAppendBufPlain(NewBanner, HKEY("\n"), 0);
+       }
+
+       // We have read the new banner from the user , now save it
+       long new_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, ChrPtr(NewBanner), FMT_RFC822, "Banner submitted with EINF command");
+       FreeStrBuf(&NewBanner);
+
+       // Update the room record with a pointer to our new banner
+       CtdlGetRoomLock(&CC->room, CC->room.QRname);
+       long old_msgnum = CC->room.msgnum_info;
+       CC->room.msgnum_info = new_msgnum;
+       CtdlPutRoomLock(&CC->room);
+
+       // Delete the old one
+       CtdlDeleteMessages(SYSCONFIGROOM, &old_msgnum, 1, "");
+}
+
+
+// cmd_lflr()   -  List all known floors
+void cmd_lflr(char *gargs) {
+       int a;
+       struct floor flbuf;
+
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+       cprintf("%d Known floors:\n", LISTING_FOLLOWS);
+
+       for (a = 0; a < MAXFLOORS; ++a) {
+               CtdlGetFloor(&flbuf, a);
+               if (flbuf.f_flags & F_INUSE) {
+                       cprintf("%d|%s|%d\n", a, flbuf.f_name, flbuf.f_ref_count);
+               }
+       }
+       cprintf("000\n");
+}
+
+
+// create a new floor
+void cmd_cflr(char *argbuf) {
+       char new_floor_name[256];
+       struct floor flbuf;
+       int cflr_ok;
+       int free_slot = (-1);
+       int a;
+
+       extract_token(new_floor_name, argbuf, 0, '|', sizeof new_floor_name);
+       cflr_ok = extract_int(argbuf, 1);
+
+       if (CtdlAccessCheck(ac_aide)) return;
+
+       if (IsEmptyStr(new_floor_name)) {
+               cprintf("%d Blank floor name not allowed.\n",
+                       ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       for (a = 0; a < MAXFLOORS; ++a) {
+               CtdlGetFloor(&flbuf, a);
+
+               /* note any free slots while we're scanning... */
+               if (((flbuf.f_flags & F_INUSE) == 0)
+                   && (free_slot < 0))
+                       free_slot = a;
+
+               /* check to see if it already exists */
+               if ((!strcasecmp(flbuf.f_name, new_floor_name))
+                   && (flbuf.f_flags & F_INUSE)) {
+                       cprintf("%d Floor '%s' already exists.\n",
+                               ERROR + ALREADY_EXISTS,
+                               flbuf.f_name);
+                       return;
+               }
+       }
+
+       if (free_slot < 0) {
+               cprintf("%d There is no space available for a new floor.\n",
+                       ERROR + INVALID_FLOOR_OPERATION);
+               return;
+       }
+       if (cflr_ok == 0) {
+               cprintf("%d ok to create...\n", CIT_OK);
+               return;
+       }
+       lgetfloor(&flbuf, free_slot);
+       flbuf.f_flags = F_INUSE;
+       flbuf.f_ref_count = 0;
+       safestrncpy(flbuf.f_name, new_floor_name, sizeof flbuf.f_name);
+       lputfloor(&flbuf, free_slot);
+       cprintf("%d %d\n", CIT_OK, free_slot);
+}
+
+
+// delete a floor
+void cmd_kflr(char *argbuf) {
+       struct floor flbuf;
+       int floor_to_delete;
+       int kflr_ok;
+       int delete_ok;
+
+       floor_to_delete = extract_int(argbuf, 0);
+       kflr_ok = extract_int(argbuf, 1);
+
+       if (CtdlAccessCheck(ac_aide)) return;
+
+       lgetfloor(&flbuf, floor_to_delete);
+
+       delete_ok = 1;
+       if ((flbuf.f_flags & F_INUSE) == 0) {
+               cprintf("%d Floor %d not in use.\n", ERROR + INVALID_FLOOR_OPERATION, floor_to_delete);
+               delete_ok = 0;
+       } else {
+               if (flbuf.f_ref_count != 0) {
+                       cprintf("%d Cannot delete; floor contains %d rooms.\n",
+                               ERROR + INVALID_FLOOR_OPERATION,
+                               flbuf.f_ref_count);
+                       delete_ok = 0;
+               }
+               else {
+                       if (kflr_ok == 1) {
+                               cprintf("%d Ok\n", CIT_OK);
+                       }
+                       else {
+                               cprintf("%d Ok to delete...\n", CIT_OK);
+                       }
+
+               }
+
+       }
+
+       if ((delete_ok == 1) && (kflr_ok == 1)) {
+               flbuf.f_flags = 0;
+       }
+       lputfloor(&flbuf, floor_to_delete);
+}
+
+
+// edit a floor
+void cmd_eflr(char *argbuf) {
+       struct floor flbuf;
+       int floor_num;
+       int np;
+
+       np = num_parms(argbuf);
+       if (np < 1) {
+               cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       if (CtdlAccessCheck(ac_aide)) return;
+
+       floor_num = extract_int(argbuf, 0);
+       lgetfloor(&flbuf, floor_num);
+       if ((flbuf.f_flags & F_INUSE) == 0) {
+               lputfloor(&flbuf, floor_num);
+               cprintf("%d Floor %d is not in use.\n", ERROR + INVALID_FLOOR_OPERATION, floor_num);
+               return;
+       }
+       if (np >= 2) {
+               extract_token(flbuf.f_name, argbuf, 1, '|', sizeof flbuf.f_name);
+       }
+       lputfloor(&flbuf, floor_num);
+
+       cprintf("%d Ok\n", CIT_OK);
+}
+
+
+// cmd_stat()  -  return the modification time of the current room (maybe other things in the future)
+void cmd_stat(char *gargs) {
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+       CtdlGetRoom(&CC->room, CC->room.QRname);
+       cprintf("%d %s|%ld|\n", CIT_OK, CC->room.QRname, CC->room.QRmtime);
+}
+
+
+/*****************************************************************************/
+/*                      MODULE INITIALIZATION STUFF                          */
+/*****************************************************************************/
+
+char *ctdl_module_init_rooms(void) {
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_lrms, "LRMS", "List rooms");
+               CtdlRegisterProtoHook(cmd_lkra, "LKRA", "List all known rooms");
+               CtdlRegisterProtoHook(cmd_lkrn, "LKRN", "List known rooms with new messages");
+               CtdlRegisterProtoHook(cmd_lkro, "LKRO", "List known rooms without new messages");
+               CtdlRegisterProtoHook(cmd_lzrm, "LZRM", "List zapped rooms");
+               CtdlRegisterProtoHook(cmd_lprm, "LPRM", "List public rooms");
+               CtdlRegisterProtoHook(cmd_goto, "GOTO", "Goto a named room");
+               CtdlRegisterProtoHook(cmd_stat, "STAT", "Get mtime of the current room");
+               CtdlRegisterProtoHook(cmd_whok, "WHOK", "List users who know this room");
+               CtdlRegisterProtoHook(cmd_rdir, "RDIR", "List files in room directory");
+               CtdlRegisterProtoHook(cmd_getr, "GETR", "Get room parameters");
+               CtdlRegisterProtoHook(cmd_setr, "SETR", "Set room parameters");
+               CtdlRegisterProtoHook(cmd_geta, "GETA", "Get the room admin name");
+               CtdlRegisterProtoHook(cmd_seta, "SETA", "Set the room admin for this room");
+               CtdlRegisterProtoHook(cmd_rinf, "RINF", "Fetch room info file");
+               CtdlRegisterProtoHook(cmd_kill, "KILL", "Kill (delete) the current room");
+               CtdlRegisterProtoHook(cmd_cre8, "CRE8", "Create a new room");
+               CtdlRegisterProtoHook(cmd_einf, "EINF", "Enter info file for the current room");
+               CtdlRegisterProtoHook(cmd_lflr, "LFLR", "List all known floors");
+               CtdlRegisterProtoHook(cmd_cflr, "CFLR", "Create a new floor");
+               CtdlRegisterProtoHook(cmd_kflr, "KFLR", "Kill a floor");
+               CtdlRegisterProtoHook(cmd_eflr, "EFLR", "Edit a floor");
+       }
+        /* return our id for the log */
+       return "rooms";
+}
diff --git a/citadel/server/modules/ctdlproto/serv_session.c b/citadel/server/modules/ctdlproto/serv_session.c
new file mode 100644 (file)
index 0000000..cb41b53
--- /dev/null
@@ -0,0 +1,229 @@
+/* 
+ * Server functions which perform operations on user objects.
+ *
+ * Copyright (c) 1987-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <libcitadel.h>
+#include "../../citserver.h"
+#include "../../ctdl_module.h"
+#include "../../config.h"
+
+
+void cmd_noop(char *argbuf)
+{
+       cprintf("%d%cok\n", CIT_OK, CtdlCheckExpress() );
+}
+
+
+void cmd_qnop(char *argbuf)
+{
+       /* do nothing, this command returns no response */
+}
+
+
+/*
+ * Set or unset asynchronous protocol mode
+ */
+void cmd_asyn(char *argbuf)
+{
+       int new_state;
+
+       new_state = extract_int(argbuf, 0);
+       if ((new_state == 0) || (new_state == 1)) {
+               CC->is_async = new_state;
+       }
+       cprintf("%d %d\n", CIT_OK, CC->is_async);
+}
+
+
+/*
+ * cmd_info()  -  Identify this server and its capabilities to the client
+ */
+void cmd_info(char *cmdbuf) {
+       cprintf("%d Server info:\n", LISTING_FOLLOWS);
+       cprintf("%d\n", CC->cs_pid);
+       cprintf("%s\n", CtdlGetConfigStr("c_nodename"));
+       cprintf("%s\n", CtdlGetConfigStr("c_humannode"));
+       cprintf("%s\n", CtdlGetConfigStr("c_fqdn"));
+       cprintf("%s\n", CITADEL);
+       cprintf("%d\n", REV_LEVEL);
+       cprintf("%s\n", CtdlGetConfigStr("c_site_location"));
+       cprintf("%s\n", CtdlGetConfigStr("c_sysadm"));
+       cprintf("%d\n", SERVER_TYPE);
+       cprintf("%s\n", CtdlGetConfigStr("c_moreprompt"));
+       cprintf("1\n"); /* 1 = yes, this system supports floors */
+       cprintf("1\n"); /* 1 = we support the extended paging options */
+       cprintf("\n");  /* no longer used */
+       cprintf("1\n"); /* 1 = yes, this system supports the QNOP command */
+       cprintf("1\n"); /* 1 = yes, this server is LDAP-enabled */
+
+       if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_NATIVE) && (CtdlGetConfigInt("c_disable_newu") == 0))
+       {
+               cprintf("%d\n", CtdlGetConfigInt("c_disable_newu"));
+       }
+       else {
+               cprintf("1\n"); /* "create new user" does not work with non-native auth modes */
+       }
+
+       cprintf("%s\n", CtdlGetConfigStr("c_default_cal_zone"));
+
+       cprintf("0\n"); /* no longer used */
+       cprintf("0\n"); /* no longer used */
+       cprintf("0\n"); /* no longer used */
+       cprintf("0\n"); /* no longer used */
+
+       cprintf("%d\n", CtdlGetConfigInt("c_enable_fulltext"));
+       cprintf("%s\n", BUILD_ID);
+
+       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_NATIVE) {
+               cprintf("%d\n", openid_level_supported); /* OpenID is enabled when using native auth */
+       }
+       else {
+               cprintf("0\n"); /* OpenID is disabled when using non-native auth */
+       }
+
+       cprintf("%d\n", CtdlGetConfigInt("c_guest_logins"));
+       cprintf("000\n");
+}
+
+
+/*
+ * echo 
+ */
+void cmd_echo(char *etext)
+{
+       cprintf("%d %s\n", CIT_OK, etext);
+}
+
+
+/* 
+ * get the paginator prompt
+ */
+void cmd_more(char *argbuf) {
+       cprintf("%d %s\n", CIT_OK, CtdlGetConfigStr("c_moreprompt"));
+}
+
+
+/*
+ * the client is identifying itself to the server
+ */
+void cmd_iden(char *argbuf)
+{
+       int dev_code;
+       int cli_code;
+       int rev_level;
+       char desc[128];
+       char from_host[128];
+
+       if (num_parms(argbuf)<4) {
+               cprintf("%d usage error\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       dev_code = extract_int(argbuf,0);
+       cli_code = extract_int(argbuf,1);
+       rev_level = extract_int(argbuf,2);
+       extract_token(desc, argbuf, 3, '|', sizeof desc);
+
+       safestrncpy(from_host, CtdlGetConfigStr("c_fqdn"), sizeof from_host);
+       from_host[sizeof from_host - 1] = 0;
+       if (num_parms(argbuf)>=5) extract_token(from_host, argbuf, 4, '|', sizeof from_host);
+
+       CC->cs_clientdev = dev_code;
+       CC->cs_clienttyp = cli_code;
+       CC->cs_clientver = rev_level;
+       safestrncpy(CC->cs_clientname, desc, sizeof CC->cs_clientname);
+       CC->cs_clientname[31] = 0;
+
+       /* For local sockets, allow the client to supply the user's origin address */
+       if ((CC->is_local_client) || (!IsEmptyStr(CC->cs_addr) && (!strcmp(CC->cs_addr, "127.0.0.1")) || (!strcmp(CC->cs_addr, "::1")))) {
+               safestrncpy(CC->cs_host, from_host, sizeof CC->cs_host);
+               CC->cs_host[sizeof CC->cs_host - 1] = 0;
+               CC->cs_addr[0] = 0;
+       }
+
+       syslog(LOG_NOTICE, "session: client %d/%d/%01d.%02d (%s) from %s",
+               dev_code,
+               cli_code,
+               (rev_level / 100),
+               (rev_level % 100),
+               desc,
+               CC->cs_host
+       );
+       cprintf("%d Ok\n",CIT_OK);
+}
+
+
+/*
+ * Terminate another running session
+ */
+void cmd_term(char *cmdbuf)
+{
+       int session_num = extract_int(cmdbuf, 0);
+       int terminated = CtdlTerminateOtherSession(session_num);
+
+       if (terminated < 0) {
+               cprintf("%d You can't kill your own session.\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       if (terminated & TERM_FOUND) {
+               if (terminated == TERM_KILLED) {
+                       cprintf("%d Session terminated.\n", CIT_OK);
+               }
+               else {
+                       cprintf("%d You are not allowed to do that.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+               }
+       }
+       else {
+               cprintf("%d No such session.\n", ERROR + ILLEGAL_VALUE);
+       }
+}
+
+
+void cmd_time(char *argbuf)
+{
+       time_t tv;
+       struct tm tmp;
+
+       tv = time(NULL);
+       localtime_r(&tv, &tmp);
+
+       /* timezone and daylight global variables are not portable. */
+#ifdef HAVE_STRUCT_TM_TM_GMTOFF
+       cprintf("%d %ld|%ld|%d|%ld\n", CIT_OK, (long)tv, tmp.tm_gmtoff, tmp.tm_isdst, server_startup_time);
+#else
+       cprintf("%d %ld|%ld|%d|%ld\n", CIT_OK, (long)tv, timezone, tmp.tm_isdst, server_startup_time);
+#endif
+}
+
+
+/*****************************************************************************/
+/*                      MODULE INITIALIZATION STUFF                          */
+/*****************************************************************************/
+
+char *ctdl_module_init_serv_session(void) {
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_noop, "NOOP", "no operation");
+               CtdlRegisterProtoHook(cmd_qnop, "QNOP", "no operation with no response");
+               CtdlRegisterProtoHook(cmd_asyn, "ASYN", "enable asynchronous server responses");
+               CtdlRegisterProtoHook(cmd_info, "INFO", "fetch server capabilities and configuration");
+               CtdlRegisterProtoHook(cmd_echo, "ECHO", "echo text back to the client");
+               CtdlRegisterProtoHook(cmd_more, "MORE", "fetch the paginator prompt");
+               CtdlRegisterProtoHook(cmd_iden, "IDEN", "identify the client software and location");
+               CtdlRegisterProtoHook(cmd_term, "TERM", "terminate another running session");
+               CtdlRegisterProtoHook(cmd_time, "TIME", "fetch the date and time from the server");
+       }
+        /* return our id for the Log */
+       return "serv_session";
+}
diff --git a/citadel/server/modules/ctdlproto/serv_syscmds.c b/citadel/server/modules/ctdlproto/serv_syscmds.c
new file mode 100644 (file)
index 0000000..a397149
--- /dev/null
@@ -0,0 +1,86 @@
+// System management commands for Citadel server
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include <stdio.h>
+#include <libcitadel.h>
+
+#include "../../serv_extensions.h"
+#include "../../ctdl_module.h"
+
+
+// Shut down or restart the server
+void cmd_down(char *argbuf) {
+       char *Reply ="%d Shutting down server.  Goodbye.\n";
+
+       if (CtdlAccessCheck(ac_aide)) return;
+
+       if (!IsEmptyStr(argbuf)) {
+               int state = CIT_OK;
+               restart_server = extract_int(argbuf, 0);
+               
+               if (restart_server > 0) {
+                       Reply = "%d citserver will now shut down and automatically restart.\n";
+               }
+               cprintf(Reply, state);
+       }
+       else {
+               cprintf(Reply, CIT_OK + SERVER_SHUTTING_DOWN); 
+       }
+       CC->kill_me = KILLME_SERVER_SHUTTING_DOWN;
+       server_shutting_down = 1;
+}
+
+
+// Halt the server without exiting the server process.
+void cmd_halt(char *argbuf) {
+       if (CtdlAccessCheck(ac_aide)) return;
+
+       cprintf("%d Halting server.  Goodbye.\n", CIT_OK);
+       server_shutting_down = 1;
+       shutdown_and_halt = 1;
+}
+
+
+// Schedule or cancel a server shutdown
+void cmd_scdn(char *argbuf) {
+       int new_state;
+       int state = CIT_OK;
+       char *Reply = "%d %d\n";
+
+       if (CtdlAccessCheck(ac_aide)) return;
+
+       new_state = extract_int(argbuf, 0);
+       if ((new_state == 2) || (new_state == 3)) {
+               restart_server = 1;
+               restart_server = extract_int(argbuf, 0);
+               new_state -= 2;
+       }
+       if ((new_state == 0) || (new_state == 1)) {
+               ScheduledShutdown = new_state;
+       }
+       cprintf(Reply, state, ScheduledShutdown);
+}
+
+
+// ****************************************************************************
+// *                     MODULE INITIALIZATION STUFF                          *
+// ****************************************************************************
+
+char *ctdl_module_init_syscmd(void) {
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_down, "DOWN", "perform a server shutdown");
+               CtdlRegisterProtoHook(cmd_halt, "HALT", "halt the server without exiting the server process");
+               CtdlRegisterProtoHook(cmd_scdn, "SCDN", "schedule or cancel a server shutdown");
+       }
+        // return our id for the log
+       return "syscmd";
+}
diff --git a/citadel/server/modules/ctdlproto/serv_user.c b/citadel/server/modules/ctdlproto/serv_user.c
new file mode 100644 (file)
index 0000000..d887317
--- /dev/null
@@ -0,0 +1,814 @@
+// Server functions which perform operations on user objects.
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+
+#include "../../support.h"
+#include "../../control.h"
+#include "../../ctdl_module.h"
+#include "../../citserver.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../internet_addressing.h"
+
+
+// USER command -- attempt to log in as an existing user
+void cmd_user(char *cmdbuf) {
+       char username[256];
+       int a;
+
+       extract_token(username, cmdbuf, 0, '|', sizeof username);
+       striplt(username);
+       syslog(LOG_DEBUG, "user_ops: cmd_user(%s)", username);
+
+       a = CtdlLoginExistingUser(username);
+       switch (a) {
+       case login_already_logged_in:
+               cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN);
+               return;
+       case login_too_many_users:
+               cprintf("%d %s: Too many users are already online (maximum is %d)\n",
+                       ERROR + MAX_SESSIONS_EXCEEDED,
+                       CtdlGetConfigStr("c_nodename"), CtdlGetConfigInt("c_maxsessions")
+               );
+               return;
+       case login_ok:
+               cprintf("%d Password required for %s\n", MORE_DATA, CC->curr_user);
+               return;
+       case login_not_found:
+               cprintf("%d %s not found.\n", ERROR + NO_SUCH_USER, username);
+               return;
+       default:
+               cprintf("%d Internal error\n", ERROR + INTERNAL_ERROR);
+       }
+}
+
+
+// PASS command -- complete logging in as an existing user (used after USER returns MORE_DATA)
+void cmd_pass(char *buf) {
+       char password[SIZ];
+       int a;
+       long len;
+
+       memset(password, 0, sizeof(password));
+       len = extract_token(password, buf, 0, '|', sizeof password);
+       a = CtdlTryPassword(password, len);
+
+       switch (a) {
+       case pass_already_logged_in:
+               cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN);
+               return;
+       case pass_no_user:
+               cprintf("%d You must send a name with USER first.\n", ERROR + USERNAME_REQUIRED);
+               return;
+       case pass_wrong_password:
+               cprintf("%d Wrong password.\n", ERROR + PASSWORD_REQUIRED);
+               return;
+       case pass_ok:
+               logged_in_response();
+               return;
+       }
+}
+
+
+// cmd_newu()  -  create a new user account and log in as that user
+void cmd_newu(char *cmdbuf) {
+       int a;
+       char username[SIZ];
+
+       if (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) {
+               cprintf("%d This system does not use native mode authentication.\n",
+                       ERROR + NOT_HERE);
+               return;
+       }
+
+       if (CtdlGetConfigInt("c_disable_newu")) {
+               cprintf("%d Self-service user account creation is disabled on this system.\n", ERROR + NOT_HERE);
+               return;
+       }
+
+       if (CC->logged_in) {
+               cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN);
+               return;
+       }
+       if (CC->nologin) {
+               cprintf("%d %s: Too many users are already online (maximum is %d)\n",
+                       ERROR + MAX_SESSIONS_EXCEEDED,
+                       CtdlGetConfigStr("c_nodename"), CtdlGetConfigInt("c_maxsessions"));
+               return;
+       }
+       extract_token(username, cmdbuf, 0, '|', sizeof username);
+       strproc(username);
+
+       if (IsEmptyStr(username)) {
+               cprintf("%d You must supply a user name.\n", ERROR + USERNAME_REQUIRED);
+               return;
+       }
+
+       if ((!strcasecmp(username, "bbs")) ||
+           (!strcasecmp(username, "new")) ||
+           (!strcasecmp(username, "."))
+       ) {
+               cprintf("%d '%s' is an invalid login name.\n", ERROR + ILLEGAL_VALUE, username);
+               return;
+       }
+
+       a = create_user(username, CREATE_USER_BECOME_USER, NATIVE_AUTH_UID);
+
+       if (a == 0) {
+               logged_in_response();
+       }
+       else if (a == ERROR + ALREADY_EXISTS) {
+               cprintf("%d '%s' already exists.\n",
+                       ERROR + ALREADY_EXISTS, username);
+               return;
+       }
+       else if (a == ERROR + INTERNAL_ERROR) {
+               cprintf("%d Internal error - user record disappeared?\n",
+                       ERROR + INTERNAL_ERROR);
+               return;
+       }
+       else {
+               cprintf("%d unknown error\n", ERROR + INTERNAL_ERROR);
+       }
+}
+
+
+// set password - citadel protocol implementation
+void cmd_setp(char *new_pw) {
+       if (CtdlAccessCheck(ac_logged_in)) {
+               return;
+       }
+       if ( (CC->user.uid != CTDLUID) && (CC->user.uid != (-1)) ) {
+               cprintf("%d Not allowed.  Use the 'passwd' command.\n", ERROR + NOT_HERE);
+               return;
+       }
+
+       if (!strcasecmp(new_pw, "GENERATE_RANDOM_PASSWORD")) {
+               char random_password[17];
+               snprintf(random_password, sizeof random_password, "%08lx%08lx", random(), random());
+               CtdlSetPassword(random_password);
+               cprintf("%d %s\n", CIT_OK, random_password);
+       }
+       else {
+               strproc(new_pw);
+               if (IsEmptyStr(new_pw)) {
+                       cprintf("%d Password unchanged.\n", CIT_OK);
+                       return;
+               }
+               CtdlSetPassword(new_pw);
+               cprintf("%d Password changed.\n", CIT_OK);
+       }
+}
+
+
+// cmd_creu() - administratively create a new user account (do not log in to it)
+void cmd_creu(char *cmdbuf) {
+       int a;
+       char username[SIZ];
+       char password[SIZ];
+       struct ctdluser tmp;
+
+       if (CtdlAccessCheck(ac_aide)) {
+               return;
+       }
+
+       extract_token(username, cmdbuf, 0, '|', sizeof username);
+       strproc(username);
+       strproc(password);
+       if (IsEmptyStr(username)) {
+               cprintf("%d You must supply a user name.\n", ERROR + USERNAME_REQUIRED);
+               return;
+       }
+
+       extract_token(password, cmdbuf, 1, '|', sizeof password);
+
+       a = create_user(username, CREATE_USER_DO_NOT_BECOME_USER, NATIVE_AUTH_UID);
+
+       if (a == 0) {
+               if (!IsEmptyStr(password)) {
+                       CtdlGetUserLock(&tmp, username);
+                       safestrncpy(tmp.password, password, sizeof(tmp.password));
+                       CtdlPutUserLock(&tmp);
+               }
+               cprintf("%d User '%s' created %s.\n", CIT_OK, username, (!IsEmptyStr(password)) ? "and password set" : "with no password");
+               return;
+       }
+       else if (a == ERROR + ALREADY_EXISTS) {
+               cprintf("%d '%s' already exists.\n", ERROR + ALREADY_EXISTS, username);
+               return;
+       }
+       else if ( (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) && (a == ERROR + NO_SUCH_USER) ) {
+               cprintf("%d User accounts are not created within Citadel in host authentication mode.\n", ERROR + NO_SUCH_USER);
+               return;
+       }
+       else {
+               cprintf("%d An error occurred creating the user account.\n", ERROR + INTERNAL_ERROR);
+       }
+}
+
+
+// get user parameters
+void cmd_getu(char *cmdbuf) {
+       if (CtdlAccessCheck(ac_logged_in)) {
+               return;
+       }
+
+       CtdlGetUser(&CC->user, CC->curr_user);
+       cprintf("%d 80|24|%d|\n", CIT_OK, (CC->user.flags & US_USER_SET));
+}
+
+
+// set user parameters
+void cmd_setu(char *new_parms) {
+       if (CtdlAccessCheck(ac_logged_in)) {
+               return;
+       }
+
+       if (num_parms(new_parms) < 3) {
+               cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+       CtdlLockGetCurrentUser();
+       CC->user.flags = CC->user.flags & (~US_USER_SET);
+       CC->user.flags = CC->user.flags | (extract_int(new_parms, 2) & US_USER_SET);
+       CtdlPutCurrentUserLock();
+       cprintf("%d Ok\n", CIT_OK);
+}
+
+
+// set last read pointer (marks all messages in the current room as read, up to the specified point)
+void cmd_slrp(char *new_ptr) {
+       long newlr;
+       visit vbuf;
+       visit original_vbuf;
+
+       if (CtdlAccessCheck(ac_logged_in)) {
+               return;
+       }
+
+       if (!strncasecmp(new_ptr, "highest", 7)) {
+               newlr = CC->room.QRhighest;
+       }
+       else {
+               newlr = atol(new_ptr);
+       }
+
+       CtdlLockGetCurrentUser();
+
+       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+       memcpy(&original_vbuf, &vbuf, sizeof(visit));
+       vbuf.v_lastseen = newlr;
+       snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld", newlr);
+
+       // Only rewrite the record if it changed
+       if (    (vbuf.v_lastseen != original_vbuf.v_lastseen)
+               || (strcmp(vbuf.v_seen, original_vbuf.v_seen))
+       ) {
+               CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
+       }
+
+       CtdlPutCurrentUserLock();
+       cprintf("%d %ld\n", CIT_OK, newlr);
+}
+
+
+void cmd_seen(char *argbuf) {
+       long target_msgnum = 0L;
+       int target_setting = 0;
+
+       if (CtdlAccessCheck(ac_logged_in)) {
+               return;
+       }
+
+       if (num_parms(argbuf) != 2) {
+               cprintf("%d Invalid parameters\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       target_msgnum = extract_long(argbuf, 0);
+       target_setting = extract_int(argbuf, 1);
+
+       CtdlSetSeen(&target_msgnum, 1, target_setting,
+                       ctdlsetseen_seen, NULL, NULL);
+       cprintf("%d OK\n", CIT_OK);
+}
+
+
+void cmd_gtsn(char *argbuf) {
+       visit vbuf;
+
+       if (CtdlAccessCheck(ac_logged_in)) {
+               return;
+       }
+
+       // Learn about the user and room in question
+       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+
+       cprintf("%d ", CIT_OK);
+       client_write(vbuf.v_seen, strlen(vbuf.v_seen));
+       client_write(HKEY("\n"));
+}
+
+
+// INVT and KICK commands (grant/revoke access to an invitation-only room)
+void cmd_invt_kick(char *iuser, int op) {
+
+       // These commands are only allowed by admins, room admins,
+       // and room namespace owners
+       if (is_room_aide()) {
+               // access granted
+       }
+       else if ( ((atol(CC->room.QRname) == CC->user.usernum) ) && (CC->user.usernum != 0) ) {
+               // access granted
+       }
+       else {
+               // access denied
+               cprintf("%d Higher access or room ownership required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+               return;
+       }
+
+       if (!strncasecmp(CC->room.QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
+               cprintf("%d Can't add/remove users from this room.\n", ERROR + NOT_HERE);
+               return;
+       }
+
+       if (CtdlInvtKick(iuser, op) != 0) {
+               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+               return;
+       }
+
+       cprintf("%d %s %s %s.\n",
+               CIT_OK, iuser,
+               ((op == 1) ? "invited to" : "kicked out of"),
+               CC->room.QRname);
+       return;
+}
+
+
+void cmd_invt(char *iuser) {
+       cmd_invt_kick(iuser, 1);
+}
+
+
+void cmd_kick(char *iuser) {
+       cmd_invt_kick(iuser, 0);
+}
+
+
+// forget (Zap) the current room
+void cmd_forg(char *argbuf) {
+
+       if (CtdlAccessCheck(ac_logged_in)) {
+               return;
+       }
+
+       if (CtdlForgetThisRoom() == 0) {
+               cprintf("%d Ok\n", CIT_OK);
+       }
+       else {
+               cprintf("%d You may not forget this room.\n", ERROR + NOT_HERE);
+       }
+}
+
+
+// Get Next Unregistered User
+void cmd_gnur(char *argbuf) {
+       struct cdbdata *cdbus;
+       struct ctdluser usbuf;
+
+       if (CtdlAccessCheck(ac_aide)) {
+               return;
+       }
+
+       if ((CtdlGetConfigInt("MMflags") & MM_VALID) == 0) {
+               cprintf("%d There are no unvalidated users.\n", CIT_OK);
+               return;
+       }
+
+       // There are unvalidated users.  Traverse the user database, and return the first user we find that needs validation.
+       cdb_rewind(CDB_USERS);
+       while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
+               memset(&usbuf, 0, sizeof(struct ctdluser));
+               memcpy(&usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ?  sizeof(struct ctdluser) : cdbus->len));
+               cdb_free(cdbus);
+               if ((usbuf.flags & US_NEEDVALID) && (usbuf.axlevel > AxDeleted)) {
+                       cprintf("%d %s\n", MORE_DATA, usbuf.fullname);
+                       cdb_close_cursor(CDB_USERS);
+                       return;
+               }
+       }
+
+       // If we get to this point, there are no more unvalidated users.  Therefore we clear the "users need validation" flag.
+       begin_critical_section(S_CONTROL);
+       int flags;
+       flags = CtdlGetConfigInt("MMflags");
+       flags = flags & (~MM_VALID);
+       CtdlSetConfigInt("MMflags", flags);
+       end_critical_section(S_CONTROL);
+       cprintf("%d *** End of registration.\n", CIT_OK);
+}
+
+
+// validate a user
+void cmd_vali(char *v_args) {
+       char user[128];
+       int newax;
+       struct ctdluser userbuf;
+
+       extract_token(user, v_args, 0, '|', sizeof user);
+       newax = extract_int(v_args, 1);
+
+       if (CtdlAccessCheck(ac_aide) || (newax > AxAideU) || (newax < AxDeleted)) {
+               return;
+       }
+
+       if (CtdlGetUserLock(&userbuf, user) != 0) {
+               cprintf("%d '%s' not found.\n", ERROR + NO_SUCH_USER, user);
+               return;
+       }
+
+       userbuf.axlevel = newax;
+       userbuf.flags = (userbuf.flags & ~US_NEEDVALID);
+
+       CtdlPutUserLock(&userbuf);
+
+       // If the access level was set to zero, delete the user
+       if (newax == 0) {
+               if (purge_user(user) == 0) {
+                       cprintf("%d %s Deleted.\n", CIT_OK, userbuf.fullname);
+                       return;
+               }
+       }
+       cprintf("%d User '%s' validated.\n", CIT_OK, userbuf.fullname);
+}
+
+
+// List one user (this works with cmd_list)
+void ListThisUser(char *username, void *data) {
+       char *searchstring;
+       struct ctdluser usbuf;
+
+       if (CtdlGetUser(&usbuf, username) != 0) {
+               return;
+       }
+
+       searchstring = (char *)data;
+       if (bmstrcasestr(usbuf.fullname, searchstring) == NULL) {
+               return;
+       }
+
+       if (usbuf.axlevel > AxDeleted) {
+               if ((CC->user.axlevel >= AxAideU)
+                   || ((usbuf.flags & US_UNLISTED) == 0)
+                   || ((CC->internal_pgm))) {
+                       cprintf("%s|%d|%ld|%ld|%ld|%ld||\n",
+                               usbuf.fullname,
+                               usbuf.axlevel,
+                               usbuf.usernum,
+                               (long)usbuf.lastcall,
+                               usbuf.timescalled,
+                               usbuf.posted);
+               }
+       }
+}
+
+
+//  List users (searchstring may be empty to list all users)
+void cmd_list(char *cmdbuf) {
+       char searchstring[256];
+       extract_token(searchstring, cmdbuf, 0, '|', sizeof searchstring);
+       striplt(searchstring);
+       cprintf("%d \n", LISTING_FOLLOWS);
+       ForEachUser(ListThisUser, (void *)searchstring );
+       cprintf("000\n");
+}
+
+
+// assorted info we need to check at login
+void cmd_chek(char *argbuf) {
+       int mail = 0;
+       int regis = 0;
+       int vali = 0;
+
+       if (CtdlAccessCheck(ac_logged_in)) {
+               return;
+       }
+
+       CtdlGetUser(&CC->user, CC->curr_user);  // no lock is needed here 
+       if ((REGISCALL != 0) && ((CC->user.flags & US_REGIS) == 0)) {
+               regis = 1;
+       }
+
+       if (CC->user.axlevel >= AxAideU) {
+               if (CtdlGetConfigInt("MMflags") & MM_VALID) {
+                       vali = 1;
+               }
+       }
+
+       mail = InitialMailCheck();              // check for mail
+       cprintf("%d %d|%d|%d|%s|\n", CIT_OK, mail, regis, vali, CC->cs_inet_email);
+}
+
+
+// check to see if a user exists
+void cmd_qusr(char *who) {
+       struct ctdluser usbuf;
+
+       if (CtdlGetUser(&usbuf, who) == 0) {
+               cprintf("%d %s\n", CIT_OK, usbuf.fullname);
+       }
+       else {
+               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+       }
+}
+
+
+// Administrative Get User Parameters
+void cmd_agup(char *cmdbuf) {
+       struct ctdluser usbuf;
+       char requested_user[128];
+
+       if (CtdlAccessCheck(ac_aide)) {
+               return;
+       }
+
+       extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user);
+       if (CtdlGetUser(&usbuf, requested_user) != 0) {
+               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+               return;
+       }
+       cprintf("%d %s|%s|%u|%ld|%ld|%d|%ld|%ld|%d\n",
+               CIT_OK,
+               usbuf.fullname,
+               usbuf.password,
+               usbuf.flags,
+               usbuf.timescalled,
+               usbuf.posted,
+               (int) usbuf.axlevel,
+               usbuf.usernum,
+               (long)usbuf.lastcall,
+               usbuf.USuserpurge);
+}
+
+
+// Administrative Set User Parameters
+void cmd_asup(char *cmdbuf) {
+       struct ctdluser usbuf;
+       char requested_user[128];
+       char notify[SIZ];
+       int np;
+       int newax;
+       int deleted = 0;
+
+       if (CtdlAccessCheck(ac_aide))
+               return;
+
+       extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user);
+       if (CtdlGetUserLock(&usbuf, requested_user) != 0) {
+               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+               return;
+       }
+       np = num_parms(cmdbuf);
+       if (np > 1)
+               extract_token(usbuf.password, cmdbuf, 1, '|', sizeof usbuf.password);
+       if (np > 2)
+               usbuf.flags = extract_int(cmdbuf, 2);
+       if (np > 3)
+               usbuf.timescalled = extract_int(cmdbuf, 3);
+       if (np > 4)
+               usbuf.posted = extract_int(cmdbuf, 4);
+       if (np > 5) {
+               newax = extract_int(cmdbuf, 5);
+               if ((newax >= AxDeleted) && (newax <= AxAideU)) {
+                       usbuf.axlevel = newax;
+               }
+       }
+       if (np > 7) {
+               usbuf.lastcall = extract_long(cmdbuf, 7);
+       }
+       if (np > 8) {
+               usbuf.USuserpurge = extract_int(cmdbuf, 8);
+       }
+       CtdlPutUserLock(&usbuf);
+       if (usbuf.axlevel == AxDeleted) {
+               if (purge_user(requested_user) == 0) {
+                       deleted = 1;
+               }
+       }
+
+       if (deleted) {
+               snprintf(notify, SIZ, 
+                       "User \"%s\" has been deleted by %s.\n",
+                       usbuf.fullname, (CC->logged_in ? CC->user.fullname : "an administrator")
+               );
+               CtdlAideMessage(notify, "User Deletion Message");
+       }
+
+       cprintf("%d Ok", CIT_OK);
+       if (deleted) {
+               cprintf(" (%s deleted)", requested_user);
+       }
+       cprintf("\n");
+}
+
+
+// Citadel protocol command to do the same
+void cmd_isme(char *argbuf) {
+       char addr[256];
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+       extract_token(addr, argbuf, 0, '|', sizeof addr);
+
+       if (CtdlIsMe(addr, sizeof addr)) {
+               cprintf("%d %s\n", CIT_OK, addr);
+       }
+       else {
+               cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
+       }
+
+}
+
+
+// Retrieve all Internet email addresses/aliases for the specified user
+void cmd_agea(char *cmdbuf) {
+       struct ctdluser usbuf;
+       char requested_user[128];
+       int i, num_e;
+       char e[512];
+
+       if (CtdlAccessCheck(ac_aide)) {
+               return;
+       }
+
+       extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user);
+       if (CtdlGetUser(&usbuf, requested_user) != 0) {
+               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+               return;
+       }
+       cprintf("%d internet email addresses for %s\n", LISTING_FOLLOWS, usbuf.fullname);
+       num_e = num_tokens(usbuf.emailaddrs, '|');
+       for (i=0; i<num_e; ++i) {
+               extract_token(e, usbuf.emailaddrs, i, '|', sizeof e);
+               cprintf("%s\n", e);
+       }
+       cprintf("000\n");
+}
+
+
+// Set the Internet email addresses/aliases for the specified user
+void cmd_asea(char *cmdbuf) {
+       struct ctdluser usbuf;
+       char requested_user[128];
+       char buf[SIZ];
+       char whodat[64];
+       char new_emailaddrs[512] = { 0 } ;
+
+       if (CtdlAccessCheck(ac_aide)) return;
+
+       extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user);
+       if (CtdlGetUser(&usbuf, requested_user) != 0) {
+               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+               return;
+       }
+
+       cprintf("%d Ok\n", SEND_LISTING);
+       while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
+               if (IsEmptyStr(buf)) {
+                       syslog(LOG_ERR, "user_ops: address <%s> is empty - not using", buf);
+               }
+               else if ((strlen(new_emailaddrs) + strlen(buf) + 2) > sizeof(new_emailaddrs)) {
+                       syslog(LOG_ERR, "user_ops: address <%s> does not fit in buffer - not using", buf);
+               }
+               else if (!IsDirectory(buf, 0)) {
+                       syslog(LOG_ERR, "user_ops: address <%s> is not in one of our domains - not using", buf);
+               }
+               else if ( (CtdlDirectoryLookup(whodat, buf, sizeof whodat) == 0) && (CtdlUserCmp(whodat, requested_user)) ) {
+                       syslog(LOG_ERR, "user_ops: address <%s> already belongs to <%s> - not using", buf, whodat);
+               }
+               else {
+                       syslog(LOG_DEBUG, "user_ops: address <%s> validated", buf);
+                       if (!IsEmptyStr(new_emailaddrs)) {
+                               strcat(new_emailaddrs, "|");
+                       }
+                       strcat(new_emailaddrs, buf);
+               }
+       }
+
+       CtdlSetEmailAddressesForUser(requested_user, new_emailaddrs);
+}
+
+
+// Set the preferred view for the current user/room combination
+void cmd_view(char *cmdbuf) {
+       int requested_view;
+       visit vbuf;
+
+       if (CtdlAccessCheck(ac_logged_in)) {
+               return;
+       }
+
+       requested_view = extract_int(cmdbuf, 0);
+
+       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+       vbuf.v_view = requested_view;
+       CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
+       
+       cprintf("%d ok\n", CIT_OK);
+}
+
+
+// Rename a user
+void cmd_renu(char *cmdbuf) {
+       int retcode;
+       char oldname[USERNAME_SIZE];
+       char newname[USERNAME_SIZE];
+
+       if (CtdlAccessCheck(ac_aide)) {
+               return;
+       }
+
+       extract_token(oldname, cmdbuf, 0, '|', sizeof oldname);
+       extract_token(newname, cmdbuf, 1, '|', sizeof newname);
+
+       retcode = rename_user(oldname, newname);
+       switch(retcode) {
+               case RENAMEUSER_OK:
+                       cprintf("%d '%s' has been renamed to '%s'.\n", CIT_OK, oldname, newname);
+                       return;
+               case RENAMEUSER_LOGGED_IN:
+                       cprintf("%d '%s' is currently logged in and cannot be renamed.\n",
+                               ERROR + ALREADY_LOGGED_IN , oldname
+                       );
+                       return;
+               case RENAMEUSER_NOT_FOUND:
+                       cprintf("%d '%s' does not exist.\n", ERROR + NO_SUCH_USER, oldname);
+                       return;
+               case RENAMEUSER_ALREADY_EXISTS:
+                       cprintf("%d A user named '%s' already exists.\n", ERROR + ALREADY_EXISTS, newname);
+                       return;
+       }
+
+       cprintf("%d An unknown error occurred.\n", ERROR);
+}
+
+
+void cmd_quit(char *argbuf) {
+       cprintf("%d Goodbye.\n", CIT_OK);
+       CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
+}
+
+
+void cmd_lout(char *argbuf) {
+       if (CC->logged_in) 
+               CtdlUserLogout();
+       cprintf("%d logged out.\n", CIT_OK);
+}
+
+
+/*****************************************************************************/
+/*                      MODULE INITIALIZATION STUFF                          */
+/*****************************************************************************/
+
+
+char *ctdl_module_init_serv_user(void) {
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_user, "USER", "Submit username for login");
+               CtdlRegisterProtoHook(cmd_pass, "PASS", "Complete login by submitting a password");
+               CtdlRegisterProtoHook(cmd_quit, "QUIT", "log out and disconnect from server");
+               CtdlRegisterProtoHook(cmd_lout, "LOUT", "log out but do not disconnect from server");
+               CtdlRegisterProtoHook(cmd_creu, "CREU", "Create User");
+               CtdlRegisterProtoHook(cmd_setp, "SETP", "Set the password for an account");
+               CtdlRegisterProtoHook(cmd_getu, "GETU", "Get User parameters");
+               CtdlRegisterProtoHook(cmd_setu, "SETU", "Set User parameters");
+               CtdlRegisterProtoHook(cmd_slrp, "SLRP", "Set Last Read Pointer");
+               CtdlRegisterProtoHook(cmd_invt, "INVT", "Invite a user to a room");
+               CtdlRegisterProtoHook(cmd_kick, "KICK", "Kick a user out of a room");
+               CtdlRegisterProtoHook(cmd_forg, "FORG", "Forget a room");
+               CtdlRegisterProtoHook(cmd_gnur, "GNUR", "Get Next Unregistered User");
+               CtdlRegisterProtoHook(cmd_vali, "VALI", "Validate new users");
+               CtdlRegisterProtoHook(cmd_list, "LIST", "List users");
+               CtdlRegisterProtoHook(cmd_chek, "CHEK", "assorted info we need to check at login");
+               CtdlRegisterProtoHook(cmd_qusr, "QUSR", "check to see if a user exists");
+               CtdlRegisterProtoHook(cmd_agup, "AGUP", "Administratively Get User Parameters");
+               CtdlRegisterProtoHook(cmd_asup, "ASUP", "Administratively Set User Parameters");
+               CtdlRegisterProtoHook(cmd_agea, "AGEA", "Administratively Get Email Addresses");
+               CtdlRegisterProtoHook(cmd_asea, "ASEA", "Administratively Set Email Addresses");
+               CtdlRegisterProtoHook(cmd_seen, "SEEN", "Manipulate seen/unread message flags");
+               CtdlRegisterProtoHook(cmd_gtsn, "GTSN", "Fetch seen/unread message flags");
+               CtdlRegisterProtoHook(cmd_view, "VIEW", "Set preferred view for user/room combination");
+               CtdlRegisterProtoHook(cmd_renu, "RENU", "Rename a user");
+               CtdlRegisterProtoHook(cmd_newu, "NEWU", "Log in as a new user");
+               CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
+       }
+       /* return our Subversion id for the Log */
+       return "user";
+}
diff --git a/citadel/server/modules/expire/.gitignore b/citadel/server/modules/expire/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/expire/expire_policy.c b/citadel/server/modules/expire/expire_policy.c
new file mode 100644 (file)
index 0000000..6aaeeff
--- /dev/null
@@ -0,0 +1,181 @@
+/* 
+ * Functions which manage expire policy for rooms
+ * Copyright (c) 1987-2015 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <string.h>
+
+#include <time.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../database.h"
+#include "../../config.h"
+#include "../../room_ops.h"
+#include "../../sysdep_decls.h"
+#include "../../support.h"
+#include "../../msgbase.h"
+#include "../../citserver.h"
+#include "../../ctdl_module.h"
+#include "../../user_ops.h"
+
+/*
+ * Retrieve the applicable expire policy for a specific room
+ */
+void GetExpirePolicy(struct ExpirePolicy *epbuf, struct ctdlroom *qrbuf) {
+       struct floor *fl;
+
+       /* If the room has its own policy, return it */ 
+       if (qrbuf->QRep.expire_mode != 0) {
+               memcpy(epbuf, &qrbuf->QRep, sizeof(struct ExpirePolicy));
+               return;
+       }
+
+       /* (non-mailbox rooms)
+        * If the floor has its own policy, return it
+        */
+       if ( (qrbuf->QRflags & QR_MAILBOX) == 0) {
+               fl = CtdlGetCachedFloor(qrbuf->QRfloor);
+               if (fl->f_ep.expire_mode != 0) {
+                       memcpy(epbuf, &fl->f_ep, sizeof(struct ExpirePolicy));
+                       return;
+               }
+       }
+
+       /* (Mailbox rooms)
+        * If there is a default policy for mailbox rooms, return it
+        */
+       if (qrbuf->QRflags & QR_MAILBOX) {
+               if (CtdlGetConfigInt("c_mbxep_mode") != 0) {
+                       epbuf->expire_mode = CtdlGetConfigInt("c_mbxep_mode");
+                       epbuf->expire_value = CtdlGetConfigInt("c_mbxep_value");
+                       return;
+               }
+       }
+
+       /* Otherwise, fall back on the system default */
+       epbuf->expire_mode = CtdlGetConfigInt("c_ep_mode");
+       epbuf->expire_value = CtdlGetConfigInt("c_ep_value");
+}
+
+
+/*
+ * Get Policy EXpire
+ */
+void cmd_gpex(char *argbuf) {
+       struct ExpirePolicy exp;
+       struct floor *fl;
+       char which[128];
+
+       memset(&exp, 0, sizeof(struct ExpirePolicy));
+       extract_token(which, argbuf, 0, '|', sizeof which);
+       if (!strcasecmp(which, strof(roompolicy)) || !strcasecmp(which, "room")) {
+               memcpy(&exp, &CC->room.QRep, sizeof(struct ExpirePolicy));
+       }
+       else if (!strcasecmp(which, strof(floorpolicy)) || !strcasecmp(which, "floor")) {
+               fl = CtdlGetCachedFloor(CC->room.QRfloor);
+               memcpy(&exp, &fl->f_ep, sizeof(struct ExpirePolicy));
+       }
+       else if (!strcasecmp(which, strof(mailboxespolicy)) || !strcasecmp(which, "mailboxes")) {
+               exp.expire_mode = CtdlGetConfigInt("c_mbxep_mode");
+               exp.expire_value = CtdlGetConfigInt("c_mbxep_value");
+       }
+       else if (!strcasecmp(which, strof(sitepolicy)) || !strcasecmp(which, "site")) {
+               exp.expire_mode = CtdlGetConfigInt("c_ep_mode");
+               exp.expire_value = CtdlGetConfigInt("c_ep_value");
+       }
+       else {
+               cprintf("%d Invalid keyword \"%s\"\n", ERROR + ILLEGAL_VALUE, which);
+               return;
+       }
+
+       cprintf("%d %d|%d\n", CIT_OK, exp.expire_mode, exp.expire_value);
+}
+
+
+/*
+ * Set Policy EXpire
+ */
+void cmd_spex(char *argbuf) {
+       struct ExpirePolicy exp;
+       struct floor flbuf;
+       char which[128];
+
+       memset(&exp, 0, sizeof(struct ExpirePolicy));
+       extract_token(which, argbuf, 0, '|', sizeof which);
+       exp.expire_mode = extract_int(argbuf, 1);
+       exp.expire_value = extract_int(argbuf, 2);
+
+       if ((exp.expire_mode < 0) || (exp.expire_mode > 3)) {
+               cprintf("%d Invalid policy.\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       if ((!strcasecmp(which, strof(roompolicy))) || (!strcasecmp(which, "room")))
+       {
+               if (!is_room_aide()) {
+                       cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+                       return;
+               }
+               CtdlGetRoomLock(&CC->room, CC->room.QRname);
+               memcpy(&CC->room.QRep, &exp, sizeof(struct ExpirePolicy));
+               CtdlPutRoomLock(&CC->room);
+               cprintf("%d Room expire policy for '%s' has been updated.\n", CIT_OK, CC->room.QRname);
+               syslog(LOG_DEBUG, "Room: %s , Policy: %d , Value: %d",
+                       CC->room.QRname,
+                       exp.expire_mode,
+                       exp.expire_value
+               );
+               return;
+       }
+
+       if (CC->user.axlevel < AxAideU) {
+               cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+               return;
+       }
+
+       if ((!strcasecmp(which, strof(floorpolicy))) || (!strcasecmp(which, "floor")))
+       {
+               lgetfloor(&flbuf, CC->room.QRfloor);
+               memcpy(&flbuf.f_ep, &exp, sizeof(struct ExpirePolicy));
+               lputfloor(&flbuf, CC->room.QRfloor);
+               cprintf("%d Floor expire policy has been updated.\n", CIT_OK);
+               return;
+       }
+
+       else if ((!strcasecmp(which, strof(mailboxespolicy))) || (!strcasecmp(which, "mailboxes")))
+       {
+               CtdlSetConfigInt("c_mbxep_mode", exp.expire_mode);
+               CtdlSetConfigInt("c_mbxep_value", exp.expire_value);
+               cprintf("%d Default expire policy for mailboxes set.\n", CIT_OK);
+               return;
+       }
+
+       else if ((!strcasecmp(which, strof(sitepolicy))) || (!strcasecmp(which, "site")))
+       {
+               if (exp.expire_mode == EXPIRE_NEXTLEVEL) {
+                       cprintf("%d Invalid policy (no higher level)\n", ERROR + ILLEGAL_VALUE);
+                       return;
+               }
+               CtdlSetConfigInt("c_ep_mode", exp.expire_mode);
+               CtdlSetConfigInt("c_ep_value", exp.expire_value);
+               cprintf("%d Site expire policy has been updated.\n", CIT_OK);
+               return;
+       }
+
+       cprintf("%d Invalid keyword '%s'\n", ERROR + ILLEGAL_VALUE, which);
+}
diff --git a/citadel/server/modules/expire/policy.h b/citadel/server/modules/expire/policy.h
new file mode 100644 (file)
index 0000000..e07fe8b
--- /dev/null
@@ -0,0 +1,3 @@
+void GetExpirePolicy(struct ExpirePolicy *epbuf, struct ctdlroom *qrbuf);
+void cmd_gpex(char *argbuf);
+void cmd_spex(char *argbuf);
diff --git a/citadel/server/modules/expire/serv_expire.c b/citadel/server/modules/expire/serv_expire.c
new file mode 100644 (file)
index 0000000..f204751
--- /dev/null
@@ -0,0 +1,836 @@
+// This module handles the expiry of old messages and the purging of old users.
+//
+// You might also see this module affectionately referred to as TDAP (The Dreaded Auto-Purger).
+//
+// Copyright (c) 1988-2022 by citadel.org (Art Cancro, Wilifried Goesgens, and others)
+//
+// This program is open source software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as published
+// by the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "policy.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../user_ops.h"
+#include "../../control.h"
+#include "../../threads.h"
+#include "../../context.h"
+
+#include "../../ctdl_module.h"
+
+
+struct PurgeList {
+       struct PurgeList *next;
+       char name[ROOMNAMELEN];         // use the larger of username or roomname
+};
+
+struct VPurgeList {
+       struct VPurgeList *next;
+       long vp_roomnum;
+       long vp_roomgen;
+       long vp_usernum;
+};
+
+struct ValidRoom {
+       struct ValidRoom *next;
+       long vr_roomnum;
+       long vr_roomgen;
+};
+
+struct ValidUser {
+       struct ValidUser *next;
+       long vu_usernum;
+};
+
+struct ctdlroomref {
+       struct ctdlroomref *next;
+       long msgnum;
+};
+
+struct UPurgeList {
+       struct UPurgeList *next;
+       char up_key[256];
+};
+
+struct EPurgeList {
+       struct EPurgeList *next;
+       int ep_keylen;
+       char *ep_key;
+};
+
+
+struct PurgeList *UserPurgeList = NULL;
+struct PurgeList *RoomPurgeList = NULL;
+struct ValidRoom *ValidRoomList = NULL;
+struct ValidUser *ValidUserList = NULL;
+int messages_purged;
+int users_not_purged;
+char *users_corrupt_msg = NULL;
+char *users_zero_msg = NULL;
+struct ctdlroomref *rr = NULL;
+int force_purge_now = 0;                       // set to nonzero to force a run right now
+
+
+// First phase of message purge -- gather the locations of messages which
+// qualify for purging and write them to a temp file.
+void GatherPurgeMessages(struct ctdlroom *qrbuf, void *data) {
+       struct ExpirePolicy epbuf;
+       long delnum;
+       time_t xtime, now;
+       struct CtdlMessage *msg = NULL;
+       int a;
+       struct cdbdata *cdbfr;
+       long *msglist = NULL;
+       int num_msgs = 0;
+       FILE *purgelist;
+
+       purgelist = (FILE *)data;
+       fprintf(purgelist, "r=%s\n", qrbuf->QRname);
+
+       time(&now);
+       GetExpirePolicy(&epbuf, qrbuf);
+
+       // If the room is set to never expire messages ... do nothing
+       if (epbuf.expire_mode == EXPIRE_NEXTLEVEL) return;
+       if (epbuf.expire_mode == EXPIRE_MANUAL) return;
+
+       // Don't purge messages containing system configuration, dumbass.
+       if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
+
+       // Ok, we got this far ... now let's see what's in the room.
+       cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
+
+       if (cdbfr != NULL) {
+               msglist = malloc(cdbfr->len);
+               memcpy(msglist, cdbfr->ptr, cdbfr->len);
+               num_msgs = cdbfr->len / sizeof(long);
+               cdb_free(cdbfr);
+       }
+
+       // Nothing to do if there aren't any messages
+       if (num_msgs == 0) {
+               if (msglist != NULL) free(msglist);
+               return;
+       }
+
+       // If the room is set to expire by count, do that.
+       if (epbuf.expire_mode == EXPIRE_NUMMSGS) {
+               if (num_msgs > epbuf.expire_value) {
+                       for (a=0; a<(num_msgs - epbuf.expire_value); ++a) {
+                               fprintf(purgelist, "m=%ld\n", msglist[a]);
+                               ++messages_purged;
+                       }
+               }
+       }
+
+       // If the room is set to expire by age...
+       if (epbuf.expire_mode == EXPIRE_AGE) {
+               for (a=0; a<num_msgs; ++a) {
+                       delnum = msglist[a];
+
+                       msg = CtdlFetchMessage(delnum, 0);      // don't need the body
+                       if (msg != NULL) {
+                               xtime = atol(msg->cm_fields[eTimestamp]);
+                               CM_Free(msg);
+                       }
+                       else {
+                               xtime = 0L;
+                       }
+
+                       if ((xtime > 0L) && (now - xtime > (time_t)(epbuf.expire_value * 86400L))) {
+                               fprintf(purgelist, "m=%ld\n", delnum);
+                               ++messages_purged;
+                       }
+               }
+       }
+
+       if (msglist != NULL) free(msglist);
+}
+
+
+// Second phase of message purge -- read list of msgs from temp file and delete them.
+void DoPurgeMessages(FILE *purgelist) {
+       char roomname[ROOMNAMELEN];
+       long msgnum;
+       char buf[SIZ];
+
+       rewind(purgelist);
+       strcpy(roomname, "nonexistent room ___ ___");
+       while (fgets(buf, sizeof buf, purgelist) != NULL) {
+               buf[strlen(buf)-1]=0;
+               if (!strncasecmp(buf, "r=", 2)) {
+                       strcpy(roomname, &buf[2]);
+               }
+               if (!strncasecmp(buf, "m=", 2)) {
+                       msgnum = atol(&buf[2]);
+                       if (msgnum > 0L) {
+                               CtdlDeleteMessages(roomname, &msgnum, 1, "");
+                       }
+               }
+       }
+}
+
+
+void PurgeMessages(void) {
+       FILE *purgelist;
+
+       syslog(LOG_DEBUG, "PurgeMessages() called");
+       messages_purged = 0;
+
+       purgelist = tmpfile();
+       if (purgelist == NULL) {
+               syslog(LOG_CRIT, "Can't create purgelist temp file: %s", strerror(errno));
+               return;
+       }
+
+       CtdlForEachRoom(GatherPurgeMessages, (void *)purgelist );
+       DoPurgeMessages(purgelist);
+       fclose(purgelist);
+}
+
+
+void AddValidUser(char *username, void *data) {
+       struct ValidUser *vuptr;
+       struct ctdluser usbuf;
+
+       if (CtdlGetUser(&usbuf, username) != 0) {
+               return;
+       }
+
+       vuptr = (struct ValidUser *)malloc(sizeof(struct ValidUser));
+       vuptr->next = ValidUserList;
+       vuptr->vu_usernum = usbuf.usernum;
+       ValidUserList = vuptr;
+}
+
+
+void AddValidRoom(struct ctdlroom *qrbuf, void *data) {
+       struct ValidRoom *vrptr;
+
+       vrptr = (struct ValidRoom *)malloc(sizeof(struct ValidRoom));
+       vrptr->next = ValidRoomList;
+       vrptr->vr_roomnum = qrbuf->QRnumber;
+       vrptr->vr_roomgen = qrbuf->QRgen;
+       ValidRoomList = vrptr;
+}
+
+
+void DoPurgeRooms(struct ctdlroom *qrbuf, void *data) {
+       time_t age, purge_secs;
+       struct PurgeList *pptr;
+       struct ValidUser *vuptr;
+       int do_purge = 0;
+
+       // For mailbox rooms, there's only one purging rule: if the user who
+       // owns the room still exists, we keep the room; otherwise, we purge
+       // it.  Bypass any other rules.
+       if (qrbuf->QRflags & QR_MAILBOX) {
+               // if user not found, do_purge will be 1
+               do_purge = 1;
+               for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
+                       if (vuptr->vu_usernum == atol(qrbuf->QRname)) {
+                               do_purge = 0;
+                       }
+               }
+       }
+       else {
+               // Any of these attributes render a room non-purgable
+               if (qrbuf->QRflags & QR_PERMANENT) return;
+               if (qrbuf->QRflags & QR_DIRECTORY) return;
+               if (qrbuf->QRflags & QR_NETWORK) return;
+               if (qrbuf->QRflags2 & QR2_SYSTEM) return;
+               if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
+               if (CtdlIsNonEditable(qrbuf)) return;
+
+               // If we don't know the modification date, be safe and don't purge
+               if (qrbuf->QRmtime <= (time_t)0) return;
+
+               // If no room purge time is set, be safe and don't purge
+               if (CtdlGetConfigLong("c_roompurge") < 0) return;
+
+               // Otherwise, check the date of last modification
+               age = time(NULL) - (qrbuf->QRmtime);
+               purge_secs = CtdlGetConfigLong("c_roompurge") * 86400;
+               if (purge_secs <= (time_t)0) return;
+               syslog(LOG_DEBUG, "<%s> is <%ld> seconds old", qrbuf->QRname, (long)age);
+               if (age > purge_secs) do_purge = 1;
+       } // !QR_MAILBOX
+
+       if (do_purge) {
+               pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
+               pptr->next = RoomPurgeList;
+               strcpy(pptr->name, qrbuf->QRname);
+               RoomPurgeList = pptr;
+       }
+
+}
+
+
+int PurgeRooms(void) {
+       struct PurgeList *pptr;
+       int num_rooms_purged = 0;
+       struct ctdlroom qrbuf;
+       struct ValidUser *vuptr;
+       char *transcript = NULL;
+
+       syslog(LOG_DEBUG, "PurgeRooms() called");
+
+       // Load up a table full of valid user numbers so we can delete
+       // user-owned rooms for users who no longer exist
+       ForEachUser(AddValidUser, NULL);
+
+       // Then cycle through the room file
+       CtdlForEachRoom(DoPurgeRooms, NULL);
+
+       // Free the valid user list
+       while (ValidUserList != NULL) {
+               vuptr = ValidUserList->next;
+               free(ValidUserList);
+               ValidUserList = vuptr;
+       }
+
+       transcript = malloc(SIZ);
+       strcpy(transcript, "The following rooms have been auto-purged:\n");
+
+       while (RoomPurgeList != NULL) {
+               if (CtdlGetRoom(&qrbuf, RoomPurgeList->name) == 0) {
+                       transcript=realloc(transcript, strlen(transcript)+SIZ);
+                       snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
+                               qrbuf.QRname);
+                       CtdlDeleteRoom(&qrbuf);
+               }
+               pptr = RoomPurgeList->next;
+               free(RoomPurgeList);
+               RoomPurgeList = pptr;
+               ++num_rooms_purged;
+       }
+
+       if (num_rooms_purged > 0) CtdlAideMessage(transcript, "Room Autopurger Message");
+       free(transcript);
+
+       syslog(LOG_DEBUG, "Purged %d rooms.", num_rooms_purged);
+       return(num_rooms_purged);
+}
+
+
+// Back end function to check user accounts for expiration.
+void do_user_purge(char *username, void *data) {
+       int purge;
+       time_t now;
+       time_t purge_time;
+       struct PurgeList *pptr;
+       struct ctdluser us;
+
+       if (CtdlGetUser(&us, username) != 0) {
+               return;
+       }
+
+       // Set purge time; if the user overrides the system default, use it
+       if (us.USuserpurge > 0) {
+               purge_time = ((time_t)us.USuserpurge) * 86400;
+       }
+       else {
+               purge_time = CtdlGetConfigLong("c_userpurge") * 86400;
+       }
+
+       // The default rule is to not purge.
+       purge = 0;
+       
+       // If the user has not logged in for the configured amount of time, expire the account.
+       if (CtdlGetConfigLong("c_userpurge") > 0) {
+               now = time(NULL);
+               if ((now - us.lastcall) > purge_time) purge = 1;
+       }
+
+       // If the account is marked as permanent, don't purge it.
+       if (us.flags & US_PERM) purge = 0;
+
+       // If the account is an administrator, don't purge it.
+       if (us.axlevel == 6) purge = 0;
+
+       // If the access level is 0, the record should already have been
+       // deleted, but maybe the user was logged in at the time or something.
+       // Delete the record now.
+       if (us.axlevel == 0) purge = 1;
+
+       // If the user set his/her password to 'deleteme', he/she
+       // wishes to be deleted, so purge the record.
+       // Moved this lower down so that aides and permanent users get purged if they ask to be.
+       if (!strcasecmp(us.password, "deleteme")) purge = 1;
+       
+       // Fewer than zero calls is impossible, indicating a corrupted record.
+       if (us.timescalled < 0) purge = 1;
+
+       // any negative user number, is also impossible.
+       if (us.usernum < 0L) purge = 1;
+       
+       // Don't purge user 0. That user is there for the system
+       if (us.usernum == 0L) {
+               // FIXME: Temporary log message. Until we do unauth access with user 0 we should
+               // try to get rid of all user 0 occurences. Many will be remnants from old code so
+               // we will need to try and purge them from users data bases.Some will not have names but
+               // those with names should be purged.
+               syslog(LOG_DEBUG, "Auto purger found a user 0 with name <%s>", us.fullname);
+               // purge = 0;
+       }
+       
+       // If the user has no full name entry then we can't purge them since the actual purge can't find them.
+       // This shouldn't happen but does somehow.
+       if (IsEmptyStr(us.fullname)) {
+               purge = 0;
+               
+               if (us.usernum > 0L) {
+                       purge=0;
+                       if (users_corrupt_msg == NULL) {
+                               users_corrupt_msg = malloc(SIZ);
+                               strcpy(users_corrupt_msg,
+                                       "The auto-purger found the following user numbers with no name.\n"
+                                       "The system has no way to purge a user with no name,"
+                                       " and should not be able to create them either.\n"
+                                       "This indicates corruption of the user DB or possibly a bug.\n"
+                                       "It may be a good idea to restore your DB from a backup.\n"
+                               );
+                       }
+               
+                       users_corrupt_msg=realloc(users_corrupt_msg, strlen(users_corrupt_msg)+30);
+                       snprintf(&users_corrupt_msg[strlen(users_corrupt_msg)], 29, " %ld\n", us.usernum);
+               }
+       }
+
+       if (purge == 1) {
+               pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
+               pptr->next = UserPurgeList;
+               strcpy(pptr->name, us.fullname);
+               UserPurgeList = pptr;
+       }
+       else {
+               ++users_not_purged;
+       }
+
+}
+
+
+int PurgeUsers(void) {
+       struct PurgeList *pptr;
+       int num_users_purged = 0;
+       char *transcript = NULL;
+
+       syslog(LOG_DEBUG, "PurgeUsers() called");
+       users_not_purged = 0;
+
+       switch(CtdlGetConfigInt("c_auth_mode")) {
+               case AUTHMODE_NATIVE:
+                       ForEachUser(do_user_purge, NULL);
+                       break;
+               default:
+                       syslog(LOG_DEBUG, "User purge for auth mode %d is not implemented.", CtdlGetConfigInt("c_auth_mode"));
+                       break;
+       }
+
+       transcript = malloc(SIZ);
+
+       if (users_not_purged == 0) {
+               strcpy(transcript, "The auto-purger was told to purge every user.  It is\n"
+                               "refusing to do this because it usually indicates a problem\n"
+                               "such as an inability to communicate with a name service.\n"
+               );
+               while (UserPurgeList != NULL) {
+                       pptr = UserPurgeList->next;
+                       free(UserPurgeList);
+                       UserPurgeList = pptr;
+                       ++num_users_purged;
+               }
+       }
+
+       else {
+               strcpy(transcript, "The following users have been auto-purged:\n");
+               while (UserPurgeList != NULL) {
+                       transcript=realloc(transcript, strlen(transcript)+SIZ);
+                       snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
+                               UserPurgeList->name);
+                       purge_user(UserPurgeList->name);
+                       pptr = UserPurgeList->next;
+                       free(UserPurgeList);
+                       UserPurgeList = pptr;
+                       ++num_users_purged;
+               }
+       }
+
+       if (num_users_purged > 0) CtdlAideMessage(transcript, "User Purge Message");
+       free(transcript);
+
+       if (users_corrupt_msg) {
+               CtdlAideMessage(users_corrupt_msg, "User Corruption Message");
+               free (users_corrupt_msg);
+               users_corrupt_msg = NULL;
+       }
+       
+       if(users_zero_msg) {
+               CtdlAideMessage(users_zero_msg, "User Zero Message");
+               free (users_zero_msg);
+               users_zero_msg = NULL;
+       }
+               
+       syslog(LOG_DEBUG, "Purged %d users.", num_users_purged);
+       return(num_users_purged);
+}
+
+
+// Purge visits
+//
+// This is a really cumbersome "garbage collection" function.  We have to
+// delete visits which refer to rooms and/or users which no longer exist.  In
+// order to prevent endless traversals of the room and user files, we first
+// build linked lists of rooms and users which _do_ exist on the system, then
+// traverse the visit file, checking each record against those two lists and
+// purging the ones that do not have a match on _both_ lists.  (Remember, if
+// either the room or user being referred to is no longer on the system, the
+// record is completely useless.)
+//
+int PurgeVisits(void) {
+       struct cdbdata *cdbvisit;
+       visit vbuf;
+       struct VPurgeList *VisitPurgeList = NULL;
+       struct VPurgeList *vptr;
+       int purged = 0;
+       char IndexBuf[32];
+       int IndexLen;
+       struct ValidRoom *vrptr;
+       struct ValidUser *vuptr;
+       int RoomIsValid, UserIsValid;
+
+       // First, load up a table full of valid room/gen combinations
+       CtdlForEachRoom(AddValidRoom, NULL);
+
+       // Then load up a table full of valid user numbers
+       ForEachUser(AddValidUser, NULL);
+
+       // Now traverse through the visits, purging irrelevant records...
+       cdb_rewind(CDB_VISIT);
+       while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
+               memset(&vbuf, 0, sizeof(visit));
+               memcpy(&vbuf, cdbvisit->ptr,
+                       ( (cdbvisit->len > sizeof(visit)) ?
+                         sizeof(visit) : cdbvisit->len) );
+               cdb_free(cdbvisit);
+
+               RoomIsValid = 0;
+               UserIsValid = 0;
+
+               // Check to see if the room exists
+               for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
+                       if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
+                            && (vrptr->vr_roomgen==vbuf.v_roomgen))
+                               RoomIsValid = 1;
+               }
+
+               // Check to see if the user exists
+               for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
+                       if (vuptr->vu_usernum == vbuf.v_usernum)
+                               UserIsValid = 1;
+               }
+
+               // Put the record on the purge list if it's dead
+               if ((RoomIsValid==0) || (UserIsValid==0)) {
+                       vptr = (struct VPurgeList *)
+                               malloc(sizeof(struct VPurgeList));
+                       vptr->next = VisitPurgeList;
+                       vptr->vp_roomnum = vbuf.v_roomnum;
+                       vptr->vp_roomgen = vbuf.v_roomgen;
+                       vptr->vp_usernum = vbuf.v_usernum;
+                       VisitPurgeList = vptr;
+               }
+
+       }
+
+       // Free the valid room/gen combination list
+       while (ValidRoomList != NULL) {
+               vrptr = ValidRoomList->next;
+               free(ValidRoomList);
+               ValidRoomList = vrptr;
+       }
+
+       // Free the valid user list
+       while (ValidUserList != NULL) {
+               vuptr = ValidUserList->next;
+               free(ValidUserList);
+               ValidUserList = vuptr;
+       }
+
+       // Now delete every visit on the purged list
+       while (VisitPurgeList != NULL) {
+               IndexLen = GenerateRelationshipIndex(IndexBuf,
+                               VisitPurgeList->vp_roomnum,
+                               VisitPurgeList->vp_roomgen,
+                               VisitPurgeList->vp_usernum);
+               cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
+               vptr = VisitPurgeList->next;
+               free(VisitPurgeList);
+               VisitPurgeList = vptr;
+               ++purged;
+       }
+
+       return(purged);
+}
+
+
+// Purge the use table of old entries.
+int PurgeUseTable(StrBuf *ErrMsg) {
+       int purged = 0;
+       struct cdbdata *cdbut;
+       struct UseTable ut;
+       struct UPurgeList *ul = NULL;
+       struct UPurgeList *uptr; 
+
+       // Phase 1: traverse through the table, discovering old records...
+
+       syslog(LOG_DEBUG, "Purge use table: phase 1");
+       cdb_rewind(CDB_USETABLE);
+       while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
+               if (cdbut->len > sizeof(struct UseTable))
+                       memcpy(&ut, cdbut->ptr, sizeof(struct UseTable));
+               else {
+                       memset(&ut, 0, sizeof(struct UseTable));
+                       memcpy(&ut, cdbut->ptr, cdbut->len);
+               }
+               cdb_free(cdbut);
+
+               if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
+                       uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
+                       if (uptr != NULL) {
+                               uptr->next = ul;
+                               safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
+                               ul = uptr;
+                       }
+                       ++purged;
+               }
+
+       }
+
+       // Phase 2: delete the records
+       syslog(LOG_DEBUG, "Purge use table: phase 2");
+       while (ul != NULL) {
+               cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
+               uptr = ul->next;
+               free(ul);
+               ul = uptr;
+       }
+
+       syslog(LOG_DEBUG, "Purge use table: finished (purged %d records)", purged);
+       return(purged);
+}
+
+
+// Purge the EUID Index of old records.
+int PurgeEuidIndexTable(void) {
+       int purged = 0;
+       struct cdbdata *cdbei;
+       struct EPurgeList *el = NULL;
+       struct EPurgeList *eptr; 
+       long msgnum;
+       struct CtdlMessage *msg = NULL;
+
+       // Phase 1: traverse through the table, discovering old records...
+       syslog(LOG_DEBUG, "Purge EUID index: phase 1");
+       cdb_rewind(CDB_EUIDINDEX);
+       while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
+
+               memcpy(&msgnum, cdbei->ptr, sizeof(long));
+
+               msg = CtdlFetchMessage(msgnum, 0);
+               if (msg != NULL) {
+                       CM_Free(msg);   // it still exists, so do nothing
+               }
+               else {
+                       eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
+                       if (eptr != NULL) {
+                               eptr->next = el;
+                               eptr->ep_keylen = cdbei->len - sizeof(long);
+                               eptr->ep_key = malloc(cdbei->len);
+                               memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
+                               el = eptr;
+                       }
+                       ++purged;
+               }
+
+              cdb_free(cdbei);
+
+       }
+
+       // Phase 2: delete the records
+       syslog(LOG_DEBUG, "Purge euid index: phase 2");
+       while (el != NULL) {
+               cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
+               free(el->ep_key);
+               eptr = el->next;
+               free(el);
+               el = eptr;
+       }
+
+       syslog(LOG_DEBUG, "Purge euid index: finished (purged %d records)", purged);
+       return(purged);
+}
+
+
+// Purge external auth assocations for missing users (theoretically this will never delete anything)
+int PurgeStaleExtAuthAssociations(void) {
+       struct cdbdata *cdboi;
+       struct ctdluser usbuf;
+       HashList *keys = NULL;
+       HashPos *HashPos;
+       char *deleteme = NULL;
+       long len;
+       void *Value;
+       const char *Key;
+       int num_deleted = 0;
+       long usernum = 0L;
+
+       keys = NewHash(1, NULL);
+       if (!keys) return(0);
+
+       cdb_rewind(CDB_EXTAUTH);
+       while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
+               if (cdboi->len > sizeof(long)) {
+                       memcpy(&usernum, cdboi->ptr, sizeof(long));
+                       if (CtdlGetUserByNumber(&usbuf, usernum) != 0) {
+                               deleteme = strdup(cdboi->ptr + sizeof(long)),
+                               Put(keys, deleteme, strlen(deleteme), deleteme, NULL);
+                       }
+               }
+               cdb_free(cdboi);
+       }
+
+       // Go through the hash list, deleting keys we stored in it
+
+       HashPos = GetNewHashPos(keys, 0);
+       while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0) {
+               syslog(LOG_DEBUG, "Deleting associated external authenticator <%s>",  (char*)Value);
+               cdb_delete(CDB_EXTAUTH, Value, strlen(Value));
+               // note: don't free(Value) -- deleting the hash list will handle this for us
+               ++num_deleted;
+       }
+       DeleteHashPos(&HashPos);
+       DeleteHash(&keys);
+       return num_deleted;
+}
+
+
+void purge_databases(void) {
+       int retval;
+       static time_t last_purge = 0;
+       time_t now;
+       struct tm tm;
+
+       // Do the auto-purge if the current hour equals the purge hour,
+       // but not if the operation has already been performed in the
+       // last twelve hours.  This is usually enough granularity.
+       now = time(NULL);
+       localtime_r(&now, &tm);
+       if (((tm.tm_hour != CtdlGetConfigInt("c_purge_hour")) || ((now - last_purge) < 43200)) && (force_purge_now == 0)) {
+               return;
+       }
+
+       syslog(LOG_INFO, "Auto-purger: starting.");
+
+       if (!server_shutting_down) {
+               retval = PurgeUsers();
+               syslog(LOG_NOTICE, "Purged %d users.", retval);
+       }
+               
+       if (!server_shutting_down) {
+               PurgeMessages();
+               syslog(LOG_NOTICE, "Expired %d messages.", messages_purged);
+       }
+
+       if (!server_shutting_down) {
+                       retval = PurgeRooms();
+                       syslog(LOG_NOTICE, "Expired %d rooms.", retval);
+       }
+
+       if (!server_shutting_down) {
+                       retval = PurgeVisits();
+                       syslog(LOG_NOTICE, "Purged %d visits.", retval);
+       }
+
+       if (!server_shutting_down) {
+               StrBuf *ErrMsg;
+               ErrMsg = NewStrBuf();
+               retval = PurgeUseTable(ErrMsg);
+                       syslog(LOG_NOTICE, "Purged %d entries from the use table.", retval);
+               FreeStrBuf(&ErrMsg);
+       }
+
+       if (!server_shutting_down) {
+                       retval = PurgeEuidIndexTable();
+                       syslog(LOG_NOTICE, "Purged %d entries from the EUID index.", retval);
+       }
+
+       if (!server_shutting_down) {
+               retval = PurgeStaleExtAuthAssociations();
+               syslog(LOG_NOTICE, "Purged %d stale external auth associations.", retval);
+       }
+
+       //if (!server_shutting_down) {
+       //      FIXME this is where we could do a non-interactive delete of zero-refcount messages
+       //}
+
+       if ( (!server_shutting_down) && (CtdlGetConfigInt("c_shrink_db_files") != 0) ) {
+               cdb_compact();                                  // Shrink the DB files on disk
+       }
+
+       if (!server_shutting_down) {
+               syslog(LOG_INFO, "Auto-purger: finished.");
+               last_purge = now;                               // So we don't do it again soon
+               force_purge_now = 0;
+       }
+       else {
+               syslog(LOG_INFO, "Auto-purger: STOPPED.");
+       }
+}
+
+
+// Manually initiate a run of The Dreaded Auto-Purger (tm)
+void cmd_tdap(char *argbuf) {
+       if (CtdlAccessCheck(ac_aide)) return;
+       force_purge_now = 1;
+       cprintf("%d Manually initiating a purger run now.\n", CIT_OK);
+}
+
+
+char *ctdl_module_init_expire(void) {
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_tdap, "TDAP", "Manually initiate auto-purger");
+               CtdlRegisterProtoHook(cmd_gpex, "GPEX", "Get expire policy");
+               CtdlRegisterProtoHook(cmd_spex, "SPEX", "Set expire policy");
+               CtdlRegisterSessionHook(purge_databases, EVT_TIMER, PRIO_CLEANUP + 20);
+       }
+
+       // return our module name for the log
+       return "expire";
+}
diff --git a/citadel/server/modules/fulltext/.gitignore b/citadel/server/modules/fulltext/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/fulltext/crc16.c b/citadel/server/modules/fulltext/crc16.c
new file mode 100644 (file)
index 0000000..abfcd8e
--- /dev/null
@@ -0,0 +1,153 @@
+/****************************************************************************
+
+  Filename:     crc16.c
+  Description:  Cyclic Redundancy Check 16 functions
+  Created:      24-Feb-1999
+
+  Copyright (c) 1999-2003, Indigo Systems Corporation
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are met:
+
+  Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+  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.
+
+  Neither the name of the Indigo Systems Corporation nor the names of its
+  contributors may be used to endorse or promote products derived from this
+  software without specific prior written permission.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
+
+****************************************************************************/
+
+#include "crc16.h"
+
+#ifdef _OPT_SIZE
+
+
+/*
+ *  ===== ByteCRC16 =====
+ *      Calculate (update) the CRC16 for a single 8-bit byte
+ */
+int ByteCRC16(int value, int crcin)
+{
+    int k = (((crcin >> 8) ^ value) & 255) << 8;
+    int crc = 0;
+    int bits = 8;
+    do
+    {
+       if (( crc ^ k ) & 0x8000)
+           crc = (crc << 1) ^ 0x1021;
+       else
+           crc <<= 1;
+       k <<= 1;
+    }
+    while (--bits);
+    return ((crcin << 8) ^ crc);
+}
+
+#else
+
+const CRC16 ccitt_16Table[] = {
+   0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
+   0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
+   0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
+   0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
+   0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
+   0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
+   0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
+   0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
+   0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
+   0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
+   0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
+   0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
+   0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
+   0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
+   0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
+   0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
+   0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
+   0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
+   0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
+   0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
+   0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
+   0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
+   0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
+   0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
+   0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
+   0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
+   0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
+   0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
+   0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
+   0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
+   0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
+   0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
+};
+
+#define ByteCRC16(v, crc) \
+       (unsigned short)((crc << 8) ^  ccitt_16Table[((crc >> 8) ^ (v)) & 255])
+
+/*
+ *  ===== CalcCRC16Words =====
+ *      Calculate the CRC for a buffer of 16-bit words.  Supports both
+ *  Little and Big Endian formats using conditional compilation.
+ *      Note: minimum count is 1 (0 case not handled)
+ */
+CRC16 CalcCRC16Words(unsigned int count, short *buffer) {
+
+    int crc = 0;
+
+    do {
+
+       int value = *buffer++;
+#ifdef _BIG_ENDIAN
+       crc = ByteCRC16(value >> 8, crc);
+       crc = ByteCRC16(value, crc);
+#else
+       crc = ByteCRC16(value, crc);
+       crc = ByteCRC16(value >> 8, crc);
+#endif
+    }
+       while (--count);
+    return (CRC16) crc;
+}
+
+#endif /* _OPT_SIZE */
+
+#ifdef _CRC16_BYTES
+
+/*
+ *  ===== CalcCRC16Bytes =====
+ *      Calculate the CRC for a buffer of 8-bit words.
+ *      Note: minimum count is 1 (0 case not handled)
+ */
+CRC16 CalcCRC16Bytes(unsigned int count, char *buffer) {
+
+    int crc = 0;
+
+    do {
+
+       int value = *buffer++;
+       crc = ByteCRC16(value, crc);
+    }
+       while (--count);
+    return crc;
+}
+
+#endif /* _CRC16_BYTES */
+
+
diff --git a/citadel/server/modules/fulltext/crc16.h b/citadel/server/modules/fulltext/crc16.h
new file mode 100644 (file)
index 0000000..9f13af7
--- /dev/null
@@ -0,0 +1,67 @@
+/****************************************************************************
+
+  Filename:     crc16.h
+  Description:  Cyclic Redundancy Check 16 functions
+  Created:      24-Feb-1999
+
+  Copyright (c) 2002-2003, Indigo Systems Corporation
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are met:
+
+  Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+  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.
+
+  Neither the name of the Indigo Systems Corporation nor the names of its
+  contributors may be used to endorse or promote products derived from this
+  software without specific prior written permission.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
+
+****************************************************************************/
+
+#define _CRC16_BYTES   1               /* ig */
+
+#ifndef __CRC16_H__
+#define __CRC16_H__
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+typedef unsigned short CRC16;
+
+#ifdef _OPT_SIZE
+    int ByteCRC16(int value, int crcin);
+#else
+    CRC16 CalcCRC16Words(unsigned int count, short *buffer);
+#endif
+
+#ifdef _CRC16_BYTES
+    CRC16 CalcCRC16Bytes(unsigned int count, char *buffer);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __CRC16_H__ */
+
+
+
diff --git a/citadel/server/modules/fulltext/ft_wordbreaker.c b/citadel/server/modules/fulltext/ft_wordbreaker.c
new file mode 100644 (file)
index 0000000..d5a07d4
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * Default wordbreaker module for full text indexing.
+ *
+ * Copyright (c) 2005-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../sysdep_decls.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../control.h"
+#include "ft_wordbreaker.h"
+#include "crc16.h"
+#include "../../ctdl_module.h"
+
+/*
+ * Noise words are not included in search indices.
+ * NOTE: if the noise word list is altered in any way, the FT_WORDBREAKER_ID
+ * must also be changed, so that the index is rebuilt.
+ */
+static char *noise_words[] = {
+       "about",
+       "after",
+       "also",
+       "another",
+       "because",
+       "been",
+       "before",
+       "being",
+       "between",
+       "both",
+       "came",
+       "come",
+       "could",
+       "each",
+       "from",
+       "have",
+       "here",
+       "himself",
+       "into",
+       "like",
+       "make",
+       "many",
+       "might",
+       "more",
+       "most",
+       "much",
+       "must",
+       "never",
+       "only",
+       "other",
+       "over",
+       "said",
+       "same",
+       "should",
+       "since",
+       "some",
+       "still",
+       "such",
+       "take",
+       "than",
+       "that",
+       "their",
+       "them",
+       "then",
+       "there",
+       "these",
+       "they",
+       "this",
+       "those",
+       "through",
+       "under",
+       "very",
+       "well",
+       "were",
+       "what",
+       "where",
+       "which",
+       "while",
+       "with",
+       "would",
+       "your"
+};
+#define NUM_NOISE (sizeof(noise_words) / sizeof(char *))
+
+
+/*
+ * Compare function
+ */
+int intcmp(const void *rec1, const void *rec2) {
+       int i1, i2;
+
+       i1 = *(const int *)rec1;
+       i2 = *(const int *)rec2;
+
+       if (i1 > i2) return(1);
+       if (i1 < i2) return(-1);
+       return(0);
+}
+
+
+void wordbreaker(const char *text, int *num_tokens, int **tokens) {
+
+       int wb_num_tokens = 0;
+       int wb_num_alloc = 0;
+       int *wb_tokens = NULL;
+
+       const char *ptr;
+       const char *word_start;
+       const char *word_end;
+       char ch;
+       int word_len;
+       char word[256];
+       int i;
+       int word_crc;
+       
+       if (text == NULL) {             /* no NULL text please */
+               *num_tokens = 0;
+               *tokens = NULL;
+               return;
+       }
+
+       if (text[0] == 0) {             /* no empty text either */
+               *num_tokens = 0;
+               *tokens = NULL;
+               return;
+       }
+
+       ptr = text;
+       word_start = NULL;
+       while (*ptr) {
+               ch = *ptr;
+               if (isalnum(ch)) {
+                       if (!word_start) {
+                               word_start = ptr;
+                       }
+               }
+               ++ptr;
+               ch = *ptr;
+               if ( (!isalnum(ch)) && (word_start) ) {
+                       word_end = ptr;
+
+                       /* extract the word */
+                       word_len = word_end - word_start;
+                       if (word_len >= sizeof word) {
+                               syslog(LOG_DEBUG, "wordbreaker: invalid word length: %d", word_len);
+                               safestrncpy(word, word_start, sizeof word);
+                               word[(sizeof word) - 1] = 0;
+                       }
+                       else {
+                               safestrncpy(word, word_start, word_len+1);
+                               word[word_len] = 0;
+                       }
+                       word_start = NULL;
+
+                       /* are we ok with the length? */
+                       if ( (word_len >= WB_MIN) && (word_len <= WB_MAX) ) {
+                               for (i=0; i<word_len; ++i) {
+                                       word[i] = tolower(word[i]);
+                               }
+                               /* disqualify noise words */
+                               for (i=0; i<NUM_NOISE; ++i) {
+                                       if (!strcmp(word, noise_words[i])) {
+                                               word_len = 0;
+                                               break;
+                                       }
+                               }
+
+                               if (word_len == 0)
+                                       continue;
+
+                               word_crc = (int) CalcCRC16Bytes(word_len, word);
+
+                               ++wb_num_tokens;
+                               if (wb_num_tokens > wb_num_alloc) {
+                                       wb_num_alloc += 512;
+                                       wb_tokens = realloc(wb_tokens, (sizeof(int) * wb_num_alloc));
+                               }
+                               wb_tokens[wb_num_tokens - 1] = word_crc;
+                       }
+               }
+       }
+
+       /* sort and purge dups */
+       if (wb_num_tokens > 1) {
+               qsort(wb_tokens, wb_num_tokens, sizeof(int), intcmp);
+               for (i=0; i<(wb_num_tokens-1); ++i) {
+                       if (wb_tokens[i] == wb_tokens[i+1]) {
+                               memmove(&wb_tokens[i], &wb_tokens[i+1],
+                                       ((wb_num_tokens - i - 1)*sizeof(int)));
+                               --wb_num_tokens;
+                               --i;
+                       }
+               }
+       }
+
+       *num_tokens = wb_num_tokens;
+       *tokens = wb_tokens;
+}
+
diff --git a/citadel/server/modules/fulltext/ft_wordbreaker.h b/citadel/server/modules/fulltext/ft_wordbreaker.h
new file mode 100644 (file)
index 0000000..e36d695
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2005-2021 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * This is an ID for the wordbreaker module.  If we do pluggable wordbreakers
+ * later on, or even if we update this one, we can use a different ID so the
+ * system knows it needs to throw away the existing index and rebuild it.
+ */
+#define        FT_WORDBREAKER_ID       0x0021
+
+/*
+ * Minimum and maximum length of words to index
+ */
+#define WB_MIN                 4       // nothing with 3 or less chars
+#define WB_MAX                 40
+
+void wordbreaker(const char *text, int *num_tokens, int **tokens);
diff --git a/citadel/server/modules/fulltext/serv_fulltext.c b/citadel/server/modules/fulltext/serv_fulltext.c
new file mode 100644 (file)
index 0000000..5ecba60
--- /dev/null
@@ -0,0 +1,475 @@
+/*
+ * This module handles fulltext indexing of the message base.
+ * Copyright (c) 2005-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../control.h"
+#include "serv_fulltext.h"
+#include "ft_wordbreaker.h"
+#include "../../threads.h"
+#include "../../context.h"
+#include "../../ctdl_module.h"
+
+long ft_newhighest = 0L;
+long *ft_newmsgs = NULL;
+int ft_num_msgs = 0;
+int ft_num_alloc = 0;
+
+int ftc_num_msgs[65536];
+long *ftc_msgs[65536];
+
+
+/*
+ * Compare function
+ */
+int longcmp(const void *rec1, const void *rec2) {
+       long i1, i2;
+
+       i1 = *(const long *)rec1;
+       i2 = *(const long *)rec2;
+
+       if (i1 > i2) return(1);
+       if (i1 < i2) return(-1);
+       return(0);
+}
+
+
+/*
+ * Flush our index cache out to disk.
+ */
+void ft_flush_cache(void) {
+       int i;
+       time_t last_update = 0;
+
+       for (i=0; i<65536; ++i) {
+               if ((time(NULL) - last_update) >= 10) {
+                       syslog(LOG_INFO,
+                               "fulltext: flushing index cache to disk (%d%% complete)",
+                               (i * 100 / 65536)
+                       );
+                       last_update = time(NULL);
+               }
+               if (ftc_msgs[i] != NULL) {
+                       cdb_store(CDB_FULLTEXT, &i, sizeof(int), ftc_msgs[i],
+                               (ftc_num_msgs[i] * sizeof(long)));
+                       ftc_num_msgs[i] = 0;
+                       free(ftc_msgs[i]);
+                       ftc_msgs[i] = NULL;
+               }
+       }
+       syslog(LOG_INFO, "fulltext: flushed index cache to disk (100%% complete)");
+}
+
+
+/*
+ * Index or de-index a message.  (op == 1 to index, 0 to de-index)
+ */
+void ft_index_message(long msgnum, int op) {
+       int num_tokens = 0;
+       int *tokens = NULL;
+       int i, j;
+       struct cdbdata *cdb_bucket;
+       StrBuf *msgtext;
+       char *txt;
+       int tok;
+       struct CtdlMessage *msg = NULL;
+
+       msg = CtdlFetchMessage(msgnum, 1);
+       if (msg == NULL) {
+               syslog(LOG_ERR, "fulltext: ft_index_message() could not load msg %ld", msgnum);
+               return;
+       }
+
+       if (!CM_IsEmpty(msg, eSuppressIdx)) {
+               syslog(LOG_DEBUG, "fulltext: ft_index_message() excluded msg %ld", msgnum);
+               CM_Free(msg);
+               return;
+       }
+
+       syslog(LOG_DEBUG, "fulltext: ft_index_message() %s msg %ld", (op ? "adding" : "removing") , msgnum);
+
+       /* Output the message as text before indexing it, so we don't end up
+        * indexing a bunch of encoded base64, etc.
+        */
+       CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+       CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_ALL, 0, 1, 0);
+       CM_Free(msg);
+       msgtext = CC->redirect_buffer;
+       CC->redirect_buffer = NULL;
+       if (msgtext != NULL) {
+               syslog(LOG_DEBUG, "fulltext: wordbreaking message %ld (%d bytes)", msgnum, StrLength(msgtext));
+       }
+       txt = SmashStrBuf(&msgtext);
+       wordbreaker(txt, &num_tokens, &tokens);
+       free(txt);
+
+       syslog(LOG_DEBUG, "fulltext: indexing message %ld [%d tokens]", msgnum, num_tokens);
+       if (num_tokens > 0) {
+               for (i=0; i<num_tokens; ++i) {
+
+                       /* Add the message to the relevant token bucket */
+
+                       /* search for tokens[i] */
+                       tok = tokens[i];
+
+                       if ( (tok >= 0) && (tok <= 65535) ) {
+                               /* fetch the bucket, Liza */
+                               if (ftc_msgs[tok] == NULL) {
+                                       cdb_bucket = cdb_fetch(CDB_FULLTEXT, &tok, sizeof(int));
+                                       if (cdb_bucket != NULL) {
+                                               ftc_num_msgs[tok] = cdb_bucket->len / sizeof(long);
+                                               ftc_msgs[tok] = (long *)cdb_bucket->ptr;
+                                               cdb_bucket->ptr = NULL;
+                                               cdb_free(cdb_bucket);
+                                       }
+                                       else {
+                                               ftc_num_msgs[tok] = 0;
+                                               ftc_msgs[tok] = malloc(sizeof(long));
+                                       }
+                               }
+       
+       
+                               if (op == 1) {  /* add to index */
+                                       ++ftc_num_msgs[tok];
+                                       ftc_msgs[tok] = realloc(ftc_msgs[tok],
+                                                               ftc_num_msgs[tok]*sizeof(long));
+                                       ftc_msgs[tok][ftc_num_msgs[tok] - 1] = msgnum;
+                               }
+       
+                               if (op == 0) {  /* remove from index */
+                                       if (ftc_num_msgs[tok] >= 1) {
+                                               for (j=0; j<ftc_num_msgs[tok]; ++j) {
+                                                       if (ftc_msgs[tok][j] == msgnum) {
+                                                               memmove(&ftc_msgs[tok][j], &ftc_msgs[tok][j+1], ((ftc_num_msgs[tok] - j - 1)*sizeof(long)));
+                                                               --ftc_num_msgs[tok];
+                                                               --j;
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+                       else {
+                               syslog(LOG_ALERT, "fulltext: invalid token %d !!", tok);
+                       }
+               }
+
+               free(tokens);
+       }
+}
+
+
+/*
+ * Add a message to the list of those to be indexed.
+ */
+void ft_index_msg(long msgnum, void *userdata) {
+
+       if ((msgnum > CtdlGetConfigLong("MMfulltext")) && (msgnum <= ft_newhighest)) {
+               ++ft_num_msgs;
+               if (ft_num_msgs > ft_num_alloc) {
+                       ft_num_alloc += 1024;
+                       ft_newmsgs = realloc(ft_newmsgs, (ft_num_alloc * sizeof(long)));
+               }
+               ft_newmsgs[ft_num_msgs - 1] = msgnum;
+       }
+
+}
+
+
+/*
+ * Scan a room for messages to index.
+ */
+void ft_index_room(struct ctdlroom *qrbuf, void *data)
+{
+       if (server_shutting_down)
+               return;
+               
+       CtdlGetRoom(&CC->room, qrbuf->QRname);
+       CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, ft_index_msg, NULL);
+}
+
+
+/*
+ * Begin the fulltext indexing process.
+ */
+void do_fulltext_indexing(void) {
+       int i;
+       static time_t last_progress = 0L;
+       static int is_running = 0;
+       if (is_running) return;         /* Concurrency check - only one can run */
+       is_running = 1;
+
+       /*
+        * Don't do this if the site doesn't have it enabled.
+        */
+       if (!CtdlGetConfigInt("c_enable_fulltext")) {
+               return;
+       }
+
+       /*
+        * If we've switched wordbreaker modules, burn the index and start over.
+        */
+       begin_critical_section(S_CONTROL);
+       if (CtdlGetConfigInt("MM_fulltext_wordbreaker") != FT_WORDBREAKER_ID) {
+               syslog(LOG_DEBUG, "fulltext: wb ver on disk = %d, code ver = %d",
+                       CtdlGetConfigInt("MM_fulltext_wordbreaker"), FT_WORDBREAKER_ID
+               );
+               syslog(LOG_INFO, "fulltext: (re)initializing index");
+               cdb_trunc(CDB_FULLTEXT);
+               CtdlSetConfigLong("MMfulltext", 0);
+       }
+       end_critical_section(S_CONTROL);
+
+       /*
+        * Silently return if our fulltext index is up to date with new messages.
+        */
+       if ((CtdlGetConfigLong("MMfulltext") >= CtdlGetConfigLong("MMhighest"))) {
+               return;         /* nothing to do! */
+       }
+
+       /*
+        * Now go through each room and find messages to index.
+        */
+       ft_newhighest = CtdlGetConfigLong("MMhighest");
+       CtdlForEachRoom(ft_index_room, NULL);   /* load all msg pointers */
+
+       if (ft_num_msgs > 0) {
+               qsort(ft_newmsgs, ft_num_msgs, sizeof(long), longcmp);
+               for (i=0; i<(ft_num_msgs-1); ++i) { /* purge dups */
+                       if (ft_newmsgs[i] == ft_newmsgs[i+1]) {
+                               memmove(&ft_newmsgs[i], &ft_newmsgs[i+1],
+                                       ((ft_num_msgs - i - 1)*sizeof(long)));
+                               --ft_num_msgs;
+                               --i;
+                       }
+               }
+
+               /* Here it is ... do each message! */
+               for (i=0; i<ft_num_msgs; ++i) {
+                       if (time(NULL) != last_progress) {
+                               syslog(LOG_DEBUG,
+                                       "fulltext: indexed %d of %d messages (%d%%)",
+                                               i, ft_num_msgs,
+                                               ((i*100) / ft_num_msgs)
+                               );
+                               last_progress = time(NULL);
+                       }
+                       ft_index_message(ft_newmsgs[i], 1);
+
+                       /* Check to see if we need to quit early */
+                       if (server_shutting_down) {
+                               syslog(LOG_DEBUG, "fulltext: indexer quitting early");
+                               ft_newhighest = ft_newmsgs[i];
+                               break;
+                       }
+
+                       /* Check to see if we have to maybe flush to disk */
+                       if (i >= FT_MAX_CACHE) {
+                               syslog(LOG_DEBUG, "fulltext: time to flush.");
+                               ft_newhighest = ft_newmsgs[i];
+                               break;
+                       }
+
+               }
+
+               free(ft_newmsgs);
+               ft_num_msgs = 0;
+               ft_num_alloc = 0;
+               ft_newmsgs = NULL;
+       }
+
+       if (server_shutting_down) {
+               is_running = 0;
+               return;
+       }
+       
+       /* Save our place so we don't have to do this again */
+       ft_flush_cache();
+       begin_critical_section(S_CONTROL);
+       CtdlSetConfigLong("MMfulltext", ft_newhighest);
+       CtdlSetConfigInt("MM_fulltext_wordbreaker", FT_WORDBREAKER_ID);
+       end_critical_section(S_CONTROL);
+
+       syslog(LOG_DEBUG, "fulltext: indexing finished");
+       is_running = 0;
+       return;
+}
+
+
+/*
+ * API call to perform searches.
+ * (This one does the "all of these words" search.)
+ * Caller is responsible for freeing the message list.
+ */
+void ft_search(int *fts_num_msgs, long **fts_msgs, const char *search_string) {
+       int num_tokens = 0;
+       int *tokens = NULL;
+       int i, j;
+       struct cdbdata *cdb_bucket;
+       int num_all_msgs = 0;
+       long *all_msgs = NULL;
+       int num_ret_msgs = 0;
+       int num_ret_alloc = 0;
+       long *ret_msgs = NULL;
+       int tok;
+
+       wordbreaker(search_string, &num_tokens, &tokens);
+       if (num_tokens > 0) {
+               for (i=0; i<num_tokens; ++i) {
+
+                       /* search for tokens[i] */
+                       tok = tokens[i];
+
+                       /* fetch the bucket, Liza */
+                       if (ftc_msgs[tok] == NULL) {
+                               cdb_bucket = cdb_fetch(CDB_FULLTEXT, &tok, sizeof(int));
+                               if (cdb_bucket != NULL) {
+                                       ftc_num_msgs[tok] = cdb_bucket->len / sizeof(long);
+                                       ftc_msgs[tok] = (long *)cdb_bucket->ptr;
+                                       cdb_bucket->ptr = NULL;
+                                       cdb_free(cdb_bucket);
+                               }
+                               else {
+                                       ftc_num_msgs[tok] = 0;
+                                       ftc_msgs[tok] = malloc(sizeof(long));
+                               }
+                       }
+
+                       num_all_msgs += ftc_num_msgs[tok];
+                       if (num_all_msgs > 0) {
+                               all_msgs = realloc(all_msgs, num_all_msgs*sizeof(long) );
+                               memcpy(&all_msgs[num_all_msgs-ftc_num_msgs[tok]],
+                                       ftc_msgs[tok], ftc_num_msgs[tok]*sizeof(long) );
+                       }
+
+               }
+               free(tokens);
+               if (all_msgs != NULL) {
+                       qsort(all_msgs, num_all_msgs, sizeof(long), longcmp);
+
+                       /*
+                        * At this point, if a message appears num_tokens times in the
+                        * list, then it contains all of the search tokens.
+                        */
+                       if (num_all_msgs >= num_tokens)
+                               for (j=0; j<(num_all_msgs-num_tokens+1); ++j) {
+                                       if (all_msgs[j] == all_msgs[j+num_tokens-1]) {
+                                               
+                                               ++num_ret_msgs;
+                                               if (num_ret_msgs > num_ret_alloc) {
+                                                       num_ret_alloc += 64;
+                                                       ret_msgs = realloc(ret_msgs,
+                                                                          (num_ret_alloc*sizeof(long)) );
+                                               }
+                                               ret_msgs[num_ret_msgs - 1] = all_msgs[j];
+                                               
+                                       }
+                               }
+                       free(all_msgs);
+               }
+       }
+
+       *fts_num_msgs = num_ret_msgs;
+       *fts_msgs = ret_msgs;
+}
+
+
+/*
+ * This search command is for diagnostic purposes and may be removed or replaced.
+ */
+void cmd_srch(char *argbuf) {
+       int num_msgs = 0;
+       long *msgs = NULL;
+       int i;
+       char search_string[256];
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       if (!CtdlGetConfigInt("c_enable_fulltext")) {
+               cprintf("%d Full text index is not enabled on this server.\n",
+                       ERROR + CMD_NOT_SUPPORTED);
+               return;
+       }
+
+       extract_token(search_string, argbuf, 0, '|', sizeof search_string);
+       ft_search(&num_msgs, &msgs, search_string);
+
+       cprintf("%d %d msgs match all search words:\n",
+               LISTING_FOLLOWS, num_msgs);
+       if (num_msgs > 0) {
+               for (i=0; i<num_msgs; ++i) {
+                       cprintf("%ld\n", msgs[i]);
+               }
+       }
+       if (msgs != NULL) free(msgs);
+       cprintf("000\n");
+}
+
+
+/*
+ * Zero out our index cache.
+ */
+void initialize_ft_cache(void) {
+       memset(ftc_num_msgs, 0, (65536 * sizeof(int)));
+       memset(ftc_msgs, 0, (65536 * sizeof(long *)));
+}
+
+
+void ft_delete_remove(char *room, long msgnum)
+{
+       if (room) return;
+       
+       /* Remove from fulltext index */
+       if (CtdlGetConfigInt("c_enable_fulltext")) {
+               ft_index_message(msgnum, 0);
+       }
+}
+
+
+/*****************************************************************************/
+
+char *ctdl_module_init_fulltext(void) {
+       if (!threading) {
+               initialize_ft_cache();
+               CtdlRegisterProtoHook(cmd_srch, "SRCH", "Full text search");
+               CtdlRegisterDeleteHook(ft_delete_remove);
+               CtdlRegisterSearchFuncHook(ft_search, "fulltext");
+               CtdlRegisterSessionHook(do_fulltext_indexing, EVT_TIMER, PRIO_CLEANUP + 300);
+       }
+       /* return our module name for the log */
+       return "fulltext";
+}
diff --git a/citadel/server/modules/fulltext/serv_fulltext.h b/citadel/server/modules/fulltext/serv_fulltext.h
new file mode 100644 (file)
index 0000000..bc05c51
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2005-2012 by the citadel.org team
+ *
+ *  This program is open source software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 3.
+ *  
+ *  
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  
+ *  
+ *  
+ */
+
+void ft_index_message(long msgnum, int op);
+void ft_search(int *fts_num_msgs, long **fts_msgs, const char *search_string);
+void *indexer_thread(void *arg);
diff --git a/citadel/server/modules/image/serv_image.c b/citadel/server/modules/image/serv_image.c
new file mode 100644 (file)
index 0000000..e6b520f
--- /dev/null
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) 1987-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../ctdl_module.h"
+#include "../../config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+
+/*
+ * DownLoad Room Image (see its icon or whatever)
+ * If this command succeeds, it follows the same protocol as the DLAT command.
+ */
+void cmd_dlri(char *cmdbuf) {
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+       if (CC->room.msgnum_pic < 1) {
+               cprintf("%d No image found.\n", ERROR + FILE_NOT_FOUND);
+               return;
+       }
+
+       struct CtdlMessage *msg = CtdlFetchMessage(CC->room.msgnum_pic, 1);
+       if (msg != NULL) {
+               // The call to CtdlOutputPreLoadedMsg() with MT_SPEW_SECTION will cause the DLRI command
+               // to have the same output format as the DLAT command, because it calls the same code.
+               // For example: 600 402132|-1||image/gif|
+               safestrncpy(CC->download_desired_section, "1", sizeof CC->download_desired_section);
+               CtdlOutputPreLoadedMsg(msg, MT_SPEW_SECTION, HEADERS_NONE, 1, 0, 0);
+               CM_Free(msg);
+       }
+       else {
+               cprintf("%d No image found.\n", ERROR + MESSAGE_NOT_FOUND);
+               return;
+       }
+}
+
+
+/*
+ * UpLoad Room Image (avatar or photo or whatever)
+ */
+void cmd_ulri(char *cmdbuf) {
+       long data_length;
+       char mimetype[SIZ];
+
+       if (CtdlAccessCheck(ac_room_aide)) return;
+
+       data_length = extract_long(cmdbuf, 0);
+       extract_token(mimetype, cmdbuf, 1, '|', sizeof mimetype);
+
+       if (data_length < 20) {
+               cprintf("%d That's an awfully small file.  Try again.\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       if (strncasecmp(mimetype, "image/", 6)) {
+               cprintf("%d Only image files are permitted.\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       char *unencoded_data = malloc(data_length + 1);
+       if (!unencoded_data) {
+               cprintf("%d Could not allocate %ld bytes of memory\n", ERROR + INTERNAL_ERROR , data_length);
+               return;
+       }
+
+       cprintf("%d %ld\n", SEND_BINARY, data_length);
+       client_read(unencoded_data, data_length);
+
+       // We've got the data read from the client, now save it.
+       char *encoded_data = malloc((data_length * 2) + 100);
+       if (encoded_data) {
+               sprintf(encoded_data, "Content-type: %s\nContent-transfer-encoding: base64\n\n", mimetype);
+               CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
+               long new_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, encoded_data, FMT_RFC822, "Image uploaded by admin user");
+
+               if (CtdlGetRoomLock(&CC->room, CC->room.QRname) == 0) {
+                       long old_msgnum = CC->room.msgnum_pic;
+                       syslog(LOG_DEBUG, "Message %ld is now the photo for %s", new_msgnum, CC->room.QRname);
+                       CC->room.msgnum_pic = new_msgnum;
+                       CtdlPutRoomLock(&CC->room);
+                       if (old_msgnum > 0) {
+                               syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, SYSCONFIGROOM);
+                               CtdlDeleteMessages(SYSCONFIGROOM, &old_msgnum, 1, "");
+                       }
+               }
+               free(encoded_data);
+       }
+
+       free(unencoded_data);
+}
+
+
+/*
+ * DownLoad User Image (see their avatar or photo or whatever)
+ * If this command succeeds, it follows the same protocol as the DLAT command.
+ */
+void cmd_dlui(char *cmdbuf) {
+       struct ctdluser ruser;
+       char buf[SIZ];
+
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+       extract_token(buf, cmdbuf, 0, '|', sizeof buf);
+       if (CtdlGetUser(&ruser, buf) != 0) {
+               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+               return;
+       }
+       if (ruser.msgnum_pic < 1) {
+               cprintf("%d No image found.\n", ERROR + FILE_NOT_FOUND);
+               return;
+       }
+
+       struct CtdlMessage *msg = CtdlFetchMessage(ruser.msgnum_pic, 1);
+       if (msg != NULL) {
+               // The call to CtdlOutputPreLoadedMsg() with MT_SPEW_SECTION will cause the DLUI command
+               // to have the same output format as the DLAT command, because it calls the same code.
+               // For example: 600 402132|-1||image/gif|
+               safestrncpy(CC->download_desired_section, "1", sizeof CC->download_desired_section);
+               CtdlOutputPreLoadedMsg(msg, MT_SPEW_SECTION, HEADERS_NONE, 1, 0, 0);
+               CM_Free(msg);
+       }
+       else {
+               cprintf("%d No image found.\n", ERROR + MESSAGE_NOT_FOUND);
+               return;
+       }
+}
+
+
+/*
+ * UpLoad User Image (avatar or photo or whatever)
+ */
+void cmd_ului(char *cmdbuf) {
+       long data_length;
+       char mimetype[SIZ];
+       char username[USERNAME_SIZE];
+       char userconfigroomname[ROOMNAMELEN];
+
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+       if (num_parms(cmdbuf) < 2) {
+               cprintf("%d Usage error\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       data_length = extract_long(cmdbuf, 0);
+       extract_token(mimetype, cmdbuf, 1, '|', sizeof mimetype);
+       extract_token(username, cmdbuf, 2, '|', sizeof username);
+
+       if (data_length < 20) {
+               cprintf("%d That's an awfully small file.  Try again.\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       if (strncasecmp(mimetype, "image/", 6)) {
+               cprintf("%d Only image files are permitted.\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       if (IsEmptyStr(username)) {
+               safestrncpy(username, CC->curr_user, sizeof username);
+       }
+
+       // Normal users can only change their own photo
+       if ( (strcasecmp(username, CC->curr_user)) && (CC->user.axlevel < AxAideU) && (!CC->internal_pgm) ) {
+               cprintf("%d Higher access required to change another user's photo.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+       }
+
+       // Check to make sure the user exists
+       struct ctdluser usbuf;
+       if (CtdlGetUser(&usbuf, username) != 0) {               // check for existing user, don't lock it yet
+               cprintf("%d %s not found.\n", ERROR + NO_SUCH_USER , username);
+               return;
+       }
+       CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &usbuf, USERCONFIGROOM);
+
+       char *unencoded_data = malloc(data_length + 1);
+       if (!unencoded_data) {
+               cprintf("%d Could not allocate %ld bytes of memory\n", ERROR + INTERNAL_ERROR , data_length);
+               return;
+       }
+
+       cprintf("%d %ld\n", SEND_BINARY, data_length);
+       client_read(unencoded_data, data_length);
+
+       // We've got the data read from the client, now save it.
+       char *encoded_data = malloc((data_length * 2) + 100);
+       if (encoded_data) {
+               sprintf(encoded_data, "Content-type: %s\nContent-transfer-encoding: base64\n\n", mimetype);
+               CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
+               long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, encoded_data, FMT_RFC822, "Photo uploaded by user");
+
+               if (CtdlGetUserLock(&usbuf, username) == 0) {   // lock it this time
+                       long old_msgnum = usbuf.msgnum_pic;
+                       syslog(LOG_DEBUG, "Message %ld is now the photo for %s", new_msgnum, username);
+                       usbuf.msgnum_pic = new_msgnum;
+                       CtdlPutUserLock(&usbuf);
+                       if (old_msgnum > 0) {
+                               syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
+                               CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
+                       }
+               }
+
+               free(encoded_data);
+       }
+
+       free(unencoded_data);
+}
+
+
+/*
+ * Import function called by import_old_userpic_files() for a single user
+ */
+void import_one_userpic_file(char *username, long usernum, char *path) {
+       syslog(LOG_DEBUG, "Import legacy userpic for %s, usernum=%ld, filename=%s", username, usernum, path);
+
+       FILE *fp = fopen(path, "r");
+       if (!fp) return;
+
+       fseek(fp, 0, SEEK_END);
+       long data_length = ftell(fp);
+
+       if (data_length >= 1) {
+               rewind(fp);
+               char *unencoded_data = malloc(data_length);
+               if (unencoded_data) {
+                       fread(unencoded_data, data_length, 1, fp);
+                       char *encoded_data = malloc((data_length * 2) + 100);
+                       if (encoded_data) {
+                               sprintf(encoded_data, "Content-type: %s\nContent-transfer-encoding: base64\n\n", GuessMimeByFilename(path, strlen(path)));
+                               CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
+
+                               char userconfigroomname[ROOMNAMELEN];
+                               struct ctdluser usbuf;
+
+                               if (CtdlGetUser(&usbuf, username) == 0) {       // no need to lock it , we are still initializing
+                                       long old_msgnum = usbuf.msgnum_pic;
+                                       CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &usbuf, USERCONFIGROOM);
+                                       long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, encoded_data, FMT_RFC822, "Photo imported from file");
+                                       syslog(LOG_DEBUG, "Message %ld is now the photo for %s", new_msgnum, username);
+                                       usbuf.msgnum_pic = new_msgnum;
+                                       CtdlPutUser(&usbuf);
+                                       unlink(path);                           // delete the old file , it's in the database now
+                                       if (old_msgnum > 0) {
+                                               syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
+                                               CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
+                                       }
+                               }
+                               free(encoded_data);
+                       }
+                       free(unencoded_data);
+               }
+       }
+       fclose(fp);
+}
+
+
+/*
+ * Look for old-format "userpic" files and import them into the message base
+ */
+void import_old_userpic_files(void) {
+       DIR *filedir = NULL;
+       struct dirent *filedir_entry;
+       size_t d_namelen;
+       struct ctdluser usbuf;
+       long usernum = 0;
+       int d_type = 0;
+       struct stat s;
+       char path[PATH_MAX];
+
+
+       syslog(LOG_DEBUG, "Importing old style userpic files into the message base");
+       filedir = opendir (ctdl_usrpic_dir);
+       if (filedir == NULL) {
+               return;
+       }
+       while ( (filedir_entry = readdir(filedir)) , (filedir_entry != NULL))
+       {
+#ifdef _DIRENT_HAVE_D_NAMLEN
+               d_namelen = filedir_entry->d_namlen;
+
+#else
+               d_namelen = strlen(filedir_entry->d_name);
+#endif
+
+#ifdef _DIRENT_HAVE_D_TYPE
+               d_type = filedir_entry->d_type;
+#else
+
+#ifndef DT_UNKNOWN
+#define DT_UNKNOWN     0
+#define DT_DIR         4
+#define DT_REG         8
+#define DT_LNK         10
+
+#define IFTODT(mode)   (((mode) & 0170000) >> 12)
+#define DTTOIF(dirtype)        ((dirtype) << 12)
+#endif
+               d_type = DT_UNKNOWN;
+#endif
+               if ((d_namelen == 1) && 
+                   (filedir_entry->d_name[0] == '.'))
+                       continue;
+
+               if ((d_namelen == 2) && 
+                   (filedir_entry->d_name[0] == '.') &&
+                   (filedir_entry->d_name[1] == '.'))
+                       continue;
+
+               snprintf(path, PATH_MAX, "%s/%s", ctdl_usrpic_dir, filedir_entry->d_name);
+               if (d_type == DT_UNKNOWN) {
+                       if (lstat(path, &s) == 0) {
+                               d_type = IFTODT(s.st_mode);
+                       }
+               }
+               switch (d_type)
+               {
+               case DT_DIR:
+                       break;
+               case DT_LNK:
+               case DT_REG:
+                       usernum = atol(filedir_entry->d_name);
+                       if (CtdlGetUserByNumber(&usbuf, usernum) == 0) {
+                               import_one_userpic_file(usbuf.fullname, usernum, path);
+                       }
+               }
+       }
+       closedir(filedir);
+       rmdir(ctdl_usrpic_dir);
+}
+
+
+
+char *ctdl_module_init_image(void) {
+       if (!threading) {
+               import_old_userpic_files();
+               CtdlRegisterProtoHook(cmd_dlri, "DLRI", "DownLoad Room Image");
+               CtdlRegisterProtoHook(cmd_ulri, "ULRI", "UpLoad Room Image");
+               CtdlRegisterProtoHook(cmd_dlui, "DLUI", "DownLoad User Image");
+               CtdlRegisterProtoHook(cmd_ului, "ULUI", "UpLoad User Image");
+       }
+       /* return our module name for the log */
+        return "image";
+}
diff --git a/citadel/server/modules/imap/.gitignore b/citadel/server/modules/imap/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/imap/imap_acl.c b/citadel/server/modules/imap/imap_acl.c
new file mode 100644 (file)
index 0000000..6c98f2e
--- /dev/null
@@ -0,0 +1,330 @@
+/*
+ * Functions which implement RFC2086 (and maybe RFC4314) (IMAP ACL extension)
+ *
+ * Copyright (c) 2007-2017 by the citadel.org team
+ *
+ *  This program is open source software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../sysdep_decls.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "imap_fetch.h"
+#include "imap_misc.h"
+#include "../../genstamp.h"
+#include "../../ctdl_module.h"
+
+
+/*
+ * Implements the SETACL command.
+ */
+void imap_setacl(int num_parms, ConstStr *Params) {
+
+       IReply("BAD not yet implemented FIXME");
+       return;
+}
+
+
+/*
+ * Implements the DELETEACL command.
+ */
+void imap_deleteacl(int num_parms, ConstStr *Params) {
+
+       IReply("BAD not yet implemented FIXME");
+       return;
+}
+
+
+/*
+ * Given the bits returned by CtdlRoomAccess(), populate a string buffer
+ * with IMAP ACL format flags.   This code is common to GETACL and MYRIGHTS.
+ */
+void imap_acl_flags(StrBuf *rights, int ra)
+{
+       FlushStrBuf(rights);
+
+       /* l - lookup (mailbox is visible to LIST/LSUB commands)
+        * r - read (SELECT the mailbox, perform STATUS et al)
+        * s - keep seen/unseen information across sessions (STORE SEEN flag)
+        */
+       if (    (ra & UA_KNOWN)                                 /* known rooms */
+          ||   ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED))     /* zapped rooms */
+       ) {
+               StrBufAppendBufPlain(rights, HKEY("l"), 0);
+               StrBufAppendBufPlain(rights, HKEY("r"), 0);
+               StrBufAppendBufPlain(rights, HKEY("s"), 0);
+
+               /* Only output the remaining flags if the room is known */
+
+               /* w - write (set or clear flags other than SEEN or DELETED, not supported in Citadel */
+
+               /* i - insert (perform APPEND, COPY into mailbox) */
+               /* p - post (send mail to submission address for mailbox - not enforced) */
+               /* c - create (CREATE new sub-mailboxes) */
+               if (ra & UA_POSTALLOWED) {
+                       StrBufAppendBufPlain(rights, HKEY("i"), 0);
+                       StrBufAppendBufPlain(rights, HKEY("p"), 0);
+                       StrBufAppendBufPlain(rights, HKEY("c"), 0);
+               }
+
+               /* d - delete messages (STORE DELETED flag, perform EXPUNGE) */
+               if (ra & UA_DELETEALLOWED) {
+                       StrBufAppendBufPlain(rights, HKEY("d"), 0);
+               }
+
+               /* a - administer (perform SETACL/DELETEACL/GETACL/LISTRIGHTS) */
+               if (ra & UA_ADMINALLOWED) {
+                       /*
+                        * This is the correct place to put the "a" flag.  We are leaving
+                        * it commented out for now, because it implies that we could
+                        * perform any of SETACL/DELETEACL/GETACL/LISTRIGHTS.  Since these
+                        * commands are not yet implemented, omitting the flag should
+                        * theoretically prevent compliant clients from attempting to
+                        * perform them.
+                        *
+                        * StrBufAppendBufPlain(rights, HKEY("a"), 0);
+                        */
+               }
+       }
+}
+
+
+/*
+ * Implements the GETACL command.
+ */
+void imap_getacl(int num_parms, ConstStr *Params) {
+       char roomname[ROOMNAMELEN];
+       char savedroom[ROOMNAMELEN];
+       int msgs, new;
+       int ret;
+       struct ctdluser temp;
+       struct cdbdata *cdbus;
+       int ra;
+       StrBuf *rights;
+
+       if (num_parms != 3) {
+               IReply("BAD usage error");
+               return;
+       }
+
+       /*
+        * Search for the specified room or folder
+        */
+       ret = imap_grabroom(roomname, Params[2].Key, 1);
+       if (ret != 0) {
+               IReply("NO Invalid mailbox name or access denied");
+               return;
+       }
+
+       /*
+        * CtdlUserGoto() formally takes us to the desired room.  (If another
+        * folder is selected, save its name so we can return there!!!!!)
+        */
+       if (IMAP->selected) {
+               strcpy(savedroom, CC->room.QRname);
+       }
+       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+       IAPuts("* ACL ");
+       IPutCParamStr(2);
+
+       /*
+        * Traverse the userlist
+        */
+       rights = NewStrBuf();
+       cdb_rewind(CDB_USERS);
+       while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) 
+       {
+               memset(&temp, 0, sizeof temp);
+               memcpy(&temp, cdbus->ptr, sizeof temp);
+               cdb_free(cdbus);
+
+               CtdlRoomAccess(&CC->room, &temp, &ra, NULL);
+               if (!IsEmptyStr(temp.fullname)) {
+                       imap_acl_flags(rights, ra);
+                       if (StrLength(rights) > 0) {
+                               IAPuts(" ");
+                               IPutStr(temp.fullname, strlen(temp.fullname));
+                               IAPuts(" ");
+                               iaputs(SKEY( rights));
+                       }
+               }
+       }
+       FreeStrBuf(&rights);
+       IAPuts("\r\n");
+
+       /*
+        * If another folder is selected, go back to that room so we can resume
+        * our happy day without violent explosions.
+        */
+       if (IMAP->selected) {
+               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+       }
+
+       IReply("OK GETACL completed");
+}
+
+
+/*
+ * Implements the LISTRIGHTS command.
+ */
+void imap_listrights(int num_parms, ConstStr *Params) {
+       char roomname[ROOMNAMELEN];
+       char savedroom[ROOMNAMELEN];
+       int msgs, new;
+       int ret;
+       struct recptypes *valid;
+       struct ctdluser temp;
+
+       if (num_parms != 4) {
+               IReply("BAD usage error");
+               return;
+       }
+
+       /*
+        * Search for the specified room/folder
+        */
+       ret = imap_grabroom(roomname, Params[2].Key, 1);
+       if (ret != 0) {
+               IReply("NO Invalid mailbox name or access denied");
+               return;
+       }
+
+       /*
+        * Search for the specified user
+        */
+       ret = (-1);
+       valid = validate_recipients(Params[3].Key, NULL, 0);
+       if (valid != NULL) {
+               if (valid->num_local == 1) {
+                       ret = CtdlGetUser(&temp, valid->recp_local);
+               }
+               free_recipients(valid);
+       }
+       if (ret != 0) {
+               IReply("NO Invalid user name or access denied");
+               return;
+       }
+
+       /*
+        * CtdlUserGoto() formally takes us to the desired room.  (If another
+        * folder is selected, save its name so we can return there!!!!!)
+        */
+       if (IMAP->selected) {
+               strcpy(savedroom, CC->room.QRname);
+       }
+       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+       /*
+        * Now output the list of rights
+        */
+       IAPuts("* LISTRIGHTS ");
+       IPutCParamStr(2);
+       IAPuts(" ");
+       IPutCParamStr(3);
+       IAPuts(" ");
+       IPutStr(HKEY(""));              /* FIXME ... do something here */
+       IAPuts("\r\n");
+
+       /*
+        * If another folder is selected, go back to that room so we can resume
+        * our happy day without violent explosions.
+        */
+       if (IMAP->selected) {
+               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+       }
+
+       IReply("OK LISTRIGHTS completed");
+       return;
+}
+
+
+/*
+ * Implements the MYRIGHTS command.
+ */
+void imap_myrights(int num_parms, ConstStr *Params) {
+       char roomname[ROOMNAMELEN];
+       char savedroom[ROOMNAMELEN];
+       int msgs, new;
+       int ret;
+       int ra;
+       StrBuf *rights;
+
+       if (num_parms != 3) {
+               IReply("BAD usage error");
+               return;
+       }
+
+       ret = imap_grabroom(roomname, Params[2].Key, 1);
+       if (ret != 0) {
+               IReply("NO Invalid mailbox name or access denied");
+               return;
+       }
+
+       /*
+        * CtdlUserGoto() formally takes us to the desired room.  (If another
+        * folder is selected, save its name so we can return there!!!!!)
+        */
+       if (IMAP->selected) {
+               strcpy(savedroom, CC->room.QRname);
+       }
+       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+       CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
+       rights = NewStrBuf();
+       imap_acl_flags(rights, ra);
+
+       IAPuts("* MYRIGHTS ");
+       IPutCParamStr(2);
+       IAPuts(" ");
+       IPutStr(SKEY(rights));
+       IAPuts("\r\n");
+
+       FreeStrBuf(&rights);
+
+       /*
+        * If a different folder was previously selected, return there now.
+        */
+       if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) {
+               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+       }
+
+       IReply("OK MYRIGHTS completed");
+       return;
+}
diff --git a/citadel/server/modules/imap/imap_acl.h b/citadel/server/modules/imap/imap_acl.h
new file mode 100644 (file)
index 0000000..a3b2605
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2007-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+void imap_setacl(int num_parms, ConstStr *Params);
+void imap_deleteacl(int num_parms, ConstStr *Params);
+void imap_getacl(int num_parms, ConstStr *Params);
+void imap_listrights(int num_parms, ConstStr *Params);
+void imap_myrights(int num_parms, ConstStr *Params);
+
diff --git a/citadel/server/modules/imap/imap_fetch.c b/citadel/server/modules/imap/imap_fetch.c
new file mode 100644 (file)
index 0000000..f2d37b3
--- /dev/null
@@ -0,0 +1,1463 @@
+/*
+ * Implements the FETCH command in IMAP.
+ * This is a good example of the protocol's gratuitous complexity.
+ *
+ * Copyright (c) 2001-2020 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../sysdep_decls.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "imap_fetch.h"
+#include "../../genstamp.h"
+#include "../../ctdl_module.h"
+
+
+
+/*
+ * Individual field functions for imap_do_fetch_msg() ...
+ */
+
+void imap_fetch_uid(int seq) {
+       IAPrintf("UID %ld", IMAP->msgids[seq-1]);
+}
+
+void imap_fetch_flags(int seq) 
+{
+       citimap *Imap = IMAP;
+       int num_flags_printed = 0;
+       IAPuts("FLAGS (");
+       if (Imap->flags[seq] & IMAP_DELETED) {
+               if (num_flags_printed > 0) 
+                       IAPuts(" ");
+               IAPuts("\\Deleted");
+               ++num_flags_printed;
+       }
+       if (Imap->flags[seq] & IMAP_SEEN) {
+               if (num_flags_printed > 0) 
+                       IAPuts(" ");
+               IAPuts("\\Seen");
+               ++num_flags_printed;
+       }
+       if (Imap->flags[seq] & IMAP_ANSWERED) {
+               if (num_flags_printed > 0) 
+                       IAPuts(" ");
+               IAPuts("\\Answered");
+               ++num_flags_printed;
+       }
+       if (Imap->flags[seq] & IMAP_RECENT) {
+               if (num_flags_printed > 0) 
+                       IAPuts(" ");
+               IAPuts("\\Recent");
+               ++num_flags_printed;
+       }
+       IAPuts(")");
+}
+
+
+void imap_fetch_internaldate(struct CtdlMessage *msg) {
+       char datebuf[64];
+       time_t msgdate;
+
+       if (!msg) return;
+       if (!CM_IsEmpty(msg, eTimestamp)) {
+               msgdate = atol(msg->cm_fields[eTimestamp]);
+       }
+       else {
+               msgdate = time(NULL);
+       }
+
+       datestring(datebuf, sizeof datebuf, msgdate, DATESTRING_IMAP);
+       IAPrintf( "INTERNALDATE \"%s\"", datebuf);
+}
+
+
+/*
+ * Fetch RFC822-formatted messages.
+ *
+ * 'whichfmt' should be set to one of:
+ *     "RFC822"        entire message
+ *     "RFC822.HEADER" headers only (with trailing blank line)
+ *     "RFC822.SIZE"   size of translated message
+ *     "RFC822.TEXT"   body only (without leading blank line)
+ */
+void imap_fetch_rfc822(long msgnum, const char *whichfmt) {
+       CitContext *CCC = CC;
+       citimap *Imap = CCCIMAP;
+       const char *ptr = NULL;
+       size_t headers_size, text_size, total_size;
+       size_t bytes_to_send = 0;
+       struct MetaData smi;
+       int need_to_rewrite_metadata = 0;
+       int need_body = 0;
+
+       /* Determine whether this particular fetch operation requires
+        * us to fetch the message body from disk.  If not, we can save
+        * on some disk operations...
+        */
+       if ( (!strcasecmp(whichfmt, "RFC822")) || (!strcasecmp(whichfmt, "RFC822.TEXT")) ) {
+               need_body = 1;
+       }
+
+       /* If this is an RFC822.SIZE fetch, first look in the message's
+        * metadata record to see if we've saved that information.
+        */
+       if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
+               GetMetaData(&smi, msgnum);
+               if (smi.meta_rfc822_length > 0L) {
+                       IAPrintf("RFC822.SIZE %ld", smi.meta_rfc822_length);
+                       return;
+               }
+               need_to_rewrite_metadata = 1;
+               need_body = 1;
+       }
+       
+       /* Cache the most recent RFC822 FETCH because some clients like to
+        * fetch in pieces, and we don't want to have to go back to the
+        * message store for each piece.  We also burn the cache if the
+        * client requests something that involves reading the message
+        * body, but we haven't fetched the body yet.
+        */
+       if ((Imap->cached_rfc822 != NULL)
+          && (Imap->cached_rfc822_msgnum == msgnum)
+          && (Imap->cached_rfc822_withbody || (!need_body)) ) {
+               /* Good to go! */
+       }
+       else if (Imap->cached_rfc822 != NULL) {
+               /* Some other message is cached -- free it */
+               FreeStrBuf(&Imap->cached_rfc822);
+               Imap->cached_rfc822_msgnum = (-1);
+       }
+
+       /* At this point, we now can fetch and convert the message iff it's not
+        * the one we had cached.
+        */
+       if (Imap->cached_rfc822 == NULL) {
+               /*
+                * Load the message into memory for translation & measurement
+                */
+               CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+               CtdlOutputMsg(msgnum, MT_RFC822,
+                       (need_body ? HEADERS_ALL : HEADERS_FAST),
+                       0, 1, NULL, SUPPRESS_ENV_TO, NULL, NULL, NULL
+               );
+               if (!need_body) {
+                       client_write(HKEY("\r\n"));             // extra trailing newline -- to the redirect_buffer, *not* to the client
+               }
+               Imap->cached_rfc822 = CCC->redirect_buffer;
+               CCC->redirect_buffer = NULL;
+               Imap->cached_rfc822_msgnum = msgnum;
+               Imap->cached_rfc822_withbody = need_body;
+               if ( (need_to_rewrite_metadata) && 
+                    (StrLength(Imap->cached_rfc822) > 0) ) {
+                       smi.meta_rfc822_length = StrLength(Imap->cached_rfc822);
+                       PutMetaData(&smi);
+               }
+       }
+
+       /*
+        * Now figure out where the headers/text break is.  IMAP considers the
+        * intervening blank line to be part of the headers, not the text.
+        */
+       headers_size = 0;
+
+       if (need_body) {
+               StrBuf *Line = NewStrBuf();
+               ptr = NULL;
+               do {
+                       StrBufSipLine(Line, Imap->cached_rfc822, &ptr);
+
+                       if ((StrLength(Line) != 0)  && (ptr != StrBufNOTNULL))
+                       {
+                               StrBufTrim(Line);
+                               if ((StrLength(Line) != 0) && 
+                                   (ptr != StrBufNOTNULL)    )
+                               {
+                                       headers_size = ptr - ChrPtr(Imap->cached_rfc822);
+                               }
+                       }
+               } while ( (headers_size == 0)    && 
+                         (ptr != StrBufNOTNULL) );
+
+               total_size = StrLength(Imap->cached_rfc822);
+               text_size = total_size - headers_size;
+               FreeStrBuf(&Line);
+       }
+       else {
+               headers_size = total_size = StrLength(Imap->cached_rfc822);
+               text_size = 0;
+       }
+
+       syslog(LOG_DEBUG, "imap: RFC822 headers=" SIZE_T_FMT ", text=" SIZE_T_FMT ", total=" SIZE_T_FMT, headers_size, text_size, total_size);
+
+       if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
+               IAPrintf("RFC822.SIZE " SIZE_T_FMT, total_size);
+               return;
+       }
+
+       else if (!strcasecmp(whichfmt, "RFC822")) {
+               ptr = ChrPtr(Imap->cached_rfc822);
+               bytes_to_send = total_size;
+       }
+
+       else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
+               ptr = ChrPtr(Imap->cached_rfc822);
+               bytes_to_send = headers_size;
+       }
+
+       else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
+               ptr = &ChrPtr(Imap->cached_rfc822)[headers_size];
+               bytes_to_send = text_size;
+       }
+
+       IAPrintf("%s {" SIZE_T_FMT "}\r\n", whichfmt, bytes_to_send);
+       iaputs(ptr, bytes_to_send);
+}
+
+
+/*
+ * Load a specific part of a message into the temp file to be output to a
+ * client.  FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
+ * but we still can't handle "2.HEADER" (which might not be a problem).
+ *
+ * Note: mime_parser() was called with dont_decode set to 1, so we have the
+ * luxury of simply spewing without having to re-encode.
+ */
+void imap_load_part(char *name, char *filename, char *partnum, char *disp,
+                   void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+                   char *cbid, void *cbuserdata)
+{
+       char mimebuf2[SIZ];
+       StrBuf *desired_section;
+
+       desired_section = (StrBuf *)cbuserdata;
+       syslog(LOG_DEBUG, "imap: imap_load_part() looking for %s, found %s", ChrPtr(desired_section), partnum);
+
+       if (!strcasecmp(partnum, ChrPtr(desired_section))) {
+               client_write(content, length);
+       }
+
+       snprintf(mimebuf2, sizeof mimebuf2, "%s.MIME", partnum);
+
+       if (!strcasecmp(ChrPtr(desired_section), mimebuf2)) {
+               client_write(HKEY("Content-type: "));
+               client_write(cbtype, strlen(cbtype));
+               if (!IsEmptyStr(cbcharset)) {
+                       client_write(HKEY("; charset=\""));
+                       client_write(cbcharset, strlen(cbcharset));
+                       client_write(HKEY("\""));
+               }
+               if (!IsEmptyStr(name)) {
+                       client_write(HKEY("; name=\""));
+                       client_write(name, strlen(name));
+                       client_write(HKEY("\""));
+               }
+               client_write(HKEY("\r\n"));
+               if (!IsEmptyStr(encoding)) {
+                       client_write(HKEY("Content-Transfer-Encoding: "));
+                       client_write(encoding, strlen(encoding));
+                       client_write(HKEY("\r\n"));
+               }
+               if (!IsEmptyStr(encoding)) {
+                       client_write(HKEY("Content-Disposition: "));
+                       client_write(disp, strlen(disp));
+               
+                       if (!IsEmptyStr(filename)) {
+                               client_write(HKEY("; filename=\""));
+                               client_write(filename, strlen(filename));
+                               client_write(HKEY("\""));
+                       }
+                       client_write(HKEY("\r\n"));
+               }
+               cprintf("Content-Length: %ld\r\n\r\n", (long)length);
+       }
+}
+
+
+/* 
+ * Called by imap_fetch_envelope() to output the "From" field.
+ * This is in its own function because its logic is kind of complex.  We
+ * really need to make this suck less.
+ */
+void imap_output_envelope_from(struct CtdlMessage *msg) {
+       char user[SIZ], node[SIZ], name[SIZ];
+
+       if (!msg) return;
+
+       /* For anonymous messages, it's so easy! */
+       if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
+               IAPuts("((\"----\" NIL \"x\" \"x.org\")) ");
+               return;
+       }
+       if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
+               IAPuts("((\"anonymous\" NIL \"x\" \"x.org\")) ");
+               return;
+       }
+
+       /* For everything else, we do stuff. */
+       IAPuts("((");                           // open double-parens
+       IPutMsgField(eAuthor);                  // display name
+       IAPuts(" NIL ");                        // source route (not used)
+
+       if (!CM_IsEmpty(msg, erFc822Addr)) {
+               process_rfc822_addr(msg->cm_fields[erFc822Addr], user, node, name);
+               IPutStr(user, strlen(user));                    /* mailbox name (user id) */
+               IAPuts(" ");
+               IPutStr(node, strlen(node));                    /* host name */
+       }
+       else {
+               IPutMsgField(eAuthor);                          /* Make up a synthetic address */
+               IAPuts(" ");
+               IPutStr(CtdlGetConfigStr("c_fqdn"), strlen(CtdlGetConfigStr("c_fqdn")));
+       }
+       
+       IAPuts(")) "); /* close double-parens */
+}
+
+
+/*
+ * Output an envelope address (or set of addresses) in the official,
+ * convoluted, braindead format.  (Note that we can't use this for
+ * the "From" address because its data may come from a number of different
+ * fields.  But we can use it for "To" and possibly others.
+ */
+void imap_output_envelope_addr(char *addr) {
+       char individual_addr[256];
+       int num_addrs;
+       int i;
+       char user[256];
+       char node[256];
+       char name[256];
+
+       if (addr == NULL) {
+               IAPuts("NIL ");
+               return;
+       }
+
+       if (IsEmptyStr(addr)) {
+               IAPuts("NIL ");
+               return;
+       }
+
+       IAPuts("(");
+
+       /* How many addresses are listed here? */
+       num_addrs = num_tokens(addr, ',');
+
+       /* Output them one by one. */
+       for (i=0; i<num_addrs; ++i) {
+               extract_token(individual_addr, addr, i, ',', sizeof individual_addr);
+               striplt(individual_addr);
+               process_rfc822_addr(individual_addr, user, node, name);
+               IAPuts("(");
+               IPutStr(name, strlen(name));
+               IAPuts(" NIL ");
+               IPutStr(user, strlen(user));
+               IAPuts(" ");
+               IPutStr(node, strlen(node));
+               IAPuts(")");
+               if (i < (num_addrs-1)) 
+                       IAPuts(" ");
+       }
+
+       IAPuts(") ");
+}
+
+
+/*
+ * Implements the ENVELOPE fetch item
+ * 
+ * Note that the imap_strout() function can cleverly output NULL fields as NIL,
+ * so we don't have to check for that condition like we do elsewhere.
+ */
+void imap_fetch_envelope(struct CtdlMessage *msg) {
+       char datestringbuf[SIZ];
+       time_t msgdate;
+       char *fieldptr = NULL;
+       long len;
+
+       if (!msg) return;
+
+       /* Parse the message date into an IMAP-format date string */
+       if (!CM_IsEmpty(msg, eTimestamp)) {
+               msgdate = atol(msg->cm_fields[eTimestamp]);
+       }
+       else {
+               msgdate = time(NULL);
+       }
+       len = datestring(datestringbuf, sizeof datestringbuf, msgdate, DATESTRING_IMAP);
+
+       /* Now start spewing data fields.  The order is important, as it is
+        * defined by the protocol specification.  Nonexistent fields must
+        * be output as NIL, existent fields must be quoted or literalled.
+        * The imap_strout() function conveniently does all this for us.
+        */
+       IAPuts("ENVELOPE (");
+
+       /* Date */
+       IPutStr(datestringbuf, len);
+       IAPuts(" ");
+
+       /* Subject */
+       IPutMsgField(eMsgSubject);
+       IAPuts(" ");
+
+       /* From */
+       imap_output_envelope_from(msg);
+
+       /* Sender (default to same as 'From' if not present) */
+       fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Sender");
+       if (fieldptr != NULL) {
+               imap_output_envelope_addr(fieldptr);
+               free(fieldptr);
+       }
+       else {
+               imap_output_envelope_from(msg);
+       }
+
+       /* Reply-to */
+       fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Reply-to");
+       if (fieldptr != NULL) {
+               imap_output_envelope_addr(fieldptr);
+               free(fieldptr);
+       }
+       else {
+               imap_output_envelope_from(msg);
+       }
+
+       /* To */
+       imap_output_envelope_addr(msg->cm_fields[eRecipient]);
+
+       /* Cc (we do it this way because there might be a legacy non-Citadel Cc: field present) */
+       fieldptr = msg->cm_fields[eCarbonCopY];
+       if (fieldptr != NULL) {
+               imap_output_envelope_addr(fieldptr);
+       }
+       else {
+               fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Cc");
+               imap_output_envelope_addr(fieldptr);
+               if (fieldptr != NULL) free(fieldptr);
+       }
+
+       /* Bcc */
+       fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Bcc");
+       imap_output_envelope_addr(fieldptr);
+       if (fieldptr != NULL) free(fieldptr);
+
+       /* In-reply-to */
+       fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "In-reply-to");
+       IPutStr(fieldptr, (fieldptr)?strlen(fieldptr):0);
+       IAPuts(" ");
+       if (fieldptr != NULL) free(fieldptr);
+
+       /* message ID */
+       len = msg->cm_lengths[emessageId];
+       
+       if ((len == 0) || (
+               (msg->cm_fields[emessageId][0] == '<') && 
+               (msg->cm_fields[emessageId][len - 1] == '>'))
+       ) {
+               IPutMsgField(emessageId);
+       }
+       else 
+       {
+               char *Buf = malloc(len + 3);
+               long pos = 0;
+               
+               if (msg->cm_fields[emessageId][0] != '<')
+               {
+                       Buf[pos] = '<';
+                       pos ++;
+               }
+               memcpy(&Buf[pos], msg->cm_fields[emessageId], len);
+               pos += len;
+               if (msg->cm_fields[emessageId][len] != '>')
+               {
+                       Buf[pos] = '>';
+                       pos++;
+               }
+               Buf[pos] = '\0';
+               IPutStr(Buf, pos);
+               free(Buf);
+       }
+       IAPuts(")");
+}
+
+
+/*
+ * This function is called only when CC->redirect_buffer contains a set of
+ * RFC822 headers with no body attached.  Its job is to strip that set of
+ * headers down to *only* the ones we're interested in.
+ */
+void imap_strip_headers(StrBuf *section) {
+       citimap_command Cmd;
+       StrBuf *which_fields = NULL;
+       int doing_headers = 0;
+       int headers_not = 0;
+       int num_parms = 0;
+       int i;
+       StrBuf *boiled_headers = NULL;
+       StrBuf *Line;
+       int ok = 0;
+       int done_headers = 0;
+       const char *Ptr = NULL;
+       CitContext *CCC = CC;
+
+       if (CCC->redirect_buffer == NULL) return;
+
+       which_fields = NewStrBufDup(section);
+
+       if (!strncasecmp(ChrPtr(which_fields), "HEADER.FIELDS", 13))
+               doing_headers = 1;
+       if (doing_headers && 
+           !strncasecmp(ChrPtr(which_fields), "HEADER.FIELDS.NOT", 17))
+               headers_not = 1;
+
+       for (i=0; i < StrLength(which_fields); ++i) {
+               if (ChrPtr(which_fields)[i]=='(')
+                       StrBufReplaceToken(which_fields, i, 1, HKEY(""));
+       }
+       for (i=0; i < StrLength(which_fields); ++i) {
+               if (ChrPtr(which_fields)[i]==')') {
+                       StrBufCutAt(which_fields, i, NULL);
+                       break;
+               }
+       }
+       memset(&Cmd, 0, sizeof(citimap_command));
+       Cmd.CmdBuf = which_fields;
+       num_parms = imap_parameterize(&Cmd);
+
+       boiled_headers = NewStrBufPlain(NULL, StrLength(CCC->redirect_buffer));
+       Line = NewStrBufPlain(NULL, SIZ);
+       Ptr = NULL;
+       ok = 0;
+       do {
+               StrBufSipLine(Line, CCC->redirect_buffer, &Ptr);
+
+               if (!isspace(ChrPtr(Line)[0])) {
+
+                       if (doing_headers == 0) ok = 1;
+                       else {
+                               /* we're supposed to print all headers that are not matching the filter list */
+                               if (headers_not) for (i=0, ok = 1; (i < num_parms) && (ok == 1); ++i) {
+                                               if ( (!strncasecmp(ChrPtr(Line), 
+                                                                  Cmd.Params[i].Key,
+                                                                  Cmd.Params[i].len)) &&
+                                                    (ChrPtr(Line)[Cmd.Params[i].len]==':') ) {
+                                                       ok = 0;
+                                               }
+                               }
+                               /* we're supposed to print all headers matching the filterlist */
+                               else for (i=0, ok = 0; ((i < num_parms) && (ok == 0)); ++i) {
+                                               if ( (!strncasecmp(ChrPtr(Line), 
+                                                                  Cmd.Params[i].Key,
+                                                                  Cmd.Params[i].len)) &&
+                                                    (ChrPtr(Line)[Cmd.Params[i].len]==':') ) {
+                                                       ok = 1;
+                                       }
+                               }
+                       }
+               }
+
+               if (ok) {
+                       StrBufAppendBuf(boiled_headers, Line, 0);
+                       StrBufAppendBufPlain(boiled_headers, HKEY("\r\n"), 0);
+               }
+
+               if ((Ptr == StrBufNOTNULL)  ||
+                   (StrLength(Line) == 0)  ||
+                   (ChrPtr(Line)[0]=='\r') ||
+                   (ChrPtr(Line)[0]=='\n')   ) done_headers = 1;
+       } while (!done_headers);
+
+       StrBufAppendBufPlain(boiled_headers, HKEY("\r\n"), 0);
+
+       /* Now save it back (it'll always be smaller) */
+       FreeStrBuf(&CCC->redirect_buffer);
+       CCC->redirect_buffer = boiled_headers;
+
+       free(Cmd.Params);
+       FreeStrBuf(&which_fields);
+       FreeStrBuf(&Line);
+}
+
+
+/*
+ * Implements the BODY and BODY.PEEK fetch items
+ */
+void imap_fetch_body(long msgnum, ConstStr item, int is_peek) {
+       struct CtdlMessage *msg = NULL;
+       StrBuf *section;
+       StrBuf *partial;
+       int is_partial = 0;
+       size_t pstart, pbytes;
+       int loading_body_now = 0;
+       int need_body = 1;
+       int burn_the_cache = 0;
+       CitContext *CCC = CC;
+       citimap *Imap = CCCIMAP;
+
+       /* extract section */
+       section = NewStrBufPlain(CKEY(item));
+       
+       if (strchr(ChrPtr(section), '[') != NULL) {
+               StrBufStripAllBut(section, '[', ']');
+       }
+       syslog(LOG_DEBUG, "imap: selected section is [%s]", (StrLength(section) == 0) ? "(empty)" : ChrPtr(section));
+
+       /* Burn the cache if we don't have the same section of the 
+        * same message again.
+        */
+       if (Imap->cached_body != NULL) {
+               if (Imap->cached_bodymsgnum != msgnum) {
+                       burn_the_cache = 1;
+               }
+               else if ( (!Imap->cached_body_withbody) && (need_body) ) {
+                       burn_the_cache = 1;
+               }
+               else if (strcasecmp(Imap->cached_bodypart, ChrPtr(section))) {
+                       burn_the_cache = 1;
+               }
+               if (burn_the_cache) {
+                       /* Yup, go ahead and burn the cache. */
+                       free(Imap->cached_body);
+                       Imap->cached_body_len = 0;
+                       Imap->cached_body = NULL;
+                       Imap->cached_bodymsgnum = (-1);
+                       strcpy(Imap->cached_bodypart, "");
+               }
+       }
+
+       /* extract partial */
+       partial = NewStrBufPlain(CKEY(item));
+       if (strchr(ChrPtr(partial), '<') != NULL) {
+               StrBufStripAllBut(partial, '<', '>');
+               is_partial = 1;
+       }
+       if ( (is_partial == 1) && (StrLength(partial) > 0) ) {
+               syslog(LOG_DEBUG, "imap: selected partial is <%s>", ChrPtr(partial));
+       }
+
+       if (Imap->cached_body == NULL) {
+               CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+               loading_body_now = 1;
+               msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
+       }
+
+       /* Now figure out what the client wants, and get it */
+
+       if (!loading_body_now) {
+               /* What we want is already in memory */
+       }
+
+       else if ( (!strcmp(ChrPtr(section), "1")) && (msg->cm_format_type != 4) ) {
+               CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, SUPPRESS_ENV_TO);
+       }
+
+       else if (StrLength(section) == 0) {
+               CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, SUPPRESS_ENV_TO);
+       }
+
+       /*
+        * If the client asked for just headers, or just particular header
+        * fields, strip it down.
+        */
+       else if (!strncasecmp(ChrPtr(section), "HEADER", 6)) {
+               /* This used to work with HEADERS_FAST, but then Apple got stupid with their
+                * IMAP library and this broke Mail.App and iPhone Mail, so we had to change it
+                * to HEADERS_ONLY so the trendy hipsters with their iPhones can read mail.
+                */
+               CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1, SUPPRESS_ENV_TO);
+               imap_strip_headers(section);
+       }
+
+       /*
+        * Strip it down if the client asked for everything _except_ headers.
+        */
+       else if (!strncasecmp(ChrPtr(section), "TEXT", 4)) {
+               CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, SUPPRESS_ENV_TO);
+       }
+
+       /*
+        * Anything else must be a part specifier.
+        * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
+        */
+       else {
+               mime_parser(CM_RANGE(msg, eMesageText),
+                           *imap_load_part, NULL, NULL,
+                           section,
+                           1
+                       );
+       }
+
+       if (loading_body_now) {
+               Imap->cached_body_len = StrLength(CCC->redirect_buffer);
+               Imap->cached_body = SmashStrBuf(&CCC->redirect_buffer);
+               Imap->cached_bodymsgnum = msgnum;
+               Imap->cached_body_withbody = need_body;
+               strcpy(Imap->cached_bodypart, ChrPtr(section));
+       }
+
+       if (is_partial == 0) {
+               IAPuts("BODY[");
+               iaputs(SKEY(section));
+               IAPrintf("] {" SIZE_T_FMT "}\r\n", Imap->cached_body_len);
+               pstart = 0;
+               pbytes = Imap->cached_body_len;
+       }
+       else {
+               sscanf(ChrPtr(partial), SIZE_T_FMT "." SIZE_T_FMT, &pstart, &pbytes);
+               if (pbytes > (Imap->cached_body_len - pstart)) {
+                       pbytes = Imap->cached_body_len - pstart;
+               }
+               IAPuts("BODY[");
+               iaputs(SKEY(section));
+               IAPrintf("]<" SIZE_T_FMT "> {" SIZE_T_FMT "}\r\n", pstart, pbytes);
+       }
+
+       FreeStrBuf(&partial);
+
+       /* Here we go -- output it */
+       iaputs(&Imap->cached_body[pstart], pbytes);
+
+       if (msg != NULL) {
+               CM_Free(msg);
+       }
+
+       /* Mark this message as "seen" *unless* this is a "peek" operation */
+       if (is_peek == 0) {
+               CtdlSetSeen(&msgnum, 1, 1, ctdlsetseen_seen, NULL, NULL);
+       }
+       FreeStrBuf(&section);
+}
+
+/*
+ * Called immediately before outputting a multipart bodystructure
+ */
+void imap_fetch_bodystructure_pre(
+               char *name, char *filename, char *partnum, char *disp,
+               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+               char *cbid, void *cbuserdata
+               ) {
+
+       IAPuts("(");
+}
+
+
+
+/*
+ * Called immediately after outputting a multipart bodystructure
+ */
+void imap_fetch_bodystructure_post(
+               char *name, char *filename, char *partnum, char *disp,
+               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+               char *cbid, void *cbuserdata
+               ) {
+       long len;
+       char subtype[128];
+
+       IAPuts(" ");
+
+       /* disposition */
+       len = extract_token(subtype, cbtype, 1, '/', sizeof subtype);
+       IPutStr(subtype, len);
+
+       /* body language */
+       /* IAPuts(" NIL"); We thought we needed this at one point, but maybe we don't... */
+
+       IAPuts(")");
+}
+
+
+
+/*
+ * Output the info for a MIME part in the format required by BODYSTRUCTURE.
+ *
+ */
+void imap_fetch_bodystructure_part(
+               char *name, char *filename, char *partnum, char *disp,
+               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+               char *cbid, void *cbuserdata
+               ) {
+
+       int have_cbtype = 0;
+       int have_encoding = 0;
+       int lines = 0;
+       size_t i;
+       char cbmaintype[128];
+       char cbsubtype[128];
+       long cbmaintype_len;
+       long cbsubtype_len;
+
+       if (cbtype != NULL) if (!IsEmptyStr(cbtype)) have_cbtype = 1;
+       if (have_cbtype) {
+               cbmaintype_len = extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
+               cbsubtype_len = extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
+       }
+       else {
+               strcpy(cbmaintype, "TEXT");
+               cbmaintype_len = 4;
+               strcpy(cbsubtype, "PLAIN");
+               cbsubtype_len = 5;
+       }
+
+       IAPuts("(");
+       IPutStr(cbmaintype, cbmaintype_len);                    /* body type */
+       IAPuts(" ");
+       IPutStr(cbsubtype, cbsubtype_len);                      /* body subtype */
+       IAPuts(" ");
+
+       IAPuts("(");                                            /* begin body parameter list */
+
+       /* "NAME" must appear as the first parameter.  This is not required by IMAP,
+        * but the Asterisk voicemail application blindly assumes that NAME will be in
+        * the first position.  If it isn't, it rejects the message.
+        */
+       if ((name != NULL) && (!IsEmptyStr(name))) {
+               IAPuts("\"NAME\" ");
+               IPutStr(name, strlen(name));
+               IAPuts(" ");
+       }
+
+       IAPuts("\"CHARSET\" ");
+       if ((cbcharset == NULL) || (cbcharset[0] == 0)){
+               IPutStr(HKEY("US-ASCII"));
+       }
+       else {
+               IPutStr(cbcharset, strlen(cbcharset));
+       }
+       IAPuts(") ");                                           /* end body parameter list */
+
+       IAPuts("NIL ");                                         /* Body ID */
+       IAPuts("NIL ");                                         /* Body description */
+
+       if ((encoding != NULL) && (encoding[0] != 0))  have_encoding = 1;
+       if (have_encoding) {
+               IPutStr(encoding, strlen(encoding));
+       }
+       else {
+               IPutStr(HKEY("7BIT"));
+       }
+       IAPuts(" ");
+
+       /* The next field is the size of the part in bytes. */
+       IAPrintf("%ld ", (long)length); /* bytes */
+
+       /* The next field is the number of lines in the part, if and only
+        * if the part is TEXT.  More gratuitous complexity.
+        */
+       if (!strcasecmp(cbmaintype, "TEXT")) {
+               if (length) for (i=0; i<length; ++i) {
+                       if (((char *)content)[i] == '\n') ++lines;
+               }
+               IAPrintf("%d ", lines);
+       }
+
+       /* More gratuitous complexity */
+       if ((!strcasecmp(cbmaintype, "MESSAGE"))
+          && (!strcasecmp(cbsubtype, "RFC822"))) {
+               /* FIXME: message/rfc822 also needs to output the envelope structure,
+                * body structure, and line count of the encapsulated message.  Fortunately
+                * there are not yet any clients depending on this, so we can get away
+                * with not implementing it for now.
+                */
+       }
+
+       /* MD5 value of body part; we can get away with NIL'ing this */
+       IAPuts("NIL ");
+
+       /* Disposition */
+       if ((disp == NULL) || IsEmptyStr(disp)) {
+               IAPuts("NIL");
+       }
+       else {
+               IAPuts("(");
+               IPutStr(disp, strlen(disp));
+               if ((filename != NULL) && (!IsEmptyStr(filename))) {
+                       IAPuts(" (\"FILENAME\" ");
+                       IPutStr(filename, strlen(filename));
+                       IAPuts(")");
+               }
+               IAPuts(")");
+       }
+
+       /* Body language (not defined yet) */
+       IAPuts(" NIL)");
+}
+
+
+
+/*
+ * Spew the BODYSTRUCTURE data for a message.
+ *
+ */
+void imap_fetch_bodystructure (long msgnum, const char *item,
+               struct CtdlMessage *msg) {
+       const char *rfc822 = NULL;
+       const char *rfc822_body = NULL;
+       size_t rfc822_len;
+       size_t rfc822_headers_len;
+       size_t rfc822_body_len;
+       const char *ptr = NULL;
+       char *pch;
+       char buf[SIZ];
+       int lines = 0;
+
+       /* Handle NULL message gracefully */
+       if (msg == NULL) {
+               IAPuts("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
+                       "(\"CHARSET\" \"US-ASCII\") NIL NIL "
+                       "\"7BIT\" 0 0)");
+               return;
+       }
+
+       /* For non-RFC822 (ordinary Citadel) messages, this is short and
+        * sweet...
+        */
+       if (msg->cm_format_type != FMT_RFC822) {
+
+               /* *sigh* We have to RFC822-format the message just to be able
+                * to measure it.  FIXME use smi cached fields if possible
+                */
+
+               CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+               CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1, SUPPRESS_ENV_TO);
+               rfc822_len = StrLength(CC->redirect_buffer);
+               rfc822 = pch = SmashStrBuf(&CC->redirect_buffer);
+
+               ptr = rfc822;
+               do {
+                       ptr = cmemreadline(ptr, buf, sizeof buf);
+                       ++lines;
+                       if ((IsEmptyStr(buf)) && (rfc822_body == NULL)) {
+                               rfc822_body = ptr;
+                       }
+               } while (*ptr != 0);
+
+               rfc822_headers_len = rfc822_body - rfc822;
+               rfc822_body_len = rfc822_len - rfc822_headers_len;
+               free(pch);
+
+               IAPuts("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
+                      "(\"CHARSET\" \"US-ASCII\") NIL NIL "
+                      "\"7BIT\" ");
+               IAPrintf(SIZE_T_FMT " %d)", rfc822_body_len, lines);
+
+               return;
+       }
+
+       /* For messages already stored in RFC822 format, we have to parse. */
+       IAPuts("BODYSTRUCTURE ");
+       mime_parser(CM_RANGE(msg, eMesageText),
+                   *imap_fetch_bodystructure_part,     /* part */
+                   *imap_fetch_bodystructure_pre,      /* pre-multi */
+                   *imap_fetch_bodystructure_post,     /* post-multi */
+                   NULL,
+                   1); /* don't decode -- we want it as-is */
+}
+
+
+/*
+ * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
+ * individual message, once it has been selected for output.
+ */
+void imap_do_fetch_msg(int seq, citimap_command *Cmd) {
+       int i;
+       citimap *Imap = IMAP;
+       struct CtdlMessage *msg = NULL;
+       int body_loaded = 0;
+
+       /* Don't attempt to fetch bogus messages or UID's */
+       if (seq < 1) return;
+       if (Imap->msgids[seq-1] < 1L) return;
+
+       buffer_output();
+       IAPrintf("* %d FETCH (", seq);
+
+       for (i=0; i<Cmd->num_parms; ++i) {
+
+               /* Fetchable without going to the message store at all */
+               if (!strcasecmp(Cmd->Params[i].Key, "UID")) {
+                       imap_fetch_uid(seq);
+               }
+               else if (!strcasecmp(Cmd->Params[i].Key, "FLAGS")) {
+                       imap_fetch_flags(seq-1);
+               }
+
+               /* Potentially fetchable from cache, if the client requests
+                * stuff from the same message several times in a row.
+                */
+               else if (!strcasecmp(Cmd->Params[i].Key, "RFC822")) {
+                       imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
+               }
+               else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.HEADER")) {
+                       imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
+               }
+               else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.SIZE")) {
+                       imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
+               }
+               else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.TEXT")) {
+                       imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
+               }
+
+               /* BODY fetches do their own fetching and caching too. */
+               else if (!strncasecmp(Cmd->Params[i].Key, "BODY[", 5)) {
+                       imap_fetch_body(Imap->msgids[seq-1], Cmd->Params[i], 0);
+               }
+               else if (!strncasecmp(Cmd->Params[i].Key, "BODY.PEEK[", 10)) {
+                       imap_fetch_body(Imap->msgids[seq-1], Cmd->Params[i], 1);
+               }
+
+               /* Otherwise, load the message into memory.
+                */
+               else if (!strcasecmp(Cmd->Params[i].Key, "BODYSTRUCTURE")) {
+                       if ((msg != NULL) && (!body_loaded)) {
+                               CM_Free(msg);   /* need the whole thing */
+                               msg = NULL;
+                       }
+                       if (msg == NULL) {
+                               msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                               body_loaded = 1;
+                       }
+                       imap_fetch_bodystructure(Imap->msgids[seq-1],
+                                       Cmd->Params[i].Key, msg);
+               }
+               else if (!strcasecmp(Cmd->Params[i].Key, "ENVELOPE")) {
+                       if (msg == NULL) {
+                               msg = CtdlFetchMessage(Imap->msgids[seq-1], 0);
+                               body_loaded = 0;
+                       }
+                       imap_fetch_envelope(msg);
+               }
+               else if (!strcasecmp(Cmd->Params[i].Key, "INTERNALDATE")) {
+                       if (msg == NULL) {
+                               msg = CtdlFetchMessage(Imap->msgids[seq-1], 0);
+                               body_loaded = 0;
+                       }
+                       imap_fetch_internaldate(msg);
+               }
+
+               if (i != Cmd->num_parms-1) IAPuts(" ");
+       }
+
+       IAPuts(")\r\n");
+       unbuffer_output();
+       if (msg != NULL) {
+               CM_Free(msg);
+       }
+}
+
+
+
+/*
+ * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
+ * validated and boiled down the request a bit.
+ */
+void imap_do_fetch(citimap_command *Cmd) {
+       citimap *Imap = IMAP;
+       int i;
+#if 0
+/* debug output the parsed vector */
+       {
+               int i;
+               syslog(LOG_DEBUG, "imap: ----- %ld params", Cmd->num_parms);
+
+       for (i=0; i < Cmd->num_parms; i++) {
+               if (Cmd->Params[i].len != strlen(Cmd->Params[i].Key))
+                       syslog(LOG_DEBUG, "imap: *********** %ld != %ld : %s",
+                                   Cmd->Params[i].len, 
+                                   strlen(Cmd->Params[i].Key),
+                                   Cmd->Params[i].Key);
+               else
+                       syslog(LOG_DEBUG, "imap: %ld : %s",
+                                   Cmd->Params[i].len, 
+                                   Cmd->Params[i].Key);
+       }}
+
+#endif
+
+       if (Imap->num_msgs > 0) {
+               for (i = 0; i < Imap->num_msgs; ++i) {
+
+                       /* Abort the fetch loop if the session breaks.
+                        * This is important for users who keep mailboxes
+                        * that are too big *and* are too impatient to
+                        * let them finish loading.  :)
+                        */
+                       if (CC->kill_me) return;
+
+                       /* Get any message marked for fetch. */
+                       if (Imap->flags[i] & IMAP_SELECTED) {
+                               imap_do_fetch_msg(i+1, Cmd);
+                       }
+               }
+       }
+}
+
+
+
+/*
+ * Back end for imap_handle_macros()
+ * Note that this function *only* looks at the beginning of the string.  It
+ * is not a generic search-and-replace function.
+ */
+void imap_macro_replace(StrBuf *Buf, long where, 
+                       StrBuf *TmpBuf,
+                       char *find, long findlen, 
+                       char *replace, long replacelen) 
+{
+
+       if (StrLength(Buf) - where > findlen)
+               return;
+
+       if (!strncasecmp(ChrPtr(Buf) + where, find, findlen)) {
+               if (ChrPtr(Buf)[where + findlen] == ' ') {
+                       StrBufPlain(TmpBuf, replace, replacelen);
+                       StrBufAppendBufPlain(TmpBuf, HKEY(" "), 0);
+                       StrBufReplaceToken(Buf, where, findlen, 
+                                          SKEY(TmpBuf));
+               }
+               if (where + findlen == StrLength(Buf)) {
+                       StrBufReplaceToken(Buf, where, findlen, 
+                                          replace, replacelen);
+               }
+       }
+}
+
+
+
+/*
+ * Handle macros embedded in FETCH data items.
+ * (What the heck are macros doing in a wire protocol?  Are we trying to save
+ * the computer at the other end the trouble of typing a lot of characters?)
+ */
+void imap_handle_macros(citimap_command *Cmd) {
+       long i;
+       int nest = 0;
+       StrBuf *Tmp = NewStrBuf();
+
+       for (i=0; i < StrLength(Cmd->CmdBuf); ++i) {
+               char ch = ChrPtr(Cmd->CmdBuf)[i];
+               if ((ch=='(') ||
+                   (ch=='[') ||
+                   (ch=='<') ||
+                   (ch=='{')) ++nest;
+               else if ((ch==')') ||
+                        (ch==']') ||
+                        (ch=='>') ||
+                        (ch=='}')) --nest;
+
+               if (nest <= 0) {
+                       imap_macro_replace(Cmd->CmdBuf, i,
+                                          Tmp, 
+                                          HKEY("ALL"),
+                                          HKEY("FLAGS INTERNALDATE RFC822.SIZE ENVELOPE")
+                       );
+                       imap_macro_replace(Cmd->CmdBuf, i,
+                                          Tmp, 
+                                          HKEY("BODY"),
+                                          HKEY("BODYSTRUCTURE")
+                       );
+                       imap_macro_replace(Cmd->CmdBuf, i,
+                                          Tmp, 
+                                          HKEY("FAST"),
+                                          HKEY("FLAGS INTERNALDATE RFC822.SIZE")
+                       );
+                       imap_macro_replace(Cmd->CmdBuf, i,
+                                          Tmp, 
+                                          HKEY("FULL"),
+                                          HKEY("FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY")
+                       );
+               }
+       }
+       FreeStrBuf(&Tmp);
+}
+
+
+/*
+ * Break out the data items requested, possibly a parenthesized list.
+ * Returns the number of data items, or -1 if the list is invalid.
+ * NOTE: this function alters the string it is fed, and uses it as a buffer
+ * to hold the data for the pointers it returns.
+ */
+int imap_extract_data_items(citimap_command *Cmd) 
+{
+       int nArgs;
+       int nest = 0;
+       const char *pch, *end;
+
+       /* Convert all whitespace to ordinary space characters. */
+       pch = ChrPtr(Cmd->CmdBuf);
+       end = pch + StrLength(Cmd->CmdBuf);
+
+       while (pch < end)
+       {
+               if (isspace(*pch)) 
+                       StrBufPeek(Cmd->CmdBuf, pch, 0, ' ');
+               pch++;
+       }
+
+       /* Strip leading and trailing whitespace, then strip leading and
+        * trailing parentheses if it's a list
+        */
+       StrBufTrim(Cmd->CmdBuf);
+       pch = ChrPtr(Cmd->CmdBuf);
+       if ( (pch[0]=='(') && 
+            (pch[StrLength(Cmd->CmdBuf)-1]==')') ) 
+       {
+               StrBufCutRight(Cmd->CmdBuf, 1);
+               StrBufCutLeft(Cmd->CmdBuf, 1);
+               StrBufTrim(Cmd->CmdBuf);
+       }
+
+       /* Parse any macro data items */
+       imap_handle_macros(Cmd);
+
+       /*
+        * Now break out the data items.  We throw in one trailing space in
+        * order to avoid having to break out the last one manually.
+        */
+       nArgs = StrLength(Cmd->CmdBuf) / 10 + 10;
+       nArgs = CmdAdjust(Cmd, nArgs, 0);
+       Cmd->num_parms = 0;
+       Cmd->Params[Cmd->num_parms].Key = pch = ChrPtr(Cmd->CmdBuf);
+       end = Cmd->Params[Cmd->num_parms].Key + StrLength(Cmd->CmdBuf);
+
+       while (pch < end) 
+       {
+               if ((*pch=='(') ||
+                   (*pch=='[') ||
+                   (*pch=='<') ||
+                   (*pch=='{'))
+                       ++nest;
+
+               else if ((*pch==')') ||
+                        (*pch==']') ||
+                        (*pch=='>') ||
+                        (*pch=='}'))
+                       --nest;
+
+               if ((nest <= 0) && (*pch==' ')) {
+                       StrBufPeek(Cmd->CmdBuf, pch, 0, '\0');
+                       Cmd->Params[Cmd->num_parms].len = 
+                               pch - Cmd->Params[Cmd->num_parms].Key;
+
+                       if (Cmd->num_parms + 1 >= Cmd->avail_parms) {
+                               nArgs = CmdAdjust(Cmd, nArgs * 2, 1);
+                       }
+                       Cmd->num_parms++;                       
+                       Cmd->Params[Cmd->num_parms].Key = ++pch;
+               }
+               else if (pch + 1 == end) {
+                       Cmd->Params[Cmd->num_parms].len = 
+                               pch - Cmd->Params[Cmd->num_parms].Key + 1;
+
+                       Cmd->num_parms++;                       
+               }
+               pch ++;
+       }
+       return Cmd->num_parms;
+
+}
+
+
+/*
+ * One particularly hideous aspect of IMAP is that we have to allow the client
+ * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
+ * handles this by setting the IMAP_SELECTED flag for each message specified in
+ * the ranges/sets, then looping through the message array, outputting messages
+ * with the flag set.  We don't bother returning an error if an out-of-range
+ * number is specified (we just return quietly) because any client braindead
+ * enough to request a bogus message number isn't going to notice the
+ * difference anyway.
+ *
+ * This function clears out the IMAP_SELECTED bits, then sets that bit for each
+ * message included in the specified range.
+ *
+ * Set is_uid to 1 to fetch by UID instead of sequence number.
+ */
+void imap_pick_range(const char *supplied_range, int is_uid) {
+       citimap *Imap = IMAP;
+       int i;
+       int num_sets;
+       int s;
+       char setstr[SIZ], lostr[SIZ], histr[SIZ];
+       long lo, hi;
+       char actual_range[SIZ];
+
+       /* 
+        * Handle the "ALL" macro
+        */
+       if (!strcasecmp(supplied_range, "ALL")) {
+               safestrncpy(actual_range, "1:*", sizeof actual_range);
+       }
+       else {
+               safestrncpy(actual_range, supplied_range, sizeof actual_range);
+       }
+
+       /*
+        * Clear out the IMAP_SELECTED flags for all messages.
+        */
+       for (i = 0; i < Imap->num_msgs; ++i) {
+               Imap->flags[i] = Imap->flags[i] & ~IMAP_SELECTED;
+       }
+
+       /*
+        * Now set it for all specified messages.
+        */
+       num_sets = num_tokens(actual_range, ',');
+       for (s=0; s<num_sets; ++s) {
+               extract_token(setstr, actual_range, s, ',', sizeof setstr);
+
+               extract_token(lostr, setstr, 0, ':', sizeof lostr);
+               if (num_tokens(setstr, ':') >= 2) {
+                       extract_token(histr, setstr, 1, ':', sizeof histr);
+                       if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
+               } 
+               else {
+                       safestrncpy(histr, lostr, sizeof histr);
+               }
+               lo = atol(lostr);
+               hi = atol(histr);
+
+               /* Loop through the array, flipping bits where appropriate */
+               for (i = 1; i <= Imap->num_msgs; ++i) {
+                       if (is_uid) {   /* fetch by sequence number */
+                               if ( (Imap->msgids[i-1]>=lo)
+                                  && (Imap->msgids[i-1]<=hi)) {
+                                       Imap->flags[i-1] |= IMAP_SELECTED;
+                               }
+                       }
+                       else {          /* fetch by uid */
+                               if ( (i>=lo) && (i<=hi)) {
+                                       Imap->flags[i-1] |= IMAP_SELECTED;
+                               }
+                       }
+               }
+       }
+}
+
+
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_fetch(int num_parms, ConstStr *Params) {
+       citimap_command Cmd;
+       int num_items;
+       
+       if (num_parms < 4) {
+               IReply("BAD invalid parameters");
+               return;
+       }
+
+       imap_pick_range(Params[2].Key, 0);
+
+       memset(&Cmd, 0, sizeof(citimap_command));
+       Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
+       MakeStringOf(Cmd.CmdBuf, 3);
+
+       num_items = imap_extract_data_items(&Cmd);
+       if (num_items < 1) {
+               IReply("BAD invalid data item list");
+               FreeStrBuf(&Cmd.CmdBuf);
+               free(Cmd.Params);
+               return;
+       }
+
+       imap_do_fetch(&Cmd);
+       IReply("OK FETCH completed");
+       FreeStrBuf(&Cmd.CmdBuf);
+       free(Cmd.Params);
+}
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_uidfetch(int num_parms, ConstStr *Params) {
+       citimap_command Cmd;
+       int num_items;
+       int i;
+       int have_uid_item = 0;
+
+       if (num_parms < 5) {
+               IReply("BAD invalid parameters");
+               return;
+       }
+
+       imap_pick_range(Params[3].Key, 1);
+
+       memset(&Cmd, 0, sizeof(citimap_command));
+       Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
+
+       MakeStringOf(Cmd.CmdBuf, 4);
+#if 0
+       syslog(LOG_DEBUG, "imap: -------%s--------", ChrPtr(Cmd.CmdBuf));
+#endif
+       num_items = imap_extract_data_items(&Cmd);
+       if (num_items < 1) {
+               IReply("BAD invalid data item list");
+               FreeStrBuf(&Cmd.CmdBuf);
+               free(Cmd.Params);
+               return;
+       }
+
+       /* If the "UID" item was not included, we include it implicitly
+        * (at the beginning) because this is a UID FETCH command
+        */
+       for (i=0; i<num_items; ++i) {
+               if (!strcasecmp(Cmd.Params[i].Key, "UID")) ++have_uid_item;
+       }
+       if (have_uid_item == 0) {
+               if (Cmd.num_parms + 1 >= Cmd.avail_parms)
+                       CmdAdjust(&Cmd, Cmd.avail_parms + 1, 1);
+               memmove(&Cmd.Params[1], 
+                       &Cmd.Params[0], 
+                       sizeof(ConstStr) * Cmd.num_parms);
+
+               Cmd.num_parms++;
+               Cmd.Params[0] = (ConstStr){HKEY("UID")};
+       }
+
+       imap_do_fetch(&Cmd);
+       IReply("OK UID FETCH completed");
+       FreeStrBuf(&Cmd.CmdBuf);
+       free(Cmd.Params);
+}
+
+
diff --git a/citadel/server/modules/imap/imap_fetch.h b/citadel/server/modules/imap/imap_fetch.h
new file mode 100644 (file)
index 0000000..7847fce
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2001-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+void imap_pick_range(const char *range, int is_uid);
+void imap_fetch(int num_parms, ConstStr *Params);
+void imap_uidfetch(int num_parms, ConstStr *Params);
+void imap_fetch_flags(int seq);
+int imap_extract_data_items(citimap_command *Cmd);
diff --git a/citadel/server/modules/imap/imap_list.c b/citadel/server/modules/imap/imap_list.c
new file mode 100644 (file)
index 0000000..a256a5c
--- /dev/null
@@ -0,0 +1,417 @@
+/*
+ * Implements the LIST and LSUB commands.
+ *
+ * Copyright (c) 2000-2017 by Art Cancro and others.
+ *
+ *  This program is open source software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../sysdep_decls.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "imap_fetch.h"
+#include "imap_search.h"
+#include "imap_store.h"
+#include "imap_acl.h"
+#include "imap_misc.h"
+#include "imap_list.h"
+#include "../../ctdl_module.h"
+
+
+typedef struct __ImapRoomListFilter {
+       char verb[16];
+       int subscribed_rooms_only;
+       int return_subscribed;
+       int return_children;
+
+       int num_patterns;
+       int num_patterns_avail;
+       StrBuf **patterns;
+}ImapRoomListFilter;
+
+/*
+ * Used by LIST and LSUB to show the floors in the listing
+ */
+void imap_list_floors(char *verb, int num_patterns, StrBuf **patterns)
+{
+       int i;
+       struct floor *fl;
+       int j = 0;
+       int match = 0;
+
+       for (i = 0; i < MAXFLOORS; ++i) {
+               fl = CtdlGetCachedFloor(i);
+               if (fl->f_flags & F_INUSE) {
+                       match = 0;
+                       for (j=0; j<num_patterns; ++j) {
+                               if (imap_mailbox_matches_pattern (ChrPtr(patterns[j]), fl->f_name)) {
+                                       match = 1;
+                               }
+                       }
+                       if (match) {
+                               IAPrintf("* %s (\\NoSelect \\HasChildren) \"/\" ", verb);
+                               IPutStr(fl->f_name, (fl->f_name)?strlen(fl->f_name):0);
+                               IAPuts("\r\n");
+                       }
+               }
+       }
+}
+
+
+/*
+ * Back end for imap_list()
+ *
+ * Implementation note: IMAP "subscribed folder" is equivalent to Citadel "known room"
+ *
+ * The "user data" field is actually an array of pointers; see below for the breakdown
+ *
+ */
+void imap_listroom(struct ctdlroom *qrbuf, void *data)
+{
+#define SUBSCRIBED_STR "\\Subscribed"
+#define HASCHILD_STR "\\HasChildren"
+       char MailboxName[SIZ];
+       char return_options[256];
+       int ra;
+       int yes_output_this_room;
+       ImapRoomListFilter *ImapFilter;
+       int i = 0;
+       int match = 0;
+       int ROLen;
+
+       /* Here's how we break down the array of pointers passed to us */
+       ImapFilter = (ImapRoomListFilter*)data;
+
+       /* Only list rooms to which the user has access!! */
+       yes_output_this_room = 0;
+       *return_options = '\0';
+       ROLen = 0;
+       CtdlRoomAccess(qrbuf, &CC->user, &ra, NULL);
+
+       if (ImapFilter->return_subscribed) {
+               if (ra & UA_KNOWN) {
+                       memcpy(return_options, HKEY(SUBSCRIBED_STR) + 1);
+                       ROLen += sizeof(SUBSCRIBED_STR) - 1;
+               }
+       }
+
+       /* Warning: ugly hack.
+        * We don't have any way to determine the presence of child mailboxes
+        * without refactoring this entire module.  So we're just going to return
+        * the \HasChildren attribute for every room.
+        * We'll fix this later when we have time.
+        */
+       if (ImapFilter->return_children) {
+               if (!IsEmptyStr(return_options)) {
+                       memcpy(return_options + ROLen, HKEY(" "));
+                       ROLen ++;
+               }
+               memcpy(return_options + ROLen, HKEY(SUBSCRIBED_STR) + 1);
+       }
+
+       if (ImapFilter->subscribed_rooms_only) {
+               if (ra & UA_KNOWN) {
+                       yes_output_this_room = 1;
+               }
+       }
+       else {
+               if ((ra & UA_KNOWN) || ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED))) {
+                       yes_output_this_room = 1;
+               }
+       }
+
+       if (yes_output_this_room) {
+               long len;
+               len = imap_mailboxname(MailboxName, sizeof MailboxName, qrbuf);
+               match = 0;
+               for (i=0; i<ImapFilter->num_patterns; ++i) {
+                       if (imap_mailbox_matches_pattern(ChrPtr(ImapFilter->patterns[i]), MailboxName)) {
+                               match = 1;
+                       }
+               }
+               if (match) {
+                       IAPrintf("* %s (%s) \"/\" ", ImapFilter->verb, return_options);
+                       IPutStr(MailboxName, len);
+                       IAPuts("\r\n");
+               }
+       }
+}
+
+
+/*
+ * Implements the LIST and LSUB commands
+ */
+void imap_list(int num_parms, ConstStr *Params)
+{
+       struct CitContext *CCC = CC;
+       citimap *Imap = CCCIMAP;
+       int i, j, paren_nest;
+       ImapRoomListFilter ImapFilter;
+       int selection_left = (-1);
+       int selection_right = (-1);
+       int return_left = (-1);
+       int root_pos = 2;
+       int patterns_left = 3;
+       int patterns_right = 3;
+       int extended_list_in_use = 0;
+
+       if (num_parms < 4) {
+               IReply("BAD arguments invalid");
+               return;
+       }
+
+       ImapFilter.num_patterns = 1;
+       ImapFilter.return_subscribed = 0;
+       ImapFilter.return_children = 0;
+       ImapFilter.subscribed_rooms_only = 0;
+       
+
+       /* parms[1] is the IMAP verb being used (e.g. LIST or LSUB)
+        * This tells us how to behave, and what verb to return back to the caller
+        */
+       safestrncpy(ImapFilter.verb, Params[1].Key, sizeof ImapFilter.verb);
+       j = Params[1].len;
+       for (i=0; i<j; ++i) {
+               ImapFilter.verb[i] = toupper(ImapFilter.verb[i]);
+       }
+
+       if (!strcasecmp(ImapFilter.verb, "LSUB")) {
+               ImapFilter.subscribed_rooms_only = 1;
+       }
+
+       /*
+        * Partial implementation of LIST-EXTENDED (which will not get used because
+        * we don't advertise it in our capabilities string).  Several requirements:
+        *
+        * Extraction of selection options:
+        *      SUBSCRIBED option: done
+        *      RECURSIVEMATCH option: not done yet
+        *      REMOTE: safe to silently ignore
+        *
+        * Extraction of return options:
+        *      SUBSCRIBED option: done
+        *      CHILDREN option: done, but needs a non-ugly rewrite
+        *
+        * Multiple match patterns: done
+        */
+
+       /*
+        * If parameter 2 begins with a '(' character, the client is specifying
+        * selection options.  Extract their exact position, and then modify our
+        * expectation of where the root folder will be specified.
+        */
+       if (Params[2].Key[0] == '(') {
+               extended_list_in_use = 1;
+               selection_left = 2;
+               paren_nest = 0;
+               for (i=2; i<num_parms; ++i) {
+                       for (j=0; Params[i].Key[j]; ++j) {
+                               if (Params[i].Key[j] == '(') ++paren_nest;
+                               if (Params[i].Key[j] == ')') --paren_nest;
+                       }
+                       if (paren_nest == 0) {
+                               selection_right = i;    /* found end of selection options */
+                               root_pos = i+1;         /* folder root appears after selection options */
+                               i = num_parms + 1;      /* break out of the loop */
+                       }
+               }
+       }
+
+       /* If selection options were found, do something with them.
+        */
+       if ((selection_left > 0) && (selection_right >= selection_left)) {
+
+               /* Strip off the outer parentheses */
+               if (Params[selection_left].Key[0] == '(') {
+                       TokenCutLeft(&Imap->Cmd, 
+                                    &Params[selection_left], 
+                                    1);
+               }
+               if (Params[selection_right].Key[Params[selection_right].len-1] == ')') {
+                       TokenCutRight(&Imap->Cmd, 
+                                     &Params[selection_right], 
+                                     1);
+               }
+
+               for (i=selection_left; i<=selection_right; ++i) {
+
+                       if (!strcasecmp(Params[i].Key, "SUBSCRIBED")) {
+                               ImapFilter.subscribed_rooms_only = 1;
+                       }
+
+                       else if (!strcasecmp(Params[i].Key, "RECURSIVEMATCH")) {
+                               /* FIXME - do this! */
+                       }
+
+               }
+
+       }
+
+       /* The folder root appears immediately after the selection options,
+        * or in position 2 if no selection options were specified.
+        */
+       ImapFilter.num_patterns_avail = num_parms + 1;
+       ImapFilter.patterns = malloc(ImapFilter.num_patterns_avail * sizeof(StrBuf*));
+       memset(ImapFilter.patterns, 0, ImapFilter.num_patterns_avail * sizeof(StrBuf*));
+
+       patterns_left = root_pos + 1;
+       patterns_right = root_pos + 1;
+
+       if (Params[patterns_left].Key[0] == '(') {
+               extended_list_in_use = 1;
+               paren_nest = 0;
+               for (i=patterns_left; i<num_parms; ++i) {
+                       for (j=0; &Params[i].Key[j]; ++j) {
+                               if (Params[i].Key[j] == '(') ++paren_nest;
+                               if (Params[i].Key[j] == ')') --paren_nest;
+                       }
+                       if (paren_nest == 0) {
+                               patterns_right = i;     /* found end of patterns */
+                               i = num_parms + 1;      /* break out of the loop */
+                       }
+               }
+               ImapFilter.num_patterns = patterns_right - patterns_left + 1;
+               for (i=0; i<ImapFilter.num_patterns; ++i) {
+                       if (i < MAX_PATTERNS) {
+                               ImapFilter.patterns[i] = NewStrBufPlain(NULL, 
+                                                                       Params[root_pos].len + 
+                                                                       Params[patterns_left+i].len);
+                               if (i == 0) {
+                                       if (Params[root_pos].len > 1)
+                                               StrBufAppendBufPlain(ImapFilter.patterns[i], 
+                                                                    1 + CKEY(Params[root_pos]) - 1, 0);
+                               }
+                               else
+                                       StrBufAppendBufPlain(ImapFilter.patterns[i], 
+                                                            CKEY(Params[root_pos]), 0);
+
+                               if (i == ImapFilter.num_patterns-1) {
+                                       if (Params[patterns_left+i].len > 1)
+                                               StrBufAppendBufPlain(ImapFilter.patterns[i], 
+                                                                    CKEY(Params[patterns_left+i]) - 1, 0);
+                               }
+                               else StrBufAppendBufPlain(ImapFilter.patterns[i], 
+                                                         CKEY(Params[patterns_left+i]), 0);
+
+                       }
+
+               }
+       }
+       else {
+               ImapFilter.num_patterns = 1;
+               ImapFilter.patterns[0] = NewStrBufPlain(NULL, 
+                                                       Params[root_pos].len + 
+                                                       Params[patterns_left].len);
+               StrBufAppendBufPlain(ImapFilter.patterns[0], 
+                                    CKEY(Params[root_pos]), 0);
+               StrBufAppendBufPlain(ImapFilter.patterns[0], 
+                                    CKEY(Params[patterns_left]), 0);
+       }
+
+       /* If the word "RETURN" appears after the folder pattern list, then the client
+        * is specifying return options.
+        */
+       if (num_parms - patterns_right > 2) if (!strcasecmp(Params[patterns_right+1].Key, "RETURN")) {
+               return_left = patterns_right + 2;
+               extended_list_in_use = 1;
+               paren_nest = 0;
+               for (i=return_left; i<num_parms; ++i) {
+                       for (j=0;   Params[i].Key[j]; ++j) {
+                               if (Params[i].Key[j] == '(') ++paren_nest;
+                               if (Params[i].Key[j] == ')') --paren_nest;
+                       }
+
+                       /* Might as well look for these while we're in here... */
+                       if (Params[i].Key[0] == '(') 
+                               TokenCutLeft(&Imap->Cmd, 
+                                            &Params[i], 
+                                            1);
+                       if (Params[i].Key[Params[i].len-1] == ')')
+                           TokenCutRight(&Imap->Cmd, 
+                                         &Params[i], 
+                                         1);
+
+                       syslog(LOG_DEBUG, "evaluating <%s>", Params[i].Key);
+
+                       if (!strcasecmp(Params[i].Key, "SUBSCRIBED")) {
+                               ImapFilter.return_subscribed = 1;
+                       }
+
+                       else if (!strcasecmp(Params[i].Key, "CHILDREN")) {
+                               ImapFilter.return_children = 1;
+                       }
+
+                       if (paren_nest == 0) {
+                               i = num_parms + 1;      /* break out of the loop */
+                       }
+               }
+       }
+
+       /* Now start setting up the data we're going to send to the CtdlForEachRoom() callback.
+        */
+       
+       /* The non-extended LIST command is required to treat an empty
+        * ("" string) mailbox name argument as a special request to return the
+        * hierarchy delimiter and the root name of the name given in the
+        * reference parameter.
+        */
+       if ( (StrLength(ImapFilter.patterns[0]) == 0) && (extended_list_in_use == 0) ) {
+               IAPrintf("* %s (\\Noselect) \"/\" \"\"\r\n", ImapFilter.verb);
+       }
+
+       /* Non-empty mailbox names, and any form of the extended LIST command,
+        * is handled by this loop.
+        */
+       else {
+               imap_list_floors(ImapFilter.verb, 
+                                ImapFilter.num_patterns, 
+                                ImapFilter.patterns);
+               CtdlForEachRoom(imap_listroom, (char**)&ImapFilter);
+       }
+
+       /* 
+        * Free the pattern buffers we allocated above.
+        */
+       for (i=0; i<ImapFilter.num_patterns; ++i) {
+               FreeStrBuf(&ImapFilter.patterns[i]);
+       }
+       free(ImapFilter.patterns);
+
+       IReplyPrintf("OK %s completed", ImapFilter.verb);
+}
diff --git a/citadel/server/modules/imap/imap_list.h b/citadel/server/modules/imap/imap_list.h
new file mode 100644 (file)
index 0000000..902c6d1
--- /dev/null
@@ -0,0 +1,8 @@
+
+/*
+ * In the extended form of LIST the client is allowed to specify
+ * multiple match patterns.  How many will we allow?
+ */
+#define MAX_PATTERNS 20
+
+void imap_list(int num_parms, ConstStr *Params);
diff --git a/citadel/server/modules/imap/imap_metadata.c b/citadel/server/modules/imap/imap_metadata.c
new file mode 100644 (file)
index 0000000..425000a
--- /dev/null
@@ -0,0 +1,301 @@
+/*
+ * IMAP METADATA extension
+ *
+ * This is an implementation of the Bynari variant of the METADATA extension.
+ *
+ * Copyright (c) 2007-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../sysdep_decls.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "imap_fetch.h"
+#include "imap_misc.h"
+#include "../../genstamp.h"
+#include "../../ctdl_module.h"
+
+/*
+ * Implements the SETMETADATA command.
+ *
+ * Again, the only thing we're interested in setting here is the folder type.
+ *
+ * Attempting to set anything else calls a stub which fools the client into
+ * thinking that there is no remaining space available to store annotations.
+ */
+void imap_setmetadata(int num_parms, ConstStr *Params) {
+       char roomname[ROOMNAMELEN];
+       char savedroom[ROOMNAMELEN];
+       int msgs, new;
+       int ret;
+       int setting_user_value = 0;
+       char set_value[32];
+       int set_view = VIEW_BBS;
+       visit vbuf;
+
+       if (num_parms != 6) {
+               IReply("BAD usage error");
+               return;
+       }
+
+       /*
+        * Don't allow other types of metadata to be set
+        */
+       if (strcasecmp(Params[3].Key, "/vendor/kolab/folder-type")) {
+               IReply("NO [METADATA TOOMANY] SETMETADATA failed");
+               return;
+       }
+
+       if (!strcasecmp(Params[4].Key, "(value.shared")) {
+               setting_user_value = 0;                         /* global view */
+       }
+       else if (!strcasecmp(Params[4].Key, "(value.priv")) {
+               setting_user_value = 1;                         /* per-user view */
+       }
+       else {
+               IReply("NO [METADATA TOOMANY] SETMETADATA failed");
+               return;
+       }
+
+       /*
+        * Extract the folder type without any parentheses.  Then learn
+        * the Citadel view type based on the supplied folder type.
+        */
+       extract_token(set_value, Params[5].Key, 0, ')', sizeof set_value);
+       if (!strncasecmp(set_value, "mail", 4)) {
+               set_view = VIEW_MAILBOX;
+       }
+       else if (!strncasecmp(set_value, "event", 5)) {
+               set_view = VIEW_CALENDAR;
+       }
+       else if (!strncasecmp(set_value, "contact", 7)) {
+               set_view = VIEW_ADDRESSBOOK;
+       }
+       else if (!strncasecmp(set_value, "journal", 7)) {
+               set_view = VIEW_JOURNAL;
+       }
+       else if (!strncasecmp(set_value, "note", 4)) {
+               set_view = VIEW_NOTES;
+       }
+       else if (!strncasecmp(set_value, "task", 4)) {
+               set_view = VIEW_TASKS;
+       }
+       else {
+               set_view = VIEW_MAILBOX;
+       }
+
+       ret = imap_grabroom(roomname, Params[2].Key, 1);
+       if (ret != 0) {
+               IReply("NO Invalid mailbox name or access denied");
+               return;
+       }
+
+       /*
+        * CtdlUserGoto() formally takes us to the desired room.  (If another
+        * folder is selected, save its name so we can return there!!!!!)
+        */
+       if (IMAP->selected) {
+               strcpy(savedroom, CC->room.QRname);
+       }
+       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+       /*
+        * Always set the per-user view to the requested one.
+        */
+       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+       vbuf.v_view = set_view;
+       CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
+
+       /* If this is a "value.priv" set operation, we're done. */
+
+       if (setting_user_value)
+       {
+               IReply("OK SETANNOTATION complete");
+       }
+
+       /* If this is a "value.shared" set operation, we are allowed to perform it
+        * under certain conditions.
+        */
+       else if (       (is_room_aide())                                        /* aide or room aide */
+               ||      (       (CC->room.QRflags & QR_MAILBOX)
+                       &&      (CC->user.usernum == atol(CC->room.QRname))     /* mailbox owner */
+                       )
+               ||      (msgs == 0)             /* hack: if room is empty, assume we just created it */
+       ) {
+               CtdlGetRoomLock(&CC->room, CC->room.QRname);
+               CC->room.QRdefaultview = set_view;
+               CtdlPutRoomLock(&CC->room);
+               IReply("OK SETANNOTATION complete");
+       }
+
+       /* If we got to this point, we don't have permission to set the default view. */
+       else {
+               IReply("NO [METADATA TOOMANY] SETMETADATA failed");
+       }
+
+       /*
+        * If a different folder was previously selected, return there now.
+        */
+       if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) {
+               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+       }
+       return;
+}
+
+
+/*
+ * Implements the GETMETADATA command.
+ *
+ * Regardless of what the client asked for, we are going to supply them with
+ * the folder type.  It's the only metadata we have anyway.
+ */
+void imap_getmetadata(int num_parms, ConstStr *Params) {
+       char roomname[ROOMNAMELEN];
+       char savedroom[ROOMNAMELEN];
+       int msgs, new;
+       int ret;
+       int found = 0;
+
+/* this doesn't work if you have rooms/floors with spaces. 
+   we need this for the bynari connector.
+       if (num_parms > 5) {
+               IReply("BAD usage error");
+               return;
+       }
+*/
+       ret = imap_grabroom(roomname, Params[2].Key, 1);
+       if (ret != 0) {
+               IReply("NO Invalid mailbox name or access denied");
+               return;
+       }
+
+       /*
+        * CtdlUserGoto() formally takes us to the desired room.  (If another
+        * folder is selected, save its name so we can return there!!!!!)
+        */
+       if (IMAP->selected) {
+               strcpy(savedroom, CC->room.QRname);
+       }
+       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+       IAPuts("* METADATA ");
+       IPutCParamStr(2);
+       IAPuts(" \"/vendor/kolab/folder-type\" (\"value.shared\" \"");
+
+       /* If it's one of our hard-coded default rooms, we know what to do... */
+
+       if (CC->room.QRname[10] == '.')
+       {
+               if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
+                       found = 1;
+                       IAPuts("mail.inbox");
+               }
+               else if (!strcasecmp(&CC->room.QRname[11], SENTITEMS)) {
+                       found = 1;
+                       IAPuts("mail.sentitems");
+               }
+               else if (!strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) {
+                       found = 1;
+                       IAPuts("mail.drafts");
+               }
+               else if (!strcasecmp(&CC->room.QRname[11], USERCALENDARROOM)) {
+                       found = 1;
+                       IAPuts("event.default");
+               }
+               else if (!strcasecmp(&CC->room.QRname[11], USERCONTACTSROOM)) {
+                       found = 1;
+                       IAPuts("contact.default");
+               }
+               else if (!strcasecmp(&CC->room.QRname[11], USERNOTESROOM)) {
+                       found = 1;
+                       IAPuts("note.default");
+               }
+               else if (!strcasecmp(&CC->room.QRname[11], USERTASKSROOM)) {
+                       found = 1;
+                       IAPuts("task.default");
+               }
+       }
+
+       /* Otherwise, use the view for this room to determine the type of data.
+        * We are going with the default view rather than the user's view, because
+        * the default view almost always defines the actual contents, while the
+        * user's view might only make changes to presentation.  It also saves us
+        * an extra database access because we don't need to load the visit record.
+        */
+       if (!found)
+       {
+               if (CC->room.QRdefaultview == VIEW_CALENDAR) {
+                       IAPuts("event");
+               }
+               else if (CC->room.QRdefaultview == VIEW_ADDRESSBOOK) {
+                       IAPuts("contact");
+               }
+               else if (CC->room.QRdefaultview == VIEW_TASKS) {
+                       IAPuts("task");
+               }
+               else if (CC->room.QRdefaultview == VIEW_NOTES) {
+                       IAPuts("note");
+               }
+               else if (CC->room.QRdefaultview == VIEW_JOURNAL) {
+                       IAPuts("journal");
+               }
+       }
+       /* If none of the above conditions were met, consider it an ordinary mailbox. */
+
+       if (!found) {
+               IAPuts("mail");
+       }
+
+       /* "mail.outbox" and "junkemail" are not implemented. */
+
+       IAPuts("\")\r\n");
+
+       /*
+        * If a different folder was previously selected, return there now.
+        */
+       if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) {
+               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+       }
+
+       IReply("OK GETMETADATA complete");
+       return;
+}
+
diff --git a/citadel/server/modules/imap/imap_metadata.h b/citadel/server/modules/imap/imap_metadata.h
new file mode 100644 (file)
index 0000000..3f2a8bc
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2007-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+void imap_getmetadata(int num_parms, ConstStr *Params);
+void imap_setmetadata(int num_parms, ConstStr *Params);
diff --git a/citadel/server/modules/imap/imap_misc.c b/citadel/server/modules/imap/imap_misc.c
new file mode 100644 (file)
index 0000000..50bd714
--- /dev/null
@@ -0,0 +1,412 @@
+/*
+ * Copyright (c) 1987-2020 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../sysdep_decls.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../room_ops.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "imap_fetch.h"
+#include "imap_misc.h"
+#include "../../genstamp.h"
+#include "../../ctdl_module.h"
+
+
+
+
+
+/*
+ * imap_copy() calls imap_do_copy() to do its actual work, once it's
+ * validated and boiled down the request a bit.  (returns 0 on success)
+ */
+int imap_do_copy(const char *destination_folder) {
+       citimap *Imap = IMAP;
+       int i;
+       char roomname[ROOMNAMELEN];
+       struct ctdlroom qrbuf;
+       long *selected_msgs = NULL;
+       int num_selected = 0;
+
+       if (Imap->num_msgs < 1) {
+               return(0);
+       }
+
+       i = imap_grabroom(roomname, destination_folder, 1);
+       if (i != 0) return(i);
+
+       /*
+        * Copy all the message pointers in one shot.
+        */
+       selected_msgs = malloc(sizeof(long) * Imap->num_msgs);
+       if (selected_msgs == NULL) return(-1);
+
+       for (i = 0; i < Imap->num_msgs; ++i) {
+               if (Imap->flags[i] & IMAP_SELECTED) {
+                       selected_msgs[num_selected++] = Imap->msgids[i];
+               }
+       }
+
+       if (num_selected > 0) {
+               CtdlSaveMsgPointersInRoom(roomname, selected_msgs, num_selected, 1, NULL, 0);
+       }
+       free(selected_msgs);
+
+       /* Don't bother wasting any more time if there were no messages. */
+       if (num_selected == 0) {
+               return(0);
+       }
+
+       /* Enumerate lists of messages for which flags are toggled */
+       long *seen_yes = NULL;
+       int num_seen_yes = 0;
+       long *seen_no = NULL;
+       int num_seen_no = 0;
+       long *answ_yes = NULL;
+       int num_answ_yes = 0;
+       long *answ_no = NULL;
+       int num_answ_no = 0;
+
+       seen_yes = malloc(num_selected * sizeof(long));
+       seen_no = malloc(num_selected * sizeof(long));
+       answ_yes = malloc(num_selected * sizeof(long));
+       answ_no = malloc(num_selected * sizeof(long));
+
+       for (i = 0; i < Imap->num_msgs; ++i) {
+               if (Imap->flags[i] & IMAP_SELECTED) {
+                       if (Imap->flags[i] & IMAP_SEEN) {
+                               seen_yes[num_seen_yes++] = Imap->msgids[i];
+                       }
+                       if ((Imap->flags[i] & IMAP_SEEN) == 0) {
+                               seen_no[num_seen_no++] = Imap->msgids[i];
+                       }
+                       if (Imap->flags[i] & IMAP_ANSWERED) {
+                               answ_yes[num_answ_yes++] = Imap->msgids[i];
+                       }
+                       if ((Imap->flags[i] & IMAP_ANSWERED) == 0) {
+                               answ_no[num_answ_no++] = Imap->msgids[i];
+                       }
+               }
+       }
+
+       /* Set the flags... */
+       i = CtdlGetRoom(&qrbuf, roomname);
+       if (i == 0) {
+               CtdlSetSeen(seen_yes, num_seen_yes, 1, ctdlsetseen_seen, NULL, &qrbuf);
+               CtdlSetSeen(seen_no, num_seen_no, 0, ctdlsetseen_seen, NULL, &qrbuf);
+               CtdlSetSeen(answ_yes, num_answ_yes, 1, ctdlsetseen_answered, NULL, &qrbuf);
+               CtdlSetSeen(answ_no, num_answ_no, 0, ctdlsetseen_answered, NULL, &qrbuf);
+       }
+
+       free(seen_yes);
+       free(seen_no);
+       free(answ_yes);
+       free(answ_no);
+
+       return(0);
+}
+
+
+/*
+ * Output the [COPYUID xxx yyy] response code required by RFC2359
+ * to tell the client the UID's of the messages that were copied (if any).
+ * We are assuming that the IMAP_SELECTED flag is still set on any relevant
+ * messages in our source room.  Since the Citadel system uses UID's that
+ * are both globally unique and persistent across a room-to-room copy, we
+ * can get this done quite easily.
+ */
+void imap_output_copyuid_response(citimap *Imap) {
+       int i;
+       StrBuf *MsgsCopied = NewStrBuf();
+
+       for (i = 0; i < Imap->num_msgs; ++i) {
+               if (Imap->flags[i] & IMAP_SELECTED) {
+                       if (StrLength(MsgsCopied) > 0) {
+                               StrBufAppendBufPlain(MsgsCopied, HKEY(","), 0);
+                       }
+                       StrBufAppendPrintf(MsgsCopied, "%ld", Imap->msgids[i]);
+               }
+       }
+
+       if (StrLength(MsgsCopied) > 0) {
+               IAPrintf("[COPYUID %ld %s %s] ", GLOBAL_UIDVALIDITY_VALUE, ChrPtr(MsgsCopied), ChrPtr(MsgsCopied));
+       }
+
+       FreeStrBuf(&MsgsCopied);
+}
+
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_copy(int num_parms, ConstStr *Params) {
+       int ret;
+
+       if (num_parms != 4) {
+               IReply("BAD invalid parameters");
+               return;
+       }
+
+       if (imap_is_message_set(Params[2].Key)) {
+               imap_pick_range(Params[2].Key, 0);
+       }
+       else {
+               IReply("BAD invalid parameters");
+               return;
+       }
+
+       ret = imap_do_copy(Params[3].Key);
+       if (!ret) {
+               IAPrintf("%s OK ", Params[0].Key);
+               imap_output_copyuid_response(IMAP);
+               IAPuts("COPY completed\r\n");
+       }
+       else {
+               IReplyPrintf("NO COPY failed (error %d)", ret);
+       }
+}
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_uidcopy(int num_parms, ConstStr *Params) {
+
+       if (num_parms != 5) {
+               IReply("BAD invalid parameters");
+               return;
+       }
+
+       if (imap_is_message_set(Params[3].Key)) {
+               imap_pick_range(Params[3].Key, 1);
+       }
+       else {
+               IReply("BAD invalid parameters");
+               return;
+       }
+
+       if (imap_do_copy(Params[4].Key) == 0) {
+               IAPrintf("%s OK ", Params[0].Key);
+               imap_output_copyuid_response(IMAP);
+               IAPuts("UID COPY completed\r\n");
+       }
+       else {
+               IReply("NO UID COPY failed");
+       }
+}
+
+
+/*
+ * imap_do_append_flags() is called by imap_append() to set any flags that
+ * the client specified at append time.
+ *
+ * FIXME find a way to do these in bulk so we don't max out our db journal
+ */
+void imap_do_append_flags(long new_msgnum, char *new_message_flags) {
+       char flags[32];
+       char this_flag[sizeof flags];
+       int i;
+
+       if (new_message_flags == NULL) return;
+       if (IsEmptyStr(new_message_flags)) return;
+
+       safestrncpy(flags, new_message_flags, sizeof flags);
+
+       for (i=0; i<num_tokens(flags, ' '); ++i) {
+               extract_token(this_flag, flags, i, ' ', sizeof this_flag);
+               if (this_flag[0] == '\\') strcpy(this_flag, &this_flag[1]);
+               if (!strcasecmp(this_flag, "Seen")) {
+                       CtdlSetSeen(&new_msgnum, 1, 1, ctdlsetseen_seen,
+                               NULL, NULL);
+               }
+               if (!strcasecmp(this_flag, "Answered")) {
+                       CtdlSetSeen(&new_msgnum, 1, 1, ctdlsetseen_answered,
+                               NULL, NULL);
+               }
+       }
+}
+
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_append(int num_parms, ConstStr *Params) {
+       long literal_length;
+       struct CtdlMessage *msg = NULL;
+       long new_msgnum = (-1L);
+       int ret = 0;
+       char roomname[ROOMNAMELEN];
+       char errbuf[SIZ];
+       char dummy[SIZ];
+       char savedroom[ROOMNAMELEN];
+       int msgs, new;
+       int i;
+       char new_message_flags[SIZ];
+       citimap *Imap;
+
+       if (num_parms < 4) {
+               IReply("BAD usage error");
+               return;
+       }
+
+       if ( (Params[num_parms-1].Key[0] != '{')
+          || (Params[num_parms-1].Key[Params[num_parms-1].len-1] != '}') )  {
+               IReply("BAD no message literal supplied");
+               return;
+       }
+
+       *new_message_flags = '\0';
+       if (num_parms >= 5) {
+               for (i=3; i<num_parms; ++i) {
+                       strcat(new_message_flags, Params[i].Key);
+                       strcat(new_message_flags, " ");
+               }
+               stripallbut(new_message_flags, '(', ')');
+       }
+
+       /* This is how we'd do this if it were relevant in our data store.
+        * if (num_parms >= 6) {
+        *  new_message_internaldate = parms[4];
+        * }
+        */
+
+       literal_length = atol(&Params[num_parms-1].Key[1]);
+       if (literal_length < 1) {
+               IReply("BAD Message length must be at least 1.");
+               return;
+       }
+
+       Imap = IMAP;
+       imap_free_transmitted_message();        /* just in case. */
+
+       Imap->TransmittedMessage = NewStrBufPlain(NULL, literal_length);
+
+       if (Imap->TransmittedMessage == NULL) {
+               IReply("NO Cannot allocate memory.");
+               return;
+       }
+       
+       IAPrintf("+ Transmit message now.\r\n");
+       
+       IUnbuffer ();
+
+       client_read_blob(Imap->TransmittedMessage, literal_length, CtdlGetConfigInt("c_sleeping"));
+
+       if ((ret < 0) || (StrLength(Imap->TransmittedMessage) < literal_length)) {
+               IReply("NO Read failed.");
+               return;
+       }
+
+       /* Client will transmit a trailing CRLF after the literal (the message
+        * text) is received.  This call to client_getln() absorbs it.
+        */
+       flush_output();
+       client_getln(dummy, sizeof dummy);
+
+       /* Convert RFC822 newlines (CRLF) to Unix newlines (LF) */
+       syslog(LOG_DEBUG, "Converting CRLF to LF");
+       StrBufToUnixLF(Imap->TransmittedMessage);
+
+       syslog(LOG_DEBUG, "Converting message format");
+       msg = convert_internet_message_buf(&Imap->TransmittedMessage);
+
+       ret = imap_grabroom(roomname, Params[2].Key, 1);
+       if (ret != 0) {
+               IReply("NO Invalid mailbox name or access denied");
+               return;
+       }
+
+       /*
+        * CtdlUserGoto() formally takes us to the desired room.  (If another
+        * folder is selected, save its name so we can return there!!!!!)
+        */
+       if (Imap->selected) {
+               strcpy(savedroom, CC->room.QRname);
+       }
+       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+       /* If the user is locally authenticated, FORCE the From: header to
+        * show up as the real sender.  (Configurable setting)
+        */
+       if (CC->logged_in) {
+               if ( ((CC->room.QRflags & QR_MAILBOX) == 0) && (CtdlGetConfigInt("c_imap_keep_from") == 0))
+               {
+                       CM_SetField(msg, eAuthor, CC->user.fullname, strlen(CC->user.fullname));
+               }
+       }
+
+       /* 
+        * Can we post here?
+        */
+       ret = CtdlDoIHavePermissionToPostInThisRoom(errbuf, sizeof errbuf, POST_LOGGED_IN, 0);
+
+       if (ret) {
+               /* Nope ... print an error message */
+               IReplyPrintf("NO %s", errbuf);
+       }
+
+       else {
+               /* Yes ... go ahead and post! */
+               if (msg != NULL) {
+                       new_msgnum = CtdlSubmitMsg(msg, NULL, "");
+               }
+               if (new_msgnum >= 0L) {
+                       IReplyPrintf("OK [APPENDUID %ld %ld] APPEND completed",
+                                    GLOBAL_UIDVALIDITY_VALUE, new_msgnum);
+               }
+               else {
+                       IReplyPrintf("BAD Error %ld saving message to disk.",
+                                    new_msgnum);
+               }
+       }
+
+       /*
+        * IMAP protocol response to client has already been sent by now.
+        *
+        * If another folder is selected, go back to that room so we can resume
+        * our happy day without violent explosions.
+        */
+       if (Imap->selected) {
+               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+       }
+
+       /* We don't need this buffer anymore */
+       CM_Free(msg);
+
+       if (IsEmptyStr(new_message_flags)) {
+               imap_do_append_flags(new_msgnum, new_message_flags);
+       }
+}
diff --git a/citadel/server/modules/imap/imap_misc.h b/citadel/server/modules/imap/imap_misc.h
new file mode 100644 (file)
index 0000000..d0dfa40
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2001-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+void imap_copy(int num_parms, ConstStr *Params);
+void imap_uidcopy(int num_parms, ConstStr *Params);
+void imap_append(int num_parms, ConstStr *Params);
diff --git a/citadel/server/modules/imap/imap_search.c b/citadel/server/modules/imap/imap_search.c
new file mode 100644 (file)
index 0000000..ecf670c
--- /dev/null
@@ -0,0 +1,648 @@
+/*
+ * Implements IMAP's gratuitously complex SEARCH command.
+ *
+ * Copyright (c) 2001-2020 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../ctdl_module.h"
+
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../sysdep_decls.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "imap_fetch.h"
+#include "imap_search.h"
+#include "../../genstamp.h"
+
+
+/*
+ * imap_do_search() calls imap_do_search_msg() to search an individual
+ * message after it has been fetched from the disk.  This function returns
+ * nonzero if there is a match.
+ *
+ * supplied_msg MAY be used to pass a pointer to the message in memory,
+ * if for some reason it's already been loaded.  If not, the message will
+ * be loaded only if one or more search criteria require it.
+ */
+int imap_do_search_msg(int seq, struct CtdlMessage *supplied_msg,
+                       int num_items, ConstStr *itemlist, int is_uid) {
+
+       citimap *Imap = IMAP;
+       int match = 0;
+       int is_not = 0;
+       int is_or = 0;
+       int pos = 0;
+       int i;
+       char *fieldptr;
+       struct CtdlMessage *msg = NULL;
+       int need_to_free_msg = 0;
+
+       if (num_items == 0) {
+               return(0);
+       }
+       msg = supplied_msg;
+
+       /* Initially we start at the beginning. */
+       pos = 0;
+
+       /* Check for the dreaded NOT criterion. */
+       if (!strcasecmp(itemlist[0].Key, "NOT")) {
+               is_not = 1;
+               pos = 1;
+       }
+
+       /* Check for the dreaded OR criterion. */
+       if (!strcasecmp(itemlist[0].Key, "OR")) {
+               is_or = 1;
+               pos = 1;
+       }
+
+       /* Now look for criteria. */
+       if (!strcasecmp(itemlist[pos].Key, "ALL")) {
+               match = 1;
+               ++pos;
+       }
+       
+       else if (!strcasecmp(itemlist[pos].Key, "ANSWERED")) {
+               if (Imap->flags[seq-1] & IMAP_ANSWERED) {
+                       match = 1;
+               }
+               ++pos;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "BCC")) {
+               if (msg == NULL) {
+                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                       need_to_free_msg = 1;
+               }
+               if (msg != NULL) {
+                       fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Bcc");
+                       if (fieldptr != NULL) {
+                               if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) {
+                                       match = 1;
+                               }
+                               free(fieldptr);
+                       }
+               }
+               pos += 2;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "BEFORE")) {
+               if (msg == NULL) {
+                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                       need_to_free_msg = 1;
+               }
+               if (msg != NULL) {
+                       if (!CM_IsEmpty(msg, eTimestamp)) {
+                               if (imap_datecmp(itemlist[pos+1].Key,
+                                               atol(msg->cm_fields[eTimestamp])) < 0) {
+                                       match = 1;
+                               }
+                       }
+               }
+               pos += 2;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "BODY")) {
+
+               /* If fulltext indexing is active, on this server,
+                *  all messages have already been qualified.
+                */
+               if (CtdlGetConfigInt("c_enable_fulltext")) {
+                       match = 1;
+               }
+
+               /* Otherwise, we have to do a slow search. */
+               else {
+                       if (msg == NULL) {
+                               msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                               need_to_free_msg = 1;
+                       }
+                       if (msg != NULL) {
+                               if (bmstrcasestr(msg->cm_fields[eMesageText], itemlist[pos+1].Key)) {
+                                       match = 1;
+                               }
+                       }
+               }
+
+               pos += 2;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "CC")) {
+               if (msg == NULL) {
+                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                       need_to_free_msg = 1;
+               }
+               if (msg != NULL) {
+                       fieldptr = msg->cm_fields[eCarbonCopY];
+                       if (fieldptr != NULL) {
+                               if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) {
+                                       match = 1;
+                               }
+                       }
+                       else {
+                               fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Cc");
+                               if (fieldptr != NULL) {
+                                       if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) {
+                                               match = 1;
+                                       }
+                                       free(fieldptr);
+                               }
+                       }
+               }
+               pos += 2;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "DELETED")) {
+               if (Imap->flags[seq-1] & IMAP_DELETED) {
+                       match = 1;
+               }
+               ++pos;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "DRAFT")) {
+               if (Imap->flags[seq-1] & IMAP_DRAFT) {
+                       match = 1;
+               }
+               ++pos;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "FLAGGED")) {
+               if (Imap->flags[seq-1] & IMAP_FLAGGED) {
+                       match = 1;
+               }
+               ++pos;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "FROM")) {
+               if (msg == NULL) {
+                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                       need_to_free_msg = 1;
+               }
+               if (msg != NULL) {
+                       if (bmstrcasestr(msg->cm_fields[eAuthor], itemlist[pos+1].Key)) {
+                               match = 1;
+                       }
+                       if (bmstrcasestr(msg->cm_fields[erFc822Addr], itemlist[pos+1].Key)) {
+                               match = 1;
+                       }
+               }
+               pos += 2;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "HEADER")) {
+
+               /* We've got to do a slow search for this because the client
+                * might be asking for an RFC822 header field that has not been
+                * converted into a Citadel header field.  That requires
+                * examining the message body.
+                */
+               if (msg == NULL) {
+                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                       need_to_free_msg = 1;
+               }
+
+               if (msg != NULL) {
+       
+                       CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+                       CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_FAST, 0, 1, 0);
+       
+                       fieldptr = rfc822_fetch_field(ChrPtr(CC->redirect_buffer), itemlist[pos+1].Key);
+                       if (fieldptr != NULL) {
+                               if (bmstrcasestr(fieldptr, itemlist[pos+2].Key)) {
+                                       match = 1;
+                               }
+                               free(fieldptr);
+                       }
+       
+                       FreeStrBuf(&CC->redirect_buffer);
+               }
+
+               pos += 3;       /* Yes, three */
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "KEYWORD")) {
+               /* not implemented */
+               pos += 2;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "LARGER")) {
+               if (msg == NULL) {
+                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                       need_to_free_msg = 1;
+               }
+               if (msg != NULL) {
+                       if (msg->cm_lengths[eMesageText] > atoi(itemlist[pos+1].Key)) {
+                               match = 1;
+                       }
+               }
+               pos += 2;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "NEW")) {
+               if ( (Imap->flags[seq-1] & IMAP_RECENT) && (!(Imap->flags[seq-1] & IMAP_SEEN))) {
+                       match = 1;
+               }
+               ++pos;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "OLD")) {
+               if (!(Imap->flags[seq-1] & IMAP_RECENT)) {
+                       match = 1;
+               }
+               ++pos;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "ON")) {
+               if (msg == NULL) {
+                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                       need_to_free_msg = 1;
+               }
+               if (msg != NULL) {
+                       if (!CM_IsEmpty(msg, eTimestamp)) {
+                               if (imap_datecmp(itemlist[pos+1].Key,
+                                               atol(msg->cm_fields[eTimestamp])) == 0) {
+                                       match = 1;
+                               }
+                       }
+               }
+               pos += 2;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "RECENT")) {
+               if (Imap->flags[seq-1] & IMAP_RECENT) {
+                       match = 1;
+               }
+               ++pos;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "SEEN")) {
+               if (Imap->flags[seq-1] & IMAP_SEEN) {
+                       match = 1;
+               }
+               ++pos;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "SENTBEFORE")) {
+               if (msg == NULL) {
+                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                       need_to_free_msg = 1;
+               }
+               if (msg != NULL) {
+                       if (!CM_IsEmpty(msg, eTimestamp)) {
+                               if (imap_datecmp(itemlist[pos+1].Key,
+                                               atol(msg->cm_fields[eTimestamp])) < 0) {
+                                       match = 1;
+                               }
+                       }
+               }
+               pos += 2;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "SENTON")) {
+               if (msg == NULL) {
+                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                       need_to_free_msg = 1;
+               }
+               if (msg != NULL) {
+                       if (!CM_IsEmpty(msg, eTimestamp)) {
+                               if (imap_datecmp(itemlist[pos+1].Key,
+                                               atol(msg->cm_fields[eTimestamp])) == 0) {
+                                       match = 1;
+                               }
+                       }
+               }
+               pos += 2;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "SENTSINCE")) {
+               if (msg == NULL) {
+                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                       need_to_free_msg = 1;
+               }
+               if (msg != NULL) {
+                       if (!CM_IsEmpty(msg, eTimestamp)) {
+                               if (imap_datecmp(itemlist[pos+1].Key,
+                                               atol(msg->cm_fields[eTimestamp])) >= 0) {
+                                       match = 1;
+                               }
+                       }
+               }
+               pos += 2;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "SINCE")) {
+               if (msg == NULL) {
+                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                       need_to_free_msg = 1;
+               }
+               if (msg != NULL) {
+                       if (!CM_IsEmpty(msg, eTimestamp)) {
+                               if (imap_datecmp(itemlist[pos+1].Key,
+                                               atol(msg->cm_fields[eTimestamp])) >= 0) {
+                                       match = 1;
+                               }
+                       }
+               }
+               pos += 2;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "SMALLER")) {
+               if (msg == NULL) {
+                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                       need_to_free_msg = 1;
+               }
+               if (msg != NULL) {
+                       if (msg->cm_lengths[eMesageText] < atoi(itemlist[pos+1].Key)) {
+                               match = 1;
+                       }
+               }
+               pos += 2;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "SUBJECT")) {
+               if (msg == NULL) {
+                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                       need_to_free_msg = 1;
+               }
+               if (msg != NULL) {
+                       if (bmstrcasestr(msg->cm_fields[eMsgSubject], itemlist[pos+1].Key)) {
+                               match = 1;
+                       }
+               }
+               pos += 2;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "TEXT")) {
+               if (msg == NULL) {
+                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                       need_to_free_msg = 1;
+               }
+               if (msg != NULL) {
+                       for (i='A'; i<='Z'; ++i) {
+                               if (bmstrcasestr(msg->cm_fields[i], itemlist[pos+1].Key)) {
+                                       match = 1;
+                               }
+                       }
+               }
+               pos += 2;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "TO")) {
+               if (msg == NULL) {
+                       msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+                       need_to_free_msg = 1;
+               }
+               if (msg != NULL) {
+                       if (bmstrcasestr(msg->cm_fields[eRecipient], itemlist[pos+1].Key)) {
+                               match = 1;
+                       }
+               }
+               pos += 2;
+       }
+
+       /* FIXME this is b0rken.  fix it. */
+       else if (imap_is_message_set(itemlist[pos].Key)) {
+               if (is_msg_in_sequence_set(itemlist[pos].Key, seq)) {
+                       match = 1;
+               }
+               pos += 1;
+       }
+
+       /* FIXME this is b0rken.  fix it. */
+       else if (!strcasecmp(itemlist[pos].Key, "UID")) {
+               if (is_msg_in_sequence_set(itemlist[pos+1].Key, Imap->msgids[seq-1])) {
+                       match = 1;
+               }
+               pos += 2;
+       }
+
+       /* Now here come the 'UN' criteria.  Why oh why do we have to
+        * implement *both* the 'UN' criteria *and* the 'NOT' keyword?  Why
+        * can't there be *one* way to do things?  More gratuitous complexity.
+        */
+
+       else if (!strcasecmp(itemlist[pos].Key, "UNANSWERED")) {
+               if ((Imap->flags[seq-1] & IMAP_ANSWERED) == 0) {
+                       match = 1;
+               }
+               ++pos;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "UNDELETED")) {
+               if ((Imap->flags[seq-1] & IMAP_DELETED) == 0) {
+                       match = 1;
+               }
+               ++pos;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "UNDRAFT")) {
+               if ((Imap->flags[seq-1] & IMAP_DRAFT) == 0) {
+                       match = 1;
+               }
+               ++pos;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "UNFLAGGED")) {
+               if ((Imap->flags[seq-1] & IMAP_FLAGGED) == 0) {
+                       match = 1;
+               }
+               ++pos;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "UNKEYWORD")) {
+               /* FIXME */
+               pos += 2;
+       }
+
+       else if (!strcasecmp(itemlist[pos].Key, "UNSEEN")) {
+               if ((Imap->flags[seq-1] & IMAP_SEEN) == 0) {
+                       match = 1;
+               }
+               ++pos;
+       }
+
+       /* Remember to negate if we were told to */
+       if (is_not) {
+               match = !match;
+       }
+
+       /* Keep going if there are more criteria! */
+       if (pos < num_items) {
+
+               if (is_or) {
+                       match = (match || imap_do_search_msg(seq, msg,
+                               num_items - pos, &itemlist[pos], is_uid));
+               }
+               else {
+                       match = (match && imap_do_search_msg(seq, msg,
+                               num_items - pos, &itemlist[pos], is_uid));
+               }
+
+       }
+
+       if (need_to_free_msg) {
+               CM_Free(msg);
+       }
+       return(match);
+}
+
+
+/*
+ * imap_search() calls imap_do_search() to do its actual work, once it's
+ * validated and boiled down the request a bit.
+ */
+void imap_do_search(int num_items, ConstStr *itemlist, int is_uid) {
+       citimap *Imap = IMAP;
+       int i, j, k;
+       int fts_num_msgs = 0;
+       long *fts_msgs = NULL;
+       int is_in_list = 0;
+       int num_results = 0;
+
+       /* Strip parentheses.  We realize that this method will not work
+        * in all cases, but it seems to work with all currently available
+        * client software.  Revisit later...
+        */
+       for (i=0; i<num_items; ++i) {
+               if (itemlist[i].Key[0] == '(') {
+                       
+                       TokenCutLeft(&Imap->Cmd, 
+                                    &itemlist[i], 
+                                    1);
+               }
+               if (itemlist[i].Key[itemlist[i].len-1] == ')') {
+                       TokenCutRight(&Imap->Cmd, 
+                                     &itemlist[i], 
+                                     1);
+               }
+       }
+
+       /* If there is a BODY search criterion in the query, use our full
+        * text index to disqualify messages that don't have any chance of
+        * matching.  (Only do this if the index is enabled!!)
+        */
+       if (CtdlGetConfigInt("c_enable_fulltext")) for (i=0; i<(num_items-1); ++i) {
+               if (!strcasecmp(itemlist[i].Key, "BODY")) {
+                       CtdlModuleDoSearch(&fts_num_msgs, &fts_msgs, itemlist[i+1].Key, "fulltext");
+                       if (fts_num_msgs > 0) {
+                               for (j=0; j < Imap->num_msgs; ++j) {
+                                       if (Imap->flags[j] & IMAP_SELECTED) {
+                                               is_in_list = 0;
+                                               for (k=0; k<fts_num_msgs; ++k) {
+                                                       if (Imap->msgids[j] == fts_msgs[k]) {
+                                                               ++is_in_list;
+                                                       }
+                                               }
+                                       }
+                                       if (!is_in_list) {
+                                               Imap->flags[j] = Imap->flags[j] & ~IMAP_SELECTED;
+                                       }
+                               }
+                       }
+                       else {          /* no hits on the index; disqualify every message */
+                               for (j=0; j < Imap->num_msgs; ++j) {
+                                       Imap->flags[j] = Imap->flags[j] & ~IMAP_SELECTED;
+                               }
+                       }
+                       if (fts_msgs) {
+                               free(fts_msgs);
+                       }
+               }
+       }
+
+       /* Now go through the messages and apply all search criteria. */
+       buffer_output();
+       IAPuts("* SEARCH ");
+       if (Imap->num_msgs > 0)
+        for (i = 0; i < Imap->num_msgs; ++i)
+         if (Imap->flags[i] & IMAP_SELECTED) {
+               if (imap_do_search_msg(i+1, NULL, num_items, itemlist, is_uid)) {
+                       if (num_results != 0) {
+                               IAPuts(" ");
+                       }
+                       if (is_uid) {
+                               IAPrintf("%ld", Imap->msgids[i]);
+                       }
+                       else {
+                               IAPrintf("%d", i+1);
+                       }
+                       ++num_results;
+               }
+       }
+       IAPuts("\r\n");
+       unbuffer_output();
+}
+
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_search(int num_parms, ConstStr *Params) {
+       int i;
+
+       if (num_parms < 3) {
+               IReply("BAD invalid parameters");
+               return;
+       }
+
+       for (i = 0; i < IMAP->num_msgs; ++i) {
+               IMAP->flags[i] |= IMAP_SELECTED;
+       }
+
+       imap_do_search(num_parms-2, &Params[2], 0);
+       IReply("OK SEARCH completed");
+}
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_uidsearch(int num_parms, ConstStr *Params) {
+       int i;
+
+       if (num_parms < 4) {
+               IReply("BAD invalid parameters");
+               return;
+       }
+
+       for (i = 0; i < IMAP->num_msgs; ++i) {
+               IMAP->flags[i] |= IMAP_SELECTED;
+       }
+
+       imap_do_search(num_parms-3, &Params[3], 1);
+       IReply("OK UID SEARCH completed");
+}
+
+
diff --git a/citadel/server/modules/imap/imap_search.h b/citadel/server/modules/imap/imap_search.h
new file mode 100644 (file)
index 0000000..ce2f27c
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2001-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+void imap_search(int num_parms, ConstStr *Params);
+void imap_uidsearch(int num_parms, ConstStr *Params);
diff --git a/citadel/server/modules/imap/imap_store.c b/citadel/server/modules/imap/imap_store.c
new file mode 100644 (file)
index 0000000..479a104
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+ * Implements the STORE command in IMAP.
+ *
+ * Copyright (c) 2001-2009 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "../../ctdl_module.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../sysdep_decls.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../room_ops.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "imap_fetch.h"
+#include "imap_store.h"
+#include "../../genstamp.h"
+
+
+/*
+ * imap_do_store() calls imap_do_store_msg() to tweak the settings of
+ * an individual message.
+ *
+ * We also implement the ".SILENT" protocol option here.  :(
+ */
+void imap_do_store_msg(int seq, const char *oper, unsigned int bits_to_twiddle) {
+       citimap *Imap = IMAP;
+
+       if (!strncasecmp(oper, "FLAGS", 5)) {
+               Imap->flags[seq] &= IMAP_MASK_SYSTEM;
+               Imap->flags[seq] |= bits_to_twiddle;
+       }
+       else if (!strncasecmp(oper, "+FLAGS", 6)) {
+               Imap->flags[seq] |= bits_to_twiddle;
+       }
+       else if (!strncasecmp(oper, "-FLAGS", 6)) {
+               Imap->flags[seq] &= (~bits_to_twiddle);
+       }
+}
+
+
+/*
+ * imap_store() calls imap_do_store() to perform the actual bit twiddling
+ * on the flags.
+ */
+void imap_do_store(citimap_command *Cmd) {
+       int i, j;
+       unsigned int bits_to_twiddle = 0;
+       const char *oper;
+       char flag[32];
+       char whichflags[256];
+       char num_flags;
+       int silent = 0;
+       long *ss_msglist;
+       int num_ss = 0;
+       int last_item_twiddled = (-1);
+       citimap *Imap = IMAP;
+
+       if (Cmd->num_parms < 2) return;
+       oper = Cmd->Params[0].Key;
+       if (cbmstrcasestr(oper, ".SILENT")) {
+               silent = 1;
+       }
+
+       /*
+        * ss_msglist is an array of message numbers to manipulate.  We
+        * are going to supply this array to CtdlSetSeen() later.
+        */
+       ss_msglist = malloc(Imap->num_msgs * sizeof(long));
+       if (ss_msglist == NULL) return;
+
+       /*
+        * Ok, go ahead and parse the flags.
+        */
+       for (i=1; i<Cmd->num_parms; ++i) {///TODO: why strcpy? 
+               strcpy(whichflags, Cmd->Params[i].Key);
+               if (whichflags[0]=='(') {
+                       safestrncpy(whichflags, &whichflags[1], 
+                               sizeof whichflags);
+               }
+               if (whichflags[strlen(whichflags)-1]==')') {
+                       whichflags[strlen(whichflags)-1]=0;
+               }
+               striplt(whichflags);
+
+               /* A client might twiddle more than one bit at a time.
+                * Note that we check for the flag names without the leading
+                * backslash because imap_parameterize() strips them out.
+                */
+               num_flags = num_tokens(whichflags, ' ');
+               for (j=0; j<num_flags; ++j) {
+                       extract_token(flag, whichflags, j, ' ', sizeof flag);
+
+                       if ((!strcasecmp(flag, "\\Deleted"))
+                          || (!strcasecmp(flag, "Deleted"))) {
+                               if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) {
+                                       bits_to_twiddle |= IMAP_DELETED;
+                               }
+                       }
+                       if ((!strcasecmp(flag, "\\Seen"))
+                          || (!strcasecmp(flag, "Seen"))) {
+                               bits_to_twiddle |= IMAP_SEEN;
+                       }
+                       if ((!strcasecmp(flag, "\\Answered")) 
+                          || (!strcasecmp(flag, "Answered"))) {
+                               bits_to_twiddle |= IMAP_ANSWERED;
+                       }
+               }
+       }
+
+       if (Imap->num_msgs > 0) {
+               for (i = 0; i < Imap->num_msgs; ++i) {
+                       if (Imap->flags[i] & IMAP_SELECTED) {
+                               last_item_twiddled = i;
+
+                               ss_msglist[num_ss++] = Imap->msgids[i];
+                               imap_do_store_msg(i, oper, bits_to_twiddle);
+
+                               if (!silent) {
+                                       IAPrintf("* %d FETCH (", i+1);
+                                       imap_fetch_flags(i);
+                                       IAPuts(")\r\n");
+                               }
+
+                       }
+               }
+       }
+
+       /*
+        * Now manipulate the database -- all in one shot.
+        */
+       if ( (last_item_twiddled >= 0) && (num_ss > 0) ) {
+
+               if (bits_to_twiddle & IMAP_SEEN) {
+                       CtdlSetSeen(ss_msglist, num_ss,
+                               ((Imap->flags[last_item_twiddled] & IMAP_SEEN) ? 1 : 0),
+                               ctdlsetseen_seen,
+                               NULL, NULL
+                       );
+               }
+
+               if (bits_to_twiddle & IMAP_ANSWERED) {
+                       CtdlSetSeen(ss_msglist, num_ss,
+                               ((Imap->flags[last_item_twiddled] & IMAP_ANSWERED) ? 1 : 0),
+                               ctdlsetseen_answered,
+                               NULL, NULL
+                       );
+               }
+
+       }
+
+       free(ss_msglist);
+       imap_do_expunge();              // Citadel always expunges immediately.
+       imap_rescan_msgids();
+}
+
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_store(int num_parms, ConstStr *Params) {
+       citimap_command Cmd;
+       int num_items;
+
+       if (num_parms < 3) {
+               IReply("BAD invalid parameters");
+               return;
+       }
+
+       if (imap_is_message_set(Params[2].Key)) {
+               imap_pick_range(Params[2].Key, 0);
+       }
+       else {
+               IReply("BAD invalid parameters");
+               return;
+       }
+
+       memset(&Cmd, 0, sizeof(citimap_command));
+       Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
+       MakeStringOf(Cmd.CmdBuf, 3);
+
+       num_items = imap_extract_data_items(&Cmd);
+       if (num_items < 1) {
+               IReply("BAD invalid data item list");
+               FreeStrBuf(&Cmd.CmdBuf);
+               free(Cmd.Params);
+               return;
+       }
+
+       imap_do_store(&Cmd);
+       IReply("OK STORE completed");
+       FreeStrBuf(&Cmd.CmdBuf);
+       free(Cmd.Params);
+}
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_uidstore(int num_parms, ConstStr *Params) {
+       citimap_command Cmd;
+       int num_items;
+
+       if (num_parms < 4) {
+               IReply("BAD invalid parameters");
+               return;
+       }
+
+       if (imap_is_message_set(Params[3].Key)) {
+               imap_pick_range(Params[3].Key, 1);
+       }
+       else {
+               IReply("BAD invalid parameters");
+               return;
+       }
+
+       memset(&Cmd, 0, sizeof(citimap_command));
+       Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
+       MakeStringOf(Cmd.CmdBuf, 4);
+
+       num_items = imap_extract_data_items(&Cmd);
+       if (num_items < 1) {
+               IReply("BAD invalid data item list");
+               FreeStrBuf(&Cmd.CmdBuf);
+               free(Cmd.Params);
+               return;
+       }
+
+       imap_do_store(&Cmd);
+       IReply("OK UID STORE completed");
+       FreeStrBuf(&Cmd.CmdBuf);
+       free(Cmd.Params);
+}
+
+
diff --git a/citadel/server/modules/imap/imap_store.h b/citadel/server/modules/imap/imap_store.h
new file mode 100644 (file)
index 0000000..cf47377
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2001-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+void imap_store(int num_parms, ConstStr *Params);
+void imap_uidstore(int num_parms, ConstStr *Params);
diff --git a/citadel/server/modules/imap/imap_tools.c b/citadel/server/modules/imap/imap_tools.c
new file mode 100644 (file)
index 0000000..96cdfc5
--- /dev/null
@@ -0,0 +1,992 @@
+/*
+ * Utility functions for the IMAP module.
+ *
+ * Copyright (c) 2001-2017 by the citadel.org team and others, except for
+ * most of the UTF7 and UTF8 handling code which was lifted from Evolution.
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#define SHOW_ME_VAPPEND_PRINTF
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdarg.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../sysdep_decls.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "../../ctdl_module.h"
+
+/* String handling helpers */
+
+/* This code uses some pretty nasty string manipulation. To make everything
+ * manageable, we use this semi-high-level string manipulation API. Strings are
+ * always \0-terminated, despite the fact that we keep track of the size.
+ */
+struct string {
+       char* buffer;
+       int maxsize;
+       int size;
+};
+
+static void string_init(struct string* s, char* buf, int bufsize)
+{
+       s->buffer = buf;
+       s->maxsize = bufsize-1;
+       s->size = strlen(buf);
+}
+
+static char* string_end(struct string* s)
+{
+       return s->buffer + s->size;
+}
+
+/* Append a UTF8 string of a particular length (in bytes). -1 to autocalculate. */
+
+static void string_append_sn(struct string* s, char* p, int len)
+{
+       if (len == -1)
+               len = strlen(p);
+       if ((s->size+len) > s->maxsize)
+               len = s->maxsize - s->size;
+       memcpy(s->buffer + s->size, p, len);
+       s->size += len;
+       s->buffer[s->size] = '\0';
+}
+
+/* As above, always autocalculate. */
+
+#define string_append_s(s, p) string_append_sn((s), (p), -1)
+
+/* Appends a UTF8 character --- which may make the size change by more than 1!
+ * If the string overflows, the last character may become invalid. */
+
+static void string_append_c(struct string* s, int c)
+{
+       char UmlChar[5];
+       int len = 0;
+
+       /* Don't do anything if there's no room. */
+
+       if (s->size == s->maxsize)
+               return;
+
+       if (c <= 0x7F)
+       {
+               /* This is the most common case, so we optimise it. */
+
+               s->buffer[s->size++] = c;
+               s->buffer[s->size] = 0;
+               return;
+       }
+       else if (c <= 0x7FF)
+       {
+               UmlChar[0] = 0xC0 | (c >> 6);
+               UmlChar[1] = 0x80 | (c & 0x3F);
+               len = 2;
+       }
+       else if (c <= 0xFFFF)
+       {
+               UmlChar[0] = 0xE0 | (c >> 12);
+               UmlChar[1] = 0x80 | ((c >> 6) & 0x3f);
+               UmlChar[2] = 0x80 | (c & 0x3f);
+               len = 3;
+       }
+       else
+       {
+               UmlChar[0] = 0xf0 | c >> 18;
+               UmlChar[1] = 0x80 | ((c >> 12) & 0x3f);
+               UmlChar[2] = 0x80 | ((c >> 6) & 0x3f);
+               UmlChar[3] = 0x80 | (c & 0x3f);
+               len = 4;
+       }
+
+       string_append_sn(s, UmlChar, len);
+}      
+
+/* Reads a UTF8 character from a char*, advancing the pointer. */
+
+int utf8_getc(char** ptr)
+{
+       unsigned char* p = (unsigned char*) *ptr;
+       unsigned char c, r;
+       int v, m;
+
+       for (;;)
+       {
+               r = *p++;
+       loop:
+               if (r < 0x80)
+               {
+                       *ptr = (char*) p;
+                       v = r;
+                       break;
+               }
+               else if (r < 0xf8)
+               {
+                       /* valid start char? (max 4 octets) */
+                       v = r;
+                       m = 0x7f80;     /* used to mask out the length bits */
+                       do {
+                               c = *p++;
+                               if ((c & 0xc0) != 0x80)
+                               {
+                                       r = c;
+                                       goto loop;
+                               }
+                               v = (v<<6) | (c & 0x3f);
+                               r<<=1;
+                               m<<=5;
+                       } while (r & 0x40);
+                       
+                       *ptr = (char*)p;
+
+                       v &= ~m;
+                       break;
+               }
+       }
+
+       return v;
+}
+
+/* IMAP name safety */
+
+/* IMAP has certain special requirements in its character set, which means we
+ * have to do a fair bit of work to convert Citadel's UTF8 strings to IMAP
+ * strings. The next two routines (and their data tables) do that.
+ */
+
+static char *utf7_alphabet =
+       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
+
+static unsigned char utf7_rank[256] = {
+       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3E,0x3F,0xFF,0xFF,0xFF,
+       0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+       0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,
+       0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF,
+       0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
+       0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0xFF,0xFF,0xFF,0xFF,0xFF,
+       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+       0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+};
+
+/* Base64 helpers. */
+
+static void utf7_closeb64(struct string* out, int v, int i)
+{
+       int x;
+
+       if (i > 0)
+       {
+               x = (v << (6-i)) & 0x3F;
+               string_append_c(out, utf7_alphabet[x]);
+       }
+       string_append_c(out, '-');
+}
+
+/* Convert from a Citadel name to an IMAP-safe name. Returns the end
+ * of the destination.
+ */
+static char* toimap(char* destp, char* destend, char* src)
+{
+       struct string dest;
+       int state = 0;
+       int v = 0;
+       int i = 0;
+
+       *destp = 0;
+       string_init(&dest, destp, destend-destp);
+       /* syslog(LOG_DEBUG, "toimap %s", src); */
+
+       for (;;)
+       {
+               int c = utf8_getc(&src);
+               if (c == '\0')
+                       break;
+
+               if (c >= 0x20 && c <= 0x7e)
+               {
+                       if (state == 1)
+                       {
+                               utf7_closeb64(&dest, v, i);
+                               state = 0;
+                               i = 0;
+                       }
+
+                       switch (c)
+                       {
+                               case '&':
+                                       string_append_sn(&dest, "&-", 2);
+                                       break;
+
+                               case '/':
+                                       /* Citadel extension: / becomes |, because /
+                                        * isn't valid as part of an IMAP name. */
+
+                                       c = '|';
+                                       goto defaultcase;
+
+                               case '\\':
+                                       /* Citadel extension: backslashes mark folder
+                                        * seperators in the IMAP subfolder emulation
+                                        * hack. We turn them into / characters,
+                                        * *except* if it's the last character in the
+                                        * string. */
+
+                                       if (*src != '\0')
+                                               c = '/';
+                                       /* fall through */
+
+                               default:
+                               defaultcase:
+                                       string_append_c(&dest, c);
+                       }
+               }
+               else
+               {
+                       if (state == 0)
+                       {
+                               string_append_c(&dest, '&');
+                               state = 1;
+                       }
+                       v = (v << 16) | c;
+                       i += 16;
+                       while (i >= 6)
+                       {
+                               int x = (v >> (i-6)) & 0x3f;
+                               string_append_c(&dest, utf7_alphabet[x]);
+                               i -= 6;
+                       }
+               }
+       }
+
+       if (state == 1)
+               utf7_closeb64(&dest, v, i);
+       /* syslog(LOG_DEBUG, "    -> %s", destp); */
+       return string_end(&dest);
+}
+
+/* Convert from an IMAP-safe name back into a Citadel name. Returns the end of the destination. */
+
+static int cfrommap(int c);
+static char* fromimap(char* destp, char* destend, const char* src)
+{
+       struct string dest;
+       unsigned const char *p = (unsigned const char*) src;
+       int v = 0;
+       int i = 0;
+       int state = 0;
+       int c;
+
+       *destp = 0;
+       string_init(&dest, destp, destend-destp);
+       /* syslog(LOG_DEBUG, "fromimap %s", src); */
+
+       do {
+               c = *p++;
+               switch (state)
+               {
+                       case 0:
+                               /* US-ASCII characters. */
+                               
+                               if (c == '&')
+                                       state = 1;
+                               else
+                                       string_append_c(&dest, cfrommap(c));
+                               break;
+
+                       case 1:
+                               if (c == '-')
+                               {
+                                       string_append_c(&dest, '&');
+                                       state = 0;
+                               }
+                               else if (utf7_rank[c] != 0xff)
+                               {
+                                       v = utf7_rank[c];
+                                       i = 6;
+                                       state = 2;
+                               }
+                               else
+                               {
+                                       /* invalid char */
+                                       string_append_sn(&dest, "&-", 2);
+                                       state = 0;
+                               }
+                               break;
+                               
+                       case 2:
+                               if (c == '-')
+                                       state = 0;
+                               else if (utf7_rank[c] != 0xFF)
+                               {
+                                       v = (v<<6) | utf7_rank[c];
+                                       i += 6;
+                                       if (i >= 16)
+                                       {
+                                               int x = (v >> (i-16)) & 0xFFFF;
+                                               string_append_c(&dest, cfrommap(x));
+                                               i -= 16;
+                                       }
+                               }
+                               else
+                               {
+                                       string_append_c(&dest, cfrommap(c));
+                                       state = 0;
+                               }
+                               break;
+                       }
+       } while (c != '\0');
+
+       /* syslog(LOG_DEBUG, "      -> %s", destp); */
+       return string_end(&dest);
+}
+
+/* Undoes the special character conversion. */
+static int cfrommap(int c)
+{
+       switch (c)
+       {
+               case '|':       return '/';
+               case '/':       return '\\';
+       }
+       return c;               
+}
+
+
+/* Break a command down into tokens, unquoting any escaped characters. */
+void MakeStringOf(StrBuf *Buf, int skip)
+{
+       int i;
+       citimap_command *Cmd = &IMAP->Cmd;
+
+       for (i=skip; i<Cmd->num_parms; ++i) {
+               StrBufAppendBufPlain(Buf, Cmd->Params[i].Key, Cmd->Params[i].len, 0);
+               if (i < (Cmd->num_parms-1)) StrBufAppendBufPlain(Buf, HKEY(" "), 0);
+       }
+}
+
+
+void TokenCutRight(citimap_command *Cmd, 
+                  ConstStr *CutMe,
+                  int n)
+{
+       const char *CutAt;
+
+       if (CutMe->len < n) {
+               CutAt = CutMe->Key;
+               CutMe->len = 0;
+       }
+       else {
+               CutAt = CutMe->Key + CutMe->len - n;
+               CutMe->len -= n;
+       }
+       StrBufPeek(Cmd->CmdBuf, CutAt, -1, '\0');
+}
+
+void TokenCutLeft(citimap_command *Cmd, 
+                 ConstStr *CutMe,
+                 int n)
+{
+       if (CutMe->len < n) {
+               CutMe->Key += CutMe->len;
+               CutMe->len = 0;
+       }
+       else {
+               CutMe->Key += n;
+               CutMe->len -= n;
+       }
+}
+
+
+
+int CmdAdjust(citimap_command *Cmd, 
+             int nArgs,
+             int Realloc)
+{
+       ConstStr *Params;
+       if (nArgs > Cmd->avail_parms) {
+               Params = (ConstStr*) malloc(sizeof(ConstStr) * nArgs);
+               if (Realloc) {
+                       memcpy(Params, 
+                              Cmd->Params, 
+                              sizeof(ConstStr) * Cmd->avail_parms);
+
+                       memset(Cmd->Params + 
+                              sizeof(ConstStr) * Cmd->avail_parms,
+                              0, 
+                              sizeof(ConstStr) * nArgs - 
+                              sizeof(ConstStr) * Cmd->avail_parms 
+                               );
+               }
+               else {
+                       Cmd->num_parms = 0;
+                       memset(Params, 0, 
+                              sizeof(ConstStr) * nArgs);
+               }
+               Cmd->avail_parms = nArgs;
+               if (Cmd->Params != NULL)
+                       free (Cmd->Params);
+               Cmd->Params = Params;
+       }
+       else {
+               if (!Realloc) {
+                       memset(Cmd->Params, 
+                              0,
+                              sizeof(ConstStr) * Cmd->avail_parms);
+                       Cmd->num_parms = 0;
+               }
+       }
+       return Cmd->avail_parms;
+}
+
+int imap_parameterize(citimap_command *Cmd)
+{
+       int nArgs;
+       const char *In, *End;
+
+       In = ChrPtr(Cmd->CmdBuf);
+       End = In + StrLength(Cmd->CmdBuf);
+
+       /* we start with 10 chars per arg, maybe we need to realloc later. */
+       nArgs = StrLength(Cmd->CmdBuf) / 10 + 10;
+       nArgs = CmdAdjust(Cmd, nArgs, 0);
+       while (In < End)
+       {
+               /* Skip whitespace. */
+               while (isspace(*In))
+                       In++;
+               if (*In == '\0')
+                       break;
+
+               /* Found the start of a token. */
+               
+               Cmd->Params[Cmd->num_parms].Key = In;
+
+               /* Read in the token. */
+
+               for (;;)
+               {
+                       if (isspace(*In))
+                               break;
+                       
+                       if (*In == '\"')
+                       {
+                               /* Found a quoted section. */
+
+                               Cmd->Params[Cmd->num_parms].Key++; 
+                               //In++;
+                               for (;;)
+                               {
+                                       In++;
+                                       if (*In == '\"') {
+                                               StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
+                                               break;
+                                       }
+                                       else if (*In == '\\')
+                                               In++;
+
+                                       if (*In == '\0') {
+                                               Cmd->Params[Cmd->num_parms].len = 
+                                                       In - Cmd->Params[Cmd->num_parms].Key;
+                                               Cmd->num_parms++;
+                                               return Cmd->num_parms;
+                                       }
+                               }
+                               break;
+                       }
+                       else if (*In == '\\')
+                       {
+                               In++;
+                       }
+
+                       if (*In == '\0') {
+                               Cmd->Params[Cmd->num_parms].len = 
+                                       In - Cmd->Params[Cmd->num_parms].Key;
+                               Cmd->num_parms++;
+                               return Cmd->num_parms;
+                       }
+                       In++;
+               }
+               StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
+               Cmd->Params[Cmd->num_parms].len = 
+                       In - Cmd->Params[Cmd->num_parms].Key;
+               if (Cmd->num_parms + 1 >= Cmd->avail_parms) {
+                       nArgs = CmdAdjust(Cmd, nArgs * 2, 1);
+               }
+               Cmd->num_parms ++;
+               In++;
+       }
+       return Cmd->num_parms;
+}
+
+
+/* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */
+long imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
+{
+       char* bufend = buf+bufsize;
+       struct floor *fl;
+       char* p = buf;
+       const char *pend;
+
+       /* For mailboxes, just do it straight.
+        * Do the Cyrus-compatible thing: all private folders are
+        * subfolders of INBOX. */
+
+       if (qrbuf->QRflags & QR_MAILBOX)
+       {
+               if (strcasecmp(qrbuf->QRname+11, MAILROOM) == 0)
+               {
+                       pend = toimap(p, bufend, "INBOX");
+                       return pend - buf;
+               }
+               else
+               {
+                       p = toimap(p, bufend, "INBOX");
+                       if (p < bufend)
+                               *p++ = '/';
+                       pend = toimap(p, bufend, qrbuf->QRname+11);
+                       return pend - buf;
+               }
+       }
+       else
+       {
+               /* Otherwise, prefix the floor name as a "public folders" moniker. */
+
+               fl = CtdlGetCachedFloor(qrbuf->QRfloor);
+               p = toimap(p, bufend, fl->f_name);
+               if (p < bufend)
+                       *p++ = '/';
+               pend = toimap(p, bufend, qrbuf->QRname);
+               return pend - buf;
+       }
+}
+
+/*
+ * Convert an inputted folder name to our best guess as to what an equivalent
+ * room name should be.
+ *
+ * If an error occurs, it returns -1.  Otherwise...
+ *
+ * The lower eight bits of the return value are the floor number on which the
+ * room most likely resides.   The upper eight bits may contain flags,
+ * including IR_MAILBOX if we're dealing with a personal room.
+ *
+ */
+int imap_roomname(char *rbuf, int bufsize, const char *foldername)
+{
+       int levels;
+       char floorname[ROOMNAMELEN*2];
+       char roomname[ROOMNAMELEN];
+       int i;
+       struct floor *fl;
+       int ret = (-1);
+
+       if (foldername == NULL)
+               return(-1);
+
+       /* Unmunge the entire string into the output buffer. */
+
+       fromimap(rbuf, rbuf+bufsize, foldername);
+
+       /* Is this an IMAP inbox? */
+
+       if (strncasecmp(rbuf, "INBOX", 5) == 0)
+       {
+               if (rbuf[5] == 0)
+               {
+                       /* It's the system inbox. */
+
+                       safestrncpy(rbuf, MAILROOM, bufsize);
+                       ret = (0 | IR_MAILBOX);
+                       goto exit;
+               }
+               else if (rbuf[5] == FDELIM)
+               {
+                       /* It's another personal mail folder. */
+
+                       safestrncpy(rbuf, rbuf+6, bufsize);
+                       ret = (0 | IR_MAILBOX);
+                       goto exit;
+               }
+
+               /* If we get here, the folder just happens to start with INBOX
+                * --- fall through. */
+       }
+
+       /* Is this a multi-level room name? */
+
+       levels = num_tokens(rbuf, FDELIM);
+       if (levels > 1)
+       {
+               long len;
+               /* Extract the main room name. */
+               
+               len = extract_token(floorname, rbuf, 0, FDELIM, sizeof floorname);
+               if (len < 0) len = 0;
+               safestrncpy(roomname, &rbuf[len  + 1], sizeof(roomname));
+
+               /* Try and find it on any floor. */
+               
+               for (i = 0; i < MAXFLOORS; ++i)
+               {
+                       fl = CtdlGetCachedFloor(i);
+                       if (fl->f_flags & F_INUSE)
+                       {
+                               if (strcasecmp(floorname, fl->f_name) == 0)
+                               {
+                                       /* Got it! */
+
+                                       safestrncpy(rbuf, roomname, bufsize);
+                                       ret = i;
+                                       goto exit;
+                               }
+                       }
+               }
+       }
+
+       /* Meh. It's either not a multi-level room name, or else we
+        * couldn't find it.
+        */
+       ret = (0 | IR_MAILBOX);
+
+exit:
+       syslog(LOG_DEBUG, "(That translates to \"%s\")", rbuf);
+       return(ret);
+}
+
+
+/*
+ * Determine whether the supplied string is a valid message set.
+ * If the string contains only numbers, colons, commas, and asterisks,
+ * return 1 for a valid message set.  If any other character is found, 
+ * return 0.
+ */
+int imap_is_message_set(const char *buf)
+{
+       int i;
+
+       if (buf == NULL)
+               return (0);     /* stupidity checks */
+       if (IsEmptyStr(buf))
+               return (0);
+
+       if (!strcasecmp(buf, "ALL"))
+               return (1);     /* macro?  why?  */
+
+       for (i = 0; buf[i]; ++i) {      /* now start the scan */
+               if (
+                          (!isdigit(buf[i]))
+                          && (buf[i] != ':')
+                          && (buf[i] != ',')
+                          && (buf[i] != '*')
+                   )
+                       return (0);
+       }
+
+       return (1);             /* looks like we're good */
+}
+
+
+/*
+ * imap_match.c, based on wildmat.c from INN
+ * hacked for Citadel/IMAP by Daniel Malament
+ */
+
+/* note: not all return statements use these; don't change them */
+#define WILDMAT_TRUE   1
+#define WILDMAT_FALSE  0
+#define WILDMAT_ABORT  -1
+#define WILDMAT_DELIM  '/'
+
+/*
+ * Match text and p, return TRUE, FALSE, or ABORT.
+ */
+static int do_imap_match(const char *supplied_text, const char *supplied_p)
+{
+       int matched, i;
+       char lcase_text[SIZ], lcase_p[SIZ];
+       char *text;
+       char *p;
+       
+       /* Copy both strings and lowercase them, in order to
+        * make this entire operation case-insensitive.
+        */
+       for (i=0; 
+            ((supplied_text[i] != '\0') && 
+             (i < sizeof(lcase_text)));
+            ++i)
+               lcase_text[i] = tolower(supplied_text[i]);
+       lcase_text[i] = '\0';
+
+       for (i=0; 
+            ((supplied_p[i] != '\0') && 
+             (i < sizeof(lcase_p))); 
+            ++i)
+               lcase_p[i] = tolower(supplied_p[i]);
+       lcase_p[i] = '\0';
+
+       /* Start matching */
+       for (p = lcase_p, text = lcase_text; 
+            !IsEmptyStr(p) && !IsEmptyStr(text); 
+            text++, p++) {
+               if ((*text == '\0') && (*p != '*') && (*p != '%')) {
+                       return WILDMAT_ABORT;
+               }
+               switch (*p) {
+               default:
+                       if (*text != *p) {
+                               return WILDMAT_FALSE;
+                       }
+                       continue;
+               case '*':
+star:
+                       while (++p, ((*p == '*') || (*p == '%'))) {
+                               /* Consecutive stars or %'s act
+                                * just like one star.
+                                */
+                               continue;
+                       }
+                       if (*p == '\0') {
+                               /* Trailing star matches everything. */
+                               return WILDMAT_TRUE;
+                       }
+                       while (*text) {
+                               if ((matched = do_imap_match(text++, p))
+                                  != WILDMAT_FALSE) {
+                                       return matched;
+                               }
+                       }
+                       return WILDMAT_ABORT;
+               case '%':
+                       while (++p, (!IsEmptyStr(p) && ((*p == '*') || (*p == '%'))))
+                       {
+                               /* Consecutive %'s act just like one, but even
+                                * a single star makes the sequence act like
+                                * one star, instead.
+                                */
+                               if (*p == '*') {
+                                       goto star;
+                               }
+                               continue;
+                       }
+                       if (*p == '\0') {
+                               /*
+                                * Trailing % matches everything
+                                * without a delimiter.
+                                */
+                               while (!IsEmptyStr(text)) {
+                                       if (*text == WILDMAT_DELIM) {
+                                               return WILDMAT_FALSE;
+                                       }
+                                       text++;
+                               }
+                               return WILDMAT_TRUE;
+                       }
+                       while (!IsEmptyStr(text) &&
+                              /* make sure text - 1 isn't before lcase_p */
+                              ((text == lcase_text) || (*(text - 1) != WILDMAT_DELIM)))
+                       {
+                               if ((matched = do_imap_match(text++, p))
+                                  != WILDMAT_FALSE) {
+                                       return matched;
+                               }
+                       }
+                       return WILDMAT_ABORT;
+               }
+       }
+
+       if ((*text == '\0') && (*p == '\0')) return WILDMAT_TRUE;
+       else return WILDMAT_FALSE;
+}
+
+
+/*
+ * Support function for mailbox pattern name matching in LIST and LSUB
+ * Returns nonzero if the supplied mailbox name matches the supplied pattern.
+ */
+int imap_mailbox_matches_pattern(const char *pattern, char *mailboxname)
+{
+       /* handle just-star case quickly */
+       if ((pattern[0] == '*') && (pattern[1] == '\0')) {
+               return WILDMAT_TRUE;
+       }
+       return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
+}
+
+
+
+/*
+ * Compare an IMAP date string (date only, no time) to the date found in
+ * a Unix timestamp.
+ */
+int imap_datecmp(const char *datestr, time_t msgtime) {
+       char daystr[256];
+       char monthstr[256];
+       char yearstr[256];
+       int i;
+       int day, month, year;
+       int msgday, msgmonth, msgyear;
+       struct tm msgtm;
+
+       char *imap_datecmp_ascmonths[12] = {
+               "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+       };
+
+       if (datestr == NULL) return(0);
+
+       /* Expecting a date in the form dd-Mmm-yyyy */
+       extract_token(daystr, datestr, 0, '-', sizeof daystr);
+       extract_token(monthstr, datestr, 1, '-', sizeof monthstr);
+       extract_token(yearstr, datestr, 2, '-', sizeof yearstr);
+
+       day = atoi(daystr);
+       year = atoi(yearstr);
+       month = 0;
+       for (i=0; i<12; ++i) {
+               if (!strcasecmp(monthstr, imap_datecmp_ascmonths[i])) {
+                       month = i;
+               }
+       }
+
+       /* Extract day/month/year from message timestamp */
+       localtime_r(&msgtime, &msgtm);
+       msgday = msgtm.tm_mday;
+       msgmonth = msgtm.tm_mon;
+       msgyear = msgtm.tm_year + 1900;
+
+       /* Now start comparing */
+
+       if (year < msgyear) return(+1);
+       if (year > msgyear) return(-1);
+
+       if (month < msgmonth) return(+1);
+       if (month > msgmonth) return(-1);
+
+       if (day < msgday) return(+1);
+       if (day > msgday) return(-1);
+
+       return(0);
+}
+
+
+void IAPrintf(const char *Format, ...)
+{
+       va_list arg_ptr;
+       
+       va_start(arg_ptr, Format);
+       StrBufVAppendPrintf(IMAP->Reply, Format, arg_ptr);
+       va_end(arg_ptr);
+}
+
+
+void iaputs(const char *Str, long Len)
+{
+       StrBufAppendBufPlain(IMAP->Reply, Str, Len, 0);
+}
+
+
+void ireply(const char *Msg, long len)
+{
+       citimap *Imap = IMAP;
+
+       StrBufAppendBufPlain(Imap->Reply, 
+                            CKEY(Imap->Cmd.Params[0]), 0);
+       StrBufAppendBufPlain(Imap->Reply, 
+                            HKEY(" "), 0);
+       StrBufAppendBufPlain(Imap->Reply, 
+                            Msg, len, 0);
+       
+       StrBufAppendBufPlain(Imap->Reply, 
+                            HKEY("\r\n"), 0);
+       
+}
+
+
+void IReplyPrintf(const char *Format, ...)
+{
+       citimap *Imap = IMAP;
+       va_list arg_ptr;
+       
+
+       StrBufAppendBufPlain(Imap->Reply, 
+                            CKEY(Imap->Cmd.Params[0]), 0);
+
+       StrBufAppendBufPlain(Imap->Reply, 
+                            HKEY(" "), 0);
+
+       va_start(arg_ptr, Format);
+       StrBufVAppendPrintf(IMAP->Reply, Format, arg_ptr);
+       va_end(arg_ptr);
+       
+       StrBufAppendBufPlain(Imap->Reply, 
+                            HKEY("\r\n"), 0);
+       
+}
+
+
+/* Output a string to the IMAP client, either as a literal or quoted.
+ * (We do a literal if it has any double-quotes or backslashes.) */
+void IPutStr(const char *Msg, long Len)
+{
+       int i;
+       int is_literal = 0;
+       citimap *Imap = IMAP;
+
+       
+       if ((Msg == NULL) || (Len == 0))
+       {       /* yeah, we handle this */
+               StrBufAppendBufPlain(Imap->Reply, HKEY("NIL"), 0);
+               return;
+       }
+
+       for (i = 0; i < Len; ++i) {
+               if ((Msg[i] == '\"') || (Msg[i] == '\\'))
+                       is_literal = 1;
+       }
+
+       if (is_literal) {
+               StrBufAppendPrintf(Imap->Reply, "{%ld}\r\n", Len);
+               StrBufAppendBufPlain(Imap->Reply, Msg, Len, 0);
+       } else {
+               StrBufAppendBufPlain(Imap->Reply, 
+                                    HKEY("\""), 0);
+               StrBufAppendBufPlain(Imap->Reply, 
+                                    Msg, Len, 0);
+               StrBufAppendBufPlain(Imap->Reply, 
+                                    HKEY("\""), 0);
+       }
+}
+
+void IUnbuffer (void)
+{
+       citimap *Imap = IMAP;
+
+       cputbuf(Imap->Reply);
+       FlushStrBuf(Imap->Reply);
+}
diff --git a/citadel/server/modules/imap/imap_tools.h b/citadel/server/modules/imap/imap_tools.h
new file mode 100644 (file)
index 0000000..2117a6e
--- /dev/null
@@ -0,0 +1,55 @@
+
+/* 
+ * since we work with shifted pointers to ConstStrs in some places, 
+ * we can't just say we want to cut the n'th of Cmd, we have to pass it in
+ * and rely on that CutMe references Cmd->CmdBuf; else peek won't work out
+ * and len will differ.
+ */
+void TokenCutRight(citimap_command *Cmd, 
+                  ConstStr *CutMe,
+                  int n);
+/*
+ * since we just move Key around here, Cmd is just here so the syntax is identical.
+ */
+void TokenCutLeft(citimap_command *Cmd, 
+                 ConstStr *CutMe,
+                 int n);
+void MakeStringOf(StrBuf *Buf, int skip);
+
+int CmdAdjust(citimap_command *Cmd, 
+             int nArgs,
+             int Realloc);
+
+
+void imap_strout(ConstStr *args);
+void imap_strbuffer(StrBuf *Reply, ConstStr *args);
+void plain_imap_strbuffer(StrBuf *Reply, char *buf);
+int imap_parameterize(citimap_command *Cmd);
+long imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf);
+int imap_roomname(char *buf, int bufsize, const char *foldername);
+int imap_is_message_set(const char *);
+int imap_mailbox_matches_pattern(const char *pattern, char *mailboxname);
+int imap_datecmp(const char *datestr, time_t msgtime);
+
+
+/* Imap Append Printf, send to the outbuffer */
+void IAPrintf(const char *Format, ...) __attribute__((__format__(__printf__,1,2)));
+
+void iaputs(const char *Str, long Len);
+#define IAPuts(Msg) iaputs(HKEY(Msg))
+/* give it a naughty name since its ugly. */
+#define _iaputs(Msg) iaputs(Msg, strlen(Msg))
+
+/* outputs a static message prepended by the sequence no */
+void ireply(const char *Msg, long len);
+#define IReply(msg) ireply(HKEY(msg))
+/* outputs a dynamic message prepended by the sequence no */
+void IReplyPrintf(const char *Format, ...);
+
+
+/* output a string like that {%ld}%s */
+void IPutStr(const char *Msg, long Len);
+#define IPutCStr(_ConstStr) IPutStr(CKEY(_ConstStr))
+#define IPutCParamStr(n) IPutStr(CKEY(Params[n]))
+#define IPutMsgField(Which) IPutStr(CM_KEY(msg, Which))
+void IUnbuffer (void);
diff --git a/citadel/server/modules/imap/serv_imap.c b/citadel/server/modules/imap/serv_imap.c
new file mode 100644 (file)
index 0000000..048e470
--- /dev/null
@@ -0,0 +1,1719 @@
+/*
+ * IMAP server for the Citadel system
+ *
+ * Copyright (C) 2000-2022 by Art Cancro and others.
+ * This code is released under the terms of the GNU General Public License.
+ *
+ * WARNING: the IMAP protocol is badly designed.  No implementation of it
+ * is perfect.  Indeed, with so much gratuitous complexity, *all* IMAP
+ * implementations have bugs.
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "imap_list.h"
+#include "imap_fetch.h"
+#include "imap_search.h"
+#include "imap_store.h"
+#include "imap_acl.h"
+#include "imap_metadata.h"
+#include "imap_misc.h"
+
+#include "../../ctdl_module.h"
+HashList *ImapCmds = NULL;
+void registerImapCMD(const char *First, long FLen, 
+                    const char *Second, long SLen,
+                    imap_handler H,
+                    int Flags)
+{
+       imap_handler_hook *h;
+
+       h = (imap_handler_hook*) malloc(sizeof(imap_handler_hook));
+       memset(h, 0, sizeof(imap_handler_hook));
+
+       h->Flags = Flags;
+       h->h = H;
+       if (SLen == 0) {
+               Put(ImapCmds, First, FLen, h, NULL);
+       }
+       else {
+               char CMD[SIZ];
+               memcpy(CMD, First, FLen);
+               memcpy(CMD+FLen, Second, SLen);
+               CMD[FLen+SLen] = '\0';
+               Put(ImapCmds, CMD, FLen + SLen, h, NULL);
+       }
+}
+
+
+const imap_handler_hook *imap_lookup(int num_parms, ConstStr *Params)
+{
+       struct CitContext *CCC = CC;
+       void *v;
+       citimap *Imap = CCCIMAP;
+
+       if (num_parms < 1)
+               return NULL;
+
+       /* we abuse the Reply-buffer for uppercasing... */
+       StrBufPlain(Imap->Reply, CKEY(Params[1]));
+       StrBufUpCase(Imap->Reply);
+
+       syslog(LOG_DEBUG, "---- Looking up [%s] -----", ChrPtr(Imap->Reply));
+       if (GetHash(ImapCmds, SKEY(Imap->Reply), &v))
+       {
+               syslog(LOG_DEBUG, "Found."); 
+               FlushStrBuf(Imap->Reply);
+               return (imap_handler_hook *) v;
+       }
+
+       if (num_parms == 1)
+       {
+               syslog(LOG_DEBUG, "NOT Found."); 
+               FlushStrBuf(Imap->Reply);
+               return NULL;
+       }
+       
+       syslog(LOG_DEBUG, "---- Looking up [%s] -----", ChrPtr(Imap->Reply));
+       StrBufAppendBufPlain(Imap->Reply, CKEY(Params[2]), 0);
+       StrBufUpCase(Imap->Reply);
+       if (GetHash(ImapCmds, SKEY(Imap->Reply), &v))
+       {
+               syslog(LOG_DEBUG, "Found."); 
+               FlushStrBuf(Imap->Reply);
+               return (imap_handler_hook *) v;
+       }
+       syslog(LOG_DEBUG, "NOT Found."); 
+       FlushStrBuf(Imap->Reply);
+               return NULL;
+}
+
+/* imap_rename() uses this struct containing list of rooms to rename */
+struct irl {
+       struct irl *next;
+       char irl_oldroom[ROOMNAMELEN];
+       char irl_newroom[ROOMNAMELEN];
+       int irl_newfloor;
+};
+
+/* Data which is passed between imap_rename() and imap_rename_backend() */
+typedef struct __irlparms {
+       const char *oldname;
+       long oldnamelen;
+       const char *newname;
+       long newnamelen;
+       struct irl **irl;
+}irlparms;
+
+
+/*
+ * If there is a message ID map in memory, free it
+ */
+void imap_free_msgids(void)
+{
+       citimap *Imap = IMAP;
+       if (Imap->msgids != NULL) {
+               free(Imap->msgids);
+               Imap->msgids = NULL;
+               Imap->num_msgs = 0;
+               Imap->num_alloc = 0;
+       }
+       if (Imap->flags != NULL) {
+               free(Imap->flags);
+               Imap->flags = NULL;
+       }
+       Imap->last_mtime = (-1);
+}
+
+
+/*
+ * If there is a transmitted message in memory, free it
+ */
+void imap_free_transmitted_message(void)
+{
+       FreeStrBuf(&IMAP->TransmittedMessage);
+}
+
+
+/*
+ * Set the \Seen, \Recent. and \Answered flags, based on the sequence
+ * sets stored in the visit record for this user/room.  Note that we have
+ * to parse each sequence set manually here, because calling the utility
+ * function is_msg_in_sequence_set() over and over again is too expensive.
+ *
+ * first_msg should be set to 0 to rescan the flags for every message in the
+ * room, or some other value if we're only interested in an incremental
+ * update.
+ */
+void imap_set_seen_flags(int first_msg)
+{
+       citimap *Imap = IMAP;
+       visit vbuf;
+       int i;
+       int num_sets;
+       int s;
+       char setstr[64], lostr[64], histr[64];
+       long lo, hi;
+
+       if (Imap->num_msgs < 1) return;
+       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+
+       for (i = first_msg; i < Imap->num_msgs; ++i) {
+               Imap->flags[i] = Imap->flags[i] & ~IMAP_SEEN;
+               Imap->flags[i] |= IMAP_RECENT;
+               Imap->flags[i] = Imap->flags[i] & ~IMAP_ANSWERED;
+       }
+
+       /*
+        * Do the "\Seen" flag.
+        * (Any message not "\Seen" is considered "\Recent".)
+        */
+       num_sets = num_tokens(vbuf.v_seen, ',');
+       for (s=0; s<num_sets; ++s) {
+               extract_token(setstr, vbuf.v_seen, s, ',', sizeof setstr);
+
+               extract_token(lostr, setstr, 0, ':', sizeof lostr);
+               if (num_tokens(setstr, ':') >= 2) {
+                       extract_token(histr, setstr, 1, ':', sizeof histr);
+                       if (!strcmp(histr, "*")) {
+                               snprintf(histr, sizeof histr, "%ld", LONG_MAX);
+                       }
+               } 
+               else {
+                       strcpy(histr, lostr);
+               }
+               lo = atol(lostr);
+               hi = atol(histr);
+
+               for (i = first_msg; i < Imap->num_msgs; ++i) {
+                       if ((Imap->msgids[i] >= lo) && (Imap->msgids[i] <= hi)){
+                               Imap->flags[i] |= IMAP_SEEN;
+                               Imap->flags[i] = Imap->flags[i] & ~IMAP_RECENT;
+                       }
+               }
+       }
+
+       /* Do the ANSWERED flag */
+       num_sets = num_tokens(vbuf.v_answered, ',');
+       for (s=0; s<num_sets; ++s) {
+               extract_token(setstr, vbuf.v_answered, s, ',', sizeof setstr);
+
+               extract_token(lostr, setstr, 0, ':', sizeof lostr);
+               if (num_tokens(setstr, ':') >= 2) {
+                       extract_token(histr, setstr, 1, ':', sizeof histr);
+                       if (!strcmp(histr, "*")) {
+                               snprintf(histr, sizeof histr, "%ld", LONG_MAX);
+                       }
+               } 
+               else {
+                       strcpy(histr, lostr);
+               }
+               lo = atol(lostr);
+               hi = atol(histr);
+
+               for (i = first_msg; i < Imap->num_msgs; ++i) {
+                       if ((Imap->msgids[i] >= lo) && (Imap->msgids[i] <= hi)){
+                               Imap->flags[i] |= IMAP_ANSWERED;
+                       }
+               }
+       }
+
+}
+
+
+
+/*
+ * Back end for imap_load_msgids()
+ *
+ * Optimization: instead of calling realloc() to add each message, we
+ * allocate space in the list for REALLOC_INCREMENT messages at a time.  This
+ * allows the mapping to proceed much faster.
+ */
+void imap_add_single_msgid(long msgnum, void *userdata)
+{
+       citimap *Imap = IMAP;
+
+       ++Imap->num_msgs;
+       if (Imap->num_msgs > Imap->num_alloc) {
+               Imap->num_alloc += REALLOC_INCREMENT;
+               Imap->msgids = realloc(Imap->msgids, (Imap->num_alloc * sizeof(long)) );
+               Imap->flags = realloc(Imap->flags, (Imap->num_alloc * sizeof(unsigned int)) );
+       }
+       Imap->msgids[Imap->num_msgs - 1] = msgnum;
+       Imap->flags[Imap->num_msgs - 1] = 0;
+}
+
+
+
+/*
+ * Set up a message ID map for the current room (folder)
+ */
+void imap_load_msgids(void)
+{
+       struct CitContext *CCC = CC;
+       struct cdbdata *cdbfr;
+       citimap *Imap = CCCIMAP;
+
+       if (Imap->selected == 0) {
+               syslog(LOG_ERR, "imap_load_msgids() can't run; no room selected");
+               return;
+       }
+
+       imap_free_msgids();     /* If there was already a map, free it */
+
+       /* Load the message list */
+       cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+       if (cdbfr != NULL) {
+               Imap->msgids = (long*)cdbfr->ptr;
+               Imap->num_msgs = cdbfr->len / sizeof(long);
+               Imap->num_alloc = cdbfr->len / sizeof(long);
+               cdbfr->ptr = NULL;
+               cdbfr->len = 0;
+               cdb_free(cdbfr);
+       }
+
+       if (Imap->num_msgs) {
+               Imap->flags = malloc(Imap->num_alloc * sizeof(unsigned int));
+               memset(Imap->flags, 0, (Imap->num_alloc * sizeof(unsigned int)) );
+       }
+
+       imap_set_seen_flags(0);
+}
+
+
+/*
+ * Re-scan the selected room (folder) and see if it's been changed at all
+ */
+void imap_rescan_msgids(void)
+{
+       struct CitContext *CCC = CC;
+       citimap *Imap = CCCIMAP;
+       int original_num_msgs = 0;
+       long original_highest = 0L;
+       int i, j, jstart;
+       int message_still_exists;
+       struct cdbdata *cdbfr;
+       long *msglist = NULL;
+       int num_msgs = 0;
+       int num_recent = 0;
+
+       if (Imap->selected == 0) {
+               syslog(LOG_ERR, "imap_load_msgids() can't run; no room selected");
+               return;
+       }
+
+       /*
+        * Check to see if the room's contents have changed.
+        * If not, we can avoid this rescan.
+        */
+       CtdlGetRoom(&CC->room, CC->room.QRname);
+       if (Imap->last_mtime == CC->room.QRmtime) {     /* No changes! */
+               return;
+       }
+
+       /* Load the *current* message list from disk, so we can compare it
+        * to what we have in memory.
+        */
+       cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+       if (cdbfr != NULL) {
+               msglist = (long*)cdbfr->ptr;
+               cdbfr->ptr = NULL;
+               num_msgs = cdbfr->len / sizeof(long);
+               cdbfr->len = 0;
+               cdb_free(cdbfr);
+       } else {
+               num_msgs = 0;
+       }
+
+       /*
+        * Check to see if any of the messages we know about have been expunged
+        */
+       if (Imap->num_msgs > 0) {
+               jstart = 0;
+               for (i = 0; i < Imap->num_msgs; ++i) {
+
+                       message_still_exists = 0;
+                       if (num_msgs > 0) {
+                               for (j = jstart; j < num_msgs; ++j) {
+                                       if (msglist[j] == Imap->msgids[i]) {
+                                               message_still_exists = 1;
+                                               jstart = j;
+                                               break;
+                                       }
+                               }
+                       }
+
+                       if (message_still_exists == 0) {
+                               IAPrintf("* %d EXPUNGE\r\n", i + 1);
+
+                               /* Here's some nice stupid nonsense.  When a
+                                * message is expunged, we have to slide all
+                                * the existing messages up in the message
+                                * array.
+                                */
+                               --Imap->num_msgs;
+                               memmove(&Imap->msgids[i],
+                                       &Imap->msgids[i + 1],
+                                       (sizeof(long) *
+                                        (Imap->num_msgs - i)));
+                               memmove(&Imap->flags[i],
+                                       &Imap->flags[i + 1],
+                                       (sizeof(unsigned int) *
+                                        (Imap->num_msgs - i)));
+                               --i;
+                       }
+
+               }
+       }
+
+       /*
+        * Remember how many messages were here before we re-scanned.
+        */
+       original_num_msgs = Imap->num_msgs;
+       if (Imap->num_msgs > 0) {
+               original_highest = Imap->msgids[Imap->num_msgs - 1];
+       } else {
+               original_highest = 0L;
+       }
+
+       /*
+        * Now peruse the room for *new* messages only.
+        * This logic is probably the cause of Bug # 368
+        * [ http://bugzilla.citadel.org/show_bug.cgi?id=368 ]
+        */
+       if (num_msgs > 0) {
+               for (j = 0; j < num_msgs; ++j) {
+                       if (msglist[j] > original_highest) {
+                               imap_add_single_msgid(msglist[j], NULL);
+                       }
+               }
+       }
+       imap_set_seen_flags(original_num_msgs);
+
+       /*
+        * If new messages have arrived, tell the client about them.
+        */
+       if (Imap->num_msgs > original_num_msgs) {
+
+               for (j = 0; j < num_msgs; ++j) {
+                       if (Imap->flags[j] & IMAP_RECENT) {
+                               ++num_recent;
+                       }
+               }
+
+               IAPrintf("* %d EXISTS\r\n", Imap->num_msgs);
+               IAPrintf("* %d RECENT\r\n", num_recent);
+       }
+
+       if (msglist != NULL) {
+               free(msglist);
+       }
+       Imap->last_mtime = CC->room.QRmtime;
+}
+
+
+/*
+ * This cleanup function blows away the temporary memory and files used by
+ * the IMAP server.
+ */
+void imap_cleanup_function(void)
+{
+       struct CitContext *CCC = CC;
+       citimap *Imap = CCCIMAP;
+
+       /* Don't do this stuff if this is not a Imap session! */
+       if (CC->h_command_function != imap_command_loop)
+               return;
+
+       /* If there is a mailbox selected, auto-expunge it. */
+       if (Imap->selected) {
+               imap_do_expunge();
+       }
+
+       syslog(LOG_DEBUG, "Performing IMAP cleanup hook");
+       imap_free_msgids();
+       imap_free_transmitted_message();
+
+       if (Imap->cached_rfc822 != NULL) {
+               FreeStrBuf(&Imap->cached_rfc822);
+               Imap->cached_rfc822_msgnum = (-1);
+               Imap->cached_rfc822_withbody = 0;
+       }
+
+       if (Imap->cached_body != NULL) {
+               free(Imap->cached_body);
+               Imap->cached_body = NULL;
+               Imap->cached_body_len = 0;
+               Imap->cached_bodymsgnum = (-1);
+       }
+       FreeStrBuf(&Imap->Cmd.CmdBuf);
+       FreeStrBuf(&Imap->Reply);
+       if (Imap->Cmd.Params != NULL) free(Imap->Cmd.Params);
+       free(Imap);
+       syslog(LOG_DEBUG, "Finished IMAP cleanup hook");
+}
+
+
+/*
+ * Does the actual work of the CAPABILITY command (because we need to
+ * output this stuff in other places as well)
+ */
+void imap_output_capability_string(void) {
+       IAPuts("CAPABILITY IMAP4REV1 NAMESPACE ID AUTH=PLAIN AUTH=LOGIN UIDPLUS");
+
+#ifdef HAVE_OPENSSL
+       if (!CC->redirect_ssl) IAPuts(" STARTTLS");
+#endif
+
+#ifndef DISABLE_IMAP_ACL
+       IAPuts(" ACL");
+#endif
+
+       /* We are building a partial implementation of METADATA for the sole purpose
+        * of interoperating with the ical/vcard version of the Bynari Insight Connector.
+        * It is not a full RFC5464 implementation, but it should refuse non-Bynari
+        * metadata in a compatible and graceful way.
+        */
+       IAPuts(" METADATA");
+
+       /*
+        * LIST-EXTENDED was originally going to be required by the METADATA extension.
+        * It was mercifully removed prior to the finalization of RFC5464.  We started
+        * implementing this but stopped when we learned that it would not be needed.
+        * If you uncomment this declaration you are responsible for writing a lot of new
+        * code.
+        *
+        * IAPuts(" LIST-EXTENDED")
+        */
+}
+
+
+/*
+ * implements the CAPABILITY command
+ */
+void imap_capability(int num_parms, ConstStr *Params)
+{
+       IAPuts("* ");
+       imap_output_capability_string();
+       IAPuts("\r\n");
+       IReply("OK CAPABILITY completed");
+}
+
+
+/*
+ * Implements the ID command (specified by RFC2971)
+ *
+ * We ignore the client-supplied information, and output a NIL response.
+ * Although this is technically a valid implementation of the extension, it
+ * is quite useless.  It exists only so that we may see which clients are
+ * making use of this extension.
+ * 
+ */
+void imap_id(int num_parms, ConstStr *Params)
+{
+       IAPuts("* ID NIL\r\n");
+       IReply("OK ID completed");
+}
+
+
+/*
+ * Here's where our IMAP session begins its happy day.
+ */
+void imap_greeting(void)
+{
+       citimap *Imap;
+       CitContext *CCC = CC;
+
+       strcpy(CCC->cs_clientname, "IMAP session");
+       CCC->session_specific_data = malloc(sizeof(citimap));
+       Imap = (citimap *)CCC->session_specific_data;
+       memset(Imap, 0, sizeof(citimap));
+       Imap->authstate = imap_as_normal;
+       Imap->cached_rfc822_msgnum = (-1);
+       Imap->cached_rfc822_withbody = 0;
+       Imap->Reply = NewStrBufPlain(NULL, SIZ * 10); /* 40k */
+
+       if (CCC->nologin)
+       {
+               IAPuts("* BYE; Server busy, try later\r\n");
+               CCC->kill_me = KILLME_NOLOGIN;
+               IUnbuffer();
+               return;
+       }
+
+       IAPuts("* OK [");
+       imap_output_capability_string();
+       IAPrintf("] %s IMAP4rev1 %s ready\r\n", CtdlGetConfigStr("c_fqdn"), CITADEL);
+       IUnbuffer();
+}
+
+
+/*
+ * IMAPS is just like IMAP, except it goes crypto right away.
+ */
+void imaps_greeting(void) {
+       CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
+#ifdef HAVE_OPENSSL
+       if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;          /* kill session if no crypto */
+#endif
+       imap_greeting();
+}
+
+
+/*
+ * implements the LOGIN command (ordinary username/password login)
+ */
+void imap_login(int num_parms, ConstStr *Params)
+{
+
+       switch (num_parms) {
+       case 3:
+               if (Params[2].Key[0] == '{') {
+                       IAPuts("+ go ahead\r\n");
+                       IMAP->authstate = imap_as_expecting_multilineusername;
+                       strcpy(IMAP->authseq, Params[0].Key);
+                       return;
+               }
+               else {
+                       IReply("BAD incorrect number of parameters");
+                       return;
+               }
+       case 4:
+               if (CtdlLoginExistingUser(Params[2].Key) == login_ok) {
+                       if (CtdlTryPassword(Params[3].Key, Params[3].len) == pass_ok) {
+                               /* hm, thats not doable by IReply :-( */
+                               IAPrintf("%s OK [", Params[0].Key);
+                               imap_output_capability_string();
+                               IAPrintf("] Hello, %s\r\n", CC->user.fullname);
+                               return;
+                       }
+                       else
+                       {
+                               IReplyPrintf("NO AUTHENTICATE %s failed", Params[3].Key);
+                               return;
+                       }
+               }
+
+               IReply("BAD Login incorrect");
+               return;
+       default:
+               IReply("BAD incorrect number of parameters");
+               return;
+       }
+
+}
+
+
+/*
+ * Implements the AUTHENTICATE command
+ */
+void imap_authenticate(int num_parms, ConstStr *Params)
+{
+       char UsrBuf[SIZ];
+
+       if (num_parms != 3) {
+               IReply("BAD incorrect number of parameters");
+               return;
+       }
+
+       if (CC->logged_in) {
+               IReply("BAD Already logged in.");
+               return;
+       }
+
+       if (!strcasecmp(Params[2].Key, "LOGIN")) {
+               size_t len = CtdlEncodeBase64(UsrBuf, "Username:", 9, 0);
+               if (UsrBuf[len - 1] == '\n') {
+                       UsrBuf[len - 1] = '\0';
+               }
+
+               IAPrintf("+ %s\r\n", UsrBuf);
+               IMAP->authstate = imap_as_expecting_username;
+               strcpy(IMAP->authseq, Params[0].Key);
+               return;
+       }
+
+       if (!strcasecmp(Params[2].Key, "PLAIN")) {
+               // size_t len = CtdlEncodeBase64(UsrBuf, "Username:", 9, 0);
+               // if (UsrBuf[len - 1] == '\n') {
+               //   UsrBuf[len - 1] = '\0';
+               // }
+               // IAPuts("+ %s\r\n", UsrBuf);
+               IAPuts("+ \r\n");
+               IMAP->authstate = imap_as_expecting_plainauth;
+               strcpy(IMAP->authseq, Params[0].Key);
+               return;
+       }
+
+       else {
+               IReplyPrintf("NO AUTHENTICATE %s failed",
+                            Params[1].Key);
+       }
+}
+
+
+void imap_auth_plain(void)
+{
+       citimap *Imap = IMAP;
+       const char *decoded_authstring;
+       char ident[256] = "";
+       char user[256] = "";
+       char pass[256] = "";
+       int result;
+       long decoded_len;
+       long len = 0;
+       long plen = 0;
+
+       memset(pass, 0, sizeof(pass));
+       decoded_len = StrBufDecodeBase64(Imap->Cmd.CmdBuf);
+
+       if (decoded_len > 0)
+       {
+               decoded_authstring = ChrPtr(Imap->Cmd.CmdBuf);
+
+               len = safestrncpy(ident, decoded_authstring, sizeof ident);
+
+               decoded_len -= len - 1;
+               decoded_authstring += len + 1;
+
+               if (decoded_len > 0)
+               {
+                       len = safestrncpy(user, decoded_authstring, sizeof user);
+
+                       decoded_authstring += len + 1;
+                       decoded_len -= len - 1;
+               }
+
+               if (decoded_len > 0)
+               {
+                       plen = safestrncpy(pass, decoded_authstring, sizeof pass);
+
+                       if (plen < 0)
+                               plen = sizeof(pass) - 1;
+               }
+       }
+       Imap->authstate = imap_as_normal;
+
+       if (!IsEmptyStr(ident)) {
+               result = CtdlLoginExistingUser(ident);
+       }
+       else {
+               result = CtdlLoginExistingUser(user);
+       }
+
+       if (result == login_ok) {
+               if (CtdlTryPassword(pass, plen) == pass_ok) {
+                       IAPrintf("%s OK authentication succeeded\r\n", Imap->authseq);
+                       return;
+               }
+       }
+       IAPrintf("%s NO authentication failed\r\n", Imap->authseq);
+}
+
+
+void imap_auth_login_user(long state)
+{
+       char PWBuf[SIZ];
+       citimap *Imap = IMAP;
+
+       switch (state){
+       case imap_as_expecting_username:
+               StrBufDecodeBase64(Imap->Cmd.CmdBuf);
+               CtdlLoginExistingUser(ChrPtr(Imap->Cmd.CmdBuf));
+               size_t len = CtdlEncodeBase64(PWBuf, "Password:", 9, 0);
+               if (PWBuf[len - 1] == '\n') {
+                       PWBuf[len - 1] = '\0';
+               }
+
+               IAPrintf("+ %s\r\n", PWBuf);
+               
+               Imap->authstate = imap_as_expecting_password;
+               return;
+       case imap_as_expecting_multilineusername:
+               extract_token(PWBuf, ChrPtr(Imap->Cmd.CmdBuf), 1, ' ', sizeof(PWBuf));
+               CtdlLoginExistingUser(ChrPtr(Imap->Cmd.CmdBuf));
+               IAPuts("+ go ahead\r\n");
+               Imap->authstate = imap_as_expecting_multilinepassword;
+               return;
+       }
+}
+
+
+void imap_auth_login_pass(long state)
+{
+       citimap *Imap = IMAP;
+       const char *pass = NULL;
+       long len = 0;
+
+       switch (state) {
+       default:
+       case imap_as_expecting_password:
+               StrBufDecodeBase64(Imap->Cmd.CmdBuf);
+               pass = ChrPtr(Imap->Cmd.CmdBuf);
+               len = StrLength(Imap->Cmd.CmdBuf);
+               break;
+       case imap_as_expecting_multilinepassword:
+               pass = ChrPtr(Imap->Cmd.CmdBuf);
+               len = StrLength(Imap->Cmd.CmdBuf);
+               break;
+       }
+       if (len > USERNAME_SIZE)
+               StrBufCutAt(Imap->Cmd.CmdBuf, USERNAME_SIZE, NULL);
+
+       if (CtdlTryPassword(pass, len) == pass_ok) {
+               IAPrintf("%s OK authentication succeeded\r\n", Imap->authseq);
+       } else {
+               IAPrintf("%s NO authentication failed\r\n", Imap->authseq);
+       }
+       Imap->authstate = imap_as_normal;
+       return;
+}
+
+
+/*
+ * implements the STARTTLS command (Citadel API version)
+ */
+void imap_starttls(int num_parms, ConstStr *Params)
+{
+       char ok_response[SIZ];
+       char nosup_response[SIZ];
+       char error_response[SIZ];
+
+       snprintf(ok_response, SIZ,      "%s OK begin TLS negotiation now\r\n",  Params[0].Key);
+       snprintf(nosup_response, SIZ,   "%s NO TLS not supported here\r\n",     Params[0].Key);
+       snprintf(error_response, SIZ,   "%s BAD Internal error\r\n",            Params[0].Key);
+       CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
+}
+
+
+/*
+ * implements the SELECT command
+ */
+void imap_select(int num_parms, ConstStr *Params)
+{
+       citimap *Imap = IMAP;
+       char towhere[ROOMNAMELEN];
+       char augmented_roomname[ROOMNAMELEN];
+       int c = 0;
+       int ok = 0;
+       int ra = 0;
+       struct ctdlroom QRscratch;
+       int msgs, new;
+       int i;
+
+       /* Convert the supplied folder name to a roomname */
+       i = imap_roomname(towhere, sizeof towhere, Params[2].Key);
+       if (i < 0) {
+               IReply("NO Invalid mailbox name.");
+               Imap->selected = 0;
+               return;
+       }
+
+       /* First try a regular match */
+       c = CtdlGetRoom(&QRscratch, towhere);
+
+       /* Then try a mailbox name match */
+       if (c != 0) {
+               CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CC->user, towhere);
+               c = CtdlGetRoom(&QRscratch, augmented_roomname);
+               if (c == 0) {
+                       safestrncpy(towhere, augmented_roomname, sizeof(towhere));
+               }
+       }
+
+       /* If the room exists, check security/access */
+       if (c == 0) {
+               /* See if there is an existing user/room relationship */
+               CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
+
+               /* normal clients have to pass through security */
+               if (ra & UA_KNOWN) {
+                       ok = 1;
+               }
+       }
+
+       /* Fail here if no such room */
+       if (!ok) {
+               IReply("NO ... no such room, or access denied");
+               return;
+       }
+
+       /* If we already had some other folder selected, auto-expunge it */
+       imap_do_expunge();
+
+       /*
+        * CtdlUserGoto() formally takes us to the desired room, happily returning
+        * the number of messages and number of new messages.
+        */
+       memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom));
+       CtdlUserGoto(NULL, 0, 0, &msgs, &new, NULL, NULL);
+       Imap->selected = 1;
+
+       if (!strcasecmp(Params[1].Key, "EXAMINE")) {
+               Imap->readonly = 1;
+       } else {
+               Imap->readonly = 0;
+       }
+
+       imap_load_msgids();
+       Imap->last_mtime = CC->room.QRmtime;
+
+       IAPrintf("* %d EXISTS\r\n", msgs);
+       IAPrintf("* %d RECENT\r\n", new);
+
+       IAPrintf("* OK [UIDVALIDITY %ld] UID validity status\r\n", GLOBAL_UIDVALIDITY_VALUE);
+       IAPrintf("* OK [UIDNEXT %ld] Predicted next UID\r\n", CtdlGetConfigLong("MMhighest") + 1);
+
+       /* Technically, \Deleted is a valid flag, but not a permanent flag,
+        * because we don't maintain its state across sessions.  Citadel
+        * automatically expunges mailboxes when they are de-selected.
+        * 
+        * Unfortunately, omitting \Deleted as a PERMANENTFLAGS flag causes
+        * some clients (particularly Thunderbird) to misbehave -- they simply
+        * elect not to transmit the flag at all.  So we have to advertise
+        * \Deleted as a PERMANENTFLAGS flag, even though it technically isn't.
+        */
+       IAPuts("* FLAGS (\\Deleted \\Seen \\Answered)\r\n");
+       IAPuts("* OK [PERMANENTFLAGS (\\Deleted \\Seen \\Answered)] permanent flags\r\n");
+
+       IReplyPrintf("OK [%s] %s completed",
+               (Imap->readonly ? "READ-ONLY" : "READ-WRITE"), Params[1].Key
+       );
+}
+
+
+/*
+ * Does the real work for expunge.
+ */
+int imap_do_expunge(void)
+{
+       struct CitContext *CCC = CC;
+       citimap *Imap = CCCIMAP;
+       int i;
+       int num_expunged = 0;
+       long *delmsgs = NULL;
+       int num_delmsgs = 0;
+
+       syslog(LOG_DEBUG, "imap_do_expunge() called");
+       if (Imap->selected == 0) {
+               return (0);
+       }
+
+       if (Imap->num_msgs > 0) {
+               delmsgs = malloc(Imap->num_msgs * sizeof(long));
+               for (i = 0; i < Imap->num_msgs; ++i) {
+                       if (Imap->flags[i] & IMAP_DELETED) {
+                               delmsgs[num_delmsgs++] = Imap->msgids[i];
+                       }
+               }
+               if (num_delmsgs > 0) {
+                       CtdlDeleteMessages(CC->room.QRname, delmsgs, num_delmsgs, "");
+               }
+               num_expunged += num_delmsgs;
+               free(delmsgs);
+       }
+
+       if (num_expunged > 0) {
+               imap_rescan_msgids();
+       }
+
+       syslog(LOG_DEBUG, "Expunged %d messages from <%s>", num_expunged, CC->room.QRname);
+       return (num_expunged);
+}
+
+
+/*
+ * implements the EXPUNGE command syntax
+ */
+void imap_expunge(int num_parms, ConstStr *Params)
+{
+       int num_expunged = 0;
+
+       num_expunged = imap_do_expunge();
+       IReplyPrintf("OK expunged %d messages.", num_expunged);
+}
+
+
+/*
+ * implements the CLOSE command
+ */
+void imap_close(int num_parms, ConstStr *Params)
+{
+
+       /* Yes, we always expunge on close. */
+       if (IMAP->selected) {
+               imap_do_expunge();
+       }
+
+       IMAP->selected = 0;
+       IMAP->readonly = 0;
+       imap_free_msgids();
+       IReply("OK CLOSE completed");
+}
+
+
+/*
+ * Implements the NAMESPACE command.
+ */
+void imap_namespace(int num_parms, ConstStr *Params)
+{
+       long len;
+       int i;
+       struct floor *fl;
+       int floors = 0;
+       char Namespace[SIZ];
+
+       IAPuts("* NAMESPACE ");
+
+       /* All personal folders are subordinate to INBOX. */
+       IAPuts("((\"INBOX/\" \"/\")) ");
+
+       /* Other users' folders ... coming soon! FIXME */
+       IAPuts("NIL ");
+
+       /* Show all floors as shared namespaces.  Neato! */
+       IAPuts("(");
+       for (i = 0; i < MAXFLOORS; ++i) {
+               fl = CtdlGetCachedFloor(i);
+               if (fl->f_flags & F_INUSE) {
+                       /* if (floors > 0) IAPuts(" "); samjam says this confuses javamail */
+                       IAPuts("(");
+                       len = snprintf(Namespace, sizeof(Namespace), "%s/", fl->f_name);
+                       IPutStr(Namespace, len);
+                       IAPuts(" \"/\")");
+                       ++floors;
+               }
+       }
+       IAPuts(")");
+
+       /* Wind it up with a newline and a completion message. */
+       IAPuts("\r\n");
+       IReply("OK NAMESPACE completed");
+}
+
+
+/*
+ * Implements the CREATE command
+ *
+ */
+void imap_create(int num_parms, ConstStr *Params)
+{
+       int ret;
+       char roomname[ROOMNAMELEN];
+       int floornum;
+       int flags;
+       int newroomtype = 0;
+       int newroomview = 0;
+       char *notification_message = NULL;
+
+       if (num_parms < 3) {
+               IReply("NO A foder name must be specified");
+               return;
+       }
+
+       if (strchr(Params[2].Key, '\\') != NULL) {
+               IReply("NO Invalid character in folder name");
+               syslog(LOG_ERR, "invalid character in folder name");
+               return;
+       }
+
+       ret = imap_roomname(roomname, sizeof roomname, Params[2].Key);
+       if (ret < 0) {
+               IReply("NO Invalid mailbox name or location");
+               syslog(LOG_ERR, "invalid mailbox name or location");
+               return;
+       }
+       floornum = (ret & 0x00ff);      /* lower 8 bits = floor number */
+       flags = (ret & 0xff00); /* upper 8 bits = flags        */
+
+       if (flags & IR_MAILBOX) {
+               if (strncasecmp(Params[2].Key, "INBOX/", 6)) {
+                       IReply("NO Personal folders must be created under INBOX");
+                       syslog(LOG_ERR, "not subordinate to inbox");
+                       return;
+               }
+       }
+
+       if (flags & IR_MAILBOX) {
+               newroomtype = 4;                /* private mailbox */
+               newroomview = VIEW_MAILBOX;
+       } else {
+               newroomtype = 0;                /* public folder */
+               newroomview = VIEW_BBS;
+       }
+
+       syslog(LOG_INFO, "Create new room <%s> on floor <%d> with type <%d>",
+                   roomname, floornum, newroomtype);
+
+       ret = CtdlCreateRoom(roomname, newroomtype, "", floornum, 1, 0, newroomview);
+       if (ret == 0) {
+               /*** DO NOT CHANGE THIS ERROR MESSAGE IN ANY WAY!  BYNARI CONNECTOR DEPENDS ON IT! ***/
+               IReply("NO Mailbox already exists, or create failed");
+       } else {
+               IReply("OK CREATE completed");
+               /* post a message in Aide> describing the new room */
+               notification_message = malloc(1024);
+               snprintf(notification_message, 1024,
+                       "A new room called \"%s\" has been created by %s%s%s%s\n",
+                       roomname,
+                       CC->user.fullname,
+                       ((ret & QR_MAILBOX) ? " [personal]" : ""),
+                       ((ret & QR_PRIVATE) ? " [private]" : ""),
+                       ((ret & QR_GUESSNAME) ? " [hidden]" : "")
+               );
+               CtdlAideMessage(notification_message, "Room Creation Message");
+               free(notification_message);
+       }
+       syslog(LOG_DEBUG, "imap_create() completed");
+}
+
+
+/*
+ * Locate a room by its IMAP folder name, and check access to it.
+ * If zapped_ok is nonzero, we can also look for the room in the zapped list.
+ */
+int imap_grabroom(char *returned_roomname, const char *foldername, int zapped_ok)
+{
+       int ret;
+       char augmented_roomname[ROOMNAMELEN];
+       char roomname[ROOMNAMELEN];
+       int c;
+       struct ctdlroom QRscratch;
+       int ra;
+       int ok = 0;
+
+       ret = imap_roomname(roomname, sizeof roomname, foldername);
+       if (ret < 0) {
+               return (1);
+       }
+
+       /* First try a regular match */
+       c = CtdlGetRoom(&QRscratch, roomname);
+
+       /* Then try a mailbox name match */
+       if (c != 0) {
+               CtdlMailboxName(augmented_roomname, sizeof augmented_roomname,
+                           &CC->user, roomname);
+               c = CtdlGetRoom(&QRscratch, augmented_roomname);
+               if (c == 0)
+                       safestrncpy(roomname, augmented_roomname, sizeof(roomname));
+       }
+
+       /* If the room exists, check security/access */
+       if (c == 0) {
+               /* See if there is an existing user/room relationship */
+               CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
+
+               /* normal clients have to pass through security */
+               if (ra & UA_KNOWN) {
+                       ok = 1;
+               }
+               if ((zapped_ok) && (ra & UA_ZAPPED)) {
+                       ok = 1;
+               }
+       }
+
+       /* Fail here if no such room */
+       if (!ok) {
+               strcpy(returned_roomname, "");
+               return (2);
+       } else {
+               safestrncpy(returned_roomname, QRscratch.QRname, ROOMNAMELEN);
+               return (0);
+       }
+}
+
+
+/*
+ * Implements the STATUS command (sort of)
+ *
+ */
+void imap_status(int num_parms, ConstStr *Params)
+{
+       long len;
+       int ret;
+       char roomname[ROOMNAMELEN];
+       char imaproomname[SIZ];
+       char savedroom[ROOMNAMELEN];
+       int msgs, new;
+
+       ret = imap_grabroom(roomname, Params[2].Key, 1);
+       if (ret != 0) {
+               IReply("NO Invalid mailbox name or location, or access denied");
+               return;
+       }
+
+       /*
+        * CtdlUserGoto() formally takes us to the desired room, happily returning
+        * the number of messages and number of new messages.  (If another
+        * folder is selected, save its name so we can return there!!!!!)
+        */
+       if (IMAP->selected) {
+               strcpy(savedroom, CC->room.QRname);
+       }
+       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+       /*
+        * Tell the client what it wants to know.  In fact, tell it *more* than
+        * it wants to know.  We happily IGnore the supplied status data item
+        * names and simply spew all possible data items.  It's far easier to
+        * code and probably saves us some processing time too.
+        */
+       len = imap_mailboxname(imaproomname, sizeof imaproomname, &CC->room);
+       IAPuts("* STATUS ");
+       IPutStr(imaproomname, len);
+       IAPrintf(" (MESSAGES %d ", msgs);
+       IAPrintf("RECENT %d ", new);    /* Initially, new==recent */
+       IAPrintf("UIDNEXT %ld ", CtdlGetConfigLong("MMhighest") + 1);
+       IAPrintf("UNSEEN %d)\r\n", new);
+       
+       /*
+        * If another folder is selected, go back to that room so we can resume
+        * our happy day without violent explosions.
+        */
+       if (IMAP->selected) {
+               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+       }
+
+       /*
+        * Oooh, look, we're done!
+        */
+       IReply("OK STATUS completed");
+}
+
+
+/*
+ * Implements the SUBSCRIBE command
+ *
+ */
+void imap_subscribe(int num_parms, ConstStr *Params)
+{
+       int ret;
+       char roomname[ROOMNAMELEN];
+       char savedroom[ROOMNAMELEN];
+       int msgs, new;
+
+       ret = imap_grabroom(roomname, Params[2].Key, 1);
+       if (ret != 0) {
+               IReplyPrintf(
+                       "NO Error %d: invalid mailbox name or location, or access denied",
+                       ret
+               );
+               return;
+       }
+
+       /*
+        * CtdlUserGoto() formally takes us to the desired room, which has the side
+        * effect of marking the room as not-zapped ... exactly the effect
+        * we're looking for.
+        */
+       if (IMAP->selected) {
+               strcpy(savedroom, CC->room.QRname);
+       }
+       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+       /*
+        * If another folder is selected, go back to that room so we can resume
+        * our happy day without violent explosions.
+        */
+       if (IMAP->selected) {
+               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+       }
+
+       IReply("OK SUBSCRIBE completed");
+}
+
+
+/*
+ * Implements the UNSUBSCRIBE command
+ *
+ */
+void imap_unsubscribe(int num_parms, ConstStr *Params)
+{
+       int ret;
+       char roomname[ROOMNAMELEN];
+       char savedroom[ROOMNAMELEN];
+       int msgs, new;
+
+       ret = imap_grabroom(roomname, Params[2].Key, 1);
+       if (ret != 0) {
+               IReply("NO Invalid mailbox name or location, or access denied");
+               return;
+       }
+
+       /*
+        * CtdlUserGoto() formally takes us to the desired room.
+        */
+       if (IMAP->selected) {
+               strcpy(savedroom, CC->room.QRname);
+       }
+       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+       /* 
+        * Now make the API call to zap the room
+        */
+       if (CtdlForgetThisRoom() == 0) {
+               IReply("OK UNSUBSCRIBE completed");
+       } else {
+               IReply("NO You may not unsubscribe from this folder.");
+       }
+
+       /*
+        * If another folder is selected, go back to that room so we can resume
+        * our happy day without violent explosions.
+        */
+       if (IMAP->selected) {
+               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+       }
+}
+
+
+/*
+ * Implements the DELETE command
+ *
+ */
+void imap_delete(int num_parms, ConstStr *Params)
+{
+       int ret;
+       char roomname[ROOMNAMELEN];
+       char savedroom[ROOMNAMELEN];
+       int msgs, new;
+
+       ret = imap_grabroom(roomname, Params[2].Key, 1);
+       if (ret != 0) {
+               IReply("NO Invalid mailbox name, or access denied");
+               return;
+       }
+
+       /*
+        * CtdlUserGoto() formally takes us to the desired room, happily returning
+        * the number of messages and number of new messages.  (If another
+        * folder is selected, save its name so we can return there!!!!!)
+        */
+       if (IMAP->selected) {
+               strcpy(savedroom, CC->room.QRname);
+       }
+       CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+       /*
+        * Now delete the room.
+        */
+       if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->room)) {
+               CtdlScheduleRoomForDeletion(&CC->room);
+               IReply("OK DELETE completed");
+       } else {
+               IReply("NO Can't delete this folder.");
+       }
+
+       /*
+        * If another folder is selected, go back to that room so we can resume
+        * our happy day without violent explosions.
+        */
+       if (IMAP->selected) {
+               CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+       }
+}
+
+
+/*
+ * Back end function for imap_rename()
+ */
+void imap_rename_backend(struct ctdlroom *qrbuf, void *data)
+{
+       char foldername[SIZ];
+       char newfoldername[SIZ];
+       char newroomname[ROOMNAMELEN];
+       int newfloor = 0;
+       struct irl *irlp = NULL;        /* scratch pointer */
+       irlparms *myirlparms;
+
+       myirlparms = (irlparms *) data;
+       imap_mailboxname(foldername, sizeof foldername, qrbuf);
+
+       /* Rename subfolders */
+       if ((!strncasecmp(foldername, myirlparms->oldname, myirlparms->oldnamelen) && (foldername[myirlparms->oldnamelen] == '/'))) {
+               snprintf(newfoldername, sizeof newfoldername, "%s/%s", myirlparms->newname, &foldername[myirlparms->oldnamelen + 1]);
+               newfloor = imap_roomname(newroomname, sizeof newroomname, newfoldername) & 0xFF;
+               irlp = (struct irl *) malloc(sizeof(struct irl));
+               strcpy(irlp->irl_newroom, newroomname);
+               strcpy(irlp->irl_oldroom, qrbuf->QRname);
+               irlp->irl_newfloor = newfloor;
+               irlp->next = *(myirlparms->irl);
+               *(myirlparms->irl) = irlp;
+       }
+}
+
+
+/*
+ * Implements the RENAME command
+ *
+ */
+void imap_rename(int num_parms, ConstStr *Params)
+{
+       char old_room[ROOMNAMELEN];
+       char new_room[ROOMNAMELEN];
+       int newr;
+       int new_floor;
+       int r;
+       struct irl *irl = NULL; /* the list */
+       struct irl *irlp = NULL;        /* scratch pointer */
+       irlparms irlparms;
+       char aidemsg[1024];
+
+       if (strchr(Params[3].Key, '\\') != NULL) {
+               IReply("NO Invalid character in folder name");
+               return;
+       }
+
+       imap_roomname(old_room, sizeof old_room, Params[2].Key);
+       newr = imap_roomname(new_room, sizeof new_room, Params[3].Key);
+       new_floor = (newr & 0xFF);
+
+       r = CtdlRenameRoom(old_room, new_room, new_floor);
+
+       if (r == crr_room_not_found) {
+               IReply("NO Could not locate this folder");
+               return;
+       }
+       if (r == crr_already_exists) {
+               IReplyPrintf("NO '%s' already exists.");
+               return;
+       }
+       if (r == crr_noneditable) {
+               IReply("NO This folder is not editable.");
+               return;
+       }
+       if (r == crr_invalid_floor) {
+               IReply("NO Folder root does not exist.");
+               return;
+       }
+       if (r == crr_access_denied) {
+               IReply("NO You do not have permission to edit this folder.");
+               return;
+       }
+       if (r != crr_ok) {
+               IReplyPrintf("NO Rename failed - undefined error %d", r);
+               return;
+       }
+
+       /* If this is the INBOX, then RFC2060 says we have to just move the
+        * contents.  In a Citadel environment it's easier to rename the room
+        * (already did that) and create a new inbox.
+        */
+       if (!strcasecmp(Params[2].Key, "INBOX")) {
+               CtdlCreateRoom(MAILROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
+       }
+
+       /* Otherwise, do the subfolders.  Build a list of rooms to rename... */
+       else {
+               irlparms.oldname = Params[2].Key;
+               irlparms.oldnamelen = Params[2].len;
+               irlparms.newname = Params[3].Key;
+               irlparms.newnamelen = Params[3].len;
+               irlparms.irl = &irl;
+               CtdlForEachRoom(imap_rename_backend, (void *) &irlparms);
+
+               /* ... and now rename them. */
+               while (irl != NULL) {
+                       r = CtdlRenameRoom(irl->irl_oldroom,
+                                          irl->irl_newroom,
+                                          irl->irl_newfloor);
+                       if (r != crr_ok) {
+                               /* FIXME handle error returns better */
+                               syslog(LOG_ERR, "CtdlRenameRoom() error %d", r);
+                       }
+                       irlp = irl;
+                       irl = irl->next;
+                       free(irlp);
+               }
+       }
+
+       snprintf(aidemsg, sizeof aidemsg, "IMAP folder \"%s\" renamed to \"%s\" by %s\n",
+               Params[2].Key,
+               Params[3].Key,
+               CC->curr_user
+       );
+       CtdlAideMessage(aidemsg, "IMAP folder rename");
+
+       IReply("OK RENAME completed");
+}
+
+
+/* 
+ * Main command loop for IMAP sessions.
+ */
+void imap_command_loop(void)
+{
+       struct CitContext *CCC = CC;
+       struct timeval tv1, tv2;
+       suseconds_t total_time = 0;
+       citimap *Imap;
+       const char *pchs, *pche;
+       const imap_handler_hook *h;
+
+       gettimeofday(&tv1, NULL);
+       CCC->lastcmd = time(NULL);
+       Imap = CCCIMAP;
+
+       flush_output();
+       if (Imap->Cmd.CmdBuf == NULL)
+               Imap->Cmd.CmdBuf = NewStrBufPlain(NULL, SIZ);
+       else
+               FlushStrBuf(Imap->Cmd.CmdBuf);
+
+       if (CtdlClientGetLine(Imap->Cmd.CmdBuf) < 1) {
+               syslog(LOG_ERR, "client disconnected: ending session.");
+               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
+               return;
+       }
+
+       if (Imap->authstate == imap_as_expecting_password) {
+               syslog(LOG_INFO, "<password>");
+       }
+       else if (Imap->authstate == imap_as_expecting_plainauth) {
+               syslog(LOG_INFO, "<plain_auth>");
+       }
+       else if ((Imap->authstate == imap_as_expecting_multilineusername) || 
+                cbmstrcasestr(ChrPtr(Imap->Cmd.CmdBuf), " LOGIN ")) {
+               syslog(LOG_INFO, "LOGIN...");
+       }
+       else {
+               syslog(LOG_DEBUG, "%s", ChrPtr(Imap->Cmd.CmdBuf));
+       }
+
+       pchs = ChrPtr(Imap->Cmd.CmdBuf);
+       pche = pchs + StrLength(Imap->Cmd.CmdBuf);
+
+       while ((pche > pchs) &&
+              ((*pche == '\n') ||
+               (*pche == '\r')))
+       {
+               pche --;
+               StrBufCutRight(Imap->Cmd.CmdBuf, 1);
+       }
+       StrBufTrim(Imap->Cmd.CmdBuf);
+
+       /* If we're in the middle of a multi-line command, handle that */
+       switch (Imap->authstate){
+       case imap_as_expecting_username:
+               imap_auth_login_user(imap_as_expecting_username);
+               IUnbuffer();
+               return;
+       case imap_as_expecting_multilineusername:
+               imap_auth_login_user(imap_as_expecting_multilineusername);
+               IUnbuffer();
+               return;
+       case imap_as_expecting_plainauth:
+               imap_auth_plain();
+               IUnbuffer();
+               return;
+       case imap_as_expecting_password:
+               imap_auth_login_pass(imap_as_expecting_password);
+               IUnbuffer();
+               return;
+       case imap_as_expecting_multilinepassword:
+               imap_auth_login_pass(imap_as_expecting_multilinepassword);
+               IUnbuffer();
+               return;
+       default:
+               break;
+       }
+
+       /* Ok, at this point we're in normal command mode.
+        * If the command just submitted does not contain a literal, we
+        * might think about delivering some untagged stuff...
+        */
+
+       /* Grab the tag, command, and parameters. */
+       imap_parameterize(&Imap->Cmd);
+#if 0 
+/* debug output the parsed vector */
+       {
+               int i;
+               syslog(LOG_DEBUG, "----- %ld params", Imap->Cmd.num_parms);
+
+       for (i=0; i < Imap->Cmd.num_parms; i++) {
+               if (Imap->Cmd.Params[i].len != strlen(Imap->Cmd.Params[i].Key))
+                       syslog(LOG_DEBUG, "*********** %ld != %ld : %s",
+                                   Imap->Cmd.Params[i].len, 
+                                   strlen(Imap->Cmd.Params[i].Key),
+                                     Imap->Cmd.Params[i].Key);
+               else
+                       syslog(LOG_DEBUG, "%ld : %s",
+                                   Imap->Cmd.Params[i].len, 
+                                   Imap->Cmd.Params[i].Key);
+       }}
+#endif
+
+       /* Now for the command set. */
+       h = imap_lookup(Imap->Cmd.num_parms, Imap->Cmd.Params);
+
+       if (h == NULL)
+       {
+               IReply("BAD command unrecognized");
+               goto BAIL;
+       }
+
+       /* RFC3501 says that we cannot output untagged data during these commands */
+       if ((h->Flags & I_FLAG_UNTAGGED) == 0) {
+
+               /* we can put any additional untagged stuff right here in the future */
+
+               /*
+                * Before processing the command that was just entered... if we happen
+                * to have a folder selected, we'd like to rescan that folder for new
+                * messages, and for deletions/changes of existing messages.  This
+                * could probably be optimized better with some deep thought...
+                */
+               if (Imap->selected) {
+                       imap_rescan_msgids();
+               }
+       }
+
+       /* does our command require a logged-in state */
+       if ((!CC->logged_in) && ((h->Flags & I_FLAG_LOGGED_IN) != 0)) {
+               IReply("BAD Not logged in.");
+               goto BAIL;
+       }
+
+       /* does our command require the SELECT state on a mailbox */
+       if ((Imap->selected == 0) && ((h->Flags & I_FLAG_SELECT) != 0)){
+               IReply("BAD no folder selected");
+               goto BAIL;
+       }
+       h->h(Imap->Cmd.num_parms, Imap->Cmd.Params);
+
+       /* If the client transmitted a message we can free it now */
+
+BAIL:
+       IUnbuffer();
+
+       imap_free_transmitted_message();
+
+       gettimeofday(&tv2, NULL);
+       total_time = (tv2.tv_usec + (tv2.tv_sec * 1000000)) - (tv1.tv_usec + (tv1.tv_sec * 1000000));
+       syslog(LOG_DEBUG, "IMAP command completed in %ld.%ld seconds",
+                   (total_time / 1000000),
+                   (total_time % 1000000)
+               );
+}
+
+void imap_noop (int num_parms, ConstStr *Params)
+{
+       IReply("OK No operation");
+}
+
+void imap_logout(int num_parms, ConstStr *Params)
+{
+       if (IMAP->selected) {
+               imap_do_expunge();      /* yes, we auto-expunge at logout */
+       }
+       IAPrintf("* BYE %s logging out\r\n", CtdlGetConfigStr("c_fqdn"));
+       IReply("OK Citadel IMAP session ended.");
+       CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
+       return;
+}
+
+const char *CitadelServiceIMAP="IMAP";
+const char *CitadelServiceIMAPS="IMAPS";
+
+/*
+ * This function is called to register the IMAP extension with Citadel.
+ */
+char *ctdl_module_init_imap(void) {
+       if (ImapCmds == NULL) {
+               ImapCmds = NewHash(1, NULL);
+       }
+
+       RegisterImapCMD("NOOP", "", imap_noop, I_FLAG_NONE);
+       RegisterImapCMD("CHECK", "", imap_noop, I_FLAG_NONE);
+       RegisterImapCMD("ID", "", imap_id, I_FLAG_NONE);
+       RegisterImapCMD("LOGOUT", "", imap_logout, I_FLAG_NONE);
+       RegisterImapCMD("LOGIN", "", imap_login, I_FLAG_NONE);
+       RegisterImapCMD("AUTHENTICATE", "", imap_authenticate, I_FLAG_NONE);
+       RegisterImapCMD("CAPABILITY", "", imap_capability, I_FLAG_NONE);
+#ifdef HAVE_OPENSSL
+       RegisterImapCMD("STARTTLS", "", imap_starttls, I_FLAG_NONE);
+#endif
+
+       /* The commans below require a logged-in state */
+       RegisterImapCMD("SELECT", "", imap_select, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("EXAMINE", "", imap_select, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("LSUB", "", imap_list, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("LIST", "", imap_list, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("CREATE", "", imap_create, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("DELETE", "", imap_delete, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("RENAME", "", imap_rename, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("STATUS", "", imap_status, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("SUBSCRIBE", "", imap_subscribe, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("UNSUBSCRIBE", "", imap_unsubscribe, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("APPEND", "", imap_append, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("NAMESPACE", "", imap_namespace, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("SETACL", "", imap_setacl, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("DELETEACL", "", imap_deleteacl, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("GETACL", "", imap_getacl, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("LISTRIGHTS", "", imap_listrights, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("MYRIGHTS", "", imap_myrights, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("GETMETADATA", "", imap_getmetadata, I_FLAG_LOGGED_IN);
+       RegisterImapCMD("SETMETADATA", "", imap_setmetadata, I_FLAG_LOGGED_IN);
+
+       /* The commands below require the SELECT state on a mailbox */
+       RegisterImapCMD("FETCH", "", imap_fetch, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED);
+       RegisterImapCMD("UID", "FETCH", imap_uidfetch, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
+       RegisterImapCMD("SEARCH", "", imap_search, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED);
+       RegisterImapCMD("UID", "SEARCH", imap_uidsearch, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
+       RegisterImapCMD("STORE", "", imap_store, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED);
+       RegisterImapCMD("UID", "STORE", imap_uidstore, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
+       RegisterImapCMD("COPY", "", imap_copy, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
+       RegisterImapCMD("UID", "COPY", imap_uidcopy, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
+       RegisterImapCMD("EXPUNGE", "", imap_expunge, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
+       RegisterImapCMD("UID", "EXPUNGE", imap_expunge, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
+       RegisterImapCMD("CLOSE", "", imap_close, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
+
+       if (!threading) {
+               CtdlRegisterServiceHook(CtdlGetConfigInt("c_imap_port"),
+                                       NULL, imap_greeting, imap_command_loop, NULL, CitadelServiceIMAP);
+#ifdef HAVE_OPENSSL
+               CtdlRegisterServiceHook(CtdlGetConfigInt("c_imaps_port"),
+                                       NULL, imaps_greeting, imap_command_loop, NULL, CitadelServiceIMAPS);
+#endif
+               CtdlRegisterSessionHook(imap_cleanup_function, EVT_STOP, PRIO_STOP + 30);
+       }
+       
+       /* return our module name for the log */
+       return "imap";
+}
diff --git a/citadel/server/modules/imap/serv_imap.h b/citadel/server/modules/imap/serv_imap.h
new file mode 100644 (file)
index 0000000..bd5b933
--- /dev/null
@@ -0,0 +1,128 @@
+#define GLOBAL_UIDVALIDITY_VALUE       1L
+
+
+void imap_cleanup_function(void);
+void imap_greeting(void);
+void imap_command_loop(void);
+int imap_grabroom(char *returned_roomname, const char *foldername, int zapped_ok);
+void imap_free_transmitted_message(void);
+int imap_do_expunge(void);
+void imap_rescan_msgids(void);
+
+/*
+ * FDELIM defines which character we want to use as a folder delimiter
+ * in room names.  Originally we used a forward slash, but that caused
+ * rooms with names like "Sent/Received Pages" to get delimited, so we
+ * changed it to a backslash.  This is completely irrelevant to how Citadel
+ * speaks to IMAP clients -- the delimiter used in the IMAP protocol is
+ * a vertical bar, which is illegal in Citadel room names anyway.
+ */
+
+typedef void (*imap_handler)(int num_parms, ConstStr *Params);
+
+typedef struct _imap_handler_hook {
+       imap_handler h;
+       int Flags;
+} imap_handler_hook;
+
+typedef struct __citimap_command {
+       StrBuf *CmdBuf;                 /* our current commandline; gets chopped into: */
+       ConstStr *Params;               /* Commandline tokens */
+       int num_parms;                  /* Number of Commandline tokens available */
+       int avail_parms;                /* Number of ConstStr args is big */
+       const imap_handler_hook *hh;
+} citimap_command;
+
+
+typedef struct __citimap {
+       StrBuf *Reply;
+       int authstate;
+       char authseq[SIZ];
+       int selected;                   /* set to 1 if in the SELECTED state */
+       int readonly;                   /* mailbox is open read only */
+       int num_msgs;                   /* Number of messages being mapped */
+       int num_alloc;                  /* Number of messages for which we've allocated space */
+       time_t last_mtime;              /* For checking whether the room was modified... */
+       long *msgids;
+       unsigned int *flags;
+
+       StrBuf *TransmittedMessage;     /* for APPEND command... */
+
+       citimap_command Cmd;            /* our current commandline */
+
+       /* Cache most recent RFC822 FETCH because client might load in pieces */
+       StrBuf *cached_rfc822;
+       long cached_rfc822_msgnum;
+       char cached_rfc822_withbody;    /* 1 = body cached; 0 = only headers cached */
+
+       /* Cache most recent BODY FETCH because client might load in pieces */
+       char *cached_body;
+       size_t cached_body_len;
+       char cached_bodypart[SIZ];
+       long cached_bodymsgnum;
+       char cached_body_withbody;      /* 1 = body cached; 0 = only headers cached */
+} citimap;
+
+/*
+ * values of 'authstate'
+ */
+enum {
+       imap_as_normal,
+       imap_as_expecting_username,
+       imap_as_expecting_password,
+       imap_as_expecting_plainauth,
+       imap_as_expecting_multilineusername,
+       imap_as_expecting_multilinepassword
+};
+
+/* Flags for the above struct.  Note that some of these are for internal use,
+ * and are not to be reported to IMAP clients.
+ */
+#define IMAP_ANSWERED          1       /* reportable and setable */
+#define IMAP_FLAGGED           2       /* reportable and setable */
+#define IMAP_DELETED           4       /* reportable and setable */
+#define IMAP_DRAFT             8       /* reportable and setable */
+#define IMAP_SEEN              16      /* reportable and setable */
+
+#define IMAP_MASK_SETABLE      0x1f
+#define IMAP_MASK_SYSTEM       0xe0
+
+#define IMAP_SELECTED          32      /* neither reportable nor setable */
+#define IMAP_RECENT            64      /* reportable but not setable */
+
+
+/*
+ * Flags that may be returned by imap_roomname()
+ * (the lower eight bits will be the floor number)
+ */
+#define IR_MAILBOX     0x0100          /* Mailbox                       */
+#define IR_EXISTS      0x0200          /* Room exists (not implemented) */
+#define IR_BABOON      0x0000          /* Just had to put this here :)  */
+
+#define FDELIM '\\'
+
+#define IMAP ((citimap *)CC->session_specific_data)
+#define CCCIMAP ((citimap *)CCC->session_specific_data)
+
+#define IMAPDBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (IMAPDebugEnabled != 0))
+
+#define I_FLAG_NONE          (0)
+#define I_FLAG_LOGGED_IN  (1<<0)
+#define I_FLAG_SELECT     (1<<1)
+/* RFC3501 says that we cannot output untagged data during these commands */
+#define I_FLAG_UNTAGGED   (1<<2)
+
+/*
+ * When loading arrays of message ID's into memory, increase the buffer to
+ * hold this many additional messages instead of calling realloc() each time.
+ */
+#define REALLOC_INCREMENT 100
+
+
+void registerImapCMD(const char *First, long FLen, 
+                    const char *Second, long SLen,
+                    imap_handler H,
+                    int Flags);
+
+#define RegisterImapCMD(First, Second, H, Flags) \
+       registerImapCMD(HKEY(First), HKEY(Second), H, Flags)
diff --git a/citadel/server/modules/inboxrules/.gitignore b/citadel/server/modules/inboxrules/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/inboxrules/serv_inboxrules.c b/citadel/server/modules/inboxrules/serv_inboxrules.c
new file mode 100644 (file)
index 0000000..17340a1
--- /dev/null
@@ -0,0 +1,980 @@
+// Inbox handling rules
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../ctdl_module.h"
+
+
+// The next sections are enums and keys that drive the serialize/deserialize functions for the inbox rules/state configuration.
+
+// Fields to be compared
+enum {
+       field_from,             
+       field_tocc,             
+       field_subject,  
+       field_replyto,  
+       field_sender,   
+       field_resentfrom,       
+       field_resentto, 
+       field_envfrom,  
+       field_envto,    
+       field_xmailer,  
+       field_xspamflag,        
+       field_xspamstatus,      
+       field_listid,   
+       field_size,             
+       field_all
+};
+char *field_keys[] = {
+       "from",
+       "tocc",
+       "subject",
+       "replyto",
+       "sender",
+       "resentfrom",
+       "resentto",
+       "envfrom",
+       "envto",
+       "xmailer",
+       "xspamflag",
+       "xspamstatus",
+       "listid",
+       "size",
+       "all"
+};
+
+// Field comparison operators
+enum {
+       fcomp_contains,
+       fcomp_notcontains,
+       fcomp_is,
+       fcomp_isnot,
+       fcomp_matches,
+       fcomp_notmatches
+};
+char *fcomp_keys[] = {
+       "contains",
+       "notcontains",
+       "is",
+       "isnot",
+       "matches",
+       "notmatches"
+};
+
+// Actions
+enum {
+       action_keep,
+       action_discard,
+       action_reject,
+       action_fileinto,
+       action_redirect,
+       action_vacation
+};
+char *action_keys[] = {
+       "keep",
+       "discard",
+       "reject",
+       "fileinto",
+       "redirect",
+       "vacation"
+};
+
+// Size comparison operators
+enum {
+       scomp_larger,
+       scomp_smaller
+};
+char *scomp_keys[] = {
+       "larger",
+       "smaller"
+};
+
+// Final actions
+enum {
+       final_continue,
+       final_stop
+};
+char *final_keys[] = {
+       "continue",
+       "stop"
+};
+
+// This data structure represents ONE inbox rule within the configuration.
+struct irule {
+       int compared_field;
+       int field_compare_op;
+       char compared_value[128];
+       int size_compare_op;
+       long compared_size;
+       int action;
+       char file_into[ROOMNAMELEN];
+       char redirect_to[1024];
+       char autoreply_message[SIZ];
+       int final_action;
+};
+
+// This data structure represents the entire inbox rules configuration AND current state for a single user.
+struct inboxrules {
+       long lastproc;
+       int num_rules;
+       struct irule *rules;
+};
+
+
+// Destructor for 'struct inboxrules'
+void free_inbox_rules(struct inboxrules *ibr) {
+       free(ibr->rules);
+       free(ibr);
+}
+
+
+// Constructor for 'struct inboxrules' that deserializes the configuration from text input.
+struct inboxrules *deserialize_inbox_rules(char *serialized_rules) {
+       int i;
+
+       if (!serialized_rules) {
+               return NULL;
+       }
+
+       // Make a copy of the supplied buffer because we're going to shit all over it with strtok_r()
+       char *sr = strdup(serialized_rules);
+       if (!sr) {
+               return NULL;
+       }
+
+       struct inboxrules *ibr = malloc(sizeof(struct inboxrules));
+       if (ibr == NULL) {
+               return NULL;
+       }
+       memset(ibr, 0, sizeof(struct inboxrules));
+
+       char *token; 
+       char *rest = sr;
+       while ((token = strtok_r(rest, "\n", &rest))) {
+
+               // For backwards compatibility, "# WEBCIT_RULE" is an alias for "rule".
+               // Prior to version 930, WebCit converted its rules to Sieve scripts, but saved the rules as comments for
+               // later re-editing.  Now, the rules hidden in the comments become the real rules.
+               if (!strncasecmp(token, "# WEBCIT_RULE|", 14)) {
+                       strcpy(token, "rule|"); 
+                       strcpy(&token[5], &token[14]);
+               }
+
+               // Lines containing actual rules are double-serialized with Base64.  It seemed like a good idea at the time :(
+               if (!strncasecmp(token, "rule|", 5)) {
+                       remove_token(&token[5], 0, '|');
+                       char *decoded_rule = malloc(strlen(token));
+                       CtdlDecodeBase64(decoded_rule, &token[5], strlen(&token[5]));
+                       ibr->num_rules++;
+                       ibr->rules = realloc(ibr->rules, (sizeof(struct irule) * ibr->num_rules));
+                       struct irule *new_rule = &ibr->rules[ibr->num_rules - 1];
+                       memset(new_rule, 0, sizeof(struct irule));
+
+                       // We have a rule , now parse it
+                       char rtoken[SIZ];
+                       int nt = num_tokens(decoded_rule, '|');
+                       int t = 0;
+                       for (t=0; t<nt; ++t) {
+                               extract_token(rtoken, decoded_rule, t, '|', sizeof(rtoken));
+                               striplt(rtoken);
+                               switch(t) {
+                                       case 1:                                                         // field to compare
+                                               for (i=0; i<=field_all; ++i) {
+                                                       if (!strcasecmp(rtoken, field_keys[i])) {
+                                                               new_rule->compared_field = i;
+                                                       }
+                                               }
+                                               break;
+                                       case 2:                                                         // field comparison operation
+                                               for (i=0; i<=fcomp_notmatches; ++i) {
+                                                       if (!strcasecmp(rtoken, fcomp_keys[i])) {
+                                                               new_rule->field_compare_op = i;
+                                                       }
+                                               }
+                                               break;
+                                       case 3:                                                         // field comparison value
+                                               safestrncpy(new_rule->compared_value, rtoken, sizeof(new_rule->compared_value));
+                                               break;
+                                       case 4:                                                         // size comparison operation
+                                               for (i=0; i<=scomp_smaller; ++i) {
+                                                       if (!strcasecmp(rtoken, scomp_keys[i])) {
+                                                               new_rule->size_compare_op = i;
+                                                       }
+                                               }
+                                               break;
+                                       case 5:                                                         // size comparison value
+                                               new_rule->compared_size = atol(rtoken);
+                                               break;
+                                       case 6:                                                         // action
+                                               for (i=0; i<=action_vacation; ++i) {
+                                                       if (!strcasecmp(rtoken, action_keys[i])) {
+                                                               new_rule->action = i;
+                                                       }
+                                               }
+                                               break;
+                                       case 7:                                                         // file into (target room)
+                                               safestrncpy(new_rule->file_into, rtoken, sizeof(new_rule->file_into));
+                                               break;
+                                       case 8:                                                         // redirect to (target address)
+                                               safestrncpy(new_rule->redirect_to, rtoken, sizeof(new_rule->redirect_to));
+                                               break;
+                                       case 9:                                                         // autoreply message
+                                               safestrncpy(new_rule->autoreply_message, rtoken, sizeof(new_rule->autoreply_message));
+                                               break;
+                                       case 10:                                                        // final_action;
+                                               for (i=0; i<=final_stop; ++i) {
+                                                       if (!strcasecmp(rtoken, final_keys[i])) {
+                                                               new_rule->final_action = i;
+                                                       }
+                                               }
+                                               break;
+                                       default:
+                                               break;
+                               }
+                       }
+                       free(decoded_rule);
+               }
+
+               // "lastproc" indicates the newest message number in the inbox that was previously processed by our inbox rules.
+               // This is a legacy location for this value and will only be used if it's the only one present.
+               else if (!strncasecmp(token, "lastproc|", 5)) {
+                       ibr->lastproc = atol(&token[9]);
+               }
+
+               // Lines which do not contain a recognizable token must be IGNORED.  These lines may be left over
+               // from a previous version and will disappear when we rewrite the config.
+
+       }
+
+       free(sr);               // free our copy of the source buffer that has now been trashed with null bytes...
+       return(ibr);            // and return our complex data type to the caller.
+}
+
+
+// Perform the "fileinto" action (save the message in another room)
+// Returns: 1 or 0 to tell the caller to keep (1) or delete (0) the inbox copy of the message.
+//
+int inbox_do_fileinto(struct irule *rule, long msgnum) {
+       char *dest_folder = rule->file_into;
+       char original_room_name[ROOMNAMELEN];
+       char foldername[ROOMNAMELEN];
+       int c;
+
+       // Situations where we want to just keep the message in the inbox:
+       if (
+               (IsEmptyStr(dest_folder))                       // no destination room was specified
+               || (!strcasecmp(dest_folder, "INBOX"))          // fileinto inbox is the same as keep
+               || (!strcasecmp(dest_folder, MAILROOM))         // fileinto "Mail" is the same as keep
+       ) {
+               return(1);                                      // don't delete the inbox copy if this failed
+       }
+
+       // Remember what room we came from
+       safestrncpy(original_room_name, CC->room.QRname, sizeof original_room_name);
+
+       // First try a mailbox name match (check personal mail folders first)
+       strcpy(foldername, original_room_name);                                 // This keeps the user namespace of the inbox
+       snprintf(&foldername[10], sizeof(foldername)-10, ".%s", dest_folder);   // And this tacks on the target room name
+       c = CtdlGetRoom(&CC->room, foldername);
+
+       // Then a regular room name match (public and private rooms)
+       if (c != 0) {
+               safestrncpy(foldername, dest_folder, sizeof foldername);
+               c = CtdlGetRoom(&CC->room, foldername);
+       }
+
+       if (c != 0) {
+               syslog(LOG_WARNING, "inboxrules: target <%s> does not exist", dest_folder);
+               return(1);                                      // don't delete the inbox copy if this failed
+       }
+
+       // Yes, we actually have to go there
+       CtdlUserGoto(NULL, 0, 0, NULL, NULL, NULL, NULL);
+
+       c = CtdlSaveMsgPointersInRoom(NULL, &msgnum, 1, 0, NULL, 0);
+
+       // Go back to the room we came from
+       if (strcasecmp(original_room_name, CC->room.QRname)) {
+               CtdlUserGoto(original_room_name, 0, 0, NULL, NULL, NULL, NULL);
+       }
+
+       return(0);                                              // delete the inbox copy
+}
+
+
+// Perform the "redirect" action (divert the message to another email address)
+// Returns: 1 or 0 to tell the caller to keep (1) or delete (0) the inbox copy of the message.
+//
+int inbox_do_redirect(struct irule *rule, long msgnum) {
+       if (IsEmptyStr(rule->redirect_to)) {
+               syslog(LOG_WARNING, "inboxrules: inbox_do_redirect() invalid recipient <%s>", rule->redirect_to);
+               return(1);                                      // don't delete the inbox copy if this failed
+       }
+
+       struct recptypes *valid = validate_recipients(rule->redirect_to, NULL, 0);
+       if (valid == NULL) {
+               syslog(LOG_WARNING, "inboxrules: inbox_do_redirect() invalid recipient <%s>", rule->redirect_to);
+               return(1);                                      // don't delete the inbox copy if this failed
+       }
+       if (valid->num_error > 0) {
+               free_recipients(valid);
+               syslog(LOG_WARNING, "inboxrules: inbox_do_redirect() invalid recipient <%s>", rule->redirect_to);
+               return(1);                                      // don't delete the inbox copy if this failed
+       }
+
+       struct CtdlMessage *msg = CtdlFetchMessage(msgnum, 1);
+       if (msg == NULL) {
+               free_recipients(valid);
+               syslog(LOG_WARNING, "inboxrules: cannot reload message %ld for forwarding", msgnum);
+               return(1);                                      // don't delete the inbox copy if this failed
+       }
+
+       CtdlSubmitMsg(msg, valid, NULL);                        // send the message to the new recipient
+       free_recipients(valid);
+       CM_Free(msg);
+       return(0);                                              // delete the inbox copy
+}
+
+
+// Perform the "reject" action (delete the message, and tell the sender we deleted it)
+void inbox_do_reject(struct irule *rule, struct CtdlMessage *msg) {
+       syslog(LOG_DEBUG, "inbox_do_reject: sender: <%s>, reject", msg->cm_fields[erFc822Addr]);
+
+       // If we can't determine who sent the message, reject silently.
+       char *sender;
+       if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
+               sender = msg->cm_fields[eMessagePath];
+       }
+       else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
+               sender = msg->cm_fields[erFc822Addr];
+       }
+       else {
+               return;
+       }
+
+       // Assemble the reject message.
+       char *reject_text = malloc(strlen(rule->autoreply_message) + 1024);
+       if (reject_text == NULL) {
+               return;
+       }
+       sprintf(reject_text, 
+               "Content-type: text/plain\n"
+               "\n"
+               "The message was refused by the recipient's mail filtering program.\n"
+               "The reason given was as follows:\n"
+               "\n"
+               "%s\n"
+               "\n"
+       ,
+               rule->autoreply_message
+       );
+
+       // Deliver the message
+       quickie_message(
+               " ",
+               msg->cm_fields[eenVelopeTo],
+               sender,
+               MAILROOM,
+               reject_text,
+               FMT_RFC822,
+               "Delivery status notification"
+       );
+       free(reject_text);
+}
+
+
+// Perform the "vacation" action (send an automatic response)
+void inbox_do_vacation(struct irule *rule, struct CtdlMessage *msg) {
+       syslog(LOG_DEBUG, "inbox_do_vacation: sender: <%s>, vacation", msg->cm_fields[erFc822Addr]);
+
+       // If we can't determine who sent the message, no auto-reply can be sent.
+       char *sender;
+       if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
+               sender = msg->cm_fields[eMessagePath];
+       }
+       else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
+               sender = msg->cm_fields[erFc822Addr];
+       }
+       else {
+               return;
+       }
+
+       // Avoid repeatedly sending auto-replies to the same correspondent over and over again by creating
+       // a hash of the user, correspondent, and reply text, and hitting the S_USETABLE database.
+       StrBuf *u = NewStrBuf();
+       StrBufPrintf(u, "vacation/%x/%x/%x",
+               HashLittle(sender, strlen(sender)),
+               HashLittle(msg->cm_fields[eenVelopeTo], msg->cm_lengths[eenVelopeTo]),
+               HashLittle(rule->autoreply_message, strlen(rule->autoreply_message))
+       );
+       int already_seen = CheckIfAlreadySeen(u);
+       FreeStrBuf(&u);
+
+       if (!already_seen) {
+               // Assemble the auto-reply message.
+               StrBuf *reject_text = NewStrBuf();
+               if (reject_text == NULL) {
+                       return;
+               }
+
+               StrBufPrintf(reject_text, 
+                       "Content-type: text/plain\n"
+                       "\n"
+                       "%s\n"
+                       "\n"
+               ,
+                       rule->autoreply_message
+               );
+       
+               // Deliver the auto-reply.
+               quickie_message(
+                       "",
+                       msg->cm_fields[eenVelopeTo],
+                       sender,
+                       MAILROOM,
+                       ChrPtr(reject_text),
+                       FMT_RFC822,
+                       "Delivery status notification"
+               );
+               FreeStrBuf(&reject_text);
+       }
+}
+
+
+/*
+ * Process a single message.  We know the room, the user, the rules, the message number, etc.
+ */
+void inbox_do_msg(long msgnum, void *userdata) {
+       struct inboxrules *ii = (struct inboxrules *) userdata;
+       struct CtdlMessage *msg = NULL;         // If we are loading a message to process, put it here.
+       int headers_loaded = 0;                 // Did we load the headers yet?  Do it only once.
+       int body_loaded = 0;                    // Did we load the message body yet?  Do it only once.
+       int metadata_loaded = 0;                // Did we load the metadata yet?  Do it only once.
+       struct MetaData smi;                    // If we are loading the metadata to compare, put it here.
+       int rule_activated = 0;                 // On each rule, this is set if the compare succeeds and the rule activates.
+       char compare_me[SIZ];                   // On each rule, we will store the field to be compared here.
+       int compare_compound = 0;               // Set to 1 when we are comparing both display name and email address
+       int keep_message = 1;                   // Nonzero to keep the message in the inbox after processing, 0 to delete it.
+       int i;
+
+       syslog(LOG_DEBUG, "inboxrules: processing message #%ld which is higher than %ld, we are in %s", msgnum, ii->lastproc, CC->room.QRname);
+
+       if (ii->num_rules <= 0) {
+               syslog(LOG_DEBUG, "inboxrules: rule set is empty");
+               return;
+       }
+
+       for (i=0; i<ii->num_rules; ++i) {
+               syslog(LOG_DEBUG, "inboxrules: processing rule %d is %s", i, field_keys[ ii->rules[i].compared_field ]);
+               rule_activated = 0;
+
+               // Before doing a field compare, check to see if we have the correct parts of the message in memory.
+
+               switch(ii->rules[i].compared_field) {
+                       // These fields require loading only the top-level headers
+                       case field_from:                // From:
+                       case field_tocc:                // To: or Cc:
+                       case field_subject:             // Subject:
+                       case field_replyto:             // Reply-to:
+                       case field_listid:              // List-ID:
+                       case field_envto:               // Envelope-to:
+                       case field_envfrom:             // Return-path:
+                               if (!headers_loaded) {
+                                       syslog(LOG_DEBUG, "inboxrules: loading headers for message %ld", msgnum);
+                                       msg = CtdlFetchMessage(msgnum, 0);
+                                       if (!msg) {
+                                               return;
+                                       }
+                                       headers_loaded = 1;
+                               }
+                               break;
+                       // These fields are not stored as Citadel headers, and therefore require a full message load.
+                       case field_sender:
+                       case field_resentfrom:
+                       case field_resentto:
+                       case field_xmailer:
+                       case field_xspamflag:
+                       case field_xspamstatus:
+                               if (!body_loaded) {
+                                       syslog(LOG_DEBUG, "inboxrules: loading all of message %ld", msgnum);
+                                       if (msg != NULL) {
+                                               CM_Free(msg);
+                                       }
+                                       msg = CtdlFetchMessage(msgnum, 1);
+                                       if (!msg) {
+                                               return;
+                                       }
+                                       headers_loaded = 1;
+                                       body_loaded = 1;
+                               }
+                               break;
+                       case field_size:
+                               if (!metadata_loaded) {
+                                       syslog(LOG_DEBUG, "inboxrules: loading metadata for message %ld", msgnum);
+                                       GetMetaData(&smi, msgnum);
+                                       metadata_loaded = 1;
+                               }
+                               break;
+                       case field_all:
+                               syslog(LOG_DEBUG, "inboxrules: this is an always-on rule");
+                               break;
+                       default:
+                               syslog(LOG_DEBUG, "inboxrules: unknown rule key");
+               }
+
+               // If the rule involves a field comparison, load the field to be compared.
+               compare_me[0] = 0;
+               compare_compound = 0;
+               switch(ii->rules[i].compared_field) {
+                       case field_from:                // From:
+                               if ( (!IsEmptyStr(msg->cm_fields[erFc822Addr])) && (!IsEmptyStr(msg->cm_fields[erFc822Addr])) ) {
+                                       snprintf(compare_me, sizeof compare_me, "%s|%s",
+                                               msg->cm_fields[eAuthor],
+                                               msg->cm_fields[erFc822Addr]
+                                       );
+                                       compare_compound = 1;           // there will be two fields to compare "name|address"
+                               }
+                               else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
+                                       safestrncpy(compare_me, msg->cm_fields[erFc822Addr], sizeof compare_me);
+                               }
+                               else if (!IsEmptyStr(msg->cm_fields[eAuthor])) {
+                                       safestrncpy(compare_me, msg->cm_fields[eAuthor], sizeof compare_me);
+                               }
+                               break;
+                       case field_tocc:                // To: or Cc:
+                               if (!IsEmptyStr(msg->cm_fields[eRecipient])) {
+                                       safestrncpy(compare_me, msg->cm_fields[eRecipient], sizeof compare_me);
+                               }
+                               if (!IsEmptyStr(msg->cm_fields[eCarbonCopY])) {
+                                       if (!IsEmptyStr(compare_me)) {
+                                               strcat(compare_me, ",");
+                                       }
+                                       safestrncpy(&compare_me[strlen(compare_me)], msg->cm_fields[eCarbonCopY], (sizeof compare_me - strlen(compare_me)));
+                               }
+                               break;
+                       case field_subject:             // Subject:
+                               if (!IsEmptyStr(msg->cm_fields[eMsgSubject])) {
+                                       safestrncpy(compare_me, msg->cm_fields[eMsgSubject], sizeof compare_me);
+                               }
+                               break;
+                       case field_replyto:             // Reply-to:
+                               if (!IsEmptyStr(msg->cm_fields[eReplyTo])) {
+                                       safestrncpy(compare_me, msg->cm_fields[eReplyTo], sizeof compare_me);
+                               }
+                               break;
+                       case field_listid:              // List-ID:
+                               if (!IsEmptyStr(msg->cm_fields[eListID])) {
+                                       safestrncpy(compare_me, msg->cm_fields[eListID], sizeof compare_me);
+                               }
+                               break;
+                       case field_envto:               // Envelope-to:
+                               if (!IsEmptyStr(msg->cm_fields[eenVelopeTo])) {
+                                       safestrncpy(compare_me, msg->cm_fields[eenVelopeTo], sizeof compare_me);
+                               }
+                               break;
+                       case field_envfrom:             // Return-path:
+                               if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
+                                       safestrncpy(compare_me, msg->cm_fields[eMessagePath], sizeof compare_me);
+                               }
+                               break;
+
+                       case field_sender:
+                       case field_resentfrom:
+                       case field_resentto:
+                       case field_xmailer:
+                       case field_xspamflag:
+                       case field_xspamstatus:
+
+                       default:
+                               break;
+               }
+
+               // Message data to compare is loaded, now do something.
+               switch(ii->rules[i].compared_field) {
+                       case field_from:                // From:
+                       case field_tocc:                // To: or Cc:
+                       case field_subject:             // Subject:
+                       case field_replyto:             // Reply-to:
+                       case field_listid:              // List-ID:
+                       case field_envto:               // Envelope-to:
+                       case field_envfrom:             // Return-path:
+                       case field_sender:
+                       case field_resentfrom:
+                       case field_resentto:
+                       case field_xmailer:
+                       case field_xspamflag:
+                       case field_xspamstatus:
+
+                               // For all of the above fields, we can compare the field we've loaded into the buffer.
+                               syslog(LOG_DEBUG, "Value of field to compare is: <%s>", compare_me);
+                               int substring_match = (bmstrcasestr(compare_me, ii->rules[i].compared_value) ? 1 : 0);
+                               int exact_match = 0;
+                               if (compare_compound) {
+                                       char *sep = strchr(compare_me, '|');
+                                       if (sep) {
+                                               *sep = 0;
+                                               exact_match =
+                                                       (strcasecmp(compare_me, ii->rules[i].compared_value) ? 0 : 1)
+                                                       + (strcasecmp(++sep, ii->rules[i].compared_value) ? 0 : 1)
+                                               ;
+                                       }
+                               }
+                               else {
+                                       exact_match = (strcasecmp(compare_me, ii->rules[i].compared_value) ? 0 : 1);
+                               }
+                               syslog(LOG_DEBUG, "substring match: %d", substring_match);
+                               syslog(LOG_DEBUG, "exact match: %d", exact_match);
+                               switch(ii->rules[i].field_compare_op) {
+                                       case fcomp_contains:
+                                       case fcomp_matches:
+                                               rule_activated = substring_match;
+                                               break;
+                                       case fcomp_notcontains:
+                                       case fcomp_notmatches:
+                                               rule_activated = !substring_match;
+                                               break;
+                                       case fcomp_is:
+                                               rule_activated = exact_match;
+                                               break;
+                                       case fcomp_isnot:
+                                               rule_activated = !exact_match;
+                                               break;
+                               }
+                               break;
+
+                       case field_size:
+                               rule_activated = 0;
+                               switch(ii->rules[i].field_compare_op) {
+                                       case scomp_larger:
+                                               rule_activated = ((smi.meta_rfc822_length > ii->rules[i].compared_size) ? 1 : 0);
+                                               break;
+                                       case scomp_smaller:
+                                               rule_activated = ((smi.meta_rfc822_length < ii->rules[i].compared_size) ? 1 : 0);
+                                               break;
+                               }
+                               break;
+                       case field_all:                 // The "all messages" rule ALWAYS triggers
+                               rule_activated = 1;
+                               break;
+                       default:                        // no matches, fall through and do nothing
+                               syslog(LOG_WARNING, "inboxrules: an unknown field comparison was encountered");
+                               rule_activated = 0;
+                               break;
+               }
+
+               // If the rule matched, perform the requested action.
+               if (rule_activated) {
+                       syslog(LOG_DEBUG, "inboxrules: rule activated");
+
+                       // Perform the requested action
+                       switch(ii->rules[i].action) {
+                               case action_keep:
+                                       keep_message = 1;
+                                       break;
+                               case action_discard:
+                                       keep_message = 0;
+                                       break;
+                               case action_reject:
+                                       inbox_do_reject(&ii->rules[i], msg);
+                                       keep_message = 0;
+                                       break;
+                               case action_fileinto:
+                                       keep_message = inbox_do_fileinto(&ii->rules[i], msgnum);
+                                       break;
+                               case action_redirect:
+                                       keep_message = inbox_do_redirect(&ii->rules[i], msgnum);
+                                       break;
+                               case action_vacation:
+                                       inbox_do_vacation(&ii->rules[i], msg);
+                                       keep_message = 1;
+                                       break;
+                       }
+
+                       // Now perform the "final" action (anything other than "stop" means continue)
+                       if (ii->rules[i].final_action == final_stop) {
+                               syslog(LOG_DEBUG, "inboxrules: stop processing");
+                               i = ii->num_rules + 1;                                  // throw us out of scope to stop
+                       }
+
+
+               }
+               else {
+                       syslog(LOG_DEBUG, "inboxrules: rule not activated");
+               }
+       }
+
+       if (msg != NULL) {              // Delete the copy of the message that is currently in memory.  We don't need it anymore.
+               CM_Free(msg);
+       }
+
+       if (!keep_message) {            // Delete the copy of the message that is currently in the inbox, if rules dictated that.
+               syslog(LOG_DEBUG, "inboxrules: delete %ld from inbox", msgnum);
+               CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");                    // we're in the inbox already
+       }
+
+       ii->lastproc = msgnum;          // make note of the last message we processed, so we don't scan the whole inbox again
+}
+
+
+/*
+ * A user account is identified as requring inbox processing.
+ * Do it.
+ */
+void do_inbox_processing_for_user(long usernum) {
+       struct CtdlMessage *msg;
+       struct inboxrules *ii;
+       char roomname[ROOMNAMELEN];
+       char username[64];
+
+       if (CtdlGetUserByNumber(&CC->user, usernum) != 0) {     // grab the user record
+               return;                                         // and bail out if we were given an invalid user
+       }
+
+       strcpy(username, CC->user.fullname);                    // save the user name so we can fetch it later and lock it
+
+       if (CC->user.msgnum_inboxrules <= 0) {
+               return;                                         // this user has no inbox rules
+       }
+
+       msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
+       if (msg == NULL) {
+               return;                                         // config msgnum is set but that message does not exist
+       }
+
+       ii = deserialize_inbox_rules(msg->cm_fields[eMesageText]);
+       CM_Free(msg);
+
+       if (ii == NULL) {
+               return;                                         // config message exists but body is null
+       }
+
+       if (ii->lastproc > CC->user.lastproc_inboxrules) {      // There might be a "last message processed" number left over
+               CC->user.lastproc_inboxrules = ii->lastproc;    // in the ruleset from a previous version.  Use this if it is
+       }                                                       // a higher number.
+       else {
+               ii->lastproc = CC->user.lastproc_inboxrules;
+       }
+
+       long original_lastproc = ii->lastproc;
+       syslog(LOG_DEBUG, "inboxrules: for %s, messages newer than %ld", CC->user.fullname, original_lastproc);
+
+       // Go to the user's inbox room and process all new messages
+       snprintf(roomname, sizeof roomname, "%010ld.%s", usernum, MAILROOM);
+       if (CtdlGetRoom(&CC->room, roomname) == 0) {
+               CtdlForEachMessage(MSGS_GT, ii->lastproc, NULL, NULL, NULL, inbox_do_msg, (void *) ii);
+       }
+
+       // Record the number of the last message we processed
+       if (ii->lastproc > original_lastproc) {
+               CtdlGetUserLock(&CC->user, username);
+               CC->user.lastproc_inboxrules = ii->lastproc;    // Avoid processing the entire inbox next time
+               CtdlPutUserLock(&CC->user);
+       }
+
+       // And free the memory.
+       free_inbox_rules(ii);
+}
+
+
+/*
+ * Here is an array of users (by number) who have received messages in their inbox and may require processing.
+ */
+long *users_requiring_inbox_processing = NULL;
+int num_urip = 0;
+int num_urip_alloc = 0;
+
+
+/*
+ * Perform inbox processing for all rooms which require it
+ */
+void perform_inbox_processing(void) {
+       int i = 0;
+
+       if (num_urip == 0) {
+               return;                                                                                 // no action required
+       }
+
+       for (i=0; i<num_urip; ++i) {
+               do_inbox_processing_for_user(users_requiring_inbox_processing[i]);
+       }
+
+       free(users_requiring_inbox_processing);
+       users_requiring_inbox_processing = NULL;
+       num_urip = 0;
+       num_urip_alloc = 0;
+}
+
+
+/*
+ * This function is called after a message is saved to a room.
+ * If it's someone's inbox, we have to check for inbox rules
+ */
+int serv_inboxrules_roomhook(struct ctdlroom *room) {
+       int i = 0;
+
+       // Is this someone's inbox?
+       if (!strcasecmp(&room->QRname[11], MAILROOM)) {
+               long usernum = atol(room->QRname);
+               if (usernum > 0) {
+
+                       // first check to see if this user is already on the list
+                       if (num_urip > 0) {
+                               for (i=0; i<=num_urip; ++i) {
+                                       if (users_requiring_inbox_processing[i] == usernum) {           // already on the list!
+                                               return(0);
+                                       }
+                               }
+                       }
+
+                       // make room if we need to
+                       if (num_urip_alloc == 0) {
+                               num_urip_alloc = 100;
+                               users_requiring_inbox_processing = malloc(sizeof(long) * num_urip_alloc);
+                       }
+                       else if (num_urip >= num_urip_alloc) {
+                               num_urip_alloc += 100;
+                               users_requiring_inbox_processing = realloc(users_requiring_inbox_processing, (sizeof(long) * num_urip_alloc));
+                       }
+                       
+                       // now add the user to the list
+                       users_requiring_inbox_processing[num_urip++] = usernum;
+               }
+       }
+
+       // No errors are possible from this function.
+       return(0);
+}
+
+
+
+/*
+ * Get InBox Rules
+ *
+ * This is a client-facing function which fetches the user's inbox rules -- it omits all lines containing anything other than a rule.
+ * 
+ * hmmmmm ... should we try to rebuild this in terms of deserialize_inbox_rules() instread?
+ */
+void cmd_gibr(char *argbuf) {
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       cprintf("%d inbox rules for %s\n", LISTING_FOLLOWS, CC->user.fullname);
+
+       struct CtdlMessage *msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
+       if (msg != NULL) {
+               if (!CM_IsEmpty(msg, eMesageText)) {
+                       char *token; 
+                       char *rest = msg->cm_fields[eMesageText];
+                       while ((token = strtok_r(rest, "\n", &rest))) {
+
+                               // for backwards compatibility, "# WEBCIT_RULE" is an alias for "rule" 
+                               if (!strncasecmp(token, "# WEBCIT_RULE|", 14)) {
+                                       strcpy(token, "rule|"); 
+                                       strcpy(&token[5], &token[14]);
+                               }
+
+                               // Output only lines containing rules.
+                               if (!strncasecmp(token, "rule|", 5)) {
+                                       cprintf("%s\n", token); 
+                               }
+                               else {
+                                       cprintf("# invalid rule found : %s\n", token);
+                               }
+                       }
+               }
+               CM_Free(msg);
+       }
+       cprintf("000\n");
+}
+
+
+/*
+ * Rewrite the rule set to disk after it has been modified
+ * Called by cmd_pibr()
+ * Returns the msgnum of the new rules message
+ */
+void rewrite_rules_to_disk(const char *new_config) {
+       long old_msgnum = CC->user.msgnum_inboxrules;
+       char userconfigroomname[ROOMNAMELEN];
+       CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &CC->user, USERCONFIGROOM);
+       long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, new_config, FMT_RFC822, "inbox rules configuration");
+       CtdlGetUserLock(&CC->user, CC->curr_user);
+       CC->user.msgnum_inboxrules = new_msgnum;                // Now we know where to get the rules next time
+       CC->user.lastproc_inboxrules = new_msgnum;              // Avoid processing the entire inbox next time
+       CtdlPutUserLock(&CC->user);
+       if (old_msgnum > 0) {
+               syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
+               CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
+       }
+}
+
+
+/*
+ * Put InBox Rules
+ *
+ * User transmits the new inbox rules for the account.  They are inserted into the account, replacing the ones already there.
+ */
+void cmd_pibr(char *argbuf) {
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       unbuffer_output();
+       cprintf("%d send new rules\n", SEND_LISTING);
+       char *newrules = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
+       StrBuf *NewConfig = NewStrBufPlain("Content-type: application/x-citadel-sieve-config; charset=UTF-8\nContent-transfer-encoding: 8bit\n\n", -1);
+
+       char *token; 
+       char *rest = newrules;
+       while ((token = strtok_r(rest, "\n", &rest))) {
+               // Accept only lines containing rules
+               if (!strncasecmp(token, "rule|", 5)) {
+                       StrBufAppendBufPlain(NewConfig, token, -1, 0);
+                       StrBufAppendBufPlain(NewConfig, HKEY("\n"), 0);
+               }
+       }
+       free(newrules);
+       rewrite_rules_to_disk(ChrPtr(NewConfig));
+       FreeStrBuf(&NewConfig);
+}
+
+
+char *ctdl_module_init_sieve(void) {
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_gibr, "GIBR", "Get InBox Rules");
+               CtdlRegisterProtoHook(cmd_pibr, "PIBR", "Put InBox Rules");
+               CtdlRegisterRoomHook(serv_inboxrules_roomhook);
+               CtdlRegisterSessionHook(perform_inbox_processing, EVT_HOUSE, PRIO_HOUSE + 10);
+       }
+       
+        /* return our module name for the log */
+       return "inboxrules";
+}
diff --git a/citadel/server/modules/inetcfg/.gitignore b/citadel/server/modules/inetcfg/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/inetcfg/serv_inetcfg.c b/citadel/server/modules/inetcfg/serv_inetcfg.c
new file mode 100644 (file)
index 0000000..ee763a9
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * This module handles the loading/saving and maintenance of the
+ * system's Internet configuration.  It's not an optional component; I
+ * wrote it as a module merely to keep things as clean and loosely coupled
+ * as possible.
+ *
+ * Copyright (c) 1987-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../genstamp.h"
+#include "../../domain.h"
+#include "../../ctdl_module.h"
+
+
+void inetcfg_setTo(struct CtdlMessage *msg) {
+       char *conf;
+       char buf[SIZ];
+       
+       if (CM_IsEmpty(msg, eMesageText)) return;
+       conf = strdup(msg->cm_fields[eMesageText]);
+
+       if (conf != NULL) {
+               do {
+                       extract_token(buf, conf, 0, '\n', sizeof buf);
+                       strcpy(conf, &conf[strlen(buf)+1]);
+               } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
+
+               if (inetcfg != NULL) free(inetcfg);
+               inetcfg = conf;
+       }
+}
+
+
+/*
+ * This handler detects changes being made to the system's Internet
+ * configuration.
+ */
+int inetcfg_aftersave(struct CtdlMessage *msg, struct recptypes *recp) {
+       char *ptr;
+       int linelen;
+
+       /* If this isn't the configuration room, or if this isn't a MIME
+        * message, don't bother.
+        */
+       if ((msg->cm_fields[eOriginalRoom]) && (strcasecmp(msg->cm_fields[eOriginalRoom], SYSCONFIGROOM))) {
+               return(0);
+       }
+       if (msg->cm_format_type != 4) {
+               return(0);
+       }
+
+       ptr = msg->cm_fields[eMesageText];
+       while (ptr != NULL) {
+       
+               linelen = strcspn(ptr, "\n");
+               if (linelen == 0) {
+                       return(0);      /* end of headers */    
+               }
+               
+               if (!strncasecmp(ptr, "Content-type: ", 14)) {
+                       if (!strncasecmp(&ptr[14], INTERNETCFG, strlen(INTERNETCFG))) {
+                               inetcfg_setTo(msg);     /* changing configs */
+                       }
+               }
+
+               ptr = strchr((char *)ptr, '\n');
+               if (ptr != NULL) ++ptr;
+       }
+
+       return(0);
+}
+
+
+void inetcfg_init_backend(long msgnum, void *userdata) {
+       struct CtdlMessage *msg;
+
+               msg = CtdlFetchMessage(msgnum, 1);
+               if (msg != NULL) {
+               inetcfg_setTo(msg);
+                       CM_Free(msg);
+       }
+}
+
+
+void inetcfg_init(void) {
+       syslog(LOG_DEBUG, "EVQ: called inetcfg_init()");
+       if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
+               return;
+       }
+       CtdlForEachMessage(MSGS_LAST, 1, NULL, INTERNETCFG, NULL, inetcfg_init_backend, NULL);
+}
+
+
+/*****************************************************************************/
+/*                      MODULE INITIALIZATION STUFF                          */
+/*****************************************************************************/
+
+
+char *ctdl_module_init_inetcfg(void) {
+       if (!threading) {
+               CtdlRegisterMessageHook(inetcfg_aftersave, EVT_AFTERSAVE);
+               inetcfg_init();
+       }
+       
+       /* return our module name for the log */
+       return "inetcfg";
+}
diff --git a/citadel/server/modules/instmsg/.gitignore b/citadel/server/modules/instmsg/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/instmsg/serv_instmsg.c b/citadel/server/modules/instmsg/serv_instmsg.c
new file mode 100644 (file)
index 0000000..1e2d0f0
--- /dev/null
@@ -0,0 +1,585 @@
+/*
+ * This module handles instant messaging between users.
+ * 
+ * Copyright (c) 1987-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../sysdep.h"
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../context.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../msgbase.h"
+#include "../../user_ops.h"
+#include "../../ctdl_module.h"
+#include "serv_instmsg.h"
+
+struct imlog {
+       struct imlog *next;
+       long usernums[2];
+       char usernames[2][128];
+       time_t lastmsg;
+       int last_serial;
+       StrBuf *conversation;
+};
+
+struct imlog *imlist = NULL;
+
+/*
+ * This function handles the logging of instant messages to disk.
+ */
+void log_instant_message(struct CitContext *me, struct CitContext *them, char *msgtext, int serial_number)
+{
+       long usernums[2];
+       long t;
+       struct imlog *iptr = NULL;
+       struct imlog *this_im = NULL;
+       
+       memset(usernums, 0, sizeof usernums);
+       usernums[0] = me->user.usernum;
+       usernums[1] = them->user.usernum;
+
+       /* Always put the lower user number first, so we can use the array as a hash value which
+        * represents a pair of users.  For a broadcast message one of the users will be 0.
+        */
+       if (usernums[0] > usernums[1]) {
+               t = usernums[0];
+               usernums[0] = usernums[1];
+               usernums[1] = t;
+       }
+
+       begin_critical_section(S_IM_LOGS);
+
+       /* Look for an existing conversation in the hash table.
+        * If not found, create a new one.
+        */
+
+       this_im = NULL;
+       for (iptr = imlist; iptr != NULL; iptr = iptr->next) {
+               if ((iptr->usernums[0] == usernums[0]) && (iptr->usernums[1] == usernums[1])) {
+                       /* Existing conversation */
+                       this_im = iptr;
+               }
+       }
+       if (this_im == NULL) {
+               /* New conversation */
+               this_im = malloc(sizeof(struct imlog));
+               memset(this_im, 0, sizeof (struct imlog));
+               this_im->usernums[0] = usernums[0];
+               this_im->usernums[1] = usernums[1];
+               /* usernames[] and usernums[] might not be in the same order.  This is not an error. */
+               if (me) {
+                       safestrncpy(this_im->usernames[0], me->user.fullname, sizeof this_im->usernames[0]);
+               }
+               if (them) {
+                       safestrncpy(this_im->usernames[1], them->user.fullname, sizeof this_im->usernames[1]);
+               }
+               this_im->conversation = NewStrBuf();
+               this_im->next = imlist;
+               imlist = this_im;
+               StrBufAppendBufPlain(this_im->conversation, HKEY("<html><body>\r\n"), 0);
+       }
+
+       /* Since it's possible for this function to get called more than once if a user is logged
+        * in on multiple sessions, we use the message's serial number to keep track of whether
+        * we've already logged it.
+        */
+       if (this_im->last_serial != serial_number)
+       {
+               this_im->lastmsg = time(NULL);          /* Touch the timestamp so we know when to flush */
+               this_im->last_serial = serial_number;
+               StrBufAppendBufPlain(this_im->conversation, HKEY("<p><b>"), 0);
+               StrBufAppendBufPlain(this_im->conversation, me->user.fullname, -1, 0);
+               StrBufAppendBufPlain(this_im->conversation, HKEY(":</b> "), 0);
+               StrEscAppend(this_im->conversation, NULL, msgtext, 0, 0);
+               StrBufAppendBufPlain(this_im->conversation, HKEY("</p>\r\n"), 0);
+       }
+       end_critical_section(S_IM_LOGS);
+}
+
+
+/*
+ * Delete any remaining instant messages
+ */
+void delete_instant_messages(void) {
+       struct ExpressMessage *ptr;
+
+       begin_critical_section(S_SESSION_TABLE);
+       while (CC->FirstExpressMessage != NULL) {
+               ptr = CC->FirstExpressMessage->next;
+               if (CC->FirstExpressMessage->text != NULL)
+                       free(CC->FirstExpressMessage->text);
+               free(CC->FirstExpressMessage);
+               CC->FirstExpressMessage = ptr;
+       }
+       end_critical_section(S_SESSION_TABLE);
+}
+
+
+/*
+ * Retrieve instant messages
+ */
+void cmd_gexp(char *argbuf) {
+       struct ExpressMessage *ptr;
+
+       if (CC->FirstExpressMessage == NULL) {
+               cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
+               return;
+       }
+
+       begin_critical_section(S_SESSION_TABLE);
+       ptr = CC->FirstExpressMessage;
+       CC->FirstExpressMessage = CC->FirstExpressMessage->next;
+       end_critical_section(S_SESSION_TABLE);
+
+       cprintf("%d %d|%ld|%d|%s|%s|%s\n",
+               LISTING_FOLLOWS,
+               ((ptr->next != NULL) ? 1 : 0),          /* more msgs? */
+               (long)ptr->timestamp,                   /* time sent */
+               ptr->flags,                             /* flags */
+               ptr->sender,                            /* sender of msg */
+               CtdlGetConfigStr("c_nodename"),         /* static for now (and possibly deprecated) */
+               ptr->sender_email                       /* email or jid of sender */
+       );
+
+       if (ptr->text != NULL) {
+               memfmout(ptr->text, "\n");
+               free(ptr->text);
+       }
+
+       cprintf("000\n");
+       free(ptr);
+}
+
+
+/*
+ * Asynchronously deliver instant messages
+ */
+void cmd_gexp_async(void) {
+
+       /* Only do this if the session can handle asynchronous protocol */
+       if (CC->is_async == 0) return;
+
+       /* And don't do it if there's nothing to send. */
+       if (CC->FirstExpressMessage == NULL) return;
+
+       cprintf("%d instant msg\n", ASYNC_MSG + ASYNC_GEXP);
+}
+
+
+/*
+ * Back end support function for send_instant_message() and company
+ */
+void add_xmsg_to_context(struct CitContext *ccptr, struct ExpressMessage *newmsg) 
+{
+       struct ExpressMessage *findend;
+
+       if (ccptr->FirstExpressMessage == NULL) {
+               ccptr->FirstExpressMessage = newmsg;
+       }
+       else {
+               findend = ccptr->FirstExpressMessage;
+               while (findend->next != NULL) {
+                       findend = findend->next;
+               }
+               findend->next = newmsg;
+       }
+
+       /* If the target context is a session which can handle asynchronous
+        * messages, go ahead and set the flag for that.
+        */
+       set_async_waiting(ccptr);
+}
+
+
+/* 
+ * This is the back end to the instant message sending function.  
+ * Returns the number of users to which the message was sent.
+ * Sending a zero-length message tests for recipients without sending messages.
+ */
+int send_instant_message(char *lun, char *lem, char *x_user, char *x_msg)
+{
+       int message_sent = 0;           /* number of successful sends */
+       struct CitContext *ccptr;
+       struct ExpressMessage *newmsg = NULL;
+       int do_send = 0;                /* 1 = send message; 0 = only check for valid recipient */
+       static int serial_number = 0;   /* this keeps messages from getting logged twice */
+
+       if (!IsEmptyStr(x_msg)) {
+               do_send = 1;
+       }
+
+       /* find the target user's context and append the message */
+       begin_critical_section(S_SESSION_TABLE);
+       ++serial_number;
+       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+
+               if ( ((!strcasecmp(ccptr->user.fullname, x_user))
+                   || (!strcasecmp(x_user, "broadcast")))
+                   && (ccptr->can_receive_im)
+                   && ((ccptr->disable_exp == 0)
+                   || (CC->user.axlevel >= AxAideU)) ) {
+                       if (do_send) {
+                               newmsg = (struct ExpressMessage *) malloc(sizeof (struct ExpressMessage));
+                               memset(newmsg, 0, sizeof (struct ExpressMessage));
+                               time(&(newmsg->timestamp));
+                               safestrncpy(newmsg->sender, lun, sizeof newmsg->sender);
+                               safestrncpy(newmsg->sender_email, lem, sizeof newmsg->sender_email);
+                               if (!strcasecmp(x_user, "broadcast")) {
+                                       newmsg->flags |= EM_BROADCAST;
+                               }
+                               newmsg->text = strdup(x_msg);
+
+                               add_xmsg_to_context(ccptr, newmsg);
+
+                               /* and log it ... */
+                               if (ccptr != CC) {
+                                       log_instant_message(CC, ccptr, newmsg->text, serial_number);
+                               }
+                       }
+                       ++message_sent;
+               }
+       }
+       end_critical_section(S_SESSION_TABLE);
+       return (message_sent);
+}
+
+
+/*
+ * send instant messages
+ */
+void cmd_sexp(char *argbuf)
+{
+       int message_sent = 0;
+       char x_user[USERNAME_SIZE];
+       char x_msg[1024];
+       char *lem;
+       char *x_big_msgbuf = NULL;
+
+       if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
+               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+               return;
+       }
+
+       lem = CC->cs_principal_id;
+
+       extract_token(x_user, argbuf, 0, '|', sizeof x_user);
+       extract_token(x_msg, argbuf, 1, '|', sizeof x_msg);
+
+       if (!x_user[0]) {
+               cprintf("%d You were not previously paged.\n", ERROR + NO_SUCH_USER);
+               return;
+       }
+       if ((!strcasecmp(x_user, "broadcast")) && (CC->user.axlevel < AxAideU)) {
+               cprintf("%d Higher access required to send a broadcast.\n",
+                       ERROR + HIGHER_ACCESS_REQUIRED);
+               return;
+       }
+       /* This loop handles text-transfer pages */
+       if (!strcmp(x_msg, "-")) {
+               message_sent = PerformXmsgHooks(CC->user.fullname, lem, x_user, "");
+               if (message_sent == 0) {
+                       if (CtdlGetUser(NULL, x_user))
+                               cprintf("%d '%s' does not exist.\n",
+                                               ERROR + NO_SUCH_USER, x_user);
+                       else
+                               cprintf("%d '%s' is not logged in "
+                                               "or is not accepting pages.\n",
+                                               ERROR + RESOURCE_NOT_OPEN, x_user);
+                       return;
+               }
+               unbuffer_output();
+               cprintf("%d Transmit message (will deliver to %d users)\n",
+                       SEND_LISTING, message_sent);
+               x_big_msgbuf = malloc(SIZ);
+               memset(x_big_msgbuf, 0, SIZ);
+               while (client_getln(x_msg, sizeof x_msg) >= 0 && strcmp(x_msg, "000")) {
+                       x_big_msgbuf = realloc(x_big_msgbuf,
+                              strlen(x_big_msgbuf) + strlen(x_msg) + 4);
+                       if (!IsEmptyStr(x_big_msgbuf))
+                          if (x_big_msgbuf[strlen(x_big_msgbuf)] != '\n')
+                               strcat(x_big_msgbuf, "\n");
+                       strcat(x_big_msgbuf, x_msg);
+               }
+               PerformXmsgHooks(CC->user.fullname, lem, x_user, x_big_msgbuf);
+               free(x_big_msgbuf);
+
+               /* This loop handles inline pages */
+       } else {
+               message_sent = PerformXmsgHooks(CC->user.fullname, lem, x_user, x_msg);
+
+               if (message_sent > 0) {
+                       if (!IsEmptyStr(x_msg)) {
+                               cprintf("%d Message sent", CIT_OK);
+                       }
+                       else {
+                               cprintf("%d Ok to send message", CIT_OK);
+                       }
+                       if (message_sent > 1) {
+                               cprintf(" to %d users", message_sent);
+                       }
+                       cprintf(".\n");
+               } else {
+                       if (CtdlGetUser(NULL, x_user)) {
+                               cprintf("%d '%s' does not exist.\n", ERROR + NO_SUCH_USER, x_user);
+                       }
+                       else {
+                               cprintf("%d '%s' is not logged in or is not accepting instant messages.\n",
+                                               ERROR + RESOURCE_NOT_OPEN, x_user);
+                       }
+               }
+
+
+       }
+}
+
+
+/*
+ * Enter or exit paging-disabled mode
+ */
+void cmd_dexp(char *argbuf)
+{
+       int new_state;
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       new_state = extract_int(argbuf, 0);
+       if ((new_state == 0) || (new_state == 1)) {
+               CC->disable_exp = new_state;
+       }
+
+       cprintf("%d %d\n", CIT_OK, CC->disable_exp);
+}
+
+
+/*
+ * Request client termination
+ */
+void cmd_reqt(char *argbuf) {
+       struct CitContext *ccptr;
+       int sessions = 0;
+       int which_session;
+       struct ExpressMessage *newmsg;
+
+       if (CtdlAccessCheck(ac_aide)) return;
+       which_session = extract_int(argbuf, 0);
+
+       begin_critical_section(S_SESSION_TABLE);
+       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+               if ((ccptr->cs_pid == which_session) || (which_session == 0)) {
+
+                       newmsg = (struct ExpressMessage *)
+                               malloc(sizeof (struct ExpressMessage));
+                       memset(newmsg, 0,
+                               sizeof (struct ExpressMessage));
+                       time(&(newmsg->timestamp));
+                       safestrncpy(newmsg->sender, CC->user.fullname,
+                                   sizeof newmsg->sender);
+                       newmsg->flags |= EM_GO_AWAY;
+                       newmsg->text = strdup("Automatic logoff requested.");
+
+                       add_xmsg_to_context(ccptr, newmsg);
+                       ++sessions;
+
+               }
+       }
+       end_critical_section(S_SESSION_TABLE);
+       cprintf("%d Sent termination request to %d sessions.\n", CIT_OK, sessions);
+}
+
+
+/*
+ * This is the back end for flush_conversations_to_disk()
+ * At this point we've isolated a single conversation (struct imlog)
+ * and are ready to write it to disk.
+ */
+void flush_individual_conversation(struct imlog *im) {
+       struct CtdlMessage *msg;
+       long msgnum = 0;
+       char roomname[ROOMNAMELEN];
+       StrBuf *MsgBuf, *FullMsgBuf;
+
+       StrBufAppendBufPlain(im->conversation, HKEY(
+               "</body>\r\n"
+               "</html>\r\n"
+               ), 0
+       );
+
+       MsgBuf = StrBufRFC2047encodeMessage(im->conversation);
+       FlushStrBuf(im->conversation);
+       FullMsgBuf = NewStrBufPlain(NULL, StrLength(im->conversation) + 100);
+
+       StrBufAppendBufPlain(FullMsgBuf, HKEY(
+               "Content-type: text/html; charset=UTF-8\r\n"
+               "Content-Transfer-Encoding: quoted-printable\r\n"
+               "\r\n"
+               ), 0
+       );
+       StrBufAppendBuf (FullMsgBuf, MsgBuf, 0);
+       FreeStrBuf(&MsgBuf);
+
+       msg = malloc(sizeof(struct CtdlMessage));
+       memset(msg, 0, sizeof(struct CtdlMessage));
+       msg->cm_magic = CTDLMESSAGE_MAGIC;
+       msg->cm_anon_type = MES_NORMAL;
+       msg->cm_format_type = FMT_RFC822;
+       if (!IsEmptyStr(im->usernames[0])) {
+               CM_SetField(msg, eAuthor, im->usernames[0], strlen(im->usernames[0]));
+       } else {
+               CM_SetField(msg, eAuthor, HKEY("Citadel"));
+       }
+       if (!IsEmptyStr(im->usernames[1])) {
+               CM_SetField(msg, eRecipient, im->usernames[1], strlen(im->usernames[1]));
+       }
+
+       CM_SetField(msg, eOriginalRoom, HKEY(PAGELOGROOM));
+       CM_SetAsFieldSB(msg, eMesageText, &FullMsgBuf); /* we own this memory now */
+
+       /* Start with usernums[1] because it's guaranteed to be higher than usernums[0],
+        * so if there's only one party, usernums[0] will be zero but usernums[1] won't.
+        * Create the room if necessary.  Note that we create as a type 5 room rather
+        * than 4, which indicates that it's a personal room but we've already supplied
+        * the namespace prefix.
+        *
+        * In the unlikely event that usernums[1] is zero, a room with an invalid namespace
+        * prefix will be created.  That's ok because the auto-purger will clean it up later.
+        */
+       snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[1], PAGELOGROOM);
+       CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS);
+       msgnum = CtdlSubmitMsg(msg, NULL, roomname);
+       CM_Free(msg);
+
+       /* If there is a valid user number in usernums[0], save a copy for them too. */
+       if (im->usernums[0] > 0) {
+               snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[0], PAGELOGROOM);
+               CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS);
+               CtdlSaveMsgPointerInRoom(roomname, msgnum, 0, NULL);
+       }
+
+       /* Finally, if we're logging instant messages globally, do that now. */
+       if (!IsEmptyStr(CtdlGetConfigStr("c_logpages"))) {
+               CtdlCreateRoom(CtdlGetConfigStr("c_logpages"), 3, "", 0, 1, 1, VIEW_BBS);
+               CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_logpages"), msgnum, 0, NULL);
+       }
+
+}
+
+/*
+ * Locate instant message conversations which have gone idle
+ * (or, if the server is shutting down, locate *all* conversations)
+ * and flush them to disk (in the participants' log rooms, etc.)
+ */
+void flush_conversations_to_disk(time_t if_older_than) {
+
+       struct imlog *flush_these = NULL;
+       struct imlog *dont_flush_these = NULL;
+       struct imlog *imptr = NULL;
+       struct CitContext *nptr;
+       int nContexts, i;
+
+       nptr = CtdlGetContextArray(&nContexts) ;        /* Make a copy of the current wholist */
+
+       begin_critical_section(S_IM_LOGS);
+       while (imlist)
+       {
+               imptr = imlist;
+               imlist = imlist->next;
+
+               /* For a two party conversation, if one party has logged out, force flush. */
+               if (nptr) {
+                       int user0_is_still_online = 0;
+                       int user1_is_still_online = 0;
+                       for (i=0; i<nContexts; i++)  {
+                               if (nptr[i].user.usernum == imptr->usernums[0]) ++user0_is_still_online;
+                               if (nptr[i].user.usernum == imptr->usernums[1]) ++user1_is_still_online;
+                       }
+                       if (imptr->usernums[0] != imptr->usernums[1]) {         /* two party conversation */
+                               if ((!user0_is_still_online) || (!user1_is_still_online)) {
+                                       imptr->lastmsg = 0L;    /* force flush */
+                               }
+                       }
+                       else {          /* one party conversation (yes, people do IM themselves) */
+                               if (!user0_is_still_online) {
+                                       imptr->lastmsg = 0L;    /* force flush */
+                               }
+                       }
+               }
+
+               /* Now test this conversation to see if it qualifies for flushing. */
+               if ((time(NULL) - imptr->lastmsg) > if_older_than)
+               {
+                       /* This conversation qualifies.  Move it to the list of ones to flush. */
+                       imptr->next = flush_these;
+                       flush_these = imptr;
+               }
+               else  {
+                       /* Move it to the list of ones not to flush. */
+                       imptr->next = dont_flush_these;
+                       dont_flush_these = imptr;
+               }
+       }
+       imlist = dont_flush_these;
+       end_critical_section(S_IM_LOGS);
+       free(nptr);
+
+       /* We are now outside of the critical section, and we are the only thread holding a
+        * pointer to a linked list of conversations to be flushed to disk.
+        */
+       while (flush_these) {
+
+               flush_individual_conversation(flush_these);     /* This will free the string buffer */
+               imptr = flush_these;
+               flush_these = flush_these->next;
+               free(imptr);
+       }
+}
+
+
+
+void instmsg_timer(void) {
+       flush_conversations_to_disk(300);       /* Anything that hasn't peeped in more than 5 minutes */
+}
+
+void instmsg_shutdown(void) {
+       flush_conversations_to_disk(0);         /* Get it ALL onto disk NOW. */
+}
+
+char *ctdl_module_init_instmsg(void) {
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_gexp, "GEXP", "Get instant messages");
+               CtdlRegisterProtoHook(cmd_sexp, "SEXP", "Send an instant message");
+               CtdlRegisterProtoHook(cmd_dexp, "DEXP", "Disable instant messages");
+               CtdlRegisterProtoHook(cmd_reqt, "REQT", "Request client termination");
+               CtdlRegisterSessionHook(cmd_gexp_async, EVT_ASYNC, PRIO_ASYNC + 1);
+               CtdlRegisterSessionHook(delete_instant_messages, EVT_STOP, PRIO_STOP + 1);
+               CtdlRegisterXmsgHook(send_instant_message, XMSG_PRI_LOCAL);
+               CtdlRegisterSessionHook(instmsg_timer, EVT_TIMER, PRIO_CLEANUP + 400);
+               CtdlRegisterSessionHook(instmsg_shutdown, EVT_SHUTDOWN, PRIO_SHUTDOWN + 10);
+       }
+       
+       /* return our module name for the log */
+       return "instmsg";
+}
diff --git a/citadel/server/modules/instmsg/serv_instmsg.h b/citadel/server/modules/instmsg/serv_instmsg.h
new file mode 100644 (file)
index 0000000..a738486
--- /dev/null
@@ -0,0 +1,9 @@
+
+void ChatUnloadingTest(void);
+void allwrite (char *cmdbuf, int flag, char *username);
+CitContext *find_context (char **unstr);
+void cmd_pexp (char *argbuf); /* arg unused */
+void cmd_sexp (char *argbuf);
+void delete_instant_messages(void);
+void cmd_gexp(char *);
+int send_instant_message(char *, char *, char *, char *);
diff --git a/citadel/server/modules/listdeliver/serv_listdeliver.c b/citadel/server/modules/listdeliver/serv_listdeliver.c
new file mode 100644 (file)
index 0000000..e3b1d23
--- /dev/null
@@ -0,0 +1,249 @@
+// This module delivers messages to mailing lists.
+//
+// Copyright (c) 2002-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//  
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../clientsocket.h"
+#include "../../ctdl_module.h"
+
+int doing_listdeliver = 0;
+
+
+// data passed back and forth between listdeliver_do_msg() and listdeliver_sweep_room()
+struct lddata {
+       long msgnum;            // number of most recent message processed
+       char *netconf;          // netconfig for this room (contains the recipients)
+};
+
+
+
+void listdeliver_do_msg(long msgnum, void *userdata) {
+       struct lddata *ld = (struct lddata *) userdata;
+       if (!ld) return;
+       char buf[SIZ];
+       char *ch;
+       char bounce_to[256];
+       int i = 0;
+
+       ld->msgnum = msgnum;
+       if (msgnum <= 0) return;
+
+       struct CtdlMessage *TheMessage = CtdlFetchMessage(msgnum, 1);
+       if (!TheMessage) return;
+
+       // If the subject line does not contain the name of the room, add it now.
+       if (!bmstrcasestr(TheMessage->cm_fields[eMsgSubject], CC->room.QRname)) {
+               snprintf(buf, sizeof buf, "[%s] %s", CC->room.QRname, TheMessage->cm_fields[eMsgSubject]);
+               CM_SetField(TheMessage, eMsgSubject, buf, strlen(buf));
+       }
+
+       // From: should be set to the list address because doing otherwise makes DKIM parsers angry.
+       // Reply-to: should be set to the list address so that replies come to the list.
+       snprintf(buf, sizeof buf, "room_%s@%s", CC->room.QRname, CtdlGetConfigStr("c_fqdn"));
+       for (ch=buf; *ch; ++ch) {
+               if (isspace(*ch)) *ch = '_';
+       }
+       CM_SetField(TheMessage, erFc822Addr, buf, strlen(buf));
+       CM_SetField(TheMessage, eReplyTo, buf, strlen(buf));
+
+       // With that out of the way, let's figure out who this message needs to be sent to.
+       char *recipients = malloc(strlen(ld->netconf));
+       if (recipients) {
+               recipients[0] = 0;
+
+               int config_lines = num_tokens(ld->netconf, '\n');
+               for (i=0; i<config_lines; ++i) {
+                       extract_token(buf, ld->netconf, i, '\n', sizeof buf);
+                       if (!strncasecmp(buf, "listrecp|", 9)) {
+                               if (recipients[0] != 0) {
+                                       strcat(recipients, ",");
+                               }
+                               strcat(recipients, &buf[9]);
+                       }
+                       if (!strncasecmp(buf, "digestrecp|", 11)) {
+                               if (recipients[0] != 0) {
+                                       strcat(recipients, ",");
+                               }
+                               strcat(recipients, &buf[11]);
+                       }
+               }
+
+               // Where do we want bounces and other noise to be sent?  Certainly not to the list members!
+               snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", CtdlGetConfigStr("c_fqdn"));
+
+               // Now submit the message
+               struct recptypes *valid = validate_recipients(recipients, NULL, 0);
+               if (valid) {
+                       valid->bounce_to = strdup(bounce_to);
+                       valid->envelope_from = strdup(bounce_to);
+                       valid->sending_room = strdup(CC->room.QRname);
+                       CtdlSubmitMsg(TheMessage, valid, "");
+                       free_recipients(valid);
+               }
+       }
+       CM_Free(TheMessage);
+}
+
+
+// Sweep through one room looking for mailing list deliveries to do
+void listdeliver_sweep_room(char *roomname) {
+       char *netconfig = NULL;
+       char *newnetconfig = NULL;
+       long lastsent = 0;
+       char buf[SIZ];
+       int config_lines;
+       int i;
+       int number_of_messages_processed = 0;
+       int number_of_recipients = 0;
+       struct lddata ld;
+
+       if (CtdlGetRoom(&CC->room, roomname)) {
+               syslog(LOG_DEBUG, "listdeliver: no room <%s>", roomname);
+               return;
+       }
+
+        netconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
+        if (!netconfig) {
+               return;                         // no netconfig, no processing, no problem
+       }
+
+       config_lines = num_tokens(netconfig, '\n');
+       for (i=0; i<config_lines; ++i) {
+               extract_token(buf, netconfig, i, '\n', sizeof buf);
+
+               if (!strncasecmp(buf, "lastsent|", 9)) {
+                       lastsent = atol(&buf[9]);
+               }
+               else if ( (!strncasecmp(buf, "listrecp|", 9)) || (!strncasecmp(buf, "digestrecp|", 11)) ) {
+                       ++number_of_recipients;
+               }
+       }
+
+       if (number_of_recipients > 0) {
+               syslog(LOG_DEBUG, "listdeliver: processing new messages in <%s> for <%d> recipients", CC->room.QRname, number_of_recipients);
+               ld.netconf = netconfig;
+               number_of_messages_processed = CtdlForEachMessage(MSGS_GT, lastsent, NULL, NULL, NULL, listdeliver_do_msg, &ld);
+               syslog(LOG_INFO, "listdeliver: processed <%d> messages in <%s> for <%d> recipients", number_of_messages_processed, CC->room.QRname, number_of_recipients);
+       
+               if (number_of_messages_processed > 0) {
+                       syslog(LOG_DEBUG, "listdeliver: new lastsent is %ld", ld.msgnum);
+
+                       // Update this room's netconfig with the updated lastsent
+                       begin_critical_section(S_NETCONFIGS);
+                       netconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
+                       if (!netconfig) {
+                               netconfig = strdup("");
+                       }
+
+                       // The new netconfig begins with the new lastsent directive
+                       newnetconfig = malloc(strlen(netconfig) + 1024);
+                       sprintf(newnetconfig, "lastsent|%ld\n", ld.msgnum);
+
+                       // And then we append all of the old netconfig, minus the old lastsent.  Also omit blank lines.
+                       config_lines = num_tokens(netconfig, '\n');
+                       for (i=0; i<config_lines; ++i) {
+                               extract_token(buf, netconfig, i, '\n', sizeof buf);
+                               if ( (!IsEmptyStr(buf)) && (strncasecmp(buf, "lastsent|", 9)) ) {
+                                       sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);
+                               }
+                       }
+
+                       // Write the new netconfig back to disk
+                       SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig);
+                       end_critical_section(S_NETCONFIGS);
+                       free(newnetconfig);     // this was the new netconfig, free it because we're done with it
+               }
+       }
+       free(netconfig);                        // this was the old netconfig, free it even if we didn't do anything
+}
+
+
+// Callback for listdeliver_sweep()
+// Adds one room to the queue
+void listdeliver_queue_room(struct ctdlroom *qrbuf, void *data) {
+       Array *roomlistarr = (Array *)data;
+       array_append(roomlistarr, qrbuf->QRname);
+}
+
+
+// Queue up the list of rooms so we can sweep them for mailing list delivery instructions
+void listdeliver_sweep(void) {
+       static time_t last_run = 0L;
+       int i = 0;
+       time_t now = time(NULL);
+
+       // Run mailing list delivery no more frequently than once every 15 minutes (we should make this configurable)
+       if ( (now - last_run) < 900 ) {
+               syslog(LOG_DEBUG,
+                       "listdeliver: delivery interval not yet reached; last run was %ldm%lds ago",
+                       ((now - last_run) / 60),
+                       ((now - last_run) % 60)
+               );
+               return;
+       }
+
+       // This is a simple concurrency check to make sure only one listdeliver
+       // run is done at a time.  We could do this with a mutex, but since we
+       // don't really require extremely fine granularity here, we'll do it
+       // with a static variable instead.
+       if (doing_listdeliver) return;
+       doing_listdeliver = 1;
+
+       // Go through each room looking for mailing lists to process
+       syslog(LOG_DEBUG, "listdeliver: sweep started");
+
+       Array *roomlistarr = array_new(ROOMNAMELEN);                    // we have to queue them
+       CtdlForEachRoom(listdeliver_queue_room, roomlistarr);           // otherwise we get multiple cursors in progress
+
+       for (i=0; i<array_len(roomlistarr); ++i) {
+               listdeliver_sweep_room((char *)array_get_element_at(roomlistarr, i));
+       }
+
+       array_free(roomlistarr);
+       syslog(LOG_DEBUG, "listdeliver: ended");
+       last_run = time(NULL);
+       doing_listdeliver = 0;
+}
+
+
+// Module entry point
+char *ctdl_module_init_listdeliver(void) {
+       if (!threading) {
+               CtdlRegisterSessionHook(listdeliver_sweep, EVT_TIMER, PRIO_AGGR + 50);
+       }
+       
+       // return our module name for the log
+       return "listsub";
+}
diff --git a/citadel/server/modules/listsub/.gitignore b/citadel/server/modules/listsub/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/listsub/serv_listsub.c b/citadel/server/modules/listsub/serv_listsub.c
new file mode 100644 (file)
index 0000000..80adb60
--- /dev/null
@@ -0,0 +1,304 @@
+// This module handles self-service subscription/unsubscription to mail lists.
+//
+// Copyright (c) 2002-2022 by the citadel.org team
+//
+// This program is open source software.  It runs great on the
+// Linux operating system (and probably elsewhere).  You can use,
+// copy, and run it under the terms of the GNU General Public
+// License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <crypt.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../clientsocket.h"
+#include "../../ctdl_module.h"
+
+
+enum {                         // one of these gets passed to do_subscribe_or_unsubscribe() so it knows what we asked for
+       UNSUBSCRIBE,
+       SUBSCRIBE
+};
+
+
+// The confirmation token will be generated by combining the room name and email address with the host key,
+// and then generating an encrypted hash of that string.  The encrypted hash is included as part of the
+// confirmation link.
+void generate_confirmation_token(char *token_buf, size_t token_buf_len, char *roomname, char *emailaddr) {
+       char string_to_hash[1024];
+       struct crypt_data cd;
+       char *ptr;
+
+       snprintf(string_to_hash, sizeof string_to_hash, "%s|%s|%s", roomname, emailaddr, CtdlGetConfigStr("host_key"));
+       memset(&cd, 0, sizeof cd);
+
+       strncpy(token_buf, crypt_r(string_to_hash, "$1$ctdl", &cd), token_buf_len);
+
+       for (ptr=token_buf; *ptr; ++ptr) {
+               if (!isalnum((char)*ptr)) *ptr='X';
+       }
+}
+
+
+// This generates an email with a link the user clicks to confirm a list subscription.
+void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
+       // We need a URL-safe representation of the room name
+       char urlroom[ROOMNAMELEN+10];
+       urlesc(urlroom, sizeof(urlroom), roomname);
+
+       char from_address[1024];
+       snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
+
+       char emailtext[SIZ];
+       snprintf(emailtext, sizeof emailtext,
+               "MIME-Version: 1.0\n"
+               "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
+               "\n"
+               "This is a multipart message in MIME format.\n"
+               "\n"
+               "--__ctdlmultipart__\n"
+               "Content-type: text/plain\n"
+               "\n"
+               "Someone (probably you) has submitted a request to subscribe\n"
+               "<%s> to the <%s> mailing list.\n"
+               "\n"
+               "Please go here to confirm this request:\n"
+               "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\n"
+               "\n"
+               "If this request has been submitted in error and you do not\n"
+               "wish to receive the <%s> mailing list, simply do nothing,\n"
+               "and you will not receive any further mailings.\n"
+               "\n"
+               "--__ctdlmultipart__\n"
+               "Content-type: text/html\n"
+               "\n"
+               "<html><body><p>Someone (probably you) has submitted a request to subscribe "
+               "<strong>%s</strong> to the <strong>%s</strong> mailing list.</p>"
+               "<p>Please go here to confirm this request:</p>"
+               "<p><a href=\"%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\">"
+               "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s</a></p>"
+               "<p>If this request has been submitted in error and you do not "
+               "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
+               "and you will not receive any further mailings.</p>"
+               "</body></html>\n"
+               "\n"
+               "--__ctdlmultipart__--\n"
+               ,
+               emailaddr, roomname,
+               url, emailaddr, urlroom, confirmation_token,
+               roomname
+               ,
+               emailaddr, roomname,
+               url, emailaddr, urlroom, confirmation_token,
+               url, emailaddr, urlroom, confirmation_token,
+               roomname
+       );
+
+       quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list subscription");
+       cprintf("%d confirmation email sent\n", CIT_OK);
+}
+
+
+// This generates an email with a link the user clicks to confirm a list unsubscription.
+void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
+       // We need a URL-safe representation of the room name
+       char urlroom[ROOMNAMELEN+10];
+       urlesc(urlroom, sizeof(urlroom), roomname);
+
+       char from_address[1024];
+       snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
+
+       char emailtext[SIZ];
+       snprintf(emailtext, sizeof emailtext,
+               "MIME-Version: 1.0\n"
+               "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
+               "\n"
+               "This is a multipart message in MIME format.\n"
+               "\n"
+               "--__ctdlmultipart__\n"
+               "Content-type: text/plain\n"
+               "\n"
+               "Someone (probably you) has submitted a request to unsubscribe\n"
+               "<%s> from the <%s> mailing list.\n"
+               "\n"
+               "Please go here to confirm this request:\n"
+               "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\n"
+               "\n"
+               "If this request has been submitted in error and you still\n"
+               "wish to receive the <%s> mailing list, simply do nothing,\n"
+               "and you will remain subscribed.\n"
+               "\n"
+               "--__ctdlmultipart__\n"
+               "Content-type: text/html\n"
+               "\n"
+               "<html><body><p>Someone (probably you) has submitted a request to unsubscribe "
+               "<strong>%s</strong> from the <strong>%s</strong> mailing list.</p>"
+               "<p>Please go here to confirm this request:</p>"
+               "<p><a href=\"%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\">"
+               "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s</a></p>"
+               "<p>If this request has been submitted in error and you still "
+               "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
+               "and you will remain subscribed.</p>"
+               "</body></html>\n"
+               "\n"
+               "--__ctdlmultipart__--\n"
+               ,
+               emailaddr, roomname,
+               url, emailaddr, urlroom, confirmation_token,
+               roomname
+               ,
+               emailaddr, roomname,
+               url, emailaddr, urlroom, confirmation_token,
+               url, emailaddr, urlroom, confirmation_token,
+               roomname
+       );
+
+       quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list unsubscription");
+       cprintf("%d confirmation email sent\n", CIT_OK);
+}
+
+
+// Confirm a list subscription or unsubscription
+void do_confirm(int cmd, char *roomname, char *emailaddr, char *url, char *generated_token, char *supplied_token) {
+       int i;
+       char buf[1024];
+       int config_lines = 0;
+       char *oldnetconfig, *newnetconfig;
+
+       // During opt #1, the server generated a persistent confirmation token for the user+room combination.
+       // Let's see if the user has supplied the same token during opt #2.
+       if (strcmp(generated_token, supplied_token)) {
+               cprintf("%d This request could not be authenticated.\n", ERROR + PASSWORD_REQUIRED);
+               return;
+       }
+
+       // If the generated token matches the supplied token, the request is authentic.  Do what it says.
+
+       // Load the room's network configuration...
+        oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
+        if (!oldnetconfig) {
+               oldnetconfig = strdup("");
+       }
+
+       // The new netconfig begins with an empty buffer...
+       begin_critical_section(S_NETCONFIGS);
+       newnetconfig = malloc(strlen(oldnetconfig) + 1024);
+       newnetconfig[0] = 0;
+
+       // Load the config lines in one by one, skipping any that reference this subscriber.  Also remove blank lines.
+       config_lines = num_tokens(oldnetconfig, '\n');
+       for (i=0; i<config_lines; ++i) {
+               char buf_email[256];
+               extract_token(buf, oldnetconfig, i, '\n', sizeof buf);
+               extract_token(buf_email, buf, 1, '|', sizeof buf_email);
+               if ( !IsEmptyStr(buf) && (strcasecmp(buf_email, emailaddr)) ) {
+                       sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);
+               }
+       }
+
+       // We have now removed all lines containing the subscriber's email address.  This deletes any pending requests.
+       // If this was an unsubscribe operation, they're now gone from the list.
+       // But if this was a subscribe operation, we now need to add them.
+       if (cmd == SUBSCRIBE) {
+               sprintf(&newnetconfig[strlen(newnetconfig)], "listrecp|%s\n", emailaddr);
+       }
+
+       // write it back to disk
+       SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig);
+       end_critical_section(S_NETCONFIGS);
+       free(oldnetconfig);
+       free(newnetconfig);
+       cprintf("%d The pending request was confirmed.\n", CIT_OK);
+}
+
+
+// process subscribe/unsubscribe requests and confirmations
+void cmd_lsub(char *cmdbuf) {
+       char cmd[20];
+       char roomname[ROOMNAMELEN];
+       char emailaddr[1024];
+       char url[1024];
+       char generated_token[128];
+       char supplied_token[128];
+
+       extract_token(cmd, cmdbuf, 0, '|', sizeof cmd);                         // token 0 is the sub-command being sent
+       extract_token(roomname, cmdbuf, 1, '|', sizeof roomname);               // token 1 is always a room name
+       extract_token(emailaddr, cmdbuf, 2, '|', sizeof emailaddr);             // token 2 is the subscriber's email address
+       extract_token(url, cmdbuf, 3, '|', sizeof url);                         // token 3 is the URL at which we subscribed
+       extract_token(supplied_token, cmdbuf, 4, '|', sizeof supplied_token);   // token 4 is the token supplied by the caller
+
+       // First confirm that the caller is referencing a room that actually exists.
+       if (CtdlGetRoom(&CC->room, roomname) != 0) {
+               cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, roomname);
+               return;
+       }
+
+       if ((CC->room.QRflags2 & QR2_SELFLIST) == 0) {
+               cprintf("%d '%s' does not accept subscribe/unsubscribe requests.\n", ERROR + ROOM_NOT_FOUND, roomname);
+               return;
+       }
+
+       // Generate a confirmation token -- either to supply to the user for opt #1 or to compare for opt #2
+       generate_confirmation_token(generated_token, sizeof generated_token, roomname, emailaddr);
+
+       // Now parse the command.
+       if (!strcasecmp(cmd, "subscribe")) {
+               send_subscribe_confirmation_email(roomname, emailaddr, url, generated_token);
+       }
+
+       else if (!strcasecmp(cmd, "unsubscribe")) {
+               send_unsubscribe_confirmation_email(roomname, emailaddr, url, generated_token);
+       }
+
+       else if (!strcasecmp(cmd, "confirm_subscribe")) {
+               do_confirm(SUBSCRIBE, roomname, emailaddr, url, generated_token, supplied_token);
+       }
+
+       else if (!strcasecmp(cmd, "confirm_unsubscribe")) {
+               do_confirm(UNSUBSCRIBE, roomname, emailaddr, url, generated_token, supplied_token);
+       }
+
+       else {                                                                  // sorry man, I can't deal with that
+               cprintf("%d Invalid command '%s'\n", ERROR + ILLEGAL_VALUE, cmd);
+       }
+}
+
+
+/*
+ * Module entry point
+ */
+char *ctdl_module_init_listsub(void) {
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_lsub, "LSUB", "List subscribe/unsubscribe");
+       }
+       
+       /* return our module name for the log */
+       return "listsub";
+}
diff --git a/citadel/server/modules/migrate/.gitignore b/citadel/server/modules/migrate/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/migrate/serv_migrate.c b/citadel/server/modules/migrate/serv_migrate.c
new file mode 100644 (file)
index 0000000..698cf1c
--- /dev/null
@@ -0,0 +1,1128 @@
+// This module dumps and/or loads the Citadel database in XML format.
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// Explanation of <progress> tags:
+//
+// 0%          started
+// 2%          finished exporting configuration
+// 7%          finished exporting users
+// 12%         finished exporting openids
+// 17%         finished exporting rooms
+// 18%         finished exporting floors
+// 25%         finished exporting visits
+// 26-99%      exporting messages
+// 100%                finished exporting messages
+//
+// These tags are inserted into the XML stream to give the reader an approximation of its progress.
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../user_ops.h"
+#include "../../euidindex.h"
+#include "../../ctdl_module.h"
+
+char migr_tempfilename1[PATH_MAX];
+char migr_tempfilename2[PATH_MAX];
+FILE *migr_global_message_list;
+int total_msgs = 0;
+char *ikey = NULL;                     // If we're importing a config key we store it here.
+
+//*****************************************************************************
+//*       Code which implements the export appears in this section            *
+//*****************************************************************************
+
+// Output a string to the client with these characters escaped:  & < > " '
+void xml_strout(char *str) {
+
+       char *c = str;
+
+       if (str == NULL) {
+               return;
+       }
+
+       while (*c != 0) {
+               if (*c == '\"') {
+                       client_write(HKEY("&quot;"));
+               }
+               else if (*c == '\'') {
+                       client_write(HKEY("&apos;"));
+               }
+               else if (*c == '<') {
+                       client_write(HKEY("&lt;"));
+               }
+               else if (*c == '>') {
+                       client_write(HKEY("&gt;"));
+               }
+               else if (*c == '&') {
+                       client_write(HKEY("&amp;"));
+               }
+               else {
+                       client_write(c, 1);
+               }
+               ++c;
+       }
+}
+
+
+// Export a user record as XML
+void migr_export_users_backend(char *username, void *data) {
+
+       struct ctdluser u;
+       if (CtdlGetUser(&u, username) != 0) {
+               return;
+       }
+
+       client_write(HKEY("<user>\n"));
+       cprintf("<u_version>%d</u_version>\n", u.version);
+       cprintf("<u_uid>%ld</u_uid>\n", (long)u.uid);
+       client_write(HKEY("<u_password>"));     xml_strout(u.password);         client_write(HKEY("</u_password>\n"));
+       cprintf("<u_flags>%u</u_flags>\n", u.flags);
+       cprintf("<u_timescalled>%ld</u_timescalled>\n", u.timescalled);
+       cprintf("<u_posted>%ld</u_posted>\n", u.posted);
+       cprintf("<u_axlevel>%d</u_axlevel>\n", u.axlevel);
+       cprintf("<u_usernum>%ld</u_usernum>\n", u.usernum);
+       cprintf("<u_lastcall>%ld</u_lastcall>\n", (long)u.lastcall);
+       cprintf("<u_USuserpurge>%d</u_USuserpurge>\n", u.USuserpurge);
+       client_write(HKEY("<u_fullname>"));     xml_strout(u.fullname);         client_write(HKEY("</u_fullname>\n"));
+       cprintf("<u_msgnum_bio>%ld</u_msgnum_bio>\n", u.msgnum_bio);
+       cprintf("<u_msgnum_pic>%ld</u_msgnum_pic>\n", u.msgnum_pic);
+       cprintf("<u_emailaddrs>%s</u_emailaddrs>\n", u.emailaddrs);
+       cprintf("<u_msgnum_inboxrules>%ld</u_msgnum_inboxrules>\n", u.msgnum_inboxrules);
+       cprintf("<u_lastproc_inboxrules>%ld</u_lastproc_inboxrules>\n", u.lastproc_inboxrules);
+       client_write(HKEY("</user>\n"));
+}
+
+
+void migr_export_users(void) {
+       ForEachUser(migr_export_users_backend, NULL);
+}
+
+
+void migr_export_room_msg(long msgnum, void *userdata) {
+       static int count = 0;
+
+       cprintf("%ld,", msgnum);
+       if (++count%10==0) {
+               cprintf("\n");
+       }
+       fprintf(migr_global_message_list, "%ld\n", msgnum);
+}
+
+
+void migr_export_rooms_backend(struct ctdlroom *buf, void *data) {
+       client_write(HKEY("<room>\n"));
+       client_write(HKEY("<QRname>"));
+       xml_strout(buf->QRname);
+       client_write(HKEY("</QRname>\n"));
+       client_write(HKEY("<QRpasswd>"));
+       xml_strout(buf->QRpasswd);
+       client_write(HKEY("</QRpasswd>\n"));
+       cprintf("<QRroomaide>%ld</QRroomaide>\n", buf->QRroomaide);
+       cprintf("<QRhighest>%ld</QRhighest>\n", buf->QRhighest);
+       cprintf("<QRgen>%ld</QRgen>\n", (long)buf->QRgen);
+       cprintf("<QRflags>%u</QRflags>\n", buf->QRflags);
+       if (buf->QRflags & QR_DIRECTORY) {
+               client_write(HKEY("<QRdirname>"));
+               xml_strout(buf->QRdirname);
+               client_write(HKEY("</QRdirname>\n"));
+       }
+       cprintf("<QRfloor>%d</QRfloor>\n", buf->QRfloor);
+       cprintf("<QRmtime>%ld</QRmtime>\n", (long)buf->QRmtime);
+       cprintf("<QRexpire_mode>%d</QRexpire_mode>\n", buf->QRep.expire_mode);
+       cprintf("<QRexpire_value>%d</QRexpire_value>\n", buf->QRep.expire_value);
+       cprintf("<QRnumber>%ld</QRnumber>\n", buf->QRnumber);
+       cprintf("<QRorder>%d</QRorder>\n", buf->QRorder);
+       cprintf("<QRflags2>%u</QRflags2>\n", buf->QRflags2);
+       cprintf("<QRdefaultview>%d</QRdefaultview>\n", buf->QRdefaultview);
+       cprintf("<msgnum_info>%ld</msgnum_info>\n", buf->msgnum_info);
+       cprintf("<msgnum_pic>%ld</msgnum_pic>\n", buf->msgnum_pic);
+       client_write(HKEY("</room>\n"));
+
+       // message list goes inside this tag
+       CtdlGetRoom(&CC->room, buf->QRname);
+       client_write(HKEY("<room_messages>"));
+       client_write(HKEY("<FRname>"));
+       xml_strout(buf->QRname);                        // buf->QRname rather than CC->room.QRname to guarantee consistency
+       client_write(HKEY("</FRname>\n"));
+       client_write(HKEY("<FRmsglist>\n"));
+       CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, migr_export_room_msg, NULL);
+       client_write(HKEY("</FRmsglist>\n"));
+       client_write(HKEY("</room_messages>\n"));
+}
+
+
+void migr_export_rooms(void) {
+       char cmd[SIZ];
+       migr_global_message_list = fopen(migr_tempfilename1, "w");
+       if (migr_global_message_list != NULL) {
+               CtdlForEachRoom(migr_export_rooms_backend, NULL);
+               fclose(migr_global_message_list);
+       }
+
+       // Process the 'global' message list.  (Sort it and remove dups.
+       // Dups are ok because a message may be in more than one room, but
+       // this will be handled by exporting the reference count, not by
+       // exporting the message multiple times.)
+       snprintf(cmd, sizeof cmd, "sort -n <%s >%s", migr_tempfilename1, migr_tempfilename2);
+       if (system(cmd) != 0) {
+               syslog(LOG_ERR, "migrate: error %d", errno);
+       }
+       snprintf(cmd, sizeof cmd, "uniq <%s >%s", migr_tempfilename2, migr_tempfilename1);
+       if (system(cmd) != 0) {
+               syslog(LOG_ERR, "migrate: error %d", errno);
+       }
+
+       snprintf(cmd, sizeof cmd, "wc -l %s", migr_tempfilename1);
+       FILE *fp = popen(cmd, "r");
+       if (fp) {
+               fgets(cmd, sizeof cmd, fp);
+               pclose(fp);
+               total_msgs = atoi(cmd);
+       }
+       else {
+               total_msgs = 1; // any nonzero just to keep it from barfing
+       }
+       syslog(LOG_DEBUG, "migrate: total messages to be exported: %d", total_msgs);
+}
+
+
+void migr_export_floors(void) {
+        struct floor qfbuf, *buf;
+        int i;
+
+        for (i=0; i < MAXFLOORS; ++i) {
+               client_write(HKEY("<floor>\n"));
+               cprintf("<f_num>%d</f_num>\n", i);
+                CtdlGetFloor(&qfbuf, i);
+               buf = &qfbuf;
+               cprintf("<f_flags>%u</f_flags>\n", buf->f_flags);
+               client_write(HKEY("<f_name>")); xml_strout(buf->f_name); client_write(HKEY("</f_name>\n"));
+               cprintf("<f_ref_count>%d</f_ref_count>\n", buf->f_ref_count);
+               cprintf("<f_ep_expire_mode>%d</f_ep_expire_mode>\n", buf->f_ep.expire_mode);
+               cprintf("<f_ep_expire_value>%d</f_ep_expire_value>\n", buf->f_ep.expire_value);
+               client_write(HKEY("</floor>\n"));
+       }
+}
+
+
+// Return nonzero if the supplied string contains only characters which are valid in a sequence set.
+int is_sequence_set(char *s) {
+       if (!s) return(0);
+
+       char *c = s;
+       char ch;
+       while (ch = *c++, ch) {
+               if (!strchr("0123456789*,:", ch)) {
+                       return(0);
+               }
+       }
+       return(1);
+}
+
+
+// Traverse the visits file...
+void migr_export_visits(void) {
+       visit vbuf;
+       struct cdbdata *cdbv;
+
+       cdb_rewind(CDB_VISIT);
+
+       while (cdbv = cdb_next_item(CDB_VISIT), cdbv != NULL) {
+               memset(&vbuf, 0, sizeof(visit));
+               memcpy(&vbuf, cdbv->ptr,
+                      ((cdbv->len > sizeof(visit)) ?
+                       sizeof(visit) : cdbv->len));
+               cdb_free(cdbv);
+
+               client_write(HKEY("<visit>\n"));
+               cprintf("<v_roomnum>%ld</v_roomnum>\n", vbuf.v_roomnum);
+               cprintf("<v_roomgen>%ld</v_roomgen>\n", vbuf.v_roomgen);
+               cprintf("<v_usernum>%ld</v_usernum>\n", vbuf.v_usernum);
+
+               client_write(HKEY("<v_seen>"));
+               if ( (!IsEmptyStr(vbuf.v_seen)) && (is_sequence_set(vbuf.v_seen)) ) {
+                       xml_strout(vbuf.v_seen);
+               }
+               else {
+                       cprintf("%ld", vbuf.v_lastseen);
+               }
+               client_write(HKEY("</v_seen>"));
+
+               if ( (!IsEmptyStr(vbuf.v_answered)) && (is_sequence_set(vbuf.v_answered)) ) {
+                       client_write(HKEY("<v_answered>"));
+                       xml_strout(vbuf.v_answered);
+                       client_write(HKEY("</v_answered>\n"));
+               }
+
+               cprintf("<v_flags>%u</v_flags>\n", vbuf.v_flags);
+               cprintf("<v_view>%d</v_view>\n", vbuf.v_view);
+               client_write(HKEY("</visit>\n"));
+       }
+}
+
+
+void migr_export_message(long msgnum) {
+       struct MetaData smi;
+       struct CtdlMessage *msg;
+       struct ser_ret smr;
+       long bytes_written = 0;
+       long this_block = 0;
+
+       // We can use a static buffer here because there will never be more than
+       // one of this operation happening at any given time, and it's really best
+       // to just keep it allocated once instead of torturing malloc/free.
+       // Call this function with msgnum "-1" to free the buffer when finished.
+
+       static int encoded_alloc = 0;
+       static char *encoded_msg = NULL;
+
+       if (msgnum < 0) {
+               if ((encoded_alloc == 0) && (encoded_msg != NULL)) {
+                       free(encoded_msg);
+                       encoded_alloc = 0;
+                       encoded_msg = NULL;
+               }
+               return;
+       }
+
+       // Ok, here we go ...
+
+       msg = CtdlFetchMessage(msgnum, 1);
+       if (msg == NULL) return;                                // fail silently
+
+       client_write(HKEY("<message>\n"));
+       GetMetaData(&smi, msgnum);
+       cprintf("<msg_msgnum>%ld</msg_msgnum>\n", msgnum);
+       cprintf("<msg_meta_refcount>%d</msg_meta_refcount>\n", smi.meta_refcount);
+       cprintf("<msg_meta_rfc822_length>%ld</msg_meta_rfc822_length>\n", smi.meta_rfc822_length);
+       client_write(HKEY("<msg_meta_content_type>")); xml_strout(smi.meta_content_type); client_write(HKEY("</msg_meta_content_type>\n"));
+
+       client_write(HKEY("<msg_text>"));
+       CtdlSerializeMessage(&smr, msg);
+       CM_Free(msg);
+
+       // Predict the buffer size we need.  Expand the buffer if necessary.
+       int encoded_len = smr.len * 15 / 10 ;
+       if (encoded_len > encoded_alloc) {
+               encoded_alloc = encoded_len;
+               encoded_msg = realloc(encoded_msg, encoded_alloc);
+       }
+
+       if (encoded_msg == NULL) {
+               // Questionable hack that hopes it'll work next time and we only lose one message
+               encoded_alloc = 0;
+       }
+       else {
+               // Once we do the encoding we know the exact size
+               encoded_len = CtdlEncodeBase64(encoded_msg, (char *)smr.ser, smr.len, 1);
+
+               // Careful now.  If the message is gargantuan, trying to write multiple gigamegs in one
+               // big write operation can make our transport unhappy.  So we'll chunk it up 10 KB at a time.
+               bytes_written = 0;
+               while ( (bytes_written < encoded_len) && (!server_shutting_down) ) {
+                       this_block = encoded_len - bytes_written;
+                       if (this_block > 10240) {
+                               this_block = 10240;
+                       }
+                       client_write(&encoded_msg[bytes_written], this_block);
+                       bytes_written += this_block;
+               }
+       }
+
+       free(smr.ser);
+
+       client_write(HKEY("</msg_text>\n"));
+       client_write(HKEY("</message>\n"));
+}
+
+
+void migr_export_openids(void) {
+       struct cdbdata *cdboi;
+       long usernum;
+       char url[512];
+
+       cdb_rewind(CDB_EXTAUTH);
+       while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
+               if (cdboi->len > sizeof(long)) {
+                       client_write(HKEY("<openid>\n"));
+                       memcpy(&usernum, cdboi->ptr, sizeof(long));
+                       snprintf(url, sizeof url, "%s", (cdboi->ptr)+sizeof(long) );
+                       client_write(HKEY("<oid_url>"));
+                       xml_strout(url);
+                       client_write(HKEY("</oid_url>\n"));
+                       cprintf("<oid_usernum>%ld</oid_usernum>\n", usernum);
+                       client_write(HKEY("</openid>\n"));
+               }
+               cdb_free(cdboi);
+       }
+}
+
+
+void migr_export_configs(void) {
+       struct cdbdata *cdbcfg;
+       int keylen = 0;
+       char *key = NULL;
+       char *value = NULL;
+
+       cdb_rewind(CDB_CONFIG);
+       while (cdbcfg = cdb_next_item(CDB_CONFIG), cdbcfg != NULL) {
+
+               keylen = strlen(cdbcfg->ptr);
+               key = cdbcfg->ptr;
+               value = cdbcfg->ptr + keylen + 1;
+
+               client_write("<config key=\"", 13);
+               xml_strout(key);
+               client_write("\">", 2);
+               xml_strout(value);
+               client_write("</config>\n", 10);
+               cdb_free(cdbcfg);
+       }
+}
+
+
+void migr_export_messages(void) {
+       char buf[SIZ];
+       long msgnum;
+       int count = 0;
+       int progress = 0;
+       int prev_progress = 0;
+       CitContext *Ctx;
+
+       Ctx = CC;
+       migr_global_message_list = fopen(migr_tempfilename1, "r");
+       if (migr_global_message_list != NULL) {
+               syslog(LOG_INFO, "migrate: opened %s", migr_tempfilename1);
+               while ((Ctx->kill_me == 0) && 
+                      (fgets(buf, sizeof(buf), migr_global_message_list) != NULL)) {
+                       msgnum = atol(buf);
+                       if (msgnum > 0L) {
+                               migr_export_message(msgnum);
+                               ++count;
+                       }
+                       progress = (count * 74 / total_msgs) + 25 ;
+                       if ((progress > prev_progress) && (progress < 100)) {
+                               cprintf("<progress>%d</progress>\n", progress);
+                       }
+                       prev_progress = progress;
+               }
+               fclose(migr_global_message_list);
+       }
+       if (Ctx->kill_me == 0) {
+               syslog(LOG_INFO, "migrate: exported %d messages.", count);
+       }
+       else {
+               syslog(LOG_ERR, "migrate: export aborted due to client disconnect!");
+       }
+
+       migr_export_message(-1L);       // This frees the encoding buffer
+}
+
+
+void migr_do_export(void) {
+       CitContext *Ctx;
+
+       Ctx = CC;
+       cprintf("%d Exporting all Citadel databases.\n", LISTING_FOLLOWS);
+       Ctx->dont_term = 1;
+
+       client_write(HKEY("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"));
+       client_write(HKEY("<citadel_migrate_data>\n"));
+       cprintf("<version>%d</version>\n", REV_LEVEL);
+       cprintf("<progress>%d</progress>\n", 0);
+
+       // export the configuration database
+       migr_export_configs();
+       cprintf("<progress>%d</progress>\n", 2);
+       
+       if (Ctx->kill_me == 0)  migr_export_users();
+       cprintf("<progress>%d</progress>\n", 7);
+       if (Ctx->kill_me == 0)  migr_export_openids();
+       cprintf("<progress>%d</progress>\n", 12);
+       if (Ctx->kill_me == 0)  migr_export_rooms();
+       cprintf("<progress>%d</progress>\n", 17);
+       if (Ctx->kill_me == 0)  migr_export_floors();
+       cprintf("<progress>%d</progress>\n", 18);
+       if (Ctx->kill_me == 0)  migr_export_visits();
+       cprintf("<progress>%d</progress>\n", 25);
+       if (Ctx->kill_me == 0)  migr_export_messages();
+       cprintf("<progress>%d</progress>\n", 100);
+       client_write(HKEY("</citadel_migrate_data>\n"));
+       client_write(HKEY("000\n"));
+       Ctx->dont_term = 0;
+}
+
+
+//                              Import code
+//   Here's the code that implements the import side.  It's going to end up
+//       being one big loop with lots of global variables.  I don't care.
+// You wouldn't run multiple concurrent imports anyway.  If this offends your
+//  delicate sensibilities  then go rewrite it in Ruby on Rails or something.
+
+
+int citadel_migrate_data = 0;          // Are we inside a <citadel_migrate_data> tag pair?
+StrBuf *migr_chardata = NULL;
+StrBuf *migr_MsgData = NULL;
+struct ctdluser usbuf;
+struct ctdlroom qrbuf;
+char openid_url[512];
+long openid_usernum = 0;
+char FRname[ROOMNAMELEN];
+struct floor flbuf;
+int floornum = 0;
+visit vbuf;
+struct MetaData smi;
+long import_msgnum = 0;
+
+// This callback stores up the data which appears in between tags.
+void migr_xml_chardata(void *data, const XML_Char *s, int len) {
+       StrBufAppendBufPlain(migr_chardata, s, len, 0);
+}
+
+
+void migr_xml_start(void *data, const char *el, const char **attr) {
+       int i;
+
+       //  *** GENERAL STUFF ***
+
+       // Reset chardata_len to zero and init buffer
+       FlushStrBuf(migr_chardata);
+       FlushStrBuf(migr_MsgData);
+
+       if (!strcasecmp(el, "citadel_migrate_data")) {
+               ++citadel_migrate_data;
+
+               // As soon as it looks like the input data is a genuine Citadel XML export,
+               // whack the existing database on disk to make room for the new one.
+               if (citadel_migrate_data == 1) {
+                       syslog(LOG_INFO, "migrate: erasing existing databases so we can receive the incoming import");
+                       for (i = 0; i < MAXCDB; ++i) {
+                               cdb_trunc(i);
+                       }
+               }
+               return;
+       }
+
+       if (citadel_migrate_data != 1) {
+               syslog(LOG_ERR, "migrate: out-of-sequence tag <%s> detected.  Warning: ODD-DATA!", el);
+               return;
+       }
+
+       // When we begin receiving XML for one of these record types, clear out the associated
+       // buffer so we don't accidentally carry over any data from a previous record.
+       if (!strcasecmp(el, "user"))                    memset(&usbuf, 0, sizeof (struct ctdluser));
+       else if (!strcasecmp(el, "openid"))             memset(openid_url, 0, sizeof openid_url);
+       else if (!strcasecmp(el, "room"))               memset(&qrbuf, 0, sizeof (struct ctdlroom));
+       else if (!strcasecmp(el, "room_messages"))      memset(FRname, 0, sizeof FRname);
+       else if (!strcasecmp(el, "floor"))              memset(&flbuf, 0, sizeof (struct floor));
+       else if (!strcasecmp(el, "visit"))              memset(&vbuf, 0, sizeof (visit));
+
+       else if (!strcasecmp(el, "message")) {
+               memset(&smi, 0, sizeof (struct MetaData));
+               import_msgnum = 0;
+       }
+       else if (!strcasecmp(el, "config")) {
+               if (ikey != NULL) {
+                       free(ikey);
+                       ikey = NULL;
+               }
+               while (*attr) {
+                       if (!strcasecmp(attr[0], "key")) {
+                               ikey = strdup(attr[1]);
+                       }
+                       attr += 2;
+               }
+       }
+
+}
+
+
+int migr_userrecord(void *data, const char *el) {
+       if (!strcasecmp(el, "u_version"))                       usbuf.version = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "u_uid"))                      usbuf.uid = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "u_password"))                 safestrncpy(usbuf.password, ChrPtr(migr_chardata), sizeof usbuf.password);
+       else if (!strcasecmp(el, "u_flags"))                    usbuf.flags = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "u_timescalled"))              usbuf.timescalled = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "u_posted"))                   usbuf.posted = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "u_axlevel"))                  usbuf.axlevel = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "u_usernum"))                  usbuf.usernum = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "u_lastcall"))                 usbuf.lastcall = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "u_USuserpurge"))              usbuf.USuserpurge = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "u_fullname"))                 safestrncpy(usbuf.fullname, ChrPtr(migr_chardata), sizeof usbuf.fullname);
+       else if (!strcasecmp(el, "u_msgnum_bio"))               usbuf.msgnum_bio = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "u_msgnum_pic"))               usbuf.msgnum_pic = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "u_emailaddrs"))               safestrncpy(usbuf.emailaddrs, ChrPtr(migr_chardata), sizeof usbuf.emailaddrs);
+       else if (!strcasecmp(el, "u_msgnum_inboxrules"))        usbuf.msgnum_inboxrules = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "u_lastproc_inboxrules"))      usbuf.lastproc_inboxrules = atol(ChrPtr(migr_chardata));
+       else return 0;
+       return 1;
+}
+
+
+int migr_roomrecord(void *data, const char *el) {
+       if (!strcasecmp(el, "QRname"))                          safestrncpy(qrbuf.QRname, ChrPtr(migr_chardata), sizeof qrbuf.QRname);
+       else if (!strcasecmp(el, "QRpasswd"))                   safestrncpy(qrbuf.QRpasswd, ChrPtr(migr_chardata), sizeof qrbuf.QRpasswd);
+       else if (!strcasecmp(el, "QRroomaide"))                 qrbuf.QRroomaide = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "QRhighest"))                  qrbuf.QRhighest = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "QRgen"))                      qrbuf.QRgen = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "QRflags"))                    qrbuf.QRflags = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "QRdirname"))                  safestrncpy(qrbuf.QRdirname, ChrPtr(migr_chardata), sizeof qrbuf.QRdirname);
+       else if (!strcasecmp(el, "QRfloor"))                    qrbuf.QRfloor = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "QRmtime"))                    qrbuf.QRmtime = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "QRexpire_mode"))              qrbuf.QRep.expire_mode = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "QRexpire_value"))             qrbuf.QRep.expire_value = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "QRnumber"))                   qrbuf.QRnumber = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "QRorder"))                    qrbuf.QRorder = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "QRflags2"))                   qrbuf.QRflags2 = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "QRdefaultview"))              qrbuf.QRdefaultview = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "msgnum_info"))                qrbuf.msgnum_info = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "msgnum_pic"))                 qrbuf.msgnum_pic = atol(ChrPtr(migr_chardata));
+       else return 0;
+       return 1;
+}
+
+
+int migr_floorrecord(void *data, const char *el) {
+       if (!strcasecmp(el, "f_num"))                           floornum = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "f_flags"))                    flbuf.f_flags = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "f_name"))                     safestrncpy(flbuf.f_name, ChrPtr(migr_chardata), sizeof flbuf.f_name);
+       else if (!strcasecmp(el, "f_ref_count"))                flbuf.f_ref_count = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "f_ep_expire_mode"))           flbuf.f_ep.expire_mode = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "f_ep_expire_value"))          flbuf.f_ep.expire_value = atoi(ChrPtr(migr_chardata));
+       else return 0;
+       return 1;
+}
+
+
+int migr_visitrecord(void *data, const char *el) {
+       if (!strcasecmp(el, "v_roomnum"))                       vbuf.v_roomnum = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "v_roomgen"))                  vbuf.v_roomgen = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "v_usernum"))                  vbuf.v_usernum = atol(ChrPtr(migr_chardata));
+
+       else if (!strcasecmp(el, "v_seen")) {
+               int is_textual_seen = 0;
+               int i;
+               int max = StrLength(migr_chardata);
+
+               vbuf.v_lastseen = atol(ChrPtr(migr_chardata));
+               is_textual_seen = 0;
+               for (i=0; i < max; ++i) 
+                       if (!isdigit(ChrPtr(migr_chardata)[i]))
+                               is_textual_seen = 1;
+               if (is_textual_seen)
+                       safestrncpy(vbuf.v_seen, ChrPtr(migr_chardata), sizeof vbuf.v_seen);
+       }
+
+       else if (!strcasecmp(el, "v_answered"))                 safestrncpy(vbuf.v_answered, ChrPtr(migr_chardata), sizeof vbuf.v_answered);
+       else if (!strcasecmp(el, "v_flags"))                    vbuf.v_flags = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "v_view"))                     vbuf.v_view = atoi(ChrPtr(migr_chardata));
+       else return 0;
+       return 1;
+}
+
+
+void migr_xml_end(void *data, const char *el) {
+       const char *ptr;
+       int msgcount = 0;
+       long msgnum = 0L;
+       long *msglist = NULL;
+       int msglist_alloc = 0;
+       // *** GENERAL STUFF ***
+
+       if (!strcasecmp(el, "citadel_migrate_data")) {
+               --citadel_migrate_data;
+               return;
+       }
+
+       if (citadel_migrate_data != 1) {
+               syslog(LOG_ERR, "migrate: out-of-sequence tag <%s> detected.  Warning: ODD-DATA!", el);
+               return;
+       }
+
+       // syslog(LOG_DEBUG, "END TAG: <%s> DATA: <%s>\n", el, (migr_chardata_len ? migr_chardata : ""));
+
+       // *** CONFIG ***
+
+       if (!strcasecmp(el, "config")) {
+               syslog(LOG_DEBUG, "migrate: imported config key=%s", ikey);
+
+               if (ikey != NULL) {
+                       CtdlSetConfigStr(ikey, (char *)ChrPtr(migr_chardata));
+                       free(ikey);
+                       ikey = NULL;
+               }
+               else {
+                       syslog(LOG_INFO, "migrate: closed a <config> tag but no key name was supplied.");
+               }
+       }
+
+       // *** USER ***
+       else if ((!strncasecmp(el, HKEY("u_"))) && 
+                migr_userrecord(data, el))
+               ; /* Nothing to do anymore */
+       else if (!strcasecmp(el, "user")) {
+               CtdlPutUser(&usbuf);
+               syslog(LOG_INFO, "migrate: imported user: %s", usbuf.fullname);
+       }
+
+       // *** OPENID ***
+
+       else if (!strcasecmp(el, "oid_url"))                    safestrncpy(openid_url, ChrPtr(migr_chardata), sizeof openid_url);
+       else if (!strcasecmp(el, "oid_usernum"))                openid_usernum = atol(ChrPtr(migr_chardata));
+
+       else if (!strcasecmp(el, "openid")) {                   // see serv_openid_rp.c for a description of the record format
+               char *oid_data;
+               int oid_data_len;
+               oid_data_len = sizeof(long) + strlen(openid_url) + 1;
+               oid_data = malloc(oid_data_len);
+               memcpy(oid_data, &openid_usernum, sizeof(long));
+               memcpy(&oid_data[sizeof(long)], openid_url, strlen(openid_url) + 1);
+               cdb_store(CDB_EXTAUTH, openid_url, strlen(openid_url), oid_data, oid_data_len);
+               free(oid_data);
+               syslog(LOG_INFO, "migrate: imported OpenID: %s (%ld)", openid_url, openid_usernum);
+       }
+
+       // *** ROOM ***
+       else if ((!strncasecmp(el, HKEY("QR"))) && 
+                migr_roomrecord(data, el))
+               ; // Nothing to do anymore
+       else if (!strcasecmp(el, "room")) {
+               CtdlPutRoom(&qrbuf);
+               syslog(LOG_INFO, "migrate: imported room: %s", qrbuf.QRname);
+       }
+
+       // *** ROOM MESSAGE POINTERS ***
+
+       else if (!strcasecmp(el, "FRname")) {
+               safestrncpy(FRname, ChrPtr(migr_chardata), sizeof FRname);
+       }
+
+       else if (!strcasecmp(el, "FRmsglist")) {
+               if (!IsEmptyStr(FRname)) {
+                       msgcount = 0;
+                       msglist_alloc = 1000;
+                       msglist = malloc(sizeof(long) * msglist_alloc);
+
+                       syslog(LOG_DEBUG, "migrate: message list for: %s", FRname);
+
+                       ptr = ChrPtr(migr_chardata);
+                       while (*ptr != 0) {
+                               while ((*ptr != 0) && (!isdigit(*ptr))) {
+                                       ++ptr;
+                               }
+                               if ((*ptr != 0) && (isdigit(*ptr))) {
+                                       msgnum = atol(ptr);
+                                       if (msgnum > 0L) {
+                                               if (msgcount >= msglist_alloc) {
+                                                       msglist_alloc *= 2;
+                                                       msglist = realloc(msglist, sizeof(long) * msglist_alloc);
+                                               }
+                                               msglist[msgcount++] = msgnum;
+                                               }
+                                       }
+                                       while ((*ptr != 0) && (isdigit(*ptr))) {
+                                               ++ptr;
+                                       }
+                               }
+                       }
+                       if (msgcount > 0) {
+                               CtdlSaveMsgPointersInRoom(FRname, msglist, msgcount, 0, NULL, 1);
+                       }
+                       free(msglist);
+                       msglist = NULL;
+                       msglist_alloc = 0;
+                       syslog(LOG_DEBUG, "migrate: imported %d messages.", msgcount);
+                       if (server_shutting_down) {
+                               return;
+               }
+       }
+
+       // *** FLOORS ***
+       else if ((!strncasecmp(el, HKEY("f_"))) && 
+                migr_floorrecord(data, el))
+               ; // Nothing to do anymore
+
+       else if (!strcasecmp(el, "floor")) {
+               CtdlPutFloor(&flbuf, floornum);
+               syslog(LOG_INFO, "migrate: imported floor #%d (%s)", floornum, flbuf.f_name);
+       }
+
+       // *** VISITS ***
+       else if ((!strncasecmp(el, HKEY("v_"))) && migr_visitrecord(data, el)) {
+               ; // Nothing to do anymore
+       }
+       else if (!strcasecmp(el, "visit")) {
+               put_visit(&vbuf);
+               syslog(LOG_INFO, "migrate: imported visit: %ld/%ld/%ld", vbuf.v_roomnum, vbuf.v_roomgen, vbuf.v_usernum);
+       }
+
+       // *** MESSAGES ***
+       
+       else if (!strcasecmp(el, "msg_msgnum"))                 smi.meta_msgnum = import_msgnum = atol(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "msg_meta_refcount"))          smi.meta_refcount = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "msg_meta_rfc822_length"))     smi.meta_rfc822_length = atoi(ChrPtr(migr_chardata));
+       else if (!strcasecmp(el, "msg_meta_content_type"))      safestrncpy(smi.meta_content_type, ChrPtr(migr_chardata), sizeof smi.meta_content_type);
+
+       else if (!strcasecmp(el, "msg_text")) {
+               long rc;
+               struct CtdlMessage *msg;
+
+               FlushStrBuf(migr_MsgData);
+               StrBufDecodeBase64To(migr_chardata, migr_MsgData);
+
+               msg = CtdlDeserializeMessage(import_msgnum, -1,
+                                            ChrPtr(migr_MsgData), 
+                                            StrLength(migr_MsgData));
+               if (msg != NULL) {
+                       rc = CtdlSaveThisMessage(msg, import_msgnum, 0);
+                       if (rc == 0) {
+                               PutMetaData(&smi);
+                       }
+                       CM_Free(msg);
+               }
+               else {
+                       rc = -1;
+               }
+
+               syslog(LOG_INFO,
+                      "migrate: %s message #%ld, size=%d, ref=%d, body=%ld, content-type: %s",
+                      (rc!= 0)?"failed to import":"imported",
+                      import_msgnum,
+                      StrLength(migr_MsgData),
+                      smi.meta_refcount,
+                      smi.meta_rfc822_length,
+                      smi.meta_content_type
+               );
+               memset(&smi, 0, sizeof(smi));
+       }
+
+       // *** MORE GENERAL STUFF ***
+
+       FlushStrBuf(migr_chardata);
+}
+
+
+// Import begins here
+void migr_do_import(void) {
+       XML_Parser xp;
+       char buf[SIZ];
+       
+       unbuffer_output();
+       migr_chardata = NewStrBufPlain(NULL, SIZ * 20);
+       migr_MsgData = NewStrBufPlain(NULL, SIZ * 20);
+       xp = XML_ParserCreate(NULL);
+       if (!xp) {
+               cprintf("%d Failed to create XML parser instance\n", ERROR+INTERNAL_ERROR);
+               return;
+       }
+       XML_SetElementHandler(xp, migr_xml_start, migr_xml_end);
+       XML_SetCharacterDataHandler(xp, migr_xml_chardata);
+
+       CC->dont_term = 1;
+
+       cprintf("%d sock it to me\n", SEND_LISTING);
+       unbuffer_output();
+
+       client_set_inbound_buf(SIZ * 10);
+
+       while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
+               XML_Parse(xp, buf, strlen(buf), 0);
+               if (server_shutting_down)
+                       break;  // Should we break or return?
+       }
+
+       XML_Parse(xp, "", 0, 1);
+       XML_ParserFree(xp);
+       FreeStrBuf(&migr_chardata);
+       FreeStrBuf(&migr_MsgData);
+       rebuild_euid_index();
+       rebuild_usersbynumber();
+       CtdlSetConfigInt("MM_fulltext_wordbreaker", -1);        // Set an invalid wordbreaker to force re-indexing
+       CC->dont_term = 0;
+}
+
+
+// ******************************************************************************
+// *                         Dispatcher, Common code                            *
+// ******************************************************************************
+
+// Dump out the pathnames of directories which can be copied "as is"
+void migr_do_listdirs(void) {
+       cprintf("%d Don't forget these:\n", LISTING_FOLLOWS);
+       cprintf("files|%s\n",           ctdl_file_dir);
+       cprintf("messages|%s\n",        ctdl_message_dir);
+       cprintf("keys|%s\n",            ctdl_key_dir);
+       cprintf("000\n");
+}
+
+
+// ******************************************************************************
+// *                    Repair database integrity                               *
+// ******************************************************************************
+
+StrBuf *PlainMessageBuf = NULL;
+HashList *UsedMessageIDS = NULL;
+
+int migr_restore_message_metadata(long msgnum, int refcount) {
+       struct MetaData smi;
+       struct CtdlMessage *msg;
+       char *mptr = NULL;
+
+       // We can use a static buffer here because there will never be more than
+       // one of this operation happening at any given time, and it's really best
+       // to just keep it allocated once instead of torturing malloc/free.
+       // Call this function with msgnum "-1" to free the buffer when finished.
+       static int encoded_alloc = 0;
+       static char *encoded_msg = NULL;
+
+       if (msgnum < 0) {
+               if ((encoded_alloc == 0) && (encoded_msg != NULL)) {
+                       free(encoded_msg);
+                       encoded_alloc = 0;
+                       encoded_msg = NULL;
+                       // todo FreeStrBuf(&PlainMessageBuf); PlainMessageBuf = NULL;
+               }
+               return 0;
+       }
+
+       if (PlainMessageBuf == NULL) {
+               PlainMessageBuf = NewStrBufPlain(NULL, 10*SIZ);
+       }
+
+       // Ok, here we go ...
+
+       msg = CtdlFetchMessage(msgnum, 1);
+       if (msg == NULL) {
+               return 1;
+       }
+
+       GetMetaData(&smi, msgnum);
+       smi.meta_msgnum = msgnum;
+       smi.meta_refcount = refcount;
+       
+       // restore the content type from the message body:
+       mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
+       if (mptr != NULL) {
+               char *aptr;
+               safestrncpy(smi.meta_content_type, &mptr[13], sizeof smi.meta_content_type);
+               striplt(smi.meta_content_type);
+               aptr = smi.meta_content_type;
+               while (!IsEmptyStr(aptr)) {
+                       if ((*aptr == ';')
+                           || (*aptr == ' ')
+                           || (*aptr == 13)
+                           || (*aptr == 10)) {
+                               memset(aptr, 0, sizeof(smi.meta_content_type) - (aptr - smi.meta_content_type));
+                       }
+                       else aptr++;
+               }
+       }
+
+       CC->redirect_buffer = PlainMessageBuf;
+       CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
+       smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
+       CC->redirect_buffer = NULL;
+
+       syslog(LOG_INFO,
+              "migrate: setting message #%ld meta data to: refcount=%d, bodylength=%ld, content-type: %s",
+              smi.meta_msgnum,
+              smi.meta_refcount,
+              smi.meta_rfc822_length,
+              smi.meta_content_type
+       );
+
+       PutMetaData(&smi);
+       CM_Free(msg);
+       return 0;
+}
+
+
+void migr_check_room_msg(long msgnum, void *userdata) {
+       fprintf(migr_global_message_list, "%ld %s\n", msgnum, CC->room.QRname);
+}
+
+
+void migr_check_rooms_backend(struct ctdlroom *buf, void *data) {
+       // message list goes inside this tag
+       CtdlGetRoom(&CC->room, buf->QRname);
+       CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, migr_check_room_msg, NULL);
+}
+
+
+void RemoveMessagesFromRooms(StrBuf *RoomNameVec, long msgnum) {
+       struct MetaData smi;
+       const char *Pos = NULL;
+       StrBuf *oneRoom = NewStrBuf();
+
+       syslog(LOG_INFO, "migrate: removing message pointer %ld from these rooms: %s", msgnum, ChrPtr(RoomNameVec));
+
+       while (Pos != StrBufNOTNULL){
+               StrBufExtract_NextToken(oneRoom, RoomNameVec, &Pos, '|');
+               CtdlDeleteMessages(ChrPtr(oneRoom), &msgnum, 1, "");
+       };
+       GetMetaData(&smi, msgnum);
+       AdjRefCount(msgnum, -smi.meta_refcount);
+}
+
+
+void migr_do_restore_meta(void) {
+       char buf[SIZ];
+       int failGetMessage;
+       long msgnum = 0;
+       int lastnum = 0;
+       int refcount = 0;
+       CitContext *Ctx;
+       char *prn;
+       StrBuf *RoomNames;
+       char cmd[SIZ];
+
+       migr_global_message_list = fopen(migr_tempfilename1, "w");
+       if (migr_global_message_list != NULL) {
+               CtdlForEachRoom(migr_check_rooms_backend, NULL);
+               fclose(migr_global_message_list);
+       }
+
+       // Process the 'global' message list.  (Sort it and remove dups.
+       // Dups are ok because a message may be in more than one room, but
+       // this will be handled by exporting the reference count, not by
+       // exporting the message multiple times.)
+       snprintf(cmd, sizeof cmd, "sort -n <%s >%s", migr_tempfilename1, migr_tempfilename2);
+       if (system(cmd) != 0) {
+               syslog(LOG_ERR, "migrate: error %d", errno);
+       }
+
+       RoomNames = NewStrBuf();
+       Ctx = CC;
+       migr_global_message_list = fopen(migr_tempfilename2, "r");
+       if (migr_global_message_list != NULL) {
+               syslog(LOG_INFO, "migrate: opened %s", migr_tempfilename1);
+               while ((Ctx->kill_me == 0) && 
+                      (fgets(buf, sizeof(buf), migr_global_message_list) != NULL)) {
+                       msgnum = atol(buf);
+                       if (msgnum == 0L) 
+                               continue;
+                       if (lastnum == 0) {
+                               lastnum = msgnum;
+                       }
+                       prn = strchr(buf, ' ');
+                       if (lastnum != msgnum) {
+                               failGetMessage = migr_restore_message_metadata(lastnum, refcount);
+                               if (failGetMessage) {
+                                       RemoveMessagesFromRooms(RoomNames, lastnum);
+                               }
+                               refcount = 1;
+                               lastnum = msgnum;
+                               if (prn != NULL) {
+                                       StrBufPlain(RoomNames, prn + 1, -1);
+                               }
+                               StrBufTrim(RoomNames);
+                       }
+                       else {
+                               if (prn != NULL) {
+                                       if (StrLength(RoomNames) > 0) {
+                                               StrBufAppendBufPlain(RoomNames, HKEY("|"), 0);
+                                       }
+                                       StrBufAppendBufPlain(RoomNames, prn, -1, 1);
+                                       StrBufTrim(RoomNames);
+                               }
+                               refcount ++;
+                       }
+                       lastnum = msgnum;
+               }
+               failGetMessage = migr_restore_message_metadata(msgnum, refcount);
+               if (failGetMessage) {
+                       RemoveMessagesFromRooms(RoomNames, lastnum);
+               }
+               fclose(migr_global_message_list);
+       }
+
+       migr_restore_message_metadata(-1L, -1); // This frees the encoding buffer
+       cprintf("%d system analysis completed", CIT_OK);
+       Ctx->kill_me = 1;
+}
+
+
+// ******************************************************************************
+// *                         Dispatcher, Common code                            *
+// ******************************************************************************
+void cmd_migr(char *cmdbuf) {
+       char cmd[32];
+       
+       if (CtdlAccessCheck(ac_aide)) return;
+       
+       if (CtdlTrySingleUser()) {
+               CtdlDisableHouseKeeping();
+               CtdlMakeTempFileName(migr_tempfilename1, sizeof migr_tempfilename1);
+               CtdlMakeTempFileName(migr_tempfilename2, sizeof migr_tempfilename2);
+
+               extract_token(cmd, cmdbuf, 0, '|', sizeof cmd);
+               if (!strcasecmp(cmd, "export")) {
+                       migr_do_export();
+               }
+               else if (!strcasecmp(cmd, "import")) {
+                       migr_do_import();
+               }
+               else if (!strcasecmp(cmd, "listdirs")) {
+                       migr_do_listdirs();
+               }
+               else if (!strcasecmp(cmd, "restoremeta")) {
+                       migr_do_restore_meta();
+               }
+               else {
+                       cprintf("%d illegal command\n", ERROR + ILLEGAL_VALUE);
+               }
+
+               unlink(migr_tempfilename1);
+               unlink(migr_tempfilename2);
+
+               CtdlEnableHouseKeeping();
+               CtdlEndSingleUser();
+       }
+       else {
+               cprintf("%d The migrator is already running.\n", ERROR + RESOURCE_BUSY);
+       }
+}
+
+
+// ******************************************************************************
+// *                              Module Hook                                   *
+// ******************************************************************************
+
+char *ctdl_module_init_migrate() {
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_migr, "MIGR", "Across-the-wire migration");
+       }
+       
+       // return our module name for the log
+       return "migrate";
+}
diff --git a/citadel/server/modules/newuser/.gitignore b/citadel/server/modules/newuser/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/newuser/serv_newuser.c b/citadel/server/modules/newuser/serv_newuser.c
new file mode 100644 (file)
index 0000000..751ecb3
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Automatically copies the contents of a "New User Greetings" room to the
+ * inbox of any new user upon account creation.
+ *
+ * Copyright (c) 1987-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Name of the New User Greetings room.
+ */
+#define NEWUSERGREETINGS       "New User Greetings"
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "../../ctdl_module.h"
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+
+/*
+ * Copy the contents of the New User Greetings> room to the user's Mail> room.
+ */
+void CopyNewUserGreetings(void) {
+       struct cdbdata *cdbfr;
+       long *msglist = NULL;
+       int num_msgs = 0;
+       char mailboxname[ROOMNAMELEN];
+
+
+       /* Only do this for new users. */
+       if (CC->user.timescalled != 1) return;
+
+       /* This user's mailbox. */
+       CtdlMailboxName(mailboxname, sizeof mailboxname, &CC->user, MAILROOM);
+
+       /* Go to the source room ... bail out silently if it's not there,
+        * or if it's not private.
+        */
+       if (CtdlGetRoom(&CC->room, NEWUSERGREETINGS) != 0) return;
+       if ((CC->room.QRflags & QR_PRIVATE) == 0) return;
+
+       cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+
+       if (cdbfr != NULL) {
+               msglist = malloc(cdbfr->len);
+               memcpy(msglist, cdbfr->ptr, cdbfr->len);
+               num_msgs = cdbfr->len / sizeof(long);
+               cdb_free(cdbfr);
+       }
+
+       if (num_msgs > 0) {
+               CtdlSaveMsgPointersInRoom(mailboxname, msglist, num_msgs, 1, NULL, 0);
+       }
+
+       /* Now free the memory we used, and go away. */
+       if (msglist != NULL) free(msglist);
+}
+
+
+char *ctdl_module_init_newuser(void) {
+       if (!threading) {
+               CtdlRegisterSessionHook(CopyNewUserGreetings, EVT_LOGIN, PRIO_LOGIN + 1);
+       }
+       
+       /* return our module name for the log */
+       return "newuser";
+}
diff --git a/citadel/server/modules/nntp/serv_nntp.c b/citadel/server/modules/nntp/serv_nntp.c
new file mode 100644 (file)
index 0000000..5db995d
--- /dev/null
@@ -0,0 +1,1168 @@
+//
+// NNTP server module (RFC 3977)
+//
+// Copyright (c) 2014-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//  
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <syslog.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../room_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../genstamp.h"
+#include "../../domain.h"
+#include "../../clientsocket.h"
+#include "../../locate_host.h"
+#include "../../citadel_dirs.h"
+#include "../../ctdl_module.h"
+#include "serv_nntp.h"
+
+#ifndef __FreeBSD__
+extern long timezone;
+#endif
+
+//
+// Tests whether the supplied string is a valid newsgroup name
+// Returns true (nonzero) or false (0)
+//
+int is_valid_newsgroup_name(char *name) {
+       char *ptr = name;
+       int has_a_letter = 0;
+       int num_dots = 0;
+
+       if (!ptr) return(0);
+       if (!strncasecmp(name, "ctdl.", 5)) return(0);
+
+       while (*ptr != 0) {
+
+               if (isalpha(ptr[0])) {
+                       has_a_letter = 1;
+               }
+
+               if (ptr[0] == '.') {
+                       ++num_dots;
+               }
+
+               if (    (isalnum(ptr[0]))
+                       || (ptr[0] == '.')
+                       || (ptr[0] == '+')
+                       || (ptr[0] == '-')
+               ) {
+                       ++ptr;
+               }
+               else {
+                       return(0);
+               }
+       }
+       return( (has_a_letter) && (num_dots >= 1) ) ;
+}
+
+
+//
+// Convert a Citadel room name to a valid newsgroup name
+//
+void room_to_newsgroup(char *target, char *source, size_t target_size) {
+
+       if (!target) return;
+       if (!source) return;
+
+       if (is_valid_newsgroup_name(source)) {
+               strncpy(target, source, target_size);
+               return;
+       }
+
+       strcpy(target, "ctdl.");
+       int len = 5;
+       char *ptr = source;
+       char ch;
+
+       while (ch=*ptr++, ch!=0) {
+               if (len >= target_size) return;
+               if (    (isalnum(ch))
+                       || (ch == '.')
+                       || (ch == '-')
+               ) {
+                       target[len++] = tolower(ch);
+                       target[len] = 0;
+               }
+               else {
+                       target[len++] = '+' ;
+                       sprintf(&target[len], "%02x", ch);
+                       len += 2;
+                       target[len] = 0;
+               }
+       }
+}
+
+
+//
+// Convert a newsgroup name to a Citadel room name.
+// This function recognizes names converted with room_to_newsgroup() and restores them with full fidelity.
+//
+void newsgroup_to_room(char *target, char *source, size_t target_size) {
+
+       if (!target) return;
+       if (!source) return;
+
+       if (strncasecmp(source, "ctdl.", 5)) {                  // not a converted room name; pass through as-is
+               strncpy(target, source, target_size);
+               return;
+       }
+
+       target[0] = 0;
+       int len = 0;
+       char *ptr = &source[5];
+       char ch;
+
+       while (ch=*ptr++, ch!=0) {
+               if (len >= target_size) return;
+               if (ch == '+') {
+                       char hex[3];
+                       long digit;
+                       hex[0] = *ptr++;
+                       hex[1] = *ptr++;
+                       hex[2] = 0;
+                       digit = strtol(hex, NULL, 16);
+                       ch = (char)digit;
+               }
+               target[len++] = ch;
+               target[len] = 0;
+       }
+}
+
+
+//
+// Here's where our NNTP session begins its happy day.
+//
+void nntp_greeting(void) {
+       strcpy(CC->cs_clientname, "NNTP session");
+       CC->cs_flags |= CS_STEALTH;
+
+       CC->session_specific_data = malloc(sizeof(citnntp));
+       citnntp *nntpstate = (citnntp *) CC->session_specific_data;
+       memset(nntpstate, 0, sizeof(citnntp));
+
+       if (CC->nologin==1) {
+               cprintf("451 Too many connections are already open; please try again later.\r\n");
+               CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED;
+               return;
+       }
+
+       // Display the standard greeting
+       cprintf("200 %s NNTP Citadel server is not finished yet\r\n", CtdlGetConfigStr("c_fqdn"));
+}
+
+
+//
+// NNTPS is just like NNTP, except it goes crypto right away.
+//
+void nntps_greeting(void) {
+       CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
+#ifdef HAVE_OPENSSL
+       if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;          /* kill session if no crypto */
+#endif
+       nntp_greeting();
+}
+
+
+//
+// implements the STARTTLS command
+//
+void nntp_starttls(void) {
+       char ok_response[SIZ];
+       char nosup_response[SIZ];
+       char error_response[SIZ];
+
+       sprintf(ok_response, "382 Begin TLS negotiation now\r\n");
+       sprintf(nosup_response, "502 Can not initiate TLS negotiation\r\n");
+       sprintf(error_response, "580 Internal error\r\n");
+       CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
+}
+
+
+//
+// Implements the CAPABILITY command
+//
+void nntp_capabilities(void) {
+       cprintf("101 Capability list:\r\n");
+       cprintf("IMPLEMENTATION Citadel %d\r\n", REV_LEVEL);
+       cprintf("VERSION 2\r\n");
+       cprintf("READER\r\n");
+       cprintf("MODE-READER\r\n");
+       cprintf("LIST ACTIVE NEWSGROUPS\r\n");
+       cprintf("OVER\r\n");
+#ifdef HAVE_OPENSSL
+       cprintf("STARTTLS\r\n");
+#endif
+       if (!CC->logged_in) {
+               cprintf("AUTHINFO USER\r\n");
+       }
+       cprintf(".\r\n");
+}
+
+
+// 
+// Implements the QUIT command
+//
+void nntp_quit(void) {
+       cprintf("221 Goodbye...\r\n");
+       CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
+}
+
+
+//
+// Implements the AUTHINFO USER command (RFC 4643)
+//
+void nntp_authinfo_user(const char *username) {
+       int a = CtdlLoginExistingUser(username);
+       switch (a) {
+       case login_already_logged_in:
+               cprintf("482 Already logged in\r\n");
+               return;
+       case login_too_many_users:
+               cprintf("481 Too many users are already online (maximum is %d)\r\n", CtdlGetConfigInt("c_maxsessions"));
+               return;
+       case login_ok:
+               cprintf("381 Password required for %s\r\n", CC->curr_user);
+               return;
+       case login_not_found:
+               cprintf("481 %s not found\r\n", username);
+               return;
+       default:
+               cprintf("502 Internal error\r\n");
+       }
+}
+
+
+//
+// Implements the AUTHINFO PASS command (RFC 4643)
+//
+void nntp_authinfo_pass(const char *buf) {
+       int a;
+
+       a = CtdlTryPassword(buf, strlen(buf));
+
+       switch (a) {
+       case pass_already_logged_in:
+               cprintf("482 Already logged in\r\n");
+               return;
+       case pass_no_user:
+               cprintf("482 Authentication commands issued out of sequence\r\n");
+               return;
+       case pass_wrong_password:
+               cprintf("481 Authentication failed\r\n");
+               return;
+       case pass_ok:
+               cprintf("281 Authentication accepted\r\n");
+               return;
+       }
+}
+
+
+//
+// Implements the AUTHINFO extension (RFC 4643) in USER/PASS mode
+//
+void nntp_authinfo(const char *cmd) {
+
+       if (!strncasecmp(cmd, "authinfo user ", 14)) {
+               nntp_authinfo_user(&cmd[14]);
+       }
+
+       else if (!strncasecmp(cmd, "authinfo pass ", 14)) {
+               nntp_authinfo_pass(&cmd[14]);
+       }
+
+       else {
+               cprintf("502 command unavailable\r\n");
+       }
+}
+
+
+//
+// Utility function to fetch the current list of message numbers in a room
+//
+struct nntp_msglist nntp_fetch_msglist(struct ctdlroom *qrbuf) {
+       struct nntp_msglist nm;
+       struct cdbdata *cdbfr;
+
+       cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
+       if (cdbfr != NULL) {
+               nm.msgnums = (long*)cdbfr->ptr;
+               cdbfr->ptr = NULL;
+               nm.num_msgs = cdbfr->len / sizeof(long);
+               cdbfr->len = 0;
+               cdb_free(cdbfr);
+       } else {
+               nm.num_msgs = 0;
+               nm.msgnums = NULL;
+       }
+       return(nm);
+}
+
+
+//
+// Output a room name (newsgroup name) in formats required for LIST and NEWGROUPS command
+//
+void output_roomname_in_list_format(struct ctdlroom *qrbuf, int which_format, char *wildmat_pattern) {
+       char n_name[1024];
+       struct nntp_msglist nm;
+       long low_water_mark = 0;
+       long high_water_mark = 0;
+
+       room_to_newsgroup(n_name, qrbuf->QRname, sizeof n_name);
+
+       if ((wildmat_pattern != NULL) && (!IsEmptyStr(wildmat_pattern))) {
+               if (!wildmat(n_name, wildmat_pattern)) {
+                       return;
+               }
+       }
+
+       nm = nntp_fetch_msglist(qrbuf);
+       if ((nm.num_msgs > 0) && (nm.msgnums != NULL)) {
+               low_water_mark = nm.msgnums[0];
+               high_water_mark = nm.msgnums[nm.num_msgs - 1];
+       }
+
+       // Only the mandatory formats are supported
+       switch(which_format) {
+       case NNTP_LIST_ACTIVE:
+               // FIXME we have hardcoded "n" for "no posting allowed" -- fix when we add posting
+               cprintf("%s %ld %ld n\r\n", n_name, high_water_mark, low_water_mark);
+               break;
+       case NNTP_LIST_NEWSGROUPS:
+               cprintf("%s %s\r\n", n_name, qrbuf->QRname);
+               break;
+       }
+
+       if (nm.msgnums != NULL) {
+               free(nm.msgnums);
+       }
+}
+
+
+//
+// Called once per room by nntp_newgroups() to qualify and possibly output a single room
+//
+void nntp_newgroups_backend(struct ctdlroom *qrbuf, void *data) {
+       int ra;
+       int view;
+       time_t thetime = *(time_t *)data;
+
+       CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
+
+       /*
+        * The "created after <date/time>" heuristics depend on the happy coincidence
+        * that for a very long time we have used a unix timestamp as the room record's
+        * generation number (QRgen).  When this module is merged into the master
+        * source tree we should rename QRgen to QR_create_time or something like that.
+        */
+
+       if (ra & UA_KNOWN) {
+               if (qrbuf->QRgen >= thetime) {
+                       output_roomname_in_list_format(qrbuf, NNTP_LIST_ACTIVE, NULL);
+               }
+       }
+}
+
+
+//
+// Implements the NEWGROUPS command
+//
+void nntp_newgroups(const char *cmd) {
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+       char stringy_date[16];
+       char stringy_time[16];
+       char stringy_gmt[16];
+       struct tm tm;
+       time_t thetime;
+
+       extract_token(stringy_date, cmd, 1, ' ', sizeof stringy_date);
+       extract_token(stringy_time, cmd, 2, ' ', sizeof stringy_time);
+       extract_token(stringy_gmt, cmd, 3, ' ', sizeof stringy_gmt);
+
+       memset(&tm, 0, sizeof tm);
+       if (strlen(stringy_date) == 6) {
+               sscanf(stringy_date, "%2d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday);
+               tm.tm_year += 100;
+       }
+       else {
+               sscanf(stringy_date, "%4d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday);
+               tm.tm_year -= 1900;
+       }
+       tm.tm_mon -= 1;         // tm_mon is zero based (0=January)
+       tm.tm_isdst = (-1);     // let the C library figure out whether DST is in effect
+       sscanf(stringy_time, "%2d%2d%2d", &tm.tm_hour, &tm.tm_min ,&tm.tm_sec);
+       thetime = mktime(&tm);
+       if (!strcasecmp(stringy_gmt, "GMT")) {
+               tzset();
+#ifdef __FreeBSD__
+               thetime += &tm.tm_gmtoff;
+#else
+               thetime += timezone;
+#endif
+       }
+
+
+       cprintf("231 list of new newsgroups follows\r\n");
+       CtdlGetUser(&CC->user, CC->curr_user);
+       CtdlForEachRoom(nntp_newgroups_backend, &thetime);
+       cprintf(".\r\n");
+}
+
+
+//
+// Called once per room by nntp_list() to qualify and possibly output a single room
+//
+void nntp_list_backend(struct ctdlroom *qrbuf, void *data) {
+       int ra;
+       int view;
+       struct nntp_list_data *nld = (struct nntp_list_data *)data;
+
+       CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
+       if (ra & UA_KNOWN) {
+               output_roomname_in_list_format(qrbuf, nld->list_format, nld->wildmat_pattern);
+       }
+}
+
+
+//
+// Implements the LIST commands
+//
+void nntp_list(const char *cmd) {
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+       char list_format[64];
+       char wildmat_pattern[1024];
+       struct nntp_list_data nld;
+
+       extract_token(list_format, cmd, 1, ' ', sizeof list_format);
+       extract_token(wildmat_pattern, cmd, 2, ' ', sizeof wildmat_pattern);
+
+       if (strlen(wildmat_pattern) > 0) {
+               nld.wildmat_pattern = wildmat_pattern;
+       }
+       else {
+               nld.wildmat_pattern = NULL;
+       }
+
+       if ( (strlen(cmd) < 6) || (!strcasecmp(list_format, "ACTIVE")) ) {
+               nld.list_format = NNTP_LIST_ACTIVE;
+       }
+       else if (!strcasecmp(list_format, "NEWSGROUPS")) {
+               nld.list_format = NNTP_LIST_NEWSGROUPS;
+       }
+       else if (!strcasecmp(list_format, "OVERVIEW.FMT")) {
+               nld.list_format = NNTP_LIST_OVERVIEW_FMT;
+       }
+       else {
+               cprintf("501 syntax error , unsupported list format\r\n");
+               return;
+       }
+
+       // OVERVIEW.FMT delivers a completely different type of data than all of the
+       // other LIST commands.  It's a stupid place to put it.  But that's how it's
+       // written into RFC3977, so we have to handle it here.
+       if (nld.list_format == NNTP_LIST_OVERVIEW_FMT) {
+               cprintf("215 Order of fields in overview database.\r\n");
+               cprintf("Subject:\r\n");
+               cprintf("From:\r\n");
+               cprintf("Date:\r\n");
+               cprintf("Message-ID:\r\n");
+               cprintf("References:\r\n");
+               cprintf("Bytes:\r\n");
+               cprintf("Lines:\r\n");
+               cprintf(".\r\n");
+               return;
+       }
+
+       cprintf("215 list of newsgroups follows\r\n");
+       CtdlGetUser(&CC->user, CC->curr_user);
+       CtdlForEachRoom(nntp_list_backend, &nld);
+       cprintf(".\r\n");
+}
+
+
+//
+// Implement HELP command.
+//
+void nntp_help(void) {
+       cprintf("100 This is the Citadel NNTP service.\r\n");
+       cprintf("RTFM http://www.ietf.org/rfc/rfc3977.txt\r\n");
+       cprintf(".\r\n");
+}
+
+
+//
+// Implement DATE command.
+//
+void nntp_date(void) {
+       time_t now;
+       struct tm nowLocal;
+       struct tm nowUtc;
+       char tsFromUtc[32];
+
+       now = time(NULL);
+       localtime_r(&now, &nowLocal);
+       gmtime_r(&now, &nowUtc);
+
+       strftime(tsFromUtc, sizeof(tsFromUtc), "%Y%m%d%H%M%S", &nowUtc);
+
+       cprintf("111 %s\r\n", tsFromUtc);
+}
+
+
+//
+// back end for the LISTGROUP command , called for each message number
+//
+void nntp_listgroup_backend(long msgnum, void *userdata) {
+
+       struct listgroup_range *lr = (struct listgroup_range *)userdata;
+
+       // check range if supplied
+       if (msgnum < lr->lo) return;
+       if ((lr->hi != 0) && (msgnum > lr->hi)) return;
+
+       cprintf("%ld\r\n", msgnum);
+}
+
+
+//
+// Implements the GROUP and LISTGROUP commands
+//
+void nntp_group(const char *cmd) {
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+       citnntp *nntpstate = (citnntp *) CC->session_specific_data;
+       char verb[16];
+       char requested_group[1024];
+       char message_range[256];
+       char range_lo[256];
+       char range_hi[256];
+       char requested_room[ROOMNAMELEN];
+       char augmented_roomname[ROOMNAMELEN];
+       int c = 0;
+       int ok = 0;
+       int ra = 0;
+       struct ctdlroom QRscratch;
+       int msgs, new;
+       long oldest,newest;
+       struct listgroup_range lr;
+
+       extract_token(verb, cmd, 0, ' ', sizeof verb);
+       extract_token(requested_group, cmd, 1, ' ', sizeof requested_group);
+       extract_token(message_range, cmd, 2, ' ', sizeof message_range);
+       extract_token(range_lo, message_range, 0, '-', sizeof range_lo);
+       extract_token(range_hi, message_range, 1, '-', sizeof range_hi);
+       lr.lo = atoi(range_lo);
+       lr.hi = atoi(range_hi);
+
+       /* In LISTGROUP mode we can specify an empty name for 'currently selected' */
+       if ((!strcasecmp(verb, "LISTGROUP")) && (IsEmptyStr(requested_group))) {
+               room_to_newsgroup(requested_group, CC->room.QRname, sizeof requested_group);
+       }
+
+       /* First try a regular match */
+       newsgroup_to_room(requested_room, requested_group, sizeof requested_room);
+       c = CtdlGetRoom(&QRscratch, requested_room);
+
+       /* Then try a mailbox name match */
+       if (c != 0) {
+               CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CC->user, requested_room);
+               c = CtdlGetRoom(&QRscratch, augmented_roomname);
+               if (c == 0) {
+                       safestrncpy(requested_room, augmented_roomname, sizeof(requested_room));
+               }
+       }
+
+       /* If the room exists, check security/access */
+       if (c == 0) {
+               /* See if there is an existing user/room relationship */
+               CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
+
+               /* normal clients have to pass through security */
+               if (ra & UA_KNOWN) {
+                       ok = 1;
+               }
+       }
+
+       /* Fail here if no such room */
+       if (!ok) {
+               cprintf("411 no such newsgroup\r\n");
+               return;
+       }
+
+
+       /*
+        * CtdlUserGoto() formally takes us to the desired room, happily returning
+        * the number of messages and number of new messages.
+        */
+       memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom));
+       CtdlUserGoto(NULL, 0, 0, &msgs, &new, &oldest, &newest);
+       cprintf("211 %d %ld %ld %s\r\n", msgs, oldest, newest, requested_group);
+
+       // If this is a GROUP command, set the "current article number" to zero, and then stop here.
+       if (!strcasecmp(verb, "GROUP")) {
+               nntpstate->current_article_number = oldest;
+               return;
+       }
+
+       // If we get to this point we are running a LISTGROUP command.  Fetch those message numbers.
+       CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, nntp_listgroup_backend, &lr);
+       cprintf(".\r\n");
+}
+
+
+//
+// Implements the MODE command
+//
+void nntp_mode(const char *cmd) {
+
+       char which_mode[16];
+
+       extract_token(which_mode, cmd, 1, ' ', sizeof which_mode);
+
+       if (!strcasecmp(which_mode, "reader")) {
+               // FIXME implement posting and change to 200
+               cprintf("201 Reader mode activated\r\n");
+       }
+       else {
+               cprintf("501 unknown mode\r\n");
+       }
+}
+
+
+//
+// Implements the ARTICLE, HEAD, BODY, and STAT commands.
+// (These commands all accept the same parameters; they differ only in how they output the retrieved message.)
+//
+void nntp_article(const char *cmd) {
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+       citnntp *nntpstate = (citnntp *) CC->session_specific_data;
+       char which_command[16];
+       int acmd = 0;
+       char requested_article[256];
+       long requested_msgnum = 0;
+       char *lb, *rb = NULL;
+       int must_change_currently_selected_article = 0;
+
+       // We're going to store one of these values in the variable 'acmd' so that
+       // we can quickly check later which version of the output we want.
+       enum {
+               ARTICLE,
+               HEAD,
+               BODY,
+               STAT
+       };
+
+       extract_token(which_command, cmd, 0, ' ', sizeof which_command);
+
+       if (!strcasecmp(which_command, "article")) {
+               acmd = ARTICLE;
+       }
+       else if (!strcasecmp(which_command, "head")) {
+               acmd = HEAD;
+       }
+       else if (!strcasecmp(which_command, "body")) {
+               acmd = BODY;
+       }
+       else if (!strcasecmp(which_command, "stat")) {
+               acmd = STAT;
+       }
+       else {
+               cprintf("500 I'm afraid I can't do that.\r\n");
+               return;
+       }
+
+       // Which NNTP command was issued, determines whether we will fetch headers, body, or both.
+       int                     headers_only = HEADERS_ALL;
+       if (acmd == HEAD)       headers_only = HEADERS_FAST;
+       else if (acmd == BODY)  headers_only = HEADERS_NONE;
+       else if (acmd == STAT)  headers_only = HEADERS_FAST;
+
+       // now figure out what the client is asking for.
+       extract_token(requested_article, cmd, 1, ' ', sizeof requested_article);
+       lb = strchr(requested_article, '<');
+       rb = strchr(requested_article, '>');
+       requested_msgnum = atol(requested_article);
+
+       // If no article number or message-id is specified, the client wants the "currently selected article"
+       if (IsEmptyStr(requested_article)) {
+               if (nntpstate->current_article_number < 1) {
+                       cprintf("420 No current article selected\r\n");
+                       return;
+               }
+               requested_msgnum = nntpstate->current_article_number;
+               must_change_currently_selected_article = 1;
+               // got it -- now fall through and keep going
+       }
+
+       // If the requested article is numeric, it maps directly to a message number.  Good.
+       else if (requested_msgnum > 0) {
+               must_change_currently_selected_article = 1;
+               // good -- fall through and keep going
+       }
+
+       // If the requested article has angle brackets, the client wants a specific message-id.
+       // We don't know how to do that yet.
+       else if ( (lb != NULL) && (rb != NULL) && (lb < rb) ) {
+               must_change_currently_selected_article = 0;
+               cprintf("500 I don't know how to fetch by message-id yet.\r\n");        // FIXME
+               return;
+       }
+
+       // Anything else is noncompliant gobbledygook and should die in a car fire.
+       else {
+               must_change_currently_selected_article = 0;
+               cprintf("500 syntax error\r\n");
+               return;
+       }
+
+       // At this point we know the message number of the "article" being requested.
+       // We have an awesome API call that does all the heavy lifting for us.
+       char *fetched_message_id = NULL;
+       CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+       int fetch = CtdlOutputMsg(requested_msgnum,
+                       MT_RFC822,              // output in RFC822 format ... sort of
+                       headers_only,           // headers, body, or both?
+                       0,                      // don't do Citadel protocol responses
+                       1,                      // CRLF newlines
+                       NULL,                   // teh whole thing, not just a section
+                       0,                      // no flags yet ... maybe new ones for Path: etc ?
+                       NULL,
+                       NULL,
+                       &fetched_message_id     // extract the message ID from the message as we go...
+       );
+       StrBuf *msgtext = CC->redirect_buffer;
+       CC->redirect_buffer = NULL;
+
+       if (fetch != om_ok) {
+               cprintf("423 no article with that number\r\n");
+               FreeStrBuf(&msgtext);
+               return;
+       }
+
+       // RFC3977 6.2.1.2 specifes conditions under which the "currently selected article"
+       // MUST or MUST NOT be set to the message we just referenced.
+       if (must_change_currently_selected_article) {
+               nntpstate->current_article_number = requested_msgnum;
+       }
+
+       // Now give the client what it asked for.
+       if (acmd == ARTICLE) {
+               cprintf("220 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
+       }
+       if (acmd == HEAD) {
+               cprintf("221 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
+       }
+       if (acmd == BODY) {
+               cprintf("222 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
+       }
+       if (acmd == STAT) {
+               cprintf("223 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
+               FreeStrBuf(&msgtext);
+               return;
+       }
+
+       client_write(SKEY(msgtext));
+       cprintf(".\r\n");                       // this protocol uses a dot terminator
+       FreeStrBuf(&msgtext);
+       if (fetched_message_id) free(fetched_message_id);
+}
+
+
+//
+// Utility function for nntp_last_next() that turns a msgnum into a message ID.
+// The memory for the returned string is pwnz0red by the caller.
+//
+char *message_id_from_msgnum(long msgnum) {
+       char *fetched_message_id = NULL;
+       CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+       CtdlOutputMsg(msgnum,
+                       MT_RFC822,              // output in RFC822 format ... sort of
+                       HEADERS_FAST,           // headers, body, or both?
+                       0,                      // don't do Citadel protocol responses
+                       1,                      // CRLF newlines
+                       NULL,                   // teh whole thing, not just a section
+                       0,                      // no flags yet ... maybe new ones for Path: etc ?
+                       NULL,
+                       NULL,
+                       &fetched_message_id     // extract the message ID from the message as we go...
+       );
+       StrBuf *msgtext = CC->redirect_buffer;
+       CC->redirect_buffer = NULL;
+
+       FreeStrBuf(&msgtext);
+       return(fetched_message_id);
+}
+
+
+//
+// The LAST and NEXT commands are so similar that they are handled by a single function.
+//
+void nntp_last_next(const char *cmd) {
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+       citnntp *nntpstate = (citnntp *) CC->session_specific_data;
+       char which_command[16];
+       int acmd = 0;
+
+       // We're going to store one of these values in the variable 'acmd' so that
+       // we can quickly check later which version of the output we want.
+       enum {
+               NNTP_LAST,
+               NNTP_NEXT
+       };
+
+       extract_token(which_command, cmd, 0, ' ', sizeof which_command);
+
+       if (!strcasecmp(which_command, "last")) {
+               acmd = NNTP_LAST;
+       }
+       else if (!strcasecmp(which_command, "next")) {
+               acmd = NNTP_NEXT;
+       }
+       else {
+               cprintf("500 I'm afraid I can't do that.\r\n");
+               return;
+       }
+
+       // ok, here we go ... fetch the msglist so we can figure out our place in the universe
+       struct nntp_msglist nm;
+       int i = 0;
+       long selected_msgnum = 0;
+       char *message_id = NULL;
+
+       nm = nntp_fetch_msglist(&CC->room);
+       if ((nm.num_msgs < 0) || (nm.msgnums == NULL)) {
+               cprintf("500 something bad happened\r\n");
+               return;
+       }
+
+       if ( (acmd == NNTP_LAST) && (nm.num_msgs == 0) ) {
+                       cprintf("422 no previous article in this group\r\n");   // nothing here
+       }
+
+       else if ( (acmd == NNTP_LAST) && (nntpstate->current_article_number <= nm.msgnums[0]) ) {
+                       cprintf("422 no previous article in this group\r\n");   // already at the beginning
+       }
+
+       else if (acmd == NNTP_LAST) {
+               for (i=0; ((i<nm.num_msgs)&&(selected_msgnum<=0)); ++i) {
+                       if ( (nm.msgnums[i] >= nntpstate->current_article_number) && (i > 0) ) {
+                               selected_msgnum = nm.msgnums[i-1];
+                       }
+               }
+               if (selected_msgnum > 0) {
+                       nntpstate->current_article_number = selected_msgnum;
+                       message_id = message_id_from_msgnum(nntpstate->current_article_number);
+                       cprintf("223 %ld <%s>\r\n", nntpstate->current_article_number, message_id);
+                       if (message_id) free(message_id);
+               }
+               else {
+                       cprintf("422 no previous article in this group\r\n");
+               }
+       }
+
+       else if ( (acmd == NNTP_NEXT) && (nm.num_msgs == 0) ) {
+                       cprintf("421 no next article in this group\r\n");       // nothing here
+       }
+
+       else if ( (acmd == NNTP_NEXT) && (nntpstate->current_article_number >= nm.msgnums[nm.num_msgs-1]) ) {
+                       cprintf("421 no next article in this group\r\n");       // already at the end
+       }
+
+       else if (acmd == NNTP_NEXT) {
+               for (i=0; ((i<nm.num_msgs)&&(selected_msgnum<=0)); ++i) {
+                       if (nm.msgnums[i] > nntpstate->current_article_number) {
+                               selected_msgnum = nm.msgnums[i];
+                       }
+               }
+               if (selected_msgnum > 0) {
+                       nntpstate->current_article_number = selected_msgnum;
+                       message_id = message_id_from_msgnum(nntpstate->current_article_number);
+                       cprintf("223 %ld <%s>\r\n", nntpstate->current_article_number, message_id);
+                       if (message_id) free(message_id);
+               }
+               else {
+                       cprintf("421 no next article in this group\r\n");
+               }
+       }
+
+       // should never get here
+       else {
+               cprintf("500 internal error\r\n");
+       }
+
+       if (nm.msgnums != NULL) {
+               free(nm.msgnums);
+       }
+
+}
+
+
+//
+// back end for the XOVER command , called for each message number
+//
+void nntp_xover_backend(long msgnum, void *userdata) {
+
+       struct listgroup_range *lr = (struct listgroup_range *)userdata;
+
+       // check range if supplied
+       if (msgnum < lr->lo) return;
+       if ((lr->hi != 0) && (msgnum > lr->hi)) return;
+
+       struct CtdlMessage *msg = CtdlFetchMessage(msgnum, 0);
+       if (msg == NULL) {
+               return;
+       }
+
+       // Teh RFC says we need:
+       // -------------------------
+       // Subject header content
+       // From header content
+       // Date header content
+       // Message-ID header content
+       // References header content
+       // :bytes metadata item
+       // :lines metadata item
+
+       time_t msgtime = atol(msg->cm_fields[eTimestamp]);
+       char strtimebuf[26];
+       ctime_r(&msgtime, strtimebuf);
+
+       // here we go -- print the line o'data
+       cprintf("%ld\t%s\t%s <%s>\t%s\t%s\t%s\t100\t10\r\n",
+               msgnum,
+               msg->cm_fields[eMsgSubject],
+               msg->cm_fields[eAuthor],
+               msg->cm_fields[erFc822Addr],
+               strtimebuf,
+               msg->cm_fields[emessageId],
+               msg->cm_fields[eWeferences]
+       );
+
+       CM_Free(msg);
+}
+
+
+//
+//
+// XOVER is used by some clients, even if we don't offer it
+//
+void nntp_xover(const char *cmd) {
+       if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+       citnntp *nntpstate = (citnntp *) CC->session_specific_data;
+       char range[256];
+       struct listgroup_range lr;
+
+       extract_token(range, cmd, 1, ' ', sizeof range);
+       lr.lo = atol(range);
+       if (lr.lo <= 0) {
+               lr.lo = nntpstate->current_article_number;
+               lr.hi = nntpstate->current_article_number;
+       }
+       else {
+               char *dash = strchr(range, '-');
+               if (dash != NULL) {
+                       ++dash;
+                       lr.hi = atol(dash);
+                       if (lr.hi == 0) {
+                               lr.hi = LONG_MAX;
+                       }
+                       if (lr.hi < lr.lo) {
+                               lr.hi = lr.lo;
+                       }
+               }
+               else {
+                       lr.hi = lr.lo;
+               }
+       }
+
+       cprintf("224 Overview information follows\r\n");
+       CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, nntp_xover_backend, &lr);
+       cprintf(".\r\n");
+}
+
+
+// 
+// Main command loop for NNTP server sessions.
+//
+void nntp_command_loop(void) {
+       StrBuf *Cmd = NewStrBuf();
+       char cmdname[16];
+
+       time(&CC->lastcmd);
+       if (CtdlClientGetLine(Cmd) < 1) {
+               syslog(LOG_CRIT, "NNTP: client disconnected: ending session.\n");
+               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
+               FreeStrBuf(&Cmd);
+               return;
+       }
+       syslog(LOG_DEBUG, "NNTP: %s\n", ((!strncasecmp(ChrPtr(Cmd), "AUTHINFO", 8)) ? "AUTHINFO ..." : ChrPtr(Cmd)));
+       extract_token(cmdname, ChrPtr(Cmd), 0, ' ', sizeof cmdname);
+
+       // Rumpelstiltskin lookups are *awesome*
+
+       if (!strcasecmp(cmdname, "quit")) {
+               nntp_quit();
+       }
+
+       else if (!strcasecmp(cmdname, "help")) {
+               nntp_help();
+       }
+
+       else if (!strcasecmp(cmdname, "date")) {
+               nntp_date();
+       }
+
+       else if (!strcasecmp(cmdname, "capabilities")) {
+               nntp_capabilities();
+       }
+
+       else if (!strcasecmp(cmdname, "starttls")) {
+               nntp_starttls();
+       }
+
+       else if (!strcasecmp(cmdname, "authinfo")) {
+               nntp_authinfo(ChrPtr(Cmd));
+       }
+
+       else if (!strcasecmp(cmdname, "newgroups")) {
+               nntp_newgroups(ChrPtr(Cmd));
+       }
+
+       else if (!strcasecmp(cmdname, "list")) {
+               nntp_list(ChrPtr(Cmd));
+       }
+
+       else if (!strcasecmp(cmdname, "group")) {
+               nntp_group(ChrPtr(Cmd));
+       }
+
+       else if (!strcasecmp(cmdname, "listgroup")) {
+               nntp_group(ChrPtr(Cmd));
+       }
+
+       else if (!strcasecmp(cmdname, "mode")) {
+               nntp_mode(ChrPtr(Cmd));
+       }
+
+       else if (
+                       (!strcasecmp(cmdname, "article"))
+                       || (!strcasecmp(cmdname, "head"))
+                       || (!strcasecmp(cmdname, "body"))
+                       || (!strcasecmp(cmdname, "stat"))
+               )
+       {
+               nntp_article(ChrPtr(Cmd));
+       }
+
+       else if (
+                       (!strcasecmp(cmdname, "last"))
+                       || (!strcasecmp(cmdname, "next"))
+               )
+       {
+               nntp_last_next(ChrPtr(Cmd));
+       }
+
+       else if (
+                       (!strcasecmp(cmdname, "xover"))
+                       || (!strcasecmp(cmdname, "over"))
+               )
+       {
+               nntp_xover(ChrPtr(Cmd));
+       }
+
+       else {
+               cprintf("500 I'm afraid I can't do that.\r\n");
+       }
+
+       FreeStrBuf(&Cmd);
+}
+
+
+//     ****************************************************************************
+//                           MODULE INITIALIZATION STUFF
+//     ****************************************************************************
+
+
+//
+// This cleanup function blows away the temporary memory used by
+// the NNTP server.
+//
+void nntp_cleanup_function(void) {
+       /* Don't do this stuff if this is not an NNTP session! */
+       if (CC->h_command_function != nntp_command_loop) return;
+
+       syslog(LOG_DEBUG, "Performing NNTP cleanup hook\n");
+       citnntp *nntpstate = (citnntp *) CC->session_specific_data;
+       if (nntpstate != NULL) {
+               free(nntpstate);
+               nntpstate = NULL;
+       }
+}
+
+const char *CitadelServiceNNTP="NNTP";
+const char *CitadelServiceNNTPS="NNTPS";
+
+char *ctdl_module_init_nntp(void) {
+       if (!threading) {
+               CtdlRegisterServiceHook(CtdlGetConfigInt("c_nntp_port"),
+                                       NULL,
+                                       nntp_greeting,
+                                       nntp_command_loop,
+                                       NULL, 
+                                       CitadelServiceNNTP);
+
+#ifdef HAVE_OPENSSL
+               CtdlRegisterServiceHook(CtdlGetConfigInt("c_nntps_port"),
+                                       NULL,
+                                       nntps_greeting,
+                                       nntp_command_loop,
+                                       NULL,
+                                       CitadelServiceNNTPS);
+#endif
+
+               CtdlRegisterSessionHook(nntp_cleanup_function, EVT_STOP, PRIO_STOP + 250);
+       }
+       
+       /* return our module name for the log */
+       return "nntp";
+}
diff --git a/citadel/server/modules/nntp/serv_nntp.h b/citadel/server/modules/nntp/serv_nntp.h
new file mode 100644 (file)
index 0000000..672dd67
--- /dev/null
@@ -0,0 +1,58 @@
+//
+// Header file for NNTP server module
+//
+// Copyright (c) 2014 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//  
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+
+
+// data returned by a message list fetch
+struct nntp_msglist {
+       int num_msgs;
+       long *msgnums;
+};
+
+
+// data passed by the LIST commands to its helper function
+struct nntp_list_data {
+       int list_format;
+       char *wildmat_pattern;
+};
+
+
+//
+// data passed between nntp_listgroup() and nntp_listgroup_backend()
+//
+struct listgroup_range {
+       long lo;
+       long hi;
+};
+
+
+typedef struct _citnntp {              // Information about the current session
+       long current_article_number;
+} citnntp;
+
+
+//
+// Various output formats for the LIST commands
+//
+enum {
+       NNTP_LIST_ACTIVE,
+       NNTP_LIST_ACTIVE_TIMES,
+       NNTP_LIST_DISTRIB_PATS,
+       NNTP_LIST_HEADERS,
+       NNTP_LIST_NEWSGROUPS,
+       NNTP_LIST_OVERVIEW_FMT
+};
+
+
+int wildmat(const char *text, const char *p);
+
diff --git a/citadel/server/modules/nntp/wildmat.c b/citadel/server/modules/nntp/wildmat.c
new file mode 100644 (file)
index 0000000..4047cfd
--- /dev/null
@@ -0,0 +1,173 @@
+/* wildmat.h - NNTP wildmat processing functions
+ *
+ * Copyright (c) 1994-2008 Carnegie Mellon University.  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. The name "Carnegie Mellon University" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For permission or any legal
+ *    details, please contact
+ *      Carnegie Mellon University
+ *      Center for Technology Transfer and Enterprise Creation
+ *      4615 Forbes Avenue
+ *      Suite 302
+ *      Pittsburgh, PA  15213
+ *      (412) 268-7393, fax: (412) 268-7395
+ *      innovation@andrew.cmu.edu
+ *
+ * 4. Redistributions of any form whatsoever must retain the following
+ *    acknowledgment:
+ *    "This product includes software developed by Computing Services
+ *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
+ *
+ * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
+ * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $Id: wildmat.c,v 1.4 2010/01/06 17:01:47 murch Exp $
+ */
+
+/*
+**
+**  Do shell-style pattern matching for ?, \, [], and * characters.
+**  Might not be robust in face of malformed patterns; e.g., "foo[a-"
+**  could cause a segmentation violation.  It is 8bit clean.
+**
+**  Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
+**  Rich $alz is now <rsalz@osf.org>.
+**  April, 1991:  Replaced mutually-recursive calls with in-line code
+**  for the star character.
+**
+**  Special thanks to Lars Mathiesen <thorinn@diku.dk> for the ABORT code.
+**  This can greatly speed up failing wildcard patterns.  For example:
+**     pattern: -*-*-*-*-*-*-12-*-*-*-m-*-*-*
+**     text 1:  -adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1
+**     text 2:  -adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1
+**  Text 1 matches with 51 calls, while text 2 fails with 54 calls.  Without
+**  the ABORT code, it takes 22310 calls to fail.  Ugh.  The following
+**  explanation is from Lars:
+**  The precondition that must be fulfilled is that DoMatch will consume
+**  at least one character in text.  This is true if *p is neither '*' nor
+**  '\0'.)  The last return has ABORT instead of FALSE to avoid quadratic
+**  behaviour in cases like pattern "*a*b*c*d" with text "abcxxxxx".  With
+**  FALSE, each star-loop has to run to the end of the text; with ABORT
+**  only the last one does.
+**
+**  Once the control of one instance of DoMatch enters the star-loop, that
+**  instance will return either TRUE or ABORT, and any calling instance
+**  will therefore return immediately after (without calling recursively
+**  again).  In effect, only one star-loop is ever active.  It would be
+**  possible to modify the code to maintain this context explicitly,
+**  eliminating all recursive calls at the cost of some complication and
+**  loss of clarity (and the ABORT stuff seems to be unclear enough by
+**  itself).  I think it would be unwise to try to get this into a
+**  released version unless you have a good test data base to try it out
+**  on.
+*/
+#include <stdio.h>
+#include <sys/types.h>
+
+
+
+#define TRUE                   1
+#define FALSE                  0
+#define ABORT                  -1
+
+
+    /* What character marks an inverted character class? */
+#define NEGATE_CLASS           '^'
+    /* Is "*" a common pattern? */
+#define OPTIMIZE_JUST_STAR
+    /* Do tar(1) matching rules, which ignore a trailing slash? */
+#undef MATCH_TAR_PATTERN
+
+
+/*
+**  Match text and p, return TRUE, FALSE, or ABORT.
+*/
+static int DoMatch(const char *text, const char *p)
+{
+    int                        last;
+    int                        matched;
+    int                        reverse;
+
+    for ( ; *p; text++, p++) {
+       if (*text == '\0' && *p != '*')
+           return ABORT;
+       switch (*p) {
+       case '\\':
+           /* Literal match with following character. */
+           p++;
+           /* FALLTHROUGH */
+       default:
+           if (*text != *p)
+               return FALSE;
+           continue;
+       case '?':
+           /* Match anything. */
+           continue;
+       case '*':
+           while (*++p == '*')
+               /* Consecutive stars act just like one. */
+               continue;
+           if (*p == '\0')
+               /* Trailing star matches everything. */
+               return TRUE;
+           while (*text)
+               if ((matched = DoMatch(text++, p)) != FALSE)
+                   return matched;
+           return ABORT;
+       case '[':
+           reverse = p[1] == NEGATE_CLASS ? TRUE : FALSE;
+           if (reverse)
+               /* Inverted character class. */
+               p++;
+           matched = FALSE;
+           if (p[1] == ']' || p[1] == '-')
+               if (*++p == *text)
+                   matched = TRUE;
+           for (last = *p; *++p && *p != ']'; last = *p)
+               /* This next line requires a good C compiler. */
+               if (*p == '-' && p[1] != ']'
+                   ? *text <= *++p && *text >= last : *text == *p)
+                   matched = TRUE;
+           if (matched == reverse)
+               return FALSE;
+           continue;
+       }
+    }
+
+#ifdef MATCH_TAR_PATTERN
+    if (*text == '/')
+       return TRUE;
+#endif /* MATCH_TAR_ATTERN */
+    return *text == '\0';
+}
+
+
+/*
+**  User-level routine.  Returns TRUE or FALSE.
+*/
+int wildmat(const char *text, const char *p)
+{
+#ifdef OPTIMIZE_JUST_STAR
+    if (p[0] == '*' && p[1] == '\0')
+       return TRUE;
+#endif /* OPTIMIZE_JUST_STAR */
+    return DoMatch(text, p) == TRUE;
+}
diff --git a/citadel/server/modules/notes/.gitignore b/citadel/server/modules/notes/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/notes/serv_notes.c b/citadel/server/modules/notes/serv_notes.c
new file mode 100644 (file)
index 0000000..2f32fae
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * Handles functions related to yellow sticky notes.
+ *
+ * Copyright (c) 2007-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../ctdl_module.h"
+
+
+/*
+ * Callback function for serv_notes_beforesave() hunts for a vNote in the MIME structure
+ */
+void notes_extract_vnote(char *name, char *filename, char *partnum, char *disp,
+                  void *content, char *cbtype, char *cbcharset, size_t length,
+                  char *encoding, char *cbid, void *cbuserdata)
+{
+       struct vnote **v = (struct vnote **) cbuserdata;
+
+       if (!strcasecmp(cbtype, "text/vnote")) {
+
+               syslog(LOG_DEBUG, "Part %s contains a vNote!  Loading...\n", partnum);
+               if (*v != NULL) {
+                       vnote_free(*v);
+               }
+               *v = vnote_new_from_str(content);
+       }
+}
+
+
+/*
+ * Before-save hook searches for two different types of notes (legacy Kolab/Aethera notes
+ * and modern vNote format notes) and does its best to learn the subject (summary)
+ * and EUID (uid) of the note for Citadel's own nefarious purposes.
+ */
+int serv_notes_beforesave(struct CtdlMessage *msg, struct recptypes *recp)
+{
+       char *p;
+       int a, i;
+       char uuid[512];
+       struct vnote *v = NULL;
+
+       /* First determine if this room has the "notes" view set */
+
+       if (CC->room.QRdefaultview != VIEW_NOTES) {
+               return(0);                      /* not notes; do nothing */
+       }
+
+       /* It must be an RFC822 message! */
+       if (msg->cm_format_type != 4) {
+               return(0);      /* You tried to save a non-RFC822 message! */
+       }
+       
+       /*
+        * If we are in a "notes" view room, and the client has sent an RFC822
+        * message containing an X-KOrg-Note-Id: field (Aethera does this, as
+        * do some Kolab clients) then set both the Subject and the Exclusive ID
+        * of the message to that.  It's going to be a UUID so we want to replace
+        * any existing message containing that UUID.
+        */
+       strcpy(uuid, "");
+       p = msg->cm_fields[eMesageText];
+       a = msg->cm_lengths[eMesageText];
+       while (--a > 0) {
+               if (!strncasecmp(p, "X-KOrg-Note-Id: ", 16)) {  /* Found it */
+                       safestrncpy(uuid, p + 16, sizeof(uuid));
+                       for (i = 0; uuid[i]; ++i) {
+                               if ( (uuid[i] == '\r') || (uuid[i] == '\n') ) {
+                                       uuid[i] = 0;
+                                       break;
+                               }
+                       }
+
+                       syslog(LOG_DEBUG, "UUID of note is: %s\n", uuid);
+                       if (!IsEmptyStr(uuid)) {
+                               CM_SetField(msg, eExclusiveID, uuid, strlen(uuid));
+
+                               CM_CopyField(msg, eMsgSubject, eExclusiveID);
+                       }
+               }
+               p++;
+       }
+
+       /* Modern clients are using vNote format.  Check for one... */
+
+       mime_parser(CM_RANGE(msg, eMesageText),
+                   *notes_extract_vnote,
+                   NULL, NULL,
+                   &v,         /* user data ptr - put the vnote here */
+                   0
+       );
+
+       if (v == NULL) return(0);       /* no vNotes were found in this message */
+
+       /* Set the message EUID to the vNote UID */
+
+       if ((v->uid) && (!IsEmptyStr(v->uid))) {
+               syslog(LOG_DEBUG, "UID of vNote is: %s\n", v->uid);
+               CM_SetField(msg, eExclusiveID, v->uid, strlen(v->uid));
+       }
+
+       /* Set the message Subject to the vNote Summary */
+
+       if ((v->summary) && (!IsEmptyStr(v->summary))) {
+               CM_SetField(msg, eMsgSubject, v->summary, strlen(v->summary));
+
+               if (msg->cm_lengths[eMsgSubject] > 72) {
+                       strcpy(&msg->cm_fields[eMsgSubject][68], "...");
+                       CM_CutFieldAt(msg, eMsgSubject, 72);
+               }
+       }
+
+       vnote_free(v);
+       
+       return(0);
+}
+
+
+char *ctdl_module_init_notes(void) {
+       if (!threading) {
+               CtdlRegisterMessageHook(serv_notes_beforesave, EVT_BEFORESAVE);
+       }
+       
+       /* return our module name for the log */
+       return "notes";
+}
diff --git a/citadel/server/modules/openid/.gitignore b/citadel/server/modules/openid/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/openid/serv_openid_rp.c b/citadel/server/modules/openid/serv_openid_rp.c
new file mode 100644 (file)
index 0000000..7f34b60
--- /dev/null
@@ -0,0 +1,1166 @@
+/*
+ * This is an implementation of OpenID 2.0 relying party support in stateless mode.
+ *
+ * Copyright (c) 2007-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <curl/curl.h>
+#include <expat.h>
+#include "../../ctdl_module.h"
+#include "../../config.h"
+#include "../../citserver.h"
+#include "../../user_ops.h"
+
+typedef struct _ctdl_openid {
+       StrBuf *op_url;                 /* OpenID Provider Endpoint URL */
+       StrBuf *claimed_id;             /* Claimed Identifier */
+       int verified;
+       HashList *sreg_keys;
+} ctdl_openid;
+
+enum {
+       openid_disco_none,
+       openid_disco_xrds,
+       openid_disco_html
+};
+
+
+void Free_ctdl_openid(ctdl_openid **FreeMe)
+{
+       if (*FreeMe == NULL) {
+               return;
+       }
+       FreeStrBuf(&(*FreeMe)->op_url);
+       FreeStrBuf(&(*FreeMe)->claimed_id);
+       DeleteHash(&(*FreeMe)->sreg_keys);
+       free(*FreeMe);
+       *FreeMe = NULL;
+}
+
+
+/*
+ * This cleanup function blows away the temporary memory used by this module.
+ */
+void openid_cleanup_function(void) {
+       struct CitContext *CCC = CC;    /* CachedCitContext - performance boost */
+
+       if (CCC->openid_data != NULL) {
+               syslog(LOG_DEBUG, "openid: Clearing OpenID session state");
+               Free_ctdl_openid((ctdl_openid **) &CCC->openid_data);
+       }
+}
+
+
+/**************************************************************************/
+/*                                                                        */
+/* Functions in this section handle Citadel internal OpenID mapping stuff */
+/*                                                                        */
+/**************************************************************************/
+
+
+/*
+ * The structure of an openid record *key* is:
+ *
+ * |--------------claimed_id-------------|
+ *     (actual length of claimed id)
+ *
+ *
+ * The structure of an openid record *value* is:
+ *
+ * |-----user_number----|------------claimed_id---------------|
+ *    (sizeof long)          (actual length of claimed id)
+ *
+ */
+
+
+
+/*
+ * Attach an external authenticator (such as an OpenID) to a Citadel account
+ */
+int attach_extauth(struct ctdluser *who, StrBuf *claimed_id)
+{
+       struct cdbdata *cdboi;
+       long fetched_usernum;
+       char *data;
+       int data_len;
+       char buf[2048];
+
+       if (!who) return(1);
+       if (StrLength(claimed_id)==0) return(1);
+
+       /* Check to see if this authenticator is already in the database */
+
+       cdboi = cdb_fetch(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id));
+       if (cdboi != NULL) {
+               memcpy(&fetched_usernum, cdboi->ptr, sizeof(long));
+               cdb_free(cdboi);
+
+               if (fetched_usernum == who->usernum) {
+                       syslog(LOG_INFO, "openid: %s already associated; no action is taken", ChrPtr(claimed_id));
+                       return(0);
+               }
+               else {
+                       syslog(LOG_INFO, "openid: %s already belongs to another user", ChrPtr(claimed_id));
+                       return(3);
+               }
+       }
+
+       /* Not already in the database, so attach it now */
+
+       data_len = sizeof(long) + StrLength(claimed_id) + 1;
+       data = malloc(data_len);
+
+       memcpy(data, &who->usernum, sizeof(long));
+       memcpy(&data[sizeof(long)], ChrPtr(claimed_id), StrLength(claimed_id) + 1);
+
+       cdb_store(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id), data, data_len);
+       free(data);
+
+       snprintf(buf, sizeof buf, "User <%s> (#%ld) is now associated with %s\n", who->fullname, who->usernum, ChrPtr(claimed_id));
+       CtdlAideMessage(buf, "External authenticator claim");
+       syslog(LOG_INFO, "openid: %s", buf);
+       return(0);
+}
+
+
+/*
+ * When a user is being deleted, we have to delete any OpenID associations
+ */
+void extauth_purge(struct ctdluser *usbuf) {
+       struct cdbdata *cdboi;
+       HashList *keys = NULL;
+       HashPos *HashPos;
+       char *deleteme = NULL;
+       long len;
+       void *Value;
+       const char *Key;
+       long usernum = 0L;
+
+       keys = NewHash(1, NULL);
+       if (!keys) return;
+
+       cdb_rewind(CDB_EXTAUTH);
+       while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
+               if (cdboi->len > sizeof(long)) {
+                       memcpy(&usernum, cdboi->ptr, sizeof(long));
+                       if (usernum == usbuf->usernum) {
+                               deleteme = strdup(cdboi->ptr + sizeof(long)),
+                               Put(keys, deleteme, strlen(deleteme), deleteme, NULL);
+                       }
+               }
+               cdb_free(cdboi);
+       }
+
+       /* Go through the hash list, deleting keys we stored in it */
+
+       HashPos = GetNewHashPos(keys, 0);
+       while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
+       {
+               syslog(LOG_DEBUG, "openid: deleting associated external authenticator <%s>", (char*)Value);
+               cdb_delete(CDB_EXTAUTH, Value, strlen(Value));
+               /* note: don't free(Value) -- deleting the hash list will handle this for us */
+       }
+       DeleteHashPos(&HashPos);
+       DeleteHash(&keys);
+}
+
+
+/*
+ * List the OpenIDs associated with the currently logged in account
+ */
+void cmd_oidl(char *argbuf) {
+       struct cdbdata *cdboi;
+       long usernum = 0L;
+
+       if (CtdlGetConfigInt("c_disable_newu"))
+       {
+               cprintf("%d this system does not support openid.\n", ERROR + CMD_NOT_SUPPORTED);
+               return;
+       }
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       cdb_rewind(CDB_EXTAUTH);
+       cprintf("%d Associated external authenticators:\n", LISTING_FOLLOWS);
+
+       while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
+               if (cdboi->len > sizeof(long)) {
+                       memcpy(&usernum, cdboi->ptr, sizeof(long));
+                       if (usernum == CC->user.usernum) {
+                               cprintf("%s\n", cdboi->ptr + sizeof(long));
+                       }
+               }
+               cdb_free(cdboi);
+       }
+       cprintf("000\n");
+}
+
+
+/*
+ * List ALL OpenIDs in the database
+ */
+void cmd_oida(char *argbuf) {
+       struct cdbdata *cdboi;
+       long usernum;
+       struct ctdluser usbuf;
+
+       if (CtdlGetConfigInt("c_disable_newu"))
+       {
+               cprintf("%d this system does not support openid.\n",
+                       ERROR + CMD_NOT_SUPPORTED);
+               return;
+       }
+       if (CtdlAccessCheck(ac_aide)) return;
+       cdb_rewind(CDB_EXTAUTH);
+       cprintf("%d List of all OpenIDs in the database:\n", LISTING_FOLLOWS);
+
+       while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
+               if (cdboi->len > sizeof(long)) {
+                       memcpy(&usernum, cdboi->ptr, sizeof(long));
+                       if (CtdlGetUserByNumber(&usbuf, usernum) != 0) {
+                               usbuf.fullname[0] = 0;
+                       } 
+                       cprintf("%s|%ld|%s\n",
+                               cdboi->ptr + sizeof(long),
+                               usernum,
+                               usbuf.fullname
+                       );
+               }
+               cdb_free(cdboi);
+       }
+       cprintf("000\n");
+}
+
+
+/*
+ * Create a new user account, manually specifying the name, after successfully
+ * verifying an OpenID (which will of course be attached to the account)
+ */
+void cmd_oidc(char *argbuf) {
+       ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
+
+       if (CtdlGetConfigInt("c_disable_newu"))
+       {
+               cprintf("%d this system does not support openid.\n",
+                       ERROR + CMD_NOT_SUPPORTED);
+               return;
+       }
+       if ( (!oiddata) || (!oiddata->verified) ) {
+               cprintf("%d You have not verified an OpenID yet.\n", ERROR);
+               return;
+       }
+
+       /* We can make the semantics of OIDC exactly the same as NEWU, simply
+        * by _calling_ cmd_newu() and letting it run.  Very clever!
+        */
+       cmd_newu(argbuf);
+
+       /* Now, if this logged us in, we have to attach the OpenID */
+       if (CC->logged_in) {
+               attach_extauth(&CC->user, oiddata->claimed_id);
+       }
+
+}
+
+
+/*
+ * Detach an OpenID from the currently logged in account
+ */
+void cmd_oidd(char *argbuf) {
+       struct cdbdata *cdboi;
+       char id_to_detach[1024];
+       int this_is_mine = 0;
+       long usernum = 0L;
+
+       if (CtdlGetConfigInt("c_disable_newu"))
+       {
+               cprintf("%d this system does not support openid.\n",
+                       ERROR + CMD_NOT_SUPPORTED);
+               return;
+       }
+       if (CtdlAccessCheck(ac_logged_in)) return;
+       extract_token(id_to_detach, argbuf, 0, '|', sizeof id_to_detach);
+       if (IsEmptyStr(id_to_detach)) {
+               cprintf("%d An empty OpenID URL is not allowed.\n", ERROR + ILLEGAL_VALUE);
+       }
+
+       cdb_rewind(CDB_EXTAUTH);
+       while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
+               if (cdboi->len > sizeof(long)) {
+                       memcpy(&usernum, cdboi->ptr, sizeof(long));
+                       if (usernum == CC->user.usernum) {
+                               this_is_mine = 1;
+                       }
+               }
+               cdb_free(cdboi);
+       }
+
+       if (!this_is_mine) {
+               cprintf("%d That OpenID was not found or not associated with your account.\n",
+                       ERROR + ILLEGAL_VALUE);
+               return;
+       }
+
+       cdb_delete(CDB_EXTAUTH, id_to_detach, strlen(id_to_detach));
+       cprintf("%d %s detached from your account.\n", CIT_OK, id_to_detach);
+}
+
+
+/*
+ * Attempt to auto-create a new Citadel account using the nickname from Attribute Exchange
+ */
+int openid_create_user_via_ax(StrBuf *claimed_id, HashList *sreg_keys)
+{
+       char *nickname = NULL;
+       char *firstname = NULL;
+       char *lastname = NULL;
+       char new_password[32];
+       long len;
+       const char *Key;
+       void *Value;
+
+       if (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) return(1);
+       if (CtdlGetConfigInt("c_disable_newu")) return(2);
+       if (CC->logged_in) return(3);
+
+       HashPos *HashPos = GetNewHashPos(sreg_keys, 0);
+       while (GetNextHashPos(sreg_keys, HashPos, &len, &Key, &Value) != 0) {
+               syslog(LOG_DEBUG, "openid: %s = %s", Key, (char *)Value);
+
+               if (cbmstrcasestr(Key, "value.nickname") != NULL) {
+                       nickname = (char *)Value;
+               }
+               else if ( (nickname == NULL) && (cbmstrcasestr(Key, "value.nickname") != NULL)) {
+                       nickname = (char *)Value;
+               }
+               else if (cbmstrcasestr(Key, "value.firstname") != NULL) {
+                       firstname = (char *)Value;
+               }
+               else if (cbmstrcasestr(Key, "value.lastname") != NULL) {
+                       lastname = (char *)Value;
+               }
+
+       }
+       DeleteHashPos(&HashPos);
+
+       if (nickname == NULL) {
+               if ((firstname != NULL) || (lastname != NULL)) {
+                       char fullname[1024] = "";
+                       if (firstname) strcpy(fullname, firstname);
+                       if (firstname && lastname) strcat(fullname, " ");
+                       if (lastname) strcat(fullname, lastname);
+                       nickname = fullname;
+               }
+       }
+
+       if (nickname == NULL) {
+               return(4);
+       }
+       syslog(LOG_DEBUG, "openid: the desired account name is <%s>", nickname);
+
+       if (!CtdlGetUser(&CC->user, nickname)) {
+               syslog(LOG_DEBUG, "openid: <%s> is already taken by another user.", nickname);
+               memset(&CC->user, 0, sizeof(struct ctdluser));
+               return(5);
+       }
+
+       /* The desired account name is available.  Create the account and log it in! */
+       if (create_user(nickname, CREATE_USER_BECOME_USER, NATIVE_AUTH_UID)) return(6);
+
+       /* Generate a random password.
+        * The user doesn't care what the password is since he is using OpenID.
+        */
+       snprintf(new_password, sizeof new_password, "%08lx%08lx", random(), random());
+       CtdlSetPassword(new_password);
+
+       /* Now attach the verified OpenID to this account. */
+       attach_extauth(&CC->user, claimed_id);
+
+       return(0);
+}
+
+
+/*
+ * If a user account exists which is associated with the Claimed ID, log it in and return zero.
+ * Otherwise it returns nonzero.
+ */
+int login_via_extauth(StrBuf *claimed_id)
+{
+       struct cdbdata *cdboi;
+       long usernum = 0;
+
+       cdboi = cdb_fetch(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id));
+       if (cdboi == NULL) {
+               return(-1);
+       }
+
+       memcpy(&usernum, cdboi->ptr, sizeof(long));
+       cdb_free(cdboi);
+
+       if (!CtdlGetUserByNumber(&CC->user, usernum)) {
+               /* Now become the user we just created */
+               safestrncpy(CC->curr_user, CC->user.fullname, sizeof CC->curr_user);
+               do_login();
+               return(0);
+       }
+       else {
+               memset(&CC->user, 0, sizeof(struct ctdluser));
+               return(-1);
+       }
+}
+
+
+
+
+/**************************************************************************/
+/*                                                                        */
+/* Functions in this section handle OpenID protocol                       */
+/*                                                                        */
+/**************************************************************************/
+
+
+/* 
+ * Locate a <link> tag and, given its 'rel=' parameter, return its 'href' parameter
+ */
+void extract_link(StrBuf *target_buf, const char *rel, long repllen, StrBuf *source_buf)
+{
+       int i;
+       const char *ptr;
+       const char *href_start = NULL;
+       const char *href_end = NULL;
+       const char *link_tag_start = NULL;
+       const char *link_tag_end = NULL;
+       const char *rel_start = NULL;
+       const char *rel_end = NULL;
+
+       if (!target_buf) return;
+       if (!rel) return;
+       if (!source_buf) return;
+
+       ptr = ChrPtr(source_buf);
+
+       FlushStrBuf(target_buf);
+       while (ptr = cbmstrcasestr(ptr, "<link"), ptr != NULL) {
+
+               link_tag_start = ptr;
+               link_tag_end = strchr(ptr, '>');
+               if (link_tag_end == NULL)
+                       break;
+               for (i=0; i < 1; i++ ){
+                       rel_start = cbmstrcasestr(link_tag_start, "rel=");
+                       if ((rel_start == NULL) ||
+                           (rel_start > link_tag_end)) 
+                               continue;
+
+                       rel_start = strchr(rel_start, '\"');
+                       if ((rel_start == NULL) ||
+                           (rel_start > link_tag_end)) 
+                               continue;
+                       ++rel_start;
+                       rel_end = strchr(rel_start, '\"');
+                       if ((rel_end == NULL) ||
+                           (rel_end == rel_start) ||
+                           (rel_end >= link_tag_end) ) 
+                               continue;
+                       if (strncasecmp(rel, rel_start, repllen)!= 0)
+                               continue; /* didn't match? never mind... */
+                       
+                       href_start = cbmstrcasestr(link_tag_start, "href=");
+                       if ((href_start == NULL) || 
+                           (href_start >= link_tag_end)) 
+                               continue;
+                       href_start = strchr(href_start, '\"');
+                       if ((href_start == NULL) |
+                           (href_start >= link_tag_end)) 
+                               continue;
+                       ++href_start;
+                       href_end = strchr(href_start, '\"');
+                       if ((href_end == NULL) || 
+                           (href_end == href_start) ||
+                           (href_start >= link_tag_end)) 
+                               continue;
+                       StrBufPlain(target_buf, href_start, href_end - href_start);
+               }
+               ptr = link_tag_end;     
+       }
+}
+
+
+/*
+ * Wrapper for curl_easy_init() that includes the options common to all calls
+ * used in this module. 
+ */
+CURL *ctdl_openid_curl_easy_init(char *errmsg) {
+       CURL *curl;
+
+       curl = curl_easy_init();
+       if (!curl) {
+               return(curl);
+       }
+
+       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
+       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
+
+       if (errmsg) {
+               curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errmsg);
+       }
+       curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
+#ifdef CURLOPT_HTTP_CONTENT_DECODING
+       curl_easy_setopt(curl, CURLOPT_HTTP_CONTENT_DECODING, 1);
+       curl_easy_setopt(curl, CURLOPT_ENCODING, "");
+#endif
+       curl_easy_setopt(curl, CURLOPT_USERAGENT, CITADEL);
+       curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30);            /* die after 30 seconds */
+
+       if (
+               (!IsEmptyStr(CtdlGetConfigStr("c_ip_addr")))
+               && (strcmp(CtdlGetConfigStr("c_ip_addr"), "*"))
+               && (strcmp(CtdlGetConfigStr("c_ip_addr"), "::"))
+               && (strcmp(CtdlGetConfigStr("c_ip_addr"), "0.0.0.0"))
+       ) {
+               curl_easy_setopt(curl, CURLOPT_INTERFACE, CtdlGetConfigStr("c_ip_addr"));
+       }
+
+       return(curl);
+}
+
+
+struct xrds {
+       StrBuf *CharData;
+       int nesting_level;
+       int in_xrd;
+       int current_service_priority;
+       int selected_service_priority;
+       StrBuf *current_service_uri;
+       StrBuf *selected_service_uri;
+       int current_service_is_oid2auth;
+};
+
+
+void xrds_xml_start(void *data, const char *supplied_el, const char **attr) {
+       struct xrds *xrds = (struct xrds *) data;
+       int i;
+
+       ++xrds->nesting_level;
+
+       if (!strcasecmp(supplied_el, "XRD")) {
+               ++xrds->in_xrd;
+       }
+
+       else if (!strcasecmp(supplied_el, "service")) {
+               xrds->current_service_priority = 0;
+               xrds->current_service_is_oid2auth = 0;
+               for (i=0; attr[i] != NULL; i+=2) {
+                       if (!strcasecmp(attr[i], "priority")) {
+                               xrds->current_service_priority = atoi(attr[i+1]);
+                       }
+               }
+       }
+
+       FlushStrBuf(xrds->CharData);
+}
+
+
+void xrds_xml_end(void *data, const char *supplied_el) {
+       struct xrds *xrds = (struct xrds *) data;
+
+       --xrds->nesting_level;
+
+       if (!strcasecmp(supplied_el, "XRD")) {
+               --xrds->in_xrd;
+       }
+
+       else if (!strcasecmp(supplied_el, "type")) {
+               if (    (xrds->in_xrd)
+                       && (!strcasecmp(ChrPtr(xrds->CharData), "http://specs.openid.net/auth/2.0/server"))
+               ) {
+                       xrds->current_service_is_oid2auth = 1;
+               }
+               if (    (xrds->in_xrd)
+                       && (!strcasecmp(ChrPtr(xrds->CharData), "http://specs.openid.net/auth/2.0/signon"))
+               ) {
+                       xrds->current_service_is_oid2auth = 1;
+                       /* FIXME in this case, the Claimed ID should be considered immutable */
+               }
+       }
+
+       else if (!strcasecmp(supplied_el, "uri")) {
+               if (xrds->in_xrd) {
+                       FlushStrBuf(xrds->current_service_uri);
+                       StrBufAppendBuf(xrds->current_service_uri, xrds->CharData, 0);
+               }
+       }
+
+       else if (!strcasecmp(supplied_el, "service")) {
+               if (    (xrds->in_xrd)
+                       && (xrds->current_service_priority < xrds->selected_service_priority)
+                       && (xrds->current_service_is_oid2auth)
+               ) {
+                       xrds->selected_service_priority = xrds->current_service_priority;
+                       FlushStrBuf(xrds->selected_service_uri);
+                       StrBufAppendBuf(xrds->selected_service_uri, xrds->current_service_uri, 0);
+               }
+
+       }
+
+       FlushStrBuf(xrds->CharData);
+}
+
+
+void xrds_xml_chardata(void *data, const XML_Char *s, int len) {
+       struct xrds *xrds = (struct xrds *) data;
+
+       StrBufAppendBufPlain (xrds->CharData, s, len, 0);
+}
+
+
+/*
+ * Parse an XRDS document.
+ * If an OpenID Provider URL is discovered, op_url to that value and return nonzero.
+ * If nothing useful happened, return 0.
+ */
+int parse_xrds_document(StrBuf *ReplyBuf) {
+       ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
+       struct xrds xrds;
+       int return_value = 0;
+
+       memset(&xrds, 0, sizeof (struct xrds));
+       xrds.selected_service_priority = INT_MAX;
+       xrds.CharData = NewStrBuf();
+       xrds.current_service_uri = NewStrBuf();
+       xrds.selected_service_uri = NewStrBuf();
+       XML_Parser xp = XML_ParserCreate(NULL);
+       if (xp) {
+               XML_SetUserData(xp, &xrds);
+               XML_SetElementHandler(xp, xrds_xml_start, xrds_xml_end);
+               XML_SetCharacterDataHandler(xp, xrds_xml_chardata);
+               XML_Parse(xp, ChrPtr(ReplyBuf), StrLength(ReplyBuf), 0);
+               XML_Parse(xp, "", 0, 1);
+               XML_ParserFree(xp);
+       }
+       else {
+               syslog(LOG_ERR, "openid: cannot create XML parser");
+       }
+
+       if (xrds.selected_service_priority < INT_MAX) {
+               if (oiddata->op_url == NULL) {
+                       oiddata->op_url = NewStrBuf();
+               }
+               FlushStrBuf(oiddata->op_url);
+               StrBufAppendBuf(oiddata->op_url, xrds.selected_service_uri, 0);
+               return_value = openid_disco_xrds;
+       }
+
+       FreeStrBuf(&xrds.CharData);
+       FreeStrBuf(&xrds.current_service_uri);
+       FreeStrBuf(&xrds.selected_service_uri);
+
+       return(return_value);
+}
+
+
+/*
+ * Callback function for perform_openid2_discovery()
+ * We're interested in the X-XRDS-Location: header.
+ */
+size_t yadis_headerfunction(void *ptr, size_t size, size_t nmemb, void *userdata) {
+       char hdr[1024];
+       StrBuf **x_xrds_location = (StrBuf **) userdata;
+
+       memcpy(hdr, ptr, (size*nmemb));
+       hdr[size*nmemb] = 0;
+
+       if (!strncasecmp(hdr, "X-XRDS-Location:", 16)) {
+               *x_xrds_location = NewStrBufPlain(&hdr[16], ((size*nmemb)-16));
+               StrBufTrim(*x_xrds_location);
+       }
+
+       return(size * nmemb);
+}
+
+
+/* Attempt to perform Yadis discovery as specified in Yadis 1.0 section 6.2.5.
+ * 
+ * If Yadis fails, we then attempt HTML discovery using the same document.
+ *
+ * If successful, returns nonzero and calls parse_xrds_document() to act upon the received data.
+ * If fails, returns 0 and does nothing else.
+ */
+int perform_openid2_discovery(StrBuf *SuppliedURL) {
+       ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
+       int docbytes = (-1);
+       StrBuf *ReplyBuf = NULL;
+       int return_value = 0;
+       CURL *curl;
+       CURLcode result;
+       char errmsg[1024] = "";
+       struct curl_slist *my_headers = NULL;
+       StrBuf *x_xrds_location = NULL;
+
+       if (!SuppliedURL) return(0);
+       syslog(LOG_DEBUG, "openid: perform_openid2_discovery(%s)", ChrPtr(SuppliedURL));
+       if (StrLength(SuppliedURL) == 0) return(0);
+
+       ReplyBuf = NewStrBuf();
+       if (!ReplyBuf) return(0);
+
+       curl = ctdl_openid_curl_easy_init(errmsg);
+       if (!curl) return(0);
+
+       curl_easy_setopt(curl, CURLOPT_URL, ChrPtr(SuppliedURL));
+       curl_easy_setopt(curl, CURLOPT_WRITEDATA, ReplyBuf);
+       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback);
+
+       my_headers = curl_slist_append(my_headers, "Accept:");  /* disable the default Accept: header */
+       my_headers = curl_slist_append(my_headers, "Accept: application/xrds+xml");
+       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, my_headers);
+
+       curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &x_xrds_location);
+       curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, yadis_headerfunction);
+
+       result = curl_easy_perform(curl);
+       if (result) {
+               syslog(LOG_DEBUG, "openid: libcurl error %d: %s", result, errmsg);
+       }
+       curl_slist_free_all(my_headers);
+       curl_easy_cleanup(curl);
+       docbytes = StrLength(ReplyBuf);
+
+       /*
+        * The response from the server will be one of:
+        * 
+        * Option 1: An HTML document with a <head> element that includes a <meta> element with http-equiv
+        * attribute, X-XRDS-Location,
+        *
+        * Does any provider actually do this?  If so then we will implement it in the future.
+        */
+
+       /*
+        * Option 2: HTTP response-headers that include an X-XRDS-Location response-header,
+        *           together with a document.
+        * Option 3: HTTP response-headers only, which MAY include an X-XRDS-Location response-header,
+        *           a contenttype response-header specifying MIME media type,
+        *           application/xrds+xml, or both.
+        *
+        * If the X-XRDS-Location header was delivered, we know about it at this point...
+        */
+       if (    (x_xrds_location)
+               && (strcmp(ChrPtr(x_xrds_location), ChrPtr(SuppliedURL)))
+       ) {
+               syslog(LOG_DEBUG, "openid: X-XRDS-Location: %s ... recursing!", ChrPtr(x_xrds_location));
+               return_value = perform_openid2_discovery(x_xrds_location);
+               FreeStrBuf(&x_xrds_location);
+       }
+
+       /*
+        * Option 4: the returned web page may *be* an XRDS document.  Try to parse it.
+        */
+       if ( (return_value == 0) && (docbytes >= 0)) {
+               return_value = parse_xrds_document(ReplyBuf);
+       }
+
+       /*
+        * Option 5: if all else fails, attempt HTML based discovery.
+        */
+       if ( (return_value == 0) && (docbytes >= 0)) {
+               if (oiddata->op_url == NULL) {
+                       oiddata->op_url = NewStrBuf();
+               }
+               extract_link(oiddata->op_url, HKEY("openid2.provider"), ReplyBuf);
+               if (StrLength(oiddata->op_url) > 0) {
+                       return_value = openid_disco_html;
+               }
+       }
+
+       if (ReplyBuf != NULL) {
+               FreeStrBuf(&ReplyBuf);
+       }
+       return(return_value);
+}
+
+
+/*
+ * Setup an OpenID authentication
+ */
+void cmd_oids(char *argbuf) {
+       struct CitContext *CCC = CC;    /* CachedCitContext - performance boost */
+       const char *Pos = NULL;
+       StrBuf *ArgBuf = NULL;
+       StrBuf *ReplyBuf = NULL;
+       StrBuf *return_to = NULL;
+       StrBuf *RedirectUrl = NULL;
+       ctdl_openid *oiddata;
+       int discovery_succeeded = 0;
+
+       if (CtdlGetConfigInt("c_disable_newu"))
+       {
+               cprintf("%d this system does not support openid.\n",
+                       ERROR + CMD_NOT_SUPPORTED);
+               return;
+       }
+       Free_ctdl_openid ((ctdl_openid**)&CCC->openid_data);
+
+       CCC->openid_data = oiddata = malloc(sizeof(ctdl_openid));
+       if (oiddata == NULL) {
+               syslog(LOG_ERR, "openid: malloc() failed: %m");
+               cprintf("%d malloc failed\n", ERROR + INTERNAL_ERROR);
+               return;
+       }
+       memset(oiddata, 0, sizeof(ctdl_openid));
+
+       ArgBuf = NewStrBufPlain(argbuf, -1);
+
+       oiddata->verified = 0;
+       oiddata->claimed_id = NewStrBufPlain(NULL, StrLength(ArgBuf));
+       return_to = NewStrBufPlain(NULL, StrLength(ArgBuf));
+
+       StrBufExtract_NextToken(oiddata->claimed_id, ArgBuf, &Pos, '|');
+       StrBufExtract_NextToken(return_to, ArgBuf, &Pos, '|');
+
+       syslog(LOG_DEBUG, "openid: user-Supplied Identifier is: %s", ChrPtr(oiddata->claimed_id));
+
+       /********** OpenID 2.0 section 7.3 - Discovery **********/
+
+       /* Section 7.3.1 says we have to attempt XRI based discovery.
+        * No one is using this, no one is asking for it, no one wants it.
+        * So we're not even going to bother attempting this mode.
+        */
+
+       /* Attempt section 7.3.2 (Yadis discovery) and section 7.3.3 (HTML discovery);
+        */
+       discovery_succeeded = perform_openid2_discovery(oiddata->claimed_id);
+
+       if (discovery_succeeded == 0) {
+               cprintf("%d There is no OpenID identity provider at this location.\n", ERROR);
+       }
+
+       else {
+               /*
+                * If we get to this point we are in possession of a valid OpenID Provider URL.
+                */
+               syslog(LOG_DEBUG, "openid: OP URI '%s' discovered using method %d",
+                       ChrPtr(oiddata->op_url),
+                       discovery_succeeded
+               );
+
+               /* We have to "normalize" our Claimed ID otherwise it will cause some OP's to barf */
+               if (cbmstrcasestr(ChrPtr(oiddata->claimed_id), "://") == NULL) {
+                       StrBuf *cid = oiddata->claimed_id;
+                       oiddata->claimed_id = NewStrBufPlain(HKEY("http://"));
+                       StrBufAppendBuf(oiddata->claimed_id, cid, 0);
+                       FreeStrBuf(&cid);
+               }
+
+               /*
+                * OpenID 2.0 section 9: request authentication
+                * Assemble a URL to which the user-agent will be redirected.
+                */
+       
+               RedirectUrl = NewStrBufDup(oiddata->op_url);
+
+               StrBufAppendBufPlain(RedirectUrl, HKEY("?openid.ns="), 0);
+               StrBufUrlescAppend(RedirectUrl, NULL, "http://specs.openid.net/auth/2.0");
+
+               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.mode=checkid_setup"), 0);
+
+               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.claimed_id="), 0);
+               StrBufUrlescAppend(RedirectUrl, oiddata->claimed_id, NULL);
+
+               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.identity="), 0);
+               StrBufUrlescAppend(RedirectUrl, oiddata->claimed_id, NULL);
+
+               /* return_to tells the provider how to complete the round trip back to our site */
+               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.return_to="), 0);
+               StrBufUrlescAppend(RedirectUrl, return_to, NULL);
+
+               /* Attribute Exchange
+                * See:
+                *      http://openid.net/specs/openid-attribute-exchange-1_0.html
+                *      http://code.google.com/apis/accounts/docs/OpenID.html#endpoint
+                *      http://test-id.net/OP/AXFetch.aspx
+                */
+
+               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ns.ax="), 0);
+               StrBufUrlescAppend(RedirectUrl, NULL, "http://openid.net/srv/ax/1.0");
+
+               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.mode=fetch_request"), 0);
+
+               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.required=firstname,lastname,friendly,nickname"), 0);
+
+               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.firstname="), 0);
+               StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/first");
+
+               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.lastname="), 0);
+               StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/last");
+
+               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.friendly="), 0);
+               StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/friendly");
+
+               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.nickname="), 0);
+               StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/nickname");
+
+               syslog(LOG_DEBUG, "openid: redirecting client to %s", ChrPtr(RedirectUrl));
+               cprintf("%d %s\n", CIT_OK, ChrPtr(RedirectUrl));
+       }
+       
+       FreeStrBuf(&ArgBuf);
+       FreeStrBuf(&ReplyBuf);
+       FreeStrBuf(&return_to);
+       FreeStrBuf(&RedirectUrl);
+}
+
+
+/*
+ * Finalize an OpenID authentication
+ */
+void cmd_oidf(char *argbuf) {
+       long len;
+       char buf[2048];
+       char thiskey[1024];
+       char thisdata[1024];
+       HashList *keys = NULL;
+       const char *Key;
+       void *Value;
+       ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
+
+       if (CtdlGetConfigInt("c_disable_newu"))
+       {
+               cprintf("%d this system does not support openid.\n",
+                       ERROR + CMD_NOT_SUPPORTED);
+               return;
+       }
+       if (oiddata == NULL) {
+               cprintf("%d run OIDS first.\n", ERROR + INTERNAL_ERROR);
+               return;
+       }
+       if (StrLength(oiddata->op_url) == 0){
+               cprintf("%d No OpenID Endpoint URL has been obtained.\n", ERROR + ILLEGAL_VALUE);
+               return;
+       }
+       keys = NewHash(1, NULL);
+       if (!keys) {
+               cprintf("%d NewHash() failed\n", ERROR + INTERNAL_ERROR);
+               return;
+       }
+       cprintf("%d Transmit OpenID data now\n", START_CHAT_MODE);
+
+       while (client_getln(buf, sizeof buf), strcmp(buf, "000")) {
+               len = extract_token(thiskey, buf, 0, '|', sizeof thiskey);
+               if (len < 0) {
+                       len = sizeof(thiskey) - 1;
+               }
+               extract_token(thisdata, buf, 1, '|', sizeof thisdata);
+               Put(keys, thiskey, len, strdup(thisdata), NULL);
+       }
+
+       /* Check to see if this is a correct response.
+        * Start with verified=1 but then set it to 0 if anything looks wrong.
+        */
+       oiddata->verified = 1;
+
+       char *openid_ns = NULL;
+       if (    (!GetHash(keys, "ns", 2, (void *) &openid_ns))
+               || (strcasecmp(openid_ns, "http://specs.openid.net/auth/2.0"))
+       ) {
+               syslog(LOG_DEBUG, "openid: this is not an an OpenID assertion");
+               oiddata->verified = 0;
+       }
+
+       char *openid_mode = NULL;
+       if (    (!GetHash(keys, "mode", 4, (void *) &openid_mode))
+               || (strcasecmp(openid_mode, "id_res"))
+       ) {
+               oiddata->verified = 0;
+       }
+
+       char *openid_claimed_id = NULL;
+       if (GetHash(keys, "claimed_id", 10, (void *) &openid_claimed_id)) {
+               FreeStrBuf(&oiddata->claimed_id);
+               oiddata->claimed_id = NewStrBufPlain(openid_claimed_id, -1);
+               syslog(LOG_DEBUG, "openid: provider is asserting the Claimed ID '%s'", ChrPtr(oiddata->claimed_id));
+       }
+
+       /* Validate the assertion against the server */
+       syslog(LOG_DEBUG, "openid: validating...");
+
+       CURL *curl;
+       CURLcode res;
+       struct curl_httppost *formpost = NULL;
+       struct curl_httppost *lastptr = NULL;
+       char errmsg[1024] = "";
+       StrBuf *ReplyBuf = NewStrBuf();
+
+       curl_formadd(&formpost, &lastptr,
+               CURLFORM_COPYNAME,      "openid.mode",
+               CURLFORM_COPYCONTENTS,  "check_authentication",
+               CURLFORM_END
+       );
+
+       HashPos *HashPos = GetNewHashPos(keys, 0);
+       while (GetNextHashPos(keys, HashPos, &len, &Key, &Value) != 0) {
+               if (strcasecmp(Key, "mode")) {
+                       char k_o_keyname[1024];
+                       snprintf(k_o_keyname, sizeof k_o_keyname, "openid.%s", (const char *)Key);
+                       curl_formadd(&formpost, &lastptr,
+                               CURLFORM_COPYNAME,      k_o_keyname,
+                               CURLFORM_COPYCONTENTS,  (char *)Value,
+                               CURLFORM_END
+                       );
+               }
+       }
+       DeleteHashPos(&HashPos);
+
+       curl = ctdl_openid_curl_easy_init(errmsg);
+       curl_easy_setopt(curl, CURLOPT_URL, ChrPtr(oiddata->op_url));
+       curl_easy_setopt(curl, CURLOPT_WRITEDATA, ReplyBuf);
+       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback);
+       curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
+
+       res = curl_easy_perform(curl);
+       if (res) {
+               syslog(LOG_DEBUG, "openid: cmd_oidf() libcurl error %d: %s", res, errmsg);
+               oiddata->verified = 0;
+       }
+       curl_easy_cleanup(curl);
+       curl_formfree(formpost);
+
+       if (cbmstrcasestr(ChrPtr(ReplyBuf), "is_valid:true") == NULL) {
+               oiddata->verified = 0;
+       }
+       FreeStrBuf(&ReplyBuf);
+
+       syslog(LOG_DEBUG, "openid: authentication %s", (oiddata->verified ? "succeeded" : "failed") );
+
+       /* Respond to the client */
+
+       if (oiddata->verified) {
+
+               /* If we were already logged in, attach the OpenID to the user's account */
+               if (CC->logged_in) {
+                       if (attach_extauth(&CC->user, oiddata->claimed_id) == 0) {
+                               cprintf("attach\n");
+                               syslog(LOG_DEBUG, "openid: attach succeeded");
+                       }
+                       else {
+                               cprintf("fail\n");
+                               syslog(LOG_DEBUG, "openid: attach failed");
+                       }
+               }
+
+               /* Otherwise, a user is attempting to log in using the verified OpenID */       
+               else {
+                       /*
+                        * Existing user who has claimed this OpenID?
+                        *
+                        * Note: if you think that sending the password back over the wire is insecure,
+                        * check your assumptions.  If someone has successfully asserted an OpenID that
+                        * is associated with the account, they already have password equivalency and can
+                        * login, so they could just as easily change the password, etc.
+                        */
+                       if (login_via_extauth(oiddata->claimed_id) == 0) {
+                               cprintf("authenticate\n%s\n%s\n", CC->user.fullname, CC->user.password);
+                               logged_in_response();
+                               syslog(LOG_DEBUG, "openid: logged in using previously claimed OpenID");
+                       }
+
+                       /*
+                        * If this system does not allow self-service new user registration, the
+                        * remaining modes do not apply, so fail here and now.
+                        */
+                       else if (CtdlGetConfigInt("c_disable_newu")) {
+                               cprintf("fail\n");
+                               syslog(LOG_DEBUG, "openid: creating user failed due to local policy");
+                       }
+
+                       /*
+                        * New user whose OpenID is verified and Attribute Exchange gave us a name?
+                        */
+                       else if (openid_create_user_via_ax(oiddata->claimed_id, keys) == 0) {
+                               cprintf("authenticate\n%s\n%s\n", CC->user.fullname, CC->user.password);
+                               logged_in_response();
+                               syslog(LOG_DEBUG, "openid: successfully auto-created new user");
+                       }
+
+                       /*
+                        * OpenID is verified, but the desired username either was not specified or
+                        * conflicts with an existing user.  Manual account creation is required.
+                        */
+                       else {
+                               char *desired_name = NULL;
+                               cprintf("verify_only\n");
+                               cprintf("%s\n", ChrPtr(oiddata->claimed_id));
+                               if (GetHash(keys, "sreg.nickname", 13, (void *) &desired_name)) {
+                                       cprintf("%s\n", desired_name);
+                               }
+                               else {
+                                       cprintf("\n");
+                               }
+                               syslog(LOG_DEBUG, "openid: the desired display name is already taken.");
+                       }
+               }
+       }
+       else {
+               cprintf("fail\n");
+       }
+       cprintf("000\n");
+
+       if (oiddata->sreg_keys != NULL) {
+               DeleteHash(&oiddata->sreg_keys);
+               oiddata->sreg_keys = NULL;
+       }
+       oiddata->sreg_keys = keys;
+}
+
+
+
+/**************************************************************************/
+/*                                                                        */
+/* Functions in this section handle module initialization and shutdown    */
+/*                                                                        */
+/**************************************************************************/
+
+
+char *ctdl_module_init_openid_rp(void) {
+       if (!threading) {
+               /* Only enable the OpenID command set when native mode authentication is in use. */
+               if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_NATIVE) {
+                       CtdlRegisterProtoHook(cmd_oids, "OIDS", "Setup OpenID authentication");
+                       CtdlRegisterProtoHook(cmd_oidf, "OIDF", "Finalize OpenID authentication");
+                       CtdlRegisterProtoHook(cmd_oidl, "OIDL", "List OpenIDs associated with an account");
+                       CtdlRegisterProtoHook(cmd_oidd, "OIDD", "Detach an OpenID from an account");
+                       CtdlRegisterProtoHook(cmd_oidc, "OIDC", "Create new user after validating OpenID");
+                       CtdlRegisterProtoHook(cmd_oida, "OIDA", "List all OpenIDs in the database");
+               }
+               CtdlRegisterSessionHook(openid_cleanup_function, EVT_LOGOUT, PRIO_LOGOUT + 10);
+               CtdlRegisterUserHook(extauth_purge, EVT_PURGEUSER);
+               openid_level_supported = 1;     /* This module supports OpenID 1.0 only */
+       }
+
+       /* return our module name for the log */
+       return "openid_rp";
+}
diff --git a/citadel/server/modules/pop3/.gitignore b/citadel/server/modules/pop3/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/pop3/serv_pop3.c b/citadel/server/modules/pop3/serv_pop3.c
new file mode 100644 (file)
index 0000000..7eca60d
--- /dev/null
@@ -0,0 +1,597 @@
+// POP3 service for the Citadel system
+//
+// Copyright (c) 1998-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// Current status of standards conformance:
+//
+// -> All required POP3 commands described in RFC1939 are implemented.
+// -> All optional POP3 commands described in RFC1939 are also implemented.
+// -> The deprecated "LAST" command is included in this implementation, because
+//    there exist mail clients which insist on using it (such as Bynari
+//    TradeMail, and certain versions of Eudora).
+// -> Capability detection via the method described in RFC2449 is implemented.
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_pop3.h"
+#include "../../ctdl_module.h"
+
+
+// This cleanup function blows away the temporary memory and files used by
+// the POP3 server.
+void pop3_cleanup_function(void) {
+       /* Don't do this stuff if this is not a POP3 session! */
+       if (CC->h_command_function != pop3_command_loop) return;
+
+       struct citpop3 *pop3 = ((struct citpop3 *)CC->session_specific_data);
+       syslog(LOG_DEBUG, "pop3: performing cleanup hook");
+       if (pop3->msgs != NULL) {
+               free(pop3->msgs);
+       }
+
+       free(pop3);
+}
+
+
+// Here's where our POP3 session begins its happy day.
+void pop3_greeting(void) {
+       strcpy(CC->cs_clientname, "POP3 session");
+       CC->internal_pgm = 1;
+       CC->session_specific_data = malloc(sizeof(struct citpop3));
+       memset(POP3, 0, sizeof(struct citpop3));
+
+       cprintf("+OK Citadel POP3 server ready.\r\n");
+}
+
+
+// POP3S is just like POP3, except it goes crypto right away.
+void pop3s_greeting(void) {
+       CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
+
+/* kill session if no crypto */
+#ifdef HAVE_OPENSSL
+       if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;
+#else
+       CC->kill_me = KILLME_NO_CRYPTO;
+#endif
+
+       pop3_greeting();
+}
+
+
+// Specify user name (implements POP3 "USER" command)
+void pop3_user(char *argbuf) {
+       char username[SIZ];
+
+       if (CC->logged_in) {
+               cprintf("-ERR You are already logged in.\r\n");
+               return;
+       }
+
+       strcpy(username, argbuf);
+       striplt(username);
+
+       if (CtdlLoginExistingUser(username) == login_ok) {
+               cprintf("+OK Password required for %s\r\n", username);
+       }
+       else {
+               cprintf("-ERR No such user.\r\n");
+       }
+}
+
+
+// Back end for pop3_grab_mailbox()
+void pop3_add_message(long msgnum, void *userdata) {
+       struct MetaData smi;
+
+       ++POP3->num_msgs;
+       if (POP3->num_msgs < 2) {
+               POP3->msgs = malloc(sizeof(struct pop3msg));
+       }
+       else {
+               POP3->msgs = realloc(POP3->msgs, (POP3->num_msgs * sizeof(struct pop3msg)) ) ;
+       }
+       POP3->msgs[POP3->num_msgs-1].msgnum = msgnum;
+       POP3->msgs[POP3->num_msgs-1].deleted = 0;
+
+       // We need to know the length of this message when it is printed in
+       // RFC822 format.  Perhaps we have cached this length in the message's
+       // metadata record.  If so, great; if not, measure it and then cache
+       // it for next time.
+       GetMetaData(&smi, msgnum);
+       if (smi.meta_rfc822_length <= 0L) {
+               CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+               CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, SUPPRESS_ENV_TO, NULL, NULL, NULL);
+               smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
+               FreeStrBuf(&CC->redirect_buffer);
+               PutMetaData(&smi);
+       }
+       POP3->msgs[POP3->num_msgs-1].rfc822_length = smi.meta_rfc822_length;
+}
+
+
+// Open the inbox and read its contents.
+// (This should be called only once, by pop3_pass(), and returns the number
+// of messages in the inbox, or -1 for error)
+int pop3_grab_mailbox(void) {
+        visit vbuf;
+       int i;
+
+       if (CtdlGetRoom(&CC->room, MAILROOM) != 0) return(-1);
+
+       /* Load up the messages */
+       CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, pop3_add_message, NULL);
+
+       /* Figure out which are old and which are new */
+        CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+       POP3->lastseen = (-1);
+       if (POP3->num_msgs) for (i=0; i<POP3->num_msgs; ++i) {
+               if (is_msg_in_sequence_set(vbuf.v_seen, (POP3->msgs[POP3->num_msgs-1].msgnum) )) {
+                       POP3->lastseen = i;
+               }
+       }
+
+       return(POP3->num_msgs);
+}
+
+
+void pop3_login(void) {
+       int msgs;
+       
+       msgs = pop3_grab_mailbox();
+       if (msgs >= 0) {
+               cprintf("+OK %s is logged in (%d messages)\r\n",
+                       CC->user.fullname, msgs);
+               syslog(LOG_DEBUG, "pop3: authenticated %s", CC->user.fullname);
+       }
+       else {
+               cprintf("-ERR Can't open your mailbox\r\n");
+       }
+       
+}
+
+
+// Authorize with password (implements POP3 "PASS" command)
+void pop3_pass(char *argbuf) {
+       char password[SIZ];
+
+       safestrncpy(password, argbuf, sizeof password);
+       striplt(password);
+
+       if (CtdlTryPassword(password, strlen(password)) == pass_ok) {
+               pop3_login();
+       }
+       else {
+               cprintf("-ERR That is NOT the password.\r\n");
+       }
+}
+
+
+// list available msgs
+void pop3_list(char *argbuf) {
+       int i;
+       int which_one;
+
+       which_one = atoi(argbuf);
+
+       // "list one" mode
+       if (which_one > 0) {
+               if (which_one > POP3->num_msgs) {
+                       cprintf("-ERR no such message, only %d are here\r\n", POP3->num_msgs);
+                       return;
+               }
+               else if (POP3->msgs[which_one-1].deleted) {
+                       cprintf("-ERR Sorry, you deleted that message.\r\n");
+                       return;
+               }
+               else {
+                       cprintf("+OK %d %ld\r\n", which_one, (long)POP3->msgs[which_one-1].rfc822_length);
+                       return;
+               }
+       }
+
+       // "list all" (scan listing) mode
+       else {
+               cprintf("+OK Here's your mail:\r\n");
+               if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
+                       if (! POP3->msgs[i].deleted) {
+                               cprintf("%d %ld\r\n", i+1, (long)POP3->msgs[i].rfc822_length);
+                       }
+               }
+               cprintf(".\r\n");
+       }
+}
+
+
+// STAT (tally up the total message count and byte count) command
+void pop3_stat(char *argbuf) {
+       int total_msgs = 0;
+       size_t total_octets = 0;
+       int i;
+       
+       if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
+               if (! POP3->msgs[i].deleted) {
+                       ++total_msgs;
+                       total_octets += POP3->msgs[i].rfc822_length;
+               }
+       }
+
+       cprintf("+OK %d %ld\r\n", total_msgs, (long)total_octets);
+}
+
+
+// RETR command (fetch a message)
+void pop3_retr(char *argbuf) {
+       int which_one;
+
+       which_one = atoi(argbuf);
+       if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
+               cprintf("-ERR No such message.\r\n");
+               return;
+       }
+
+       if (POP3->msgs[which_one - 1].deleted) {
+               cprintf("-ERR Sorry, you deleted that message.\r\n");
+               return;
+       }
+
+       cprintf("+OK Message %d:\r\n", which_one);
+       CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum, MT_RFC822,
+               HEADERS_ALL, 0, 1, NULL,
+               (ESC_DOT|SUPPRESS_ENV_TO), NULL, NULL, NULL
+       );
+       cprintf(".\r\n");
+}
+
+
+// TOP command (dumb way of fetching a partial message or headers-only)
+void pop3_top(char *argbuf) {
+       int which_one;
+       int lines_requested = 0;
+       int lines_dumped = 0;
+       char buf[1024];
+       StrBuf *msgtext;
+       const char *ptr;
+       int in_body = 0;
+       int done = 0;
+
+       sscanf(argbuf, "%d %d", &which_one, &lines_requested);
+       if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
+               cprintf("-ERR No such message.\r\n");
+               return;
+       }
+
+       if (POP3->msgs[which_one - 1].deleted) {
+               cprintf("-ERR Sorry, you deleted that message.\r\n");
+               return;
+       }
+
+       CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+
+       CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum,
+                     MT_RFC822,
+                     HEADERS_ALL,
+                     0, 1, NULL,
+                     SUPPRESS_ENV_TO,
+                     NULL, NULL, NULL);
+
+       msgtext = CC->redirect_buffer;
+       CC->redirect_buffer = NULL;
+
+       cprintf("+OK Message %d:\r\n", which_one);
+       
+       ptr = ChrPtr(msgtext);
+       while (ptr = cmemreadline(ptr, buf, (sizeof buf - 2)),
+             ( (*ptr != 0) && (done == 0))) {
+               strcat(buf, "\r\n");
+               if (in_body == 1) {
+                       if (lines_dumped >= lines_requested) {
+                               done = 1;
+                       }
+               }
+               if ((in_body == 0) || (done == 0)) {
+                       client_write(buf, strlen(buf));
+               }
+               if (in_body) {
+                       ++lines_dumped;
+               }
+               if ((buf[0]==13)||(buf[0]==10)) in_body = 1;
+       }
+
+       if (buf[strlen(buf)-1] != 10) cprintf("\n");
+       FreeStrBuf(&msgtext);
+
+       cprintf(".\r\n");
+}
+
+
+// DELE (delete message from mailbox)
+void pop3_dele(char *argbuf) {
+       int which_one;
+
+       which_one = atoi(argbuf);
+       if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
+               cprintf("-ERR No such message.\r\n");
+               return;
+       }
+
+       if (POP3->msgs[which_one - 1].deleted) {
+               cprintf("-ERR You already deleted that message.\r\n");
+               return;
+       }
+
+       // Flag the message as deleted.  Will expunge during QUIT command.
+       POP3->msgs[which_one - 1].deleted = 1;
+       cprintf("+OK Message %d deleted.\r\n",
+               which_one);
+}
+
+
+// Perform "UPDATE state" stuff
+void pop3_update(void) {
+       int i;
+        visit vbuf;
+
+       long *deletemsgs = NULL;
+       int num_deletemsgs = 0;
+
+       // Remove messages marked for deletion
+       if (POP3->num_msgs > 0) {
+               deletemsgs = malloc(POP3->num_msgs * sizeof(long));
+               for (i=0; i<POP3->num_msgs; ++i) {
+                       if (POP3->msgs[i].deleted) {
+                               deletemsgs[num_deletemsgs++] = POP3->msgs[i].msgnum;
+                       }
+               }
+               if (num_deletemsgs > 0) {
+                       CtdlDeleteMessages(MAILROOM, deletemsgs, num_deletemsgs, "");
+               }
+               free(deletemsgs);
+       }
+
+       // Set last read pointer
+       if (POP3->num_msgs > 0) {
+               CtdlLockGetCurrentUser();
+               CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+               snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld", POP3->msgs[POP3->num_msgs-1].msgnum);
+               CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
+               CtdlPutCurrentUserLock();
+       }
+
+}
+
+
+// RSET (reset, i.e. undelete any deleted messages) command
+void pop3_rset(char *argbuf) {
+       int i;
+
+       if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
+               if (POP3->msgs[i].deleted) {
+                       POP3->msgs[i].deleted = 0;
+               }
+       }
+       cprintf("+OK Reset completed.\r\n");
+}
+
+
+// LAST (Determine which message is the last unread message)
+void pop3_last(char *argbuf) {
+       cprintf("+OK %d\r\n", POP3->lastseen + 1);
+}
+
+
+// CAPA is a command which tells the client which POP3 extensions are supported.
+void pop3_capa(void) {
+       cprintf("+OK Capability list follows\r\n"
+               "TOP\r\n"
+               "USER\r\n"
+               "UIDL\r\n"
+               "IMPLEMENTATION %s\r\n"
+               ".\r\n"
+               ,
+               CITADEL
+       );
+}
+
+
+// UIDL (Universal IDentifier Listing) is easy.  Our 'unique' message
+// identifiers are simply the Citadel message numbers in the database.
+void pop3_uidl(char *argbuf) {
+       int i;
+       int which_one;
+
+       which_one = atoi(argbuf);
+
+       // "list one" mode
+       if (which_one > 0) {
+               if (which_one > POP3->num_msgs) {
+                       cprintf("-ERR no such message, only %d are here\r\n", POP3->num_msgs);
+                       return;
+               }
+               else if (POP3->msgs[which_one-1].deleted) {
+                       cprintf("-ERR Sorry, you deleted that message.\r\n");
+                       return;
+               }
+               else {
+                       cprintf("+OK %d %ld\r\n", which_one, POP3->msgs[which_one-1].msgnum);
+                       return;
+               }
+       }
+
+       // "list all" (scan listing) mode
+       else {
+               cprintf("+OK Here's your mail:\r\n");
+               if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
+                       if (! POP3->msgs[i].deleted) {
+                               cprintf("%d %ld\r\n", i+1, POP3->msgs[i].msgnum);
+                       }
+               }
+               cprintf(".\r\n");
+       }
+}
+
+
+// implements the STLS command (Citadel API version)
+void pop3_stls(void) {
+       char ok_response[SIZ];
+       char nosup_response[SIZ];
+       char error_response[SIZ];
+
+       sprintf(ok_response,    "+OK Begin TLS negotiation now\r\n");
+       sprintf(nosup_response, "-ERR TLS not supported here\r\n");
+       sprintf(error_response, "-ERR Internal error\r\n");
+       CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
+}
+
+
+// Main command loop for POP3 sessions.
+void pop3_command_loop(void) {
+       char cmdbuf[SIZ];
+
+       time(&CC->lastcmd);
+       memset(cmdbuf, 0, sizeof cmdbuf);                       // Clear it, just in case
+       if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
+               syslog(LOG_INFO, "pop3: client disconnected; ending session.");
+               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
+               return;
+       }
+       if (!strncasecmp(cmdbuf, "PASS", 4)) {
+               syslog(LOG_DEBUG, "pop3: PASS...");
+       }
+       else {
+               syslog(LOG_DEBUG, "pop3: %s", cmdbuf);
+       }
+       while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
+
+       if (!strncasecmp(cmdbuf, "NOOP", 4)) {
+               cprintf("+OK No operation.\r\n");
+       }
+
+       else if (!strncasecmp(cmdbuf, "CAPA", 4)) {
+               pop3_capa();
+       }
+
+       else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
+               cprintf("+OK Goodbye...\r\n");
+               pop3_update();
+               CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
+               return;
+       }
+
+       else if (!strncasecmp(cmdbuf, "USER", 4)) {
+               pop3_user(&cmdbuf[5]);
+       }
+
+       else if (!strncasecmp(cmdbuf, "PASS", 4)) {
+               pop3_pass(&cmdbuf[5]);
+       }
+
+#ifdef HAVE_OPENSSL
+       else if (!strncasecmp(cmdbuf, "STLS", 4)) {
+               pop3_stls();
+       }
+#endif
+
+       else if (!CC->logged_in) {
+               cprintf("-ERR Not logged in.\r\n");
+       }
+       
+       else if (CC->nologin) {
+               cprintf("-ERR System busy, try later.\r\n");
+               CC->kill_me = KILLME_NOLOGIN;
+       }
+
+       else if (!strncasecmp(cmdbuf, "LIST", 4)) {
+               pop3_list(&cmdbuf[5]);
+       }
+
+       else if (!strncasecmp(cmdbuf, "STAT", 4)) {
+               pop3_stat(&cmdbuf[5]);
+       }
+
+       else if (!strncasecmp(cmdbuf, "RETR", 4)) {
+               pop3_retr(&cmdbuf[5]);
+       }
+
+       else if (!strncasecmp(cmdbuf, "DELE", 4)) {
+               pop3_dele(&cmdbuf[5]);
+       }
+
+       else if (!strncasecmp(cmdbuf, "RSET", 4)) {
+               pop3_rset(&cmdbuf[5]);
+       }
+
+       else if (!strncasecmp(cmdbuf, "UIDL", 4)) {
+               pop3_uidl(&cmdbuf[5]);
+       }
+
+       else if (!strncasecmp(cmdbuf, "TOP", 3)) {
+               pop3_top(&cmdbuf[4]);
+       }
+
+       else if (!strncasecmp(cmdbuf, "LAST", 4)) {
+               pop3_last(&cmdbuf[4]);
+       }
+
+       else {
+               cprintf("-ERR I'm afraid I can't do that.\r\n");
+       }
+
+}
+
+const char *CitadelServicePop3="POP3";
+const char *CitadelServicePop3S="POP3S";
+
+
+char *ctdl_module_init_pop3(void) {
+       if (!threading) {
+               CtdlRegisterServiceHook(CtdlGetConfigInt("c_pop3_port"),
+                                       NULL,
+                                       pop3_greeting,
+                                       pop3_command_loop,
+                                       NULL,
+                                       CitadelServicePop3);
+#ifdef HAVE_OPENSSL
+               CtdlRegisterServiceHook(CtdlGetConfigInt("c_pop3s_port"),
+                                       NULL,
+                                       pop3s_greeting,
+                                       pop3_command_loop,
+                                       NULL,
+                                       CitadelServicePop3S);
+#endif
+               CtdlRegisterSessionHook(pop3_cleanup_function, EVT_STOP, PRIO_STOP + 30);
+       }
+       
+       /* return our module name for the log */
+       return "pop3";
+}
diff --git a/citadel/server/modules/pop3/serv_pop3.h b/citadel/server/modules/pop3/serv_pop3.h
new file mode 100644 (file)
index 0000000..e10213c
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 1998-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+struct pop3msg {
+       long msgnum;
+       size_t rfc822_length;
+       int deleted;
+};
+
+struct citpop3 {               /* Information about the current session */
+       struct pop3msg *msgs;   /* Array of message pointers */
+       int num_msgs;           /* Number of messages in array */
+       int lastseen;           /* Offset of last-read message in array */
+};
+                               /* Note: the "lastseen" is represented as the
+                                * offset in this array (zero-based), so when
+                                * displaying it to a POP3 client, it must be
+                                * incremented by one.
+                                */
+
+#define POP3 ((struct citpop3 *)CC->session_specific_data)
+
+void pop3_cleanup_function(void);
+void pop3_greeting(void);
+void pop3_user(char *argbuf);
+void pop3_pass(char *argbuf);
+void pop3_list(char *argbuf);
+void pop3_command_loop(void);
+void pop3_login(void);
+
diff --git a/citadel/server/modules/pop3client/.gitignore b/citadel/server/modules/pop3client/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/pop3client/serv_pop3client.c b/citadel/server/modules/pop3client/serv_pop3client.c
new file mode 100644 (file)
index 0000000..d8fc927
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ * Consolidate mail from remote POP3 accounts.
+ *
+ * Copyright (c) 2007-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <time.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <curl/curl.h>
+#include <libcitadel.h>
+#include "../../sysconfig.h"
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../ctdl_module.h"
+#include "../../clientsocket.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../database.h"
+#include "../../citadel_dirs.h"
+
+struct p3cq {                          // module-local queue of pop3 client work that needs processing
+       struct p3cq *next;
+       char *room;
+       char *host;
+       char *user;
+       char *pass;
+       int keep;
+       long interval;
+};
+
+static int doing_pop3client = 0;
+struct p3cq *p3cq = NULL;
+
+/*
+ * Process one mailbox.
+ */
+void pop3client_one_mailbox(char *room, const char *host, const char *user, const char *pass, int keep, long interval) {
+       syslog(LOG_DEBUG, "pop3client: room=<%s> host=<%s> user=<%s> keep=<%d> interval=<%ld>", room, host, user, keep, interval);
+
+       char url[SIZ];
+       CURL *curl;
+       CURLcode res = CURLE_OK;
+       StrBuf *Uidls = NULL;
+       int i;
+       char cmd[1024];
+
+       curl = curl_easy_init();
+       if (!curl) {
+               return;
+       }
+
+       Uidls = NewStrBuf();
+
+       curl_easy_setopt(curl, CURLOPT_USERNAME, user);
+       curl_easy_setopt(curl, CURLOPT_PASSWORD, pass);
+       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
+       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
+       curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15);
+       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback); // What to do with downloaded data
+       curl_easy_setopt(curl, CURLOPT_WRITEDATA, Uidls);                       // Give it our StrBuf to work with
+       curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "UIDL");
+
+       /* Try POP3S (SSL encrypted) first */
+       snprintf(url, sizeof url, "pop3s://%s", host);
+       curl_easy_setopt(curl, CURLOPT_URL, url);
+       res = curl_easy_perform(curl);
+       if (res == CURLE_OK) {
+       }
+       else {
+               syslog(LOG_DEBUG, "pop3client: POP3S connection failed: %s , trying POP3 next", curl_easy_strerror(res));
+               snprintf(url, sizeof url, "pop3://%s", host);                   // try unencrypted next
+               curl_easy_setopt(curl, CURLOPT_URL, url);
+               FlushStrBuf(Uidls);
+               res = curl_easy_perform(curl);
+       }
+
+       if (res != CURLE_OK) {
+               syslog(LOG_DEBUG, "pop3client: POP3 connection failed: %s", curl_easy_strerror(res));
+               curl_easy_cleanup(curl);
+               FreeStrBuf(&Uidls);
+               return;
+       }
+
+       // If we got this far, a connection was established, we know whether it's pop3s or pop3, and UIDL is supported.
+       // Now go through the UIDL list and look for messages.
+
+       int num_msgs = num_tokens(ChrPtr(Uidls), '\n');
+       syslog(LOG_DEBUG, "pop3client: there are %d messages", num_msgs);
+       for (i=0; i<num_msgs; ++i) {
+               char oneuidl[1024];
+               extract_token(oneuidl, ChrPtr(Uidls), i, '\n', sizeof oneuidl);
+               if (strlen(oneuidl) > 2) {
+                       if (oneuidl[strlen(oneuidl)-1] == '\r') {
+                               oneuidl[strlen(oneuidl)-1] = 0;
+                       }
+                       int this_msg = atoi(oneuidl);
+                       char *c = strchr(oneuidl, ' ');
+                       if (c) strcpy(oneuidl, ++c);
+
+                       // Make up the Use Table record so we can check if we've already seen this message.
+                       StrBuf *UT = NewStrBuf();
+                       StrBufPrintf(UT, "pop3/%s/%s:%s@%s", room, oneuidl, user, host);
+                       int already_seen = CheckIfAlreadySeen(UT);
+                       FreeStrBuf(&UT);
+
+                       // Only fetch the message if we haven't seen it before.
+                       if (already_seen == 0) {
+                               StrBuf *TheMsg = NewStrBuf();
+                               snprintf(cmd, sizeof cmd, "RETR %d", this_msg);
+                               curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, cmd);
+                               curl_easy_setopt(curl, CURLOPT_WRITEDATA, TheMsg);
+                               res = curl_easy_perform(curl);
+                               if (res == CURLE_OK) {
+                                       struct CtdlMessage *msg = convert_internet_message_buf(&TheMsg);
+                                       CtdlSubmitMsg(msg, NULL, room);
+                                       CM_Free(msg);
+                               }
+                               else {
+                                       FreeStrBuf(&TheMsg);
+                               }
+
+                               // Unless the configuration says to keep the message on the server, delete it.
+                               if (keep == 0) {
+                                       snprintf(cmd, sizeof cmd, "DELE %d", this_msg);
+                                       curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, cmd);
+                                       res = curl_easy_perform(curl);
+                               }
+                       }
+                       else {
+                               syslog(LOG_DEBUG, "pop3client: %s has already been retrieved", oneuidl);
+                       }
+               }
+       }
+
+       curl_easy_cleanup(curl);
+       FreeStrBuf(&Uidls);
+       return;
+}
+
+
+// Scan a room's netconfig looking for RSS feed parsing requests
+//
+void pop3client_scan_room(struct ctdlroom *qrbuf, void *data)
+{
+       char *serialized_config = NULL;
+       int num_configs = 0;
+       char cfgline[SIZ];
+       char cfgelement[SIZ];
+       int i = 0;
+
+        serialized_config = LoadRoomNetConfigFile(qrbuf->QRnumber);
+        if (!serialized_config) {
+               return;
+       }
+
+       num_configs = num_tokens(serialized_config, '\n');
+       for (i=0; i<num_configs; ++i) {
+               extract_token(cfgline, serialized_config, i, '\n', sizeof cfgline);
+               if (!strncasecmp(cfgline, HKEY("pop3client|"))) {
+                       struct p3cq *pptr = malloc(sizeof(struct p3cq));
+                       pptr->next = p3cq;
+                       p3cq = pptr;
+                       p3cq->room = strdup(qrbuf->QRname);
+                       extract_token(cfgelement, cfgline, 1, '|', sizeof cfgelement);
+                       p3cq->host = strdup(cfgelement);
+                       extract_token(cfgelement, cfgline, 2, '|', sizeof cfgelement);
+                       p3cq->user = strdup(cfgelement);
+                       extract_token(cfgelement, cfgline, 3, '|', sizeof cfgelement);
+                       p3cq->pass = strdup(cfgelement);
+                       p3cq->keep = extract_int(cfgline, 4);
+                       p3cq->interval = extract_long(cfgline, 5);
+               }
+       }
+
+       free(serialized_config);
+}
+
+
+void pop3client_scan(void) {
+       static time_t last_run = 0L;
+       time_t fastest_scan;
+       struct p3cq *pptr = NULL;
+
+       if (CtdlGetConfigLong("c_pop3_fastest") < CtdlGetConfigLong("c_pop3_fetch")) {
+               fastest_scan = CtdlGetConfigLong("c_pop3_fastest");
+       }
+       else {
+               fastest_scan = CtdlGetConfigLong("c_pop3_fetch");
+       }
+
+       /*
+        * Run POP3 aggregation no more frequently than once every n seconds
+        */
+       if ( (time(NULL) - last_run) < fastest_scan ) {
+               return;
+       }
+
+       /*
+        * This is a simple concurrency check to make sure only one pop3client
+        * run is done at a time.  We could do this with a mutex, but since we
+        * don't really require extremely fine granularity here, we'll do it
+        * with a static variable instead.
+        */
+       if (doing_pop3client) return;
+       doing_pop3client = 1;
+
+       syslog(LOG_DEBUG, "pop3client: scan started");
+       CtdlForEachRoom(pop3client_scan_room, NULL);
+
+       /*
+        * We have to queue and process in separate phases, otherwise we leave a cursor open
+        */
+       syslog(LOG_DEBUG, "pop3client: processing started");
+       while (p3cq != NULL) {
+               pptr = p3cq;
+               p3cq = p3cq->next;
+
+               pop3client_one_mailbox(pptr->room, pptr->host, pptr->user, pptr->pass, pptr->keep, pptr->interval);
+
+               free(pptr->room);
+               free(pptr->host);
+               free(pptr->user);
+               free(pptr->pass);
+               free(pptr);
+       }
+
+       syslog(LOG_DEBUG, "pop3client: ended");
+       last_run = time(NULL);
+       doing_pop3client = 0;
+}
+
+
+char *ctdl_module_init_pop3client(void) {
+       if (!threading) {
+               CtdlRegisterSessionHook(pop3client_scan, EVT_TIMER, PRIO_AGGR + 50);
+       }
+
+       /* return our module id for the log */
+       return "pop3client";
+}
diff --git a/citadel/server/modules/roomchat/.gitignore b/citadel/server/modules/roomchat/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/roomchat/serv_roomchat.c b/citadel/server/modules/roomchat/serv_roomchat.c
new file mode 100644 (file)
index 0000000..4f3a71e
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * This module handles instant messaging between users.
+ * 
+ * Copyright (c) 2012-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../msgbase.h"
+#include "../../user_ops.h"
+#include "../../ctdl_module.h"
+
+struct chatmsg {
+       struct chatmsg *next;
+       time_t timestamp;
+       int seq;
+       long roomnum;
+       char *sender;
+       char *msgtext;
+};
+
+struct chatmsg *first_chat_msg = NULL;
+struct chatmsg *last_chat_msg = NULL;
+
+
+/* 
+ * Periodically called for housekeeping.  Expire old chat messages so they don't take up memory forever.
+ */
+void roomchat_timer(void) {
+       struct chatmsg *ptr;
+
+       begin_critical_section(S_CHATQUEUE);
+
+       while ((first_chat_msg != NULL) && ((time(NULL) - first_chat_msg->timestamp) > 300)) {
+               ptr = first_chat_msg->next;
+               free(first_chat_msg->sender);
+               free(first_chat_msg->msgtext);
+               free(first_chat_msg);
+               first_chat_msg = ptr;
+               if (first_chat_msg == NULL) {
+                       last_chat_msg = NULL;
+               }
+       }
+
+       end_critical_section(S_CHATQUEUE);
+}
+
+
+/*
+ * Perform shutdown-related activities...
+ */
+void roomchat_shutdown(void) {
+       /* if we ever start logging chats, we have to flush them to disk here .*/
+}
+
+
+/*
+ * Add a message into the chat queue
+ */
+void add_to_chat_queue(char *msg) {
+       static int seq = 0;
+
+       struct chatmsg *m = malloc(sizeof(struct chatmsg));
+       if (!m) return;
+
+       m->next = NULL;
+       m->timestamp = time(NULL);
+       m->roomnum = CC->room.QRnumber;
+       m->sender = strdup(CC->user.fullname);
+       m->msgtext = strdup(msg);
+
+       if ((m->sender == NULL) || (m->msgtext == NULL)) {
+               free(m->sender);
+               free(m->msgtext);
+               free(m);
+               return;
+       }
+
+       begin_critical_section(S_CHATQUEUE);
+       m->seq = ++seq;
+
+       if (first_chat_msg == NULL) {
+               assert(last_chat_msg == NULL);
+               first_chat_msg = m;
+               last_chat_msg = m;
+       }
+       else {
+               assert(last_chat_msg != NULL);
+               assert(last_chat_msg->next == NULL);
+               last_chat_msg->next = m;
+               last_chat_msg = m;
+       }
+
+       end_critical_section(S_CHATQUEUE);
+}
+
+
+/*
+ * Transmit a message into a room chat
+ */
+void roomchat_send(char *argbuf) {
+       char buf[1024];
+
+       if ((CC->cs_flags & CS_CHAT) == 0) {
+               cprintf("%d Session is not in chat mode.\n", ERROR);
+               return;
+       }
+
+       cprintf("%d send now\n", SEND_LISTING);
+       while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
+               add_to_chat_queue(buf);
+       }
+}
+
+
+/*
+ * Poll room for incoming chat messages
+ */
+void roomchat_poll(char *argbuf) {
+       int newer_than = 0;
+       struct chatmsg *found = NULL;
+       struct chatmsg *ptr = NULL;
+
+       newer_than = extract_int(argbuf, 1);
+
+       if ((CC->cs_flags & CS_CHAT) == 0) {
+               cprintf("%d Session is not in chat mode.\n", ERROR);
+               return;
+       }
+
+       begin_critical_section(S_CHATQUEUE);
+       for (ptr = first_chat_msg; ((ptr != NULL) && (found == NULL)); ptr = ptr->next) {
+               if ((ptr->seq > newer_than) && (ptr->roomnum == CC->room.QRnumber)) {
+                       found = ptr;
+               }
+       }
+       end_critical_section(S_CHATQUEUE);
+
+       if (found == NULL) {
+               cprintf("%d no messages\n", ERROR + MESSAGE_NOT_FOUND);
+               return;
+       }
+
+       cprintf("%d %d|%ld|%s\n", LISTING_FOLLOWS, found->seq, found->timestamp, found->sender);
+       cprintf("%s\n", found->msgtext);
+       cprintf("000\n");
+}
+
+
+
+/*
+ * list users in chat in this room
+ */
+void roomchat_rwho(char *argbuf) {
+       struct CitContext *nptr;
+       int nContexts, i;
+
+       if ((CC->cs_flags & CS_CHAT) == 0) {
+               cprintf("%d Session is not in chat mode.\n", ERROR);
+               return;
+       }
+
+       cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() );
+       
+       nptr = CtdlGetContextArray(&nContexts) ;                // grab a copy of the wholist
+       if (nptr) {
+               for (i=0; i<nContexts; i++)  {                  // list the users
+                       if ( (nptr[i].room.QRnumber == CC->room.QRnumber) 
+                          && (nptr[i].cs_flags & CS_CHAT)
+                       ) {
+                               cprintf("%s\n", nptr[i].user.fullname);
+                       }
+               }
+               free(nptr);                                     // free our copy
+       }
+
+       cprintf("000\n");
+}
+
+
+
+/*
+ * Participate in real time chat in a room
+ */
+void cmd_rcht(char *argbuf)
+{
+       char subcmd[16];
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
+
+       if (!strcasecmp(subcmd, "enter")) {
+               CC->cs_flags |= CS_CHAT;
+               cprintf("%d Entering chat mode.\n", CIT_OK);
+       }
+       else if (!strcasecmp(subcmd, "exit")) {
+               CC->cs_flags &= ~CS_CHAT;
+               cprintf("%d Exiting chat mode.\n", CIT_OK);
+       }
+       else if (!strcasecmp(subcmd, "send")) {
+               roomchat_send(argbuf);
+       }
+       else if (!strcasecmp(subcmd, "poll")) {
+               roomchat_poll(argbuf);
+       }
+       else if (!strcasecmp(subcmd, "rwho")) {
+               roomchat_rwho(argbuf);
+       }
+       else {
+               cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
+       }
+}
+
+
+char *ctdl_module_init_roomchat(void) {
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_rcht, "RCHT", "Participate in real time chat in a room");
+               CtdlRegisterSessionHook(roomchat_timer, EVT_TIMER, PRIO_CLEANUP + 400);
+               CtdlRegisterSessionHook(roomchat_shutdown, EVT_SHUTDOWN, PRIO_SHUTDOWN + 55);
+       }
+       
+       /* return our module name for the log */
+       return "roomchat";
+}
diff --git a/citadel/server/modules/rssclient/.gitignore b/citadel/server/modules/rssclient/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/rssclient/serv_rssclient.c b/citadel/server/modules/rssclient/serv_rssclient.c
new file mode 100644 (file)
index 0000000..e23463a
--- /dev/null
@@ -0,0 +1,460 @@
+/*
+ * Bring external RSS and/or Atom feeds into rooms.  This module implements a
+ * very loose parser that scrapes both kinds of feeds and is not picky about
+ * the standards compliance of the source data.
+ *
+ * Copyright (c) 2007-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <time.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <expat.h>
+#include <curl/curl.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../threads.h"
+#include "../../ctdl_module.h"
+#include "../../msgbase.h"
+#include "../../parsedate.h"
+#include "../../database.h"
+#include "../../citadel_dirs.h"
+#include "../../context.h"
+#include "../../internet_addressing.h"
+
+struct rssroom {
+       struct rssroom *next;
+       char *room;
+};
+
+struct rssurl {
+       struct rssurl *next;
+       char *url;
+       struct rssroom *rooms;
+};
+
+struct rssparser {
+       StrBuf *CData;
+       struct CtdlMessage *msg;
+       char *link;
+       char *description;
+       char *item_id;
+       struct rssroom *rooms;
+};
+
+time_t last_run = 0L;
+struct rssurl *rsstodo = NULL;
+
+
+// This handler is called whenever an XML tag opens.
+//
+void rss_start_element(void *data, const char *el, const char **attribute) {
+       struct rssparser *r = (struct rssparser *)data;
+       int i;
+
+       if (server_shutting_down) return;                       // shunt the whole operation if we're exiting
+
+       if (
+               (!strcasecmp(el, "entry"))
+               || (!strcasecmp(el, "item"))
+       ) {
+               // this is the start of a new item(rss) or entry(atom)
+               if (r->msg != NULL) {
+                       CM_Free(r->msg);
+                       r->msg = NULL;
+               }
+               r->msg = malloc(sizeof(struct CtdlMessage));
+               memset(r->msg, 0, sizeof(struct CtdlMessage));
+               r->msg->cm_magic = CTDLMESSAGE_MAGIC;
+               r->msg->cm_anon_type = MES_NORMAL;
+               r->msg->cm_format_type = FMT_RFC822;
+       }
+
+       else if (!strcasecmp(el, "link")) {                     // atom feeds have the link as an attribute
+               for(i = 0; attribute[i]; i += 2) {
+                       if (!strcasecmp(attribute[i], "href")) {
+                               if (r->link != NULL) {
+                                       free(r->link);
+                                       r->link = NULL;
+                               }
+                               r->link = strdup(attribute[i+1]);
+                               striplt(r->link);
+                       }
+               }
+       }
+}
+
+
+// This handler is called whenever an XML tag closes.
+//
+void rss_end_element(void *data, const char *el) {
+       struct rssparser *r = (struct rssparser *)data;
+       StrBuf *encoded_field;
+
+       if (server_shutting_down) return;                       // shunt the whole operation if we're exiting
+
+       if (StrLength(r->CData) > 0) {                          // strip leading/trailing whitespace from field
+               StrBufTrim(r->CData);
+       }
+
+       if (                                                    // end of a new item(rss) or entry(atom)
+               (!strcasecmp(el, "entry"))
+               || (!strcasecmp(el, "item"))
+       ) {
+               if (r->msg != NULL) {                           // Save the message to the rooms
+
+                       // use the link as an item id if nothing else is available
+                       if ((r->item_id == NULL) && (r->link != NULL)) {
+                               r->item_id = strdup(r->link);
+                       }
+
+                       // check the use table
+                       StrBuf *u = NewStrBuf();
+                       StrBufAppendPrintf(u, "rss/%s", r->item_id);
+                       int already_seen = CheckIfAlreadySeen(u);
+                       FreeStrBuf(&u);
+
+                       if (already_seen == 0) {
+
+                               // Compose the message text
+                               StrBuf *TheMessage = NewStrBuf();
+                               StrBufAppendPrintf(TheMessage,
+                                       "Content-type: text/html\n\n"
+                                       "\n\n"
+                                       "<html><head></head><body>"
+                               );
+               
+                               if (r->description != NULL) {
+                                       StrBufAppendPrintf(TheMessage, "%s<br><br>\r\n", r->description);
+                                       free(r->description);
+                                       r->description = NULL;
+                               }
+               
+                               if (r->link != NULL) {
+                                       StrBufAppendPrintf(TheMessage, "<a href=\"%s\">%s</a>\r\n", r->link, r->link);
+                                       free(r->link);
+                                       r->link = NULL;
+                               }
+       
+                               StrBufAppendPrintf(TheMessage, "</body></html>\r\n");
+                               CM_SetField(r->msg, eMesageText, ChrPtr(TheMessage), StrLength(TheMessage));
+                               FreeStrBuf(&TheMessage);
+       
+                               if (CM_IsEmpty(r->msg, eAuthor)) {
+                                       CM_SetField(r->msg, eAuthor, HKEY("rss"));
+                               }
+       
+                               if (CM_IsEmpty(r->msg, eTimestamp)) {
+                                       CM_SetFieldLONG(r->msg, eTimestamp, time(NULL));
+                               }
+       
+                               // Save it to the room(s)
+                               struct rssroom *rr = NULL;
+                               long msgnum = (-1);
+                               for (rr=r->rooms; rr!=NULL; rr=rr->next) {
+                                       if (rr == r->rooms) {
+                                               msgnum = CtdlSubmitMsg(r->msg, NULL, rr->room);         // in first room, save msg
+                                       }
+                                       else {
+                                               CtdlSaveMsgPointerInRoom(rr->room, msgnum, 0, NULL);    // elsewhere, save a pointer
+                                       }
+                                       syslog(LOG_DEBUG, "rssclient: saved message %ld to %s", msgnum, rr->room);
+                               }
+                       }
+                       else {
+                               syslog(LOG_DEBUG, "rssclient: already seen %s", r->item_id);
+                       }
+       
+                       CM_Free(r->msg);
+                       r->msg = NULL;
+               }
+
+               if (r->item_id != NULL) {
+                       free(r->item_id);
+                       r->item_id = NULL;
+               }
+       }
+
+       else if (!strcasecmp(el, "title")) {                    // item subject (rss and atom)
+               if ((r->msg != NULL) && (CM_IsEmpty(r->msg, eMsgSubject))) {
+                       encoded_field = NewStrBuf();
+                       StrBufRFC2047encode(&encoded_field, r->CData);
+                       CM_SetAsFieldSB(r->msg, eMsgSubject, &encoded_field);
+               }
+       }
+
+       else if (!strcasecmp(el, "creator")) {                  // <creator> can be used if <author> is not present
+               if ((r->msg != NULL) && (CM_IsEmpty(r->msg, eAuthor))) {
+                       encoded_field = NewStrBuf();
+                       StrBufRFC2047encode(&encoded_field, r->CData);
+                       CM_SetAsFieldSB(r->msg, eAuthor, &encoded_field);
+               }
+       }
+
+       else if (!strcasecmp(el, "author")) {                   // <author> supercedes <creator> if both are present
+               if (r->msg != NULL) {
+                       encoded_field = NewStrBuf();
+                       StrBufRFC2047encode(&encoded_field, r->CData);
+                       CM_SetAsFieldSB(r->msg, eAuthor, &encoded_field);
+               }
+       }
+
+       else if (!strcasecmp(el, "pubdate")) {                  // date/time stamp (rss) Sat, 25 Feb 2017 14:28:01 EST
+               if ((r->msg)&&(r->msg->cm_fields[eTimestamp]==NULL)) {
+                       CM_SetFieldLONG(r->msg, eTimestamp, parsedate(ChrPtr(r->CData)));
+               }
+       }
+
+       else if (!strcasecmp(el, "updated")) {                  // date/time stamp (atom) 2003-12-13T18:30:02Z
+               if ((r->msg)&&(r->msg->cm_fields[eTimestamp]==NULL)) {
+                       struct tm t;
+                       char zulu;
+                       memset(&t, 0, sizeof t);
+                       sscanf(ChrPtr(r->CData), "%d-%d-%dT%d:%d:%d%c", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec, &zulu);
+                       t.tm_year -= 1900;
+                       t.tm_mon -= 1;
+                       CM_SetFieldLONG(r->msg, eTimestamp, mktime(&t));
+               }
+       }
+
+       else if (!strcasecmp(el, "link")) {                     // link to story (rss)
+               if (r->link != NULL) {
+                       free(r->link);
+                       r->link = NULL;
+               }
+               r->link = strdup(ChrPtr(r->CData));
+       }
+
+       else if (
+               (!strcasecmp(el, "guid"))                       // unique item id (rss)
+               || (!strcasecmp(el, "id"))                      // unique item id (atom)
+       ) {
+               if (r->item_id != NULL) {
+                       free(r->item_id);
+                       r->item_id = NULL;
+               }
+               r->item_id = strdup(ChrPtr(r->CData));
+       }
+
+       else if (
+               (!strcasecmp(el, "description"))                // message text (rss)
+               || (!strcasecmp(el, "summary"))                 // message text (atom)
+               || (!strcasecmp(el, "content"))                 // message text (atom)
+       ) {
+               if (r->description != NULL) {
+                       free(r->description);
+                       r->description = NULL;
+               }
+               r->description = strdup(ChrPtr(r->CData));
+       }
+
+       if (r->CData != NULL) {
+               FreeStrBuf(&r->CData);
+               r->CData = NULL;
+       }
+}
+
+
+// This handler is called whenever data appears between opening and closing tags.
+//
+void rss_handle_data(void *data, const char *content, int length)
+{
+       struct rssparser *r = (struct rssparser *)data;
+
+       if (r->CData == NULL) {
+               r->CData = NewStrBuf();
+       }
+
+       StrBufAppendBufPlain(r->CData, content, length, 0);
+}
+
+
+// Feed has been downloaded, now parse it.
+//
+void rss_parse_feed(StrBuf *Feed, struct rssroom *rooms)
+{
+       struct rssparser r;
+
+       memset(&r, 0, sizeof r);
+       r.rooms = rooms;
+       XML_Parser p = XML_ParserCreate("UTF-8");
+       XML_SetElementHandler(p, rss_start_element, rss_end_element);
+       XML_SetCharacterDataHandler(p, rss_handle_data);
+       XML_SetUserData(p, (void *)&r);
+       XML_Parse(p, ChrPtr(Feed), StrLength(Feed), XML_TRUE);
+       XML_ParserFree(p);
+}
+
+
+// Add a feed/room pair into the todo list
+//
+void rssclient_push_todo(char *rssurl, char *roomname)
+{
+       struct rssurl *r = NULL;
+       struct rssurl *thisone = NULL;
+       struct rssroom *newroom = NULL;
+
+       syslog(LOG_DEBUG, "rssclient: will fetch %s to %s", rssurl, roomname);
+
+       for (r=rsstodo; r!=NULL; r=r->next) {
+               if (!strcasecmp(r->url, rssurl)) {
+                       thisone = r;
+               }
+       }
+
+       if (thisone == NULL) {
+               thisone = malloc(sizeof(struct rssurl));
+               thisone->url = strdup(rssurl);
+               thisone->rooms = NULL;
+               thisone->next = rsstodo;
+               rsstodo = thisone;
+       }
+
+       newroom = malloc(sizeof(struct rssroom));
+       newroom->room = strdup(roomname);
+       newroom->next = thisone->rooms;
+       thisone->rooms = newroom;
+}
+
+
+// pull one feed (possibly multiple rooms)
+//
+void rss_pull_one_feed(struct rssurl *url)
+{
+       CURL *curl;
+       CURLcode res;
+       StrBuf *Downloaded = NULL;
+
+       syslog(LOG_DEBUG, "rssclient: fetching %s", url->url);
+
+       curl = curl_easy_init();
+       if (!curl) {
+               return;
+       }
+
+       Downloaded = NewStrBuf();
+
+       curl_easy_setopt(curl, CURLOPT_URL, url->url);
+       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
+       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
+       curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);                     // Follow redirects
+       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback); // What to do with downloaded data
+       curl_easy_setopt(curl, CURLOPT_WRITEDATA, Downloaded);                  // Give it our StrBuf to work with
+       curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L);                           // Time out after 20 seconds
+       res = curl_easy_perform(curl);                                          // Perform the request
+       if (res != CURLE_OK) {
+               syslog(LOG_WARNING, "rssclient: failed to load feed: %s", curl_easy_strerror(res));
+       }
+       curl_easy_cleanup(curl);
+
+       rss_parse_feed(Downloaded, url->rooms);                                 // parse the feed
+       FreeStrBuf(&Downloaded);                                                // free the downloaded feed data
+}
+
+
+// We have a list, now download the feeds
+//
+void rss_pull_feeds(void)
+{
+       struct rssurl *r;
+       struct rssroom *rr;
+
+       while ((rsstodo != NULL) && (!server_shutting_down)) {
+               rss_pull_one_feed(rsstodo);
+               r = rsstodo;
+               rsstodo = rsstodo->next;
+               while (r->rooms != NULL) {
+                       rr = r->rooms;
+                       r->rooms = r->rooms->next;
+                       free(rr->room);
+                       free(rr);
+               }
+               free(r->url);
+               free(r);
+       }
+}
+
+
+// Scan a room's netconfig looking for RSS feed parsing requests
+//
+void rssclient_scan_room(struct ctdlroom *qrbuf, void *data)
+{
+       char *serialized_config = NULL;
+       int num_configs = 0;
+       char cfgline[SIZ];
+       int i = 0;
+
+       if (server_shutting_down) return;
+
+        serialized_config = LoadRoomNetConfigFile(qrbuf->QRnumber);
+        if (!serialized_config) {
+               return;
+       }
+
+       num_configs = num_tokens(serialized_config, '\n');
+       for (i=0; i<num_configs; ++i) {
+               extract_token(cfgline, serialized_config, i, '\n', sizeof cfgline);
+               if (!strncasecmp(cfgline, HKEY("rssclient|"))) {
+                       strcpy(cfgline, &cfgline[10]);
+                       char *vbar = strchr(cfgline, '|');
+                       if (vbar != NULL) {
+                               *vbar = 0;
+                       }
+                       rssclient_push_todo(cfgline, qrbuf->QRname);
+               }
+       }
+
+       free(serialized_config);
+}
+
+
+/*
+ * Scan for rooms that have RSS client requests configured
+ */
+void rssclient_scan(void) {
+       time_t now = time(NULL);
+
+       /* Run no more than once every 15 minutes. */
+       if ((now - last_run) < 900) {
+               syslog(LOG_DEBUG,
+                       "rssclient: polling interval not yet reached; last run was %ldm%lds ago",
+                       ((now - last_run) / 60),
+                       ((now - last_run) % 60)
+               );
+               return;
+       }
+
+       syslog(LOG_DEBUG, "rssclient: started");
+       CtdlForEachRoom(rssclient_scan_room, NULL);
+       rss_pull_feeds();
+       syslog(LOG_DEBUG, "rssclient: ended");
+       last_run = time(NULL);
+       return;
+}
+
+
+char *ctdl_module_init_rssclient(void) {
+       if (!threading) {
+               syslog(LOG_INFO, "rssclient: using %s", curl_version());
+               CtdlRegisterSessionHook(rssclient_scan, EVT_TIMER, PRIO_AGGR + 300);
+       }
+       return "rssclient";
+}
diff --git a/citadel/server/modules/rwho/.gitignore b/citadel/server/modules/rwho/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/rwho/serv_rwho.c b/citadel/server/modules/rwho/serv_rwho.c
new file mode 100644 (file)
index 0000000..ef1b7da
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * This module implements server commands related to the display and
+ * manipulation of the "Who's online" list.
+ *
+ * Copyright (c) 1987-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../ctdl_module.h"
+
+/* Don't show the names of private rooms unless the viewing
+ * user also knows the rooms.
+ */
+void GenerateRoomDisplay(char *real_room,
+                       CitContext *viewed,
+                       CitContext *viewer) {
+
+       int ra;
+
+       strcpy(real_room, viewed->room.QRname);
+       if (viewed->room.QRflags & QR_MAILBOX) {
+               strcpy(real_room, &real_room[11]);
+       }
+       if (viewed->room.QRflags & QR_PRIVATE) {
+               CtdlRoomAccess(&viewed->room, &viewer->user, &ra, NULL);
+               if ( (ra & UA_KNOWN) == 0) {
+                       strcpy(real_room, " ");
+               }
+       }
+
+       if (viewed->cs_flags & CS_CHAT) {
+               while (strlen(real_room) < 14) {
+                       strcat(real_room, " ");
+               }
+               strcpy(&real_room[14], "<chat>");
+       }
+
+}
+
+
+
+/*
+ * display who's online
+ */
+void cmd_rwho(char *argbuf) {
+       struct CitContext *nptr;
+       int nContexts, i;
+       int spoofed = 0;
+       int aide;
+       char room[ROOMNAMELEN];
+       char flags[5];
+       
+       /* So that we don't keep the context list locked for a long time
+        * we create a copy of it first
+        */
+       nptr = CtdlGetContextArray(&nContexts) ;
+       if (!nptr)
+       {
+               /* Couldn't malloc so we have to bail but stick to the protocol */
+               cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() );
+               cprintf("000\n");
+               return;
+       }
+       
+       aide = ( (CC->user.axlevel >= AxAideU) || (CC->internal_pgm) ) ;
+       cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() );
+       
+       for (i=0; i<nContexts; i++)  {
+               flags[0] = '\0';
+               spoofed = 0;
+               
+               if (!aide && nptr[i].state == CON_SYS)
+                       continue;
+
+               if (!aide && nptr[i].kill_me != 0)
+                       continue;
+
+               if (nptr[i].cs_flags & CS_POSTING) {
+                       strcat(flags, "*");
+               }
+               else {
+                       strcat(flags, ".");
+               }
+                  
+               GenerateRoomDisplay(room, &nptr[i], CC);
+
+                if ((aide) && (spoofed)) {
+                       strcat(flags, "+");
+               }
+               
+               if ((nptr[i].cs_flags & CS_STEALTH) && (aide)) {
+                       strcat(flags, "-");
+               }
+               
+               if (((nptr[i].cs_flags&CS_STEALTH)==0) || (aide)) {
+
+                       cprintf("%d|%s|%s|%s|%s|%ld|%s|%s|",
+                               nptr[i].cs_pid, nptr[i].curr_user, room,
+                               nptr[i].cs_host, nptr[i].cs_clientname,
+                               (long)(nptr[i].lastidle),
+                               nptr[i].lastcmdname, flags
+                       );
+
+                       cprintf("|");   // no spoofed user names anymore
+                       cprintf("|");   // no spoofed room names anymore
+                       cprintf("|");   // no spoofed host names anymore
+       
+                       cprintf("%d\n", nptr[i].logged_in);
+               }
+       }
+       
+       /* release our copy of the context list */
+       free(nptr);
+
+       /* Now it's magic time.  Before we finish, call any EVT_RWHO hooks
+        * so that external paging modules such as serv_icq can add more
+        * content to the Wholist.
+        */
+       PerformSessionHooks(EVT_RWHO);
+       cprintf("000\n");
+}
+
+
+/*
+ * enter or exit "stealth mode"
+ */
+void cmd_stel(char *cmdbuf)
+{
+       int requested_mode;
+
+       requested_mode = extract_int(cmdbuf,0);
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       if (requested_mode == 1) {
+               CC->cs_flags = CC->cs_flags | CS_STEALTH;
+               PerformSessionHooks(EVT_STEALTH);
+       }
+       if (requested_mode == 0) {
+               CC->cs_flags = CC->cs_flags & ~CS_STEALTH;
+               PerformSessionHooks(EVT_UNSTEALTH);
+       }
+
+       cprintf("%d %d\n", CIT_OK,
+               ((CC->cs_flags & CS_STEALTH) ? 1 : 0) );
+}
+
+
+char *ctdl_module_init_rwho(void) {
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_rwho, "RWHO", "Display who is online");
+               CtdlRegisterProtoHook(cmd_stel, "STEL", "Enter/exit stealth mode");
+               //CtdlRegisterSessionHook(dead_io_check, EVT_TIMER, PRIO_QUEUE + 50);
+
+       }
+       
+       /* return our module name for the log */
+        return "rwho";
+}
diff --git a/citadel/server/modules/smtp/.gitignore b/citadel/server/modules/smtp/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/smtp/serv_smtp.c b/citadel/server/modules/smtp/serv_smtp.c
new file mode 100644 (file)
index 0000000..3a89d4e
--- /dev/null
@@ -0,0 +1,1065 @@
+// This module is an SMTP and ESMTP server for the Citadel system.
+// It is compliant with all of the following:
+//
+// RFC  821 - Simple Mail Transfer Protocol
+// RFC  876 - Survey of SMTP Implementations
+// RFC 1047 - Duplicate messages and SMTP
+// RFC 1652 - 8 bit MIME
+// RFC 1869 - Extended Simple Mail Transfer Protocol
+// RFC 1870 - SMTP Service Extension for Message Size Declaration
+// RFC 2033 - Local Mail Transfer Protocol
+// RFC 2197 - SMTP Service Extension for Command Pipelining
+// RFC 2476 - Message Submission
+// RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
+// RFC 2554 - SMTP Service Extension for Authentication
+// RFC 2821 - Simple Mail Transfer Protocol
+// RFC 2822 - Internet Message Format
+// RFC 2920 - SMTP Service Extension for Command Pipelining
+//  
+// The VRFY and EXPN commands have been removed from this implementation
+// because nobody uses these commands anymore, except for spammers.
+//
+// Copyright (c) 1998-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//  
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <syslog.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../room_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../genstamp.h"
+#include "../../domain.h"
+#include "../../clientsocket.h"
+#include "../../locate_host.h"
+#include "../../citadel_dirs.h"
+#include "../../ctdl_module.h"
+
+#include "smtp_util.h"
+
+enum {                         // Command states for login authentication
+       smtp_command,
+       smtp_user,
+       smtp_password,
+       smtp_plain
+};
+
+enum SMTP_FLAGS {
+       HELO,
+       EHLO,
+       LHLO
+};
+
+
+// Here's where our SMTP session begins its happy day.
+void smtp_greeting(int is_msa) {
+       char message_to_spammer[1024];
+
+       strcpy(CC->cs_clientname, "SMTP session");
+       CC->internal_pgm = 1;
+       CC->cs_flags |= CS_STEALTH;
+       CC->session_specific_data = malloc(sizeof(struct citsmtp));
+       memset(SMTP, 0, sizeof(struct citsmtp));
+       SMTP->is_msa = is_msa;
+       SMTP->Cmd = NewStrBufPlain(NULL, SIZ);
+       SMTP->helo_node = NewStrBuf();
+       SMTP->from = NewStrBufPlain(NULL, SIZ);
+       SMTP->recipients = NewStrBufPlain(NULL, SIZ);
+       SMTP->OneRcpt = NewStrBufPlain(NULL, SIZ);
+       SMTP->preferred_sender_email = NULL;
+       SMTP->preferred_sender_name = NULL;
+
+       // If this config option is set, reject connections from problem
+       // addresses immediately instead of after they execute a RCPT
+       if ( (CtdlGetConfigInt("c_rbl_at_greeting")) && (SMTP->is_msa == 0) ) {
+               if (rbl_check(CC->cs_addr, message_to_spammer)) {
+                       if (server_shutting_down)
+                               cprintf("421 %s\r\n", message_to_spammer);
+                       else
+                               cprintf("550 %s\r\n", message_to_spammer);
+                       CC->kill_me = KILLME_SPAMMER;
+                       /* no need to free_recipients(valid), it's not allocated yet */
+                       return;
+               }
+       }
+
+       // Otherwise we're either clean or we check later.
+
+       if (CC->nologin==1) {
+               cprintf("451 Too many connections are already open; please try again later.\r\n");
+               CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED;
+               // no need to free_recipients(valid), it's not allocated yet
+               return;
+       }
+
+       // Note: the FQDN *must* appear as the first thing after the 220 code.
+       // Some clients (including citmail.c) depend on it being there.
+       cprintf("220 %s ESMTP Citadel server ready.\r\n", CtdlGetConfigStr("c_fqdn"));
+}
+
+
+// SMTPS is just like SMTP, except it goes crypto right away.
+void smtps_greeting(void) {
+       CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
+#ifdef HAVE_OPENSSL
+       if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;          // kill session if no crypto
+#endif
+       smtp_greeting(0);
+}
+
+
+// SMTP MSA port requires authentication.
+void smtp_msa_greeting(void) {
+       smtp_greeting(1);
+}
+
+
+// LMTP is like SMTP but with some extra bonus footage added.
+void lmtp_greeting(void) {
+
+       smtp_greeting(0);
+       SMTP->is_lmtp = 1;
+}
+
+
+// Generic SMTP MTA greeting
+void smtp_mta_greeting(void) {
+       smtp_greeting(0);
+}
+
+
+// We also have an unfiltered LMTP socket that bypasses spam filters.
+void lmtp_unfiltered_greeting(void) {
+       smtp_greeting(0);
+       SMTP->is_lmtp = 1;
+       SMTP->is_unfiltered = 1;
+}
+
+
+// Login greeting common to all auth methods
+void smtp_auth_greeting(void) {
+       cprintf("235 Hello, %s\r\n", CC->user.fullname);
+       syslog(LOG_INFO, "serv_smtp: SMTP authenticated %s", CC->user.fullname);
+       CC->internal_pgm = 0;
+       CC->cs_flags &= ~CS_STEALTH;
+}
+
+
+// Implement HELO and EHLO commands.
+// which_command:  0=HELO, 1=EHLO, 2=LHLO
+void smtp_hello(int which_command) {
+
+       if (StrLength(SMTP->Cmd) >= 6) {
+               FlushStrBuf(SMTP->helo_node);
+               StrBufAppendBuf(SMTP->helo_node, SMTP->Cmd, 5);
+       }
+
+       if ( (which_command != LHLO) && (SMTP->is_lmtp) ) {
+               cprintf("500 Only LHLO is allowed when running LMTP\r\n");
+               return;
+       }
+
+       if ( (which_command == LHLO) && (SMTP->is_lmtp == 0) ) {
+               cprintf("500 LHLO is only allowed when running LMTP\r\n");
+               return;
+       }
+
+       if (which_command == HELO) {
+               cprintf("250 Hello %s (%s [%s])\r\n",
+                       ChrPtr(SMTP->helo_node),
+                       CC->cs_host,
+                       CC->cs_addr
+               );
+       }
+       else {
+               if (which_command == EHLO) {
+                       cprintf("250-Hello %s (%s [%s])\r\n",
+                               ChrPtr(SMTP->helo_node),
+                               CC->cs_host,
+                               CC->cs_addr
+                       );
+               }
+               else {
+                       cprintf("250-Greetings and joyous salutations.\r\n");
+               }
+               cprintf("250-HELP\r\n");
+               cprintf("250-SIZE %ld\r\n", CtdlGetConfigLong("c_maxmsglen"));
+
+#ifdef HAVE_OPENSSL
+               // Offer TLS, but only if TLS is not already active.
+               // Furthermore, only offer TLS when running on
+               // the SMTP-MSA port, not on the SMTP-MTA port, due to
+               // questionable reliability of TLS in certain sending MTA's.
+               if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
+                       cprintf("250-STARTTLS\r\n");
+               }
+#endif
+
+               cprintf("250-AUTH LOGIN PLAIN\r\n"
+                       "250-AUTH=LOGIN PLAIN\r\n"
+                       "250 8BITMIME\r\n"
+               );
+       }
+}
+
+
+// Backend function for smtp_webcit_preferences_hack().
+// Look at a message and determine if it's the preferences file.
+void smtp_webcit_preferences_hack_backend(long msgnum, void *userdata) {
+       struct CtdlMessage *msg;
+       char **webcit_conf = (char **) userdata;
+
+       if (*webcit_conf) {
+               return;         // already got it
+       }
+
+       msg = CtdlFetchMessage(msgnum, 1);
+       if (msg == NULL) {
+               return;
+       }
+
+       if ( !CM_IsEmpty(msg, eMsgSubject) && (!strcasecmp(msg->cm_fields[eMsgSubject], "__ WebCit Preferences __"))) {
+               // This is it!  Change ownership of the message text so it doesn't get freed.
+               *webcit_conf = (char *)msg->cm_fields[eMesageText];
+               msg->cm_fields[eMesageText] = NULL;
+       }
+       CM_Free(msg);
+}
+
+
+// The configuration item for the user's preferred display name for outgoing email is, unfortunately,
+// stored in the account's WebCit configuration.  We have to fetch it now.
+void smtp_webcit_preferences_hack(void) {
+       char config_roomname[ROOMNAMELEN];
+       char *webcit_conf = NULL;
+
+       snprintf(config_roomname, sizeof config_roomname, "%010ld.%s", CC->user.usernum, USERCONFIGROOM);
+       if (CtdlGetRoom(&CC->room, config_roomname) != 0) {
+               return;
+       }
+
+       // Find the WebCit configuration message
+       CtdlForEachMessage(MSGS_ALL, 1, NULL, NULL, NULL, smtp_webcit_preferences_hack_backend, (void *)&webcit_conf);
+
+       if (!webcit_conf) {
+               return;
+       }
+
+       // Parse the webcit configuration and attempt to do something useful with it
+       char *str = webcit_conf;
+       char *saveptr = str;
+       char *this_line = NULL;
+       while (this_line = strtok_r(str, "\n", &saveptr), this_line != NULL) {
+               str = NULL;
+               if (!strncasecmp(this_line, "defaultfrom|", 12)) {
+                       SMTP->preferred_sender_email = NewStrBufPlain(&this_line[12], -1);
+               }
+               if (!strncasecmp(this_line, "defaultname|", 12)) {
+                       SMTP->preferred_sender_name = NewStrBufPlain(&this_line[12], -1);
+               }
+               if ((!strncasecmp(this_line, "defaultname|", 12)) && (SMTP->preferred_sender_name == NULL)) {
+                       SMTP->preferred_sender_name = NewStrBufPlain(&this_line[12], -1);
+               }
+
+       }
+       free(webcit_conf);
+}
+
+
+// Implement HELP command.
+void smtp_help(void) {
+       cprintf("214 RTFM http://www.ietf.org/rfc/rfc2821.txt\r\n");
+}
+
+
+void smtp_get_user(int offset) {
+       char buf[SIZ];
+
+       StrBuf *UserName = NewStrBufDup(SMTP->Cmd);
+       StrBufCutLeft(UserName, offset);
+       StrBufDecodeBase64(UserName);
+
+       if (CtdlLoginExistingUser(ChrPtr(UserName)) == login_ok) {
+               size_t len = CtdlEncodeBase64(buf, "Password:", 9, 0);
+
+               if (buf[len - 1] == '\n') {
+                       buf[len - 1] = '\0';
+               }
+               cprintf("334 %s\r\n", buf);
+               SMTP->command_state = smtp_password;
+       }
+       else {
+               cprintf("500 No such user.\r\n");
+               SMTP->command_state = smtp_command;
+       }
+       FreeStrBuf(&UserName);
+}
+
+
+void smtp_get_pass(void) {
+       char password[SIZ];
+
+       memset(password, 0, sizeof(password));
+       StrBufDecodeBase64(SMTP->Cmd);
+       syslog(LOG_DEBUG, "serv_smtp: trying <%s>", password);
+       if (CtdlTryPassword(SKEY(SMTP->Cmd)) == pass_ok) {
+               smtp_auth_greeting();
+       }
+       else {
+               cprintf("535 Authentication failed.\r\n");
+       }
+       SMTP->command_state = smtp_command;
+}
+
+
+// Back end for PLAIN auth method (either inline or multistate)
+void smtp_try_plain(void) {
+       const char*decoded_authstring;
+       char ident[256] = "";
+       char user[256] = "";
+       char pass[256] = "";
+       int result;
+
+       long decoded_len;
+       long len = 0;
+       long plen = 0;
+
+       memset(pass, 0, sizeof(pass));
+       decoded_len = StrBufDecodeBase64(SMTP->Cmd);
+
+       if (decoded_len > 0) {
+               decoded_authstring = ChrPtr(SMTP->Cmd);
+
+               len = safestrncpy(ident, decoded_authstring, sizeof ident);
+
+               decoded_len -= len - 1;
+               decoded_authstring += len + 1;
+
+               if (decoded_len > 0) {
+                       len = safestrncpy(user, decoded_authstring, sizeof user);
+
+                       decoded_authstring += len + 1;
+                       decoded_len -= len - 1;
+               }
+
+               if (decoded_len > 0) {
+                       plen = safestrncpy(pass, decoded_authstring, sizeof pass);
+
+                       if (plen < 0)
+                               plen = sizeof(pass) - 1;
+               }
+       }
+
+       SMTP->command_state = smtp_command;
+
+       if (!IsEmptyStr(ident)) {
+               result = CtdlLoginExistingUser(ident);
+       }
+       else {
+               result = CtdlLoginExistingUser(user);
+       }
+
+       if (result == login_ok) {
+               if (CtdlTryPassword(pass, plen) == pass_ok) {
+                       smtp_webcit_preferences_hack();
+                       smtp_auth_greeting();
+                       return;
+               }
+       }
+       cprintf("504 Authentication failed.\r\n");
+}
+
+
+// Attempt to perform authenticated SMTP
+void smtp_auth(void) {
+       char username_prompt[64];
+       char method[64];
+       char encoded_authstring[1024];
+
+       if (CC->logged_in) {
+               cprintf("504 Already logged in.\r\n");
+               return;
+       }
+
+       if (StrLength(SMTP->Cmd) < 6) {
+               cprintf("501 Syntax error\r\n");
+               return;
+       }
+
+       extract_token(method, ChrPtr(SMTP->Cmd) + 5, 0, ' ', sizeof method);
+
+       if (!strncasecmp(method, "login", 5) ) {
+               if (StrLength(SMTP->Cmd) >= 12) {
+                       syslog(LOG_DEBUG, "serv_smtp: username <%s> supplied inline", ChrPtr(SMTP->Cmd)+11);
+                       smtp_get_user(11);
+               }
+               else {
+                       size_t len = CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
+                       if (username_prompt[len - 1] == '\n') {
+                               username_prompt[len - 1] = '\0';
+                       }
+                       cprintf("334 %s\r\n", username_prompt);
+                       SMTP->command_state = smtp_user;
+               }
+               return;
+       }
+
+       if (!strncasecmp(method, "plain", 5) ) {
+               long len;
+               if (num_tokens(ChrPtr(SMTP->Cmd) + 5, ' ') < 2) {
+                       cprintf("334 \r\n");
+                       SMTP->command_state = smtp_plain;
+                       return;
+               }
+
+               len = extract_token(encoded_authstring, 
+                                   ChrPtr(SMTP->Cmd) + 5,
+                                   1, ' ',
+                                   sizeof encoded_authstring);
+               StrBufPlain(SMTP->Cmd, encoded_authstring, len);
+               smtp_try_plain();
+               return;
+       }
+
+       cprintf("504 Unknown authentication method.\r\n");
+       return;
+}
+
+
+// Implements the RSET (reset state) command.
+// Currently this just zeroes out the state buffer.  If pointers to data
+// allocated with malloc() are ever placed in the state buffer, we have to
+// be sure to free() them first!
+//
+// Set do_response to nonzero to output the SMTP RSET response code.
+void smtp_rset(int do_response) {
+       FlushStrBuf(SMTP->Cmd);
+       FlushStrBuf(SMTP->helo_node);
+       FlushStrBuf(SMTP->from);
+       FlushStrBuf(SMTP->recipients);
+       FlushStrBuf(SMTP->OneRcpt);
+
+       SMTP->command_state = 0;
+       SMTP->number_of_recipients = 0;
+       SMTP->delivery_mode = 0;
+       SMTP->message_originated_locally = 0;
+       SMTP->is_msa = 0;
+       // is_lmtp and is_unfiltered should not be cleared. 
+
+       if (do_response) {
+               cprintf("250 Zap!\r\n");
+       }
+}
+
+
+// Clear out the portions of the state buffer that need to be cleared out
+// after the DATA command finishes.
+void smtp_data_clear(void) {
+       FlushStrBuf(SMTP->from);
+       FlushStrBuf(SMTP->recipients);
+       FlushStrBuf(SMTP->OneRcpt);
+       SMTP->number_of_recipients = 0;
+       SMTP->delivery_mode = 0;
+       SMTP->message_originated_locally = 0;
+}
+
+
+// Implements the "MAIL FROM:" command
+void smtp_mail(void) {
+       char user[SIZ];
+       char node[SIZ];
+       char name[SIZ];
+
+       if (StrLength(SMTP->from) > 0) {
+               cprintf("503 Only one sender permitted\r\n");
+               return;
+       }
+
+       if (StrLength(SMTP->Cmd) < 6) {
+               cprintf("501 Syntax error\r\n");
+               return;
+       }
+
+       if (strncasecmp(ChrPtr(SMTP->Cmd) + 5, "From:", 5)) {
+               cprintf("501 Syntax error\r\n");
+               return;
+       }
+
+       StrBufAppendBuf(SMTP->from, SMTP->Cmd, 5);
+       StrBufTrim(SMTP->from);
+       if (strchr(ChrPtr(SMTP->from), '<') != NULL) {
+               StrBufStripAllBut(SMTP->from, '<', '>');
+       }
+
+       // We used to reject empty sender names, until it was brought to our
+       // attention that RFC1123 5.2.9 requires that this be allowed.  So now
+       // we allow it, but replace the empty string with a fake
+       // address so we don't have to contend with the empty string causing
+       // other code to fail when it's expecting something there.
+       if (StrLength(SMTP->from) == 0) {
+               StrBufPlain(SMTP->from, HKEY("someone@example.com"));
+       }
+
+       // If this SMTP connection is from a logged-in user, force the 'from'
+       // to be the user's Internet e-mail address as Citadel knows it.
+       if (CC->logged_in) {
+               StrBufPlain(SMTP->from, CC->cs_inet_email, -1);
+               cprintf("250 Sender ok <%s>\r\n", ChrPtr(SMTP->from));
+               SMTP->message_originated_locally = 1;
+               return;
+       }
+
+       else if (SMTP->is_lmtp) {
+               // Bypass forgery checking for LMTP
+       }
+
+       // Otherwise, make sure outsiders aren't trying to forge mail from
+       // this system (unless, of course, c_allow_spoofing is enabled)
+       else if (CtdlGetConfigInt("c_allow_spoofing") == 0) {
+               process_rfc822_addr(ChrPtr(SMTP->from), user, node, name);
+               syslog(LOG_DEBUG, "serv_smtp: claimed envelope sender is '%s' == '%s' @ '%s' ('%s')",
+                       ChrPtr(SMTP->from), user, node, name
+               );
+               if (CtdlHostAlias(node) != hostalias_nomatch) {
+                       cprintf("550 You must log in to send mail from %s\r\n", node);
+                       FlushStrBuf(SMTP->from);
+                       syslog(LOG_DEBUG, "serv_smtp: rejecting unauthenticated mail from %s", node);
+                       return;
+               }
+       }
+
+       cprintf("250 Sender ok\r\n");
+}
+
+
+// Implements the "RCPT To:" command
+void smtp_rcpt(void) {
+       char message_to_spammer[SIZ];
+       struct recptypes *valid = NULL;
+
+       if (StrLength(SMTP->from) == 0) {
+               cprintf("503 Need MAIL before RCPT\r\n");
+               return;
+       }
+       
+       if (StrLength(SMTP->Cmd) < 6) {
+               cprintf("501 Syntax error\r\n");
+               return;
+       }
+
+       if (strncasecmp(ChrPtr(SMTP->Cmd) + 5, "To:", 3)) {
+               cprintf("501 Syntax error\r\n");
+               return;
+       }
+
+       if ( (SMTP->is_msa) && (!CC->logged_in) ) {
+               cprintf("550 You must log in to send mail on this port.\r\n");
+               FlushStrBuf(SMTP->from);
+               return;
+       }
+
+       FlushStrBuf(SMTP->OneRcpt);
+       StrBufAppendBuf(SMTP->OneRcpt, SMTP->Cmd, 8);
+       StrBufTrim(SMTP->OneRcpt);
+       StrBufStripAllBut(SMTP->OneRcpt, '<', '>');
+
+       if ( (StrLength(SMTP->OneRcpt) + StrLength(SMTP->recipients)) >= SIZ) {
+               cprintf("452 Too many recipients\r\n");
+               return;
+       }
+
+       // RBL check
+       if (
+               (!CC->logged_in)                                                // Don't RBL authenticated users
+               && (!SMTP->is_lmtp)                                             // Don't RBL LMTP clients
+               && (CtdlGetConfigInt("c_rbl_at_greeting") == 0)                 // Don't RBL if we did it at connection time
+               && (rbl_check(CC->cs_addr, message_to_spammer))
+       ) {
+               cprintf("550 %s\r\n", message_to_spammer);
+               return;                                                         // no need to free_recipients(valid)
+       }                                                                       // because it hasn't been allocated yet
+
+       // This is a *preliminary* call to validate_recipients() to evaluate one recipient.
+       valid = validate_recipients(
+               (char *)ChrPtr(SMTP->OneRcpt), 
+               smtp_get_Recipients(),
+               (SMTP->is_lmtp)? POST_LMTP: (CC->logged_in)? POST_LOGGED_IN: POST_EXTERNAL
+       );
+
+       // Any type of error thrown by validate_recipients() will make the SMTP transaction fail at this point.
+       if (valid->num_error != 0) {
+               cprintf("550 %s\r\n", valid->errormsg);
+               free_recipients(valid);
+               return;
+       }
+
+       if (
+               (valid->num_internet > 0)                                       // If it's outbound Internet mail...
+               && (CC->logged_in)                                              // ...and we're a logged-in user...
+               && (CtdlCheckInternetMailPermission(&CC->user)==0)              // ...who does not have Internet mail rights...
+       ) {
+               cprintf("551 <%s> - you do not have permission to send Internet mail\r\n", ChrPtr(SMTP->OneRcpt));
+               free_recipients(valid);
+               return;
+       }
+
+       if (
+               (valid->num_internet > 0)                                       // If it's outbound Internet mail...
+               && (SMTP->message_originated_locally == 0)                      // ...and also inbound Internet mail...
+               && (SMTP->is_lmtp == 0)                                         /// ...and didn't arrive via LMTP...
+       ) {
+               cprintf("551 <%s> - relaying denied\r\n", ChrPtr(SMTP->OneRcpt));
+               free_recipients(valid);
+               return;
+       }
+
+       if (
+               (valid->num_room > 0)                                           // If it's mail to a room (mailing list)...
+               && (SMTP->message_originated_locally == 0)                      // ...and also inbound Internet mail...
+               && (is_email_subscribed_to_list((char *)ChrPtr(SMTP->from), valid->recp_room) == 0)     // ...and not a subscriber
+       ) {
+               cprintf("551 <%s> - The message is not from a list member\r\n", ChrPtr(SMTP->OneRcpt));
+               free_recipients(valid);
+               return;
+       }
+
+       cprintf("250 RCPT ok <%s>\r\n", ChrPtr(SMTP->OneRcpt));
+       if (StrLength(SMTP->recipients) > 0) {
+               StrBufAppendBufPlain(SMTP->recipients, HKEY(","), 0);
+       }
+       StrBufAppendBuf(SMTP->recipients, SMTP->OneRcpt, 0);
+       SMTP->number_of_recipients ++;
+       if (valid != NULL)  {
+               free_recipients(valid);
+       }
+}
+
+
+// Implements the DATA command
+void smtp_data(void) {
+       StrBuf *body;
+       StrBuf *defbody; 
+       struct CtdlMessage *msg = NULL;
+       long msgnum = (-1L);
+       char nowstamp[SIZ];
+       struct recptypes *valid;
+       int scan_errors;
+       int i;
+
+       if (StrLength(SMTP->from) == 0) {
+               cprintf("503 Need MAIL command first.\r\n");
+               return;
+       }
+
+       if (SMTP->number_of_recipients < 1) {
+               cprintf("503 Need RCPT command first.\r\n");
+               return;
+       }
+
+       cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
+       
+       datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
+       defbody = NewStrBufPlain(NULL, SIZ);
+
+       if (defbody != NULL) {
+               if (SMTP->is_lmtp && (CC->cs_UDSclientUID != -1)) {
+                       StrBufPrintf(
+                               defbody,
+                               "Received: from %s (Citadel from userid %ld)\n"
+                               "       by %s; %s\n",
+                               ChrPtr(SMTP->helo_node),
+                               (long int) CC->cs_UDSclientUID,
+                               CtdlGetConfigStr("c_fqdn"),
+                               nowstamp);
+               }
+               else {
+                       StrBufPrintf(
+                               defbody,
+                               "Received: from %s (%s [%s])\n"
+                               "       by %s; %s\n",
+                               ChrPtr(SMTP->helo_node),
+                               CC->cs_host,
+                               CC->cs_addr,
+                               CtdlGetConfigStr("c_fqdn"),
+                               nowstamp);
+               }
+       }
+       body = CtdlReadMessageBodyBuf(HKEY("."), CtdlGetConfigLong("c_maxmsglen"), defbody, 1);
+       FreeStrBuf(&defbody);
+       if (body == NULL) {
+               cprintf("550 Unable to save message: internal error.\r\n");
+               return;
+       }
+
+       syslog(LOG_DEBUG, "serv_smtp: converting message...");
+       msg = convert_internet_message_buf(&body);
+
+       // If the user is locally authenticated, FORCE the From: header to
+       // show up as the real sender.  Yes, this violates the RFC standard,
+       // but IT MAKES SENSE.  If you prefer strict RFC adherence over
+       // common sense, you can disable this in the configuration.
+       //
+       // We also set the "message room name" ('O' field) to MAILROOM
+       // (which is Mail> on most systems) to prevent it from getting set
+       // to something ugly like "0000058008.Sent Items>" when the message
+       // is read with a Citadel client.
+
+       if ( (CC->logged_in) && (CtdlGetConfigInt("c_rfc822_strict_from") != CFG_SMTP_FROM_NOFILTER) ) {
+               int validemail = 0;
+               
+               if (!CM_IsEmpty(msg, erFc822Addr)       &&
+                   ((CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_CORRECT) || 
+                    (CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_REJECT)    )  )
+               {
+                       if (!IsEmptyStr(CC->cs_inet_email))
+                               validemail = strcmp(CC->cs_inet_email, msg->cm_fields[erFc822Addr]) == 0;
+                       if ((!validemail) && 
+                           (!IsEmptyStr(CC->cs_inet_other_emails)))
+                       {
+                               int num_secondary_emails = 0;
+                               int i;
+                               num_secondary_emails = num_tokens(CC->cs_inet_other_emails, '|');
+                               for (i=0; i < num_secondary_emails && !validemail; ++i) {
+                                       char buf[256];
+                                       extract_token(buf, CC->cs_inet_other_emails,i,'|',sizeof CC->cs_inet_other_emails);
+                                       validemail = strcmp(buf, msg->cm_fields[erFc822Addr]) == 0;
+                               }
+                       }
+               }
+
+               if (!validemail && (CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_REJECT)) {
+                       syslog(LOG_ERR, "serv_smtp: invalid sender '%s' - rejecting this message", msg->cm_fields[erFc822Addr]);
+                       cprintf("550 Invalid sender '%s' - rejecting this message.\r\n", msg->cm_fields[erFc822Addr]);
+                       return;
+               }
+
+               CM_SetField(msg, eOriginalRoom, HKEY(MAILROOM));
+               if (SMTP->preferred_sender_name != NULL)
+                       CM_SetField(msg, eAuthor, SKEY(SMTP->preferred_sender_name));
+               else 
+                       CM_SetField(msg, eAuthor, CC->user.fullname, strlen(CC->user.fullname));
+
+               if (!validemail) {
+                       if (SMTP->preferred_sender_email != NULL) {
+                               CM_SetField(msg, erFc822Addr, SKEY(SMTP->preferred_sender_email));
+                       }
+                       else {
+                               CM_SetField(msg, erFc822Addr, CC->cs_inet_email, strlen(CC->cs_inet_email));
+                       }
+               }
+       }
+
+       // Set the "envelope from" address
+       CM_SetField(msg, eMessagePath, SKEY(SMTP->from));
+
+       // Set the "envelope to" address
+       CM_SetField(msg, eenVelopeTo, SKEY(SMTP->recipients));
+
+       // Submit the message into the Citadel system.
+       valid = validate_recipients(
+               (char *)ChrPtr(SMTP->recipients),
+               smtp_get_Recipients(),
+               (SMTP->is_lmtp)? POST_LMTP: (CC->logged_in)? POST_LOGGED_IN: POST_EXTERNAL
+       );
+
+       // If there are modules that want to scan this message before final
+       // submission (such as virus checkers or spam filters), call them now
+       // and give them an opportunity to reject the message.
+       if (SMTP->is_unfiltered) {
+               scan_errors = 0;
+       }
+       else {
+               scan_errors = PerformMessageHooks(msg, valid, EVT_SMTPSCAN);
+       }
+
+       if (scan_errors > 0) {  // We don't want this message!
+
+               if (CM_IsEmpty(msg, eErrorMsg)) {
+                       CM_SetField(msg, eErrorMsg, HKEY("Message rejected by filter"));
+               }
+
+               StrBufPrintf(SMTP->OneRcpt, "550 %s\r\n", msg->cm_fields[eErrorMsg]);
+       }
+       
+       else {                  // Ok, we'll accept this message.
+               msgnum = CtdlSubmitMsg(msg, valid, "");
+               if (msgnum > 0L) {
+                       StrBufPrintf(SMTP->OneRcpt, "250 Message accepted.\r\n");
+               }
+               else {
+                       StrBufPrintf(SMTP->OneRcpt, "550 Internal delivery error\r\n");
+               }
+       }
+
+       // For SMTP and ESMTP, just print the result message.  For LMTP, we
+       // have to print one result message for each recipient.  Since there
+       // is nothing in Citadel which would cause different recipients to
+       // have different results, we can get away with just spitting out the
+       // same message once for each recipient.
+       if (SMTP->is_lmtp) {
+               for (i=0; i<SMTP->number_of_recipients; ++i) {
+                       cputbuf(SMTP->OneRcpt);
+               }
+       }
+       else {
+               cputbuf(SMTP->OneRcpt);
+       }
+
+       // Write something to the syslog(which may or may not be where the
+       // rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
+       syslog((LOG_MAIL | LOG_INFO),
+                   "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
+                   msgnum,
+                   ChrPtr(SMTP->from),
+                   SMTP->number_of_recipients,
+                   CC->cs_host,
+                   CC->cs_addr,
+                   ChrPtr(SMTP->OneRcpt)
+       );
+
+       // Clean up
+       CM_Free(msg);
+       free_recipients(valid);
+       smtp_data_clear();      // clear out the buffers now
+}
+
+
+// Implements the STARTTLS command
+void smtp_starttls(void) {
+       char ok_response[SIZ];
+       char nosup_response[SIZ];
+       char error_response[SIZ];
+
+       sprintf(ok_response, "220 Begin TLS negotiation now\r\n");
+       sprintf(nosup_response, "554 TLS not supported here\r\n");
+       sprintf(error_response, "554 Internal error\r\n");
+       CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
+       smtp_rset(0);
+}
+
+
+// Implements the NOOP (NO OPeration) command
+void smtp_noop(void) {
+       cprintf("250 NOOP\r\n");
+}
+
+
+// Implements the QUIT command
+void smtp_quit(void) {
+       cprintf("221 Goodbye...\r\n");
+       CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
+}
+
+
+// Main command loop for SMTP server sessions.
+void smtp_command_loop(void) {
+       static const ConstStr AuthPlainStr = {HKEY("AUTH PLAIN")};
+
+       if (SMTP == NULL) {
+               syslog(LOG_ERR, "serv_smtp: Session SMTP data is null.  WTF?  We will crash now.");
+               abort();
+       }
+
+       time(&CC->lastcmd);
+       if (CtdlClientGetLine(SMTP->Cmd) < 1) {
+               syslog(LOG_INFO, "SMTP: client disconnected: ending session.");
+               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
+               return;
+       }
+
+       if (SMTP->command_state == smtp_user) {
+               if (!strncmp(ChrPtr(SMTP->Cmd), AuthPlainStr.Key, AuthPlainStr.len)) {
+                       smtp_try_plain();
+               }
+               else {
+                       smtp_get_user(0);
+               }
+               return;
+       }
+
+       else if (SMTP->command_state == smtp_password) {
+               smtp_get_pass();
+               return;
+       }
+
+       else if (SMTP->command_state == smtp_plain) {
+               smtp_try_plain();
+               return;
+       }
+
+       syslog(LOG_DEBUG, "serv_smtp: client sent command <%s>", ChrPtr(SMTP->Cmd));
+
+       if (!strncasecmp(ChrPtr(SMTP->Cmd), "NOOP", 4)) {
+               smtp_noop();
+               return;
+       }
+
+       if (!strncasecmp(ChrPtr(SMTP->Cmd), "QUIT", 4)) {
+               smtp_quit();
+               return;
+       }
+
+       if (!strncasecmp(ChrPtr(SMTP->Cmd), "HELO", 4)) {
+               smtp_hello(HELO);
+               return;
+       }
+
+       if (!strncasecmp(ChrPtr(SMTP->Cmd), "EHLO", 4)) {
+               smtp_hello(EHLO);
+               return;
+       }
+
+       if (!strncasecmp(ChrPtr(SMTP->Cmd), "LHLO", 4)) {
+               smtp_hello(LHLO);
+               return;
+       }
+
+       if (!strncasecmp(ChrPtr(SMTP->Cmd), "RSET", 4)) {
+               smtp_rset(1);
+               return;
+       }
+
+       if (!strncasecmp(ChrPtr(SMTP->Cmd), "AUTH", 4)) {
+               smtp_auth();
+               return;
+       }
+
+       if (!strncasecmp(ChrPtr(SMTP->Cmd), "DATA", 4)) {
+               smtp_data();
+               return;
+       }
+
+       if (!strncasecmp(ChrPtr(SMTP->Cmd), "HELP", 4)) {
+               smtp_help();
+               return;
+       }
+
+       if (!strncasecmp(ChrPtr(SMTP->Cmd), "MAIL", 4)) {
+               smtp_mail();
+               return;
+       }
+       
+       if (!strncasecmp(ChrPtr(SMTP->Cmd), "RCPT", 4)) {
+               smtp_rcpt();
+               return;
+       }
+#ifdef HAVE_OPENSSL
+       if (!strncasecmp(ChrPtr(SMTP->Cmd), "STARTTLS", 8)) {
+               smtp_starttls();
+               return;
+       }
+#endif
+
+       cprintf("502 I'm afraid I can't do that.\r\n");
+}
+
+
+/*****************************************************************************/
+/*                      MODULE INITIALIZATION STUFF                          */
+/*****************************************************************************/
+/*
+ * This cleanup function blows away the temporary memory used by
+ * the SMTP server.
+ */
+void smtp_cleanup_function(void)
+{
+       /* Don't do this stuff if this is not an SMTP session! */
+       if (CC->h_command_function != smtp_command_loop) return;
+
+       syslog(LOG_DEBUG, "Performing SMTP cleanup hook");
+
+       FreeStrBuf(&SMTP->Cmd);
+       FreeStrBuf(&SMTP->helo_node);
+       FreeStrBuf(&SMTP->from);
+       FreeStrBuf(&SMTP->recipients);
+       FreeStrBuf(&SMTP->OneRcpt);
+       FreeStrBuf(&SMTP->preferred_sender_email);
+       FreeStrBuf(&SMTP->preferred_sender_name);
+
+       free(SMTP);
+}
+
+const char *CitadelServiceSMTP_MTA="SMTP-MTA";
+const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
+const char *CitadelServiceSMTP_MSA="SMTP-MSA";
+const char *CitadelServiceSMTP_LMTP="LMTP";
+const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
+
+
+char *ctdl_module_init_smtp(void) {
+       if (!threading) {
+               CtdlRegisterServiceHook(CtdlGetConfigInt("c_smtp_port"),        /* SMTP MTA */
+                                       NULL,
+                                       smtp_mta_greeting,
+                                       smtp_command_loop,
+                                       NULL, 
+                                       CitadelServiceSMTP_MTA);
+
+#ifdef HAVE_OPENSSL
+               CtdlRegisterServiceHook(CtdlGetConfigInt("c_smtps_port"),       /* SMTPS MTA */
+                                       NULL,
+                                       smtps_greeting,
+                                       smtp_command_loop,
+                                       NULL,
+                                       CitadelServiceSMTPS_MTA);
+#endif
+
+               CtdlRegisterServiceHook(CtdlGetConfigInt("c_msa_port"),         /* SMTP MSA */
+                                       NULL,
+                                       smtp_msa_greeting,
+                                       smtp_command_loop,
+                                       NULL,
+                                       CitadelServiceSMTP_MSA);
+
+               CtdlRegisterServiceHook(0,                      /* local LMTP */
+                                       file_lmtp_socket,
+                                       lmtp_greeting,
+                                       smtp_command_loop,
+                                       NULL,
+                                       CitadelServiceSMTP_LMTP);
+
+               CtdlRegisterServiceHook(0,                      /* local LMTP */
+                                       file_lmtp_unfiltered_socket,
+                                       lmtp_unfiltered_greeting,
+                                       smtp_command_loop,
+                                       NULL,
+                                       CitadelServiceSMTP_LMTP_UNF);
+
+               CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP, PRIO_STOP + 250);
+       }
+       
+       /* return our module name for the log */
+       return "smtp";
+}
diff --git a/citadel/server/modules/smtp/serv_smtpclient.c b/citadel/server/modules/smtp/serv_smtpclient.c
new file mode 100644 (file)
index 0000000..1ff3d6d
--- /dev/null
@@ -0,0 +1,571 @@
+// Transmit outbound SMTP mail to the big wide world of the Internet
+//
+// This is the new, exciting, clever version that makes libcurl do all the work  :)
+//
+// Copyright (c) 1997-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as published
+// by the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <time.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <libcitadel.h>
+#include <curl/curl.h>
+#include "../../sysconfig.h"
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../ctdl_module.h"
+#include "../../clientsocket.h"
+#include "../../msgbase.h"
+#include "../../domain.h"
+#include "../../internet_addressing.h"
+#include "../../citadel_dirs.h"
+#include "../smtp/smtp_util.h"
+
+struct smtpmsgsrc {            // Data passed in and out of libcurl for message upload
+       StrBuf *TheMessage;
+       int bytes_total;
+       int bytes_sent;
+};
+
+static int doing_smtpclient = 0;
+long *smtpq = NULL;            // array of msgnums containing queue instructions
+int smtpq_count = 0;           // number of queue messages in smtpq
+int smtpq_alloc = 0;           // current allocation size for smtpq
+
+
+// Initialize the SMTP outbound queue
+void smtp_init_spoolout(void) {
+       struct ctdlroom qrbuf;
+
+       // Create the room.  This will silently fail if the room already
+       // exists, and that's perfectly ok, because we want it to exist.
+       CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_QUEUE);
+
+       // Make sure it's set to be a "system room" so it doesn't show up
+       // in the <K>nown rooms list for administrators.
+       if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
+               qrbuf.QRflags2 |= QR2_SYSTEM;
+               CtdlPutRoomLock(&qrbuf);
+       }
+}
+
+
+// For internet mail, generate delivery instructions.
+// Yes, this is recursive.  Deal with it.  Infinite recursion does
+// not happen because the delivery instructions message does not
+// contain a recipient.
+int smtp_aftersave(struct CtdlMessage *msg, struct recptypes *recps) {
+       if ((recps != NULL) && (recps->num_internet > 0)) {
+               struct CtdlMessage *imsg = NULL;
+               char recipient[SIZ];
+               StrBuf *SpoolMsg = NewStrBuf();
+               long nTokens;
+               int i;
+
+               syslog(LOG_DEBUG, "smtpclient: generating delivery instructions");
+
+               StrBufPrintf(SpoolMsg,
+                            "Content-type: " SPOOLMIME "\n"
+                            "\n"
+                            "msgid|%s\n"
+                            "submitted|%ld\n" "bounceto|%s\n", msg->cm_fields[eVltMsgNum], (long) time(NULL), recps->bounce_to);
+
+               if (recps->envelope_from != NULL) {
+                       StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
+                       StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
+                       StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
+               }
+               if (recps->sending_room != NULL) {
+                       StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
+                       StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
+                       StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
+               }
+
+               nTokens = num_tokens(recps->recp_internet, '|');
+               for (i = 0; i < nTokens; i++) {
+                       long len;
+                       len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
+                       if (len > 0) {
+                               StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
+                               StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
+                               StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
+                       }
+               }
+
+               imsg = malloc(sizeof(struct CtdlMessage));
+               memset(imsg, 0, sizeof(struct CtdlMessage));
+               imsg->cm_magic = CTDLMESSAGE_MAGIC;
+               imsg->cm_anon_type = MES_NORMAL;
+               imsg->cm_format_type = FMT_RFC822;
+               CM_SetField(imsg, eMsgSubject, HKEY("QMSG"));
+               CM_SetField(imsg, eAuthor, HKEY("Citadel"));
+               CM_SetField(imsg, eJournal, HKEY("do not journal"));
+               CM_SetAsFieldSB(imsg, eMesageText, &SpoolMsg);
+               CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
+               CM_Free(imsg);
+       }
+       return 0;
+}
+
+
+// Callback for smtp_attempt_delivery() to supply libcurl with upload data.
+static size_t upload_source(void *ptr, size_t size, size_t nmemb, void *userp) {
+       struct smtpmsgsrc *s = (struct smtpmsgsrc *) userp;
+       int sendbytes = 0;
+       const char *send_this = NULL;
+
+       sendbytes = (size * nmemb);
+
+       if (s->bytes_sent >= s->bytes_total) {
+               return (0);                                     // no data remaining; we are done
+       }
+
+       if (sendbytes > (s->bytes_total - s->bytes_sent)) {
+               sendbytes = s->bytes_total - s->bytes_sent;     // can't send more than we have
+       }
+
+       send_this = ChrPtr(s->TheMessage);
+       send_this += s->bytes_sent;                             // start where we last left off
+
+       memcpy(ptr, send_this, sendbytes);
+       s->bytes_sent += sendbytes;
+       return(sendbytes);                                      // return the number of bytes _actually_ copied
+}
+
+
+// The libcurl API doesn't provide a way to capture the actual SMTP result message returned
+// by the remote server.  This is an ugly way to extract it, by capturing debug data from
+// the library and filtering on the lines we want.
+int ctdl_libcurl_smtp_debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *userptr) {
+       if (type != CURLINFO_HEADER_IN)
+               return 0;
+       if (!userptr)
+               return 0;
+       char *debugbuf = (char *) userptr;
+
+       int len = strlen(debugbuf);
+       if (len + size > SIZ)
+               return 0;
+
+       memcpy(&debugbuf[len], data, size);
+       debugbuf[len + size] = 0;
+       return 0;
+}
+
+
+// Go through the debug output of an SMTP transaction, and boil it down to just the final success or error response message.
+void trim_response(long response_code, char *response) {
+       if ((response_code < 100) || (response_code > 999) || (IsEmptyStr(response))) {
+               return;
+       }
+
+       char *t = malloc(strlen(response));
+       if (!t) {
+               return;
+       }
+       t[0] = 0;
+
+       char *p;
+       for (p = response; *p != 0; ++p) {
+               if ( (*p != '\n') && (!isprint(*p)) ) {         // expunge any nonprintables except for newlines
+                       *p = ' ';
+               }
+       }
+
+       char response_code_str[4];
+       snprintf(response_code_str, sizeof response_code_str, "%ld", response_code);
+       char *respstart = strstr(response, response_code_str);
+       if (respstart == NULL) {                                // If we have a response code but no response text,
+               strcpy(response, smtpstatus(response_code));    // use one of our canned messages.
+               return;
+       }
+       strcpy(response, respstart);
+
+       p = strstr(response, "\n");
+       if (p != NULL) {
+               *p = 0;
+       }
+}
+
+
+// Attempt a delivery to one recipient.
+// Returns a three-digit SMTP status code.
+int smtp_attempt_delivery(long msgid, char *recp, char *envelope_from, char *source_room, char *response) {
+       struct smtpmsgsrc s;
+       char *fromaddr = NULL;
+       CURL *curl;
+       CURLcode res = CURLE_OK;
+       struct curl_slist *recipients = NULL;
+       long response_code = 421;
+       int num_mx = 0;
+       char mxes[SIZ];
+       char user[1024];
+       char node[1024];
+       char name[1024];
+       char try_this_mx[256];
+       char smtp_url[512];
+       int i;
+
+       syslog(LOG_DEBUG, "smtpclient: smtp_attempt_delivery(%ld, %s)", msgid, recp);
+
+       process_rfc822_addr(recp, user, node, name);    // split recipient address into username, hostname, displayname
+       num_mx = getmx(mxes, node);
+       if (num_mx < 1) {
+               return (421);
+       }
+
+       CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+       if (!IsEmptyStr(source_room)) {
+               // If we have a source room, it's probably a mailing list message; generate an unsubscribe header
+               char esc_room[ROOMNAMELEN*2];
+               char esc_email[1024];
+               urlesc(esc_room, sizeof esc_room, source_room);
+               urlesc(esc_email, sizeof esc_email, recp);
+               cprintf("List-Unsubscribe: <http://%s/listsub?cmd=unsubscribe&room=%s&email=%s>\r\n",
+                       CtdlGetConfigStr("c_fqdn"),
+                       esc_room,
+                       esc_email
+               );
+       }
+       CtdlOutputMsg(msgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0, NULL, &fromaddr, NULL);
+       s.TheMessage = CC->redirect_buffer;
+       s.bytes_total = StrLength(CC->redirect_buffer);
+       s.bytes_sent = 0;
+       CC->redirect_buffer = NULL;
+       response_code = 421;
+                                       // keep trying MXes until one works or we run out
+       for (i = 0; ((i < num_mx) && ((response_code / 100) == 4)); ++i) {
+               response_code = 421;    // default 421 makes non-protocol errors transient
+               s.bytes_sent = 0;       // rewind our buffer in case we try multiple MXes
+
+               curl = curl_easy_init();
+               if (curl) {
+                       response[0] = 0;
+
+                       if (!IsEmptyStr(envelope_from)) {
+                               curl_easy_setopt(curl, CURLOPT_MAIL_FROM, envelope_from);
+                       }
+                       else {
+                               curl_easy_setopt(curl, CURLOPT_MAIL_FROM, fromaddr);
+                       }
+
+                       recipients = curl_slist_append(recipients, recp);
+                       curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
+                       curl_easy_setopt(curl, CURLOPT_READFUNCTION, upload_source);
+                       curl_easy_setopt(curl, CURLOPT_READDATA, &s);
+                       curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);                              // tell libcurl we are uploading
+                       curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L);                           // Time out after 20 seconds
+                       if (CtdlGetConfigInt("c_smtpclient_disable_starttls") == 0) {
+                               curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_TRY);        // Attempt STARTTLS if offered
+                       }
+                       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
+                       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
+                       curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, ctdl_libcurl_smtp_debug_callback);
+                       curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void *) response);
+                       curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+
+                       // Construct an SMTP URL in the form of:
+                       //      smtp[s]://target_host/source_host
+                       // This looks weird but libcurl uses that last part to set our name for EHLO or HELO.
+                       // We check for "smtp://" and "smtps://" because the admin may have put those prefixes in a smart-host entry.
+                       // If there is no prefix we add "smtp://"
+                       extract_token(try_this_mx, mxes, i, '|', (sizeof try_this_mx - 7));
+                       snprintf(smtp_url, sizeof smtp_url,
+                               "%s%s/%s",
+                               (((!strncasecmp(try_this_mx, HKEY("smtp://")))
+                               || (!strncasecmp(try_this_mx, HKEY("smtps://")))) ? "" : "smtp://"),
+                               try_this_mx, CtdlGetConfigStr("c_fqdn")
+                       );
+                       curl_easy_setopt(curl, CURLOPT_URL, smtp_url);
+                       syslog(LOG_DEBUG, "smtpclient: trying MX %d of %d <%s>", i+1, num_mx, smtp_url);        // send the message
+                       res = curl_easy_perform(curl);
+                       curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
+                       syslog(LOG_DEBUG,
+                              "smtpclient: libcurl returned %d (%s) , SMTP response %ld",
+                              res, curl_easy_strerror(res), response_code
+                       );
+
+                       if ((res != CURLE_OK) && (response_code == 0)) {        // check for errors
+                               response_code = 421;
+                       }
+
+                       curl_slist_free_all(recipients);
+                       recipients = NULL;                                      // this gets reused; avoid double-free
+                       curl_easy_cleanup(curl);
+                       curl = NULL;                                            // this gets reused; avoid double-free
+
+                       // Trim the error message buffer down to just the actual message
+                       trim_response(response_code, response);
+               }
+       }
+
+       FreeStrBuf(&s.TheMessage);
+       if (fromaddr) {
+               free(fromaddr);
+       }
+       return ((int) response_code);
+}
+
+
+// Process one outbound message.
+void smtp_process_one_msg(long qmsgnum) {
+       struct CtdlMessage *msg = NULL;
+       char *instr = NULL;
+       int i;
+       int num_success = 0;
+       int num_fail = 0;
+       int num_delayed = 0;
+       long deletes[2];
+       int delete_this_queue = 0;
+       char server_response[SIZ];
+
+       msg = CtdlFetchMessage(qmsgnum, 1);
+       if (msg == NULL) {
+               syslog(LOG_WARNING, "smtpclient: %ld does not exist", qmsgnum);
+               return;
+       }
+
+       instr = msg->cm_fields[eMesageText];
+       msg->cm_fields[eMesageText] = NULL;
+       CM_Free(msg);
+
+       // if the queue message has any CRLF's convert them to LF's
+       char *crlf = NULL;
+       while (crlf = strstr(instr, "\r\n"), crlf != NULL) {
+               strcpy(crlf, crlf + 1);
+       }
+
+       // Strip out the headers and we are now left with just the instructions.
+       char *soi = strstr(instr, "\n\n");
+       if (soi) {
+               strcpy(instr, soi + 2);
+       }
+
+       long msgid = 0;
+       time_t submitted = time(NULL);
+       time_t attempted = 0;
+       char *bounceto = NULL;
+       char *envelope_from = NULL;
+       char *source_room = NULL;
+
+       char cfgline[SIZ];
+       for (i = 0; i < num_tokens(instr, '\n'); ++i) {
+               extract_token(cfgline, instr, i, '\n', sizeof cfgline);
+               if (!strncasecmp(cfgline, HKEY("msgid|")))
+                       msgid = atol(&cfgline[6]);
+               if (!strncasecmp(cfgline, HKEY("submitted|")))
+                       submitted = atol(&cfgline[10]);
+               if (!strncasecmp(cfgline, HKEY("attempted|")))
+                       attempted = atol(&cfgline[10]);
+               if (!strncasecmp(cfgline, HKEY("bounceto|")))
+                       bounceto = strdup(&cfgline[9]);
+               if (!strncasecmp(cfgline, HKEY("envelope_from|")))
+                       envelope_from = strdup(&cfgline[14]);
+               if (!strncasecmp(cfgline, HKEY("source_room|")))
+                       source_room = strdup(&cfgline[12]);
+       }
+
+       int should_try_now = 0;
+       if (attempted < submitted) {                    // If no attempts have been made yet, try now
+               should_try_now = 1;
+       }
+       else if ((attempted - submitted) <= 14400) {
+               if ((time(NULL) - attempted) > 1800) {  // First four hours, retry every 30 minutes
+                       should_try_now = 1;
+               }
+       }
+       else {
+               if ((time(NULL) - attempted) > 14400) { // After that, retry once every 4 hours
+                       should_try_now = 1;
+               }
+       }
+
+       if (should_try_now) {
+               syslog(LOG_DEBUG, "smtpclient: attempting delivery of message <%ld> now", qmsgnum);
+               if (source_room) {
+                       syslog(LOG_DEBUG, "smtpclient: this message originated in <%s>", source_room);
+               }
+               StrBuf *NewInstr = NewStrBuf();
+               StrBufAppendPrintf(NewInstr, "Content-type: " SPOOLMIME "\n\n");
+               StrBufAppendPrintf(NewInstr, "msgid|%ld\n", msgid);
+               StrBufAppendPrintf(NewInstr, "submitted|%ld\n", submitted);
+               if (bounceto) {
+                       StrBufAppendPrintf(NewInstr, "bounceto|%s\n", bounceto);
+               }
+               if (envelope_from) {
+                       StrBufAppendPrintf(NewInstr, "envelope_from|%s\n", envelope_from);
+               }
+               for (i = 0; i < num_tokens(instr, '\n'); ++i) {
+                       extract_token(cfgline, instr, i, '\n', sizeof cfgline);
+                       if (!strncasecmp(cfgline, HKEY("remote|"))) {
+                               char recp[SIZ];
+                               int previous_result = extract_int(cfgline, 2);
+                               if ((previous_result == 0)
+                                   || (previous_result == 4)) {
+                                       int new_result = 421;
+                                       extract_token(recp, cfgline, 1, '|', sizeof recp);
+                                       new_result = smtp_attempt_delivery(msgid, recp, envelope_from, source_room, server_response);
+                                       syslog(LOG_DEBUG, "smtpclient: recp: <%s> , result: %d (%s)", recp, new_result, server_response);
+                                       if ((new_result / 100) == 2) {
+                                               ++num_success;
+                                       }
+                                       else {
+                                               if ((new_result / 100) == 5) {
+                                                       ++num_fail;
+                                               }
+                                               else {
+                                                       ++num_delayed;
+                                               }
+                                               StrBufAppendPrintf
+                                                   (NewInstr,
+                                                    "remote|%s|%ld|%ld (%s)\n",
+                                                    recp, (new_result / 100), new_result, server_response);
+                                       }
+                               }
+                       }
+               }
+
+               StrBufAppendPrintf(NewInstr, "attempted|%ld\n", time(NULL));
+
+               // All deliveries have now been attempted.  Now determine the disposition of this queue entry.
+
+               time_t age = time(NULL) - submitted;
+               syslog(LOG_DEBUG,
+                      "smtpclient: submission age: %ldd%ldh%ldm%lds",
+                      (age / 86400), ((age % 86400) / 3600), ((age % 3600) / 60), (age % 60));
+               syslog(LOG_DEBUG, "smtpclient: num_success=%d , num_fail=%d , num_delayed=%d", num_success, num_fail, num_delayed);
+
+               // If there are permanent fails on this attempt, deliver a bounce to the user.
+               // The 5XX fails will be recorded in the rewritten queue, but they will be removed before the next attempt.
+               if (num_fail > 0) {
+                       smtp_do_bounce(ChrPtr(NewInstr), SDB_BOUNCE_FATALS);
+               }
+               // If all deliveries have either succeeded or failed, we are finished with this queue entry.
+               if (num_delayed == 0) {
+                       delete_this_queue = 1;
+               }
+               // If it's been more than five days, give up and tell the sender that delivery failed
+               else if ((time(NULL) - submitted) > SMTP_DELIVER_FAIL) {
+                       smtp_do_bounce(ChrPtr(NewInstr), SDB_BOUNCE_ALL);
+                       delete_this_queue = 1;
+               }
+               // If it's been more than four hours but less than five days, warn the sender that delivery is delayed
+               else if (((attempted - submitted) < SMTP_DELIVER_WARN)
+                        && ((time(NULL) - submitted) >= SMTP_DELIVER_WARN)) {
+                       smtp_do_bounce(ChrPtr(NewInstr), SDB_WARN);
+               }
+
+               if (delete_this_queue) {
+                       syslog(LOG_DEBUG, "smtpclient: %ld deleting", qmsgnum);
+                       deletes[0] = qmsgnum;
+                       deletes[1] = msgid;
+                       CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, deletes, 2, "");
+                       FreeStrBuf(&NewInstr);  // We have to free NewInstr here, no longer needed
+               }
+               else {
+                       // replace the old queue entry with the new one
+                       syslog(LOG_DEBUG, "smtpclient: %ld rewriting", qmsgnum);
+                       msg = convert_internet_message_buf(&NewInstr);  // This function will free NewInstr for us
+                       CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
+                       CM_Free(msg);
+                       CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &qmsgnum, 1, "");
+               }
+       }
+       else {
+               syslog(LOG_DEBUG, "smtpclient: %ld retry time not reached", qmsgnum);
+       }
+
+       if (bounceto != NULL) {
+               free(bounceto);
+       }
+       if (envelope_from != NULL) {
+               free(envelope_from);
+       }
+       if (source_room != NULL) {
+               free(source_room);
+       }
+       free(instr);
+}
+
+
+// Callback for smtp_do_queue()
+void smtp_add_msg(long msgnum, void *userdata) {
+
+       if (smtpq == NULL) {
+               smtpq_count = 0;
+               smtpq_alloc = 100;
+               smtpq = malloc(smtpq_alloc * sizeof(long));
+       }
+
+       if (smtpq_alloc >= smtpq_count) {
+               smtpq_alloc += 100;
+               smtpq = realloc(smtpq, (smtpq_alloc * sizeof(long)));
+       }
+
+       smtpq[smtpq_count++] = msgnum;
+}
+
+
+// Run through the queue sending out messages.
+void smtp_do_queue(void) {
+       int i = 0;
+
+       // This is a simple concurrency check to make sure only one smtpclient
+       // run is done at a time.  We could do this with a mutex, but since we
+       // don't really require extremely fine granularity here, we'll do it
+       // with a static variable instead.
+       if (doing_smtpclient) {
+               return;
+       }
+       doing_smtpclient = 1;
+
+       syslog(LOG_DEBUG, "smtpclient: start queue run");
+
+       if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
+               syslog(LOG_WARNING, "Cannot find room <%s>", SMTP_SPOOLOUT_ROOM);
+               doing_smtpclient = 0;
+               return;
+       }
+       // Put the queue in memory so we can close the db cursor
+       CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_add_msg, NULL);
+
+       // We are ready to run through the queue now.
+       for (i = 0; i < smtpq_count; ++i) {
+               smtp_process_one_msg(smtpq[i]);
+       }
+
+       smtpq_count = 0;        // don't free it, we will use this memory on the next run
+       doing_smtpclient = 0;
+       syslog(LOG_DEBUG, "smtpclient: end queue run");
+}
+
+
+// Module entry point
+char *ctdl_module_init_smtpclient(void) {
+       if (!threading) {
+               CtdlRegisterMessageHook(smtp_aftersave, EVT_AFTERSAVE);
+               CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER, PRIO_AGGR + 51);
+               smtp_init_spoolout();
+       }
+
+       // return our module id for the log
+       return "smtpclient";
+}
diff --git a/citadel/server/modules/smtp/smtp_util.c b/citadel/server/modules/smtp/smtp_util.c
new file mode 100644 (file)
index 0000000..d62df94
--- /dev/null
@@ -0,0 +1,315 @@
+/*
+ * Utility functions for the Citadel SMTP implementation
+ *
+ * Copyright (c) 1998-2021 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <syslog.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../genstamp.h"
+#include "../../domain.h"
+#include "../../clientsocket.h"
+#include "../../locate_host.h"
+#include "../../citadel_dirs.h"
+#include "../../ctdl_module.h"
+#include "smtp_util.h"
+
+const char *smtp_get_Recipients(void) {
+       struct citsmtp *sSMTP = SMTP;
+
+       if (sSMTP == NULL)
+               return NULL;
+       else return ChrPtr(sSMTP->from);
+}
+
+
+/*
+ * smtp_do_bounce() is caled by smtp_process_one_msg() to scan a set of delivery
+ * instructions for errors and produce/deliver a "bounce" message (delivery
+ * status notification).
+ *
+ * is_final should be set to:
+ *     SDB_BOUNCE_FATALS       Advise the sender of all 5XX (permanent failures)
+ *     SDB_BOUNCE_ALL          Advise the sender that all deliveries have failed and will not be retried
+ *     SDB_WARN                Warn the sender about all 4XX transient delays
+ */
+void smtp_do_bounce(const char *instr, int is_final) {
+       int i;
+       int lines;
+       int status;
+       char buf[1024];
+       char key[1024];
+       char addr[1024];
+       char dsn[1024];
+       char bounceto[1024];
+       StrBuf *boundary;
+       int num_bounces = 0;
+       int bounce_this = 0;
+       struct CtdlMessage *bmsg = NULL;
+       struct recptypes *valid;
+       int successful_bounce = 0;
+       static int seq = 0;
+       StrBuf *BounceMB;
+       long omsgid = (-1);
+
+       syslog(LOG_DEBUG, "smtp_do_bounce() called");
+       strcpy(bounceto, "");
+       boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
+
+       StrBufAppendPrintf(boundary, "%s_%04x%04x", CtdlGetConfigStr("c_fqdn"), getpid(), ++seq);
+
+       /* Start building our bounce message */
+
+       bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
+       if (bmsg == NULL) return;
+       memset(bmsg, 0, sizeof(struct CtdlMessage));
+       BounceMB = NewStrBufPlain(NULL, 1024);
+
+       bmsg->cm_magic = CTDLMESSAGE_MAGIC;
+       bmsg->cm_anon_type = MES_NORMAL;
+       bmsg->cm_format_type = FMT_RFC822;
+       CM_SetField(bmsg, eAuthor, HKEY("Citadel"));
+       CM_SetField(bmsg, eOriginalRoom, HKEY(MAILROOM));
+       CM_SetField(bmsg, eMsgSubject, HKEY("Delivery Status Notification (Failure)"));
+       StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
+       StrBufAppendBuf(BounceMB, boundary, 0);
+       StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
+       StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
+       StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
+       StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
+       StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
+       StrBufAppendBuf(BounceMB, boundary, 0);
+       StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
+       StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
+
+       if (is_final == SDB_BOUNCE_ALL) {
+               StrBufAppendBufPlain(
+                       BounceMB,
+                       HKEY(   "A message you sent could not be delivered "
+                               "to some or all of its recipients\ndue to "
+                               "prolonged unavailability of its destination(s).\n"
+                               "Giving up on the following addresses:\n\n"),
+                       0);
+       }
+       else if (is_final == SDB_BOUNCE_FATALS) {
+               StrBufAppendBufPlain(
+                       BounceMB,
+                       HKEY(   "A message you sent could not be delivered "
+                               "to some or all of its recipients.\n"
+                               "The following addresses were undeliverable:\n\n"),
+                       0);
+       }
+       else if (is_final == SDB_WARN) {
+               StrBufAppendBufPlain(
+                       BounceMB,
+                       HKEY("A message you sent has not been delivered "
+                               "to some or all of its recipients.\n"
+                               "Citadel will continue attempting delivery for five days.\n"
+                               "The following addresses were undeliverable:\n\n"),
+                       0);
+       }
+       else {  // should never get here
+               StrBufAppendBufPlain(BounceMB, HKEY("This message should never occur.\n\n"), 0);
+       }
+
+       /*
+        * Now go through the instructions checking for stuff.
+        */
+       lines = num_tokens(instr, '\n');
+       for (i=0; i<lines; ++i) {
+               long addrlen;
+               long dsnlen;
+               extract_token(buf, instr, i, '\n', sizeof buf);
+               extract_token(key, buf, 0, '|', sizeof key);
+               addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
+               status = extract_int(buf, 2);
+               dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
+               bounce_this = 0;
+
+               syslog(LOG_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>", key, addr, status, dsn);
+
+               if (!strcasecmp(key, "bounceto")) {
+                       strcpy(bounceto, addr);
+               }
+
+               if (!strcasecmp(key, "msgid")) {
+                       omsgid = atol(addr);
+               }
+
+               if (!strcasecmp(key, "remote")) {
+                       if ((is_final == SDB_BOUNCE_FATALS) && (status == 5)) bounce_this = 1;
+                       if ((is_final == SDB_BOUNCE_ALL) && (status != 2)) bounce_this = 1;
+                       if ((is_final == SDB_WARN) && (status == 4)) bounce_this = 1;
+               }
+
+               if (bounce_this) {
+                       ++num_bounces;
+                       StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
+                       StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
+                       StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
+                       StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
+               }
+       }
+
+       /* Attach the original message */
+       if (omsgid >= 0) {
+               StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
+               StrBufAppendBuf(BounceMB, boundary, 0);
+               StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
+               StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
+               StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
+               StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
+               StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
+
+               CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+               CtdlOutputMsg(omsgid,
+                       MT_RFC822,
+                       HEADERS_ALL,
+                       0, 1, NULL, 0,
+                       NULL, NULL, NULL
+               );
+               StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
+               FreeStrBuf(&CC->redirect_buffer);
+       }
+
+       /* Close the multipart MIME scope */
+       StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
+       StrBufAppendBuf(BounceMB, boundary, 0);
+       StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
+       CM_SetAsFieldSB(bmsg, eMesageText, &BounceMB);
+
+       /* Deliver the bounce if there's anything worth mentioning */
+       syslog(LOG_DEBUG, "num_bounces = %d", num_bounces);
+       if (num_bounces > 0) {
+
+               /* First try the user who sent the message */
+               if (IsEmptyStr(bounceto)) {
+                       syslog(LOG_ERR, "No bounce address specified");
+               }
+               else {
+                       syslog(LOG_DEBUG, "bounce to user <%s>", bounceto);
+               }
+               /* Can we deliver the bounce to the original sender? */
+               valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
+               if (valid != NULL) {
+                       if (valid->num_error == 0) {
+                               CtdlSubmitMsg(bmsg, valid, "");
+                               successful_bounce = 1;
+                       }
+               }
+
+               /* If not, post it in the Aide> room */
+               if (successful_bounce == 0) {
+                       CtdlSubmitMsg(bmsg, NULL, CtdlGetConfigStr("c_aideroom"));
+               }
+
+               /* Free up the memory we used */
+               if (valid != NULL) {
+                       free_recipients(valid);
+               }
+       }
+       FreeStrBuf(&boundary);
+       CM_Free(bmsg);
+       syslog(LOG_DEBUG, "Done processing bounces");
+}
+
+
+char *smtpcodes[][2] = {
+       { "211 - System status / system help reply" },
+       { "214", "Help message" },
+       { "220", "Domain service ready" },
+       { "221", "Domain service closing transmission channel" },
+       { "250", "Requested mail action completed and OK" },
+       { "251", "Not Local User, forward email to forward path" },
+       { "252", "Cannot Verify user, will attempt delivery later" },
+       { "253", "Pending messages for node started" },
+       { "354", "Start mail input; end with ." },
+       { "355", "Octet-offset is the transaction offset" },
+       { "421", "Domain service not available, closing transmission channel" },
+       { "432", "Domain service not available, closing transmission channel" },
+       { "450", "Requested mail action not taken: mailbox unavailable. request refused" },
+       { "451", "Requested action aborted: local error in processing Request is unable to be processed, try again" },
+       { "452", "Requested action not taken: insufficient system storage" },
+       { "453", "No mail" },
+       { "454", "TLS not available due to temporary reason. Encryption required for requested authentication mechanism" },
+       { "458", "Unable to queue messages for node" },
+       { "459", "Node not allowed: reason" },
+       { "500", "Syntax error, command unrecognized" },
+       { "501", "Syntax error in parameters or arguments" },
+       { "502", "Command not implemented" },
+       { "503", "Bad sequence of commands" },
+       { "504", "Command parameter not implemented" },
+       { "510", "Check the recipient address" },
+       { "512", "Domain can not be found. Unknown host." },
+       { "515", "Destination mailbox address invalid" },
+       { "517", "Problem with senders mail attribute, check properties" },
+       { "521", "Domain does not accept mail" },
+       { "522", "Recipient has exceeded mailbox limit" },
+       { "523", "Server limit exceeded. Message too large" },
+       { "530", "Access Denied. Authentication required" },
+       { "531", "Mail system Full" },
+       { "533", "Remote ../../server.has insufficient disk space to hold email" },
+       { "534", "Authentication mechanism is too weak. Message too big" },
+       { "535", "Multiple servers using same IP. Required Authentication" },
+       { "538", "Encryption required for requested authentication mechanism" },
+       { "540", "Email address has no DNS Server" },
+       { "541", "No response from host" },
+       { "542", "Bad Connection" },
+       { "543", "Routing server failure. No available route" },
+       { "546", "Email looping" },
+       { "547", "Delivery time-out" },
+       { "550", "Requested action not taken: mailbox unavailable or relaying denied" },
+       { "551", "User not local; please try forward path" },
+       { "552", "Requested mail action aborted: exceeded storage allocation" },
+       { "553", "Requested action not taken: mailbox name not allowed" },
+       { "554", "Transaction failed" }
+};
+
+
+char *smtpstatus(int code) {
+       int i;
+
+       for (i=0; i<(sizeof(smtpcodes)/sizeof(char *)/2); ++i) {
+               if (atoi(smtpcodes[i][0]) == code) {
+                       return(smtpcodes[i][1]);
+               }
+       }
+       
+       return("Unknown or other SMTP status");
+}
diff --git a/citadel/server/modules/smtp/smtp_util.h b/citadel/server/modules/smtp/smtp_util.h
new file mode 100644 (file)
index 0000000..ea2433c
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 1998-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+const char *smtp_get_Recipients(void);
+
+struct citsmtp {               /* Information about the current session */
+       int command_state;
+       StrBuf *Cmd;
+       StrBuf *helo_node;
+       StrBuf *from;
+       StrBuf *recipients;
+       StrBuf *OneRcpt;
+       int number_of_recipients;
+       int delivery_mode;
+       int message_originated_locally;
+       int is_lmtp;
+       int is_unfiltered;
+       int is_msa;
+       StrBuf *preferred_sender_email;
+       StrBuf *preferred_sender_name;
+};
+
+#define SMTP           ((struct citsmtp *)CC->session_specific_data)
+
+// These are all the values that can be passed to the is_final parameter of smtp_do_bounce()
+enum {
+       SDB_BOUNCE_FATALS,
+       SDB_BOUNCE_ALL,
+       SDB_WARN
+};
+
+void smtp_do_bounce(const char *instr, int is_final);
+char *smtpstatus(int code);
diff --git a/citadel/server/modules/spam/.gitignore b/citadel/server/modules/spam/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/spam/serv_spam.c b/citadel/server/modules/spam/serv_spam.c
new file mode 100644 (file)
index 0000000..97d1e23
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * This module allows Citadel to use SpamAssassin to filter incoming messages
+ * arriving via SMTP.  For more information on SpamAssassin, visit
+ * http://www.spamassassin.org (the SpamAssassin project is not in any way
+ * affiliated with the Citadel project).
+ *
+ * Copyright (c) 1998-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define SPAMASSASSIN_PORT       "783"
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../domain.h"
+#include "../../clientsocket.h"
+
+
+#include "../../ctdl_module.h"
+
+
+
+/*
+ * Connect to the SpamAssassin server and scan a message.
+ */
+int spam_assassin(struct CtdlMessage *msg, struct recptypes *recp) {
+       int sock = (-1);
+       char sahosts[SIZ];
+       int num_sahosts;
+       char buf[SIZ];
+       int is_spam = 0;
+       int sa;
+       StrBuf *msgtext;
+       CitContext *CCC=CC;
+
+       /* For users who have authenticated to this server we never want to
+        * apply spam filtering, because presumably they're trustworthy.
+        */
+       if (CC->logged_in) return(0);
+
+       /* See if we have any SpamAssassin hosts configured */
+       num_sahosts = get_hosts(sahosts, "spamassassin");
+       if (num_sahosts < 1) return(0);
+
+       /* Try them one by one until we get a working one */
+        for (sa=0; sa<num_sahosts; ++sa) {
+                extract_token(buf, sahosts, sa, '|', sizeof buf);
+                syslog(LOG_INFO, "Connecting to SpamAssassin at <%s>\n", buf);
+                sock = sock_connect(buf, SPAMASSASSIN_PORT);
+                if (sock >= 0) syslog(LOG_DEBUG, "Connected!\n");
+        }
+
+       if (sock < 0) {
+               /* If the service isn't running, just pass the mail
+                * through.  Potentially throwing away mails isn't good.
+                */
+               return(0);
+       }
+
+       CCC->SBuf.Buf = NewStrBuf();
+       CCC->sMigrateBuf = NewStrBuf();
+       CCC->SBuf.ReadWritePointer = NULL;
+
+       /* Command */
+       syslog(LOG_DEBUG, "Transmitting command\n");
+       sprintf(buf, "CHECK SPAMC/1.2\r\n\r\n");
+       sock_write(&sock, buf, strlen(buf));
+
+       /* Message */
+       CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+       CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, 0);
+       msgtext = CC->redirect_buffer;
+       CC->redirect_buffer = NULL;
+
+       sock_write(&sock, SKEY(msgtext));
+       FreeStrBuf(&msgtext);
+
+       /* Close one end of the socket connection; this tells SpamAssassin
+        * that we're done.
+        */
+       if (sock != -1)
+               sock_shutdown(sock, SHUT_WR);
+       
+       /* Response */
+       syslog(LOG_DEBUG, "Awaiting response\n");
+        if (sock_getln(&sock, buf, sizeof buf) < 0) {
+                goto bail;
+        }
+        syslog(LOG_DEBUG, "<%s\n", buf);
+       if (strncasecmp(buf, "SPAMD", 5)) {
+               goto bail;
+       }
+        if (sock_getln(&sock, buf, sizeof buf) < 0) {
+                goto bail;
+        }
+        syslog(LOG_DEBUG, "<%s\n", buf);
+        syslog(LOG_DEBUG, "c_spam_flag_only setting %d\n", CtdlGetConfigInt("c_spam_flag_only"));
+        if (CtdlGetConfigInt("c_spam_flag_only")) {
+               int headerlen;
+               char *cur;
+               char sastatus[10];
+               char sascore[10];
+               char saoutof[10];
+               int numscore;
+
+                syslog(LOG_DEBUG, "flag spam code used");
+
+                extract_token(sastatus, buf, 1, ' ', sizeof sastatus);
+                extract_token(sascore, buf, 3, ' ', sizeof sascore);
+                extract_token(saoutof, buf, 5, ' ', sizeof saoutof);
+
+               memcpy(buf, HKEY("X-Spam-Level: "));
+               cur = buf + 14;
+               for (numscore = atoi(sascore); numscore>0; numscore--)
+                       *(cur++) = '*';
+               *cur = '\0';
+
+               headerlen  = cur - buf;
+               headerlen += snprintf(cur, (sizeof(buf) - headerlen), 
+                                    "\r\nX-Spam-Status: %s, score=%s required=%s\r\n",
+                                    sastatus, sascore, saoutof);
+
+               CM_PrependToField(msg, eMesageText, buf, headerlen);
+
+       } else {
+                syslog(LOG_DEBUG, "reject spam code used");
+               if (!strncasecmp(buf, "Spam: True", 10)) {
+                       is_spam = 1;
+               }
+
+               if (is_spam) {
+                       CM_SetField(msg, eErrorMsg, HKEY("message rejected by spam filter"));
+               }
+       }
+
+bail:  close(sock);
+       FreeStrBuf(&CCC->SBuf.Buf);
+       FreeStrBuf(&CCC->sMigrateBuf);
+       return(is_spam);
+}
+
+
+
+char *ctdl_module_init_spam(void) {
+       if (!threading) {
+               CtdlRegisterMessageHook(spam_assassin, EVT_SMTPSCAN);
+       }
+       
+       /* return our module name for the log */
+        return "spam";
+}
diff --git a/citadel/server/modules/test/.gitignore b/citadel/server/modules/test/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/test/serv_test.c b/citadel/server/modules/test/serv_test.c
new file mode 100644 (file)
index 0000000..7445ac0
--- /dev/null
@@ -0,0 +1,66 @@
+// This is an empty skeleton of a Citadel server module, included to demonstrate
+// how to add a new module to the system and how to activate it in the server.
+// 
+// Copyright (c) 1998-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "../../ctdl_module.h"
+
+
+void CleanupTest(void) {
+       syslog(LOG_DEBUG, "--- test of adding an unload hook --- \n");
+}
+
+void NewRoomTest(void) {
+       syslog(LOG_DEBUG, "--- test module was told we're now in a new room ---\n");
+}
+
+void SessionStartTest(void) {
+       syslog(LOG_DEBUG, "--- starting up session %d ---\n", CC->cs_pid);
+}
+
+void SessionStopTest(void) {
+       syslog(LOG_DEBUG, "--- ending session %d ---\n", CC->cs_pid);
+}
+
+void LoginTest(void) {
+       syslog(LOG_DEBUG, "--- Hello, %s ---\n", CC->curr_user);
+}
+
+// To insert this module into the server activate the next block by changing the #if 0 to #if 1
+char *ctdl_module_init_test() {
+#if 0
+       if (!threading) {
+               CtdlRegisterCleanupHook(CleanupTest);
+               CtdlRegisterSessionHook(NewRoomTest, EVT_NEWROOM, 1);
+               CtdlRegisterSessionHook(SessionStartTest, EVT_START, 1);
+               CtdlRegisterSessionHook(SessionStopTest, EVT_STOP, 1);
+               CtdlRegisterSessionHook(LoginTest, EVT_LOGIN, 1);
+       }
+#else
+       syslog(LOG_DEBUG, "test: module is disabled");
+#endif
+
+       // return our module name for the log
+       return "test";
+}
diff --git a/citadel/server/modules/upgrade/.gitignore b/citadel/server/modules/upgrade/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/upgrade/serv_upgrade.c b/citadel/server/modules/upgrade/serv_upgrade.c
new file mode 100644 (file)
index 0000000..d836996
--- /dev/null
@@ -0,0 +1,576 @@
+// Transparently handle the upgrading of server data formats.  If we see
+// an existing version number of our database, we can make some intelligent
+// guesses about what kind of data format changes need to be applied, and
+// we apply them transparently.
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../database.h"
+#include "../../user_ops.h"
+#include "../../msgbase.h"
+#include "serv_upgrade.h"
+#include "../../euidindex.h"
+#include "../../ctdl_module.h"
+#include "../../serv_vcard.h"
+#include "../../internet_addressing.h"
+
+// oldver is the version number of Citadel Server which was active on the previous run of the program, learned from the system configuration.
+// If we are running a new Citadel Server for the first time, oldver will be 0.
+// We keep this value around for the entire duration of the program run because we'll need it during several stages of startup.
+int oldver = 0;
+
+// Try to remove any extra users with number 0
+void fix_sys_user_name(void) {
+       struct ctdluser usbuf;
+       char usernamekey[USERNAME_SIZE];
+
+       while (CtdlGetUserByNumber(&usbuf, 0) == 0) {
+               // delete user with number 0 and no name
+               if (IsEmptyStr(usbuf.fullname)) {
+                       cdb_delete(CDB_USERS, "", 0);
+               }
+               else {
+                       // temporarily set this user to -1
+                       usbuf.usernum = -1;
+                       CtdlPutUser(&usbuf);
+               }
+       }
+
+       // Delete any "user 0" accounts
+       while (CtdlGetUserByNumber(&usbuf, -1) == 0) {
+               makeuserkey(usernamekey, usbuf.fullname);
+               cdb_delete(CDB_USERS, usernamekey, strlen(usernamekey));
+       }
+}
+
+
+// Back end processing function for reindex_uids()
+void reindex_uids_backend(char *username, void *data) {
+
+       struct ctdluser us;
+
+       if (CtdlGetUserLock(&us, username) == 0) {
+               syslog(LOG_DEBUG, "Processing <%s> (%d)", us.fullname, us.uid);
+               if (us.uid == CTDLUID) {
+                       us.uid = NATIVE_AUTH_UID;
+               }
+               CtdlPutUserLock(&us);
+               if ((us.uid > 0) && (us.uid != NATIVE_AUTH_UID)) {              // if non-native auth , index by uid
+                       StrBuf *claimed_id = NewStrBuf();
+                       StrBufPrintf(claimed_id, "uid:%d", us.uid);
+                       attach_extauth(&us, claimed_id);
+                       FreeStrBuf(&claimed_id);
+               }
+       }
+}
+
+
+// Build extauth index of all users with uid-based join (system auth, LDAP auth)
+// Also changes all users with a uid of CTDLUID to NATIVE_AUTH_UID (-1)
+void reindex_uids(void) {
+       syslog(LOG_WARNING, "upgrade: reindexing and applying uid changes");
+       ForEachUser(reindex_uids_backend, NULL);
+       return;
+}
+
+
+// These accounts may have been created by code that ran between mid 2008 and early 2011.
+// If present they are no longer in use and may be deleted.
+void remove_thread_users(void) {
+       char *deleteusers[] = {
+               "SYS_checkpoint",
+               "SYS_extnotify",
+               "SYS_IGnet Queue",
+               "SYS_indexer",
+               "SYS_network",
+               "SYS_popclient",
+               "SYS_purger",
+               "SYS_rssclient",
+               "SYS_select_on_master",
+               "SYS_SMTP Send"
+       };
+
+       int i;
+       struct ctdluser usbuf;
+       for (i=0; i<(sizeof(deleteusers)/sizeof(char *)); ++i) {
+               if (CtdlGetUser(&usbuf, deleteusers[i]) == 0) {
+                       usbuf.axlevel = 0;
+                       strcpy(usbuf.password, "deleteme");
+                       CtdlPutUser(&usbuf);
+                       syslog(LOG_INFO,
+                               "System user account <%s> is no longer in use and will be deleted.",
+                               deleteusers[i]
+                       );
+               }
+       }
+}
+
+
+// Attempt to guess the name of the time zone currently in use
+// on the underlying host system.
+void guess_time_zone(void) {
+       FILE *fp;
+       char buf[PATH_MAX];
+
+       fp = popen(file_guesstimezone, "r");
+       if (fp) {
+               if (fgets(buf, sizeof buf, fp) && (strlen(buf) > 2)) {
+                       buf[strlen(buf)-1] = 0;
+                       CtdlSetConfigStr("c_default_cal_zone", buf);
+                       syslog(LOG_INFO, "Configuring timezone: %s", buf);
+               }
+               fclose(fp);
+       }
+}
+
+
+// Per-room callback function for ingest_old_roominfo_and_roompic_files()
+// This is the second pass, where we process the list of rooms with info or pic files.
+void iorarf_oneroom(char *roomname, char *infofile, char *picfile) {
+       FILE *fp;
+       long data_length;
+       char *unencoded_data;
+       char *encoded_data;
+       long info_msgnum = 0;
+       long pic_msgnum = 0;
+       char subject[SIZ];
+
+       // Test for the presence of a legacy "room info file"
+       if (!IsEmptyStr(infofile)) {
+               fp = fopen(infofile, "r");
+       }
+       else {
+               fp = NULL;
+       }
+       if (fp) {
+               fseek(fp, 0, SEEK_END);
+               data_length = ftell(fp);
+
+               if (data_length >= 1) {
+                       rewind(fp);
+                       unencoded_data = malloc(data_length);
+                       if (unencoded_data) {
+                               fread(unencoded_data, data_length, 1, fp);
+                               encoded_data = malloc((data_length * 2) + 100);
+                               if (encoded_data) {
+                                       sprintf(encoded_data, "Content-type: text/plain\nContent-transfer-encoding: base64\n\n");
+                                       CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
+                                       snprintf(subject, sizeof subject, "Imported room banner for %s", roomname);
+                                       info_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, encoded_data, FMT_RFC822, subject);
+                                       free(encoded_data);
+                               }
+                               free(unencoded_data);
+                       }
+               }
+               fclose(fp);
+               if (info_msgnum > 0) unlink(infofile);
+       }
+
+       // Test for the presence of a legacy "room picture file" and import it.
+       if (!IsEmptyStr(picfile)) {
+               fp = fopen(picfile, "r");
+       }
+       else {
+               fp = NULL;
+       }
+       if (fp) {
+               fseek(fp, 0, SEEK_END);
+               data_length = ftell(fp);
+
+               if (data_length >= 1) {
+                       rewind(fp);
+                       unencoded_data = malloc(data_length);
+                       if (unencoded_data) {
+                               fread(unencoded_data, data_length, 1, fp);
+                               encoded_data = malloc((data_length * 2) + 100);
+                               if (encoded_data) {
+                                       sprintf(encoded_data, "Content-type: image/gif\nContent-transfer-encoding: base64\n\n");
+                                       CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
+                                       snprintf(subject, sizeof subject, "Imported room icon for %s", roomname);
+                                       pic_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, encoded_data, FMT_RFC822, subject);
+                                       free(encoded_data);
+                               }
+                               free(unencoded_data);
+                       }
+               }
+               fclose(fp);
+               if (pic_msgnum > 0) unlink(picfile);
+       }
+
+       // Now we have the message numbers of our new banner and icon.  Record them in the room record.
+       // NOTE: we are not deleting the old msgnum_info because that position in the record was previously
+       // a pointer to the highest message number which existed in the room when the info file was saved,
+       // and we don't want to delete messages that are not *actually* old banners.
+       struct ctdlroom qrbuf;
+       if (CtdlGetRoomLock(&qrbuf, roomname) == 0) {
+               qrbuf.msgnum_info = info_msgnum;
+               qrbuf.msgnum_pic = pic_msgnum;
+               CtdlPutRoomLock(&qrbuf);
+       }
+
+}
+
+
+struct iorarf_list {
+       struct iorarf_list *next;
+       char name[ROOMNAMELEN];
+       char info[PATH_MAX];
+       char pic[PATH_MAX];
+};
+
+
+// Per-room callback function for ingest_old_roominfo_and_roompic_files()
+// This is the first pass, where the list of qualifying rooms is gathered.
+void iorarf_backend(struct ctdlroom *qrbuf, void *data) {
+       FILE *fp;
+       struct iorarf_list **iorarf_list = (struct iorarf_list **)data;
+
+       struct iorarf_list *i = malloc(sizeof(struct iorarf_list));
+       i->next = *iorarf_list;
+       strcpy(i->name, qrbuf->QRname);
+       strcpy(i->info, "");
+       strcpy(i->pic, "");
+
+       // Test for the presence of a legacy "room info file"
+       assoc_file_name(i->info, sizeof i->info, qrbuf, ctdl_info_dir);
+       fp = fopen(i->info, "r");
+       if (fp) {
+               fclose(fp);
+       }
+       else {
+               i->info[0] = 0;
+       }
+
+       // Test for the presence of a legacy "room picture file"
+       assoc_file_name(i->pic, sizeof i->pic, qrbuf, ctdl_image_dir);
+       fp = fopen(i->pic, "r");
+       if (fp) {
+               fclose(fp);
+       }
+       else {
+               i->pic[0] = 0;
+       }
+
+       if ( (!IsEmptyStr(i->info)) || (!IsEmptyStr(i->pic)) ) {
+               *iorarf_list = i;
+       }
+       else {
+               free(i);
+       }
+}
+
+
+// Prior to Citadel Server version 902, room info and pictures (which comprise the
+// displayed banner for each room) were stored in the filesystem.  If we are upgrading
+// from version >000 to version >=902, ingest those files into the database.
+void ingest_old_roominfo_and_roompic_files(void) {
+       struct iorarf_list *il = NULL;
+
+       CtdlForEachRoom(iorarf_backend, &il);
+
+       struct iorarf_list *p;
+       while (il) {
+               iorarf_oneroom(il->name, il->info, il->pic);
+               p = il->next;
+               free(il);
+               il = p;
+       }
+
+       unlink(ctdl_info_dir);
+}
+
+
+// For upgrades in which a new config setting appears for the first time, set default values.
+// For new installations (oldver == 0) also set default values.
+void update_config(void) {
+
+       if (oldver < 606) {
+               CtdlSetConfigInt("c_rfc822_strict_from", 0);
+       }
+
+       if (oldver < 609) {
+               CtdlSetConfigInt("c_purge_hour", 3);
+       }
+
+       if (oldver < 615) {
+               CtdlSetConfigInt("c_ldap_port", 389);
+       }
+
+       if (oldver < 623) {
+               CtdlSetConfigStr("c_ip_addr", "*");
+       }
+
+       if (oldver < 650) {
+               CtdlSetConfigInt("c_enable_fulltext", 1);
+       }
+
+       if (oldver < 652) {
+               CtdlSetConfigInt("c_auto_cull", 1);
+       }
+
+       if (oldver < 725) {
+               CtdlSetConfigInt("c_xmpp_c2s_port", 5222);
+               CtdlSetConfigInt("c_xmpp_s2s_port", 5269);
+       }
+
+       if (oldver < 830) {
+               CtdlSetConfigInt("c_nntp_port", 119);
+               CtdlSetConfigInt("c_nntps_port", 563);
+       }
+
+       if (IsEmptyStr(CtdlGetConfigStr("c_default_cal_zone"))) {
+               guess_time_zone();
+       }
+}
+
+
+// Helper function for move_inet_addrs_from_vcards_to_user_records()
+//
+// Call this function as a ForEachUser backend in order to queue up
+// user names, or call it with a null user to make it do the processing.
+// This allows us to maintain the list as a static instead of passing
+// pointers around.
+void miafvtur_backend(char *username, void *data) {
+       struct ctdluser usbuf;
+       char primary_inet_email[512] = { 0 };
+       char other_inet_emails[512] = { 0 };
+       char combined_inet_emails[512] = { 0 };
+
+       if (CtdlGetUser(&usbuf, username) != 0) {
+               return;
+       }
+
+       struct vCard *v = vcard_get_user(&usbuf);
+       if (!v) return;
+       extract_inet_email_addrs(primary_inet_email, sizeof primary_inet_email, other_inet_emails, sizeof other_inet_emails, v, 1);
+       vcard_free(v);
+       
+       if ( (IsEmptyStr(primary_inet_email)) && (IsEmptyStr(other_inet_emails)) ) {
+               return;
+       }
+
+       snprintf(combined_inet_emails, 512, "%s%s%s",
+               (!IsEmptyStr(primary_inet_email) ? primary_inet_email : ""),
+               ((!IsEmptyStr(primary_inet_email)&&(!IsEmptyStr(other_inet_emails))) ? "|" : ""),
+               (!IsEmptyStr(other_inet_emails) ? other_inet_emails : "")
+       );
+
+       CtdlSetEmailAddressesForUser(usbuf.fullname, combined_inet_emails);
+}
+
+
+// If our system still has a "refcount_adjustments.dat" sitting around from an old version, ingest it now.
+int ProcessOldStyleAdjRefCountQueue(void) {
+       int r;
+       FILE *fp;
+       struct arcq arcq_rec;
+       int num_records_processed = 0;
+
+       fp = fopen(file_arcq, "rb");
+       if (fp == NULL) {
+               return(num_records_processed);
+       }
+
+       syslog(LOG_INFO, "msgbase: ingesting %s", file_arcq);
+
+       while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
+               AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
+               ++num_records_processed;
+       }
+
+       fclose(fp);
+       r = unlink(file_arcq);
+       if (r != 0) {
+               syslog(LOG_ERR, "%s: %m", file_arcq);
+       }
+
+       return(num_records_processed);
+}
+
+
+// Prior to version 912 we kept a user's various Internet email addresses in their vCards.
+// This function moves them over to the user record, which is where we keep them now.
+void move_inet_addrs_from_vcards_to_user_records(void) {
+       ForEachUser(miafvtur_backend, NULL);
+       CtdlRebuildDirectoryIndex();
+}
+
+
+// We found the legacy sieve config in the user's config room.  Store the message number in the user record.
+void mifm_found_config(long msgnum, void *userdata) {
+       struct ctdluser *us = (struct ctdluser *)userdata;
+
+       us->msgnum_inboxrules = msgnum;
+       syslog(LOG_DEBUG, "user: <%s> inbox filter msgnum: <%ld>", us->fullname, us->msgnum_inboxrules);
+}
+
+
+// Helper function for migrate_inbox_filter_msgnums()
+void mifm_backend(char *username, void *data) {
+       struct ctdluser us;
+       char roomname[ROOMNAMELEN];
+
+       if (CtdlGetUserLock(&us, username) == 0) {
+               // Take a spin through the user's personal config room
+               syslog(LOG_DEBUG, "Processing <%s> (%ld)", us.fullname, us.usernum);
+               snprintf(roomname, sizeof roomname, "%010ld.%s", us.usernum, USERCONFIGROOM);
+               if (CtdlGetRoom(&CC->room, roomname) == 0) {
+                       CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL, mifm_found_config, (void *)&us );
+               }
+               CtdlPutUserLock(&us);
+       }
+}
+
+
+// Prior to version 930 we used a MIME type search to locate the user's inbox filter rules. 
+// This function locates those ruleset messages and simply stores the message number in the user record.
+void migrate_inbox_filter_msgnums(void) {
+       ForEachUser(mifm_backend, NULL);
+}
+
+
+// Create a default administrator account so we can log in to a new installation
+void create_default_admin_account(void) {
+       struct ctdluser usbuf;
+
+       create_user(DEFAULT_ADMIN_USERNAME, CREATE_USER_DO_NOT_BECOME_USER, (-1));
+       CtdlGetUser(&usbuf, DEFAULT_ADMIN_USERNAME);
+       safestrncpy(usbuf.password, DEFAULT_ADMIN_PASSWORD, sizeof(usbuf.password));
+       usbuf.axlevel = AxAideU;
+       CtdlPutUser(&usbuf);
+       CtdlSetConfigStr("c_sysadm", DEFAULT_ADMIN_USERNAME);
+}
+
+
+// Based on the server version number reported by the existing database,
+// run in-place data format upgrades until everything is up to date.
+void pre_startup_upgrades(void) {
+
+       oldver = CtdlGetConfigInt("MM_hosted_upgrade_level");
+       syslog(LOG_INFO, "Existing database version on disk is %d", oldver);
+       update_config();
+
+       if (oldver < REV_LEVEL) {
+               syslog(LOG_WARNING, "Running pre-startup database upgrades.");
+       }
+       else {
+               return;
+       }
+
+       if ((oldver > 000) && (oldver < 591)) {
+               syslog(LOG_EMERG, "This database is too old to be upgraded.  Citadel server will exit.");
+               exit(EXIT_FAILURE);
+       }
+       if ((oldver > 000) && (oldver < 913)) {
+               reindex_uids();
+       }
+       if ((oldver > 000) && (oldver < 659)) {
+               rebuild_euid_index();
+       }
+       if (oldver < 735) {
+               fix_sys_user_name();
+       }
+       if (oldver < 736) {
+               rebuild_usersbynumber();
+       }
+       if (oldver < 790) {
+               remove_thread_users();
+       }
+       if (oldver < 810) {
+               struct ctdlroom QRoom;
+               if (!CtdlGetRoom(&QRoom, SMTP_SPOOLOUT_ROOM)) {
+                       QRoom.QRdefaultview = VIEW_QUEUE;
+                       CtdlPutRoom(&QRoom);
+               }
+       }
+
+       if ((oldver > 000) && (oldver < 902)) {
+               ingest_old_roominfo_and_roompic_files();
+       }
+
+       CtdlSetConfigInt("MM_hosted_upgrade_level", REV_LEVEL);
+
+       // Negative values for maxsessions are not allowed.
+       if (CtdlGetConfigInt("c_maxsessions") < 0) {
+               CtdlSetConfigInt("c_maxsessions", 0);
+       }
+
+       // We need a system default message expiry policy, because this is
+       // the top level and there's no 'higher' policy to fall back on.
+       // By default, do not expire messages at all.
+       if (CtdlGetConfigInt("c_ep_mode") == 0) {
+               CtdlSetConfigInt("c_ep_mode", EXPIRE_MANUAL);
+               CtdlSetConfigInt("c_ep_value", 0);
+       }
+
+       // If this is the first run on an empty database, create a default administrator
+       if (oldver == 0) {
+               create_default_admin_account();
+       }
+}
+
+
+// Based on the server version number reported by the existing database,
+// run in-place data format upgrades until everything is up to date.
+void post_startup_upgrades(void) {
+
+       syslog(LOG_INFO, "Existing database version on disk is %d", oldver);
+
+       if (oldver < REV_LEVEL) {
+               syslog(LOG_WARNING, "Running post-startup database upgrades.");
+       }
+       else {
+               return;
+       }
+
+       if ((oldver > 000) && (oldver < 912)) {
+               move_inet_addrs_from_vcards_to_user_records();
+       }
+
+       if ((oldver > 000) && (oldver < 922)) {
+               ProcessOldStyleAdjRefCountQueue();
+       }
+
+       if ((oldver > 000) && (oldver < 930)) {
+               migrate_inbox_filter_msgnums();
+       }
+
+}
+
+
+char *ctdl_module_init_upgrade(void) {
+       if (!threading) {
+               post_startup_upgrades();
+       }
+
+       /* return our module name for the log */
+        return "upgrade";
+}
diff --git a/citadel/server/modules/upgrade/serv_upgrade.h b/citadel/server/modules/upgrade/serv_upgrade.h
new file mode 100644 (file)
index 0000000..14c6142
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 1987-2012 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ * 
+ * 
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 
+ * 
+ * 
+ */
+
diff --git a/citadel/server/modules/vcard/.gitignore b/citadel/server/modules/vcard/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/vcard/serv_vcard.c b/citadel/server/modules/vcard/serv_vcard.c
new file mode 100644 (file)
index 0000000..61d9ef7
--- /dev/null
@@ -0,0 +1,1252 @@
+/*
+ * A server-side module for Citadel which supports address book information
+ * using the standard vCard format.
+ * 
+ * Copyright (c) 1999-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Format of the "Exclusive ID" field of the message containing a user's
+ * vCard.  Doesn't matter what it really looks like as long as it's both
+ * unique and consistent (because we use it for replication checking to
+ * delete the old vCard network-wide when the user enters a new one).
+ */
+#define VCARD_EXT_FORMAT       "Citadel vCard: personal card for %s at %s"
+
+/*
+ * Citadel will accept either text/vcard or text/x-vcard as the MIME type
+ * for a vCard.  The following definition determines which one it *generates*
+ * when serializing.
+ */
+#define VCARD_MIME_TYPE                "text/x-vcard"
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../room_ops.h"
+#include "../../internet_addressing.h"
+#include "../../serv_vcard.h"
+#include "../../citadel_ldap.h"
+#include "../../ctdl_module.h"
+
+/*
+ * set global flag calling for an aide to validate new users
+ */
+void set_mm_valid(void) {
+       int flags = 0;
+
+       begin_critical_section(S_CONTROL);
+       flags = CtdlGetConfigInt("MMflags");
+       flags = flags | MM_VALID ;
+       CtdlSetConfigInt("MMflags", flags);
+       end_critical_section(S_CONTROL);
+}
+
+
+///TODO: gettext!
+#define _(a) a
+
+/*
+ * See if there is a valid Internet address in a vCard to use for outbound
+ * Internet messages.  If there is, stick it in the buffer.
+ */
+void extract_inet_email_addrs(char *emailaddrbuf, size_t emailaddrbuf_len,
+                             char *secemailaddrbuf, size_t secemailaddrbuf_len,
+                             struct vCard *v,
+                             int local_addrs_only
+) {
+       char *s, *k, *addr;
+       int instance = 0;
+       int IsDirectoryAddress;
+       int saved_instance = 0;
+
+       /* Go through the vCard searching for *all* Internet email addresses
+        */
+       while (s = vcard_get_prop(v, "email", 1, instance, 0),  s != NULL) {
+               k = vcard_get_prop(v, "email", 1, instance, 1);
+               if ( (s != NULL) && (k != NULL) && (bmstrcasestr(k, "internet")) ) {
+                       addr = strdup(s);
+                       striplt(addr);
+                       if (!IsEmptyStr(addr)) {
+                               IsDirectoryAddress = IsDirectory(addr, 1);
+
+                               syslog(LOG_DEBUG, "EVQ: addr=<%s> IsDirectoryAddress=<%d> local_addrs_only=<%d>", addr, IsDirectoryAddress, local_addrs_only);
+
+                               if ( IsDirectoryAddress || !local_addrs_only)
+                               {
+                                       ++saved_instance;
+                                       if ((saved_instance == 1) && (emailaddrbuf != NULL)) {
+                                               safestrncpy(emailaddrbuf, addr, emailaddrbuf_len);
+                                       }
+                                       else if ((saved_instance == 2) && (secemailaddrbuf != NULL)) {
+                                               safestrncpy(secemailaddrbuf, addr, secemailaddrbuf_len);
+                                       }
+                                       else if ((saved_instance > 2) && (secemailaddrbuf != NULL)) {
+                                               if ( (strlen(addr) + strlen(secemailaddrbuf) + 2) 
+                                               < secemailaddrbuf_len ) {
+                                                       strcat(secemailaddrbuf, "|");
+                                                       strcat(secemailaddrbuf, addr);
+                                               }
+                                       }
+                               }
+                               if (!IsDirectoryAddress && local_addrs_only)
+                               {
+                                       StrBufAppendPrintf(CC->StatusMessage, "\n%d|", ERROR+ ILLEGAL_VALUE);
+                                       StrBufAppendBufPlain(CC->StatusMessage, addr, -1, 0);
+                                       StrBufAppendBufPlain(CC->StatusMessage, HKEY("|"), 0);
+                                       StrBufAppendBufPlain(CC->StatusMessage, _("unable to add this emailaddress; its not matching our domain."), -1, 0);
+                               }
+                       }
+                       free(addr);
+               }
+               ++instance;
+       }
+}
+
+
+/*
+ * See if there is a name / screen name / friendly name  in a vCard to use for outbound
+ * Internet messages.  If there is, stick it in the buffer.
+ */
+void extract_friendly_name(char *namebuf, size_t namebuf_len, struct vCard *v)
+{
+       char *s;
+
+       s = vcard_get_prop(v, "fn", 1, 0, 0);
+       if (s == NULL) {
+               s = vcard_get_prop(v, "n", 1, 0, 0);
+       }
+
+       if (s != NULL) {
+               safestrncpy(namebuf, s, namebuf_len);
+       }
+}
+
+
+/*
+ * Callback function for vcard_upload_beforesave() hunts for the real vcard in the MIME structure
+ */
+void vcard_extract_vcard(char *name, char *filename, char *partnum, char *disp,
+                  void *content, char *cbtype, char *cbcharset, size_t length,
+                  char *encoding, char *cbid, void *cbuserdata)
+{
+       struct vCard **v = (struct vCard **) cbuserdata;
+
+       if (  (!strcasecmp(cbtype, "text/x-vcard"))
+          || (!strcasecmp(cbtype, "text/vcard")) ) {
+
+               syslog(LOG_DEBUG, "vcard: part %s contains a vCard!  Loading...", partnum);
+               if (*v != NULL) {
+                       vcard_free(*v);
+               }
+               *v = vcard_load(content);
+       }
+}
+
+
+/*
+ * This handler detects whether the user is attempting to save a new
+ * vCard as part of his/her personal configuration, and handles the replace
+ * function accordingly (delete the user's existing vCard in the config room
+ * and in the global address book).
+ */
+int vcard_upload_beforesave(struct CtdlMessage *msg, struct recptypes *recp) {
+       char *s;
+       char buf[SIZ];
+       struct ctdluser usbuf;
+       long what_user;
+       struct vCard *v = NULL;
+       char *ser = NULL;
+       int i = 0;
+       int yes_my_citadel_config = 0;
+       int yes_any_vcard_room = 0;
+
+       if ((!CC->logged_in) && (CC->vcard_updated_by_ldap==0)) return(0);      /* Only do this if logged in, or if ldap changed the vcard. */
+
+       /* Is this some user's "My Citadel Config" room? */
+       if (((CC->room.QRflags & QR_MAILBOX) != 0) &&
+             (!strcasecmp(&CC->room.QRname[11], USERCONFIGROOM)) ) {
+               /* Yes, we want to do this */
+               yes_my_citadel_config = 1;
+#ifdef VCARD_SAVES_BY_AIDES_ONLY
+               /* Prevent non-aides from performing registration changes, but ldap is ok. */
+               if ((CC->user.axlevel < AxAideU) && (CC->vcard_updated_by_ldap==0)) {
+                       return(1);
+               }
+#endif
+
+       }
+
+       /* Is this a room with an address book in it? */
+       if (CC->room.QRdefaultview == VIEW_ADDRESSBOOK) {
+               yes_any_vcard_room = 1;
+       }
+
+       /* If neither condition exists, don't run this hook. */
+       if ( (!yes_my_citadel_config) && (!yes_any_vcard_room) ) {
+               return(0);
+       }
+
+       /* If this isn't a MIME message, don't bother. */
+       if (msg->cm_format_type != 4) return(0);
+
+       /* Ok, if we got this far, look into the situation further... */
+
+       if (CM_IsEmpty(msg, eMesageText)) return(0);
+
+       mime_parser(CM_RANGE(msg, eMesageText),
+                   *vcard_extract_vcard,
+                   NULL, NULL,
+                   &v,         /* user data ptr - put the vcard here */
+                   0
+       );
+
+       if (v == NULL) return(0);       /* no vCards were found in this message */
+
+       /* If users cannot create their own accounts, they cannot re-register either. */
+       if ( (yes_my_citadel_config) &&
+            (CtdlGetConfigInt("c_disable_newu")) &&
+            (CC->user.axlevel < AxAideU) &&
+            (CC->vcard_updated_by_ldap==0) )
+       {
+               return(1);
+       }
+
+       vcard_get_prop(v, "fn", 1, 0, 0);
+
+
+       if (yes_my_citadel_config) {
+               /* Bingo!  The user is uploading a new vCard, so
+                * delete the old one.  First, figure out which user
+                * is being re-registered...
+                */
+               what_user = atol(CC->room.QRname);
+
+               if (what_user == CC->user.usernum) {
+                       /* It's the logged in user.  That was easy. */
+                       memcpy(&usbuf, &CC->user, sizeof(struct ctdluser));
+               }
+               
+               else if (CtdlGetUserByNumber(&usbuf, what_user) == 0) {
+                       /* We fetched a valid user record */
+               }
+
+               else {
+                       /* somebody set up us the bomb! */
+                       yes_my_citadel_config = 0;
+               }
+       }
+       
+       if (yes_my_citadel_config) {
+               /* Delete the user's old vCard.  This would probably
+                * get taken care of by the replication check, but we
+                * want to make sure there is absolutely only one
+                * vCard in the user's config room at all times.
+                *
+                */
+               CtdlDeleteMessages(CC->room.QRname, NULL, 0, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$");
+
+               /* Make the author of the message the name of the user. */
+               if (!IsEmptyStr(usbuf.fullname)) {
+                       CM_SetField(msg, eAuthor, usbuf.fullname, strlen(usbuf.fullname));
+               }
+       }
+
+       /* Insert or replace RFC2739-compliant free/busy URL */
+       if (yes_my_citadel_config) {
+               sprintf(buf, "http://%s/%s.vfb",
+                       CtdlGetConfigStr("c_fqdn"),
+                       usbuf.fullname);
+               for (i=0; buf[i]; ++i) {
+                       if (buf[i] == ' ') buf[i] = '_';
+               }
+               vcard_set_prop(v, "FBURL;PREF", buf, 0);
+       }
+
+
+       s = vcard_get_prop(v, "UID", 1, 0, 0);
+       if (s == NULL) { /* Note LDAP auth sets UID from the LDAP UUID, use that if it exists. */
+         /* Enforce local UID policy if applicable */
+         if (yes_my_citadel_config) {
+               snprintf(buf, sizeof buf, VCARD_EXT_FORMAT, msg->cm_fields[eAuthor], NODENAME);
+         } else {
+               /* If the vCard has no UID, then give it one. */
+               generate_uuid(buf);
+         }
+         vcard_set_prop(v, "UID", buf, 0);
+    }
+
+
+       /* 
+        * Set the EUID of the message to the UID of the vCard.
+        */
+       CM_FlushField(msg, eExclusiveID);
+
+       s = vcard_get_prop(v, "UID", 1, 0, 0);
+       if (!IsEmptyStr(s)) {
+               CM_SetField(msg, eExclusiveID, s, strlen(s));
+               if (CM_IsEmpty(msg, eMsgSubject)) {
+                       CM_CopyField(msg, eMsgSubject, eExclusiveID);
+               }
+       }
+
+       /*
+        * Set the Subject to the name in the vCard.
+        */
+       s = vcard_get_prop(v, "FN", 1, 0, 0);
+       if (s == NULL) {
+               s = vcard_get_prop(v, "N", 1, 0, 0);
+       }
+       if (!IsEmptyStr(s)) {
+               CM_SetField(msg, eMsgSubject, s, strlen(s));
+       }
+
+       /* Re-serialize it back into the msg body */
+       ser = vcard_serialize(v);
+       if (!IsEmptyStr(ser)) {
+               StrBuf *buf;
+               long serlen;
+
+               serlen = strlen(ser);
+               buf = NewStrBufPlain(NULL, serlen + 1024);
+
+               StrBufAppendBufPlain(buf, HKEY("Content-type: " VCARD_MIME_TYPE "\r\n\r\n"), 0);
+               StrBufAppendBufPlain(buf, ser, serlen, 0);
+               StrBufAppendBufPlain(buf, HKEY("\r\n"), 0);
+               CM_SetAsFieldSB(msg, eMesageText, &buf);
+               free(ser);
+       }
+
+       /* Now allow the save to complete. */
+       vcard_free(v);
+       return(0);
+}
+
+
+/*
+ * This handler detects whether the user is attempting to save a new
+ * vCard as part of his/her personal configuration, and handles the replace
+ * function accordingly (copy the vCard from the config room to the global
+ * address book).
+ */
+int vcard_upload_aftersave(struct CtdlMessage *msg, struct recptypes *recp) {
+       char *ptr;
+       int linelen;
+       long I;
+       struct vCard *v;
+       int is_UserConf=0;
+       int is_MY_UserConf=0;
+       int is_GAB=0;
+       char roomname[ROOMNAMELEN];
+
+       if (msg->cm_format_type != 4) return(0);
+       if ((!CC->logged_in) && (CC->vcard_updated_by_ldap==0)) return(0);      /* Only do this if logged in, or if ldap changed the vcard. */
+
+       /* We're interested in user config rooms only. */
+
+       if ( !IsEmptyStr(CC->room.QRname) &&
+             (strlen(CC->room.QRname) >= 12) &&
+             (!strcasecmp(&CC->room.QRname[11], USERCONFIGROOM)) ) {
+               is_UserConf = 1;        /* It's someone's config room */
+       }
+       CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCONFIGROOM);
+       if (!strcasecmp(CC->room.QRname, roomname)) {
+               is_UserConf = 1;
+               is_MY_UserConf = 1;     /* It's MY config room */
+       }
+       if (!strcasecmp(CC->room.QRname, ADDRESS_BOOK_ROOM)) {
+               is_GAB = 1;             /* It's the Global Address Book */
+       }
+
+       if (!is_UserConf && !is_GAB) return(0);
+
+       if (CM_IsEmpty(msg, eMesageText))
+               return 0;
+
+       ptr = msg->cm_fields[eMesageText];
+
+       CC->vcard_updated_by_ldap=0;  /* As this will write LDAP's previous changes, disallow LDAP change auth until next LDAP change. */ 
+
+       NewStrBufDupAppendFlush(&CC->StatusMessage, NULL, NULL, 0);
+
+       StrBufPrintf(CC->StatusMessage, "%d\n", LISTING_FOLLOWS);
+
+       while (ptr != NULL) {
+       
+               linelen = strcspn(ptr, "\n");
+               if (linelen == 0) return(0);    /* end of headers */    
+               
+               if (  (!strncasecmp(ptr, "Content-type: text/x-vcard", 26))
+                  || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
+                       /*
+                        * Bingo!  The user is uploading a new vCard, so
+                        * copy it to the Global Address Book room.
+                        */
+
+                       I = atol(msg->cm_fields[eVltMsgNum]);
+                       if (I <= 0L) return(0);
+
+                       /* Store our friendly/display name in memory */
+                       if (is_MY_UserConf) {
+                               v = vcard_load(msg->cm_fields[eMesageText]);
+                               extract_friendly_name(CC->cs_inet_fn, sizeof CC->cs_inet_fn, v);
+                               vcard_free(v);
+                       }
+
+                       if (!is_GAB)
+                       {       // This is not the GAB
+                               /* Put it in the Global Address Book room... */
+                               CtdlSaveMsgPointerInRoom(ADDRESS_BOOK_ROOM, I, 1, msg);
+                       }
+
+                       /* Some sites want an Aide to be notified when a
+                        * user registers or re-registers
+                        * But if the user was an Aide or was edited by an Aide then we can
+                        * Assume they don't need validating.
+                        */
+                       if (CC->user.axlevel >= AxAideU) {
+                               CtdlLockGetCurrentUser();
+                               CC->user.flags |= US_REGIS;
+                               CtdlPutCurrentUserLock();
+                               return (0);
+                       }
+                       
+                       set_mm_valid();
+
+                       /* ...which also means we need to flag the user */
+                       CtdlLockGetCurrentUser();
+                       CC->user.flags |= (US_REGIS|US_NEEDVALID);
+                       CtdlPutCurrentUserLock();
+
+                       return(0);
+               }
+
+               ptr = strchr((char *)ptr, '\n');
+               if (ptr != NULL) ++ptr;
+       }
+
+       return(0);
+}
+
+
+
+/*
+ * back end function used for callbacks
+ */
+void vcard_gu_backend(long supplied_msgnum, void *userdata) {
+       long *msgnum;
+
+       msgnum = (long *) userdata;
+       *msgnum = supplied_msgnum;
+}
+
+
+/*
+ * If this user has a vcard on disk, read it into memory, otherwise allocate
+ * and return an empty vCard.
+ */
+struct vCard *vcard_get_user(struct ctdluser *u) {
+       char hold_rm[ROOMNAMELEN];
+       char config_rm[ROOMNAMELEN];
+       struct CtdlMessage *msg = NULL;
+       struct vCard *v;
+       long VCmsgnum;
+
+       strcpy(hold_rm, CC->room.QRname);       /* save current room */
+       CtdlMailboxName(config_rm, sizeof config_rm, u, USERCONFIGROOM);
+
+       if (CtdlGetRoom(&CC->room, config_rm) != 0) {
+               CtdlGetRoom(&CC->room, hold_rm);
+               return vcard_new();
+       }
+
+       /* We want the last (and probably only) vcard in this room */
+       VCmsgnum = (-1);
+       CtdlForEachMessage(MSGS_LAST, 1, NULL, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$",
+               NULL, vcard_gu_backend, (void *)&VCmsgnum );
+       CtdlGetRoom(&CC->room, hold_rm);        /* return to saved room */
+
+       if (VCmsgnum < 0L) return vcard_new();
+
+       msg = CtdlFetchMessage(VCmsgnum, 1);
+       if (msg == NULL) return vcard_new();
+
+       v = vcard_load(msg->cm_fields[eMesageText]);
+       CM_Free(msg);
+       return v;
+}
+
+
+/*
+ * Store this user's vCard in the appropriate place
+ */
+/*
+ * Write our config to disk
+ */
+void vcard_write_user(struct ctdluser *u, struct vCard *v) {
+       char *ser;
+
+       ser = vcard_serialize(v);
+       if (ser == NULL) {
+               ser = strdup("begin:vcard\r\nend:vcard\r\n");
+       }
+       if (ser == NULL) return;
+
+       /* This handy API function does all the work for us.
+        * NOTE: normally we would want to set that last argument to 1, to
+        * force the system to delete the user's old vCard.  But it doesn't
+        * have to, because the vcard_upload_beforesave() hook above
+        * is going to notice what we're trying to do, and delete the old vCard.
+        */
+       CtdlWriteObject(USERCONFIGROOM,         /* which room */
+                       VCARD_MIME_TYPE,        /* MIME type */
+                       ser,                    /* data */
+                       strlen(ser)+1,          /* length */
+                       u,                      /* which user */
+                       0,                      /* not binary */
+                       0);                     /* no flags */
+
+       free(ser);
+}
+
+
+
+/*
+ * Old style "enter registration info" command.  This function simply honors
+ * the REGI protocol command, translates the entered parameters into a vCard,
+ * and enters the vCard into the user's configuration.
+ */
+void cmd_regi(char *argbuf) {
+       int a,b,c;
+       char buf[SIZ];
+       struct vCard *my_vcard;
+
+       char tmpaddr[SIZ];
+       char tmpcity[SIZ];
+       char tmpstate[SIZ];
+       char tmpzip[SIZ];
+       char tmpaddress[SIZ];
+       char tmpcountry[SIZ];
+
+       unbuffer_output();
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR + NOT_LOGGED_IN);
+               return;
+       }
+
+       /* If users cannot create their own accounts, they cannot re-register either. */
+       if ( (CtdlGetConfigInt("c_disable_newu")) && (CC->user.axlevel < AxAideU) ) {
+               cprintf("%d Self-service registration is not allowed here.\n",
+                       ERROR + HIGHER_ACCESS_REQUIRED);
+       }
+
+       my_vcard = vcard_get_user(&CC->user);
+       strcpy(tmpaddr, "");
+       strcpy(tmpcity, "");
+       strcpy(tmpstate, "");
+       strcpy(tmpzip, "");
+       strcpy(tmpcountry, "USA");
+
+       cprintf("%d Send registration...\n", SEND_LISTING);
+       a=0;
+       while (client_getln(buf, sizeof buf), strcmp(buf,"000")) {
+               if (a==0) vcard_set_prop(my_vcard, "n", buf, 0);
+               if (a==1) strcpy(tmpaddr, buf);
+               if (a==2) strcpy(tmpcity, buf);
+               if (a==3) strcpy(tmpstate, buf);
+               if (a==4) {
+                       for (c=0; buf[c]; ++c) {
+                               if ((buf[c]>='0') && (buf[c]<='9')) {
+                                       b = strlen(tmpzip);
+                                       tmpzip[b] = buf[c];
+                                       tmpzip[b+1] = 0;
+                               }
+                       }
+               }
+               if (a==5) vcard_set_prop(my_vcard, "tel", buf, 0);
+               if (a==6) vcard_set_prop(my_vcard, "email;internet", buf, 0);
+               if (a==7) strcpy(tmpcountry, buf);
+               ++a;
+       }
+
+       snprintf(tmpaddress, sizeof tmpaddress, ";;%s;%s;%s;%s;%s",
+               tmpaddr, tmpcity, tmpstate, tmpzip, tmpcountry);
+       vcard_set_prop(my_vcard, "adr", tmpaddress, 0);
+       vcard_write_user(&CC->user, my_vcard);
+       vcard_free(my_vcard);
+}
+
+
+/*
+ * Protocol command to fetch registration info for a user
+ */
+void cmd_greg(char *argbuf)
+{
+       struct ctdluser usbuf;
+       struct vCard *v;
+       char *s;
+       char who[USERNAME_SIZE];
+       char adr[256];
+       char buf[256];
+
+       extract_token(who, argbuf, 0, '|', sizeof who);
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+               return;
+       }
+
+       if (!strcasecmp(who,"_SELF_")) strcpy(who,CC->curr_user);
+
+       if ((CC->user.axlevel < AxAideU) && (strcasecmp(who,CC->curr_user))) {
+               cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+               return;
+       }
+
+       if (CtdlGetUser(&usbuf, who) != 0) {
+               cprintf("%d '%s' not found.\n", ERROR + NO_SUCH_USER, who);
+               return;
+       }
+
+       v = vcard_get_user(&usbuf);
+
+       cprintf("%d %s\n", LISTING_FOLLOWS, usbuf.fullname);
+       cprintf("%ld\n", usbuf.usernum);
+       cprintf("%s\n", usbuf.password);
+       s = vcard_get_prop(v, "n", 1, 0, 0);
+       cprintf("%s\n", s ? s : " ");                   /* name */
+       s = vcard_get_prop(v, "adr", 1, 0, 0);
+       snprintf(adr, sizeof adr, "%s", s ? s : " ");   /* address */
+       extract_token(buf, adr, 2, ';', sizeof buf);
+       cprintf("%s\n", buf);                           /* street */
+       extract_token(buf, adr, 3, ';', sizeof buf);
+       cprintf("%s\n", buf);                           /* city */
+       extract_token(buf, adr, 4, ';', sizeof buf);
+       cprintf("%s\n", buf);                           /* state */
+       extract_token(buf, adr, 5, ';', sizeof buf);
+       cprintf("%s\n", buf);                           /* zip */
+
+       s = vcard_get_prop(v, "tel", 1, 0, 0);
+       if (s == NULL) s = vcard_get_prop(v, "tel", 1, 0, 0);
+       if (s != NULL) {
+               cprintf("%s\n", s);
+       }
+       else {
+               cprintf(" \n");
+       }
+
+       cprintf("%d\n", usbuf.axlevel);
+
+       s = vcard_get_prop(v, "email;internet", 0, 0, 0);
+       cprintf("%s\n", s ? s : " ");
+       s = vcard_get_prop(v, "adr", 0, 0, 0);
+       snprintf(adr, sizeof adr, "%s", s ? s : " ");/* address... */
+
+       extract_token(buf, adr, 6, ';', sizeof buf);
+       cprintf("%s\n", buf);                           /* country */
+       cprintf("000\n");
+       vcard_free(v);
+}
+
+
+
+/*
+ * When a user is being created, create his/her vCard.
+ */
+void vcard_newuser(struct ctdluser *usbuf) {
+       char vname[256];
+       char buf[256];
+       int i;
+       struct vCard *v;
+       int need_default_vcard;
+
+       need_default_vcard =1;
+       vcard_fn_to_n(vname, usbuf->fullname, sizeof vname);
+       syslog(LOG_DEBUG, "vcard: converted <%s> to <%s>", usbuf->fullname, vname);
+
+       /* Create and save the vCard */
+       v = vcard_new();
+       if (v == NULL) return;
+       vcard_add_prop(v, "fn", usbuf->fullname);
+       vcard_add_prop(v, "n", vname);
+       vcard_add_prop(v, "adr", "adr:;;_;_;_;00000;__");
+
+#ifdef HAVE_GETPWUID_R
+       /* If using host auth mode, we add an email address based on the login */
+       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
+               struct passwd pwd;
+               char pwd_buffer[SIZ];
+               
+#ifdef SOLARIS_GETPWUID
+               if (getpwuid_r(usbuf->uid, &pwd, pwd_buffer, sizeof pwd_buffer) != NULL) {
+#else // SOLARIS_GETPWUID
+               struct passwd *result = NULL;
+               syslog(LOG_DEBUG, "vcard: searching for uid %d", usbuf->uid);
+               if (getpwuid_r(usbuf->uid, &pwd, pwd_buffer, sizeof pwd_buffer, &result) == 0) {
+#endif // HAVE_GETPWUID_R
+                       snprintf(buf, sizeof buf, "%s@%s", pwd.pw_name, CtdlGetConfigStr("c_fqdn"));
+                       vcard_add_prop(v, "email;internet", buf);
+                       need_default_vcard = 0;
+               }
+       }
+#endif
+
+       /*
+        * Is this an LDAP session?  If so, copy various LDAP attributes from the directory entry
+        * into the user's vCard.
+        */
+       if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
+               //uid_t ldap_uid;
+               int found_user;
+               char ldap_cn[512];
+               char ldap_dn[512];
+
+               syslog(LOG_DEBUG, "\033[31m FIXME BORK BORK BORK try lookup by uid , or maybe dn?\033[0m");
+
+               found_user = CtdlTryUserLDAP(usbuf->fullname, ldap_dn, sizeof ldap_dn, ldap_cn, sizeof ldap_cn, &usbuf->uid);
+               if (found_user == 0) {
+                       if (Ctdl_LDAP_to_vCard(ldap_dn, v)) {
+                               /* Allow global address book and internet directory update without login long enough to write this. */
+                               CC->vcard_updated_by_ldap++;  /* Otherwise we'll only update the user config. */
+                               need_default_vcard = 0;
+                               syslog(LOG_DEBUG, "vcard: LDAP Created Initial vCard for %s\n",usbuf->fullname);
+                       }
+               }
+       }
+
+       if (need_default_vcard!=0) {
+               /* Everyone gets an email address based on their display name */
+               snprintf(buf, sizeof buf, "%s@%s", usbuf->fullname, CtdlGetConfigStr("c_fqdn"));
+               for (i=0; buf[i]; ++i) {
+                       if (buf[i] == ' ') buf[i] = '_';
+               }
+               vcard_add_prop(v, "email;internet", buf);
+       }
+       vcard_write_user(usbuf, v);
+       vcard_free(v);
+}
+
+
+/*
+ * Get Valid Screen Names
+ */
+void cmd_gvsn(char *argbuf)
+{
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       cprintf("%d valid screen names:\n", LISTING_FOLLOWS);
+       cprintf("%s\n", CC->user.fullname);
+       if ( (!IsEmptyStr(CC->cs_inet_fn)) && (strcasecmp(CC->user.fullname, CC->cs_inet_fn)) ) {
+               cprintf("%s\n", CC->cs_inet_fn);
+       }
+       cprintf("000\n");
+}
+
+
+/*
+ * Get Valid Email Addresses
+ * FIXME this doesn't belong in serv_vcard.c anymore , maybe move it to internet_addressing.c
+ */
+void cmd_gvea(char *argbuf)
+{
+       int num_secondary_emails = 0;
+       int i;
+       char buf[256];
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       cprintf("%d valid email addresses:\n", LISTING_FOLLOWS);
+       if (!IsEmptyStr(CC->cs_inet_email)) {
+               cprintf("%s\n", CC->cs_inet_email);
+       }
+       if (!IsEmptyStr(CC->cs_inet_other_emails)) {
+               num_secondary_emails = num_tokens(CC->cs_inet_other_emails, '|');
+               for (i=0; i<num_secondary_emails; ++i) {
+                       extract_token(buf, CC->cs_inet_other_emails,i,'|',sizeof CC->cs_inet_other_emails);
+                       cprintf("%s\n", buf);
+               }
+       }
+       cprintf("000\n");
+}
+
+
+/*
+ * Callback function for cmd_dvca() that hunts for vCard content types
+ * and outputs any email addresses found within.
+ */
+void dvca_mime_callback(char *name, char *filename, char *partnum, char *disp,
+               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+               char *cbid, void *cbuserdata) {
+
+       struct vCard *v;
+       char displayname[256] = "";
+       int displayname_len;
+       char emailaddr[256] = "";
+       int i;
+       int has_commas = 0;
+
+       if ( (strcasecmp(cbtype, "text/vcard")) && (strcasecmp(cbtype, "text/x-vcard")) ) {
+               return;
+       }
+
+       v = vcard_load(content);
+       if (v == NULL) return;
+
+       extract_friendly_name(displayname, sizeof displayname, v);
+       extract_inet_email_addrs(emailaddr, sizeof emailaddr, NULL, 0, v, 0);
+
+       displayname_len = strlen(displayname);
+       for (i=0; i<displayname_len; ++i) {
+               if (displayname[i] == '\"') displayname[i] = ' ';
+               if (displayname[i] == ';') displayname[i] = ',';
+               if (displayname[i] == ',') has_commas = 1;
+       }
+       striplt(displayname);
+
+       cprintf("%s%s%s <%s>\n",
+               (has_commas ? "\"" : ""),
+               displayname,
+               (has_commas ? "\"" : ""),
+               emailaddr
+       );
+
+       vcard_free(v);
+}
+
+
+/*
+ * Back end callback function for cmd_dvca()
+ *
+ * It's basically just passed a list of message numbers, which we're going
+ * to fetch off the disk and then pass along to the MIME parser via another
+ * layer of callback...
+ */
+void dvca_callback(long msgnum, void *userdata) {
+       struct CtdlMessage *msg = NULL;
+
+       msg = CtdlFetchMessage(msgnum, 1);
+       if (msg == NULL) return;
+       mime_parser(CM_RANGE(msg, eMesageText),
+                   *dvca_mime_callback,        /* callback function */
+                   NULL, NULL,
+                   NULL,                       /* user data */
+                   0
+               );
+       CM_Free(msg);
+}
+
+
+/*
+ * Dump VCard Addresses
+ */
+void cmd_dvca(char *argbuf)
+{
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       cprintf("%d addresses:\n", LISTING_FOLLOWS);
+       CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, dvca_callback, NULL);
+       cprintf("000\n");
+}
+
+
+/*
+ * Query Directory
+ */
+void cmd_qdir(char *argbuf) {
+       char citadel_addr[256];
+       char internet_addr[256];
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
+
+       if (CtdlDirectoryLookup(citadel_addr, internet_addr, sizeof citadel_addr) != 0) {
+               cprintf("%d %s was not found.\n",
+                       ERROR + NO_SUCH_USER, internet_addr);
+               return;
+       }
+
+       cprintf("%d %s\n", CIT_OK, citadel_addr);
+}
+
+
+/*
+ * Query Directory, in fact an alias to match postfix tcp auth.
+ */
+void check_get(void) {
+       char internet_addr[256];
+
+       char cmdbuf[SIZ];
+
+       time(&CC->lastcmd);
+       memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
+       if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
+               syslog(LOG_ERR, "vcard: client disconnected: ending session.");
+               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
+               return;
+       }
+       syslog(LOG_INFO, ": %s", cmdbuf);
+       while (strlen(cmdbuf) < 3) strcat(cmdbuf, " ");
+       syslog(LOG_INFO, "[ %s]", cmdbuf);
+       
+       if (strncasecmp(cmdbuf, "GET ", 4)==0)
+       {
+               struct recptypes *rcpt;
+               char *argbuf = &cmdbuf[4];
+               
+               extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
+               rcpt = validate_recipients(internet_addr, NULL, CHECK_EXIST);
+               if (    (rcpt != NULL) &&
+                       (
+                               (*rcpt->recp_local != '\0') ||
+                               (*rcpt->recp_room != '\0')
+                       )
+               ) {
+                       cprintf("200 OK %s\n", internet_addr);
+                       syslog(LOG_INFO, "vcard: sending 200 OK for the room %s", rcpt->display_recp);
+               }
+               else 
+               {
+                       cprintf("500 REJECT no one here by that name.\n");
+                       
+                       syslog(LOG_INFO, "vcard: sending 500 REJECT no one here by that name: %s", internet_addr);
+               }
+               if (rcpt != NULL) 
+                       free_recipients(rcpt);
+       }
+       else {
+               cprintf("500 REJECT invalid Query.\n");
+               syslog(LOG_INFO, "vcard: sending 500 REJECT invalid query: %s", internet_addr);
+       }
+}
+
+
+void check_get_greeting(void) {
+/* dummy function, we have no greeting in this very simple protocol. */
+}
+
+
+/*
+ * We don't know if the Contacts room exists so we just create it at login
+ */
+void vcard_CtdlCreateRoom(void)
+{
+       struct ctdlroom qr;
+       visit vbuf;
+
+       /* Create the calendar room if it doesn't already exist */
+       CtdlCreateRoom(USERCONTACTSROOM, 4, "", 0, 1, 0, VIEW_ADDRESSBOOK);
+
+       /* Set expiration policy to manual; otherwise objects will be lost! */
+       if (CtdlGetRoomLock(&qr, USERCONTACTSROOM)) {
+               syslog(LOG_ERR, "vcard: couldn't get the user CONTACTS room!");
+               return;
+       }
+       qr.QRep.expire_mode = EXPIRE_MANUAL;
+       qr.QRdefaultview = VIEW_ADDRESSBOOK;    /* 2 = address book view */
+       CtdlPutRoomLock(&qr);
+
+       /* Set the view to a calendar view */
+       CtdlGetRelationship(&vbuf, &CC->user, &qr);
+       vbuf.v_view = 2;        /* 2 = address book view */
+       CtdlSetRelationship(&vbuf, &CC->user, &qr);
+
+       return;
+}
+
+
+
+
+/*
+ * When a user logs in...
+ */
+void vcard_session_login_hook(void) {
+       struct vCard *v = NULL;
+
+       /*
+        * Is this an LDAP session?  If so, copy various LDAP attributes from the directory entry
+        * into the user's vCard.
+        */
+       if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
+               v = vcard_get_user(&CC->user);
+               if (v) {
+                       if (Ctdl_LDAP_to_vCard(CC->ldap_dn, v)) {
+                               CC->vcard_updated_by_ldap++; /* Make sure changes make it to the global address book and internet directory, not just the user config. */
+                               syslog(LOG_DEBUG, "vcard: LDAP Detected vcard change");
+                               vcard_write_user(&CC->user, v);
+                       }
+               }
+       }
+
+       /*
+        * Extract the user's friendly/screen name
+        * These are inserted into the session data for various message entry commands to use.
+        */
+       v = vcard_get_user(&CC->user);
+       if (v) {
+               extract_friendly_name(CC->cs_inet_fn, sizeof CC->cs_inet_fn, v);
+               vcard_free(v);
+       }
+
+       /*
+        * Create the user's 'Contacts' room (personal address book) if it doesn't already exist.
+        */
+       vcard_CtdlCreateRoom();
+}
+
+
+/* 
+ * Turn an arbitrary RFC822 address into a struct vCard for possible
+ * inclusion into an address book.
+ */
+struct vCard *vcard_new_from_rfc822_addr(char *addr) {
+       struct vCard *v;
+       char user[256], node[256], name[256], email[256], n[256], uid[256];
+       int i;
+
+       v = vcard_new();
+       if (v == NULL) return(NULL);
+
+       process_rfc822_addr(addr, user, node, name);
+       vcard_set_prop(v, "fn", name, 0);
+
+       vcard_fn_to_n(n, name, sizeof n);
+       vcard_set_prop(v, "n", n, 0);
+
+       snprintf(email, sizeof email, "%s@%s", user, node);
+       vcard_set_prop(v, "email;internet", email, 0);
+
+       snprintf(uid, sizeof uid, "collected: %s %s@%s", name, user, node);
+       for (i=0; uid[i]; ++i) {
+               if (isspace(uid[i])) uid[i] = '_';
+               uid[i] = tolower(uid[i]);
+       }
+       vcard_set_prop(v, "UID", uid, 0);
+
+       return(v);
+}
+
+
+/*
+ * This is called by store_harvested_addresses() to remove from the
+ * list any addresses we already have in our address book.
+ */
+void strip_addresses_already_have(long msgnum, void *userdata) {
+       char *collected_addresses;
+       struct CtdlMessage *msg = NULL;
+       struct vCard *v;
+       char *value = NULL;
+       int i, j;
+       char addr[256], user[256], node[256], name[256];
+
+       collected_addresses = (char *)userdata;
+
+       msg = CtdlFetchMessage(msgnum, 1);
+       if (msg == NULL) return;
+       v = vcard_load(msg->cm_fields[eMesageText]);
+       CM_Free(msg);
+
+       i = 0;
+       while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
+
+               for (j=0; j<num_tokens(collected_addresses, ','); ++j) {
+                       extract_token(addr, collected_addresses, j, ',', sizeof addr);
+
+                       /* Remove the address if we already have it! */
+                       process_rfc822_addr(addr, user, node, name);
+                       snprintf(addr, sizeof addr, "%s@%s", user, node);
+                       if (!strcasecmp(value, addr)) {
+                               remove_token(collected_addresses, j, ',');
+                       }
+               }
+
+       }
+
+       vcard_free(v);
+}
+
+
+
+/*
+ * Back end function for store_harvested_addresses()
+ */
+void store_this_ha(struct addresses_to_be_filed *aptr) {
+       struct CtdlMessage *vmsg = NULL;
+       char *ser = NULL;
+       struct vCard *v = NULL;
+       char recipient[256];
+       int i;
+
+       /* First remove any addresses we already have in the address book */
+       CtdlUserGoto(aptr->roomname, 0, 0, NULL, NULL, NULL, NULL);
+       CtdlForEachMessage(MSGS_ALL, 0, NULL, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$", NULL,
+               strip_addresses_already_have, aptr->collected_addresses);
+
+       if (!IsEmptyStr(aptr->collected_addresses))
+          for (i=0; i<num_tokens(aptr->collected_addresses, ','); ++i) {
+
+               /* Make a vCard out of each address */
+               extract_token(recipient, aptr->collected_addresses, i, ',', sizeof recipient);
+               striplt(recipient);
+               v = vcard_new_from_rfc822_addr(recipient);
+               if (v != NULL) {
+                       const char *s;
+                       vmsg = malloc(sizeof(struct CtdlMessage));
+                       memset(vmsg, 0, sizeof(struct CtdlMessage));
+                       vmsg->cm_magic = CTDLMESSAGE_MAGIC;
+                       vmsg->cm_anon_type = MES_NORMAL;
+                       vmsg->cm_format_type = FMT_RFC822;
+                       CM_SetField(vmsg, eAuthor, HKEY("Citadel"));
+                       s = vcard_get_prop(v, "UID", 1, 0, 0);
+                       if (!IsEmptyStr(s)) {
+                               CM_SetField(vmsg, eExclusiveID, s, strlen(s));
+                       }
+                       ser = vcard_serialize(v);
+                       if (ser != NULL) {
+                               StrBuf *buf;
+                               long serlen;
+                               
+                               serlen = strlen(ser);
+                               buf = NewStrBufPlain(NULL, serlen + 1024);
+
+                               StrBufAppendBufPlain(buf, HKEY("Content-type: " VCARD_MIME_TYPE "\r\n\r\n"), 0);
+                               StrBufAppendBufPlain(buf, ser, serlen, 0);
+                               StrBufAppendBufPlain(buf, HKEY("\r\n"), 0);
+                               CM_SetAsFieldSB(vmsg, eMesageText, &buf);
+                               free(ser);
+                       }
+                       vcard_free(v);
+
+                       syslog(LOG_DEBUG, "vcard: adding contact: %s", recipient);
+                       CtdlSubmitMsg(vmsg, NULL, aptr->roomname);
+                       CM_Free(vmsg);
+               }
+       }
+
+       free(aptr->roomname);
+       free(aptr->collected_addresses);
+       free(aptr);
+}
+
+
+/*
+ * When a user sends a message, we may harvest one or more email addresses
+ * from the recipient list to be added to the user's address book.  But we
+ * want to do this asynchronously so it doesn't keep the user waiting.
+ */
+void store_harvested_addresses(void) {
+
+       struct addresses_to_be_filed *aptr = NULL;
+
+       if (atbf == NULL) return;
+
+       begin_critical_section(S_ATBF);
+       while (atbf != NULL) {
+               aptr = atbf;
+               atbf = atbf->next;
+               end_critical_section(S_ATBF);
+               store_this_ha(aptr);
+               begin_critical_section(S_ATBF);
+       }
+       end_critical_section(S_ATBF);
+}
+
+
+/* 
+ * Function to output vCard data as plain text.  Nobody uses MSG0 anymore, so
+ * really this is just so we expose the vCard data to the full text indexer.
+ */
+void vcard_fixed_output(char *ptr, int len) {
+       char *serialized_vcard;
+       struct vCard *v;
+       char *key, *value;
+       int i = 0;
+
+       serialized_vcard = malloc(len + 1);
+       safestrncpy(serialized_vcard, ptr, len+1);
+       v = vcard_load(serialized_vcard);
+       free(serialized_vcard);
+
+       i = 0;
+       while (key = vcard_get_prop(v, "", 0, i, 1), key != NULL) {
+               value = vcard_get_prop(v, "", 0, i++, 0);
+               cprintf("%s\n", value);
+       }
+
+       vcard_free(v);
+}
+
+
+const char *CitadelServiceDICT_TCP="DICT_TCP";
+
+char *ctdl_module_init_vcard(void) {
+       struct ctdlroom qr;
+
+       if (!threading) {
+               CtdlRegisterSessionHook(vcard_session_login_hook, EVT_LOGIN, PRIO_LOGIN + 70);
+               CtdlRegisterMessageHook(vcard_upload_beforesave, EVT_BEFORESAVE);
+               CtdlRegisterMessageHook(vcard_upload_aftersave, EVT_AFTERSAVE);
+               CtdlRegisterProtoHook(cmd_regi, "REGI", "Enter registration info");
+               CtdlRegisterProtoHook(cmd_greg, "GREG", "Get registration info");
+               CtdlRegisterProtoHook(cmd_qdir, "QDIR", "Query Directory");
+               CtdlRegisterProtoHook(cmd_gvsn, "GVSN", "Get Valid Screen Names");
+               CtdlRegisterProtoHook(cmd_gvea, "GVEA", "Get Valid Email Addresses");
+               CtdlRegisterProtoHook(cmd_dvca, "DVCA", "Dump VCard Addresses");
+               CtdlRegisterUserHook(vcard_newuser, EVT_NEWUSER);
+               CtdlRegisterSessionHook(store_harvested_addresses, EVT_TIMER, PRIO_CLEANUP + 470);
+               CtdlRegisterFixedOutputHook("text/x-vcard", vcard_fixed_output);
+               CtdlRegisterFixedOutputHook("text/vcard", vcard_fixed_output);
+
+               /* Create the Global Address Book room if necessary */
+               CtdlCreateRoom(ADDRESS_BOOK_ROOM, 3, "", 0, 1, 0, VIEW_ADDRESSBOOK);
+
+               /* Set expiration policy to manual; otherwise objects will be lost! */
+               if (!CtdlGetRoomLock(&qr, ADDRESS_BOOK_ROOM)) {
+                       qr.QRep.expire_mode = EXPIRE_MANUAL;
+                       qr.QRdefaultview = VIEW_ADDRESSBOOK;                    // 2 = address book view
+                       CtdlPutRoomLock(&qr);
+               }
+
+               /* for postfix tcpdict */
+               CtdlRegisterServiceHook(CtdlGetConfigInt("c_pftcpdict_port"),   // Postfix
+                                       NULL,
+                                       check_get_greeting,
+                                       check_get,
+                                       NULL,
+                                       CitadelServiceDICT_TCP
+               );
+       }
+
+       /* return our module name for the log */
+       return "vcard";
+}
diff --git a/citadel/server/modules/wiki/.gitignore b/citadel/server/modules/wiki/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/wiki/serv_wiki.c b/citadel/server/modules/wiki/serv_wiki.c
new file mode 100644 (file)
index 0000000..a4bc7c6
--- /dev/null
@@ -0,0 +1,726 @@
+/*
+ * Server-side module for Wiki rooms.  This handles things like version control. 
+ * 
+ * Copyright (c) 2009-2022 by the citadel.org team
+ *
+ * This program is open source software.  You can redistribute it and/or
+ * modify it under the terms of the GNU General Public License, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../room_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../euidindex.h"
+#include "../../ctdl_module.h"
+
+/*
+ * Data passed back and forth between wiki_rev() and its MIME parser callback
+ */
+struct HistoryEraserCallBackData {
+       char *tempfilename;             /* name of temp file being patched */
+       char *stop_when;                /* stop when we hit this uuid */
+       int done;                       /* set to nonzero when we're done patching */
+};
+
+/*
+ * Name of the temporary room we create to store old revisions when someone requests them.
+ * We put it in an invalid namespace so the DAP cleans up after us later.
+ */
+char *wwm = "9999999999.WikiWaybackMachine";
+
+/*
+ * Before allowing a wiki page save to execute, we have to perform version control.
+ * This involves fetching the old version of the page if it exists.
+ */
+int wiki_upload_beforesave(struct CtdlMessage *msg, struct recptypes *recp) {
+       long old_msgnum = (-1L);
+       struct CtdlMessage *old_msg = NULL;
+       long history_msgnum = (-1L);
+       struct CtdlMessage *history_msg = NULL;
+       char diff_old_filename[PATH_MAX];
+       char diff_new_filename[PATH_MAX];
+       char diff_out_filename[PATH_MAX];
+       char diff_cmd[PATH_MAX];
+       FILE *fp;
+       int rv;
+       char history_page[1024];
+       long history_page_len;
+       char boundary[256];
+       char prefixed_boundary[258];
+       char buf[1024];
+       char *diffbuf = NULL;
+       size_t diffbuf_len = 0;
+       char *ptr = NULL;
+       long newmsgid;
+       StrBuf *msgidbuf;
+
+       if (!CC->logged_in) return(0);  /* Only do this if logged in. */
+
+       /* Is this a room with a Wiki in it, don't run this hook. */
+       if (CC->room.QRdefaultview != VIEW_WIKI) {
+               return(0);
+       }
+
+       /* If this isn't a MIME message, don't bother. */
+       if (msg->cm_format_type != 4) return(0);
+
+       /* If there's no EUID we can't do this.  Reject the post. */
+       if (CM_IsEmpty(msg, eExclusiveID)) return(1);
+
+       newmsgid = get_new_message_number();
+       msgidbuf = NewStrBuf();
+       StrBufPrintf(msgidbuf, "%08lX-%08lX@%s/%s",
+                    (long unsigned int) time(NULL),
+                    (long unsigned int) newmsgid,
+                    CtdlGetConfigStr("c_fqdn"),
+                    msg->cm_fields[eExclusiveID]
+               );
+
+       CM_SetAsFieldSB(msg, emessageId, &msgidbuf);
+
+       history_page_len = snprintf(history_page, sizeof history_page,
+                                   "%s_HISTORY_", msg->cm_fields[eExclusiveID]);
+
+       /* Make sure we're saving a real wiki page rather than a wiki history page.
+        * This is important in order to avoid recursing infinitely into this hook.
+        */
+       if (    (msg->cm_lengths[eExclusiveID] >= 9)
+               && (!strcasecmp(&msg->cm_fields[eExclusiveID][msg->cm_lengths[eExclusiveID]-9], "_HISTORY_"))
+       ) {
+               syslog(LOG_DEBUG, "History page not being historied\n");
+               return(0);
+       }
+
+       /* If there's no message text, obviously this is all b0rken and shouldn't happen at all */
+       if (CM_IsEmpty(msg, eMesageText)) return(0);
+
+       /* Set the message subject identical to the page name */
+       CM_CopyField(msg, eMsgSubject, eExclusiveID);
+
+       /* See if we can retrieve the previous version. */
+       old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
+       if (old_msgnum > 0L) {
+               old_msg = CtdlFetchMessage(old_msgnum, 1);
+       }
+       else {
+               old_msg = NULL;
+       }
+
+       if ((old_msg != NULL) && (CM_IsEmpty(old_msg, eMesageText))) {  /* old version is corrupt? */
+               CM_Free(old_msg);
+               old_msg = NULL;
+       }
+       
+       /* If no changes were made, don't bother saving it again */
+       if ((old_msg != NULL) && (!strcmp(msg->cm_fields[eMesageText], old_msg->cm_fields[eMesageText]))) {
+               CM_Free(old_msg);
+               return(1);
+       }
+
+       /*
+        * Generate diffs
+        */
+       CtdlMakeTempFileName(diff_old_filename, sizeof diff_old_filename);
+       CtdlMakeTempFileName(diff_new_filename, sizeof diff_new_filename);
+       CtdlMakeTempFileName(diff_out_filename, sizeof diff_out_filename);
+
+       if (old_msg != NULL) {
+               fp = fopen(diff_old_filename, "w");
+               rv = fwrite(old_msg->cm_fields[eMesageText], old_msg->cm_lengths[eMesageText], 1, fp);
+               fclose(fp);
+               CM_Free(old_msg);
+       }
+
+       fp = fopen(diff_new_filename, "w");
+       rv = fwrite(msg->cm_fields[eMesageText], msg->cm_lengths[eMesageText], 1, fp);
+       fclose(fp);
+
+       snprintf(diff_cmd, sizeof diff_cmd,
+               "diff -u %s %s >%s",
+               diff_new_filename,
+               ((old_msg != NULL) ? diff_old_filename : "/dev/null"),
+               diff_out_filename
+       );
+       syslog(LOG_DEBUG, "diff cmd: %s", diff_cmd);
+       rv = system(diff_cmd);
+       syslog(LOG_DEBUG, "diff cmd returned %d", rv);
+
+       diffbuf_len = 0;
+       diffbuf = NULL;
+       fp = fopen(diff_out_filename, "r");
+       if (fp == NULL) {
+               fp = fopen("/dev/null", "r");
+       }
+       if (fp != NULL) {
+               fseek(fp, 0L, SEEK_END);
+               diffbuf_len = ftell(fp);
+               fseek(fp, 0L, SEEK_SET);
+               diffbuf = malloc(diffbuf_len + 1);
+               fread(diffbuf, diffbuf_len, 1, fp);
+               diffbuf[diffbuf_len] = '\0';
+               fclose(fp);
+       }
+
+       syslog(LOG_DEBUG, "diff length is "SIZE_T_FMT" bytes", diffbuf_len);
+
+       unlink(diff_old_filename);
+       unlink(diff_new_filename);
+       unlink(diff_out_filename);
+
+       /* Determine whether this was a bogus (empty) edit */
+       if ((diffbuf_len = 0) && (diffbuf != NULL)) {
+               free(diffbuf);
+               diffbuf = NULL;
+       }
+       if (diffbuf == NULL) {
+               return(1);              /* No changes at all?  Abandon the post entirely! */
+       }
+
+       /* Now look for the existing edit history */
+
+       history_msgnum = CtdlLocateMessageByEuid(history_page, &CC->room);
+       history_msg = NULL;
+       if (history_msgnum > 0L) {
+               history_msg = CtdlFetchMessage(history_msgnum, 1);
+       }
+
+       /* Create a new history message if necessary */
+       if (history_msg == NULL) {
+               char *buf;
+               long len;
+
+               history_msg = malloc(sizeof(struct CtdlMessage));
+               memset(history_msg, 0, sizeof(struct CtdlMessage));
+               history_msg->cm_magic = CTDLMESSAGE_MAGIC;
+               history_msg->cm_anon_type = MES_NORMAL;
+               history_msg->cm_format_type = FMT_RFC822;
+               CM_SetField(history_msg, eAuthor, HKEY("Citadel"));
+               if (!IsEmptyStr(CC->room.QRname)){
+                       CM_SetField(history_msg, eRecipient, CC->room.QRname, strlen(CC->room.QRname));
+               }
+               CM_SetField(history_msg, eExclusiveID, history_page, history_page_len);
+               CM_SetField(history_msg, eMsgSubject, history_page, history_page_len);
+               CM_SetField(history_msg, eSuppressIdx, HKEY("1")); /* suppress full text indexing */
+               snprintf(boundary, sizeof boundary, "Citadel--Multipart--%04x--%08lx", getpid(), time(NULL));
+               buf = (char*) malloc(1024);
+               len = snprintf(buf, 1024,
+                              "Content-type: multipart/mixed; boundary=\"%s\"\n\n"
+                              "This is a Citadel wiki history encoded as multipart MIME.\n"
+                              "Each part is comprised of a diff script representing one change set.\n"
+                              "\n"
+                              "--%s--\n",
+                              boundary, boundary
+               );
+               CM_SetAsField(history_msg, eMesageText, &buf, len);
+       }
+
+       /* Update the history message (regardless of whether it's new or existing) */
+
+       /* Remove the Message-ID from the old version of the history message.  This will cause a brand
+        * new one to be generated, avoiding an uninitentional hit of the loop zapper when we replicate.
+        */
+       CM_FlushField(history_msg, emessageId);
+
+       /* Figure out the boundary string.  We do this even when we generated the
+        * boundary string in the above code, just to be safe and consistent.
+        */
+       *boundary = '\0';
+
+       ptr = history_msg->cm_fields[eMesageText];
+       do {
+               ptr = memreadline(ptr, buf, sizeof buf);
+               if (*ptr != 0) {
+                       striplt(buf);
+                       if (!IsEmptyStr(buf) && (!strncasecmp(buf, "Content-type:", 13))) {
+                               if (
+                                       (bmstrcasestr(buf, "multipart") != NULL)
+                                       && (bmstrcasestr(buf, "boundary=") != NULL)
+                               ) {
+                                       safestrncpy(boundary, bmstrcasestr(buf, "\""), sizeof boundary);
+                                       char *qu;
+                                       qu = strchr(boundary, '\"');
+                                       if (qu) {
+                                               strcpy(boundary, ++qu);
+                                       }
+                                       qu = strchr(boundary, '\"');
+                                       if (qu) {
+                                               *qu = 0;
+                                       }
+                               }
+                       }
+               }
+       } while ( (IsEmptyStr(boundary)) && (*ptr != 0) );
+
+       /*
+        * Now look for the first boundary.  That is where we need to insert our fun.
+        */
+       if (!IsEmptyStr(boundary)) {
+               char *MsgText;
+               long MsgTextLen;
+               time_t Now = time(NULL);
+
+               snprintf(prefixed_boundary, sizeof(prefixed_boundary), "--%s", boundary);
+               
+               CM_GetAsField(history_msg, eMesageText, &MsgText, &MsgTextLen);
+
+               ptr = bmstrcasestr(MsgText, prefixed_boundary);
+               if (ptr != NULL) {
+                       StrBuf *NewMsgText;
+                       char uuid[64];
+                       char memo[512];
+                       long memolen;
+                       char encoded_memo[1024];
+                       
+                       NewMsgText = NewStrBufPlain(NULL, MsgTextLen + diffbuf_len + 1024);
+
+                       generate_uuid(uuid);
+                       memolen = snprintf(memo, sizeof(memo), "%s|%ld|%s|%s", 
+                                          uuid,
+                                          Now,
+                                          CC->user.fullname,
+                                          CtdlGetConfigStr("c_nodename"));
+
+                       memolen = CtdlEncodeBase64(encoded_memo, memo, memolen, 0);
+
+                       StrBufAppendBufPlain(NewMsgText, HKEY("--"), 0);
+                       StrBufAppendBufPlain(NewMsgText, boundary, -1, 0);
+                       StrBufAppendBufPlain(
+                               NewMsgText, 
+                               HKEY("\n"
+                                    "Content-type: text/plain\n"
+                                    "Content-Disposition: inline; filename=\""), 0);
+
+                       StrBufAppendBufPlain(NewMsgText, encoded_memo, memolen, 0);
+
+                       StrBufAppendBufPlain(
+                               NewMsgText, 
+                               HKEY("\"\n"
+                                    "Content-Transfer-Encoding: 8bit\n"
+                                    "\n"), 0);
+
+                       StrBufAppendBufPlain(NewMsgText, diffbuf, diffbuf_len, 0);
+                       StrBufAppendBufPlain(NewMsgText, HKEY("\n"), 0);
+
+                       StrBufAppendBufPlain(NewMsgText, ptr, MsgTextLen - (ptr - MsgText), 0);
+                       free(MsgText);
+                       CM_SetAsFieldSB(history_msg, eMesageText, &NewMsgText); 
+               }
+               else
+               {
+                       CM_SetAsField(history_msg, eMesageText, &MsgText, MsgTextLen); 
+               }
+
+               CM_SetFieldLONG(history_msg, eTimestamp, Now);
+       
+               CtdlSubmitMsg(history_msg, NULL, "");
+       }
+       else {
+               syslog(LOG_ALERT, "Empty boundary string in history message.  No history!\n");
+       }
+
+       free(diffbuf);
+       CM_Free(history_msg);
+       return(0);
+}
+
+
+/*
+ * MIME Parser callback for wiki_history()
+ *
+ * The "filename" field will contain a memo field.  All we have to do is decode
+ * the base64 and output it.  The data is already in a delimited format suitable
+ * for our client protocol.
+ */
+void wiki_history_callback(char *name, char *filename, char *partnum, char *disp,
+                  void *content, char *cbtype, char *cbcharset, size_t length,
+                  char *encoding, char *cbid, void *cbuserdata)
+{
+       char memo[1024];
+
+       CtdlDecodeBase64(memo, filename, strlen(filename));
+       cprintf("%s\n", memo);
+}
+
+
+/*
+ * Fetch a list of revisions for a particular wiki page
+ */
+void wiki_history(char *pagename) {
+       int r;
+       char history_page_name[270];
+       long msgnum;
+       struct CtdlMessage *msg;
+
+       r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
+       if (r != om_ok) {
+               if (r == om_not_logged_in) {
+                       cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+               }
+               else {
+                       cprintf("%d An unknown error has occurred.\n", ERROR);
+               }
+               return;
+       }
+
+       snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename);
+       msgnum = CtdlLocateMessageByEuid(history_page_name, &CC->room);
+       if (msgnum > 0L) {
+               msg = CtdlFetchMessage(msgnum, 1);
+       }
+       else {
+               msg = NULL;
+       }
+
+       if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
+               CM_Free(msg);
+               msg = NULL;
+       }
+
+       if (msg == NULL) {
+               cprintf("%d Revision history for '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
+               return;
+       }
+
+       
+       cprintf("%d Revision history for '%s'\n", LISTING_FOLLOWS, pagename);
+       mime_parser(CM_RANGE(msg, eMesageText), *wiki_history_callback, NULL, NULL, NULL, 0);
+       cprintf("000\n");
+
+       CM_Free(msg);
+       return;
+}
+
+/*
+ * MIME Parser callback for wiki_rev()
+ *
+ * The "filename" field will contain a memo field, which includes (among other things)
+ * the uuid of this revision.  After we hit the desired revision, we stop processing.
+ *
+ * The "content" filed will contain "diff" output suitable for applying via "patch"
+ * to our temporary file.
+ */
+void wiki_rev_callback(char *name, char *filename, char *partnum, char *disp,
+                  void *content, char *cbtype, char *cbcharset, size_t length,
+                  char *encoding, char *cbid, void *cbuserdata)
+{
+       struct HistoryEraserCallBackData *hecbd = (struct HistoryEraserCallBackData *)cbuserdata;
+       char memo[1024];
+       char this_rev[256];
+       FILE *fp;
+       char *ptr = NULL;
+       char buf[1024];
+
+       /* Did a previous callback already indicate that we've reached our target uuid?
+        * If so, don't process anything else.
+        */
+       if (hecbd->done) {
+               return;
+       }
+
+       CtdlDecodeBase64(memo, filename, strlen(filename));
+       extract_token(this_rev, memo, 0, '|', sizeof this_rev);
+       striplt(this_rev);
+
+       /* Perform the patch */
+       fp = popen("patch -f -s -p0 -r /dev/null >/dev/null 2>/dev/null", "w");
+       if (fp) {
+               /* Replace the filenames in the patch with the tempfilename we're actually tweaking */
+               fprintf(fp, "--- %s\n", hecbd->tempfilename);
+               fprintf(fp, "+++ %s\n", hecbd->tempfilename);
+
+               ptr = (char *)content;
+               int linenum = 0;
+               do {
+                       ++linenum;
+                       ptr = memreadline(ptr, buf, sizeof buf);
+                       if (*ptr != 0) {
+                               if (linenum <= 2) {
+                                       /* skip the first two lines; they contain bogus filenames */
+                               }
+                               else {
+                                       fprintf(fp, "%s\n", buf);
+                               }
+                       }
+               } while ((*ptr != 0) && (ptr < ((char*)content + length)));
+               if (pclose(fp) != 0) {
+                       syslog(LOG_ERR, "pclose() returned an error - patch failed\n");
+               }
+       }
+
+       if (!strcasecmp(this_rev, hecbd->stop_when)) {
+               /* Found our target rev.  Tell any subsequent callbacks to suppress processing. */
+               syslog(LOG_DEBUG, "Target revision has been reached -- stop patching.\n");
+               hecbd->done = 1;
+       }
+}
+
+
+/*
+ * Fetch a specific revision of a wiki page.  The "operation" string may be set to "fetch" in order
+ * to simply fetch the desired revision and store it in a temporary location for viewing, or "revert"
+ * to revert the currently active page to that revision.
+ */
+void wiki_rev(char *pagename, char *rev, char *operation)
+{
+       int r;
+       char history_page_name[270];
+       long msgnum;
+       char temp[PATH_MAX];
+       struct CtdlMessage *msg;
+       FILE *fp;
+       struct HistoryEraserCallBackData hecbd;
+       long len = 0L;
+       int rv;
+
+       r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
+       if (r != om_ok) {
+               if (r == om_not_logged_in) {
+                       cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+               }
+               else {
+                       cprintf("%d An unknown error has occurred.\n", ERROR);
+               }
+               return;
+       }
+
+       if (!strcasecmp(operation, "revert")) {
+               r = CtdlDoIHavePermissionToPostInThisRoom(temp, sizeof temp, POST_LOGGED_IN, 0);
+               if (r != 0) {
+                       cprintf("%d %s\n", r, temp);
+                       return;
+               }
+       }
+
+       /* Begin by fetching the current version of the page.  We're going to patch
+        * backwards through the diffs until we get the one we want.
+        */
+       msgnum = CtdlLocateMessageByEuid(pagename, &CC->room);
+       if (msgnum > 0L) {
+               msg = CtdlFetchMessage(msgnum, 1);
+       }
+       else {
+               msg = NULL;
+       }
+
+       if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
+               CM_Free(msg);
+               msg = NULL;
+       }
+
+       if (msg == NULL) {
+               cprintf("%d Page '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
+               return;
+       }
+
+       /* Output it to a temporary file */
+
+       CtdlMakeTempFileName(temp, sizeof temp);
+       fp = fopen(temp, "w");
+       if (fp != NULL) {
+               r = fwrite(msg->cm_fields[eMesageText], msg->cm_lengths[eMesageText], 1, fp);
+               fclose(fp);
+       }
+       else {
+               syslog(LOG_ERR, "%s: %m", temp);
+       }
+       CM_Free(msg);
+
+       /* Get the revision history */
+
+       snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename);
+       msgnum = CtdlLocateMessageByEuid(history_page_name, &CC->room);
+       if (msgnum > 0L) {
+               msg = CtdlFetchMessage(msgnum, 1);
+       }
+       else {
+               msg = NULL;
+       }
+
+       if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
+               CM_Free(msg);
+               msg = NULL;
+       }
+
+       if (msg == NULL) {
+               cprintf("%d Revision history for '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
+               return;
+       }
+
+       /* Start patching backwards (newest to oldest) through the revision history until we
+        * hit the revision uuid requested by the user.  (The callback will perform each one.)
+        */
+
+       memset(&hecbd, 0, sizeof(struct HistoryEraserCallBackData));
+       hecbd.tempfilename = temp;
+       hecbd.stop_when = rev;
+       striplt(hecbd.stop_when);
+
+       mime_parser(CM_RANGE(msg, eMesageText), *wiki_rev_callback, NULL, NULL, (void *)&hecbd, 0);
+       CM_Free(msg);
+
+       /* Were we successful? */
+       if (hecbd.done == 0) {
+               cprintf("%d Revision '%s' of page '%s' was not found.\n",
+                       ERROR + MESSAGE_NOT_FOUND, rev, pagename
+               );
+       }
+
+       /* We have the desired revision on disk.  Now do something with it. */
+
+       else if ( (!strcasecmp(operation, "fetch")) || (!strcasecmp(operation, "revert")) ) {
+               msg = malloc(sizeof(struct CtdlMessage));
+               memset(msg, 0, sizeof(struct CtdlMessage));
+               msg->cm_magic = CTDLMESSAGE_MAGIC;
+               msg->cm_anon_type = MES_NORMAL;
+               msg->cm_format_type = FMT_RFC822;
+               fp = fopen(temp, "r");
+               if (fp) {
+                       char *msgbuf;
+                       fseek(fp, 0L, SEEK_END);
+                       len = ftell(fp);
+                       fseek(fp, 0L, SEEK_SET);
+                       msgbuf = malloc(len + 1);
+                       rv = fread(msgbuf, len, 1, fp);
+                       syslog(LOG_DEBUG, "did %d blocks of %ld bytes\n", rv, len);
+                       msgbuf[len] = '\0';
+                       CM_SetAsField(msg, eMesageText, &msgbuf, len);
+                       fclose(fp);
+               }
+               if (len <= 0) {
+                       msgnum = (-1L);
+               }
+               else if (!strcasecmp(operation, "fetch")) {
+                       CM_SetField(msg, eAuthor, HKEY("Citadel"));
+                       CtdlCreateRoom(wwm, 5, "", 0, 1, 1, VIEW_BBS);  /* Not an error if already exists */
+                       msgnum = CtdlSubmitMsg(msg, NULL, wwm);         /* Store the revision here */
+
+                       /*
+                        * WARNING: VILE SLEAZY HACK
+                        * This will avoid the 'message xxx is not in this room' security error,
+                        * but only if the client fetches the message we just generated immediately
+                        * without first trying to perform other fetch operations.
+                        */
+                       if (CC->cached_msglist != NULL) {
+                               free(CC->cached_msglist);
+                               CC->cached_msglist = NULL;
+                               CC->cached_num_msgs = 0;
+                       }
+                       CC->cached_msglist = malloc(sizeof(long));
+                       if (CC->cached_msglist != NULL) {
+                               CC->cached_num_msgs = 1;
+                               CC->cached_msglist[0] = msgnum;
+                       }
+
+               }
+               else if (!strcasecmp(operation, "revert")) {
+                       CM_SetFieldLONG(msg, eTimestamp, time(NULL));
+                       if (!IsEmptyStr(CC->user.fullname)) {
+                               CM_SetField(msg, eAuthor, CC->user.fullname, strlen(CC->user.fullname));
+                       }
+
+                       if (!IsEmptyStr(CC->cs_inet_email)) {
+                               CM_SetField(msg, erFc822Addr, CC->cs_inet_email, strlen(CC->cs_inet_email));
+                       }
+
+                       if (!IsEmptyStr(CC->room.QRname)) {
+                               CM_SetField(msg, eOriginalRoom, CC->room.QRname, strlen(CC->room.QRname));
+                       }
+
+                       if (!IsEmptyStr(pagename)) {
+                               CM_SetField(msg, eExclusiveID, pagename, strlen(pagename));
+                       }
+                       msgnum = CtdlSubmitMsg(msg, NULL, "");          /* Replace the current revision */
+               }
+               else {
+                       /* Theoretically it is impossible to get here, but throw an error anyway */
+                       msgnum = (-1L);
+               }
+               CM_Free(msg);
+               if (msgnum >= 0L) {
+                       cprintf("%d %ld\n", CIT_OK, msgnum);            /* Give the client a msgnum */
+               }
+               else {
+                       cprintf("%d Error %ld has occurred.\n", ERROR+INTERNAL_ERROR, msgnum);
+               }
+       }
+
+       /* We did all this work for nothing.  Express anguish to the caller. */
+       else {
+               cprintf("%d An unknown operation was requested.\n", ERROR+CMD_NOT_SUPPORTED);
+       }
+
+       unlink(temp);
+       return;
+}
+
+
+
+/*
+ * commands related to wiki management
+ */
+void cmd_wiki(char *argbuf) {
+       char subcmd[32];
+       char pagename[256];
+       char rev[128];
+       char operation[16];
+
+       extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
+
+       if (!strcasecmp(subcmd, "history")) {
+               extract_token(pagename, argbuf, 1, '|', sizeof pagename);
+               wiki_history(pagename);
+               return;
+       }
+
+       if (!strcasecmp(subcmd, "rev")) {
+               extract_token(pagename, argbuf, 1, '|', sizeof pagename);
+               extract_token(rev, argbuf, 2, '|', sizeof rev);
+               extract_token(operation, argbuf, 3, '|', sizeof operation);
+               wiki_rev(pagename, rev, operation);
+               return;
+       }
+
+       cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
+}
+
+
+
+/*
+ * Module initialization
+ */
+char *ctdl_module_init_wiki() {
+       if (!threading) {
+               CtdlRegisterMessageHook(wiki_upload_beforesave, EVT_BEFORESAVE);
+               CtdlRegisterProtoHook(cmd_wiki, "WIKI", "Commands related to Wiki management");
+       }
+
+       /* return our module name for the log */
+       return "wiki";
+}
diff --git a/citadel/server/modules/xmpp/.gitignore b/citadel/server/modules/xmpp/.gitignore
new file mode 100644 (file)
index 0000000..5761abc
--- /dev/null
@@ -0,0 +1 @@
+*.o
diff --git a/citadel/server/modules/xmpp/serv_xmpp.c b/citadel/server/modules/xmpp/serv_xmpp.c
new file mode 100644 (file)
index 0000000..c81720c
--- /dev/null
@@ -0,0 +1,673 @@
+/*
+ * XMPP (Jabber) service for the Citadel system
+ * Copyright (c) 2007-2022 by Art Cancro and citadel.org
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <libcitadel.h>
+#include <expat.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../ctdl_module.h"
+#include "serv_xmpp.h"
+
+/* uncomment for more verbosity - it will log all received XML tags */
+// #define XMPP_XML_DEBUG
+
+/* XML_StopParser is present in expat 2.x */
+#if XML_MAJOR_VERSION > 1
+#define HAVE_XML_STOPPARSER
+#endif
+
+struct xmpp_event *xmpp_queue = NULL;
+
+#ifdef HAVE_XML_STOPPARSER
+/* Stop the parser if an entity declaration is hit. */
+static void xmpp_entity_declaration(void *userData, const XML_Char *entityName,
+                               int is_parameter_entity, const XML_Char *value,
+                               int value_length, const XML_Char *base,
+                               const XML_Char *systemId, const XML_Char *publicId,
+                               const XML_Char *notationName
+) {
+       syslog(LOG_WARNING, "xmpp: illegal entity declaration encountered; stopping parser.");
+       XML_StopParser(XMPP->xp, XML_FALSE);
+}
+#endif
+
+
+/*
+ * support function for xmlesc() which helps with UTF-8 strings
+ */
+static inline int Ctdl_GetUtf8SequenceLength(const char *CharS, const char *CharE)
+{
+       int n = 0;
+        unsigned char test = (1<<7);
+
+       if ((*CharS & 0xC0) != 0xC0) {
+               return 1;
+       }
+
+       while ((n < 8) && ((test & ((unsigned char)*CharS)) != 0))  {
+               test = test >> 1;
+               n ++;
+       }
+       if ((n > 6) || ((CharE - CharS) < n)) {
+               n = 0;
+       }
+       return n;
+}
+
+
+/*
+ * Given a source string and a target buffer, returns the string
+ * properly escaped for insertion into an XML stream.  Returns a
+ * pointer to the target buffer for convenience.
+ */
+char *xmlesc(char *buf, char *str, int bufsiz)
+{
+       int IsUtf8Sequence;
+       char *ptr, *pche;
+       unsigned char ch;
+       int inlen;
+       int len = 0;
+
+       if (!buf) return(NULL);
+       buf[0] = 0;
+       len = 0;
+       if (!str) {
+               return(buf);
+       }
+       inlen = strlen(str);
+       pche = str + inlen;
+
+       for (ptr=str; *ptr; ptr++) {
+               ch = *ptr;
+               if (ch == '<') {
+                       strcpy(&buf[len], "&lt;");
+                       len += 4;
+               }
+               else if (ch == '>') {
+                       strcpy(&buf[len], "&gt;");
+                       len += 4;
+               }
+               else if (ch == '&') {
+                       strcpy(&buf[len], "&amp;");
+                       len += 5;
+               }
+               else if ((ch >= 0x20) && (ch <= 0x7F)) {
+                       buf[len++] = ch;
+                       buf[len] = 0;
+               }
+               else if (ch < 0x20) {
+                       buf[len++] = '_';       // we probably shouldn't be doing this
+                       buf[len] = 0;
+               }
+               else {
+                       IsUtf8Sequence =  Ctdl_GetUtf8SequenceLength(ptr, pche);
+                       if (IsUtf8Sequence)
+                       {
+                               while ((IsUtf8Sequence > 0) && 
+                                      (ptr < pche))
+                               {
+                                       buf[len] = *ptr;
+                                       ptr ++;
+                                       --IsUtf8Sequence;
+                               }
+                       }
+                       else
+                       {
+                               char oct[10];
+                               sprintf(oct, "&#%o;", ch);
+                               strcpy(&buf[len], oct);
+                               len += strlen(oct);
+                       }
+               }
+               if ((len + 6) > bufsiz) {
+                       return(buf);
+               }
+       }
+       return(buf);
+}
+
+
+/*
+ * We have just received a <stream> tag from the client, so send them ours
+ */
+void xmpp_stream_start(void *data, const char *supplied_el, const char **attr)
+{
+       char xmlbuf[256];
+
+       while (*attr) {
+               if (!strcasecmp(attr[0], "to")) {
+                       safestrncpy(XMPP->server_name, attr[1], sizeof XMPP->server_name);
+               }
+               attr += 2;
+       }
+
+       cprintf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+       cprintf("<stream:stream ");
+       cprintf("from=\"%s\" ", xmlesc(xmlbuf, XMPP->server_name, sizeof xmlbuf));
+       cprintf("id=\"%08x\" ", CC->cs_pid);
+       cprintf("version=\"1.0\" ");
+       cprintf("xmlns:stream=\"http://etherx.jabber.org/streams\" ");
+       cprintf("xmlns=\"jabber:client\">");
+
+       /* The features of this stream are... */
+       cprintf("<stream:features>");
+
+       /*
+        * TLS encryption (but only if it isn't already active)
+        */ 
+#ifdef HAVE_OPENSSL
+       if (!CC->redirect_ssl) {
+               cprintf("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>");
+       }
+#endif
+
+       if (!CC->logged_in) {
+               /* If we're not logged in yet, offer SASL as our feature set */
+               xmpp_output_auth_mechs();
+
+               /* Also offer non-SASL authentication */
+               cprintf("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>");
+       }
+
+       /* Offer binding and sessions as part of our feature set */
+       cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>");
+       cprintf("<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>");
+       cprintf("</stream:features>");
+
+       CC->is_async = 1;               /* XMPP sessions are inherently async-capable */
+}
+
+
+void xmpp_xml_start(void *data, const char *supplied_el, const char **attr) {
+       char el[256];
+       char *sep = NULL;
+       int i;
+
+       /* Create a version of the element with the namespace removed.
+        * Now we can access "el" or "supplied_el" depending on whether we want to see the whole namespace.
+        */
+       safestrncpy(el, supplied_el, sizeof el);
+       while (sep = strchr(el, ':'), sep) {
+               strcpy(el, ++sep);
+       }
+
+#ifdef XMPP_XML_DEBUG
+       syslog(LOG_DEBUG, "xmpp: ELEMENT START: <%s>", el);
+       for (i=0; attr[i] != NULL; i+=2) {
+               syslog(LOG_DEBUG, "xmpp: Attribute '%s' = '%s'", attr[i], attr[i+1]);
+       }
+#endif
+
+       if (!strcasecmp(el, "stream")) {
+               xmpp_stream_start(data, supplied_el, attr);
+       }
+
+       else if (!strcasecmp(el, "query")) {
+               XMPP->iq_query_xmlns[0] = 0;
+               safestrncpy(XMPP->iq_query_xmlns, supplied_el, sizeof XMPP->iq_query_xmlns);
+       }
+
+       else if (!strcasecmp(el, "bind")) {
+               XMPP->bind_requested = 1;
+       }
+
+       else if (!strcasecmp(el, "iq")) {
+               for (i=0; attr[i] != NULL; i+=2) {
+                       if (!strcasecmp(attr[i], "type")) {
+                               safestrncpy(XMPP->iq_type, attr[i+1], sizeof XMPP->iq_type);
+                       }
+                       else if (!strcasecmp(attr[i], "id")) {
+                               safestrncpy(XMPP->iq_id, attr[i+1], sizeof XMPP->iq_id);
+                       }
+                       else if (!strcasecmp(attr[i], "from")) {
+                               safestrncpy(XMPP->iq_from, attr[i+1], sizeof XMPP->iq_from);
+                       }
+                       else if (!strcasecmp(attr[i], "to")) {
+                               safestrncpy(XMPP->iq_to, attr[i+1], sizeof XMPP->iq_to);
+                       }
+               }
+       }
+
+       else if (!strcasecmp(el, "auth")) {
+               XMPP->sasl_auth_mech[0] = 0;
+               for (i=0; attr[i] != NULL; i+=2) {
+                       if (!strcasecmp(attr[i], "mechanism")) {
+                               safestrncpy(XMPP->sasl_auth_mech, attr[i+1], sizeof XMPP->sasl_auth_mech);
+                       }
+               }
+       }
+
+       else if (!strcasecmp(el, "message")) {
+               for (i=0; attr[i] != NULL; i+=2) {
+                       if (!strcasecmp(attr[i], "to")) {
+                               safestrncpy(XMPP->message_to, attr[i+1], sizeof XMPP->message_to);
+                       }
+               }
+       }
+
+       else if (!strcasecmp(el, "html")) {
+               ++XMPP->html_tag_level;
+       }
+}
+
+
+void xmpp_xml_end(void *data, const char *supplied_el) {
+       char el[256];
+       char *sep = NULL;
+       char xmlbuf[256];
+
+       /* Create a version of the element with the namespace removed.
+        * Now we can access "el" or "supplied_el" depending on whether we want to see the whole namespace.
+        */
+       safestrncpy(el, supplied_el, sizeof el);
+       while (sep = strchr(el, ':'), sep) {
+               strcpy(el, ++sep);
+       }
+
+#ifdef XMPP_XML_DEBUG
+       syslog(LOG_DEBUG, "xmpp: ELEMENT END  : <%s>", el);
+       if (XMPP->chardata_len > 0) {
+               syslog(LOG_DEBUG, "xmpp: chardata: %s", XMPP->chardata);
+       }
+#endif
+
+       if (!strcasecmp(el, "resource")) {
+               if (XMPP->chardata_len > 0) {
+                       safestrncpy(XMPP->iq_client_resource, XMPP->chardata, sizeof XMPP->iq_client_resource);
+                       striplt(XMPP->iq_client_resource);
+               }
+       }
+
+       else if (!strcasecmp(el, "username")) {         /* NON SASL ONLY */
+               if (XMPP->chardata_len > 0) {
+                       safestrncpy(XMPP->iq_client_username, XMPP->chardata, sizeof XMPP->iq_client_username);
+                       striplt(XMPP->iq_client_username);
+               }
+       }
+
+       else if (!strcasecmp(el, "password")) {         /* NON SASL ONLY */
+               if (XMPP->chardata_len > 0) {
+                       safestrncpy(XMPP->iq_client_password, XMPP->chardata, sizeof XMPP->iq_client_password);
+                       striplt(XMPP->iq_client_password);
+               }
+       }
+
+       else if (!strcasecmp(el, "iq")) {
+
+               /*
+                * iq type="get" (handle queries)
+                */
+               if (!strcasecmp(XMPP->iq_type, "get")) {
+
+                       /*
+                        * Query on a namespace
+                        */
+                       if (!IsEmptyStr(XMPP->iq_query_xmlns)) {
+                               xmpp_query_namespace(XMPP->iq_id, XMPP->iq_from, XMPP->iq_to, XMPP->iq_query_xmlns);
+                       }
+
+                       /*
+                        * ping ( http://xmpp.org/extensions/xep-0199.html )
+                        */
+                       else if (XMPP->ping_requested) {
+                               cprintf("<iq type=\"result\" ");
+                               if (!IsEmptyStr(XMPP->iq_from)) {
+                                       cprintf("to=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_from, sizeof xmlbuf));
+                               }
+                               if (!IsEmptyStr(XMPP->iq_to)) {
+                                       cprintf("from=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_to, sizeof xmlbuf));
+                               }
+                               cprintf("id=\"%s\"/>", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
+                       }
+
+                       /*
+                        * Client is requesting its own vCard
+                        * (If we make this more elaborate, move it to a separate function)
+                        */
+                       else if (XMPP->iq_vcard) {
+                               cprintf("<iq type=\"result\" id=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
+                               cprintf("to=\"%s\">", xmlesc(xmlbuf, XMPP->iq_from, sizeof xmlbuf));
+                               cprintf("<vCard xmlns=\"vcard-temp\">");
+                               cprintf("<fn>%s</fn>", xmlesc(xmlbuf, CC->user.fullname, sizeof xmlbuf));
+                               cprintf("<nickname>%s</nickname>", xmlesc(xmlbuf, CC->user.fullname, sizeof xmlbuf));
+                               cprintf("</vCard>");
+                               cprintf("</iq>");
+                       }
+
+                       /*
+                        * Unknown query ... return the XML equivalent of a blank stare
+                        */
+                       else {
+                               syslog(LOG_DEBUG, "xmpp: Unknown query <%s> - returning <service-unavailable/>", el);
+                               cprintf("<iq type=\"error\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
+                               cprintf("<error code=\"503\" type=\"cancel\">"
+                                       "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
+                                       "</error>"
+                               );
+                               cprintf("</iq>");
+                       }
+               }
+
+               /*
+                * Non SASL authentication
+                */
+               else if (
+                       (!strcasecmp(XMPP->iq_type, "set"))
+                       && (!strcasecmp(XMPP->iq_query_xmlns, "jabber:iq:auth:query"))
+               ) {
+                       xmpp_non_sasl_authenticate(XMPP->iq_id, XMPP->iq_client_username, XMPP->iq_client_password);
+               }       
+
+               /*
+                * If this <iq> stanza was a "bind" attempt, process it ...
+                */
+               else if (
+                       (XMPP->bind_requested)
+                       && (!IsEmptyStr(XMPP->iq_id))
+                       && (CC->logged_in)
+               ) {
+                       /* If the client has not specified a client resource, generate one */
+                       if (IsEmptyStr(XMPP->iq_client_resource)) {
+                               snprintf(XMPP->iq_client_resource, sizeof XMPP->iq_client_resource, "%d", CC->cs_pid);
+                       }
+
+                       /* Generate the "full JID" of the client (user@host/resource) */
+                       snprintf(XMPP->client_jid, sizeof XMPP->client_jid, "%s/%s", CC->cs_principal_id, XMPP->iq_client_resource);
+
+                       /* Tell the client what its JID is */
+                       cprintf("<iq type=\"result\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
+                       cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">");
+                       cprintf("<jid>%s</jid>", xmlesc(xmlbuf, XMPP->client_jid, sizeof xmlbuf));
+                       cprintf("</bind>");
+                       cprintf("</iq>");
+               }
+
+               else if (XMPP->iq_session) {
+                       cprintf("<iq type=\"result\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
+                       cprintf("</iq>");
+               }
+
+               else {
+                       cprintf("<iq type=\"error\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
+                       cprintf("<error>Don't know how to do '%s'!</error>", xmlesc(xmlbuf, XMPP->iq_type, sizeof xmlbuf));
+                       cprintf("</iq>");
+                       syslog(LOG_DEBUG, "XMPP: don't know how to do iq_type='%s' with iq_query_xmlns='%s'", XMPP->iq_type, XMPP->iq_query_xmlns);
+               }
+
+               /* Now clear these fields out so they don't get used by a future stanza */
+               XMPP->iq_id[0] = 0;
+               XMPP->iq_from[0] = 0;
+               XMPP->iq_to[0] = 0;
+               XMPP->iq_type[0] = 0;
+               XMPP->iq_client_resource[0] = 0;
+               XMPP->iq_session = 0;
+               XMPP->iq_vcard = 0;
+               XMPP->iq_query_xmlns[0] = 0;
+               XMPP->bind_requested = 0;
+               XMPP->ping_requested = 0;
+       }
+
+       else if (!strcasecmp(el, "auth")) {
+               /* Try to authenticate (this function is responsible for the output stanza) */
+               xmpp_sasl_auth(XMPP->sasl_auth_mech, (XMPP->chardata != NULL ? XMPP->chardata : "") );
+
+               /* Now clear these fields out so they don't get used by a future stanza */
+               XMPP->sasl_auth_mech[0] = 0;
+       }
+
+       else if (!strcasecmp(el, "session")) {
+               XMPP->iq_session = 1;
+       }
+
+       else if (!strcasecmp(supplied_el, "vcard-temp:vCard")) {
+               XMPP->iq_vcard = 1;
+       }
+
+       else if (!strcasecmp(el, "presence")) {
+               /* Respond to a <presence> update by firing back with presence information
+                * on the entire wholist.  Check this assumption, it's probably wrong.
+                */
+               xmpp_wholist_presence_dump();
+       }
+
+       else if ( (!strcasecmp(el, "body")) && (XMPP->html_tag_level == 0) ) {
+               if (XMPP->message_body != NULL) {
+                       free(XMPP->message_body);
+                       XMPP->message_body = NULL;
+               }
+               if (XMPP->chardata_len > 0) {
+                       XMPP->message_body = strdup(XMPP->chardata);
+               }
+       }
+
+       else if (!strcasecmp(el, "message")) {
+               xmpp_send_message(XMPP->message_to, XMPP->message_body);
+               XMPP->html_tag_level = 0;
+       }
+
+       else if (!strcasecmp(el, "html")) {
+               --XMPP->html_tag_level;
+       }
+
+       else if (!strcasecmp(el, "starttls")) {
+#ifdef HAVE_OPENSSL
+               cprintf("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
+               CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
+               if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;
+#else
+               cprintf("<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
+               CC->kill_me = KILLME_NO_CRYPTO;
+#endif
+       }
+
+       else if (!strcasecmp(el, "ping")) {
+               XMPP->ping_requested = 1;
+       }
+
+       else if (!strcasecmp(el, "stream")) {
+               syslog(LOG_DEBUG, "xmpp: client shut down their stream");
+               xmpp_massacre_roster();
+               cprintf("</stream>\n");
+               CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
+       }
+
+       else if (!strcasecmp(el, "query")) {
+               /* already processed , no further action needed here */
+       }
+
+       else if (!strcasecmp(el, "bind")) {
+               /* already processed , no further action needed here */
+       }
+
+       else {
+               syslog(LOG_DEBUG, "xmpp: ignoring unknown tag <%s>", el);
+       }
+
+       XMPP->chardata_len = 0;
+       if (XMPP->chardata_alloc > 0) {
+               XMPP->chardata[0] = 0;
+       }
+}
+
+
+void xmpp_xml_chardata(void *data, const XML_Char *s, int len)
+{
+       citxmpp *X = XMPP;
+
+       if (X->chardata_alloc == 0) {
+               X->chardata_alloc = SIZ;
+               X->chardata = malloc(X->chardata_alloc);
+       }
+       if ((X->chardata_len + len + 1) > X->chardata_alloc) {
+               X->chardata_alloc = X->chardata_len + len + 1024;
+               X->chardata = realloc(X->chardata, X->chardata_alloc);
+       }
+       memcpy(&X->chardata[X->chardata_len], s, len);
+       X->chardata_len += len;
+       X->chardata[X->chardata_len] = 0;
+}
+
+
+/*
+ * This cleanup function blows away the temporary memory and files used by the XMPP service.
+ */
+void xmpp_cleanup_function(void) {
+
+       /* Don't do this stuff if this is not a XMPP session! */
+       if (CC->h_command_function != xmpp_command_loop) return;
+
+       if (XMPP->chardata != NULL) {
+               free(XMPP->chardata);
+               XMPP->chardata = NULL;
+               XMPP->chardata_len = 0;
+               XMPP->chardata_alloc = 0;
+               if (XMPP->message_body != NULL) {
+                       free(XMPP->message_body);
+               }
+       }
+       XML_ParserFree(XMPP->xp);
+       free(XMPP);
+}
+
+
+/*
+ * Here's where our XMPP session begins its happy day.
+ */
+void xmpp_greeting(void) {
+       client_set_inbound_buf(4);
+       strcpy(CC->cs_clientname, "XMPP session");
+       CC->session_specific_data = malloc(sizeof(citxmpp));
+       memset(XMPP, 0, sizeof(citxmpp));
+       XMPP->last_event_processed = queue_event_seq;
+
+       /* XMPP does not use a greeting, but we still have to initialize some things. */
+
+       XMPP->xp = XML_ParserCreateNS("UTF-8", ':');
+       if (XMPP->xp == NULL) {
+               syslog(LOG_ERR, "xmpp: cannot create XML parser");
+               CC->kill_me = KILLME_XML_PARSER;
+               return;
+       }
+
+       XML_SetElementHandler(XMPP->xp, xmpp_xml_start, xmpp_xml_end);
+       XML_SetCharacterDataHandler(XMPP->xp, xmpp_xml_chardata);
+       // XML_SetUserData(XMPP->xp, something...);
+
+       /* Prevent the "billion laughs" attack against expat by disabling
+        * internal entity expansion.  With 2.x, forcibly stop the parser
+        * if an entity is declared - this is safer and a more obvious
+        * failure mode.  With older versions, simply prevent expansion
+        * of such entities. */
+#ifdef HAVE_XML_STOPPARSER
+       XML_SetEntityDeclHandler(XMPP->xp, xmpp_entity_declaration);
+#else
+       XML_SetDefaultHandler(XMPP->xp, NULL);
+#endif
+
+       CC->can_receive_im = 1;         /* This protocol is capable of receiving instant messages */
+}
+
+
+/* 
+ * Main command loop for XMPP sessions.
+ */
+void xmpp_command_loop(void) {
+       int rc;
+       StrBuf *stream_input = NewStrBuf();
+
+       time(&CC->lastcmd);
+       rc = client_read_random_blob(stream_input, 30);
+       if (rc > 0) {
+               XML_Parse(XMPP->xp, ChrPtr(stream_input), rc, 0);
+       }
+       else {
+               syslog(LOG_ERR, "xmpp: client disconnected: ending session.");
+               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
+       }
+       FreeStrBuf(&stream_input);
+}
+
+
+/*
+ * Async loop for XMPP sessions (handles the transmission of unsolicited stanzas)
+ */
+void xmpp_async_loop(void) {
+       xmpp_process_events();
+       xmpp_output_incoming_messages();
+}
+
+
+/*
+ * Login hook for XMPP sessions
+ */
+void xmpp_login_hook(void) {
+       xmpp_queue_event(XMPP_EVT_LOGIN, CC->cs_principal_id);
+}
+
+
+/*
+ * Logout hook for XMPP sessions
+ */
+void xmpp_logout_hook(void) {
+       xmpp_queue_event(XMPP_EVT_LOGOUT, CC->cs_principal_id);
+}
+
+
+const char *CitadelServiceXMPP="XMPP";
+char *ctdl_module_init_xmpp() {
+       if (!threading) {
+               CtdlRegisterServiceHook(CtdlGetConfigInt("c_xmpp_c2s_port"),
+                                       NULL,
+                                       xmpp_greeting,
+                                       xmpp_command_loop,
+                                       xmpp_async_loop,
+                                       CitadelServiceXMPP
+               );
+               CtdlRegisterSessionHook(xmpp_cleanup_function, EVT_STOP, PRIO_STOP + 70);
+                CtdlRegisterSessionHook(xmpp_login_hook, EVT_LOGIN, PRIO_LOGIN + 90);
+                CtdlRegisterSessionHook(xmpp_logout_hook, EVT_LOGOUT, PRIO_LOGOUT + 90);
+                CtdlRegisterSessionHook(xmpp_login_hook, EVT_UNSTEALTH, PRIO_UNSTEALTH + 1);
+                CtdlRegisterSessionHook(xmpp_logout_hook, EVT_STEALTH, PRIO_STEALTH + 1);
+
+       }
+
+       /* return our module name for the log */
+       return "xmpp";
+}
diff --git a/citadel/server/modules/xmpp/serv_xmpp.h b/citadel/server/modules/xmpp/serv_xmpp.h
new file mode 100644 (file)
index 0000000..c491427
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2007-2019 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+typedef struct _citxmpp {              /* Information about the current session */
+       XML_Parser xp;                  /* XML parser instance for incoming client stream */
+       char server_name[256];          /* who they think we are */
+       char *chardata;
+       int chardata_len;
+       int chardata_alloc;
+       char client_jid[256];           /* "full JID" of the client */
+       int last_event_processed;
+
+       char iq_type[256];              /* for <iq> stanzas */
+       char iq_id[256];
+       char iq_from[256];
+       char iq_to[256];
+       char iq_client_username[256];   /* username requested by the client (NON SASL ONLY) */
+       char iq_client_password[256];   /* password requested by the client (NON SASL ONLY) */
+       char iq_client_resource[256];   /* resource name requested by the client */
+       int iq_session;                 /* nonzero == client is requesting a session */
+       int iq_vcard;                   /* nonzero == client is requesting its vCard */
+       char iq_query_xmlns[256];       /* Namespace of <query> */
+
+       char sasl_auth_mech[32];        /* SASL auth mechanism requested by the client */
+
+       char message_to[256];
+       char *message_body;             /* Message body in transit */
+       int html_tag_level;             /* <html> tag nesting level */
+
+       int bind_requested;             /* In this stanza, client is asking server to bind a resource. */
+       int ping_requested;             /* In this stanza, client is pinging the server. */
+} citxmpp;
+
+#define XMPP ((citxmpp *)CC->session_specific_data)
+
+struct xmpp_event {
+       struct xmpp_event *next;
+       int event_seq;
+       time_t event_time;
+       int event_type;
+       char event_jid[256];
+       int session_which_generated_this_event;
+};
+
+extern struct xmpp_event *xmpp_queue;
+extern int queue_event_seq;
+
+enum {
+       XMPP_EVT_LOGIN,
+       XMPP_EVT_LOGOUT
+};
+
+void xmpp_cleanup_function(void);
+void xmpp_greeting(void);
+void xmpp_command_loop(void);
+void xmpp_async_loop(void);
+void xmpp_sasl_auth(char *, char *);
+void xmpp_output_auth_mechs(void);
+void xmpp_query_namespace(char *, char *, char *, char *);
+void xmpp_wholist_presence_dump(void);
+void xmpp_output_incoming_messages(void);
+void xmpp_queue_event(int, char *);
+void xmpp_process_events(void);
+void xmpp_presence_notify(char *, int);
+void xmpp_roster_item(struct CitContext *);
+void xmpp_send_message(char *, char *);
+void xmpp_non_sasl_authenticate(char *, char *, char *);
+void xmpp_massacre_roster(void);
+void xmpp_delete_old_buddies_who_no_longer_exist_from_the_client_roster(void);
+int xmpp_is_visible(struct CitContext *from, struct CitContext *to_whom);
+char *xmlesc(char *buf, char *str, int bufsiz);
diff --git a/citadel/server/modules/xmpp/xmpp_messages.c b/citadel/server/modules/xmpp/xmpp_messages.c
new file mode 100644 (file)
index 0000000..45e4822
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Handle messages sent and received using XMPP (Jabber) protocol
+ *
+ * Copyright (c) 2007-2010 by Art Cancro
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../internet_addressing.h"
+#include "../../ctdl_module.h"
+#include "serv_xmpp.h"
+
+
+/*
+ * This function is called by the XMPP service's async loop.
+ * If the client session has instant messages waiting, it outputs
+ * unsolicited XML stanzas containing them.
+ */
+void xmpp_output_incoming_messages(void) {
+
+       struct ExpressMessage *ptr;
+       char xmlbuf1[4096];
+       char xmlbuf2[4096];
+
+       while (CC->FirstExpressMessage != NULL) {
+
+               begin_critical_section(S_SESSION_TABLE);
+               ptr = CC->FirstExpressMessage;
+               CC->FirstExpressMessage = CC->FirstExpressMessage->next;
+               end_critical_section(S_SESSION_TABLE);
+
+               cprintf("<message to=\"%s\" from=\"%s\" type=\"chat\">",
+                       xmlesc(xmlbuf1, XMPP->client_jid, sizeof xmlbuf1),
+                       xmlesc(xmlbuf2, ptr->sender_email, sizeof xmlbuf2)
+               );
+               if (ptr->text != NULL) {
+                       striplt(ptr->text);
+                       cprintf("<body>%s</body>", xmlesc(xmlbuf1, ptr->text, sizeof xmlbuf1));
+                       free(ptr->text);
+               }
+               cprintf("</message>");
+               free(ptr);
+       }
+}
+
+
+/*
+ * Client is sending a message.
+ */
+void xmpp_send_message(char *message_to, char *message_body) {
+       char *recp = NULL;
+       struct CitContext *cptr;
+
+       if (message_body == NULL) return;
+       if (message_to == NULL) return;
+       if (IsEmptyStr(message_to)) return;
+       if (!CC->logged_in) return;
+
+       for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
+               if (    (cptr->logged_in)
+                       && (cptr->can_receive_im)
+                       && (!strcasecmp(cptr->cs_principal_id, message_to))
+               ) {
+                       recp = cptr->user.fullname;
+               }
+       }
+
+       if (recp) {
+               PerformXmsgHooks(CC->user.fullname, CC->cs_principal_id, recp, message_body);
+       }
+
+       free(XMPP->message_body);
+       XMPP->message_body = NULL;
+       XMPP->message_to[0] = 0;
+       time(&CC->lastidle);
+}
+
diff --git a/citadel/server/modules/xmpp/xmpp_presence.c b/citadel/server/modules/xmpp/xmpp_presence.c
new file mode 100644 (file)
index 0000000..0dc2432
--- /dev/null
@@ -0,0 +1,377 @@
+/*
+ * Handle XMPP presence exchanges
+ *
+ * Copyright (c) 2007-2021 by Art Cancro and citadel.org
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../internet_addressing.h"
+#include "../../ctdl_module.h"
+#include "serv_xmpp.h"
+
+
+/* 
+ * Indicate the presence of another user to the client
+ * (used in several places)
+ */
+void xmpp_indicate_presence(char *presence_jid) {
+       char xmlbuf[256];
+
+       syslog(LOG_DEBUG, "xmpp: indicating presence of <%s> to <%s>", presence_jid, XMPP->client_jid);
+       cprintf("<presence from=\"%s\" ", xmlesc(xmlbuf, presence_jid, sizeof xmlbuf));
+       cprintf("to=\"%s\"></presence>", xmlesc(xmlbuf, XMPP->client_jid, sizeof xmlbuf));
+}
+
+
+/*
+ * Convenience function to determine whether any given session is 'visible' to any other given session,
+ * and is capable of receiving instant messages from that session.
+ */
+int xmpp_is_visible(struct CitContext *cptr, struct CitContext *to_whom) {
+       int aide = (to_whom->user.axlevel >= AxAideU);
+
+       if (    (cptr->logged_in)
+               &&      (((cptr->cs_flags&CS_STEALTH)==0) || (aide))    /* aides see everyone */
+               &&      (cptr->user.usernum != to_whom->user.usernum)   /* don't show myself */
+               &&      (cptr->can_receive_im)                          /* IM-capable session */
+       ) {
+               return(1);
+       }
+       else {
+               return(0);
+       }
+}
+
+
+/* 
+ * Initial dump of the entire wholist
+ */
+void xmpp_wholist_presence_dump(void) {
+       struct CitContext *cptr = NULL;
+       int nContexts, i;
+       
+       cptr = CtdlGetContextArray(&nContexts);
+       if (!cptr) {
+               return;
+       }
+
+       for (i=0; i<nContexts; i++) {
+               if (xmpp_is_visible(&cptr[i], CC)) {
+                       xmpp_indicate_presence(cptr[i].cs_principal_id);
+               }
+       }
+       free(cptr);
+}
+
+
+/*
+ * Function to remove a buddy subscription and delete from the roster
+ * (used in several places)
+ */
+void xmpp_destroy_buddy(char *presence_jid, int aggressively) {
+       static int unsolicited_id = 1;
+       char xmlbuf1[256];
+       char xmlbuf2[256];
+
+       if (!presence_jid) return;
+       if (!XMPP) return;
+       if (!XMPP->client_jid) return;
+
+       /* Transmit non-presence information */
+       cprintf("<presence type=\"unavailable\" from=\"%s\" to=\"%s\"></presence>",
+               xmlesc(xmlbuf1, presence_jid, sizeof xmlbuf1),
+               xmlesc(xmlbuf2, XMPP->client_jid, sizeof xmlbuf2)
+       );
+
+       /*
+        * Setting the "aggressively" flag also sends an "unsubscribed" presence update.
+        * We only ask for this when flushing the client side roster, because if we do it
+        * in the middle of a session when another user logs off, some clients (Jitsi) interpret
+        * it as a rejection of a subscription request.
+        */
+       if (aggressively) {
+               cprintf("<presence type=\"unsubscribed\" from=\"%s\" to=\"%s\"></presence>",
+                       xmlesc(xmlbuf1, presence_jid, sizeof xmlbuf1),
+                       xmlesc(xmlbuf2, XMPP->client_jid, sizeof xmlbuf2)
+               );
+       }
+
+       // note: we should implement xmpp_indicate_nonpresence so we can use it elsewhere
+
+       /* Do an unsolicited roster update that deletes the contact. */
+       cprintf("<iq from=\"%s\" to=\"%s\" id=\"unbuddy_%x\" type=\"result\">",
+               xmlesc(xmlbuf1, CC->cs_principal_id, sizeof xmlbuf1),
+               xmlesc(xmlbuf2, XMPP->client_jid, sizeof xmlbuf2),
+               ++unsolicited_id
+       );
+       cprintf("<query xmlns=\"jabber:iq:roster\">");
+       cprintf("<item jid=\"%s\" subscription=\"remove\">", xmlesc(xmlbuf1, presence_jid, sizeof xmlbuf1));
+       cprintf("<group>%s</group>", xmlesc(xmlbuf1, CtdlGetConfigStr("c_humannode"), sizeof xmlbuf1));
+       cprintf("</item>");
+       cprintf("</query>"
+               "</iq>"
+       );
+}
+
+
+/*
+ * When a user logs in or out of the local Citadel system, notify all XMPP sessions about it.
+ * THIS FUNCTION HAS A BUG IN IT THAT ENUMERATES THE SESSIONS WRONG.
+ */
+void xmpp_presence_notify(char *presence_jid, int event_type) {
+       struct CitContext *cptr;
+       static int unsolicited_id = 12345;
+       int visible_sessions = 0;
+       int nContexts, i;
+       int which_cptr_is_relevant = (-1);
+
+       if (IsEmptyStr(presence_jid)) return;
+       if (CC->kill_me) return;
+
+       cptr = CtdlGetContextArray(&nContexts);
+       if (!cptr) {
+               return;
+       }
+
+       /* Count the visible sessions for this user */
+       for (i=0; i<nContexts; i++) {
+               if ( (!strcasecmp(cptr[i].cs_principal_id, presence_jid))
+                  && (xmpp_is_visible(&cptr[i], CC))
+               )  {
+                       ++visible_sessions;
+                       which_cptr_is_relevant = i;
+               }
+       }
+
+       syslog(LOG_DEBUG, "xmpp: %d sessions for <%s> are now visible to session %d", visible_sessions, presence_jid, CC->cs_pid);
+
+       if ( (event_type == XMPP_EVT_LOGIN) && (visible_sessions == 1) ) {
+
+               syslog(LOG_DEBUG, "xmpp: telling session %d that <%s> logged in", CC->cs_pid, presence_jid);
+
+               /* Do an unsolicited roster update that adds a new contact. */
+               assert(which_cptr_is_relevant >= 0);
+               cprintf("<iq id=\"unsolicited_%x\" type=\"result\">", ++unsolicited_id);
+               cprintf("<query xmlns=\"jabber:iq:roster\">");
+               xmpp_roster_item(&cptr[which_cptr_is_relevant]);
+               cprintf("</query></iq>");
+
+               /* Transmit presence information */
+               xmpp_indicate_presence(presence_jid);
+       }
+
+       if (visible_sessions == 0) {
+               syslog(LOG_DEBUG, "xmpp: telling session %d that <%s> logged out", CC->cs_pid, presence_jid);
+               xmpp_destroy_buddy(presence_jid, 0);    /* non aggressive presence update */
+       }
+
+       free(cptr);
+}
+
+
+void xmpp_fetch_mortuary_backend(long msgnum, void *userdata) {
+       HashList *mortuary = (HashList *) userdata;
+       struct CtdlMessage *msg;
+       char *ptr = NULL;
+       char *lasts = NULL;
+
+       msg = CtdlFetchMessage(msgnum, 1);
+       if (msg == NULL) {
+               return;
+       }
+
+       /* now add anyone we find into the hashlist */
+
+       /* skip past the headers */
+       ptr = strstr(msg->cm_fields[eMesageText], "\n\n");
+       if (ptr != NULL) {
+               ptr += 2;
+       }
+       else {
+               ptr = strstr(msg->cm_fields[eMesageText], "\n\r\n");
+               if (ptr != NULL) {
+                       ptr += 3;
+               }
+       }
+
+       /* the remaining lines are addresses */
+       if (ptr != NULL) {
+               ptr = strtok_r(ptr, "\n", &lasts);
+               while (ptr != NULL) {
+                       char *pch = strdup(ptr);
+                       Put(mortuary, pch, strlen(pch), pch, NULL);
+                       ptr = strtok_r(NULL, "\n", &lasts);
+               }
+       }
+
+       CM_Free(msg);
+}
+
+
+/*
+ * Fetch the "mortuary" - a list of dead buddies which we keep around forever
+ * so we can remove them from any client's roster that still has them listed
+ */
+HashList *xmpp_fetch_mortuary(void) {
+       HashList *mortuary = NewHash(1, NULL);
+       if (!mortuary) {
+               syslog(LOG_ALERT, "xmpp: NewHash() failed!");
+               return(NULL);
+       }
+
+        if (CtdlGetRoom(&CC->room, USERCONFIGROOM) != 0) {
+               /* no config room exists - no further processing is required. */
+                return(mortuary);
+        }
+        CtdlForEachMessage(MSGS_LAST, 1, NULL, XMPPMORTUARY, NULL,
+                xmpp_fetch_mortuary_backend, (void *)mortuary );
+
+       return(mortuary);
+}
+
+
+/*
+ * Fetch the "mortuary" - a list of dead buddies which we keep around forever
+ * so we can remove them from any client's roster that still has them listed
+ */
+void xmpp_store_mortuary(HashList *mortuary) {
+       HashPos *HashPos;
+       long len;
+       void *Value;
+       const char *Key;
+       StrBuf *themsg;
+
+       themsg = NewStrBuf();
+       StrBufPrintf(themsg,    "Content-type: " XMPPMORTUARY "\n"
+                               "Content-transfer-encoding: 7bit\n"
+                               "\n"
+       );
+
+       HashPos = GetNewHashPos(mortuary, 0);
+       while (GetNextHashPos(mortuary, HashPos, &len, &Key, &Value) != 0) {
+               StrBufAppendPrintf(themsg, "%s\n", (char *)Value);
+       }
+       DeleteHashPos(&HashPos);
+
+       /* Delete the old mortuary */
+       CtdlDeleteMessages(USERCONFIGROOM, NULL, 0, XMPPMORTUARY);
+
+       /* And save the new one to disk */
+       quickie_message(CC->user.fullname, NULL, NULL, USERCONFIGROOM, ChrPtr(themsg), 4, "XMPP Mortuary");
+       FreeStrBuf(&themsg);
+}
+
+
+/*
+ * Upon logout we make an attempt to delete the whole roster, in order to
+ * try to keep "ghost" buddies from remaining in the client-side roster.
+ *
+ * Since the client is probably not still alive, also remember the current
+ * roster for next time so we can delete dead buddies then.
+ */
+void xmpp_massacre_roster(void) {
+       struct CitContext *cptr;
+       int nContexts, i;
+       HashList *mortuary = xmpp_fetch_mortuary();
+
+       cptr = CtdlGetContextArray(&nContexts);
+       if (cptr) {
+               for (i=0; i<nContexts; i++) {
+                       if (xmpp_is_visible(&cptr[i], CC)) {
+                               if (mortuary) {
+                                       char *buddy = strdup(cptr[i].cs_principal_id);
+                                       Put(mortuary, buddy, strlen(buddy), buddy, NULL);
+                               }
+                       }
+               }
+               free (cptr);
+       }
+
+       if (mortuary) {
+               xmpp_store_mortuary(mortuary);
+               DeleteHash(&mortuary);
+       }
+}
+
+
+/*
+ * Stupidly, XMPP does not specify a way to tell the client to flush its client-side roster
+ * and prepare to receive a new one.  So instead we remember every buddy we've ever told the
+ * client about, and push delete operations out at the beginning of a session.
+ * 
+ * We omit any users who happen to be online right now, but we still keep them in the mortuary,
+ * which needs to be maintained as a list of every buddy the user has ever seen.  We don't know
+ * when they're connecting from the same client and when they're connecting from a different client,
+ * so we have no guarantee of what is in the client side roster at connect time.
+ */
+void xmpp_delete_old_buddies_who_no_longer_exist_from_the_client_roster(void) {
+       long len;
+       void *Value;
+       const char *Key;
+       struct CitContext *cptr;
+       int nContexts, i;
+       int online_now = 0;
+       HashList *mortuary = xmpp_fetch_mortuary();
+       HashPos *HashPos = GetNewHashPos(mortuary, 0);
+
+       /* we need to omit anyone who is currently online */
+       cptr = CtdlGetContextArray(&nContexts);
+
+       /* go through the list of users in the mortuary... */
+       while (GetNextHashPos(mortuary, HashPos, &len, &Key, &Value) != 0)
+       {
+
+               online_now = 0;
+               if (cptr) for (i=0; i<nContexts; i++) {
+                       if (xmpp_is_visible(&cptr[i], CC)) {
+                               if (!strcasecmp(cptr[i].cs_principal_id, (char *)Value)) {
+                                       online_now = 1;
+                               }
+                       }
+               }
+
+               if (!online_now) {
+                       xmpp_destroy_buddy((char *)Value, 1);   /* aggressive presence update */
+               }
+
+       }
+       DeleteHashPos(&HashPos);
+       DeleteHash(&mortuary);
+       free(cptr);
+}
diff --git a/citadel/server/modules/xmpp/xmpp_query_namespace.c b/citadel/server/modules/xmpp/xmpp_query_namespace.c
new file mode 100644 (file)
index 0000000..01b9768
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * Handle <iq> <get> <query> type situations (namespace queries)
+ *
+ * Copyright (c) 2007-2015 by Art Cancro and citadel.org
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../internet_addressing.h"
+#include "../../ctdl_module.h"
+#include "serv_xmpp.h"
+
+
+/*
+ * Output a single roster item, for roster queries or pushes
+ */
+void xmpp_roster_item(struct CitContext *cptr) {
+       char xmlbuf1[256];
+       char xmlbuf2[256];
+
+       cprintf("<item jid=\"%s\" name=\"%s\" subscription=\"both\">",
+               xmlesc(xmlbuf1, cptr->cs_principal_id, sizeof xmlbuf1),
+               xmlesc(xmlbuf2, cptr->user.fullname, sizeof xmlbuf2)
+       );
+       cprintf("<group>%s</group>", xmlesc(xmlbuf1, CtdlGetConfigStr("c_humannode"), sizeof xmlbuf1));
+       cprintf("</item>");
+}
+
+
+/* 
+ * Return the results for a "jabber:iq:roster:query"
+ *
+ * Since we are not yet managing a roster, we simply return the entire wholist
+ * (minus any entries for this user -- don't tell me about myself)
+ *
+ */
+void xmpp_iq_roster_query(void)
+{
+       struct CitContext *cptr;
+       int nContexts, i;
+
+       syslog(LOG_DEBUG, "xmpp: roster push!");
+       cprintf("<query xmlns=\"jabber:iq:roster\">");
+       cptr = CtdlGetContextArray(&nContexts);
+       if (cptr) {
+               for (i=0; i<nContexts; i++) {
+                       if (xmpp_is_visible(&cptr[i], CC)) {
+                               syslog(LOG_DEBUG, "xmpp: rosterizing %s", cptr[i].user.fullname);
+                               xmpp_roster_item(&cptr[i]);
+                       }
+               }
+               free (cptr);
+       }
+       cprintf("</query>");
+}
+
+
+/*
+ * Client is doing a namespace query.  These are all handled differently.
+ */
+void xmpp_query_namespace(char *iq_id, char *iq_from, char *iq_to, char *query_xmlns)
+{
+       int supported_namespace = 0;
+       int roster_query = 0;
+       char xmlbuf[256];
+       int reply_must_be_from_my_jid = 0;
+
+       /* We need to know before we begin the response whether this is a supported namespace, so
+        * unfortunately all supported namespaces need to be defined here *and* down below where
+        * they are handled.
+        */
+       if (
+               (!strcasecmp(query_xmlns, "jabber:iq:roster:query"))
+               || (!strcasecmp(query_xmlns, "jabber:iq:auth:query"))
+               || (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#items:query"))
+               || (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#info:query"))
+       ) {
+               supported_namespace = 1;
+       }
+
+       syslog(LOG_DEBUG, "xmpp: xmpp_query_namespace(id=%s, from=%s, to=%s, xmlns=%s)", iq_id, iq_from, iq_to, query_xmlns);
+
+       /*
+        * Beginning of query result.
+        */
+
+       if (!strcasecmp(query_xmlns, "jabber:iq:roster:query")) {
+               reply_must_be_from_my_jid = 1;
+       }
+
+       char dom[1024];                                                         // client is expecting to see the reply
+       if (reply_must_be_from_my_jid) {                                        // coming "from" the user's jid
+               safestrncpy(dom, XMPP->client_jid, sizeof(dom));
+               char *slash = strchr(dom, '/');
+               if (slash) {
+                       *slash = 0;
+               }
+       }
+       else {
+               safestrncpy(dom, XMPP->client_jid, sizeof(dom));                // client is expecting to see the reply
+               if (IsEmptyStr(dom)) {                                          // coming "from" the domain of the user's jid
+                       safestrncpy(dom, XMPP->server_name, sizeof(dom));
+               }
+               char *at = strrchr(dom, '@');
+               if (at) {
+                       strcpy(dom, ++at);
+               }
+               char *slash = strchr(dom, '/');
+               if (slash) {
+                       *slash = 0;
+               }
+       }
+
+       if (supported_namespace) {
+               cprintf("<iq type=\"result\" from=\"%s\" ", xmlesc(xmlbuf, dom, sizeof xmlbuf) );
+       }
+       else {
+               cprintf("<iq type=\"error\" from=\"%s\" ", xmlesc(xmlbuf, dom, sizeof xmlbuf) );
+       }
+       if (!IsEmptyStr(iq_from)) {
+               cprintf("to=\"%s\" ", xmlesc(xmlbuf, iq_from, sizeof xmlbuf));
+       }
+       cprintf("id=\"%s\">", xmlesc(xmlbuf, iq_id, sizeof xmlbuf));
+
+       /*
+        * Is this a query we know how to handle?
+        */
+
+       if (!strcasecmp(query_xmlns, "jabber:iq:roster:query")) {
+               roster_query = 1;
+               xmpp_iq_roster_query();
+       }
+
+       else if (!strcasecmp(query_xmlns, "jabber:iq:auth:query")) {
+               cprintf("<query xmlns=\"jabber:iq:auth\">"
+                       "<username/><password/><resource/>"
+                       "</query>"
+               );
+       }
+
+       // Extension "xep-0030" (http://xmpp.org/extensions/xep-0030.html) (return an empty set of results)
+       else if (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#items:query")) {
+               cprintf("<query xmlns=\"%s\"/>", xmlesc(xmlbuf, query_xmlns, sizeof xmlbuf));
+       }
+
+       // Extension "xep-0030" (http://xmpp.org/extensions/xep-0030.html) (return an empty set of results)
+       else if (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#info:query")) {
+               cprintf("<query xmlns=\"%s\"/>", xmlesc(xmlbuf, query_xmlns, sizeof xmlbuf));
+       }
+
+       /*
+        * If we didn't hit any known query namespaces then we should deliver a
+        * "service unavailable" error (see RFC3921 section 2.4 and 11.1.5.4)
+        */
+
+       else {
+               syslog(LOG_DEBUG, "xmpp: unknown query namespace '%s' - returning <service-unavailable/>", query_xmlns);
+               cprintf("<error code=\"503\" type=\"cancel\">"
+                       "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
+                       "</error>"
+               );
+       }
+
+       cprintf("</iq>");
+
+       /* If we told the client who is on the roster, we also need to tell the client
+        * who is *not* on the roster.  (It's down here because we can't do it in the same
+        * stanza; this will be an unsolicited push.)
+        */
+       if (roster_query) {
+               xmpp_delete_old_buddies_who_no_longer_exist_from_the_client_roster();
+       }
+}
diff --git a/citadel/server/modules/xmpp/xmpp_queue.c b/citadel/server/modules/xmpp/xmpp_queue.c
new file mode 100644 (file)
index 0000000..175c7d9
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * XMPP event queue
+ *
+ * Copyright (c) 2007-2021 by Art Cancro
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../internet_addressing.h"
+#include "../../ctdl_module.h"
+#include "serv_xmpp.h"
+
+int queue_event_seq = 0;
+
+void xmpp_queue_event(int event_type, char *email_addr) {
+
+       struct xmpp_event *xptr = NULL;
+       struct xmpp_event *new_event = NULL;
+       struct xmpp_event *last = NULL;
+       int purged_something = 0;
+       struct CitContext *cptr;
+
+       syslog(LOG_DEBUG, "xmpp: xmpp_queue_event(%d, %s)", event_type, email_addr);
+
+       /* Purge events more than a minute old */
+       begin_critical_section(S_XMPP_QUEUE);
+       do {
+               purged_something = 0;
+               if (xmpp_queue != NULL) {
+                       if ((time(NULL) - xmpp_queue->event_time) > 60) {
+                               xptr = xmpp_queue->next;
+                               free(xmpp_queue);
+                               xmpp_queue = xptr;
+                               purged_something = 1;
+                       }
+               }
+       } while(purged_something);
+       end_critical_section(S_XMPP_QUEUE);
+
+       /* Create a new event */
+       new_event = (struct xmpp_event *) malloc(sizeof(struct xmpp_event));
+       new_event->next = NULL;
+       new_event->event_time = time(NULL);
+       new_event->event_seq = ++queue_event_seq;
+       new_event->event_type = event_type;
+       new_event->session_which_generated_this_event = CC->cs_pid;
+       safestrncpy(new_event->event_jid, email_addr, sizeof new_event->event_jid);
+
+       /* Add it to the list */
+       begin_critical_section(S_XMPP_QUEUE);
+       if (xmpp_queue == NULL) {
+               xmpp_queue = new_event;
+       }
+       else {
+               for (xptr = xmpp_queue; xptr != NULL; xptr = xptr->next) {
+                       if (xptr->next == NULL) {
+                               last = xptr;
+                       }
+               }
+               last->next = new_event;
+       }
+       end_critical_section(S_XMPP_QUEUE);
+
+       /* Tell the sessions that something is happening */
+       begin_critical_section(S_SESSION_TABLE);
+       for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
+               if ((cptr->logged_in) && (cptr->h_async_function == xmpp_async_loop)) {
+                       set_async_waiting(cptr);
+               }
+       }
+       end_critical_section(S_SESSION_TABLE);
+}
+
+
+/* 
+ * Are we interested in anything from the queue?  (Called in async loop)
+ */
+void xmpp_process_events(void) {
+       struct xmpp_event *xptr = NULL;
+       int highest_event = 0;
+
+       for (xptr=xmpp_queue; xptr!=NULL; xptr=xptr->next) {
+               if (xptr->event_seq > XMPP->last_event_processed) {
+
+                       switch(xptr->event_type) {
+
+                               case XMPP_EVT_LOGIN:
+                               case XMPP_EVT_LOGOUT:
+                                       if (xptr->session_which_generated_this_event != CC->cs_pid) {
+                                               xmpp_presence_notify(xptr->event_jid, xptr->event_type);
+                                       }
+                                       break;
+                       }
+
+                       if (xptr->event_seq > highest_event) {
+                               highest_event = xptr->event_seq;
+                       }
+               }
+       }
+
+       XMPP->last_event_processed = highest_event;
+}
diff --git a/citadel/server/modules/xmpp/xmpp_sasl_service.c b/citadel/server/modules/xmpp/xmpp_sasl_service.c
new file mode 100644 (file)
index 0000000..494c0da
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * Barebones SASL authentication service for XMPP (Jabber) clients.
+ *
+ * Note: RFC3920 says we "must" support DIGEST-MD5 but we only support PLAIN.
+ *
+ * Copyright (c) 2007-2019 by Art Cancro
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../internet_addressing.h"
+#include "../../ctdl_module.h"
+#include "serv_xmpp.h"
+
+
+/*
+ * PLAIN authentication.  Returns zero on success, nonzero on failure.
+ */
+int xmpp_auth_plain(char *authstring)
+{
+       char decoded_authstring[1024];
+       char ident[256];
+       char user[256];
+       char pass[256];
+       int result;
+       long len;
+
+       /* Take apart the authentication string */
+       memset(pass, 0, sizeof(pass));
+
+       CtdlDecodeBase64(decoded_authstring, authstring, strlen(authstring));
+       safestrncpy(ident, decoded_authstring, sizeof ident);
+       safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
+       len = safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
+       if (len < 0) {
+               len = -len;
+       }
+
+       if (!IsEmptyStr(ident)) {
+               result = CtdlLoginExistingUser(ident);
+       }
+       else {
+               result = CtdlLoginExistingUser(user);
+       }
+
+       if (result == login_ok) {
+               if (CtdlTryPassword(pass, len) == pass_ok) {
+                       return(0);                              /* success */
+               }
+       }
+
+       return(1);                                              /* failure */
+}
+
+
+/*
+ * Output the list of SASL mechanisms offered by this stream.
+ */
+void xmpp_output_auth_mechs(void) {
+       cprintf("<mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
+       cprintf("<mechanism>PLAIN</mechanism>");
+       cprintf("</mechanisms>");
+}
+
+
+/*
+ * Here we go ... client is trying to authenticate.
+ */
+void xmpp_sasl_auth(char *sasl_auth_mech, char *authstring) {
+
+       if (strcasecmp(sasl_auth_mech, "PLAIN")) {
+               cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
+               cprintf("<invalid-mechanism/>");
+               cprintf("</failure>");
+               return;
+       }
+
+        if (CC->logged_in) {
+               CtdlUserLogout();  /* Client may try to log in twice.  Handle this. */
+       }
+
+       if (CC->nologin) {
+               cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
+               cprintf("<system-shutdown/>");
+               cprintf("</failure>");
+       }
+
+       else if (xmpp_auth_plain(authstring) == 0) {
+               cprintf("<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>");
+       }
+
+       else {
+               cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
+               cprintf("<not-authorized/>");
+               cprintf("</failure>");
+       }
+}
+
+
+/*
+ * Non-SASL authentication
+ */
+void xmpp_non_sasl_authenticate(char *iq_id, char *username, char *password) {
+       int result;
+       char xmlbuf[256];
+
+        if (CC->logged_in) {
+               CtdlUserLogout();  /* Client may try to log in twice.  Handle this. */
+       }
+
+       result = CtdlLoginExistingUser(username);
+       if (result == login_ok) {
+               result = CtdlTryPassword(password, strlen(password));
+               if (result == pass_ok) {
+                       cprintf("<iq type=\"result\" id=\"%s\"></iq>", xmlesc(xmlbuf, iq_id, sizeof xmlbuf));   /* success */
+                       return;
+               }
+       }
+
+       /* failure */
+       cprintf("<iq type=\"error\" id=\"%s\">", xmlesc(xmlbuf, iq_id, sizeof xmlbuf));
+       cprintf("<error code=\"401\" type=\"auth\">"
+               "<not-authorized xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
+               "</error>"
+               "</iq>"
+       );
+}
diff --git a/citadel/server/modules_init.c b/citadel/server/modules_init.c
new file mode 100644 (file)
index 0000000..966c769
--- /dev/null
@@ -0,0 +1,75 @@
+// Module initialization hub
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or disclosure
+// is subject to the terms of the GNU General Public License, version 3.
+// The program is distributed without any warranty, expressed or implied.
+//
+// To add new modules to the server:
+// 1. Write the module and place it in a server/modules/[module_name]/ directory
+// 2. Add its initialization function to server/modules_init.h
+// 3. Call its initialization function from server/modules_init.c
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <stdio.h>
+#include "modules_init.h"
+
+// Module initialization functions will be called TWICE during startup, once before
+// the server has gone into multithreading mode, and once afterwards.  Most modules
+// can be initialized while in multithreading mode, but your module code should be
+// prepared for this kind of initialization.  Look at the existing modules to see what
+// goes on there.
+int threading = 0;
+
+void initialize_modules(int is_threading) {
+       threading = is_threading;
+       syslog(LOG_DEBUG, "modules: begin initializing modules (threading=%d)", threading);
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_control());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_euidindex());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_msgbase());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_database());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_autocompletion());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_bio());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_blog());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_calendar());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_checkpoint());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_virus());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_file_ops());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_ctdl_message());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_rooms());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_serv_session());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_syscmd());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_serv_user());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_expire());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_fulltext());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_image());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_imap());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_sieve());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_inetcfg());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_instmsg());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_listdeliver());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_listsub());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_migrate());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_newuser());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_nntp());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_notes());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_openid_rp());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_pop3());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_pop3client());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_roomchat());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_rssclient());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_rwho());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_smtp());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_smtpclient());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_spam());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_test());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_upgrade());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_vcard());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_wiki());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_xmpp());
+       syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_netconfig());
+       syslog(LOG_DEBUG, "modules: finished initializing modules (threading=%d)", threading);
+}
diff --git a/citadel/server/modules_init.h b/citadel/server/modules_init.h
new file mode 100644 (file)
index 0000000..0513dcd
--- /dev/null
@@ -0,0 +1,64 @@
+// Headers for module initialization hub
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or disclosure
+// is subject to the terms of the GNU General Public License, version 3.
+// The program is distributed without any warranty, expressed or implied.
+//
+// To add new modules to the server:
+// 1. Write the module and place it in a server/modules/[module_name]/ directory
+// 2. Add its initialization function to server/modules_init.h
+// 3. Call its initialization function from server/modules_init.c
+
+#ifndef MODULES_INIT_H
+#define MODULES_INIT_H
+#include "ctdl_module.h"
+extern size_t nSizErrmsg;
+void initialize_modules (int threading);
+void pre_startup_upgrades(void);
+char *ctdl_module_init_control(void);
+char *ctdl_module_init_euidindex(void);
+char *ctdl_module_init_msgbase(void);
+char *ctdl_module_init_database(void);
+char *ctdl_module_init_autocompletion(void);
+char *ctdl_module_init_bio(void);
+char *ctdl_module_init_blog(void);
+char *ctdl_module_init_calendar(void);
+char *ctdl_module_init_checkpoint(void);
+char *ctdl_module_init_virus(void);
+char *ctdl_module_init_file_ops(void);
+char *ctdl_module_init_ctdl_message(void);
+char *ctdl_module_init_rooms(void);
+char *ctdl_module_init_serv_session(void);
+char *ctdl_module_init_syscmd(void);
+char *ctdl_module_init_serv_user(void);
+char *ctdl_module_init_expire(void);
+char *ctdl_module_init_fulltext(void);
+char *ctdl_module_init_image(void);
+char *ctdl_module_init_imap(void);
+char *ctdl_module_init_sieve(void);
+char *ctdl_module_init_inetcfg(void);
+char *ctdl_module_init_instmsg(void);
+char *ctdl_module_init_listdeliver(void);
+char *ctdl_module_init_listsub(void);
+char *ctdl_module_init_migrate(void);
+char *ctdl_module_init_newuser(void);
+char *ctdl_module_init_nntp(void);
+char *ctdl_module_init_notes(void);
+char *ctdl_module_init_openid_rp(void);
+char *ctdl_module_init_pop3(void);
+char *ctdl_module_init_pop3client(void);
+char *ctdl_module_init_roomchat(void);
+char *ctdl_module_init_rssclient(void);
+char *ctdl_module_init_rwho(void);
+char *ctdl_module_init_smtp(void);
+char *ctdl_module_init_smtpclient(void);
+char *ctdl_module_init_spam(void);
+char *ctdl_module_init_test(void);
+char *ctdl_module_init_upgrade(void);
+char *ctdl_module_init_vcard(void);
+char *ctdl_module_init_wiki(void);
+char *ctdl_module_init_xmpp(void);
+char *ctdl_module_init_netconfig(void);
+#endif /* MODULES_INIT_H */
diff --git a/citadel/server/msgbase.c b/citadel/server/msgbase.c
new file mode 100644 (file)
index 0000000..0cde681
--- /dev/null
@@ -0,0 +1,3559 @@
+// Implements the message store.
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <regex.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <libcitadel.h>
+#include "ctdl_module.h"
+#include "citserver.h"
+#include "control.h"
+#include "config.h"
+#include "clientsocket.h"
+#include "genstamp.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "internet_addressing.h"
+#include "euidindex.h"
+#include "msgbase.h"
+#include "journaling.h"
+
+struct addresses_to_be_filed *atbf = NULL;
+
+// These are the four-character field headers we use when outputting
+// messages in Citadel format (as opposed to RFC822 format).
+char *msgkeys[] = {
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
+       NULL, 
+       "from", // A -> eAuthor
+       NULL,   // B -> eBig_message
+       NULL,   // C (formerly used as eRemoteRoom)
+       NULL,   // D (formerly used as eDestination)
+       "exti", // E -> eXclusivID
+       "rfca", // F -> erFc822Addr
+       NULL,   // G
+       "hnod", // H (formerly used as eHumanNode)
+       "msgn", // I -> emessageId
+       "jrnl", // J -> eJournal
+       "rep2", // K -> eReplyTo
+       "list", // L -> eListID
+       "text", // M -> eMesageText
+       NULL,   // N (formerly used as eNodename)
+       "room", // O -> eOriginalRoom
+       "path", // P -> eMessagePath
+       NULL,   // Q
+       "rcpt", // R -> eRecipient
+       NULL,   // S (formerly used as eSpecialField)
+       "time", // T -> eTimestamp
+       "subj", // U -> eMsgSubject
+       "nvto", // V -> eenVelopeTo
+       "wefw", // W -> eWeferences
+       NULL,   // X
+       "cccc", // Y -> eCarbonCopY
+       NULL    // Z
+};
+
+
+HashList *msgKeyLookup = NULL;
+
+int GetFieldFromMnemonic(eMsgField *f, const char* c) {
+       void *v = NULL;
+       if (GetHash(msgKeyLookup, c, 4, &v)) {
+               *f = (eMsgField) v;
+               return 1;
+       }
+       return 0;
+}
+
+void FillMsgKeyLookupTable(void) {
+       long i;
+
+       msgKeyLookup = NewHash (1, FourHash);
+
+       for (i=0; i < 91; i++) {
+               if (msgkeys[i] != NULL) {
+                       Put(msgKeyLookup, msgkeys[i], 4, (void*)i, reference_free_handler);
+               }
+       }
+}
+
+
+eMsgField FieldOrder[]  = {
+/* Important fields */
+       emessageId   ,
+       eMessagePath ,
+       eTimestamp   ,
+       eAuthor      ,
+       erFc822Addr  ,
+       eOriginalRoom,
+       eRecipient   ,
+/* Semi-important fields */
+       eBig_message ,
+       eExclusiveID ,
+       eWeferences  ,
+       eJournal     ,
+/* G is not used yet */
+       eReplyTo     ,
+       eListID      ,
+/* Q is not used yet */
+       eenVelopeTo  ,
+/* X is not used yet */
+/* Z is not used yet */
+       eCarbonCopY  ,
+       eMsgSubject  ,
+/* internal only */
+       eErrorMsg    ,
+       eSuppressIdx ,
+       eExtnotify   ,
+/* Message text (MUST be last) */
+       eMesageText 
+/* Not saved to disk: 
+       eVltMsgNum
+*/
+};
+
+static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
+
+
+int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which) {
+       return !((Msg->cm_fields[which] != NULL) && (Msg->cm_fields[which][0] != '\0'));
+}
+
+
+void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
+       if (Msg->cm_fields[which] != NULL) {
+               free (Msg->cm_fields[which]);
+       }
+       if (length < 0) {                       // You can set the length to -1 to have CM_SetField measure it for you
+               length = strlen(buf);
+       }
+       Msg->cm_fields[which] = malloc(length + 1);
+       memcpy(Msg->cm_fields[which], buf, length);
+       Msg->cm_fields[which][length] = '\0';
+       Msg->cm_lengths[which] = length;
+}
+
+
+void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue) {
+       char buf[128];
+       long len;
+       len = snprintf(buf, sizeof(buf), "%ld", lvalue);
+       CM_SetField(Msg, which, buf, len);
+}
+
+
+void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen) {
+       if (Msg->cm_fields[WhichToCut] == NULL)
+               return;
+
+       if (Msg->cm_lengths[WhichToCut] > maxlen)
+       {
+               Msg->cm_fields[WhichToCut][maxlen] = '\0';
+               Msg->cm_lengths[WhichToCut] = maxlen;
+       }
+}
+
+
+void CM_FlushField(struct CtdlMessage *Msg, eMsgField which) {
+       if (Msg->cm_fields[which] != NULL)
+               free (Msg->cm_fields[which]);
+       Msg->cm_fields[which] = NULL;
+       Msg->cm_lengths[which] = 0;
+}
+
+
+void CM_Flush(struct CtdlMessage *Msg) {
+       int i;
+
+       if (CM_IsValidMsg(Msg) == 0) {
+               return;
+       }
+
+       for (i = 0; i < 256; ++i) {
+               CM_FlushField(Msg, i);
+       }
+}
+
+
+void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy) {
+       long len;
+       if (Msg->cm_fields[WhichToPutTo] != NULL) {
+               free (Msg->cm_fields[WhichToPutTo]);
+       }
+
+       if (Msg->cm_fields[WhichtToCopy] != NULL) {
+               len = Msg->cm_lengths[WhichtToCopy];
+               Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
+               memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len);
+               Msg->cm_fields[WhichToPutTo][len] = '\0';
+               Msg->cm_lengths[WhichToPutTo] = len;
+       }
+       else {
+               Msg->cm_fields[WhichToPutTo] = NULL;
+               Msg->cm_lengths[WhichToPutTo] = 0;
+       }
+}
+
+
+void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
+       if (Msg->cm_fields[which] != NULL) {
+               long oldmsgsize;
+               long newmsgsize;
+               char *new;
+
+               oldmsgsize = Msg->cm_lengths[which] + 1;
+               newmsgsize = length + oldmsgsize;
+
+               new = malloc(newmsgsize);
+               memcpy(new, buf, length);
+               memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
+               free(Msg->cm_fields[which]);
+               Msg->cm_fields[which] = new;
+               Msg->cm_lengths[which] = newmsgsize - 1;
+       }
+       else {
+               Msg->cm_fields[which] = malloc(length + 1);
+               memcpy(Msg->cm_fields[which], buf, length);
+               Msg->cm_fields[which][length] = '\0';
+               Msg->cm_lengths[which] = length;
+       }
+}
+
+
+void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length) {
+       if (Msg->cm_fields[which] != NULL) {
+               free (Msg->cm_fields[which]);
+       }
+
+       Msg->cm_fields[which] = *buf;
+       *buf = NULL;
+       if (length < 0) {                       // You can set the length to -1 to have CM_SetField measure it for you
+               Msg->cm_lengths[which] = strlen(Msg->cm_fields[which]);
+       }
+       else {
+               Msg->cm_lengths[which] = length;
+       }
+}
+
+
+void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf) {
+       if (Msg->cm_fields[which] != NULL) {
+               free (Msg->cm_fields[which]);
+       }
+
+       Msg->cm_lengths[which] = StrLength(*buf);
+       Msg->cm_fields[which] = SmashStrBuf(buf);
+}
+
+
+void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen) {
+       if (Msg->cm_fields[which] != NULL) {
+               *retlen = Msg->cm_lengths[which];
+               *ret = Msg->cm_fields[which];
+               Msg->cm_fields[which] = NULL;
+               Msg->cm_lengths[which] = 0;
+       }
+       else {
+               *ret = NULL;
+               *retlen = 0;
+       }
+}
+
+
+// Returns 1 if the supplied pointer points to a valid Citadel message.
+// If the pointer is NULL or the magic number check fails, returns 0.
+int CM_IsValidMsg(struct CtdlMessage *msg) {
+       if (msg == NULL) {
+               return 0;
+       }
+       if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
+               syslog(LOG_WARNING, "msgbase: CM_IsValidMsg() self-check failed");
+               return 0;
+       }
+       return 1;
+}
+
+
+void CM_FreeContents(struct CtdlMessage *msg) {
+       int i;
+
+       for (i = 0; i < 256; ++i)
+               if (msg->cm_fields[i] != NULL) {
+                       free(msg->cm_fields[i]);
+                       msg->cm_lengths[i] = 0;
+               }
+
+       msg->cm_magic = 0;      // just in case
+}
+
+
+// 'Destructor' for struct CtdlMessage
+void CM_Free(struct CtdlMessage *msg) {
+       if (CM_IsValidMsg(msg) == 0) {
+               if (msg != NULL) free (msg);
+               return;
+       }
+       CM_FreeContents(msg);
+       free(msg);
+}
+
+
+int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg) {
+       long len;
+       len = OrgMsg->cm_lengths[i];
+       NewMsg->cm_fields[i] = malloc(len + 1);
+       if (NewMsg->cm_fields[i] == NULL) {
+               return 0;
+       }
+       memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
+       NewMsg->cm_fields[i][len] = '\0';
+       NewMsg->cm_lengths[i] = len;
+       return 1;
+}
+
+
+struct CtdlMessage *CM_Duplicate(struct CtdlMessage *OrgMsg) {
+       int i;
+       struct CtdlMessage *NewMsg;
+
+       if (CM_IsValidMsg(OrgMsg) == 0) {
+               return NULL;
+       }
+       NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
+       if (NewMsg == NULL) {
+               return NULL;
+       }
+
+       memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
+
+       memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
+       
+       for (i = 0; i < 256; ++i) {
+               if (OrgMsg->cm_fields[i] != NULL) {
+                       if (!CM_DupField(i, OrgMsg, NewMsg)) {
+                               CM_Free(NewMsg);
+                               return NULL;
+                       }
+               }
+       }
+
+       return NewMsg;
+}
+
+
+// Determine if a given message matches the fields in a message template.
+// Return 0 for a successful match.
+int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
+       int i;
+
+       // If there aren't any fields in the template, all messages will match.
+       if (template == NULL) return(0);
+
+       // Null messages are bogus.
+       if (msg == NULL) return(1);
+
+       for (i='A'; i<='Z'; ++i) {
+               if (template->cm_fields[i] != NULL) {
+                       if (msg->cm_fields[i] == NULL) {
+                               // Considered equal if temmplate is empty string
+                               if (IsEmptyStr(template->cm_fields[i])) continue;
+                               return 1;
+                       }
+                       if ((template->cm_lengths[i] != msg->cm_lengths[i]) ||
+                           (strcasecmp(msg->cm_fields[i], template->cm_fields[i])))
+                               return 1;
+               }
+       }
+
+       // All compares succeeded: we have a match!
+       return 0;
+}
+
+
+// Retrieve the "seen" message list for the current room.
+void CtdlGetSeen(char *buf, int which_set) {
+       visit vbuf;
+
+       // Learn about the user and room in question
+       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+
+       if (which_set == ctdlsetseen_seen) {
+               safestrncpy(buf, vbuf.v_seen, SIZ);
+       }
+       if (which_set == ctdlsetseen_answered) {
+               safestrncpy(buf, vbuf.v_answered, SIZ);
+       }
+}
+
+
+// Manipulate the "seen msgs" string (or other message set strings)
+void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
+               int target_setting, int which_set,
+               struct ctdluser *which_user, struct ctdlroom *which_room) {
+       struct cdbdata *cdbfr;
+       int i, k;
+       int is_seen = 0;
+       int was_seen = 0;
+       long lo = (-1L);
+       long hi = (-1L);
+       visit vbuf;
+       long *msglist;
+       int num_msgs = 0;
+       StrBuf *vset;
+       StrBuf *setstr;
+       StrBuf *lostr;
+       StrBuf *histr;
+       const char *pvset;
+       char *is_set;   // actually an array of booleans
+
+       // Don't bother doing *anything* if we were passed a list of zero messages
+       if (num_target_msgnums < 1) {
+               return;
+       }
+
+       // If no room was specified, we go with the current room.
+       if (!which_room) {
+               which_room = &CC->room;
+       }
+
+       // If no user was specified, we go with the current user.
+       if (!which_user) {
+               which_user = &CC->user;
+       }
+
+       syslog(LOG_DEBUG, "msgbase: CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>",
+                  num_target_msgnums, target_msgnums[0],
+                  (target_setting ? "SET" : "CLEAR"),
+                  which_set,
+                  which_room->QRname);
+
+       // Learn about the user and room in question
+       CtdlGetRelationship(&vbuf, which_user, which_room);
+
+       // Load the message list
+       cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
+       if (cdbfr != NULL) {
+               msglist = (long *) cdbfr->ptr;
+               cdbfr->ptr = NULL;      // CtdlSetSeen() now owns this memory
+               num_msgs = cdbfr->len / sizeof(long);
+               cdb_free(cdbfr);
+       }
+       else {
+               return; // No messages at all?  No further action.
+       }
+
+       is_set = malloc(num_msgs * sizeof(char));
+       memset(is_set, 0, (num_msgs * sizeof(char)) );
+
+       // Decide which message set we're manipulating
+       switch(which_set) {
+       case ctdlsetseen_seen:
+               vset = NewStrBufPlain(vbuf.v_seen, -1);
+               break;
+       case ctdlsetseen_answered:
+               vset = NewStrBufPlain(vbuf.v_answered, -1);
+               break;
+       default:
+               vset = NewStrBuf();
+       }
+
+
+#if 0  // This is a special diagnostic section.  Do not allow it to run during normal operation.
+       syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
+       for (i=0; i<num_msgs; ++i) {
+               if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
+       }
+       syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
+       for (k=0; k<num_target_msgnums; ++k) {
+               if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
+       }
+#endif
+
+       // Translate the existing sequence set into an array of booleans
+       setstr = NewStrBuf();
+       lostr = NewStrBuf();
+       histr = NewStrBuf();
+       pvset = NULL;
+       while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
+
+               StrBufExtract_token(lostr, setstr, 0, ':');
+               if (StrBufNum_tokens(setstr, ':') >= 2) {
+                       StrBufExtract_token(histr, setstr, 1, ':');
+               }
+               else {
+                       FlushStrBuf(histr);
+                       StrBufAppendBuf(histr, lostr, 0);
+               }
+               lo = StrTol(lostr);
+               if (!strcmp(ChrPtr(histr), "*")) {
+                       hi = LONG_MAX;
+               }
+               else {
+                       hi = StrTol(histr);
+               }
+
+               for (i = 0; i < num_msgs; ++i) {
+                       if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
+                               is_set[i] = 1;
+                       }
+               }
+       }
+       FreeStrBuf(&setstr);
+       FreeStrBuf(&lostr);
+       FreeStrBuf(&histr);
+
+       // Now translate the array of booleans back into a sequence set
+       FlushStrBuf(vset);
+       was_seen = 0;
+       lo = (-1);
+       hi = (-1);
+
+       for (i=0; i<num_msgs; ++i) {
+               is_seen = is_set[i];
+
+               // Apply changes
+               for (k=0; k<num_target_msgnums; ++k) {
+                       if (msglist[i] == target_msgnums[k]) {
+                               is_seen = target_setting;
+                       }
+               }
+
+               if ((was_seen == 0) && (is_seen == 1)) {
+                       lo = msglist[i];
+               }
+               else if ((was_seen == 1) && (is_seen == 0)) {
+                       hi = msglist[i-1];
+
+                       if (StrLength(vset) > 0) {
+                               StrBufAppendBufPlain(vset, HKEY(","), 0);
+                       }
+                       if (lo == hi) {
+                               StrBufAppendPrintf(vset, "%ld", hi);
+                       }
+                       else {
+                               StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
+                       }
+               }
+
+               if ((is_seen) && (i == num_msgs - 1)) {
+                       if (StrLength(vset) > 0) {
+                               StrBufAppendBufPlain(vset, HKEY(","), 0);
+                       }
+                       if ((i==0) || (was_seen == 0)) {
+                               StrBufAppendPrintf(vset, "%ld", msglist[i]);
+                       }
+                       else {
+                               StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
+                       }
+               }
+
+               was_seen = is_seen;
+       }
+
+       // We will have to stuff this string back into a 4096 byte buffer, so if it's
+       // larger than that now, truncate it by removing tokens from the beginning.
+       // The limit of 100 iterations is there to prevent an infinite loop in case
+       // something unexpected happens.
+       int number_of_truncations = 0;
+       while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
+               StrBufRemove_token(vset, 0, ',');
+               ++number_of_truncations;
+       }
+
+       // If we're truncating the sequence set of messages marked with the 'seen' flag,
+       // we want the earliest messages (the truncated ones) to be marked, not unmarked.
+       // Otherwise messages at the beginning will suddenly appear to be 'unseen'.
+       if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
+               StrBuf *first_tok;
+               first_tok = NewStrBuf();
+               StrBufExtract_token(first_tok, vset, 0, ',');
+               StrBufRemove_token(vset, 0, ',');
+
+               if (StrBufNum_tokens(first_tok, ':') > 1) {
+                       StrBufRemove_token(first_tok, 0, ':');
+               }
+               
+               StrBuf *new_set;
+               new_set = NewStrBuf();
+               StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
+               StrBufAppendBuf(new_set, first_tok, 0);
+               StrBufAppendBufPlain(new_set, HKEY(":"), 0);
+               StrBufAppendBuf(new_set, vset, 0);
+
+               FreeStrBuf(&vset);
+               FreeStrBuf(&first_tok);
+               vset = new_set;
+       }
+
+       // Decide which message set we're manipulating
+       switch (which_set) {
+               case ctdlsetseen_seen:
+                       safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
+                       break;
+               case ctdlsetseen_answered:
+                       safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
+                       break;
+       }
+
+       free(is_set);
+       free(msglist);
+       CtdlSetRelationship(&vbuf, which_user, which_room);
+       FreeStrBuf(&vset);
+}
+
+
+// API function to perform an operation for each qualifying message in the
+// current room.  (Returns the number of messages processed.)
+int CtdlForEachMessage(int mode, long ref, char *search_string,
+                       char *content_type,
+                       struct CtdlMessage *compare,
+                        ForEachMsgCallback CallBack,
+                       void *userdata)
+{
+       int a, i, j;
+       visit vbuf;
+       struct cdbdata *cdbfr;
+       long *msglist = NULL;
+       int num_msgs = 0;
+       int num_processed = 0;
+       long thismsg;
+       struct MetaData smi;
+       struct CtdlMessage *msg = NULL;
+       int is_seen = 0;
+       long lastold = 0L;
+       int printed_lastold = 0;
+       int num_search_msgs = 0;
+       long *search_msgs = NULL;
+       regex_t re;
+       int need_to_free_re = 0;
+       regmatch_t pm;
+
+       if ((content_type) && (!IsEmptyStr(content_type))) {
+               regcomp(&re, content_type, 0);
+               need_to_free_re = 1;
+       }
+
+       // Learn about the user and room in question
+       if (server_shutting_down) {
+               if (need_to_free_re) regfree(&re);
+               return -1;
+       }
+       CtdlGetUser(&CC->user, CC->curr_user);
+
+       if (server_shutting_down) {
+               if (need_to_free_re) regfree(&re);
+               return -1;
+       }
+       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+
+       if (server_shutting_down) {
+               if (need_to_free_re) regfree(&re);
+               return -1;
+       }
+
+       // Load the message list
+       cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+       if (cdbfr == NULL) {
+               if (need_to_free_re) regfree(&re);
+               return 0;       // No messages at all?  No further action.
+       }
+
+       msglist = (long *) cdbfr->ptr;
+       num_msgs = cdbfr->len / sizeof(long);
+
+       cdbfr->ptr = NULL;      // clear this so that cdb_free() doesn't free it
+       cdb_free(cdbfr);        // we own this memory now
+
+       /*
+        * Now begin the traversal.
+        */
+       if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
+
+               /* If the caller is looking for a specific MIME type, filter
+                * out all messages which are not of the type requested.
+                */
+               if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
+
+                       /* This call to GetMetaData() sits inside this loop
+                        * so that we only do the extra database read per msg
+                        * if we need to.  Doing the extra read all the time
+                        * really kills the server.  If we ever need to use
+                        * metadata for another search criterion, we need to
+                        * move the read somewhere else -- but still be smart
+                        * enough to only do the read if the caller has
+                        * specified something that will need it.
+                        */
+                       if (server_shutting_down) {
+                               if (need_to_free_re) regfree(&re);
+                               free(msglist);
+                               return -1;
+                       }
+                       GetMetaData(&smi, msglist[a]);
+
+                       /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
+                       if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
+                               msglist[a] = 0L;
+                       }
+               }
+       }
+
+       num_msgs = sort_msglist(msglist, num_msgs);
+
+       /* If a template was supplied, filter out the messages which
+        * don't match.  (This could induce some delays!)
+        */
+       if (num_msgs > 0) {
+               if (compare != NULL) {
+                       for (a = 0; a < num_msgs; ++a) {
+                               if (server_shutting_down) {
+                                       if (need_to_free_re) regfree(&re);
+                                       free(msglist);
+                                       return -1;
+                               }
+                               msg = CtdlFetchMessage(msglist[a], 1);
+                               if (msg != NULL) {
+                                       if (CtdlMsgCmp(msg, compare)) {
+                                               msglist[a] = 0L;
+                                       }
+                                       CM_Free(msg);
+                               }
+                       }
+               }
+       }
+
+       /* If a search string was specified, get a message list from
+        * the full text index and remove messages which aren't on both
+        * lists.
+        *
+        * How this works:
+        * Since the lists are sorted and strictly ascending, and the
+        * output list is guaranteed to be shorter than or equal to the
+        * input list, we overwrite the bottom of the input list.  This
+        * eliminates the need to memmove big chunks of the list over and
+        * over again.
+        */
+       if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
+
+               /* Call search module via hook mechanism.
+                * NULL means use any search function available.
+                * otherwise replace with a char * to name of search routine
+                */
+               CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
+
+               if (num_search_msgs > 0) {
+       
+                       int orig_num_msgs;
+
+                       orig_num_msgs = num_msgs;
+                       num_msgs = 0;
+                       for (i=0; i<orig_num_msgs; ++i) {
+                               for (j=0; j<num_search_msgs; ++j) {
+                                       if (msglist[i] == search_msgs[j]) {
+                                               msglist[num_msgs++] = msglist[i];
+                                       }
+                               }
+                       }
+               }
+               else {
+                       num_msgs = 0;   /* No messages qualify */
+               }
+               if (search_msgs != NULL) free(search_msgs);
+
+               /* Now that we've purged messages which don't contain the search
+                * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
+                * point on.
+                */
+               mode = MSGS_ALL;
+       }
+
+       /*
+        * Now iterate through the message list, according to the
+        * criteria supplied by the caller.
+        */
+       if (num_msgs > 0)
+               for (a = 0; a < num_msgs; ++a) {
+                       if (server_shutting_down) {
+                               if (need_to_free_re) regfree(&re);
+                               free(msglist);
+                               return num_processed;
+                       }
+                       thismsg = msglist[a];
+                       if (mode == MSGS_ALL) {
+                               is_seen = 0;
+                       }
+                       else {
+                               is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
+                               if (is_seen) lastold = thismsg;
+                       }
+                       if (
+                               (thismsg > 0L)
+                       && (
+                               (mode == MSGS_ALL)
+                               || ((mode == MSGS_OLD) && (is_seen))
+                               || ((mode == MSGS_NEW) && (!is_seen))
+                               || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
+                               || ((mode == MSGS_FIRST) && (a < ref))
+                               || ((mode == MSGS_GT) && (thismsg > ref))
+                               || ((mode == MSGS_LT) && (thismsg < ref))
+                               || ((mode == MSGS_EQ) && (thismsg == ref))
+                           )
+                           ) {
+                               if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
+                                       if (CallBack) {
+                                               CallBack(lastold, userdata);
+                                       }
+                                       printed_lastold = 1;
+                                       ++num_processed;
+                               }
+                               if (CallBack) {
+                                       CallBack(thismsg, userdata);
+                               }
+                               ++num_processed;
+                       }
+               }
+       if (need_to_free_re) regfree(&re);
+
+       /*
+        * We cache the most recent msglist in order to do security checks later
+        */
+       if (CC->client_socket > 0) {
+               if (CC->cached_msglist != NULL) {
+                       free(CC->cached_msglist);
+               }
+               CC->cached_msglist = msglist;
+               CC->cached_num_msgs = num_msgs;
+       }
+       else {
+               free(msglist);
+       }
+
+       return num_processed;
+}
+
+
+/*
+ * memfmout()  -  Citadel text formatter and paginator.
+ *          Although the original purpose of this routine was to format
+ *          text to the reader's screen width, all we're really using it
+ *          for here is to format text out to 80 columns before sending it
+ *          to the client.  The client software may reformat it again.
+ */
+void memfmout(
+       char *mptr,             /* where are we going to get our text from? */
+       const char *nl          /* string to terminate lines with */
+) {
+       int column = 0;
+       unsigned char ch = 0;
+       char outbuf[1024];
+       int len = 0;
+       int nllen = 0;
+
+       if (!mptr) return;
+       nllen = strlen(nl);
+       while (ch=*(mptr++), ch != 0) {
+
+               if (ch == '\n') {
+                       if (client_write(outbuf, len) == -1) {
+                               syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
+                               return;
+                       }
+                       len = 0;
+                       if (client_write(nl, nllen) == -1) {
+                               syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
+                               return;
+                       }
+                       column = 0;
+               }
+               else if (ch == '\r') {
+                       /* Ignore carriage returns.  Newlines are always LF or CRLF but never CR. */
+               }
+               else if (isspace(ch)) {
+                       if (column > 72) {              /* Beyond 72 columns, break on the next space */
+                               if (client_write(outbuf, len) == -1) {
+                                       syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
+                                       return;
+                               }
+                               len = 0;
+                               if (client_write(nl, nllen) == -1) {
+                                       syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
+                                       return;
+                               }
+                               column = 0;
+                       }
+                       else {
+                               outbuf[len++] = ch;
+                               ++column;
+                       }
+               }
+               else {
+                       outbuf[len++] = ch;
+                       ++column;
+                       if (column > 1000) {            /* Beyond 1000 columns, break anywhere */
+                               if (client_write(outbuf, len) == -1) {
+                                       syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
+                                       return;
+                               }
+                               len = 0;
+                               if (client_write(nl, nllen) == -1) {
+                                       syslog(LOG_ERR, "msgbase: memfmout(): aborting due to write failure");
+                                       return;
+                               }
+                               column = 0;
+                       }
+               }
+       }
+       if (len) {
+               if (client_write(outbuf, len) == -1) {
+                       syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
+                       return;
+               }
+               client_write(nl, nllen);
+               column = 0;
+       }
+}
+
+
+/*
+ * Callback function for mime parser that simply lists the part
+ */
+void list_this_part(char *name, char *filename, char *partnum, char *disp,
+                   void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+                   char *cbid, void *cbuserdata)
+{
+       struct ma_info *ma;
+       
+       ma = (struct ma_info *)cbuserdata;
+       if (ma->is_ma == 0) {
+               cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
+                       name, 
+                       filename, 
+                       partnum, 
+                       disp, 
+                       cbtype, 
+                       (long)length, 
+                       cbid, 
+                       cbcharset);
+       }
+}
+
+
+/* 
+ * Callback function for multipart prefix
+ */
+void list_this_pref(char *name, char *filename, char *partnum, char *disp,
+                   void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+                   char *cbid, void *cbuserdata)
+{
+       struct ma_info *ma;
+       
+       ma = (struct ma_info *)cbuserdata;
+       if (!strcasecmp(cbtype, "multipart/alternative")) {
+               ++ma->is_ma;
+       }
+
+       if (ma->is_ma == 0) {
+               cprintf("pref=%s|%s\n", partnum, cbtype);
+       }
+}
+
+
+/* 
+ * Callback function for multipart sufffix
+ */
+void list_this_suff(char *name, char *filename, char *partnum, char *disp,
+                   void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+                   char *cbid, void *cbuserdata)
+{
+       struct ma_info *ma;
+       
+       ma = (struct ma_info *)cbuserdata;
+       if (ma->is_ma == 0) {
+               cprintf("suff=%s|%s\n", partnum, cbtype);
+       }
+       if (!strcasecmp(cbtype, "multipart/alternative")) {
+               --ma->is_ma;
+       }
+}
+
+
+/*
+ * Callback function for mime parser that opens a section for downloading
+ * we use serv_files function here: 
+ */
+extern void OpenCmdResult(char *filename, const char *mime_type);
+void mime_download(char *name, char *filename, char *partnum, char *disp,
+                  void *content, char *cbtype, char *cbcharset, size_t length,
+                  char *encoding, char *cbid, void *cbuserdata)
+{
+       int rv = 0;
+
+       /* Silently go away if there's already a download open. */
+       if (CC->download_fp != NULL)
+               return;
+
+       if (
+               (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
+       ||      (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
+       ) {
+               CC->download_fp = tmpfile();
+               if (CC->download_fp == NULL) {
+                       syslog(LOG_ERR, "msgbase: mime_download() couldn't write: %m");
+                       cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
+                       return;
+               }
+       
+               rv = fwrite(content, length, 1, CC->download_fp);
+               if (rv <= 0) {
+                       syslog(LOG_ERR, "msgbase: mime_download() Couldn't write: %m");
+                       cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG);
+                       fclose(CC->download_fp);
+                       CC->download_fp = NULL;
+                       return;
+               }
+               fflush(CC->download_fp);
+               rewind(CC->download_fp);
+       
+               OpenCmdResult(filename, cbtype);
+       }
+}
+
+
+/*
+ * Callback function for mime parser that outputs a section all at once.
+ * We can specify the desired section by part number *or* content-id.
+ */
+void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
+                  void *content, char *cbtype, char *cbcharset, size_t length,
+                  char *encoding, char *cbid, void *cbuserdata)
+{
+       int *found_it = (int *)cbuserdata;
+
+       if (
+               (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
+       ||      (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
+       ) {
+               *found_it = 1;
+               cprintf("%d %d|-1|%s|%s|%s\n",
+                       BINARY_FOLLOWS,
+                       (int)length,
+                       filename,
+                       cbtype,
+                       cbcharset
+               );
+               client_write(content, length);
+       }
+}
+
+
+struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length) {
+       struct CtdlMessage *ret = NULL;
+       const char *mptr;
+       const char *upper_bound;
+       cit_uint8_t ch;
+       cit_uint8_t field_header;
+       eMsgField which;
+
+       mptr = Buffer;
+       upper_bound = Buffer + Length;
+       if (msgnum <= 0) {
+               return NULL;
+       }
+
+       // Parse the three bytes that begin EVERY message on disk.
+       // The first is always 0xFF, the on-disk magic number.
+       // The second is the anonymous/public type byte.
+       // The third is the format type byte (vari, fixed, or MIME).
+       //
+       ch = *mptr++;
+       if (ch != 255) {
+               syslog(LOG_ERR, "msgbase: message %ld appears to be corrupted", msgnum);
+               return NULL;
+       }
+       ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
+       memset(ret, 0, sizeof(struct CtdlMessage));
+
+       ret->cm_magic = CTDLMESSAGE_MAGIC;
+       ret->cm_anon_type = *mptr++;                            // Anon type byte
+       ret->cm_format_type = *mptr++;                          // Format type byte
+
+       // The rest is zero or more arbitrary fields.  Load them in.
+       // We're done when we encounter either a zero-length field or
+       // have just processed the 'M' (message text) field.
+       //
+       do {
+               field_header = '\0';
+               long len;
+
+               while (field_header == '\0') {                  // work around possibly buggy messages
+                       if (mptr >= upper_bound) {
+                               break;
+                       }
+                       field_header = *mptr++;
+               }
+               if (mptr >= upper_bound) {
+                       break;
+               }
+               which = field_header;
+               len = strlen(mptr);
+
+               CM_SetField(ret, which, mptr, len);
+
+               mptr += len + 1;                                // advance to next field
+
+       } while ((mptr < upper_bound) && (field_header != 'M'));
+       return (ret);
+}
+
+
+// Load a message from disk into memory.
+// This is used by CtdlOutputMsg() and other fetch functions.
+//
+// NOTE: Caller is responsible for freeing the returned CtdlMessage struct
+//       using the CM_Free(); function.
+//
+struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body) {
+       struct cdbdata *dmsgtext;
+       struct CtdlMessage *ret = NULL;
+
+       syslog(LOG_DEBUG, "msgbase: CtdlFetchMessage(%ld, %d)", msgnum, with_body);
+       dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
+       if (dmsgtext == NULL) {
+               syslog(LOG_ERR, "msgbase: message #%ld was not found", msgnum);
+               return NULL;
+       }
+
+       if (dmsgtext->ptr[dmsgtext->len - 1] != '\0') {
+               syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Forcefully terminating message!!", msgnum, with_body);
+               dmsgtext->ptr[dmsgtext->len - 1] = '\0';
+       }
+
+       ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext->ptr, dmsgtext->len);
+
+       cdb_free(dmsgtext);
+
+       if (ret == NULL) {
+               return NULL;
+       }
+
+       // Always make sure there's something in the msg text field.  If
+       // it's NULL, the message text is most likely stored separately,
+       // so go ahead and fetch that.  Failing that, just set a dummy
+       // body so other code doesn't barf.
+       //
+       if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
+               dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
+               if (dmsgtext != NULL) {
+                       CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len - 1);
+                       cdb_free(dmsgtext);
+               }
+       }
+       if (CM_IsEmpty(ret, eMesageText)) {
+               CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
+       }
+
+       return (ret);
+}
+
+
+// Pre callback function for multipart/alternative
+//
+// NOTE: this differs from the standard behavior for a reason.  Normally when
+//       displaying multipart/alternative you want to show the _last_ usable
+//       format in the message.  Here we show the _first_ one, because it's
+//       usually text/plain.  Since this set of functions is designed for text
+//       output to non-MIME-aware clients, this is the desired behavior.
+//
+void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
+               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+               char *cbid, void *cbuserdata)
+{
+       struct ma_info *ma;
+       
+       ma = (struct ma_info *)cbuserdata;
+       syslog(LOG_DEBUG, "msgbase: fixed_output_pre() type=<%s>", cbtype);     
+       if (!strcasecmp(cbtype, "multipart/alternative")) {
+               ++ma->is_ma;
+               ma->did_print = 0;
+       }
+       if (!strcasecmp(cbtype, "message/rfc822")) {
+               ++ma->freeze;
+       }
+}
+
+
+//
+// Post callback function for multipart/alternative
+//
+void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
+               void *content, char *cbtype, char *cbcharset, size_t length,
+               char *encoding, char *cbid, void *cbuserdata)
+{
+       struct ma_info *ma;
+       
+       ma = (struct ma_info *)cbuserdata;
+       syslog(LOG_DEBUG, "msgbase: fixed_output_post() type=<%s>", cbtype);    
+       if (!strcasecmp(cbtype, "multipart/alternative")) {
+               --ma->is_ma;
+               ma->did_print = 0;
+       }
+       if (!strcasecmp(cbtype, "message/rfc822")) {
+               --ma->freeze;
+       }
+}
+
+
+// Inline callback function for mime parser that wants to display text
+void fixed_output(char *name, char *filename, char *partnum, char *disp,
+               void *content, char *cbtype, char *cbcharset, size_t length,
+               char *encoding, char *cbid, void *cbuserdata)
+{
+       char *ptr;
+       char *wptr;
+       size_t wlen;
+       struct ma_info *ma;
+
+       ma = (struct ma_info *)cbuserdata;
+
+       syslog(LOG_DEBUG,
+               "msgbase: fixed_output() part %s: %s (%s) (%ld bytes)",
+               partnum, filename, cbtype, (long)length
+       );
+
+       // If we're in the middle of a multipart/alternative scope and
+       // we've already printed another section, skip this one.
+       if ( (ma->is_ma) && (ma->did_print) ) {
+               syslog(LOG_DEBUG, "msgbase: skipping part %s (%s)", partnum, cbtype);
+               return;
+       }
+       ma->did_print = 1;
+
+       if ( (!strcasecmp(cbtype, "text/plain")) 
+          || (IsEmptyStr(cbtype)) ) {
+               wptr = content;
+               if (length > 0) {
+                       client_write(wptr, length);
+                       if (wptr[length-1] != '\n') {
+                               cprintf("\n");
+                       }
+               }
+               return;
+       }
+
+       if (!strcasecmp(cbtype, "text/html")) {
+               ptr = html_to_ascii(content, length, 80);
+               wlen = strlen(ptr);
+               client_write(ptr, wlen);
+               if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
+                       cprintf("\n");
+               }
+               free(ptr);
+               return;
+       }
+
+       if (ma->use_fo_hooks) {
+               if (PerformFixedOutputHooks(cbtype, content, length)) {         // returns nonzero if it handled the part
+                       return;
+               }
+       }
+
+       if (strncasecmp(cbtype, "multipart/", 10)) {
+               cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
+                       partnum, filename, cbtype, (long)length);
+               return;
+       }
+}
+
+
+// The client is elegant and sophisticated and wants to be choosy about
+// MIME content types, so figure out which multipart/alternative part
+// we're going to send.
+//
+// We use a system of weights.  When we find a part that matches one of the
+// MIME types we've declared as preferential, we can store it in ma->chosen_part
+// and then set ma->chosen_pref to that MIME type's position in our preference
+// list.  If we then hit another match, we only replace the first match if
+// the preference value is lower.
+void choose_preferred(char *name, char *filename, char *partnum, char *disp,
+               void *content, char *cbtype, char *cbcharset, size_t length,
+               char *encoding, char *cbid, void *cbuserdata)
+{
+       char buf[1024];
+       int i;
+       struct ma_info *ma;
+       
+       ma = (struct ma_info *)cbuserdata;
+
+       for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
+               extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
+               if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
+                       if (i < ma->chosen_pref) {
+                               syslog(LOG_DEBUG, "msgbase: setting chosen part to <%s>", partnum);
+                               safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
+                               ma->chosen_pref = i;
+                       }
+               }
+       }
+}
+
+
+// Now that we've chosen our preferred part, output it.
+void output_preferred(char *name, 
+                     char *filename, 
+                     char *partnum, 
+                     char *disp,
+                     void *content, 
+                     char *cbtype, 
+                     char *cbcharset, 
+                     size_t length,
+                     char *encoding, 
+                     char *cbid, 
+                     void *cbuserdata)
+{
+       int i;
+       char buf[128];
+       int add_newline = 0;
+       char *text_content;
+       struct ma_info *ma;
+       char *decoded = NULL;
+       size_t bytes_decoded;
+       int rc = 0;
+
+       ma = (struct ma_info *)cbuserdata;
+
+       // This is not the MIME part you're looking for...
+       if (strcasecmp(partnum, ma->chosen_part)) return;
+
+       // If the content-type of this part is in our preferred formats
+       // list, we can simply output it verbatim.
+       for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
+               extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
+               if (!strcasecmp(buf, cbtype)) {
+                       /* Yeah!  Go!  W00t!! */
+                       if (ma->dont_decode == 0) 
+                               rc = mime_decode_now (content, 
+                                                     length,
+                                                     encoding,
+                                                     &decoded,
+                                                     &bytes_decoded);
+                       if (rc < 0)
+                               break; // Give us the chance, maybe theres another one.
+
+                       if (rc == 0) text_content = (char *)content;
+                       else {
+                               text_content = decoded;
+                               length = bytes_decoded;
+                       }
+
+                       if (text_content[length-1] != '\n') {
+                               ++add_newline;
+                       }
+                       cprintf("Content-type: %s", cbtype);
+                       if (!IsEmptyStr(cbcharset)) {
+                               cprintf("; charset=%s", cbcharset);
+                       }
+                       cprintf("\nContent-length: %d\n",
+                               (int)(length + add_newline) );
+                       if (!IsEmptyStr(encoding)) {
+                               cprintf("Content-transfer-encoding: %s\n", encoding);
+                       }
+                       else {
+                               cprintf("Content-transfer-encoding: 7bit\n");
+                       }
+                       cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
+                       cprintf("\n");
+                       if (client_write(text_content, length) == -1)
+                       {
+                               syslog(LOG_ERR, "msgbase: output_preferred() aborting due to write failure");
+                               return;
+                       }
+                       if (add_newline) cprintf("\n");
+                       if (decoded != NULL) free(decoded);
+                       return;
+               }
+       }
+
+       // No translations required or possible: output as text/plain
+       cprintf("Content-type: text/plain\n\n");
+       rc = 0;
+       if (ma->dont_decode == 0)
+               rc = mime_decode_now (content, 
+                                     length,
+                                     encoding,
+                                     &decoded,
+                                     &bytes_decoded);
+       if (rc < 0)
+               return; // Give us the chance, maybe theres another one.
+       
+       if (rc == 0) text_content = (char *)content;
+       else {
+               text_content = decoded;
+               length = bytes_decoded;
+       }
+
+       fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset, length, encoding, cbid, cbuserdata);
+       if (decoded != NULL) free(decoded);
+}
+
+
+struct encapmsg {
+       char desired_section[64];
+       char *msg;
+       size_t msglen;
+};
+
+
+// Callback function
+void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
+                  void *content, char *cbtype, char *cbcharset, size_t length,
+                  char *encoding, char *cbid, void *cbuserdata)
+{
+       struct encapmsg *encap;
+
+       encap = (struct encapmsg *)cbuserdata;
+
+       // Only proceed if this is the desired section...
+       if (!strcasecmp(encap->desired_section, partnum)) {
+               encap->msglen = length;
+               encap->msg = malloc(length + 2);
+               memcpy(encap->msg, content, length);
+               return;
+       }
+}
+
+
+// Determine whether the specified message exists in the cached_msglist
+// (This is a security check)
+int check_cached_msglist(long msgnum) {
+
+       // cases in which we skip the check
+       if (!CC) return om_ok;                                          // not a session
+       if (CC->client_socket <= 0) return om_ok;                       // not a client session
+       if (CC->cached_msglist == NULL) return om_access_denied;        // no msglist fetched
+       if (CC->cached_num_msgs == 0) return om_access_denied;          // nothing to check 
+
+       // Do a binary search within the cached_msglist for the requested msgnum
+       int min = 0;
+       int max = (CC->cached_num_msgs - 1);
+
+       while (max >= min) {
+               int middle = min + (max-min) / 2 ;
+               if (msgnum == CC->cached_msglist[middle]) {
+                       return om_ok;
+               }
+               if (msgnum > CC->cached_msglist[middle]) {
+                       min = middle + 1;
+               }
+               else {
+                       max = middle - 1;
+               }
+       }
+
+       return om_access_denied;
+}
+
+
+// Get a message off disk.  (returns om_* values found in msgbase.h)
+int CtdlOutputMsg(long msg_num,                // message number (local) to fetch
+               int mode,               // how would you like that message?
+               int headers_only,       // eschew the message body?
+               int do_proto,           // do Citadel protocol responses?
+               int crlf,               // Use CRLF newlines instead of LF?
+               char *section,          // NULL or a message/rfc822 section
+               int flags,              // various flags; see msgbase.h
+               char **Author,
+               char **Address,
+               char **MessageID
+) {
+       struct CtdlMessage *TheMessage = NULL;
+       int retcode = CIT_OK;
+       struct encapmsg encap;
+       int r;
+
+       syslog(LOG_DEBUG, "msgbase: CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)", 
+               msg_num, mode,
+               (section ? section : "<>")
+       );
+
+       r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
+       if (r != om_ok) {
+               if (do_proto) {
+                       if (r == om_not_logged_in) {
+                               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+                       }
+                       else {
+                               cprintf("%d An unknown error has occurred.\n", ERROR);
+                       }
+               }
+               return(r);
+       }
+
+       /*
+        * Check to make sure the message is actually IN this room
+        */
+       r = check_cached_msglist(msg_num);
+       if (r == om_access_denied) {
+               /* Not in the cache?  We get ONE shot to check it again. */
+               CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
+               r = check_cached_msglist(msg_num);
+       }
+       if (r != om_ok) {
+               syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s",
+                          msg_num, CC->room.QRname
+               );
+               if (do_proto) {
+                       if (r == om_access_denied) {
+                               cprintf("%d message %ld was not found in this room\n",
+                                       ERROR + HIGHER_ACCESS_REQUIRED,
+                                       msg_num
+                               );
+                       }
+               }
+               return(r);
+       }
+
+       /*
+        * Fetch the message from disk.  If we're in HEADERS_FAST mode,
+        * request that we don't even bother loading the body into memory.
+        */
+       if (headers_only == HEADERS_FAST) {
+               TheMessage = CtdlFetchMessage(msg_num, 0);
+       }
+       else {
+               TheMessage = CtdlFetchMessage(msg_num, 1);
+       }
+
+       if (TheMessage == NULL) {
+               if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
+                       ERROR + MESSAGE_NOT_FOUND, msg_num);
+               return(om_no_such_msg);
+       }
+
+       /* Here is the weird form of this command, to process only an
+        * encapsulated message/rfc822 section.
+        */
+       if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
+               memset(&encap, 0, sizeof encap);
+               safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
+               mime_parser(CM_RANGE(TheMessage, eMesageText),
+                           *extract_encapsulated_message,
+                           NULL, NULL, (void *)&encap, 0
+                       );
+
+               if ((Author != NULL) && (*Author == NULL))
+               {
+                       long len;
+                       CM_GetAsField(TheMessage, eAuthor, Author, &len);
+               }
+               if ((Address != NULL) && (*Address == NULL))
+               {       
+                       long len;
+                       CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
+               }
+               if ((MessageID != NULL) && (*MessageID == NULL))
+               {       
+                       long len;
+                       CM_GetAsField(TheMessage, emessageId, MessageID, &len);
+               }
+               CM_Free(TheMessage);
+               TheMessage = NULL;
+
+               if (encap.msg) {
+                       encap.msg[encap.msglen] = 0;
+                       TheMessage = convert_internet_message(encap.msg);
+                       encap.msg = NULL;       /* no free() here, TheMessage owns it now */
+
+                       /* Now we let it fall through to the bottom of this
+                        * function, because TheMessage now contains the
+                        * encapsulated message instead of the top-level
+                        * message.  Isn't that neat?
+                        */
+               }
+               else {
+                       if (do_proto) {
+                               cprintf("%d msg %ld has no part %s\n",
+                                       ERROR + MESSAGE_NOT_FOUND,
+                                       msg_num,
+                                       section);
+                       }
+                       retcode = om_no_such_msg;
+               }
+
+       }
+
+       /* Ok, output the message now */
+       if (retcode == CIT_OK)
+               retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
+       if ((Author != NULL) && (*Author == NULL))
+       {
+               long len;
+               CM_GetAsField(TheMessage, eAuthor, Author, &len);
+       }
+       if ((Address != NULL) && (*Address == NULL))
+       {       
+               long len;
+               CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
+       }
+       if ((MessageID != NULL) && (*MessageID == NULL))
+       {       
+               long len;
+               CM_GetAsField(TheMessage, emessageId, MessageID, &len);
+       }
+
+       CM_Free(TheMessage);
+
+       return(retcode);
+}
+
+
+void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto) {
+       int i;
+       char buf[SIZ];
+       char display_name[256];
+
+       /* begin header processing loop for Citadel message format */
+       safestrncpy(display_name, "<unknown>", sizeof display_name);
+       if (!CM_IsEmpty(TheMessage, eAuthor)) {
+               strcpy(buf, TheMessage->cm_fields[eAuthor]);
+               if (TheMessage->cm_anon_type == MES_ANONONLY) {
+                       safestrncpy(display_name, "****", sizeof display_name);
+               }
+               else if (TheMessage->cm_anon_type == MES_ANONOPT) {
+                       safestrncpy(display_name, "anonymous", sizeof display_name);
+               }
+               else {
+                       safestrncpy(display_name, buf, sizeof display_name);
+               }
+               if ((is_room_aide())
+                   && ((TheMessage->cm_anon_type == MES_ANONONLY)
+                       || (TheMessage->cm_anon_type == MES_ANONOPT))) {
+                       size_t tmp = strlen(display_name);
+                       snprintf(&display_name[tmp],
+                                sizeof display_name - tmp,
+                                " [%s]", buf);
+               }
+       }
+
+       /* Now spew the header fields in the order we like them. */
+       for (i=0; i< NDiskFields; ++i) {
+               eMsgField Field;
+               Field = FieldOrder[i];
+               if (Field != eMesageText) {
+                       if ( (!CM_IsEmpty(TheMessage, Field)) && (msgkeys[Field] != NULL) ) {
+                               if ((Field == eenVelopeTo) || (Field == eRecipient) || (Field == eCarbonCopY)) {
+                                       sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
+                               }
+                               if (Field == eAuthor) {
+                                       if (do_proto) {
+                                               cprintf("%s=%s\n", msgkeys[Field], display_name);
+                                       }
+                               }
+                               /* Masquerade display name if needed */
+                               else {
+                                       if (do_proto) {
+                                               cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
+                                       }
+                               }
+                               /* Give the client a hint about whether the message originated locally */
+                               if (Field == erFc822Addr) {
+                                       if (IsDirectory(TheMessage->cm_fields[Field] ,0)) {
+                                               cprintf("locl=yes\n");                          // message originated locally.
+                                       }
+
+
+
+                               }
+                       }
+               }
+       }
+}
+
+
+void OutputRFC822MsgHeaders(
+       struct CtdlMessage *TheMessage,
+       int flags,              /* should the message be exported clean */
+       const char *nl, int nlen,
+       char *mid, long sizeof_mid,
+       char *suser, long sizeof_suser,
+       char *luser, long sizeof_luser,
+       char *fuser, long sizeof_fuser,
+       char *snode, long sizeof_snode)
+{
+       char datestamp[100];
+       int subject_found = 0;
+       char buf[SIZ];
+       int i, j, k;
+       char *mptr = NULL;
+       char *mpptr = NULL;
+       char *hptr;
+
+       for (i = 0; i < NDiskFields; ++i) {
+               if (TheMessage->cm_fields[FieldOrder[i]]) {
+                       mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
+                       switch (FieldOrder[i]) {
+                       case eAuthor:
+                               safestrncpy(luser, mptr, sizeof_luser);
+                               safestrncpy(suser, mptr, sizeof_suser);
+                               break;
+                       case eCarbonCopY:
+                               if ((flags & QP_EADDR) != 0) {
+                                       mptr = qp_encode_email_addrs(mptr);
+                               }
+                               sanitize_truncated_recipient(mptr);
+                               cprintf("CC: %s%s", mptr, nl);
+                               break;
+                       case eMessagePath:
+                               cprintf("Return-Path: %s%s", mptr, nl);
+                               break;
+                       case eListID:
+                               cprintf("List-ID: %s%s", mptr, nl);
+                               break;
+                       case eenVelopeTo:
+                               if ((flags & QP_EADDR) != 0) 
+                                       mptr = qp_encode_email_addrs(mptr);
+                               hptr = mptr;
+                               while ((*hptr != '\0') && isspace(*hptr))
+                                       hptr ++;
+                               if (!IsEmptyStr(hptr))
+                                       cprintf("Envelope-To: %s%s", hptr, nl);
+                               break;
+                       case eMsgSubject:
+                               cprintf("Subject: %s%s", mptr, nl);
+                               subject_found = 1;
+                               break;
+                       case emessageId:
+                               safestrncpy(mid, mptr, sizeof_mid);
+                               break;
+                       case erFc822Addr:
+                               safestrncpy(fuser, mptr, sizeof_fuser);
+                               break;
+                       case eRecipient:
+                               if (haschar(mptr, '@') == 0) {
+                                       sanitize_truncated_recipient(mptr);
+                                       cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
+                                       cprintf("%s", nl);
+                               }
+                               else {
+                                       if ((flags & QP_EADDR) != 0) {
+                                               mptr = qp_encode_email_addrs(mptr);
+                                       }
+                                       sanitize_truncated_recipient(mptr);
+                                       cprintf("To: %s", mptr);
+                                       cprintf("%s", nl);
+                               }
+                               break;
+                       case eTimestamp:
+                               datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
+                               cprintf("Date: %s%s", datestamp, nl);
+                               break;
+                       case eWeferences:
+                               cprintf("References: ");
+                               k = num_tokens(mptr, '|');
+                               for (j=0; j<k; ++j) {
+                                       extract_token(buf, mptr, j, '|', sizeof buf);
+                                       cprintf("<%s>", buf);
+                                       if (j == (k-1)) {
+                                               cprintf("%s", nl);
+                                       }
+                                       else {
+                                               cprintf(" ");
+                                       }
+                               }
+                               break;
+                       case eReplyTo:
+                               hptr = mptr;
+                               while ((*hptr != '\0') && isspace(*hptr))
+                                       hptr ++;
+                               if (!IsEmptyStr(hptr))
+                                       cprintf("Reply-To: %s%s", mptr, nl);
+                               break;
+
+                       case eExclusiveID:
+                       case eJournal:
+                       case eMesageText:
+                       case eBig_message:
+                       case eOriginalRoom:
+                       case eErrorMsg:
+                       case eSuppressIdx:
+                       case eExtnotify:
+                       case eVltMsgNum:
+                               /* these don't map to mime message headers. */
+                               break;
+                       }
+                       if (mptr != mpptr) {
+                               free (mptr);
+                       }
+               }
+       }
+       if (subject_found == 0) {
+               cprintf("Subject: (no subject)%s", nl);
+       }
+}
+
+
+void Dump_RFC822HeadersBody(
+       struct CtdlMessage *TheMessage,
+       int headers_only,       /* eschew the message body? */
+       int flags,              /* should the bessage be exported clean? */
+       const char *nl, int nlen)
+{
+       cit_uint8_t prev_ch;
+       int eoh = 0;
+       const char *StartOfText = StrBufNOTNULL;
+       char outbuf[1024];
+       int outlen = 0;
+       int nllen = strlen(nl);
+       char *mptr;
+       int lfSent = 0;
+
+       mptr = TheMessage->cm_fields[eMesageText];
+
+       prev_ch = '\0';
+       while (*mptr != '\0') {
+               if (*mptr == '\r') {
+                       /* do nothing */
+               }
+               else {
+                       if ((!eoh) &&
+                           (*mptr == '\n'))
+                       {
+                               eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
+                               if (!eoh)
+                                       eoh = *(mptr+1) == '\n';
+                               if (eoh)
+                               {
+                                       StartOfText = mptr;
+                                       StartOfText = strchr(StartOfText, '\n');
+                                       StartOfText = strchr(StartOfText, '\n');
+                               }
+                       }
+                       if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
+                           ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
+                           ((headers_only != HEADERS_NONE) && 
+                            (headers_only != HEADERS_ONLY))
+                       ) {
+                               if (*mptr == '\n') {
+                                       memcpy(&outbuf[outlen], nl, nllen);
+                                       outlen += nllen;
+                                       outbuf[outlen] = '\0';
+                               }
+                               else {
+                                       outbuf[outlen++] = *mptr;
+                               }
+                       }
+               }
+               if (flags & ESC_DOT) {
+                       if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
+                               outbuf[outlen++] = '.';
+                       }
+                       prev_ch = *mptr;
+               }
+               ++mptr;
+               if (outlen > 1000) {
+                       if (client_write(outbuf, outlen) == -1) {
+                               syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
+                               return;
+                       }
+                       lfSent =  (outbuf[outlen - 1] == '\n');
+                       outlen = 0;
+               }
+       }
+       if (outlen > 0) {
+               client_write(outbuf, outlen);
+               lfSent =  (outbuf[outlen - 1] == '\n');
+       }
+       if (!lfSent)
+               client_write(nl, nlen);
+}
+
+
+/* If the format type on disk is 1 (fixed-format), then we want
+ * everything to be output completely literally ... regardless of
+ * what message transfer format is in use.
+ */
+void DumpFormatFixed(
+       struct CtdlMessage *TheMessage,
+       int mode,               /* how would you like that message? */
+       const char *nl, int nllen)
+{
+       cit_uint8_t ch;
+       char buf[SIZ];
+       int buflen;
+       int xlline = 0;
+       char *mptr;
+
+       mptr = TheMessage->cm_fields[eMesageText];
+       
+       if (mode == MT_MIME) {
+               cprintf("Content-type: text/plain\n\n");
+       }
+       *buf = '\0';
+       buflen = 0;
+       while (ch = *mptr++, ch > 0) {
+               if (ch == '\n')
+                       ch = '\r';
+
+               if ((buflen > 250) && (!xlline)){
+                       int tbuflen;
+                       tbuflen = buflen;
+
+                       while ((buflen > 0) && 
+                              (!isspace(buf[buflen])))
+                               buflen --;
+                       if (buflen == 0) {
+                               xlline = 1;
+                       }
+                       else {
+                               mptr -= tbuflen - buflen;
+                               buf[buflen] = '\0';
+                               ch = '\r';
+                       }
+               }
+
+               /* if we reach the outer bounds of our buffer, abort without respect for what we purge. */
+               if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
+                       ch = '\r';
+               }
+
+               if (ch == '\r') {
+                       memcpy (&buf[buflen], nl, nllen);
+                       buflen += nllen;
+                       buf[buflen] = '\0';
+
+                       if (client_write(buf, buflen) == -1) {
+                               syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
+                               return;
+                       }
+                       *buf = '\0';
+                       buflen = 0;
+                       xlline = 0;
+               } else {
+                       buf[buflen] = ch;
+                       buflen++;
+               }
+       }
+       buf[buflen] = '\0';
+       if (!IsEmptyStr(buf)) {
+               cprintf("%s%s", buf, nl);
+       }
+}
+
+
+/*
+ * Get a message off disk.  (returns om_* values found in msgbase.h)
+ */
+int CtdlOutputPreLoadedMsg(
+               struct CtdlMessage *TheMessage,
+               int mode,               /* how would you like that message? */
+               int headers_only,       /* eschew the message body? */
+               int do_proto,           /* do Citadel protocol responses? */
+               int crlf,               /* Use CRLF newlines instead of LF? */
+               int flags               /* should the bessage be exported clean? */
+) {
+       int i;
+       const char *nl; /* newline string */
+       int nlen;
+       struct ma_info ma;
+
+       /* Buffers needed for RFC822 translation.  These are all filled
+        * using functions that are bounds-checked, and therefore we can
+        * make them substantially smaller than SIZ.
+        */
+       char suser[1024];
+       char luser[1024];
+       char fuser[1024];
+       char snode[1024];
+       char mid[1024];
+
+       syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
+                  ((TheMessage == NULL) ? "NULL" : "not null"),
+                  mode, headers_only, do_proto, crlf
+       );
+
+       strcpy(mid, "unknown");
+       nl = (crlf ? "\r\n" : "\n");
+       nlen = crlf ? 2 : 1;
+
+       if (!CM_IsValidMsg(TheMessage)) {
+               syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
+               return(om_no_such_msg);
+       }
+
+       /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
+        * Pad it with spaces in order to avoid changing the RFC822 length of the message.
+        */
+       if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
+               memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
+       }
+               
+       /* Are we downloading a MIME component? */
+       if (mode == MT_DOWNLOAD) {
+               if (TheMessage->cm_format_type != FMT_RFC822) {
+                       if (do_proto)
+                               cprintf("%d This is not a MIME message.\n",
+                               ERROR + ILLEGAL_VALUE);
+               } else if (CC->download_fp != NULL) {
+                       if (do_proto) cprintf(
+                               "%d You already have a download open.\n",
+                               ERROR + RESOURCE_BUSY);
+               } else {
+                       /* Parse the message text component */
+                       mime_parser(CM_RANGE(TheMessage, eMesageText),
+                                   *mime_download, NULL, NULL, NULL, 0);
+                       /* If there's no file open by this time, the requested
+                        * section wasn't found, so print an error
+                        */
+                       if (CC->download_fp == NULL) {
+                               if (do_proto) cprintf(
+                                       "%d Section %s not found.\n",
+                                       ERROR + FILE_NOT_FOUND,
+                                       CC->download_desired_section);
+                       }
+               }
+               return((CC->download_fp != NULL) ? om_ok : om_mime_error);
+       }
+
+       // MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
+       // in a single server operation instead of opening a download file.
+       if (mode == MT_SPEW_SECTION) {
+               if (TheMessage->cm_format_type != FMT_RFC822) {
+                       if (do_proto)
+                               cprintf("%d This is not a MIME message.\n",
+                               ERROR + ILLEGAL_VALUE);
+               }
+               else {
+                       // Locate and parse the component specified by the caller
+                       int found_it = 0;
+                       mime_parser(CM_RANGE(TheMessage, eMesageText), *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
+
+                       // If section wasn't found, print an error
+                       if (!found_it) {
+                               if (do_proto) {
+                                       cprintf( "%d Section %s not found.\n", ERROR + FILE_NOT_FOUND, CC->download_desired_section);
+                               }
+                       }
+               }
+               return((CC->download_fp != NULL) ? om_ok : om_mime_error);
+       }
+
+       // now for the user-mode message reading loops
+       if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
+
+       // Does the caller want to skip the headers?
+       if (headers_only == HEADERS_NONE) goto START_TEXT;
+
+       // Tell the client which format type we're using.
+       if ( (mode == MT_CITADEL) && (do_proto) ) {
+               cprintf("type=%d\n", TheMessage->cm_format_type);       // Tell the client which format type we're using.
+       }
+
+       // nhdr=yes means that we're only displaying headers, no body
+       if ( (TheMessage->cm_anon_type == MES_ANONONLY)
+          && ((mode == MT_CITADEL) || (mode == MT_MIME))
+          && (do_proto)
+          ) {
+               cprintf("nhdr=yes\n");
+       }
+
+       if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
+               OutputCtdlMsgHeaders(TheMessage, do_proto);
+       }
+
+       // begin header processing loop for RFC822 transfer format
+       strcpy(suser, "");
+       strcpy(luser, "");
+       strcpy(fuser, "");
+       strcpy(snode, "");
+       if (mode == MT_RFC822) 
+               OutputRFC822MsgHeaders(
+                       TheMessage,
+                       flags,
+                       nl, nlen,
+                       mid, sizeof(mid),
+                       suser, sizeof(suser),
+                       luser, sizeof(luser),
+                       fuser, sizeof(fuser),
+                       snode, sizeof(snode)
+                       );
+
+
+       for (i=0; !IsEmptyStr(&suser[i]); ++i) {
+               suser[i] = tolower(suser[i]);
+               if (!isalnum(suser[i])) suser[i]='_';
+       }
+
+       if (mode == MT_RFC822) {
+               /* Construct a fun message id */
+               cprintf("Message-ID: <%s", mid);
+               if (strchr(mid, '@')==NULL) {
+                       cprintf("@%s", snode);
+               }
+               cprintf(">%s", nl);
+
+               if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
+                       cprintf("From: \"----\" <x@x.org>%s", nl);
+               }
+               else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
+                       cprintf("From: \"anonymous\" <x@x.org>%s", nl);
+               }
+               else if (!IsEmptyStr(fuser)) {
+                       cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
+               }
+               else {
+                       cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
+               }
+
+               /* Blank line signifying RFC822 end-of-headers */
+               if (TheMessage->cm_format_type != FMT_RFC822) {
+                       cprintf("%s", nl);
+               }
+       }
+
+       // end header processing loop ... at this point, we're in the text
+START_TEXT:
+       if (headers_only == HEADERS_FAST) goto DONE;
+
+       // Tell the client about the MIME parts in this message
+       if (TheMessage->cm_format_type == FMT_RFC822) {
+               if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
+                       memset(&ma, 0, sizeof(struct ma_info));
+                       mime_parser(CM_RANGE(TheMessage, eMesageText),
+                               (do_proto ? *list_this_part : NULL),
+                               (do_proto ? *list_this_pref : NULL),
+                               (do_proto ? *list_this_suff : NULL),
+                               (void *)&ma, 1);
+               }
+               else if (mode == MT_RFC822) {   // unparsed RFC822 dump
+                       Dump_RFC822HeadersBody(
+                               TheMessage,
+                               headers_only,
+                               flags,
+                               nl, nlen);
+                       goto DONE;
+               }
+       }
+
+       if (headers_only == HEADERS_ONLY) {
+               goto DONE;
+       }
+
+       // signify start of msg text
+       if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
+               if (do_proto) cprintf("text\n");
+       }
+
+       if (TheMessage->cm_format_type == FMT_FIXED) 
+               DumpFormatFixed(
+                       TheMessage,
+                       mode,           // how would you like that message?
+                       nl, nlen);
+
+       // If the message on disk is format 0 (Citadel vari-format), we
+       // output using the formatter at 80 columns.  This is the final output
+       // form if the transfer format is RFC822, but if the transfer format
+       // is Citadel proprietary, it'll still work, because the indentation
+       // for new paragraphs is correct and the client will reformat the
+       // message to the reader's screen width.
+       //
+       if (TheMessage->cm_format_type == FMT_CITADEL) {
+               if (mode == MT_MIME) {
+                       cprintf("Content-type: text/x-citadel-variformat\n\n");
+               }
+               memfmout(TheMessage->cm_fields[eMesageText], nl);
+       }
+
+       // If the message on disk is format 4 (MIME), we've gotta hand it
+       // off to the MIME parser.  The client has already been told that
+       // this message is format 1 (fixed format), so the callback function
+       // we use will display those parts as-is.
+       //
+       if (TheMessage->cm_format_type == FMT_RFC822) {
+               memset(&ma, 0, sizeof(struct ma_info));
+
+               if (mode == MT_MIME) {
+                       ma.use_fo_hooks = 0;
+                       strcpy(ma.chosen_part, "1");
+                       ma.chosen_pref = 9999;
+                       ma.dont_decode = CC->msg4_dont_decode;
+                       mime_parser(CM_RANGE(TheMessage, eMesageText),
+                                   *choose_preferred, *fixed_output_pre,
+                                   *fixed_output_post, (void *)&ma, 1);
+                       mime_parser(CM_RANGE(TheMessage, eMesageText),
+                                   *output_preferred, NULL, NULL, (void *)&ma, 1);
+               }
+               else {
+                       ma.use_fo_hooks = 1;
+                       mime_parser(CM_RANGE(TheMessage, eMesageText),
+                                   *fixed_output, *fixed_output_pre,
+                                   *fixed_output_post, (void *)&ma, 0);
+               }
+
+       }
+
+DONE:  /* now we're done */
+       if (do_proto) cprintf("000\n");
+       return(om_ok);
+}
+
+// Save one or more message pointers into a specified room
+// (Returns 0 for success, nonzero for failure)
+// roomname may be NULL to use the current room
+//
+// Note that the 'supplied_msg' field may be set to NULL, in which case
+// the message will be fetched from disk, by number, if we need to perform
+// replication checks.  This adds an additional database read, so if the
+// caller already has the message in memory then it should be supplied.  (Obviously
+// this mode of operation only works if we're saving a single message.)
+//
+int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
+                       int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
+) {
+       int i, j, unique;
+       char hold_rm[ROOMNAMELEN];
+       struct cdbdata *cdbfr;
+       int num_msgs;
+       long *msglist;
+       long highest_msg = 0L;
+
+       long msgid = 0;
+       struct CtdlMessage *msg = NULL;
+
+       long *msgs_to_be_merged = NULL;
+       int num_msgs_to_be_merged = 0;
+
+       syslog(LOG_DEBUG,
+               "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
+               roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
+       );
+
+       strcpy(hold_rm, CC->room.QRname);
+
+       /* Sanity checks */
+       if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
+       if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
+       if (num_newmsgs > 1) supplied_msg = NULL;
+
+       /* Now the regular stuff */
+       if (CtdlGetRoomLock(&CC->room,
+          ((roomname != NULL) ? roomname : CC->room.QRname) )
+          != 0) {
+               syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
+               return(ERROR + ROOM_NOT_FOUND);
+       }
+
+
+       msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
+       num_msgs_to_be_merged = 0;
+
+
+       cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+       if (cdbfr == NULL) {
+               msglist = NULL;
+               num_msgs = 0;
+       } else {
+               msglist = (long *) cdbfr->ptr;
+               cdbfr->ptr = NULL;      /* CtdlSaveMsgPointerInRoom() now owns this memory */
+               num_msgs = cdbfr->len / sizeof(long);
+               cdb_free(cdbfr);
+       }
+
+
+       /* Create a list of msgid's which were supplied by the caller, but do
+        * not already exist in the target room.  It is absolutely taboo to
+        * have more than one reference to the same message in a room.
+        */
+       for (i=0; i<num_newmsgs; ++i) {
+               unique = 1;
+               if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
+                       if (msglist[j] == newmsgidlist[i]) {
+                               unique = 0;
+                       }
+               }
+               if (unique) {
+                       msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
+               }
+       }
+
+       syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
+
+       /*
+        * Now merge the new messages
+        */
+       msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
+       if (msglist == NULL) {
+               syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
+               free(msgs_to_be_merged);
+               return (ERROR + INTERNAL_ERROR);
+       }
+       memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
+       num_msgs += num_msgs_to_be_merged;
+
+       /* Sort the message list, so all the msgid's are in order */
+       num_msgs = sort_msglist(msglist, num_msgs);
+
+       /* Determine the highest message number */
+       highest_msg = msglist[num_msgs - 1];
+
+       /* Write it back to disk. */
+       cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
+                 msglist, (int)(num_msgs * sizeof(long)));
+
+       /* Free up the memory we used. */
+       free(msglist);
+
+       /* Update the highest-message pointer and unlock the room. */
+       CC->room.QRhighest = highest_msg;
+       CtdlPutRoomLock(&CC->room);
+
+       /* Perform replication checks if necessary */
+       if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
+               syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
+
+               for (i=0; i<num_msgs_to_be_merged; ++i) {
+                       msgid = msgs_to_be_merged[i];
+       
+                       if (supplied_msg != NULL) {
+                               msg = supplied_msg;
+                       }
+                       else {
+                               msg = CtdlFetchMessage(msgid, 0);
+                       }
+       
+                       if (msg != NULL) {
+                               ReplicationChecks(msg);
+               
+                               /* If the message has an Exclusive ID, index that... */
+                               if (!CM_IsEmpty(msg, eExclusiveID)) {
+                                       index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
+                               }
+
+                               /* Free up the memory we may have allocated */
+                               if (msg != supplied_msg) {
+                                       CM_Free(msg);
+                               }
+                       }
+       
+               }
+       }
+
+       else {
+               syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
+       }
+
+       /* Submit this room for processing by hooks */
+       int total_roomhook_errors = PerformRoomHooks(&CC->room);
+       if (total_roomhook_errors) {
+               syslog(LOG_WARNING, "msgbase: room hooks returned %d errors", total_roomhook_errors);
+       }
+
+       /* Go back to the room we were in before we wandered here... */
+       CtdlGetRoom(&CC->room, hold_rm);
+
+       /* Bump the reference count for all messages which were merged */
+       if (!suppress_refcount_adj) {
+               AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
+       }
+
+       /* Free up memory... */
+       if (msgs_to_be_merged != NULL) {
+               free(msgs_to_be_merged);
+       }
+
+       /* Return success. */
+       return (0);
+}
+
+
+/*
+ * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
+ * a single message.
+ */
+int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
+                            int do_repl_check, struct CtdlMessage *supplied_msg)
+{
+       return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
+}
+
+
+/*
+ * Message base operation to save a new message to the message store
+ * (returns new message number)
+ *
+ * This is the back end for CtdlSubmitMsg() and should not be directly
+ * called by server-side modules.
+ *
+ */
+long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
+       long retval;
+       struct ser_ret smr;
+       int is_bigmsg = 0;
+       char *holdM = NULL;
+       long holdMLen = 0;
+
+       /*
+        * If the message is big, set its body aside for storage elsewhere
+        * and we hide the message body from the serializer
+        */
+       if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG) {
+               is_bigmsg = 1;
+               holdM = msg->cm_fields[eMesageText];
+               msg->cm_fields[eMesageText] = NULL;
+               holdMLen = msg->cm_lengths[eMesageText];
+               msg->cm_lengths[eMesageText] = 0;
+       }
+
+       /* Serialize our data structure for storage in the database */  
+       CtdlSerializeMessage(&smr, msg);
+
+       if (is_bigmsg) {
+               /* put the message body back into the message */
+               msg->cm_fields[eMesageText] = holdM;
+               msg->cm_lengths[eMesageText] = holdMLen;
+       }
+
+       if (smr.len == 0) {
+               if (Reply) {
+                       cprintf("%d Unable to serialize message\n",
+                               ERROR + INTERNAL_ERROR);
+               }
+               else {
+                       syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
+
+               }
+               return (-1L);
+       }
+
+       /* Write our little bundle of joy into the message base */
+       retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len);
+       if (retval < 0) {
+               syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
+       }
+       else {
+               if (is_bigmsg) {
+                       retval = cdb_store(CDB_BIGMSGS,
+                                          &msgid,
+                                          (int)sizeof(long),
+                                          holdM,
+                                          (holdMLen + 1)
+                               );
+                       if (retval < 0) {
+                               syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
+                       }
+               }
+       }
+
+       /* Free the memory we used for the serialized message */
+       free(smr.ser);
+
+       return(retval);
+}
+
+
+long send_message(struct CtdlMessage *msg) {
+       long newmsgid;
+       long retval;
+       char msgidbuf[256];
+       long msgidbuflen;
+
+       /* Get a new message number */
+       newmsgid = get_new_message_number();
+
+       /* Generate an ID if we don't have one already */
+       if (CM_IsEmpty(msg, emessageId)) {
+               msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
+                                      (long unsigned int) time(NULL),
+                                      (long unsigned int) newmsgid,
+                                      CtdlGetConfigStr("c_fqdn")
+                       );
+
+               CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
+       }
+
+       retval = CtdlSaveThisMessage(msg, newmsgid, 1);
+
+       if (retval == 0) {
+               retval = newmsgid;
+       }
+
+       /* Return the *local* message ID to the caller
+        * (even if we're storing an incoming network message)
+        */
+       return(retval);
+}
+
+
+/*
+ * Serialize a struct CtdlMessage into the format used on disk.
+ * 
+ * This function loads up a "struct ser_ret" (defined in server.h) which
+ * contains the length of the serialized message and a pointer to the
+ * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
+ */
+void CtdlSerializeMessage(struct ser_ret *ret,         /* return values */
+                         struct CtdlMessage *msg)      /* unserialized msg */
+{
+       size_t wlen;
+       int i;
+
+       /*
+        * Check for valid message format
+        */
+       if (CM_IsValidMsg(msg) == 0) {
+               syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
+               ret->len = 0;
+               ret->ser = NULL;
+               return;
+       }
+
+       ret->len = 3;
+       for (i=0; i < NDiskFields; ++i)
+               if (msg->cm_fields[FieldOrder[i]] != NULL)
+                       ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
+
+       ret->ser = malloc(ret->len);
+       if (ret->ser == NULL) {
+               syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
+               ret->len = 0;
+               ret->ser = NULL;
+               return;
+       }
+
+       ret->ser[0] = 0xFF;
+       ret->ser[1] = msg->cm_anon_type;
+       ret->ser[2] = msg->cm_format_type;
+       wlen = 3;
+
+       for (i=0; i < NDiskFields; ++i) {
+               if (msg->cm_fields[FieldOrder[i]] != NULL) {
+                       ret->ser[wlen++] = (char)FieldOrder[i];
+
+                       memcpy(&ret->ser[wlen],
+                              msg->cm_fields[FieldOrder[i]],
+                              msg->cm_lengths[FieldOrder[i]] + 1);
+
+                       wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
+               }
+       }
+
+       if (ret->len != wlen) {
+               syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
+       }
+
+       return;
+}
+
+
+/*
+ * Check to see if any messages already exist in the current room which
+ * carry the same Exclusive ID as this one.  If any are found, delete them.
+ */
+void ReplicationChecks(struct CtdlMessage *msg) {
+       long old_msgnum = (-1L);
+
+       if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
+
+       syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
+
+       /* No exclusive id?  Don't do anything. */
+       if (msg == NULL) return;
+       if (CM_IsEmpty(msg, eExclusiveID)) return;
+
+       /*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
+         msg->cm_fields[eExclusiveID], CC->room.QRname);*/
+
+       old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
+       if (old_msgnum > 0L) {
+               syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
+               CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
+       }
+}
+
+
+/*
+ * Save a message to disk and submit it into the delivery system.
+ */
+long CtdlSubmitMsg(struct CtdlMessage *msg,    /* message to save */
+                  struct recptypes *recps,     /* recipients (if mail) */
+                  const char *force            /* force a particular room? */
+) {
+       char hold_rm[ROOMNAMELEN];
+       char actual_rm[ROOMNAMELEN];
+       char force_room[ROOMNAMELEN];
+       char content_type[SIZ];                 /* We have to learn this */
+       char recipient[SIZ];
+       char bounce_to[1024];
+       const char *room;
+       long newmsgid;
+       const char *mptr = NULL;
+       struct ctdluser userbuf;
+       int a, i;
+       struct MetaData smi;
+       char *collected_addresses = NULL;
+       struct addresses_to_be_filed *aptr = NULL;
+       StrBuf *saved_rfc822_version = NULL;
+       int qualified_for_journaling = 0;
+
+       syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
+       if (CM_IsValidMsg(msg) == 0) return(-1);        /* self check */
+
+       /* If this message has no timestamp, we take the liberty of
+        * giving it one, right now.
+        */
+       if (CM_IsEmpty(msg, eTimestamp)) {
+               CM_SetFieldLONG(msg, eTimestamp, time(NULL));
+       }
+
+       /* If this message has no path, we generate one.
+        */
+       if (CM_IsEmpty(msg, eMessagePath)) {
+               if (!CM_IsEmpty(msg, eAuthor)) {
+                       CM_CopyField(msg, eMessagePath, eAuthor);
+                       for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
+                               if (isspace(msg->cm_fields[eMessagePath][a])) {
+                                       msg->cm_fields[eMessagePath][a] = ' ';
+                               }
+                       }
+               }
+               else {
+                       CM_SetField(msg, eMessagePath, HKEY("unknown"));
+               }
+       }
+
+       if (force == NULL) {
+               force_room[0] = '\0';
+       }
+       else {
+               strcpy(force_room, force);
+       }
+
+       /* Learn about what's inside, because it's what's inside that counts */
+       if (CM_IsEmpty(msg, eMesageText)) {
+               syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
+               return(-2);
+       }
+
+       switch (msg->cm_format_type) {
+       case 0:
+               strcpy(content_type, "text/x-citadel-variformat");
+               break;
+       case 1:
+               strcpy(content_type, "text/plain");
+               break;
+       case 4:
+               strcpy(content_type, "text/plain");
+               mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
+               if (mptr != NULL) {
+                       char *aptr;
+                       safestrncpy(content_type, &mptr[13], sizeof content_type);
+                       striplt(content_type);
+                       aptr = content_type;
+                       while (!IsEmptyStr(aptr)) {
+                               if ((*aptr == ';')
+                                   || (*aptr == ' ')
+                                   || (*aptr == 13)
+                                   || (*aptr == 10)) {
+                                       *aptr = 0;
+                               }
+                               else aptr++;
+                       }
+               }
+       }
+
+       /* Goto the correct room */
+       room = (recps) ? CC->room.QRname : SENTITEMS;
+       syslog(LOG_DEBUG, "msgbase: selected room %s", room);
+       strcpy(hold_rm, CC->room.QRname);
+       strcpy(actual_rm, CC->room.QRname);
+       if (recps != NULL) {
+               strcpy(actual_rm, SENTITEMS);
+       }
+
+       /* If the user is a twit, move to the twit room for posting */
+       if (TWITDETECT) {
+               if (CC->user.axlevel == AxProbU) {
+                       strcpy(hold_rm, actual_rm);
+                       strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
+                       syslog(LOG_DEBUG, "msgbase: diverting to twit room");
+               }
+       }
+
+       /* ...or if this message is destined for Aide> then go there. */
+       if (!IsEmptyStr(force_room)) {
+               strcpy(actual_rm, force_room);
+       }
+
+       syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
+       if (strcasecmp(actual_rm, CC->room.QRname)) {
+               CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
+       }
+
+       /*
+        * If this message has no O (room) field, generate one.
+        */
+       if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
+               CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
+       }
+
+       /* Perform "before save" hooks (aborting if any return nonzero) */
+       syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
+       if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
+
+       /*
+        * If this message has an Exclusive ID, and the room is replication
+        * checking enabled, then do replication checks.
+        */
+       if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
+               ReplicationChecks(msg);
+       }
+
+       /* Save it to disk */
+       syslog(LOG_DEBUG, "msgbase: saving to disk");
+       newmsgid = send_message(msg);
+       if (newmsgid <= 0L) return(-5);
+
+       /* Write a supplemental message info record.  This doesn't have to
+        * be a critical section because nobody else knows about this message
+        * yet.
+        */
+       syslog(LOG_DEBUG, "msgbase: creating metadata record");
+       memset(&smi, 0, sizeof(struct MetaData));
+       smi.meta_msgnum = newmsgid;
+       smi.meta_refcount = 0;
+       safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
+
+       /*
+        * Measure how big this message will be when rendered as RFC822.
+        * We do this for two reasons:
+        * 1. We need the RFC822 length for the new metadata record, so the
+        *    POP and IMAP services don't have to calculate message lengths
+        *    while the user is waiting (multiplied by potentially hundreds
+        *    or thousands of messages).
+        * 2. If journaling is enabled, we will need an RFC822 version of the
+        *    message to attach to the journalized copy.
+        */
+       if (CC->redirect_buffer != NULL) {
+               syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
+               abort();
+       }
+       CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+       CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
+       smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
+       saved_rfc822_version = CC->redirect_buffer;
+       CC->redirect_buffer = NULL;
+
+       PutMetaData(&smi);
+
+       /* Now figure out where to store the pointers */
+       syslog(LOG_DEBUG, "msgbase: storing pointers");
+
+       /* If this is being done by the networker delivering a private
+        * message, we want to BYPASS saving the sender's copy (because there
+        * is no local sender; it would otherwise go to the Trashcan).
+        */
+       if ((!CC->internal_pgm) || (recps == NULL)) {
+               if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
+                       syslog(LOG_ERR, "msgbase: ERROR saving message pointer %ld in %s", newmsgid, actual_rm);
+                       CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
+               }
+       }
+
+       /* For internet mail, drop a copy in the outbound queue room */
+       if ((recps != NULL) && (recps->num_internet > 0)) {
+               CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
+       }
+
+       /* If other rooms are specified, drop them there too. */
+       if ((recps != NULL) && (recps->num_room > 0)) {
+               for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
+                       extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
+                       syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
+                       CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
+               }
+       }
+
+       /* Bump this user's messages posted counter. */
+       syslog(LOG_DEBUG, "msgbase: updating user");
+       CtdlLockGetCurrentUser();
+       CC->user.posted = CC->user.posted + 1;
+       CtdlPutCurrentUserLock();
+
+       /* Decide where bounces need to be delivered */
+       if ((recps != NULL) && (recps->bounce_to == NULL)) {
+               if (CC->logged_in) {
+                       strcpy(bounce_to, CC->user.fullname);
+               }
+               else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
+                       strcpy(bounce_to, msg->cm_fields[eAuthor]);
+               }
+               recps->bounce_to = bounce_to;
+       }
+               
+       CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
+
+       /* If this is private, local mail, make a copy in the
+        * recipient's mailbox and bump the reference count.
+        */
+       if ((recps != NULL) && (recps->num_local > 0)) {
+               char *pch;
+               int ntokens;
+
+               pch = recps->recp_local;
+               recps->recp_local = recipient;
+               ntokens = num_tokens(pch, '|');
+               for (i=0; i<ntokens; ++i) {
+                       extract_token(recipient, pch, i, '|', sizeof recipient);
+                       syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
+                       if (CtdlGetUser(&userbuf, recipient) == 0) {
+                               CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
+                               CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
+                               CtdlBumpNewMailCounter(userbuf.usernum);
+                               PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
+                       }
+                       else {
+                               syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
+                               CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
+                       }
+               }
+               recps->recp_local = pch;
+       }
+
+       /* Perform "after save" hooks */
+       syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
+
+       PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
+       CM_FlushField(msg, eVltMsgNum);
+
+       /* Go back to the room we started from */
+       syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
+       if (strcasecmp(hold_rm, CC->room.QRname)) {
+               CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
+       }
+
+       /*
+        * Any addresses to harvest for someone's address book?
+        */
+       if ( (CC->logged_in) && (recps != NULL) ) {
+               collected_addresses = harvest_collected_addresses(msg);
+       }
+
+       if (collected_addresses != NULL) {
+               aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
+               CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
+               aptr->roomname = strdup(actual_rm);
+               aptr->collected_addresses = collected_addresses;
+               begin_critical_section(S_ATBF);
+               aptr->next = atbf;
+               atbf = aptr;
+               end_critical_section(S_ATBF);
+       }
+
+       /*
+        * Determine whether this message qualifies for journaling.
+        */
+       if (!CM_IsEmpty(msg, eJournal)) {
+               qualified_for_journaling = 0;
+       }
+       else {
+               if (recps == NULL) {
+                       qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
+               }
+               else if (recps->num_local + recps->num_internet > 0) {
+                       qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
+               }
+               else {
+                       qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
+               }
+       }
+
+       /*
+        * Do we have to perform journaling?  If so, hand off the saved
+        * RFC822 version will be handed off to the journaler for background
+        * submit.  Otherwise, we have to free the memory ourselves.
+        */
+       if (saved_rfc822_version != NULL) {
+               if (qualified_for_journaling) {
+                       JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
+               }
+               else {
+                       FreeStrBuf(&saved_rfc822_version);
+               }
+       }
+
+       if ((recps != NULL) && (recps->bounce_to == bounce_to))
+               recps->bounce_to = NULL;
+
+       /* Done. */
+       return(newmsgid);
+}
+
+
+/*
+ * Convenience function for generating small administrative messages.
+ */
+long quickie_message(char *from,
+                    char *fromaddr,
+                    char *to,
+                    char *room,
+                    char *text, 
+                    int format_type,
+                    char *subject)
+{
+       struct CtdlMessage *msg;
+       struct recptypes *recp = NULL;
+
+       msg = malloc(sizeof(struct CtdlMessage));
+       memset(msg, 0, sizeof(struct CtdlMessage));
+       msg->cm_magic = CTDLMESSAGE_MAGIC;
+       msg->cm_anon_type = MES_NORMAL;
+       msg->cm_format_type = format_type;
+
+       if (!IsEmptyStr(from)) {
+               CM_SetField(msg, eAuthor, from, -1);
+       }
+       else if (!IsEmptyStr(fromaddr)) {
+               char *pAt;
+               CM_SetField(msg, eAuthor, fromaddr, -1);
+               pAt = strchr(msg->cm_fields[eAuthor], '@');
+               if (pAt != NULL) {
+                       CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
+               }
+       }
+       else {
+               msg->cm_fields[eAuthor] = strdup("Citadel");
+       }
+
+       if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr, -1);
+       if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room, -1);
+       if (!IsEmptyStr(to)) {
+               CM_SetField(msg, eRecipient, to, -1);
+               recp = validate_recipients(to, NULL, 0);
+       }
+       if (!IsEmptyStr(subject)) {
+               CM_SetField(msg, eMsgSubject, subject, -1);
+       }
+       if (!IsEmptyStr(text)) {
+               CM_SetField(msg, eMesageText, text, -1);
+       }
+
+       long msgnum = CtdlSubmitMsg(msg, recp, room);
+       CM_Free(msg);
+       if (recp != NULL) free_recipients(recp);
+       return msgnum;
+}
+
+
+/*
+ * Back end function used by CtdlMakeMessage() and similar functions
+ */
+StrBuf *CtdlReadMessageBodyBuf(char *terminator,       // token signalling EOT
+                              long tlen,
+                              size_t maxlen,           // maximum message length
+                              StrBuf *exist,           // if non-null, append to it; exist is ALWAYS freed
+                              int crlf                 // CRLF newlines instead of LF
+) {
+       StrBuf *Message;
+       StrBuf *LineBuf;
+       int flushing = 0;
+       int finished = 0;
+       int dotdot = 0;
+
+       LineBuf = NewStrBufPlain(NULL, SIZ);
+       if (exist == NULL) {
+               Message = NewStrBufPlain(NULL, 4 * SIZ);
+       }
+       else {
+               Message = NewStrBufDup(exist);
+       }
+
+       /* Do we need to change leading ".." to "." for SMTP escaping? */
+       if ((tlen == 1) && (*terminator == '.')) {
+               dotdot = 1;
+       }
+
+       /* read in the lines of message text one by one */
+       do {
+               if (CtdlClientGetLine(LineBuf) < 0) {
+                       finished = 1;
+               }
+               if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
+                       finished = 1;
+               }
+               if ( (!flushing) && (!finished) ) {
+                       if (crlf) {
+                               StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
+                       }
+                       else {
+                               StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
+                       }
+                       
+                       /* Unescape SMTP-style input of two dots at the beginning of the line */
+                       if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
+                               StrBufCutLeft(LineBuf, 1);
+                       }
+                       StrBufAppendBuf(Message, LineBuf, 0);
+               }
+
+               /* if we've hit the max msg length, flush the rest */
+               if (StrLength(Message) >= maxlen) {
+                       flushing = 1;
+               }
+
+       } while (!finished);
+       FreeStrBuf(&LineBuf);
+
+       if (flushing) {
+               syslog(LOG_ERR, "msgbase: exceeded maximum message length of %ld - message was truncated", maxlen);
+       }
+
+       return Message;
+}
+
+
+// Back end function used by CtdlMakeMessage() and similar functions
+char *CtdlReadMessageBody(char *terminator,    // token signalling EOT
+                         long tlen,
+                         size_t maxlen,        // maximum message length
+                         StrBuf *exist,        // if non-null, append to it; exist is ALWAYS freed
+                         int crlf              // CRLF newlines instead of LF
+) {
+       StrBuf *Message;
+
+       Message = CtdlReadMessageBodyBuf(terminator,
+                                        tlen,
+                                        maxlen,
+                                        exist,
+                                        crlf
+       );
+       if (Message == NULL) {
+               return NULL;
+       }
+       else {
+               return SmashStrBuf(&Message);
+       }
+}
+
+
+struct CtdlMessage *CtdlMakeMessage(
+        struct ctdluser *author,        /* author's user structure */
+        char *recipient,                /* NULL if it's not mail */
+        char *recp_cc,                 /* NULL if it's not mail */
+        char *room,                     /* room where it's going */
+        int type,                       /* see MES_ types in header file */
+        int format_type,                /* variformat, plain text, MIME... */
+        char *fake_name,                /* who we're masquerading as */
+       char *my_email,                 /* which of my email addresses to use (empty is ok) */
+        char *subject,                  /* Subject (optional) */
+       char *supplied_euid,            /* ...or NULL if this is irrelevant */
+        char *preformatted_text,        /* ...or NULL to read text from client */
+       char *references                /* Thread references */
+) {
+       return CtdlMakeMessageLen(
+               author, /* author's user structure */
+               recipient,              /* NULL if it's not mail */
+               (recipient)?strlen(recipient) : 0,
+               recp_cc,                        /* NULL if it's not mail */
+               (recp_cc)?strlen(recp_cc): 0,
+               room,                   /* room where it's going */
+               (room)?strlen(room): 0,
+               type,                   /* see MES_ types in header file */
+               format_type,            /* variformat, plain text, MIME... */
+               fake_name,              /* who we're masquerading as */
+               (fake_name)?strlen(fake_name): 0,
+               my_email,                       /* which of my email addresses to use (empty is ok) */
+               (my_email)?strlen(my_email): 0,
+               subject,                        /* Subject (optional) */
+               (subject)?strlen(subject): 0,
+               supplied_euid,          /* ...or NULL if this is irrelevant */
+               (supplied_euid)?strlen(supplied_euid):0,
+               preformatted_text,      /* ...or NULL to read text from client */
+               (preformatted_text)?strlen(preformatted_text) : 0,
+               references,             /* Thread references */
+               (references)?strlen(references):0);
+
+}
+
+
+/*
+ * Build a binary message to be saved on disk.
+ * (NOTE: if you supply 'preformatted_text', the buffer you give it
+ * will become part of the message.  This means you are no longer
+ * responsible for managing that memory -- it will be freed along with
+ * the rest of the fields when CM_Free() is called.)
+ */
+struct CtdlMessage *CtdlMakeMessageLen(
+       struct ctdluser *author,        /* author's user structure */
+       char *recipient,                /* NULL if it's not mail */
+       long rcplen,
+       char *recp_cc,                  /* NULL if it's not mail */
+       long cclen,
+       char *room,                     /* room where it's going */
+       long roomlen,
+       int type,                       /* see MES_ types in header file */
+       int format_type,                /* variformat, plain text, MIME... */
+       char *fake_name,                /* who we're masquerading as */
+       long fnlen,
+       char *my_email,                 /* which of my email addresses to use (empty is ok) */
+       long myelen,
+       char *subject,                  /* Subject (optional) */
+       long subjlen,
+       char *supplied_euid,            /* ...or NULL if this is irrelevant */
+       long euidlen,
+       char *preformatted_text,        /* ...or NULL to read text from client */
+       long textlen,
+       char *references,               /* Thread references */
+       long reflen
+) {
+       long blen;
+       char buf[1024];
+       struct CtdlMessage *msg;
+       StrBuf *FakeAuthor;
+       StrBuf *FakeEncAuthor = NULL;
+
+       msg = malloc(sizeof(struct CtdlMessage));
+       memset(msg, 0, sizeof(struct CtdlMessage));
+       msg->cm_magic = CTDLMESSAGE_MAGIC;
+       msg->cm_anon_type = type;
+       msg->cm_format_type = format_type;
+
+       if (recipient != NULL) rcplen = striplt(recipient);
+       if (recp_cc != NULL) cclen = striplt(recp_cc);
+
+       /* Path or Return-Path */
+       if (myelen > 0) {
+               CM_SetField(msg, eMessagePath, my_email, myelen);
+       }
+       else if (!IsEmptyStr(author->fullname)) {
+               CM_SetField(msg, eMessagePath, author->fullname, -1);
+       }
+       convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
+
+       blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
+       CM_SetField(msg, eTimestamp, buf, blen);
+
+       if (fnlen > 0) {
+               FakeAuthor = NewStrBufPlain (fake_name, fnlen);
+       }
+       else {
+               FakeAuthor = NewStrBufPlain (author->fullname, -1);
+       }
+       StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
+       CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
+       FreeStrBuf(&FakeAuthor);
+
+       if (!!IsEmptyStr(CC->room.QRname)) {
+               if (CC->room.QRflags & QR_MAILBOX) {            /* room */
+                       CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11], -1);
+               }
+               else {
+                       CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
+               }
+       }
+
+       if (rcplen > 0) {
+               CM_SetField(msg, eRecipient, recipient, rcplen);
+       }
+       if (cclen > 0) {
+               CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
+       }
+
+       if (myelen > 0) {
+               CM_SetField(msg, erFc822Addr, my_email, myelen);
+       }
+       else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
+               CM_SetField(msg, erFc822Addr, CC->cs_inet_email, -1);
+       }
+
+       if (subject != NULL) {
+               long length;
+               length = striplt(subject);
+               if (length > 0) {
+                       long i;
+                       long IsAscii;
+                       IsAscii = -1;
+                       i = 0;
+                       while ((subject[i] != '\0') &&
+                              (IsAscii = isascii(subject[i]) != 0 ))
+                               i++;
+                       if (IsAscii != 0)
+                               CM_SetField(msg, eMsgSubject, subject, subjlen);
+                       else /* ok, we've got utf8 in the string. */
+                       {
+                               char *rfc2047Subj;
+                               rfc2047Subj = rfc2047encode(subject, length);
+                               CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
+                       }
+
+               }
+       }
+
+       if (euidlen > 0) {
+               CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
+       }
+
+       if (reflen > 0) {
+               CM_SetField(msg, eWeferences, references, reflen);
+       }
+
+       if (preformatted_text != NULL) {
+               CM_SetField(msg, eMesageText, preformatted_text, textlen);
+       }
+       else {
+               StrBuf *MsgBody;
+               MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
+               if (MsgBody != NULL) {
+                       CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
+               }
+       }
+
+       return(msg);
+}
+
+
+/*
+ * API function to delete messages which match a set of criteria
+ * (returns the actual number of messages deleted)
+ */
+int CtdlDeleteMessages(const char *room_name,  // which room
+                      long *dmsgnums,          // array of msg numbers to be deleted
+                      int num_dmsgnums,        // number of msgs to be deleted, or 0 for "any"
+                      char *content_type       // or "" for any.  regular expressions expected.
+) {
+       struct ctdlroom qrbuf;
+       struct cdbdata *cdbfr;
+       long *msglist = NULL;
+       long *dellist = NULL;
+       int num_msgs = 0;
+       int i, j;
+       int num_deleted = 0;
+       int delete_this;
+       struct MetaData smi;
+       regex_t re;
+       regmatch_t pm;
+       int need_to_free_re = 0;
+
+       if (content_type) if (!IsEmptyStr(content_type)) {
+                       regcomp(&re, content_type, 0);
+                       need_to_free_re = 1;
+               }
+       syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
+
+       /* get room record, obtaining a lock... */
+       if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
+               syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
+               if (need_to_free_re) regfree(&re);
+               return(0);      /* room not found */
+       }
+       cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
+
+       if (cdbfr != NULL) {
+               dellist = malloc(cdbfr->len);
+               msglist = (long *) cdbfr->ptr;
+               cdbfr->ptr = NULL;      /* CtdlDeleteMessages() now owns this memory */
+               num_msgs = cdbfr->len / sizeof(long);
+               cdb_free(cdbfr);
+       }
+       if (num_msgs > 0) {
+               int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
+               int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
+               int have_more_del = 1;
+
+               num_msgs = sort_msglist(msglist, num_msgs);
+               if (num_dmsgnums > 1)
+                       num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
+/*
+               {
+                       StrBuf *dbg = NewStrBuf();
+                       for (i = 0; i < num_dmsgnums; i++)
+                               StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
+                       syslog(LOG_DEBUG, "msgbase: Deleting before: %s", ChrPtr(dbg));
+                       FreeStrBuf(&dbg);
+               }
+*/
+               i = 0; j = 0;
+               while ((i < num_msgs) && (have_more_del)) {
+                       delete_this = 0x00;
+
+                       /* Set/clear a bit for each criterion */
+
+                       /* 0 messages in the list or a null list means that we are
+                        * interested in deleting any messages which meet the other criteria.
+                        */
+                       if (have_delmsgs) {
+                               delete_this |= 0x01;
+                       }
+                       else {
+                               while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
+
+                               if (i >= num_msgs)
+                                       continue;
+
+                               if (msglist[i] == dmsgnums[j]) {
+                                       delete_this |= 0x01;
+                               }
+                               j++;
+                               have_more_del = (j < num_dmsgnums);
+                       }
+
+                       if (have_contenttype) {
+                               GetMetaData(&smi, msglist[i]);
+                               if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
+                                       delete_this |= 0x02;
+                               }
+                       } else {
+                               delete_this |= 0x02;
+                       }
+
+                       /* Delete message only if all bits are set */
+                       if (delete_this == 0x03) {
+                               dellist[num_deleted++] = msglist[i];
+                               msglist[i] = 0L;
+                       }
+                       i++;
+               }
+/*
+               {
+                       StrBuf *dbg = NewStrBuf();
+                       for (i = 0; i < num_deleted; i++)
+                               StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
+                       syslog(LOG_DEBUG, "msgbase: Deleting: %s", ChrPtr(dbg));
+                       FreeStrBuf(&dbg);
+               }
+*/
+               num_msgs = sort_msglist(msglist, num_msgs);
+               cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
+                         msglist, (int)(num_msgs * sizeof(long)));
+
+               if (num_msgs > 0)
+                       qrbuf.QRhighest = msglist[num_msgs - 1];
+               else
+                       qrbuf.QRhighest = 0;
+       }
+       CtdlPutRoomLock(&qrbuf);
+
+       /* Go through the messages we pulled out of the index, and decrement
+        * their reference counts by 1.  If this is the only room the message
+        * was in, the reference count will reach zero and the message will
+        * automatically be deleted from the database.  We do this in a
+        * separate pass because there might be plug-in hooks getting called,
+        * and we don't want that happening during an S_ROOMS critical
+        * section.
+        */
+       if (num_deleted) {
+               for (i=0; i<num_deleted; ++i) {
+                       PerformDeleteHooks(qrbuf.QRname, dellist[i]);
+               }
+               AdjRefCountList(dellist, num_deleted, -1);
+       }
+       /* Now free the memory we used, and go away. */
+       if (msglist != NULL) free(msglist);
+       if (dellist != NULL) free(dellist);
+       syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
+       if (need_to_free_re) regfree(&re);
+       return (num_deleted);
+}
+
+
+/*
+ * GetMetaData()  -  Get the supplementary record for a message
+ */
+void GetMetaData(struct MetaData *smibuf, long msgnum)
+{
+       struct cdbdata *cdbsmi;
+       long TheIndex;
+
+       memset(smibuf, 0, sizeof(struct MetaData));
+       smibuf->meta_msgnum = msgnum;
+       smibuf->meta_refcount = 1;      /* Default reference count is 1 */
+
+       /* Use the negative of the message number for its supp record index */
+       TheIndex = (0L - msgnum);
+
+       cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
+       if (cdbsmi == NULL) {
+               return;                 /* record not found; leave it alone */
+       }
+       memcpy(smibuf, cdbsmi->ptr,
+              ((cdbsmi->len > sizeof(struct MetaData)) ?
+               sizeof(struct MetaData) : cdbsmi->len)
+       );
+       cdb_free(cdbsmi);
+       return;
+}
+
+
+/*
+ * PutMetaData()  -  (re)write supplementary record for a message
+ */
+void PutMetaData(struct MetaData *smibuf)
+{
+       long TheIndex;
+
+       /* Use the negative of the message number for the metadata db index */
+       TheIndex = (0L - smibuf->meta_msgnum);
+
+       cdb_store(CDB_MSGMAIN,
+                 &TheIndex, (int)sizeof(long),
+                 smibuf, (int)sizeof(struct MetaData)
+       );
+}
+
+
+/*
+ * Convenience function to process a big block of AdjRefCount() operations
+ */
+void AdjRefCountList(long *msgnum, long nmsg, int incr)
+{
+       long i;
+
+       for (i = 0; i < nmsg; i++) {
+               AdjRefCount(msgnum[i], incr);
+       }
+}
+
+
+/*
+ * AdjRefCount - adjust the reference count for a message.  We need to delete from disk any message whose reference count reaches zero.
+ */
+void AdjRefCount(long msgnum, int incr)
+{
+       struct MetaData smi;
+       long delnum;
+
+       /* This is a *tight* critical section; please keep it that way, as
+        * it may get called while nested in other critical sections.  
+        * Complicating this any further will surely cause deadlock!
+        */
+       begin_critical_section(S_SUPPMSGMAIN);
+       GetMetaData(&smi, msgnum);
+       smi.meta_refcount += incr;
+       PutMetaData(&smi);
+       end_critical_section(S_SUPPMSGMAIN);
+       syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
+
+       /* If the reference count is now zero, delete both the message and its metadata record.
+        */
+       if (smi.meta_refcount == 0) {
+               syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
+               
+               /* Call delete hooks with NULL room to show it has gone altogether */
+               PerformDeleteHooks(NULL, msgnum);
+
+               /* Remove from message base */
+               delnum = msgnum;
+               cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
+               cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
+
+               /* Remove metadata record */
+               delnum = (0L - msgnum);
+               cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
+       }
+}
+
+
+/*
+ * Write a generic object to this room
+ *
+ * Returns the message number of the written object, in case you need it.
+ */
+long CtdlWriteObject(char *req_room,                   /* Room to stuff it in */
+                    char *content_type,                /* MIME type of this object */
+                    char *raw_message,                 /* Data to be written */
+                    off_t raw_length,                  /* Size of raw_message */
+                    struct ctdluser *is_mailbox,       /* Mailbox room? */
+                    int is_binary,                     /* Is encoding necessary? */
+                    unsigned int flags                 /* Internal save flags */
+) {
+       struct ctdlroom qrbuf;
+       char roomname[ROOMNAMELEN];
+       struct CtdlMessage *msg;
+       StrBuf *encoded_message = NULL;
+
+       if (is_mailbox != NULL) {
+               CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
+       }
+       else {
+               safestrncpy(roomname, req_room, sizeof(roomname));
+       }
+
+       syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
+
+       if (is_binary) {
+               encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
+       }
+       else {
+               encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
+       }
+
+       StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
+       StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
+       StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
+
+       if (is_binary) {
+               StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
+       }
+       else {
+               StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
+       }
+
+       if (is_binary) {
+               StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
+       }
+       else {
+               StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
+       }
+
+       syslog(LOG_DEBUG, "msgbase: allocating");
+       msg = malloc(sizeof(struct CtdlMessage));
+       memset(msg, 0, sizeof(struct CtdlMessage));
+       msg->cm_magic = CTDLMESSAGE_MAGIC;
+       msg->cm_anon_type = MES_NORMAL;
+       msg->cm_format_type = 4;
+       CM_SetField(msg, eAuthor, CC->user.fullname, -1);
+       CM_SetField(msg, eOriginalRoom, req_room, -1);
+       msg->cm_flags = flags;
+       
+       CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
+
+       /* Create the requested room if we have to. */
+       if (CtdlGetRoom(&qrbuf, roomname) != 0) {
+               CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
+       }
+
+       /* Now write the data */
+       long new_msgnum = CtdlSubmitMsg(msg, NULL, roomname);
+       CM_Free(msg);
+       return new_msgnum;
+}
+
+
+/************************************************************************/
+/*                      MODULE INITIALIZATION                           */
+/************************************************************************/
+
+char *ctdl_module_init_msgbase(void) {
+       if (!threading) {
+               FillMsgKeyLookupTable();
+       }
+
+        /* return our module id for the log */
+       return "msgbase";
+}
diff --git a/citadel/server/msgbase.h b/citadel/server/msgbase.h
new file mode 100644 (file)
index 0000000..6ccb93b
--- /dev/null
@@ -0,0 +1,229 @@
+
+#ifndef MSGBASE_H
+#define MSGBASE_H
+
+enum {
+       MSGS_ALL,
+       MSGS_OLD,
+       MSGS_NEW,
+       MSGS_FIRST,
+       MSGS_LAST,
+       MSGS_GT,
+       MSGS_EQ,
+       MSGS_SEARCH,
+       MSGS_LT
+};
+
+enum {
+       MSG_HDRS_BRIEF = 0,
+       MSG_HDRS_ALL = 1,
+       MSG_HDRS_EUID = 4,
+       MSG_HDRS_BRIEFFILTER = 8,
+       MSG_HDRS_THREADS = 9
+};
+
+/*
+ * Possible return codes from CtdlOutputMsg()
+ */
+enum {
+       om_ok,
+       om_not_logged_in,
+       om_no_such_msg,
+       om_mime_error,
+       om_access_denied
+};
+
+/*
+ * Values of "headers_only" when calling message output routines
+ */
+#define HEADERS_ALL    0       /* Headers and body */
+#define        HEADERS_ONLY    1       /* Headers only */
+#define        HEADERS_NONE    2       /* Body only */
+#define HEADERS_FAST   3       /* Headers only with no MIME info */
+
+
+struct ma_info {
+       int is_ma;              /* Set to 1 if we are using this stuff */
+       int freeze;             /* Freeze the replacement chain because we're
+                                * digging through a subsection */
+       int did_print;          /* One alternative has been displayed */
+       char chosen_part[128];  /* Which part of a m/a did we choose? */
+       int chosen_pref;        /* Chosen part preference level (lower is better) */
+       int use_fo_hooks;       /* Use fixed output hooks */
+       int dont_decode;        /* should we call the decoder or not? */
+};
+
+
+struct repl {                  /* Info for replication checking */
+       char exclusive_id[SIZ];
+       time_t highest;
+};
+
+
+/*
+ * This is a list of "harvested" email addresses that we might want to
+ * stick into someone's address book.  But we defer this operaiton so
+ * it can be done asynchronously.
+ */
+struct addresses_to_be_filed {
+       struct addresses_to_be_filed *next;
+       char *roomname;
+       char *collected_addresses;
+};
+
+extern struct addresses_to_be_filed *atbf;
+
+int GetFieldFromMnemonic(eMsgField *f, const char* c);
+void memfmout (char *mptr, const char *nl);
+void output_mime_parts(char *);
+long send_message (struct CtdlMessage *);
+void loadtroom (void);
+long CtdlSubmitMsg(struct CtdlMessage *, struct recptypes *, const char *);
+long quickie_message(char *from, char *fromaddr, char *to, char *room, char *text, int format_type, char *subject);
+void GetMetaData(struct MetaData *, long);
+void PutMetaData(struct MetaData *);
+void AdjRefCount(long, int);
+void TDAP_AdjRefCount(long, int);
+int TDAP_ProcessAdjRefCountQueue(void);
+void simple_listing(long, void *);
+int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template);
+typedef void (*ForEachMsgCallback)(long MsgNumber, void *UserData);
+int CtdlForEachMessage(int mode,
+                       long ref,
+                       char *searchstring,
+                       char *content_type,
+                       struct CtdlMessage *compare,
+                        ForEachMsgCallback CallBack,
+                       void *userdata);
+int CtdlDeleteMessages(const char *, long *, int, char *);
+long CtdlWriteObject(char *req_room,                   /* Room to stuff it in */
+                       char *content_type,             /* MIME type of this object */
+                       char *raw_message,              /* Data to be written */
+                       off_t raw_length,               /* Size of raw_message */
+                       struct ctdluser *is_mailbox,    /* Mailbox room? */
+                       int is_binary,                  /* Is encoding necessary? */
+                       unsigned int flags              /* Internal save flags */
+);
+struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body);
+struct CtdlMessage * CM_Duplicate
+                       (struct CtdlMessage *OrgMsg);
+int  CM_IsEmpty        (struct CtdlMessage *Msg, eMsgField which);
+void CM_SetField       (struct CtdlMessage *Msg, eMsgField which, const char *buf, long length);
+void CM_SetFieldLONG   (struct CtdlMessage *Msg, eMsgField which, long lvalue);
+void CM_CopyField      (struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy);
+void CM_CutFieldAt     (struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen);
+void CM_FlushField     (struct CtdlMessage *Msg, eMsgField which);
+void CM_Flush          (struct CtdlMessage *Msg);
+void CM_SetAsField     (struct CtdlMessage *Msg, eMsgField which, char **buf, long length);
+void CM_SetAsFieldSB   (struct CtdlMessage *Msg, eMsgField which, StrBuf **buf);
+void CM_GetAsField     (struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen);
+void CM_PrependToField (struct CtdlMessage *Msg, eMsgField which, const char *buf, long length);
+
+void CM_Free           (struct CtdlMessage *msg);
+void CM_FreeContents   (struct CtdlMessage *msg);
+int  CM_IsValidMsg     (struct CtdlMessage *msg);
+
+#define CM_KEY(Message, Which) Message->cm_fields[Which], Message->cm_lengths[Which]
+#define CM_RANGE(Message, Which) Message->cm_fields[Which], \
+               Message->cm_fields[Which] + Message->cm_lengths[Which]
+
+void CtdlSerializeMessage(struct ser_ret *, struct CtdlMessage *);
+struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length);
+void ReplicationChecks(struct CtdlMessage *);
+int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
+                             int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
+);
+int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, struct CtdlMessage *msg);
+long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply);
+char *CtdlReadMessageBody(char *terminator, long tlen, size_t maxlen, StrBuf *exist, int crlf);
+StrBuf *CtdlReadMessageBodyBuf(
+               char *terminator,       /* token signalling EOT */
+               long tlen,
+               size_t maxlen,          /* maximum message length */
+               StrBuf *exist,          /* if non-null, append to it; exist is ALWAYS freed  */
+               int crlf                /* CRLF newlines instead of LF */
+);
+
+int CtdlOutputMsg(long msg_num,                /* message number (local) to fetch */
+               int mode,               /* how would you like that message? */
+               int headers_only,       /* eschew the message body? */
+               int do_proto,           /* do Citadel protocol responses? */
+               int crlf,               /* 0=LF, 1=CRLF */
+               char *section,          /* output a message/rfc822 section */
+               int flags,              /* should the bessage be exported clean? */
+               char **Author,          /* if you want to know the author of the message... */
+               char **Address,         /* if you want to know the sender address of the message... */
+               char **MessageID        /* if you want to know the Message-ID of the message... */
+);
+
+/* Flags which may be passed to CtdlOutputMsg() and CtdlOutputPreLoadedMsg() */
+#define QP_EADDR       (1<<0)          /* quoted-printable encode email addresses */
+#define CRLF           (1<<1)
+#define ESC_DOT                (1<<2)          /* output a line containing only "." as ".." instead */
+#define SUPPRESS_ENV_TO        (1<<3)          /* suppress Envelope-to: header (warning: destructive!) */
+
+int CtdlOutputPreLoadedMsg(struct CtdlMessage *,
+                          int mode,            /* how would you like that message? */
+                          int headers_only,    /* eschew the message body? */
+                          int do_proto,        /* do Citadel protocol responses? */
+                          int crlf,            /* 0=LF, 1=CRLF */
+                          int flags            /* should the bessage be exported clean? */
+);
+
+
+/* values for which_set */
+enum {
+       ctdlsetseen_seen,
+       ctdlsetseen_answered
+};
+
+void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
+       int target_setting, int which_set,
+       struct ctdluser *which_user, struct ctdlroom *which_room
+);
+
+void CtdlGetSeen(char *buf, int which_set);
+
+
+struct CtdlMessage *CtdlMakeMessage(
+        struct ctdluser *author,        /* author's user structure */
+        char *recipient,                /* NULL if it's not mail */
+        char *recp_cc,                 /* NULL if it's not mail */
+        char *room,                     /* room where it's going */
+        int type,                       /* see MES_ types in header file */
+        int format_type,                /* variformat, plain text, MIME... */
+        char *fake_name,                /* who we're masquerading as */
+       char *my_email,                 /* which of my email addresses to use (empty is ok) */
+        char *subject,                  /* Subject (optional) */
+       char *supplied_euid,            /* ...or NULL if this is irrelevant */
+        char *preformatted_text,        /* ...or NULL to read text from client */
+       char *references                /* Thread references */
+);
+
+struct CtdlMessage *CtdlMakeMessageLen(
+       struct ctdluser *author,        /* author's user structure */
+       char *recipient,                /* NULL if it's not mail */
+       long rcplen,
+       char *recp_cc,                  /* NULL if it's not mail */
+       long cclen,
+       char *room,                     /* room where it's going */
+       long roomlen,
+       int type,                       /* see MES_ types in header file */
+       int format_type,                /* variformat, plain text, MIME... */
+       char *fake_name,                /* who we're masquerading as */
+       long fnlen,
+       char *my_email,                 /* which of my email addresses to use (empty is ok) */
+       long myelen,
+       char *subject,                  /* Subject (optional) */
+       long subjlen,
+       char *supplied_euid,            /* ...or NULL if this is irrelevant */
+       long euidlen,
+       char *preformatted_text,        /* ...or NULL to read text from client */
+       long textlen,
+       char *references,               /* Thread references */
+       long reflen
+);
+
+void AdjRefCountList(long *msgnum, long nmsg, int incr);
+
+#endif /* MSGBASE_H */
diff --git a/citadel/server/netconfig.c b/citadel/server/netconfig.c
new file mode 100644 (file)
index 0000000..e71d262
--- /dev/null
@@ -0,0 +1,188 @@
+// This module handles loading, saving, and parsing of room network configurations.
+//
+// Copyright (c) 2000-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "sysdep.h"
+#include <stdio.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#ifdef HAVE_SYSCALL_H
+# include <syscall.h>
+#else 
+# if HAVE_SYS_SYSCALL_H
+#  include <sys/syscall.h>
+# endif
+#endif
+#include <dirent.h>
+#include <assert.h>
+#include <libcitadel.h>
+#include "ctdl_module.h"
+#include "serv_extensions.h"
+#include "config.h"
+
+
+// Create a config key for a room's netconfig entry
+void netcfg_keyname(char *keybuf, long roomnum) {
+       if (!keybuf) return;
+       sprintf(keybuf, "c_netconfig_%010ld", roomnum);
+}
+
+
+// Given a room number and a textual netconfig, convert to base64 and write to the configdb
+void SaveRoomNetConfigFile(long roomnum, const char *raw_netconfig) {
+       char keyname[25];
+       char *enc;
+       int enc_len;
+       int len;
+
+       len = strlen(raw_netconfig);
+       netcfg_keyname(keyname, roomnum);
+       enc = malloc(len * 2);
+
+       if (enc) {
+               enc_len = CtdlEncodeBase64(enc, raw_netconfig, len, 0);
+               if ((enc_len > 1) && (enc[enc_len-2] == 13)) enc[enc_len-2] = 0;
+               if ((enc_len > 0) && (enc[enc_len-1] == 10)) enc[enc_len-1] = 0;
+               enc[enc_len] = 0;
+               syslog(LOG_DEBUG, "netconfig: writing key '%s' (length=%d)", keyname, enc_len);
+               CtdlSetConfigStr(keyname, enc);
+               free(enc);
+       }
+}
+
+
+// Given a room number, attempt to load the netconfig configdb entry for that room.
+// If it returns NULL, there is no netconfig.
+// Otherwise the caller owns the returned memory and is responsible for freeing it.
+char *LoadRoomNetConfigFile(long roomnum) {
+       char keyname[25];
+       char *encoded_netconfig = NULL;
+       char *decoded_netconfig = NULL;
+
+       netcfg_keyname(keyname, roomnum);
+       encoded_netconfig = CtdlGetConfigStr(keyname);
+       if (!encoded_netconfig) return NULL;
+
+       decoded_netconfig = malloc(strlen(encoded_netconfig));  // yeah, way bigger than it needs to be, but safe
+       CtdlDecodeBase64(decoded_netconfig, encoded_netconfig, strlen(encoded_netconfig));
+       return decoded_netconfig;
+}
+
+
+//-----------------------------------------------------------------------------
+//              Per room network configs : exchange with client                
+//-----------------------------------------------------------------------------
+void cmd_gnet(char *argbuf) {
+       if ( (CC->room.QRflags & QR_MAILBOX) && (CC->user.usernum == atol(CC->room.QRname)) ) {
+               // users can edit the netconfigs for their own mailbox rooms
+       }
+       else if (CtdlAccessCheck(ac_room_aide)) return;
+       
+       cprintf("%d Network settings for room #%ld <%s>\n", LISTING_FOLLOWS, CC->room.QRnumber, CC->room.QRname);
+
+       char *c = LoadRoomNetConfigFile(CC->room.QRnumber);
+       if (c) {
+               int len = strlen(c);
+               client_write(c, len);                   // Can't use cprintf() here, it has a limit of 1024 bytes
+               if (c[len] != '\n') {
+                       client_write(HKEY("\n"));
+               }
+               free(c);
+       }
+       cprintf("000\n");
+}
+
+
+void cmd_snet(char *argbuf) {
+       StrBuf *Line = NULL;
+       StrBuf *TheConfig = NULL;
+       int rc;
+
+       unbuffer_output();
+       Line = NewStrBuf();
+       TheConfig = NewStrBuf();
+       cprintf("%d send new netconfig now\n", SEND_LISTING);
+
+       while (rc = CtdlClientGetLine(Line), (rc >= 0)) {
+               if ((rc == 3) && (strcmp(ChrPtr(Line), "000") == 0))
+                       break;
+
+               StrBufAppendBuf(TheConfig, Line, 0);
+               StrBufAppendBufPlain(TheConfig, HKEY("\n"), 0);
+       }
+       FreeStrBuf(&Line);
+
+       begin_critical_section(S_NETCONFIGS);
+       SaveRoomNetConfigFile(CC->room.QRnumber, ChrPtr(TheConfig));
+       end_critical_section(S_NETCONFIGS);
+       FreeStrBuf(&TheConfig);
+}
+
+
+// Convert any legacy configuration files in the "netconfigs" directory
+void convert_legacy_netcfg_files(void) {
+       DIR *dh = NULL;
+       struct dirent *dit = NULL;
+       char filename[PATH_MAX];
+       long roomnum;
+       FILE *fp;
+       long len;
+       char *v;
+
+       dh = opendir(ctdl_netcfg_dir);
+       if (!dh) return;
+
+       syslog(LOG_INFO, "netconfig: legacy netconfig files exist - converting them!");
+
+       while (dit = readdir(dh), dit != NULL) {        // yes, we use the non-reentrant version; we're not in threaded mode yet
+               roomnum = atol(dit->d_name);
+               if (roomnum > 0) {
+                       snprintf(filename, sizeof filename, "%s/%ld", ctdl_netcfg_dir, roomnum);
+                       fp = fopen(filename, "r");
+                       if (fp) {
+                               fseek(fp, 0L, SEEK_END);
+                               len = ftell(fp);
+                               if (len > 0) {
+                                       v = malloc(len);
+                                       if (v) {
+                                               rewind(fp);
+                                               if (fread(v, len, 1, fp)) {
+                                                       SaveRoomNetConfigFile(roomnum, v);
+                                                       unlink(filename);
+                                               }
+                                               free(v);
+                                       }
+                               }
+                               else {
+                                       unlink(filename);       // zero length netconfig, just delete it
+                               }
+                               fclose(fp);
+                       }
+               }
+       }
+
+       closedir(dh);
+       rmdir(ctdl_netcfg_dir);
+}
+
+
+/*
+ * Module entry point
+ */
+char *ctdl_module_init_netconfig(void) {
+       if (!threading) {
+               convert_legacy_netcfg_files();
+               CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
+               CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
+       }
+       return "netconfig";
+}
diff --git a/citadel/server/parsedate.c b/citadel/server/parsedate.c
new file mode 100644 (file)
index 0000000..af4f055
--- /dev/null
@@ -0,0 +1,2379 @@
+/* A Bison parser, made by GNU Bison 3.0.4.  */
+
+/* Bison implementation for Yacc-like parsers in C
+
+   Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* As a special exception, you may create a larger work that contains
+   part or all of the Bison parser skeleton and distribute that work
+   under terms of your choice, so long as that work isn't itself a
+   parser generator using the skeleton or a modified version thereof
+   as a parser skeleton.  Alternatively, if you modify or redistribute
+   the parser skeleton itself, you may (at your option) remove this
+   special exception, which will cause the skeleton and the resulting
+   Bison output files to be licensed under the GNU General Public
+   License without this special exception.
+
+   This special exception was added by the Free Software Foundation in
+   version 2.2 of Bison.  */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+   simplifying the original so-called "semantic" parser.  */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+   infringing on user name space.  This should be done even for local
+   variables, as they might otherwise be expanded by user macros.
+   There are some unavoidable exceptions within include files to
+   define necessary library symbols; they are noted "INFRINGES ON
+   USER NAME SPACE" below.  */
+
+/* Identify Bison output.  */
+#define YYBISON 1
+
+/* Bison version.  */
+#define YYBISON_VERSION "3.0.4"
+
+/* Skeleton name.  */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers.  */
+#define YYPURE 0
+
+/* Push parsers.  */
+#define YYPUSH 0
+
+/* Pull parsers.  */
+#define YYPULL 1
+
+
+
+
+/* Copy the first part of user declarations.  */
+#line 1 "parsedate.y" /* yacc.c:339  */
+
+/* $Revision$
+**
+**  Originally written by Steven M. Bellovin <smb@research.att.com> while
+**  at the University of North Carolina at Chapel Hill.  Later tweaked by
+**  a couple of people on Usenet.  Completely overhauled by Rich $alz
+**  <rsalz@osf.org> and Jim Berets <jberets@bbn.com> in August, 1990.
+**  Further revised (removed obsolete constructs and cleaned up timezone
+**  names) in August, 1991, by Rich.  Paul Eggert <eggert@twinsun.com>
+**  helped in September, 1992.  Art Cancro <ajc@citadel.org> cleaned
+**  it up for ANSI C in December, 1999.
+**
+**  This grammar has six shift/reduce conflicts.
+**
+**  This code is in the public domain and has no copyright.
+*/
+/* SUPPRESS 530 *//* Empty body for statement */
+/* SUPPRESS 593 on yyerrlab *//* Label was not used */
+/* SUPPRESS 593 on yynewstate *//* Label was not used */
+/* SUPPRESS 595 on yypvt *//* Automatic variable may be used before set */
+
+#include "sysdep.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <ctype.h>
+#include <time.h>
+#if HAVE_STRING_H
+# if !STDC_HEADERS && HAVE_MEMORY_H
+#  include <memory.h>
+# endif
+# include <string.h>
+#endif
+#if HAVE_STRINGS_H
+# include <strings.h>
+#endif
+
+#include "parsedate.h"
+
+int date_lex(void);
+
+#define yyparse                date_parse
+#define yylex          date_lex
+#define yyerror                date_error
+
+
+    /* See the LeapYears table in Convert. */
+#define EPOCH          1970
+#define END_OF_TIME    2038
+    /* Constants for general time calculations. */
+#define DST_OFFSET     1
+#define SECSPERDAY     (24L * 60L * 60L)
+    /* Readability for TABLE stuff. */
+#define HOUR(x)                (x * 60)
+
+#define LPAREN         '('
+#define RPAREN         ')'
+#define IS7BIT(x)      ((unsigned int)(x) < 0200)
+
+#define SIZEOF(array)  ((int)(sizeof array / sizeof array[0]))
+#define ENDOF(array)   (&array[SIZEOF(array)])
+
+
+/*
+**  An entry in the lexical lookup table.
+*/
+typedef struct _TABLE {
+    char       *name;
+    int                type;
+    time_t     value;
+} TABLE;
+
+/*
+**  Daylight-savings mode:  on, off, or not yet known.
+*/
+typedef enum _DSTMODE {
+    DSTon, DSToff, DSTmaybe
+} DSTMODE;
+
+/*
+**  Meridian:  am, pm, or 24-hour style.
+*/
+typedef enum _MERIDIAN {
+    MERam, MERpm, MER24
+} MERIDIAN;
+
+
+/*
+**  Global variables.  We could get rid of most of them by using a yacc
+**  union, but this is more efficient.  (This routine predates the
+**  yacc %union construct.)
+*/
+static const char      *yyInput;
+static DSTMODE yyDSTmode;
+static int     yyHaveDate;
+static int     yyHaveRel;
+static int     yyHaveTime;
+static time_t  yyTimezone;
+static time_t  yyDay;
+static time_t  yyHour;
+static time_t  yyMinutes;
+static time_t  yyMonth;
+static time_t  yySeconds;
+static time_t  yyYear;
+static MERIDIAN        yyMeridian;
+static time_t  yyRelMonth;
+static time_t  yyRelSeconds;
+
+
+static void            date_error(char *);
+
+#line 179 "y.tab.c" /* yacc.c:339  */
+
+# ifndef YY_NULLPTR
+#  if defined __cplusplus && 201103L <= __cplusplus
+#   define YY_NULLPTR nullptr
+#  else
+#   define YY_NULLPTR 0
+#  endif
+# endif
+
+/* Enabling verbose error messages.  */
+#ifdef YYERROR_VERBOSE
+# undef YYERROR_VERBOSE
+# define YYERROR_VERBOSE 1
+#else
+# define YYERROR_VERBOSE 0
+#endif
+
+
+/* Debug traces.  */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Token type.  */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+  enum yytokentype
+  {
+    tDAY = 258,
+    tDAYZONE = 259,
+    tMERIDIAN = 260,
+    tMONTH = 261,
+    tMONTH_UNIT = 262,
+    tSEC_UNIT = 263,
+    tSNUMBER = 264,
+    tUNUMBER = 265,
+    tZONE = 266
+  };
+#endif
+/* Tokens.  */
+#define tDAY 258
+#define tDAYZONE 259
+#define tMERIDIAN 260
+#define tMONTH 261
+#define tMONTH_UNIT 262
+#define tSEC_UNIT 263
+#define tSNUMBER 264
+#define tUNUMBER 265
+#define tZONE 266
+
+/* Value type.  */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+
+union YYSTYPE
+{
+#line 114 "parsedate.y" /* yacc.c:355  */
+
+    time_t             Number;
+    enum _MERIDIAN     Meridian;
+
+#line 243 "y.tab.c" /* yacc.c:355  */
+};
+
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+extern YYSTYPE yylval;
+
+int yyparse (void);
+
+
+
+/* Copy the second part of user declarations.  */
+
+#line 260 "y.tab.c" /* yacc.c:358  */
+
+#ifdef short
+# undef short
+#endif
+
+#ifdef YYTYPE_UINT8
+typedef YYTYPE_UINT8 yytype_uint8;
+#else
+typedef unsigned char yytype_uint8;
+#endif
+
+#ifdef YYTYPE_INT8
+typedef YYTYPE_INT8 yytype_int8;
+#else
+typedef signed char yytype_int8;
+#endif
+
+#ifdef YYTYPE_UINT16
+typedef YYTYPE_UINT16 yytype_uint16;
+#else
+typedef unsigned short int yytype_uint16;
+#endif
+
+#ifdef YYTYPE_INT16
+typedef YYTYPE_INT16 yytype_int16;
+#else
+typedef short int yytype_int16;
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+#  define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+#  define YYSIZE_T size_t
+# elif ! defined YYSIZE_T
+#  include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+#  define YYSIZE_T size_t
+# else
+#  define YYSIZE_T unsigned int
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM ((YYSIZE_T) -1)
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+#  if ENABLE_NLS
+#   include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+#   define YY_(Msgid) dgettext ("bison-runtime", Msgid)
+#  endif
+# endif
+# ifndef YY_
+#  define YY_(Msgid) Msgid
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE
+# if (defined __GNUC__                                               \
+      && (2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__)))  \
+     || defined __SUNPRO_C && 0x5110 <= __SUNPRO_C
+#  define YY_ATTRIBUTE(Spec) __attribute__(Spec)
+# else
+#  define YY_ATTRIBUTE(Spec) /* empty */
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE_PURE
+# define YY_ATTRIBUTE_PURE   YY_ATTRIBUTE ((__pure__))
+#endif
+
+#ifndef YY_ATTRIBUTE_UNUSED
+# define YY_ATTRIBUTE_UNUSED YY_ATTRIBUTE ((__unused__))
+#endif
+
+#if !defined _Noreturn \
+     && (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112)
+# if defined _MSC_VER && 1200 <= _MSC_VER
+#  define _Noreturn __declspec (noreturn)
+# else
+#  define _Noreturn YY_ATTRIBUTE ((__noreturn__))
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E.  */
+#if ! defined lint || defined __GNUC__
+# define YYUSE(E) ((void) (E))
+#else
+# define YYUSE(E) /* empty */
+#endif
+
+#if defined __GNUC__ && 407 <= __GNUC__ * 100 + __GNUC_MINOR__
+/* Suppress an incorrect diagnostic about yylval being uninitialized.  */
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+    _Pragma ("GCC diagnostic push") \
+    _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\
+    _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+    _Pragma ("GCC diagnostic pop")
+#else
+# define YY_INITIAL_VALUE(Value) Value
+#endif
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+
+#if ! defined yyoverflow || YYERROR_VERBOSE
+
+/* The parser invokes alloca or malloc; define the necessary symbols.  */
+
+# ifdef YYSTACK_USE_ALLOCA
+#  if YYSTACK_USE_ALLOCA
+#   ifdef __GNUC__
+#    define YYSTACK_ALLOC __builtin_alloca
+#   elif defined __BUILTIN_VA_ARG_INCR
+#    include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+#   elif defined _AIX
+#    define YYSTACK_ALLOC __alloca
+#   elif defined _MSC_VER
+#    include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+#    define alloca _alloca
+#   else
+#    define YYSTACK_ALLOC alloca
+#    if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
+#     include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+      /* Use EXIT_SUCCESS as a witness for stdlib.h.  */
+#     ifndef EXIT_SUCCESS
+#      define EXIT_SUCCESS 0
+#     endif
+#    endif
+#   endif
+#  endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+   /* Pacify GCC's 'empty if-body' warning.  */
+#  define YYSTACK_FREE(Ptr) do { /* empty */; } while (0)
+#  ifndef YYSTACK_ALLOC_MAXIMUM
+    /* The OS might guarantee only one guard page at the bottom of the stack,
+       and a page size can be as small as 4096 bytes.  So we cannot safely
+       invoke alloca (N) if N exceeds 4096.  Use a slightly smaller number
+       to allow for a few compiler-allocated temporary stack slots.  */
+#   define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+#  endif
+# else
+#  define YYSTACK_ALLOC YYMALLOC
+#  define YYSTACK_FREE YYFREE
+#  ifndef YYSTACK_ALLOC_MAXIMUM
+#   define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+#  endif
+#  if (defined __cplusplus && ! defined EXIT_SUCCESS \
+       && ! ((defined YYMALLOC || defined malloc) \
+             && (defined YYFREE || defined free)))
+#   include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+#   ifndef EXIT_SUCCESS
+#    define EXIT_SUCCESS 0
+#   endif
+#  endif
+#  ifndef YYMALLOC
+#   define YYMALLOC malloc
+#   if ! defined malloc && ! defined EXIT_SUCCESS
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+#   endif
+#  endif
+#  ifndef YYFREE
+#   define YYFREE free
+#   if ! defined free && ! defined EXIT_SUCCESS
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+#   endif
+#  endif
+# endif
+#endif /* ! defined yyoverflow || YYERROR_VERBOSE */
+
+
+#if (! defined yyoverflow \
+     && (! defined __cplusplus \
+         || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member.  */
+union yyalloc
+{
+  yytype_int16 yyss_alloc;
+  YYSTYPE yyvs_alloc;
+};
+
+/* The size of the maximum gap between one aligned stack and the next.  */
+# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+   N elements.  */
+# define YYSTACK_BYTES(N) \
+     ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \
+      + YYSTACK_GAP_MAXIMUM)
+
+# define YYCOPY_NEEDED 1
+
+/* Relocate STACK from its old location to the new one.  The
+   local variables YYSIZE and YYSTACKSIZE give the old and new number of
+   elements in the stack, and YYPTR gives the new location of the
+   stack.  Advance YYPTR to a properly aligned location for the next
+   stack.  */
+# define YYSTACK_RELOCATE(Stack_alloc, Stack)                           \
+    do                                                                  \
+      {                                                                 \
+        YYSIZE_T yynewbytes;                                            \
+        YYCOPY (&yyptr->Stack_alloc, Stack, yysize);                    \
+        Stack = &yyptr->Stack_alloc;                                    \
+        yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \
+        yyptr += yynewbytes / sizeof (*yyptr);                          \
+      }                                                                 \
+    while (0)
+
+#endif
+
+#if defined YYCOPY_NEEDED && YYCOPY_NEEDED
+/* Copy COUNT objects from SRC to DST.  The source and destination do
+   not overlap.  */
+# ifndef YYCOPY
+#  if defined __GNUC__ && 1 < __GNUC__
+#   define YYCOPY(Dst, Src, Count) \
+      __builtin_memcpy (Dst, Src, (Count) * sizeof (*(Src)))
+#  else
+#   define YYCOPY(Dst, Src, Count)              \
+      do                                        \
+        {                                       \
+          YYSIZE_T yyi;                         \
+          for (yyi = 0; yyi < (Count); yyi++)   \
+            (Dst)[yyi] = (Src)[yyi];            \
+        }                                       \
+      while (0)
+#  endif
+# endif
+#endif /* !YYCOPY_NEEDED */
+
+/* YYFINAL -- State number of the termination state.  */
+#define YYFINAL  2
+/* YYLAST -- Last index in YYTABLE.  */
+#define YYLAST   40
+
+/* YYNTOKENS -- Number of terminals.  */
+#define YYNTOKENS  15
+/* YYNNTS -- Number of nonterminals.  */
+#define YYNNTS  9
+/* YYNRULES -- Number of rules.  */
+#define YYNRULES  30
+/* YYNSTATES -- Number of states.  */
+#define YYNSTATES  44
+
+/* YYTRANSLATE[YYX] -- Symbol number corresponding to YYX as returned
+   by yylex, with out-of-bounds checking.  */
+#define YYUNDEFTOK  2
+#define YYMAXUTOK   266
+
+#define YYTRANSLATE(YYX)                                                \
+  ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK)
+
+/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM
+   as returned by yylex, without out-of-bounds checking.  */
+static const yytype_uint8 yytranslate[] =
+{
+       0,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,    14,     2,     2,    13,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,    12,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     1,     2,     3,     4,
+       5,     6,     7,     8,     9,    10,    11
+};
+
+#if YYDEBUG
+  /* YYRLINE[YYN] -- Source line where rule number YYN was defined.  */
+static const yytype_uint16 yyrline[] =
+{
+       0,   128,   128,   129,   132,   141,   145,   148,   153,   165,
+     171,   178,   184,   194,   198,   202,   210,   216,   237,   241,
+     253,   257,   262,   266,   271,   278,   281,   284,   287,   292,
+     295
+};
+#endif
+
+#if YYDEBUG || YYERROR_VERBOSE || 0
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+   First, the terminals, then, starting at YYNTOKENS, nonterminals.  */
+static const char *const yytname[] =
+{
+  "$end", "error", "$undefined", "tDAY", "tDAYZONE", "tMERIDIAN",
+  "tMONTH", "tMONTH_UNIT", "tSEC_UNIT", "tSNUMBER", "tUNUMBER", "tZONE",
+  "':'", "'/'", "','", "$accept", "spec", "item", "time", "zone",
+  "numzone", "date", "rel", "o_merid", YY_NULLPTR
+};
+#endif
+
+# ifdef YYPRINT
+/* YYTOKNUM[NUM] -- (External) token number corresponding to the
+   (internal) symbol number NUM (which must be that of a token).  */
+static const yytype_uint16 yytoknum[] =
+{
+       0,   256,   257,   258,   259,   260,   261,   262,   263,   264,
+     265,   266,    58,    47,    44
+};
+# endif
+
+#define YYPACT_NINF -29
+
+#define yypact_value_is_default(Yystate) \
+  (!!((Yystate) == (-29)))
+
+#define YYTABLE_NINF -1
+
+#define yytable_value_is_error(Yytable_value) \
+  0
+
+  /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+     STATE-NUM.  */
+static const yytype_int8 yypact[] =
+{
+     -29,     1,   -29,   -11,    11,    20,    12,   -29,     4,   -29,
+     -29,    13,    16,   -29,   -29,   -29,    21,   -29,   -29,    22,
+      23,   -29,   -29,   -29,     5,   -29,   -29,    28,    25,   -29,
+      17,    24,   -29,    26,   -29,    29,   -29,   -29,    30,   -29,
+       0,   -29,   -29,   -29
+};
+
+  /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
+     Performed when YYTABLE does not specify something else to do.  Zero
+     means the default is an error.  */
+static const yytype_uint8 yydefact[] =
+{
+       2,     0,     1,     0,     0,     0,    29,     3,     4,     6,
+       7,     0,    20,    27,    25,    30,    22,    28,    26,     0,
+       0,     8,    14,    17,    13,     5,    16,     0,     0,    23,
+      29,    18,    15,     0,    21,     0,    10,     9,     0,    24,
+      29,    19,    12,    11
+};
+
+  /* YYPGOTO[NTERM-NUM].  */
+static const yytype_int8 yypgoto[] =
+{
+     -29,   -29,   -29,   -29,   -29,   -24,   -29,   -29,   -28
+};
+
+  /* YYDEFGOTO[NTERM-NUM].  */
+static const yytype_int8 yydefgoto[] =
+{
+      -1,     1,     7,     8,    25,    26,     9,    10,    21
+};
+
+  /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM.  If
+     positive, shift that token.  If negative, reduce the rule whose
+     number is the opposite.  If YYTABLE_NINF, syntax error.  */
+static const yytype_uint8 yytable[] =
+{
+      32,     2,    37,    11,     3,    15,    36,     4,    22,    23,
+       5,     6,    43,    23,    23,    24,    42,    15,    16,    17,
+      18,    12,    15,    27,    19,    20,    23,    13,    14,    35,
+      28,    29,    30,    31,    33,    34,    39,    38,     0,    40,
+      41
+};
+
+static const yytype_int8 yycheck[] =
+{
+      24,     0,    30,    14,     3,     5,    30,     6,     4,     9,
+       9,    10,    40,     9,     9,    11,    40,     5,     6,     7,
+       8,    10,     5,    10,    12,    13,     9,     7,     8,    12,
+      14,    10,    10,    10,     6,    10,    10,    13,    -1,    10,
+      10
+};
+
+  /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
+     symbol of state STATE-NUM.  */
+static const yytype_uint8 yystos[] =
+{
+       0,    16,     0,     3,     6,     9,    10,    17,    18,    21,
+      22,    14,    10,     7,     8,     5,     6,     7,     8,    12,
+      13,    23,     4,     9,    11,    19,    20,    10,    14,    10,
+      10,    10,    20,     6,    10,    12,    20,    23,    13,    10,
+      10,    10,    20,    23
+};
+
+  /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives.  */
+static const yytype_uint8 yyr1[] =
+{
+       0,    15,    16,    16,    17,    17,    17,    17,    18,    18,
+      18,    18,    18,    19,    19,    19,    19,    20,    21,    21,
+      21,    21,    21,    21,    21,    22,    22,    22,    22,    23,
+      23
+};
+
+  /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN.  */
+static const yytype_uint8 yyr2[] =
+{
+       0,     2,     0,     2,     1,     2,     1,     1,     2,     4,
+       4,     6,     6,     1,     1,     2,     1,     1,     3,     5,
+       2,     4,     2,     3,     5,     2,     2,     2,     2,     0,
+       1
+};
+
+
+#define yyerrok         (yyerrstatus = 0)
+#define yyclearin       (yychar = YYEMPTY)
+#define YYEMPTY         (-2)
+#define YYEOF           0
+
+#define YYACCEPT        goto yyacceptlab
+#define YYABORT         goto yyabortlab
+#define YYERROR         goto yyerrorlab
+
+
+#define YYRECOVERING()  (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value)                                  \
+do                                                              \
+  if (yychar == YYEMPTY)                                        \
+    {                                                           \
+      yychar = (Token);                                         \
+      yylval = (Value);                                         \
+      YYPOPSTACK (yylen);                                       \
+      yystate = *yyssp;                                         \
+      goto yybackup;                                            \
+    }                                                           \
+  else                                                          \
+    {                                                           \
+      yyerror (YY_("syntax error: cannot back up")); \
+      YYERROR;                                                  \
+    }                                                           \
+while (0)
+
+/* Error token number */
+#define YYTERROR        1
+#define YYERRCODE       256
+
+
+
+/* Enable debugging if requested.  */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+#  include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+#  define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args)                        \
+do {                                            \
+  if (yydebug)                                  \
+    YYFPRINTF Args;                             \
+} while (0)
+
+/* This macro is provided for backward compatibility. */
+#ifndef YY_LOCATION_PRINT
+# define YY_LOCATION_PRINT(File, Loc) ((void) 0)
+#endif
+
+
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location)                    \
+do {                                                                      \
+  if (yydebug)                                                            \
+    {                                                                     \
+      YYFPRINTF (stderr, "%s ", Title);                                   \
+      yy_symbol_print (stderr,                                            \
+                  Type, Value); \
+      YYFPRINTF (stderr, "\n");                                           \
+    }                                                                     \
+} while (0)
+
+
+/*----------------------------------------.
+| Print this symbol's value on YYOUTPUT.  |
+`----------------------------------------*/
+
+static void
+yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep)
+{
+  FILE *yyo = yyoutput;
+  YYUSE (yyo);
+  if (!yyvaluep)
+    return;
+# ifdef YYPRINT
+  if (yytype < YYNTOKENS)
+    YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep);
+# endif
+  YYUSE (yytype);
+}
+
+
+/*--------------------------------.
+| Print this symbol on YYOUTPUT.  |
+`--------------------------------*/
+
+static void
+yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep)
+{
+  YYFPRINTF (yyoutput, "%s %s (",
+             yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]);
+
+  yy_symbol_value_print (yyoutput, yytype, yyvaluep);
+  YYFPRINTF (yyoutput, ")");
+}
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included).                                                   |
+`------------------------------------------------------------------*/
+
+static void
+yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop)
+{
+  YYFPRINTF (stderr, "Stack now");
+  for (; yybottom <= yytop; yybottom++)
+    {
+      int yybot = *yybottom;
+      YYFPRINTF (stderr, " %d", yybot);
+    }
+  YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top)                            \
+do {                                                            \
+  if (yydebug)                                                  \
+    yy_stack_print ((Bottom), (Top));                           \
+} while (0)
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced.  |
+`------------------------------------------------*/
+
+static void
+yy_reduce_print (yytype_int16 *yyssp, YYSTYPE *yyvsp, int yyrule)
+{
+  unsigned long int yylno = yyrline[yyrule];
+  int yynrhs = yyr2[yyrule];
+  int yyi;
+  YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n",
+             yyrule - 1, yylno);
+  /* The symbols being reduced.  */
+  for (yyi = 0; yyi < yynrhs; yyi++)
+    {
+      YYFPRINTF (stderr, "   $%d = ", yyi + 1);
+      yy_symbol_print (stderr,
+                       yystos[yyssp[yyi + 1 - yynrhs]],
+                       &(yyvsp[(yyi + 1) - (yynrhs)])
+                                              );
+      YYFPRINTF (stderr, "\n");
+    }
+}
+
+# define YY_REDUCE_PRINT(Rule)          \
+do {                                    \
+  if (yydebug)                          \
+    yy_reduce_print (yyssp, yyvsp, Rule); \
+} while (0)
+
+/* Nonzero means print parse trace.  It is left uninitialized so that
+   multiple parsers can coexist.  */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args)
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks.  */
+#ifndef YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+   if the built-in stack extension method is used).
+
+   Do not make this value too large; the results are undefined if
+   YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+   evaluated with infinite-precision integer arithmetic.  */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+
+#if YYERROR_VERBOSE
+
+# ifndef yystrlen
+#  if defined __GLIBC__ && defined _STRING_H
+#   define yystrlen strlen
+#  else
+/* Return the length of YYSTR.  */
+static YYSIZE_T
+yystrlen (const char *yystr)
+{
+  YYSIZE_T yylen;
+  for (yylen = 0; yystr[yylen]; yylen++)
+    continue;
+  return yylen;
+}
+#  endif
+# endif
+
+# ifndef yystpcpy
+#  if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE
+#   define yystpcpy stpcpy
+#  else
+/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in
+   YYDEST.  */
+static char *
+yystpcpy (char *yydest, const char *yysrc)
+{
+  char *yyd = yydest;
+  const char *yys = yysrc;
+
+  while ((*yyd++ = *yys++) != '\0')
+    continue;
+
+  return yyd - 1;
+}
+#  endif
+# endif
+
+# ifndef yytnamerr
+/* Copy to YYRES the contents of YYSTR after stripping away unnecessary
+   quotes and backslashes, so that it's suitable for yyerror.  The
+   heuristic is that double-quoting is unnecessary unless the string
+   contains an apostrophe, a comma, or backslash (other than
+   backslash-backslash).  YYSTR is taken from yytname.  If YYRES is
+   null, do not copy; instead, return the length of what the result
+   would have been.  */
+static YYSIZE_T
+yytnamerr (char *yyres, const char *yystr)
+{
+  if (*yystr == '"')
+    {
+      YYSIZE_T yyn = 0;
+      char const *yyp = yystr;
+
+      for (;;)
+        switch (*++yyp)
+          {
+          case '\'':
+          case ',':
+            goto do_not_strip_quotes;
+
+          case '\\':
+            if (*++yyp != '\\')
+              goto do_not_strip_quotes;
+            /* Fall through.  */
+          default:
+            if (yyres)
+              yyres[yyn] = *yyp;
+            yyn++;
+            break;
+
+          case '"':
+            if (yyres)
+              yyres[yyn] = '\0';
+            return yyn;
+          }
+    do_not_strip_quotes: ;
+    }
+
+  if (! yyres)
+    return yystrlen (yystr);
+
+  return yystpcpy (yyres, yystr) - yyres;
+}
+# endif
+
+/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message
+   about the unexpected token YYTOKEN for the state stack whose top is
+   YYSSP.
+
+   Return 0 if *YYMSG was successfully written.  Return 1 if *YYMSG is
+   not large enough to hold the message.  In that case, also set
+   *YYMSG_ALLOC to the required number of bytes.  Return 2 if the
+   required number of bytes is too large to store.  */
+static int
+yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg,
+                yytype_int16 *yyssp, int yytoken)
+{
+  YYSIZE_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]);
+  YYSIZE_T yysize = yysize0;
+  enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 };
+  /* Internationalized format string. */
+  const char *yyformat = YY_NULLPTR;
+  /* Arguments of yyformat. */
+  char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM];
+  /* Number of reported tokens (one for the "unexpected", one per
+     "expected"). */
+  int yycount = 0;
+
+  /* There are many possibilities here to consider:
+     - If this state is a consistent state with a default action, then
+       the only way this function was invoked is if the default action
+       is an error action.  In that case, don't check for expected
+       tokens because there are none.
+     - The only way there can be no lookahead present (in yychar) is if
+       this state is a consistent state with a default action.  Thus,
+       detecting the absence of a lookahead is sufficient to determine
+       that there is no unexpected or expected token to report.  In that
+       case, just report a simple "syntax error".
+     - Don't assume there isn't a lookahead just because this state is a
+       consistent state with a default action.  There might have been a
+       previous inconsistent state, consistent state with a non-default
+       action, or user semantic action that manipulated yychar.
+     - Of course, the expected token list depends on states to have
+       correct lookahead information, and it depends on the parser not
+       to perform extra reductions after fetching a lookahead from the
+       scanner and before detecting a syntax error.  Thus, state merging
+       (from LALR or IELR) and default reductions corrupt the expected
+       token list.  However, the list is correct for canonical LR with
+       one exception: it will still contain any token that will not be
+       accepted due to an error action in a later state.
+  */
+  if (yytoken != YYEMPTY)
+    {
+      int yyn = yypact[*yyssp];
+      yyarg[yycount++] = yytname[yytoken];
+      if (!yypact_value_is_default (yyn))
+        {
+          /* Start YYX at -YYN if negative to avoid negative indexes in
+             YYCHECK.  In other words, skip the first -YYN actions for
+             this state because they are default actions.  */
+          int yyxbegin = yyn < 0 ? -yyn : 0;
+          /* Stay within bounds of both yycheck and yytname.  */
+          int yychecklim = YYLAST - yyn + 1;
+          int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
+          int yyx;
+
+          for (yyx = yyxbegin; yyx < yyxend; ++yyx)
+            if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR
+                && !yytable_value_is_error (yytable[yyx + yyn]))
+              {
+                if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM)
+                  {
+                    yycount = 1;
+                    yysize = yysize0;
+                    break;
+                  }
+                yyarg[yycount++] = yytname[yyx];
+                {
+                  YYSIZE_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]);
+                  if (! (yysize <= yysize1
+                         && yysize1 <= YYSTACK_ALLOC_MAXIMUM))
+                    return 2;
+                  yysize = yysize1;
+                }
+              }
+        }
+    }
+
+  switch (yycount)
+    {
+# define YYCASE_(N, S)                      \
+      case N:                               \
+        yyformat = S;                       \
+      break
+      YYCASE_(0, YY_("syntax error"));
+      YYCASE_(1, YY_("syntax error, unexpected %s"));
+      YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s"));
+      YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s"));
+      YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s"));
+      YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"));
+# undef YYCASE_
+    }
+
+  {
+    YYSIZE_T yysize1 = yysize + yystrlen (yyformat);
+    if (! (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM))
+      return 2;
+    yysize = yysize1;
+  }
+
+  if (*yymsg_alloc < yysize)
+    {
+      *yymsg_alloc = 2 * yysize;
+      if (! (yysize <= *yymsg_alloc
+             && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM))
+        *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM;
+      return 1;
+    }
+
+  /* Avoid sprintf, as that infringes on the user's name space.
+     Don't have undefined behavior even if the translation
+     produced a string with the wrong number of "%s"s.  */
+  {
+    char *yyp = *yymsg;
+    int yyi = 0;
+    while ((*yyp = *yyformat) != '\0')
+      if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount)
+        {
+          yyp += yytnamerr (yyp, yyarg[yyi++]);
+          yyformat += 2;
+        }
+      else
+        {
+          yyp++;
+          yyformat++;
+        }
+  }
+  return 0;
+}
+#endif /* YYERROR_VERBOSE */
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol.  |
+`-----------------------------------------------*/
+
+static void
+yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep)
+{
+  YYUSE (yyvaluep);
+  if (!yymsg)
+    yymsg = "Deleting";
+  YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp);
+
+  YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+  YYUSE (yytype);
+  YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+
+
+/* The lookahead symbol.  */
+int yychar;
+
+/* The semantic value of the lookahead symbol.  */
+YYSTYPE yylval;
+/* Number of syntax errors so far.  */
+int yynerrs;
+
+
+/*----------.
+| yyparse.  |
+`----------*/
+
+int
+yyparse (void)
+{
+    int yystate;
+    /* Number of tokens to shift before error messages enabled.  */
+    int yyerrstatus;
+
+    /* The stacks and their tools:
+       'yyss': related to states.
+       'yyvs': related to semantic values.
+
+       Refer to the stacks through separate pointers, to allow yyoverflow
+       to reallocate them elsewhere.  */
+
+    /* The state stack.  */
+    yytype_int16 yyssa[YYINITDEPTH];
+    yytype_int16 *yyss;
+    yytype_int16 *yyssp;
+
+    /* The semantic value stack.  */
+    YYSTYPE yyvsa[YYINITDEPTH];
+    YYSTYPE *yyvs;
+    YYSTYPE *yyvsp;
+
+    YYSIZE_T yystacksize;
+
+  int yyn;
+  int yyresult;
+  /* Lookahead token as an internal (translated) token number.  */
+  int yytoken = 0;
+  /* The variables used to return semantic value and location from the
+     action routines.  */
+  YYSTYPE yyval;
+
+#if YYERROR_VERBOSE
+  /* Buffer for error messages, and its allocated size.  */
+  char yymsgbuf[128];
+  char *yymsg = yymsgbuf;
+  YYSIZE_T yymsg_alloc = sizeof yymsgbuf;
+#endif
+
+#define YYPOPSTACK(N)   (yyvsp -= (N), yyssp -= (N))
+
+  /* The number of symbols on the RHS of the reduced rule.
+     Keep to zero when no symbol should be popped.  */
+  int yylen = 0;
+
+  yyssp = yyss = yyssa;
+  yyvsp = yyvs = yyvsa;
+  yystacksize = YYINITDEPTH;
+
+  YYDPRINTF ((stderr, "Starting parse\n"));
+
+  yystate = 0;
+  yyerrstatus = 0;
+  yynerrs = 0;
+  yychar = YYEMPTY; /* Cause a token to be read.  */
+  goto yysetstate;
+
+/*------------------------------------------------------------.
+| yynewstate -- Push a new state, which is found in yystate.  |
+`------------------------------------------------------------*/
+ yynewstate:
+  /* In all cases, when you get here, the value and location stacks
+     have just been pushed.  So pushing a state here evens the stacks.  */
+  yyssp++;
+
+ yysetstate:
+  *yyssp = yystate;
+
+  if (yyss + yystacksize - 1 <= yyssp)
+    {
+      /* Get the current used size of the three stacks, in elements.  */
+      YYSIZE_T yysize = yyssp - yyss + 1;
+
+#ifdef yyoverflow
+      {
+        /* Give user a chance to reallocate the stack.  Use copies of
+           these so that the &'s don't force the real ones into
+           memory.  */
+        YYSTYPE *yyvs1 = yyvs;
+        yytype_int16 *yyss1 = yyss;
+
+        /* Each stack pointer address is followed by the size of the
+           data in use in that stack, in bytes.  This used to be a
+           conditional around just the two extra args, but that might
+           be undefined if yyoverflow is a macro.  */
+        yyoverflow (YY_("memory exhausted"),
+                    &yyss1, yysize * sizeof (*yyssp),
+                    &yyvs1, yysize * sizeof (*yyvsp),
+                    &yystacksize);
+
+        yyss = yyss1;
+        yyvs = yyvs1;
+      }
+#else /* no yyoverflow */
+# ifndef YYSTACK_RELOCATE
+      goto yyexhaustedlab;
+# else
+      /* Extend the stack our own way.  */
+      if (YYMAXDEPTH <= yystacksize)
+        goto yyexhaustedlab;
+      yystacksize *= 2;
+      if (YYMAXDEPTH < yystacksize)
+        yystacksize = YYMAXDEPTH;
+
+      {
+        yytype_int16 *yyss1 = yyss;
+        union yyalloc *yyptr =
+          (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize));
+        if (! yyptr)
+          goto yyexhaustedlab;
+        YYSTACK_RELOCATE (yyss_alloc, yyss);
+        YYSTACK_RELOCATE (yyvs_alloc, yyvs);
+#  undef YYSTACK_RELOCATE
+        if (yyss1 != yyssa)
+          YYSTACK_FREE (yyss1);
+      }
+# endif
+#endif /* no yyoverflow */
+
+      yyssp = yyss + yysize - 1;
+      yyvsp = yyvs + yysize - 1;
+
+      YYDPRINTF ((stderr, "Stack size increased to %lu\n",
+                  (unsigned long int) yystacksize));
+
+      if (yyss + yystacksize - 1 <= yyssp)
+        YYABORT;
+    }
+
+  YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+
+  if (yystate == YYFINAL)
+    YYACCEPT;
+
+  goto yybackup;
+
+/*-----------.
+| yybackup.  |
+`-----------*/
+yybackup:
+
+  /* Do appropriate processing given the current state.  Read a
+     lookahead token if we need one and don't already have one.  */
+
+  /* First try to decide what to do without reference to lookahead token.  */
+  yyn = yypact[yystate];
+  if (yypact_value_is_default (yyn))
+    goto yydefault;
+
+  /* Not known => get a lookahead token if don't already have one.  */
+
+  /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol.  */
+  if (yychar == YYEMPTY)
+    {
+      YYDPRINTF ((stderr, "Reading a token: "));
+      yychar = yylex ();
+    }
+
+  if (yychar <= YYEOF)
+    {
+      yychar = yytoken = YYEOF;
+      YYDPRINTF ((stderr, "Now at end of input.\n"));
+    }
+  else
+    {
+      yytoken = YYTRANSLATE (yychar);
+      YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+    }
+
+  /* If the proper action on seeing token YYTOKEN is to reduce or to
+     detect an error, take that action.  */
+  yyn += yytoken;
+  if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+    goto yydefault;
+  yyn = yytable[yyn];
+  if (yyn <= 0)
+    {
+      if (yytable_value_is_error (yyn))
+        goto yyerrlab;
+      yyn = -yyn;
+      goto yyreduce;
+    }
+
+  /* Count tokens shifted since error; after three, turn off error
+     status.  */
+  if (yyerrstatus)
+    yyerrstatus--;
+
+  /* Shift the lookahead token.  */
+  YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+
+  /* Discard the shifted token.  */
+  yychar = YYEMPTY;
+
+  yystate = yyn;
+  YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+  *++yyvsp = yylval;
+  YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+  goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state.  |
+`-----------------------------------------------------------*/
+yydefault:
+  yyn = yydefact[yystate];
+  if (yyn == 0)
+    goto yyerrlab;
+  goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- Do a reduction.  |
+`-----------------------------*/
+yyreduce:
+  /* yyn is the number of a rule to reduce with.  */
+  yylen = yyr2[yyn];
+
+  /* If YYLEN is nonzero, implement the default value of the action:
+     '$$ = $1'.
+
+     Otherwise, the following line sets YYVAL to garbage.
+     This behavior is undocumented and Bison
+     users should not rely upon it.  Assigning to YYVAL
+     unconditionally makes the parser a bit smaller, and it avoids a
+     GCC warning that YYVAL may be used uninitialized.  */
+  yyval = yyvsp[1-yylen];
+
+
+  YY_REDUCE_PRINT (yyn);
+  switch (yyn)
+    {
+        case 4:
+#line 132 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyHaveTime++;
+#ifdef lint
+           /* I am compulsive about lint natterings... */
+           if (yyHaveTime == -1) {
+               YYERROR;
+           }
+#endif /* lint */
+       }
+#line 1366 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 5:
+#line 141 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyHaveTime++;
+           yyTimezone = (yyvsp[0].Number);
+       }
+#line 1375 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 6:
+#line 145 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyHaveDate++;
+       }
+#line 1383 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 7:
+#line 148 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyHaveRel = 1;
+       }
+#line 1391 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 8:
+#line 153 "parsedate.y" /* yacc.c:1646  */
+    {
+           if ((yyvsp[-1].Number) < 100) {
+               yyHour = (yyvsp[-1].Number);
+               yyMinutes = 0;
+           }
+           else {
+               yyHour = (yyvsp[-1].Number) / 100;
+               yyMinutes = (yyvsp[-1].Number) % 100;
+           }
+           yySeconds = 0;
+           yyMeridian = (yyvsp[0].Meridian);
+       }
+#line 1408 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 9:
+#line 165 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyHour = (yyvsp[-3].Number);
+           yyMinutes = (yyvsp[-1].Number);
+           yySeconds = 0;
+           yyMeridian = (yyvsp[0].Meridian);
+       }
+#line 1419 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 10:
+#line 171 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyHour = (yyvsp[-3].Number);
+           yyMinutes = (yyvsp[-1].Number);
+           yyTimezone = (yyvsp[0].Number);
+           yyMeridian = MER24;
+           yyDSTmode = DSToff;
+       }
+#line 1431 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 11:
+#line 178 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyHour = (yyvsp[-5].Number);
+           yyMinutes = (yyvsp[-3].Number);
+           yySeconds = (yyvsp[-1].Number);
+           yyMeridian = (yyvsp[0].Meridian);
+       }
+#line 1442 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 12:
+#line 184 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyHour = (yyvsp[-5].Number);
+           yyMinutes = (yyvsp[-3].Number);
+           yySeconds = (yyvsp[-1].Number);
+           yyTimezone = (yyvsp[0].Number);
+           yyMeridian = MER24;
+           yyDSTmode = DSToff;
+       }
+#line 1455 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 13:
+#line 194 "parsedate.y" /* yacc.c:1646  */
+    {
+           (yyval.Number) = (yyvsp[0].Number);
+           yyDSTmode = DSToff;
+       }
+#line 1464 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 14:
+#line 198 "parsedate.y" /* yacc.c:1646  */
+    {
+           (yyval.Number) = (yyvsp[0].Number);
+           yyDSTmode = DSTon;
+       }
+#line 1473 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 15:
+#line 202 "parsedate.y" /* yacc.c:1646  */
+    {
+           /* Only allow "GMT+300" and "GMT-0800" */
+           if ((yyvsp[-1].Number) != 0) {
+               YYABORT;
+           }
+           (yyval.Number) = (yyvsp[0].Number);
+           yyDSTmode = DSToff;
+       }
+#line 1486 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 16:
+#line 210 "parsedate.y" /* yacc.c:1646  */
+    {
+           (yyval.Number) = (yyvsp[0].Number);
+           yyDSTmode = DSToff;
+       }
+#line 1495 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 17:
+#line 216 "parsedate.y" /* yacc.c:1646  */
+    {
+           int         i;
+
+           /* Unix and GMT and numeric timezones -- a little confusing. */
+           if ((yyvsp[0].Number) < 0) {
+               /* Don't work with negative modulus. */
+               (yyvsp[0].Number) = -(yyvsp[0].Number);
+               if ((yyvsp[0].Number) > 9999 || (i = (yyvsp[0].Number) % 100) >= 60) {
+                   YYABORT;
+               }
+               (yyval.Number) = ((yyvsp[0].Number) / 100) * 60 + i;
+           }
+           else {
+               if ((yyvsp[0].Number) > 9999 || (i = (yyvsp[0].Number) % 100) >= 60) {
+                   YYABORT;
+               }
+               (yyval.Number) = -(((yyvsp[0].Number) / 100) * 60 + i);
+           }
+       }
+#line 1519 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 18:
+#line 237 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyMonth = (yyvsp[-2].Number);
+           yyDay = (yyvsp[0].Number);
+       }
+#line 1528 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 19:
+#line 241 "parsedate.y" /* yacc.c:1646  */
+    {
+           if ((yyvsp[-4].Number) > 100) {
+               yyYear = (yyvsp[-4].Number);
+               yyMonth = (yyvsp[-2].Number);
+               yyDay = (yyvsp[0].Number);
+           }
+           else {
+               yyMonth = (yyvsp[-4].Number);
+               yyDay = (yyvsp[-2].Number);
+               yyYear = (yyvsp[0].Number);
+           }
+       }
+#line 1545 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 20:
+#line 253 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyMonth = (yyvsp[-1].Number);
+           yyDay = (yyvsp[0].Number);
+       }
+#line 1554 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 21:
+#line 257 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyMonth = (yyvsp[-3].Number);
+           yyDay = (yyvsp[-2].Number);
+           yyYear = (yyvsp[0].Number);
+       }
+#line 1564 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 22:
+#line 262 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyDay = (yyvsp[-1].Number);
+           yyMonth = (yyvsp[0].Number);
+       }
+#line 1573 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 23:
+#line 266 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyDay = (yyvsp[-2].Number);
+           yyMonth = (yyvsp[-1].Number);
+           yyYear = (yyvsp[0].Number);
+       }
+#line 1583 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 24:
+#line 271 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyDay = (yyvsp[-2].Number);
+           yyMonth = (yyvsp[-1].Number);
+           yyYear = (yyvsp[0].Number);
+       }
+#line 1593 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 25:
+#line 278 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyRelSeconds += (yyvsp[-1].Number) * (yyvsp[0].Number);
+       }
+#line 1601 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 26:
+#line 281 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyRelSeconds += (yyvsp[-1].Number) * (yyvsp[0].Number);
+       }
+#line 1609 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 27:
+#line 284 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyRelMonth += (yyvsp[-1].Number) * (yyvsp[0].Number);
+       }
+#line 1617 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 28:
+#line 287 "parsedate.y" /* yacc.c:1646  */
+    {
+           yyRelMonth += (yyvsp[-1].Number) * (yyvsp[0].Number);
+       }
+#line 1625 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 29:
+#line 292 "parsedate.y" /* yacc.c:1646  */
+    {
+           (yyval.Meridian) = MER24;
+       }
+#line 1633 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+  case 30:
+#line 295 "parsedate.y" /* yacc.c:1646  */
+    {
+           (yyval.Meridian) = (yyvsp[0].Meridian);
+       }
+#line 1641 "y.tab.c" /* yacc.c:1646  */
+    break;
+
+
+#line 1645 "y.tab.c" /* yacc.c:1646  */
+      default: break;
+    }
+  /* User semantic actions sometimes alter yychar, and that requires
+     that yytoken be updated with the new translation.  We take the
+     approach of translating immediately before every use of yytoken.
+     One alternative is translating here after every semantic action,
+     but that translation would be missed if the semantic action invokes
+     YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or
+     if it invokes YYBACKUP.  In the case of YYABORT or YYACCEPT, an
+     incorrect destructor might then be invoked immediately.  In the
+     case of YYERROR or YYBACKUP, subsequent parser actions might lead
+     to an incorrect destructor call or verbose syntax error message
+     before the lookahead is translated.  */
+  YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
+
+  YYPOPSTACK (yylen);
+  yylen = 0;
+  YY_STACK_PRINT (yyss, yyssp);
+
+  *++yyvsp = yyval;
+
+  /* Now 'shift' the result of the reduction.  Determine what state
+     that goes to, based on the state we popped back to and the rule
+     number reduced by.  */
+
+  yyn = yyr1[yyn];
+
+  yystate = yypgoto[yyn - YYNTOKENS] + *yyssp;
+  if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp)
+    yystate = yytable[yystate];
+  else
+    yystate = yydefgoto[yyn - YYNTOKENS];
+
+  goto yynewstate;
+
+
+/*--------------------------------------.
+| yyerrlab -- here on detecting error.  |
+`--------------------------------------*/
+yyerrlab:
+  /* Make sure we have latest lookahead translation.  See comments at
+     user semantic actions for why this is necessary.  */
+  yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar);
+
+  /* If not already recovering from an error, report this error.  */
+  if (!yyerrstatus)
+    {
+      ++yynerrs;
+#if ! YYERROR_VERBOSE
+      yyerror (YY_("syntax error"));
+#else
+# define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \
+                                        yyssp, yytoken)
+      {
+        char const *yymsgp = YY_("syntax error");
+        int yysyntax_error_status;
+        yysyntax_error_status = YYSYNTAX_ERROR;
+        if (yysyntax_error_status == 0)
+          yymsgp = yymsg;
+        else if (yysyntax_error_status == 1)
+          {
+            if (yymsg != yymsgbuf)
+              YYSTACK_FREE (yymsg);
+            yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc);
+            if (!yymsg)
+              {
+                yymsg = yymsgbuf;
+                yymsg_alloc = sizeof yymsgbuf;
+                yysyntax_error_status = 2;
+              }
+            else
+              {
+                yysyntax_error_status = YYSYNTAX_ERROR;
+                yymsgp = yymsg;
+              }
+          }
+        yyerror (yymsgp);
+        if (yysyntax_error_status == 2)
+          goto yyexhaustedlab;
+      }
+# undef YYSYNTAX_ERROR
+#endif
+    }
+
+
+
+  if (yyerrstatus == 3)
+    {
+      /* If just tried and failed to reuse lookahead token after an
+         error, discard it.  */
+
+      if (yychar <= YYEOF)
+        {
+          /* Return failure if at end of input.  */
+          if (yychar == YYEOF)
+            YYABORT;
+        }
+      else
+        {
+          yydestruct ("Error: discarding",
+                      yytoken, &yylval);
+          yychar = YYEMPTY;
+        }
+    }
+
+  /* Else will try to reuse lookahead token after shifting the error
+     token.  */
+  goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR.  |
+`---------------------------------------------------*/
+yyerrorlab:
+
+  /* Pacify compilers like GCC when the user code never invokes
+     YYERROR and the label yyerrorlab therefore never appears in user
+     code.  */
+  if (/*CONSTCOND*/ 0)
+     goto yyerrorlab;
+
+  /* Do not reclaim the symbols of the rule whose action triggered
+     this YYERROR.  */
+  YYPOPSTACK (yylen);
+  yylen = 0;
+  YY_STACK_PRINT (yyss, yyssp);
+  yystate = *yyssp;
+  goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR.  |
+`-------------------------------------------------------------*/
+yyerrlab1:
+  yyerrstatus = 3;      /* Each real token shifted decrements this.  */
+
+  for (;;)
+    {
+      yyn = yypact[yystate];
+      if (!yypact_value_is_default (yyn))
+        {
+          yyn += YYTERROR;
+          if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR)
+            {
+              yyn = yytable[yyn];
+              if (0 < yyn)
+                break;
+            }
+        }
+
+      /* Pop the current state because it cannot handle the error token.  */
+      if (yyssp == yyss)
+        YYABORT;
+
+
+      yydestruct ("Error: popping",
+                  yystos[yystate], yyvsp);
+      YYPOPSTACK (1);
+      yystate = *yyssp;
+      YY_STACK_PRINT (yyss, yyssp);
+    }
+
+  YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+  *++yyvsp = yylval;
+  YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+
+  /* Shift the error token.  */
+  YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp);
+
+  yystate = yyn;
+  goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here.  |
+`-------------------------------------*/
+yyacceptlab:
+  yyresult = 0;
+  goto yyreturn;
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here.  |
+`-----------------------------------*/
+yyabortlab:
+  yyresult = 1;
+  goto yyreturn;
+
+#if !defined yyoverflow || YYERROR_VERBOSE
+/*-------------------------------------------------.
+| yyexhaustedlab -- memory exhaustion comes here.  |
+`-------------------------------------------------*/
+yyexhaustedlab:
+  yyerror (YY_("memory exhausted"));
+  yyresult = 2;
+  /* Fall through.  */
+#endif
+
+yyreturn:
+  if (yychar != YYEMPTY)
+    {
+      /* Make sure we have latest lookahead translation.  See comments at
+         user semantic actions for why this is necessary.  */
+      yytoken = YYTRANSLATE (yychar);
+      yydestruct ("Cleanup: discarding lookahead",
+                  yytoken, &yylval);
+    }
+  /* Do not reclaim the symbols of the rule whose action triggered
+     this YYABORT or YYACCEPT.  */
+  YYPOPSTACK (yylen);
+  YY_STACK_PRINT (yyss, yyssp);
+  while (yyssp != yyss)
+    {
+      yydestruct ("Cleanup: popping",
+                  yystos[*yyssp], yyvsp);
+      YYPOPSTACK (1);
+    }
+#ifndef yyoverflow
+  if (yyss != yyssa)
+    YYSTACK_FREE (yyss);
+#endif
+#if YYERROR_VERBOSE
+  if (yymsg != yymsgbuf)
+    YYSTACK_FREE (yymsg);
+#endif
+  return yyresult;
+}
+#line 300 "parsedate.y" /* yacc.c:1906  */
+
+
+/* Month and day table. */
+static TABLE   MonthDayTable[] = {
+    { "january",       tMONTH,  1 },
+    { "february",      tMONTH,  2 },
+    { "march",         tMONTH,  3 },
+    { "april",         tMONTH,  4 },
+    { "may",           tMONTH,  5 },
+    { "june",          tMONTH,  6 },
+    { "july",          tMONTH,  7 },
+    { "august",                tMONTH,  8 },
+    { "september",     tMONTH,  9 },
+    { "october",       tMONTH, 10 },
+    { "november",      tMONTH, 11 },
+    { "december",      tMONTH, 12 },
+       /* The value of the day isn't used... */
+    { "sunday",                tDAY, 0 },
+    { "monday",                tDAY, 0 },
+    { "tuesday",       tDAY, 0 },
+    { "wednesday",     tDAY, 0 },
+    { "thursday",      tDAY, 0 },
+    { "friday",                tDAY, 0 },
+    { "saturday",      tDAY, 0 },
+};
+
+/* Time units table. */
+static TABLE   UnitsTable[] = {
+    { "year",          tMONTH_UNIT,    12 },
+    { "month",         tMONTH_UNIT,    1 },
+    { "week",          tSEC_UNIT,      7L * 24 * 60 * 60 },
+    { "day",           tSEC_UNIT,      1L * 24 * 60 * 60 },
+    { "hour",          tSEC_UNIT,      60 * 60 },
+    { "minute",                tSEC_UNIT,      60 },
+    { "min",           tSEC_UNIT,      60 },
+    { "second",                tSEC_UNIT,      1 },
+    { "sec",           tSEC_UNIT,      1 },
+};
+
+/* Timezone table. */
+static TABLE   TimezoneTable[] = {
+    { "gmt",   tZONE,     HOUR( 0) },  /* Greenwich Mean */
+    { "ut",    tZONE,     HOUR( 0) },  /* Universal */
+    { "utc",   tZONE,     HOUR( 0) },  /* Universal Coordinated */
+    { "cut",   tZONE,     HOUR( 0) },  /* Coordinated Universal */
+    { "z",     tZONE,     HOUR( 0) },  /* Greenwich Mean */
+    { "wet",   tZONE,     HOUR( 0) },  /* Western European */
+    { "bst",   tDAYZONE,  HOUR( 0) },  /* British Summer */
+    { "nst",   tZONE,     HOUR(3)+30 }, /* Newfoundland Standard */
+    { "ndt",   tDAYZONE,  HOUR(3)+30 }, /* Newfoundland Daylight */
+    { "ast",   tZONE,     HOUR( 4) },  /* Atlantic Standard */
+    { "adt",   tDAYZONE,  HOUR( 4) },  /* Atlantic Daylight */
+    { "est",   tZONE,     HOUR( 5) },  /* Eastern Standard */
+    { "edt",   tDAYZONE,  HOUR( 5) },  /* Eastern Daylight */
+    { "cst",   tZONE,     HOUR( 6) },  /* Central Standard */
+    { "cdt",   tDAYZONE,  HOUR( 6) },  /* Central Daylight */
+    { "mst",   tZONE,     HOUR( 7) },  /* Mountain Standard */
+    { "mdt",   tDAYZONE,  HOUR( 7) },  /* Mountain Daylight */
+    { "pst",   tZONE,     HOUR( 8) },  /* Pacific Standard */
+    { "pdt",   tDAYZONE,  HOUR( 8) },  /* Pacific Daylight */
+    { "yst",   tZONE,     HOUR( 9) },  /* Yukon Standard */
+    { "ydt",   tDAYZONE,  HOUR( 9) },  /* Yukon Daylight */
+    { "akst",  tZONE,     HOUR( 9) },  /* Alaska Standard */
+    { "akdt",  tDAYZONE,  HOUR( 9) },  /* Alaska Daylight */
+    { "hst",   tZONE,     HOUR(10) },  /* Hawaii Standard */
+    { "hast",  tZONE,     HOUR(10) },  /* Hawaii-Aleutian Standard */
+    { "hadt",  tDAYZONE,  HOUR(10) },  /* Hawaii-Aleutian Daylight */
+    { "ces",   tDAYZONE,  -HOUR(1) },  /* Central European Summer */
+    { "cest",  tDAYZONE,  -HOUR(1) },  /* Central European Summer */
+    { "mez",   tZONE,     -HOUR(1) },  /* Middle European */
+    { "mezt",  tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
+    { "cet",   tZONE,     -HOUR(1) },  /* Central European */
+    { "met",   tZONE,     -HOUR(1) },  /* Middle European */
+    { "eet",   tZONE,     -HOUR(2) },  /* Eastern Europe */
+    { "msk",   tZONE,     -HOUR(3) },  /* Moscow Winter */
+    { "msd",   tDAYZONE,  -HOUR(3) },  /* Moscow Summer */
+    { "wast",  tZONE,     -HOUR(8) },  /* West Australian Standard */
+    { "wadt",  tDAYZONE,  -HOUR(8) },  /* West Australian Daylight */
+    { "hkt",   tZONE,     -HOUR(8) },  /* Hong Kong */
+    { "cct",   tZONE,     -HOUR(8) },  /* China Coast */
+    { "jst",   tZONE,     -HOUR(9) },  /* Japan Standard */
+    { "kst",   tZONE,     -HOUR(9) },  /* Korean Standard */
+    { "kdt",   tZONE,     -HOUR(9) },  /* Korean Daylight */
+    { "cast",  tZONE,     -(HOUR(9)+30) }, /* Central Australian Standard */
+    { "cadt",  tDAYZONE,  -(HOUR(9)+30) }, /* Central Australian Daylight */
+    { "east",  tZONE,     -HOUR(10) }, /* Eastern Australian Standard */
+    { "eadt",  tDAYZONE,  -HOUR(10) }, /* Eastern Australian Daylight */
+    { "nzst",  tZONE,     -HOUR(12) }, /* New Zealand Standard */
+    { "nzdt",  tDAYZONE,  -HOUR(12) }, /* New Zealand Daylight */
+
+    /* For completeness we include the following entries. */
+#if 0
+
+    /* Duplicate names.  Either they conflict with a zone listed above
+     * (which is either more likely to be seen or just been in circulation
+     * longer), or they conflict with another zone in this section and
+     * we could not reasonably choose one over the other. */
+    { "fst",   tZONE,     HOUR( 2) },  /* Fernando De Noronha Standard */
+    { "fdt",   tDAYZONE,  HOUR( 2) },  /* Fernando De Noronha Daylight */
+    { "bst",   tZONE,     HOUR( 3) },  /* Brazil Standard */
+    { "est",   tZONE,     HOUR( 3) },  /* Eastern Standard (Brazil) */
+    { "edt",   tDAYZONE,  HOUR( 3) },  /* Eastern Daylight (Brazil) */
+    { "wst",   tZONE,     HOUR( 4) },  /* Western Standard (Brazil) */
+    { "wdt",   tDAYZONE,  HOUR( 4) },  /* Western Daylight (Brazil) */
+    { "cst",   tZONE,     HOUR( 5) },  /* Chile Standard */
+    { "cdt",   tDAYZONE,  HOUR( 5) },  /* Chile Daylight */
+    { "ast",   tZONE,     HOUR( 5) },  /* Acre Standard */
+    { "adt",   tDAYZONE,  HOUR( 5) },  /* Acre Daylight */
+    { "cst",   tZONE,     HOUR( 5) },  /* Cuba Standard */
+    { "cdt",   tDAYZONE,  HOUR( 5) },  /* Cuba Daylight */
+    { "est",   tZONE,     HOUR( 6) },  /* Easter Island Standard */
+    { "edt",   tDAYZONE,  HOUR( 6) },  /* Easter Island Daylight */
+    { "sst",   tZONE,     HOUR(11) },  /* Samoa Standard */
+    { "ist",   tZONE,     -HOUR(2) },  /* Israel Standard */
+    { "idt",   tDAYZONE,  -HOUR(2) },  /* Israel Daylight */
+    { "idt",   tDAYZONE,  -(HOUR(3)+30) }, /* Iran Daylight */
+    { "ist",   tZONE,     -(HOUR(3)+30) }, /* Iran Standard */
+    { "cst",    tZONE,     -HOUR(8) }, /* China Standard */
+    { "cdt",    tDAYZONE,  -HOUR(8) }, /* China Daylight */
+    { "sst",    tZONE,     -HOUR(8) }, /* Singapore Standard */
+
+    /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
+    { "gst",   tZONE,     HOUR( 3) },  /* Greenland Standard */
+    { "wat",   tZONE,     -HOUR(1) },  /* West Africa */
+    { "at",    tZONE,     HOUR( 2) },  /* Azores */
+    { "gst",   tZONE,     -HOUR(10) }, /* Guam Standard */
+    { "nft",   tZONE,     HOUR(3)+30 }, /* Newfoundland */
+    { "idlw",  tZONE,     HOUR(12) },  /* International Date Line West */
+    { "mewt",  tZONE,     -HOUR(1) },  /* Middle European Winter */
+    { "mest",  tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
+    { "swt",   tZONE,     -HOUR(1) },  /* Swedish Winter */
+    { "sst",   tDAYZONE,  -HOUR(1) },  /* Swedish Summer */
+    { "fwt",   tZONE,     -HOUR(1) },  /* French Winter */
+    { "fst",   tDAYZONE,  -HOUR(1) },  /* French Summer */
+    { "bt",    tZONE,     -HOUR(3) },  /* Baghdad */
+    { "it",    tZONE,     -(HOUR(3)+30) }, /* Iran */
+    { "zp4",   tZONE,     -HOUR(4) },  /* USSR Zone 3 */
+    { "zp5",   tZONE,     -HOUR(5) },  /* USSR Zone 4 */
+    { "ist",   tZONE,     -(HOUR(5)+30) }, /* Indian Standard */
+    { "zp6",   tZONE,     -HOUR(6) },  /* USSR Zone 5 */
+    { "nst",   tZONE,     -HOUR(7) },  /* North Sumatra */
+    { "sst",   tZONE,     -HOUR(7) },  /* South Sumatra */
+    { "jt",    tZONE,     -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
+    { "nzt",   tZONE,     -HOUR(12) }, /* New Zealand */
+    { "idle",  tZONE,     -HOUR(12) }, /* International Date Line East */
+    { "cat",   tZONE,     HOUR(10) },  /* -- expired 1967 */
+    { "nt",    tZONE,     HOUR(11) },  /* -- expired 1967 */
+    { "ahst",  tZONE,     HOUR(10) },  /* -- expired 1983 */
+    { "hdt",   tDAYZONE,  HOUR(10) },  /* -- expired 1986 */
+#endif /* 0 */
+};
+
+
+/* ARGSUSED */
+static void
+date_error(char *s)
+{
+    /* NOTREACHED */
+}
+
+
+static time_t
+ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
+{
+    if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 61)
+       return -1;
+    if (Meridian == MER24) {
+       if (Hours < 0 || Hours > 23)
+           return -1;
+    }
+    else {
+       if (Hours < 1 || Hours > 12)
+           return -1;
+       if (Hours == 12)
+           Hours = 0;
+       if (Meridian == MERpm)
+           Hours += 12;
+    }
+    return (Hours * 60L + Minutes) * 60L + Seconds;
+}
+
+
+static time_t
+Convert(time_t Month, time_t Day, time_t Year,
+       time_t Hours, time_t Minutes, time_t Seconds,
+       MERIDIAN Meridian, DSTMODE dst)
+{
+    static int DaysNormal[13] = {
+       0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+    };
+    static int DaysLeap[13] = {
+       0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+    };
+    static int LeapYears[] = {
+       1972, 1976, 1980, 1984, 1988, 1992, 1996,
+       2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
+    };
+    register int       *yp;
+    register int       *mp;
+    register time_t    Julian;
+    register int       i;
+    time_t             tod;
+
+    if (Year < 0)
+       Year = -Year;
+    if (Year < 100)
+       Year += 1900;
+    if (Year < EPOCH)
+       Year += 100;
+    for (mp = DaysNormal, yp = LeapYears; yp < ENDOF(LeapYears); yp++)
+       if (Year == *yp) {
+           mp = DaysLeap;
+           break;
+       }
+    if (Year < EPOCH || Year > END_OF_TIME
+     || Month < 1 || Month > 12
+     /* NOSTRICT *//* conversion from long may lose accuracy */
+     || Day < 1 || Day > mp[(int)Month])
+       return -1;
+
+    Julian = Day - 1 + (Year - EPOCH) * 365;
+    for (yp = LeapYears; yp < ENDOF(LeapYears); yp++, Julian++)
+       if (Year <= *yp)
+           break;
+    for (i = 1; i < Month; i++)
+       Julian += *++mp;
+    Julian *= SECSPERDAY;
+    Julian += yyTimezone * 60L;
+    if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
+       return -1;
+    Julian += tod;
+    tod = Julian;
+    if (dst == DSTon || (dst == DSTmaybe && localtime(&tod)->tm_isdst))
+       Julian -= DST_OFFSET * 60L * 60L;
+    return Julian;
+}
+
+
+static time_t
+DSTcorrect(time_t Start, time_t Future)
+{
+    time_t     StartDay;
+    time_t     FutureDay;
+
+    StartDay = (localtime(&Start)->tm_hour + 1) % 24;
+    FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
+    return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60L * 60L;
+}
+
+
+static time_t
+RelativeMonth(time_t Start, time_t RelMonth)
+{
+    struct tm  *tm;
+    time_t     Month;
+    time_t     Year;
+
+    tm = localtime(&Start);
+    Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
+    Year = Month / 12;
+    Month = Month % 12 + 1;
+    return DSTcorrect(Start,
+           Convert(Month, (time_t)tm->tm_mday, Year,
+               (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
+               MER24, DSTmaybe));
+}
+
+
+static int
+LookupWord(char *buff, register int length)
+{
+    register char      *p;
+    register char      *q;
+    register TABLE     *tp;
+    register int       c;
+
+    p = buff;
+    c = p[0];
+
+    /* See if we have an abbreviation for a month. */
+    if (length == 3 || (length == 4 && p[3] == '.'))
+       for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
+           q = tp->name;
+           if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+       }
+    else
+       for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++)
+           if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+
+    /* Try for a timezone. */
+    for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
+       if (c == tp->name[0] && p[1] == tp->name[1]
+        && strcmp(p, tp->name) == 0) {
+           yylval.Number = tp->value;
+           return tp->type;
+       }
+
+    /* Try the units table. */
+    for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
+       if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+           yylval.Number = tp->value;
+           return tp->type;
+       }
+
+    /* Strip off any plural and try the units table again. */
+    if (--length > 0 && p[length] == 's') {
+       p[length] = '\0';
+       for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
+           if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+               p[length] = 's';
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+       p[length] = 's';
+    }
+    length++;
+
+    /* Drop out any periods. */
+    for (p = buff, q = (char*)buff; *q; q++)
+       if (*q != '.')
+           *p++ = *q;
+    *p = '\0';
+
+    /* Try the meridians. */
+    if (buff[1] == 'm' && buff[2] == '\0') {
+       if (buff[0] == 'a') {
+           yylval.Meridian = MERam;
+           return tMERIDIAN;
+       }
+       if (buff[0] == 'p') {
+           yylval.Meridian = MERpm;
+           return tMERIDIAN;
+       }
+    }
+
+    /* If we saw any periods, try the timezones again. */
+    if (p - buff != length) {
+       c = buff[0];
+       for (p = buff, tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
+           if (c == tp->name[0] && p[1] == tp->name[1]
+           && strcmp(p, tp->name) == 0) {
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+    }
+
+    /* Unknown word -- assume GMT timezone. */
+    yylval.Number = 0;
+    return tZONE;
+}
+
+
+int
+date_lex(void)
+{
+    register char      c;
+    register char      *p;
+    char               buff[20];
+    register int       sign;
+    register int       i;
+    register int       nesting;
+
+    for ( ; ; ) {
+       /* Get first character after the whitespace. */
+       for ( ; ; ) {
+           while (isspace(*yyInput))
+               yyInput++;
+           c = *yyInput;
+
+           /* Ignore RFC 822 comments, typically time zone names. */
+           if (c != LPAREN)
+               break;
+           for (nesting = 1; (c = *++yyInput) != RPAREN || --nesting; )
+               if (c == LPAREN)
+                   nesting++;
+               else if (!IS7BIT(c) || c == '\0' || c == '\r'
+                    || (c == '\\' && ((c = *++yyInput) == '\0' || !IS7BIT(c))))
+                   /* Lexical error: bad comment. */
+                   return '?';
+           yyInput++;
+       }
+
+       /* A number? */
+       if (isdigit(c) || c == '-' || c == '+') {
+           if (c == '-' || c == '+') {
+               sign = c == '-' ? -1 : 1;
+               yyInput++;
+               if (!isdigit(*yyInput))
+                   /* Skip the plus or minus sign. */
+                   continue;
+           }
+           else
+               sign = 0;
+           for (i = 0; (c = *yyInput++) != '\0' && isdigit(c); )
+               i = 10 * i + c - '0';
+           yyInput--;
+           yylval.Number = sign < 0 ? -i : i;
+           return sign ? tSNUMBER : tUNUMBER;
+       }
+
+       /* A word? */
+       if (isalpha(c)) {
+           for (p = buff; (c = *yyInput++) == '.' || isalpha(c); )
+               if (p < &buff[sizeof buff - 1])
+                   *p++ = isupper(c) ? tolower(c) : c;
+           *p = '\0';
+           yyInput--;
+           return LookupWord(buff, p - buff);
+       }
+
+       return *yyInput++;
+    }
+}
+
+
+time_t
+parsedate(const char *p)
+{
+    extern int         date_parse(void);
+    time_t             Start;
+
+    yyInput = p; /* well, its supposed to be const... */
+
+    yyYear = 0;
+    yyMonth = 0;
+    yyDay = 0;
+    yyTimezone = 0;
+    yyDSTmode = DSTmaybe;
+    yyHour = 0;
+    yyMinutes = 0;
+    yySeconds = 0;
+    yyMeridian = MER24;
+    yyRelSeconds = 0;
+    yyRelMonth = 0;
+    yyHaveDate = 0;
+    yyHaveRel = 0;
+    yyHaveTime = 0;
+
+    if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
+       return -1;
+
+    if (yyHaveDate || yyHaveTime) {
+       Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
+                   yyMeridian, yyDSTmode);
+       if (Start < 0)
+           return -1;
+    }
+    else
+       return -1;
+
+    Start += yyRelSeconds;
+    if (yyRelMonth)
+       Start += RelativeMonth(Start, yyRelMonth);
+
+    /* Have to do *something* with a legitimate -1 so it's distinguishable
+     * from the error return value.  (Alternately could set errno on error.) */
+    return Start == -1 ? 0 : Start;
+}
+
+
+#ifdef TEST
+
+#if YYDEBUG
+extern int     yydebug;
+#endif /* YYDEBUG */
+
+/* ARGSUSED */
+int
+main(int ac, char *av[])
+{
+    char       buff[128];
+    time_t     d;
+
+#if YYDEBUG
+    yydebug = 1;
+#endif /* YYDEBUG */
+
+    (void)printf("Enter date, or blank line to exit.\n\t> ");
+    for ( ; ; ) {
+       (void)printf("\t> ");
+       (void)fflush(stdout);
+       if (fgets(buff, sizeof buff, stdin) == NULL || buff[0] == '\n')
+           break;
+#if YYDEBUG
+       if (strcmp(buff, "yydebug") == 0) {
+           yydebug = !yydebug;
+           printf("yydebug = %s\n", yydebug ? "on" : "off");
+           continue;
+       }
+#endif /* YYDEBUG */
+       d = parsedate(buff, (TIMEINFO *)NULL);
+       if (d == -1)
+           (void)printf("Bad format - couldn't convert.\n");
+       else
+           (void)printf("%s", ctime(&d));
+    }
+
+    exit(0);
+    /* NOTREACHED */
+}
+#endif /* TEST */
diff --git a/citadel/server/parsedate.h b/citadel/server/parsedate.h
new file mode 100644 (file)
index 0000000..345e071
--- /dev/null
@@ -0,0 +1,2 @@
+
+time_t parsedate(const char *);
diff --git a/citadel/server/parsedate.y b/citadel/server/parsedate.y
new file mode 100644 (file)
index 0000000..d6022d3
--- /dev/null
@@ -0,0 +1,806 @@
+%{
+/* $Revision$
+**
+**  Originally written by Steven M. Bellovin <smb@research.att.com> while
+**  at the University of North Carolina at Chapel Hill.  Later tweaked by
+**  a couple of people on Usenet.  Completely overhauled by Rich $alz
+**  <rsalz@osf.org> and Jim Berets <jberets@bbn.com> in August, 1990.
+**  Further revised (removed obsolete constructs and cleaned up timezone
+**  names) in August, 1991, by Rich.  Paul Eggert <eggert@twinsun.com>
+**  helped in September, 1992.  Art Cancro <ajc@citadel.org> cleaned
+**  it up for ANSI C in December, 1999.
+**
+**  This grammar has six shift/reduce conflicts.
+**
+**  This code is in the public domain and has no copyright.
+*/
+/* SUPPRESS 530 *//* Empty body for statement */
+/* SUPPRESS 593 on yyerrlab *//* Label was not used */
+/* SUPPRESS 593 on yynewstate *//* Label was not used */
+/* SUPPRESS 595 on yypvt *//* Automatic variable may be used before set */
+
+#include "sysdep.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <ctype.h>
+#include <time.h>
+#if HAVE_STRING_H
+# if !STDC_HEADERS && HAVE_MEMORY_H
+#  include <memory.h>
+# endif
+# include <string.h>
+#endif
+#if HAVE_STRINGS_H
+# include <strings.h>
+#endif
+
+#include "parsedate.h"
+
+int date_lex(void);
+
+#define yyparse                date_parse
+#define yylex          date_lex
+#define yyerror                date_error
+
+
+    /* See the LeapYears table in Convert. */
+#define EPOCH          1970
+#define END_OF_TIME    2038
+    /* Constants for general time calculations. */
+#define DST_OFFSET     1
+#define SECSPERDAY     (24L * 60L * 60L)
+    /* Readability for TABLE stuff. */
+#define HOUR(x)                (x * 60)
+
+#define LPAREN         '('
+#define RPAREN         ')'
+#define IS7BIT(x)      ((unsigned int)(x) < 0200)
+
+#define SIZEOF(array)  ((int)(sizeof array / sizeof array[0]))
+#define ENDOF(array)   (&array[SIZEOF(array)])
+
+
+/*
+**  An entry in the lexical lookup table.
+*/
+typedef struct _TABLE {
+    char       *name;
+    int                type;
+    time_t     value;
+} TABLE;
+
+/*
+**  Daylight-savings mode:  on, off, or not yet known.
+*/
+typedef enum _DSTMODE {
+    DSTon, DSToff, DSTmaybe
+} DSTMODE;
+
+/*
+**  Meridian:  am, pm, or 24-hour style.
+*/
+typedef enum _MERIDIAN {
+    MERam, MERpm, MER24
+} MERIDIAN;
+
+
+/*
+**  Global variables.  We could get rid of most of them by using a yacc
+**  union, but this is more efficient.  (This routine predates the
+**  yacc %union construct.)
+*/
+static const char      *yyInput;
+static DSTMODE yyDSTmode;
+static int     yyHaveDate;
+static int     yyHaveRel;
+static int     yyHaveTime;
+static time_t  yyTimezone;
+static time_t  yyDay;
+static time_t  yyHour;
+static time_t  yyMinutes;
+static time_t  yyMonth;
+static time_t  yySeconds;
+static time_t  yyYear;
+static MERIDIAN        yyMeridian;
+static time_t  yyRelMonth;
+static time_t  yyRelSeconds;
+
+
+static void            date_error(char *);
+%}
+
+%union {
+    time_t             Number;
+    enum _MERIDIAN     Meridian;
+}
+
+%token tDAY tDAYZONE tMERIDIAN tMONTH tMONTH_UNIT tSEC_UNIT tSNUMBER
+%token tUNUMBER tZONE
+
+%type  <Number>        tDAYZONE tMONTH tMONTH_UNIT tSEC_UNIT
+%type  <Number>        tSNUMBER tUNUMBER tZONE numzone zone
+%type  <Meridian>      tMERIDIAN o_merid
+
+%%
+
+spec   : /* NULL */
+       | spec item
+       ;
+
+item   : time {
+           yyHaveTime++;
+#ifdef lint
+           /* I am compulsive about lint natterings... */
+           if (yyHaveTime == -1) {
+               YYERROR;
+           }
+#endif /* lint */
+       }
+       | time zone {
+           yyHaveTime++;
+           yyTimezone = $2;
+       }
+       | date {
+           yyHaveDate++;
+       }
+       | rel {
+           yyHaveRel = 1;
+       }
+       ;
+
+time   : tUNUMBER o_merid {
+           if ($1 < 100) {
+               yyHour = $1;
+               yyMinutes = 0;
+           }
+           else {
+               yyHour = $1 / 100;
+               yyMinutes = $1 % 100;
+           }
+           yySeconds = 0;
+           yyMeridian = $2;
+       }
+       | tUNUMBER ':' tUNUMBER o_merid {
+           yyHour = $1;
+           yyMinutes = $3;
+           yySeconds = 0;
+           yyMeridian = $4;
+       }
+       | tUNUMBER ':' tUNUMBER numzone {
+           yyHour = $1;
+           yyMinutes = $3;
+           yyTimezone = $4;
+           yyMeridian = MER24;
+           yyDSTmode = DSToff;
+       }
+       | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
+           yyHour = $1;
+           yyMinutes = $3;
+           yySeconds = $5;
+           yyMeridian = $6;
+       }
+       | tUNUMBER ':' tUNUMBER ':' tUNUMBER numzone {
+           yyHour = $1;
+           yyMinutes = $3;
+           yySeconds = $5;
+           yyTimezone = $6;
+           yyMeridian = MER24;
+           yyDSTmode = DSToff;
+       }
+       ;
+
+zone   : tZONE {
+           $$ = $1;
+           yyDSTmode = DSToff;
+       }
+       | tDAYZONE {
+           $$ = $1;
+           yyDSTmode = DSTon;
+       }
+       | tZONE numzone {
+           /* Only allow "GMT+300" and "GMT-0800" */
+           if ($1 != 0) {
+               YYABORT;
+           }
+           $$ = $2;
+           yyDSTmode = DSToff;
+       }
+       | numzone {
+           $$ = $1;
+           yyDSTmode = DSToff;
+       }
+       ;
+
+numzone        : tSNUMBER {
+           int         i;
+
+           /* Unix and GMT and numeric timezones -- a little confusing. */
+           if ($1 < 0) {
+               /* Don't work with negative modulus. */
+               $1 = -$1;
+               if ($1 > 9999 || (i = $1 % 100) >= 60) {
+                   YYABORT;
+               }
+               $$ = ($1 / 100) * 60 + i;
+           }
+           else {
+               if ($1 > 9999 || (i = $1 % 100) >= 60) {
+                   YYABORT;
+               }
+               $$ = -(($1 / 100) * 60 + i);
+           }
+       }
+       ;
+
+date   : tUNUMBER '/' tUNUMBER {
+           yyMonth = $1;
+           yyDay = $3;
+       }
+       | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
+           if ($1 > 100) {
+               yyYear = $1;
+               yyMonth = $3;
+               yyDay = $5;
+           }
+           else {
+               yyMonth = $1;
+               yyDay = $3;
+               yyYear = $5;
+           }
+       }
+       | tMONTH tUNUMBER {
+           yyMonth = $1;
+           yyDay = $2;
+       }
+       | tMONTH tUNUMBER ',' tUNUMBER {
+           yyMonth = $1;
+           yyDay = $2;
+           yyYear = $4;
+       }
+       | tUNUMBER tMONTH {
+           yyDay = $1;
+           yyMonth = $2;
+       }
+       | tUNUMBER tMONTH tUNUMBER {
+           yyDay = $1;
+           yyMonth = $2;
+           yyYear = $3;
+       }
+       | tDAY ',' tUNUMBER tMONTH tUNUMBER {
+           yyDay = $3;
+           yyMonth = $4;
+           yyYear = $5;
+       }
+       ;
+
+rel    : tSNUMBER tSEC_UNIT {
+           yyRelSeconds += $1 * $2;
+       }
+       | tUNUMBER tSEC_UNIT {
+           yyRelSeconds += $1 * $2;
+       }
+       | tSNUMBER tMONTH_UNIT {
+           yyRelMonth += $1 * $2;
+       }
+       | tUNUMBER tMONTH_UNIT {
+           yyRelMonth += $1 * $2;
+       }
+       ;
+
+o_merid        : /* NULL */ {
+           $$ = MER24;
+       }
+       | tMERIDIAN {
+           $$ = $1;
+       }
+       ;
+
+%%
+
+/* Month and day table. */
+static TABLE   MonthDayTable[] = {
+    { "january",       tMONTH,  1 },
+    { "february",      tMONTH,  2 },
+    { "march",         tMONTH,  3 },
+    { "april",         tMONTH,  4 },
+    { "may",           tMONTH,  5 },
+    { "june",          tMONTH,  6 },
+    { "july",          tMONTH,  7 },
+    { "august",                tMONTH,  8 },
+    { "september",     tMONTH,  9 },
+    { "october",       tMONTH, 10 },
+    { "november",      tMONTH, 11 },
+    { "december",      tMONTH, 12 },
+       /* The value of the day isn't used... */
+    { "sunday",                tDAY, 0 },
+    { "monday",                tDAY, 0 },
+    { "tuesday",       tDAY, 0 },
+    { "wednesday",     tDAY, 0 },
+    { "thursday",      tDAY, 0 },
+    { "friday",                tDAY, 0 },
+    { "saturday",      tDAY, 0 },
+};
+
+/* Time units table. */
+static TABLE   UnitsTable[] = {
+    { "year",          tMONTH_UNIT,    12 },
+    { "month",         tMONTH_UNIT,    1 },
+    { "week",          tSEC_UNIT,      7L * 24 * 60 * 60 },
+    { "day",           tSEC_UNIT,      1L * 24 * 60 * 60 },
+    { "hour",          tSEC_UNIT,      60 * 60 },
+    { "minute",                tSEC_UNIT,      60 },
+    { "min",           tSEC_UNIT,      60 },
+    { "second",                tSEC_UNIT,      1 },
+    { "sec",           tSEC_UNIT,      1 },
+};
+
+/* Timezone table. */
+static TABLE   TimezoneTable[] = {
+    { "gmt",   tZONE,     HOUR( 0) },  /* Greenwich Mean */
+    { "ut",    tZONE,     HOUR( 0) },  /* Universal */
+    { "utc",   tZONE,     HOUR( 0) },  /* Universal Coordinated */
+    { "cut",   tZONE,     HOUR( 0) },  /* Coordinated Universal */
+    { "z",     tZONE,     HOUR( 0) },  /* Greenwich Mean */
+    { "wet",   tZONE,     HOUR( 0) },  /* Western European */
+    { "bst",   tDAYZONE,  HOUR( 0) },  /* British Summer */
+    { "nst",   tZONE,     HOUR(3)+30 }, /* Newfoundland Standard */
+    { "ndt",   tDAYZONE,  HOUR(3)+30 }, /* Newfoundland Daylight */
+    { "ast",   tZONE,     HOUR( 4) },  /* Atlantic Standard */
+    { "adt",   tDAYZONE,  HOUR( 4) },  /* Atlantic Daylight */
+    { "est",   tZONE,     HOUR( 5) },  /* Eastern Standard */
+    { "edt",   tDAYZONE,  HOUR( 5) },  /* Eastern Daylight */
+    { "cst",   tZONE,     HOUR( 6) },  /* Central Standard */
+    { "cdt",   tDAYZONE,  HOUR( 6) },  /* Central Daylight */
+    { "mst",   tZONE,     HOUR( 7) },  /* Mountain Standard */
+    { "mdt",   tDAYZONE,  HOUR( 7) },  /* Mountain Daylight */
+    { "pst",   tZONE,     HOUR( 8) },  /* Pacific Standard */
+    { "pdt",   tDAYZONE,  HOUR( 8) },  /* Pacific Daylight */
+    { "yst",   tZONE,     HOUR( 9) },  /* Yukon Standard */
+    { "ydt",   tDAYZONE,  HOUR( 9) },  /* Yukon Daylight */
+    { "akst",  tZONE,     HOUR( 9) },  /* Alaska Standard */
+    { "akdt",  tDAYZONE,  HOUR( 9) },  /* Alaska Daylight */
+    { "hst",   tZONE,     HOUR(10) },  /* Hawaii Standard */
+    { "hast",  tZONE,     HOUR(10) },  /* Hawaii-Aleutian Standard */
+    { "hadt",  tDAYZONE,  HOUR(10) },  /* Hawaii-Aleutian Daylight */
+    { "ces",   tDAYZONE,  -HOUR(1) },  /* Central European Summer */
+    { "cest",  tDAYZONE,  -HOUR(1) },  /* Central European Summer */
+    { "mez",   tZONE,     -HOUR(1) },  /* Middle European */
+    { "mezt",  tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
+    { "cet",   tZONE,     -HOUR(1) },  /* Central European */
+    { "met",   tZONE,     -HOUR(1) },  /* Middle European */
+    { "eet",   tZONE,     -HOUR(2) },  /* Eastern Europe */
+    { "msk",   tZONE,     -HOUR(3) },  /* Moscow Winter */
+    { "msd",   tDAYZONE,  -HOUR(3) },  /* Moscow Summer */
+    { "wast",  tZONE,     -HOUR(8) },  /* West Australian Standard */
+    { "wadt",  tDAYZONE,  -HOUR(8) },  /* West Australian Daylight */
+    { "hkt",   tZONE,     -HOUR(8) },  /* Hong Kong */
+    { "cct",   tZONE,     -HOUR(8) },  /* China Coast */
+    { "jst",   tZONE,     -HOUR(9) },  /* Japan Standard */
+    { "kst",   tZONE,     -HOUR(9) },  /* Korean Standard */
+    { "kdt",   tZONE,     -HOUR(9) },  /* Korean Daylight */
+    { "cast",  tZONE,     -(HOUR(9)+30) }, /* Central Australian Standard */
+    { "cadt",  tDAYZONE,  -(HOUR(9)+30) }, /* Central Australian Daylight */
+    { "east",  tZONE,     -HOUR(10) }, /* Eastern Australian Standard */
+    { "eadt",  tDAYZONE,  -HOUR(10) }, /* Eastern Australian Daylight */
+    { "nzst",  tZONE,     -HOUR(12) }, /* New Zealand Standard */
+    { "nzdt",  tDAYZONE,  -HOUR(12) }, /* New Zealand Daylight */
+
+    /* For completeness we include the following entries. */
+#if 0
+
+    /* Duplicate names.  Either they conflict with a zone listed above
+     * (which is either more likely to be seen or just been in circulation
+     * longer), or they conflict with another zone in this section and
+     * we could not reasonably choose one over the other. */
+    { "fst",   tZONE,     HOUR( 2) },  /* Fernando De Noronha Standard */
+    { "fdt",   tDAYZONE,  HOUR( 2) },  /* Fernando De Noronha Daylight */
+    { "bst",   tZONE,     HOUR( 3) },  /* Brazil Standard */
+    { "est",   tZONE,     HOUR( 3) },  /* Eastern Standard (Brazil) */
+    { "edt",   tDAYZONE,  HOUR( 3) },  /* Eastern Daylight (Brazil) */
+    { "wst",   tZONE,     HOUR( 4) },  /* Western Standard (Brazil) */
+    { "wdt",   tDAYZONE,  HOUR( 4) },  /* Western Daylight (Brazil) */
+    { "cst",   tZONE,     HOUR( 5) },  /* Chile Standard */
+    { "cdt",   tDAYZONE,  HOUR( 5) },  /* Chile Daylight */
+    { "ast",   tZONE,     HOUR( 5) },  /* Acre Standard */
+    { "adt",   tDAYZONE,  HOUR( 5) },  /* Acre Daylight */
+    { "cst",   tZONE,     HOUR( 5) },  /* Cuba Standard */
+    { "cdt",   tDAYZONE,  HOUR( 5) },  /* Cuba Daylight */
+    { "est",   tZONE,     HOUR( 6) },  /* Easter Island Standard */
+    { "edt",   tDAYZONE,  HOUR( 6) },  /* Easter Island Daylight */
+    { "sst",   tZONE,     HOUR(11) },  /* Samoa Standard */
+    { "ist",   tZONE,     -HOUR(2) },  /* Israel Standard */
+    { "idt",   tDAYZONE,  -HOUR(2) },  /* Israel Daylight */
+    { "idt",   tDAYZONE,  -(HOUR(3)+30) }, /* Iran Daylight */
+    { "ist",   tZONE,     -(HOUR(3)+30) }, /* Iran Standard */
+    { "cst",    tZONE,     -HOUR(8) }, /* China Standard */
+    { "cdt",    tDAYZONE,  -HOUR(8) }, /* China Daylight */
+    { "sst",    tZONE,     -HOUR(8) }, /* Singapore Standard */
+
+    /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
+    { "gst",   tZONE,     HOUR( 3) },  /* Greenland Standard */
+    { "wat",   tZONE,     -HOUR(1) },  /* West Africa */
+    { "at",    tZONE,     HOUR( 2) },  /* Azores */
+    { "gst",   tZONE,     -HOUR(10) }, /* Guam Standard */
+    { "nft",   tZONE,     HOUR(3)+30 }, /* Newfoundland */
+    { "idlw",  tZONE,     HOUR(12) },  /* International Date Line West */
+    { "mewt",  tZONE,     -HOUR(1) },  /* Middle European Winter */
+    { "mest",  tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
+    { "swt",   tZONE,     -HOUR(1) },  /* Swedish Winter */
+    { "sst",   tDAYZONE,  -HOUR(1) },  /* Swedish Summer */
+    { "fwt",   tZONE,     -HOUR(1) },  /* French Winter */
+    { "fst",   tDAYZONE,  -HOUR(1) },  /* French Summer */
+    { "bt",    tZONE,     -HOUR(3) },  /* Baghdad */
+    { "it",    tZONE,     -(HOUR(3)+30) }, /* Iran */
+    { "zp4",   tZONE,     -HOUR(4) },  /* USSR Zone 3 */
+    { "zp5",   tZONE,     -HOUR(5) },  /* USSR Zone 4 */
+    { "ist",   tZONE,     -(HOUR(5)+30) }, /* Indian Standard */
+    { "zp6",   tZONE,     -HOUR(6) },  /* USSR Zone 5 */
+    { "nst",   tZONE,     -HOUR(7) },  /* North Sumatra */
+    { "sst",   tZONE,     -HOUR(7) },  /* South Sumatra */
+    { "jt",    tZONE,     -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
+    { "nzt",   tZONE,     -HOUR(12) }, /* New Zealand */
+    { "idle",  tZONE,     -HOUR(12) }, /* International Date Line East */
+    { "cat",   tZONE,     HOUR(10) },  /* -- expired 1967 */
+    { "nt",    tZONE,     HOUR(11) },  /* -- expired 1967 */
+    { "ahst",  tZONE,     HOUR(10) },  /* -- expired 1983 */
+    { "hdt",   tDAYZONE,  HOUR(10) },  /* -- expired 1986 */
+#endif /* 0 */
+};
+
+
+/* ARGSUSED */
+static void
+date_error(char *s)
+{
+    /* NOTREACHED */
+}
+
+
+static time_t
+ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
+{
+    if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 61)
+       return -1;
+    if (Meridian == MER24) {
+       if (Hours < 0 || Hours > 23)
+           return -1;
+    }
+    else {
+       if (Hours < 1 || Hours > 12)
+           return -1;
+       if (Hours == 12)
+           Hours = 0;
+       if (Meridian == MERpm)
+           Hours += 12;
+    }
+    return (Hours * 60L + Minutes) * 60L + Seconds;
+}
+
+
+static time_t
+Convert(time_t Month, time_t Day, time_t Year,
+       time_t Hours, time_t Minutes, time_t Seconds,
+       MERIDIAN Meridian, DSTMODE dst)
+{
+    static int DaysNormal[13] = {
+       0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+    };
+    static int DaysLeap[13] = {
+       0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+    };
+    static int LeapYears[] = {
+       1972, 1976, 1980, 1984, 1988, 1992, 1996,
+       2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
+    };
+    register int       *yp;
+    register int       *mp;
+    register time_t    Julian;
+    register int       i;
+    time_t             tod;
+
+    if (Year < 0)
+       Year = -Year;
+    if (Year < 100)
+       Year += 1900;
+    if (Year < EPOCH)
+       Year += 100;
+    for (mp = DaysNormal, yp = LeapYears; yp < ENDOF(LeapYears); yp++)
+       if (Year == *yp) {
+           mp = DaysLeap;
+           break;
+       }
+    if (Year < EPOCH || Year > END_OF_TIME
+     || Month < 1 || Month > 12
+     /* NOSTRICT *//* conversion from long may lose accuracy */
+     || Day < 1 || Day > mp[(int)Month])
+       return -1;
+
+    Julian = Day - 1 + (Year - EPOCH) * 365;
+    for (yp = LeapYears; yp < ENDOF(LeapYears); yp++, Julian++)
+       if (Year <= *yp)
+           break;
+    for (i = 1; i < Month; i++)
+       Julian += *++mp;
+    Julian *= SECSPERDAY;
+    Julian += yyTimezone * 60L;
+    if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
+       return -1;
+    Julian += tod;
+    tod = Julian;
+    if (dst == DSTon || (dst == DSTmaybe && localtime(&tod)->tm_isdst))
+       Julian -= DST_OFFSET * 60L * 60L;
+    return Julian;
+}
+
+
+static time_t
+DSTcorrect(time_t Start, time_t Future)
+{
+    time_t     StartDay;
+    time_t     FutureDay;
+
+    StartDay = (localtime(&Start)->tm_hour + 1) % 24;
+    FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
+    return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60L * 60L;
+}
+
+
+static time_t
+RelativeMonth(time_t Start, time_t RelMonth)
+{
+    struct tm  *tm;
+    time_t     Month;
+    time_t     Year;
+
+    tm = localtime(&Start);
+    Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
+    Year = Month / 12;
+    Month = Month % 12 + 1;
+    return DSTcorrect(Start,
+           Convert(Month, (time_t)tm->tm_mday, Year,
+               (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
+               MER24, DSTmaybe));
+}
+
+
+static int
+LookupWord(char *buff, register int length)
+{
+    register char      *p;
+    register char      *q;
+    register TABLE     *tp;
+    register int       c;
+
+    p = buff;
+    c = p[0];
+
+    /* See if we have an abbreviation for a month. */
+    if (length == 3 || (length == 4 && p[3] == '.'))
+       for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
+           q = tp->name;
+           if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+       }
+    else
+       for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++)
+           if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+
+    /* Try for a timezone. */
+    for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
+       if (c == tp->name[0] && p[1] == tp->name[1]
+        && strcmp(p, tp->name) == 0) {
+           yylval.Number = tp->value;
+           return tp->type;
+       }
+
+    /* Try the units table. */
+    for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
+       if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+           yylval.Number = tp->value;
+           return tp->type;
+       }
+
+    /* Strip off any plural and try the units table again. */
+    if (--length > 0 && p[length] == 's') {
+       p[length] = '\0';
+       for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
+           if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+               p[length] = 's';
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+       p[length] = 's';
+    }
+    length++;
+
+    /* Drop out any periods. */
+    for (p = buff, q = (char*)buff; *q; q++)
+       if (*q != '.')
+           *p++ = *q;
+    *p = '\0';
+
+    /* Try the meridians. */
+    if (buff[1] == 'm' && buff[2] == '\0') {
+       if (buff[0] == 'a') {
+           yylval.Meridian = MERam;
+           return tMERIDIAN;
+       }
+       if (buff[0] == 'p') {
+           yylval.Meridian = MERpm;
+           return tMERIDIAN;
+       }
+    }
+
+    /* If we saw any periods, try the timezones again. */
+    if (p - buff != length) {
+       c = buff[0];
+       for (p = buff, tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
+           if (c == tp->name[0] && p[1] == tp->name[1]
+           && strcmp(p, tp->name) == 0) {
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+    }
+
+    /* Unknown word -- assume GMT timezone. */
+    yylval.Number = 0;
+    return tZONE;
+}
+
+
+int
+date_lex(void)
+{
+    register char      c;
+    register char      *p;
+    char               buff[20];
+    register int       sign;
+    register int       i;
+    register int       nesting;
+
+    for ( ; ; ) {
+       /* Get first character after the whitespace. */
+       for ( ; ; ) {
+           while (isspace(*yyInput))
+               yyInput++;
+           c = *yyInput;
+
+           /* Ignore RFC 822 comments, typically time zone names. */
+           if (c != LPAREN)
+               break;
+           for (nesting = 1; (c = *++yyInput) != RPAREN || --nesting; )
+               if (c == LPAREN)
+                   nesting++;
+               else if (!IS7BIT(c) || c == '\0' || c == '\r'
+                    || (c == '\\' && ((c = *++yyInput) == '\0' || !IS7BIT(c))))
+                   /* Lexical error: bad comment. */
+                   return '?';
+           yyInput++;
+       }
+
+       /* A number? */
+       if (isdigit(c) || c == '-' || c == '+') {
+           if (c == '-' || c == '+') {
+               sign = c == '-' ? -1 : 1;
+               yyInput++;
+               if (!isdigit(*yyInput))
+                   /* Skip the plus or minus sign. */
+                   continue;
+           }
+           else
+               sign = 0;
+           for (i = 0; (c = *yyInput++) != '\0' && isdigit(c); )
+               i = 10 * i + c - '0';
+           yyInput--;
+           yylval.Number = sign < 0 ? -i : i;
+           return sign ? tSNUMBER : tUNUMBER;
+       }
+
+       /* A word? */
+       if (isalpha(c)) {
+           for (p = buff; (c = *yyInput++) == '.' || isalpha(c); )
+               if (p < &buff[sizeof buff - 1])
+                   *p++ = isupper(c) ? tolower(c) : c;
+           *p = '\0';
+           yyInput--;
+           return LookupWord(buff, p - buff);
+       }
+
+       return *yyInput++;
+    }
+}
+
+
+time_t
+parsedate(const char *p)
+{
+    extern int         date_parse(void);
+    time_t             Start;
+
+    yyInput = p; /* well, its supposed to be const... */
+
+    yyYear = 0;
+    yyMonth = 0;
+    yyDay = 0;
+    yyTimezone = 0;
+    yyDSTmode = DSTmaybe;
+    yyHour = 0;
+    yyMinutes = 0;
+    yySeconds = 0;
+    yyMeridian = MER24;
+    yyRelSeconds = 0;
+    yyRelMonth = 0;
+    yyHaveDate = 0;
+    yyHaveRel = 0;
+    yyHaveTime = 0;
+
+    if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
+       return -1;
+
+    if (yyHaveDate || yyHaveTime) {
+       Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
+                   yyMeridian, yyDSTmode);
+       if (Start < 0)
+           return -1;
+    }
+    else
+       return -1;
+
+    Start += yyRelSeconds;
+    if (yyRelMonth)
+       Start += RelativeMonth(Start, yyRelMonth);
+
+    /* Have to do *something* with a legitimate -1 so it's distinguishable
+     * from the error return value.  (Alternately could set errno on error.) */
+    return Start == -1 ? 0 : Start;
+}
+
+
+#ifdef TEST
+
+#if YYDEBUG
+extern int     yydebug;
+#endif /* YYDEBUG */
+
+/* ARGSUSED */
+int
+main(int ac, char *av[])
+{
+    char       buff[128];
+    time_t     d;
+
+#if YYDEBUG
+    yydebug = 1;
+#endif /* YYDEBUG */
+
+    (void)printf("Enter date, or blank line to exit.\n\t> ");
+    for ( ; ; ) {
+       (void)printf("\t> ");
+       (void)fflush(stdout);
+       if (fgets(buff, sizeof buff, stdin) == NULL || buff[0] == '\n')
+           break;
+#if YYDEBUG
+       if (strcmp(buff, "yydebug") == 0) {
+           yydebug = !yydebug;
+           printf("yydebug = %s\n", yydebug ? "on" : "off");
+           continue;
+       }
+#endif /* YYDEBUG */
+       d = parsedate(buff, (TIMEINFO *)NULL);
+       if (d == -1)
+           (void)printf("Bad format - couldn't convert.\n");
+       else
+           (void)printf("%s", ctime(&d));
+    }
+
+    exit(0);
+    /* NOTREACHED */
+}
+#endif /* TEST */
diff --git a/citadel/server/room_ops.c b/citadel/server/room_ops.c
new file mode 100644 (file)
index 0000000..1b2c184
--- /dev/null
@@ -0,0 +1,1188 @@
+// Server functions which perform operations on room objects.
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include <stdio.h>
+#include <libcitadel.h>
+
+#include "citserver.h"
+#include "ctdl_module.h"
+#include "config.h"
+#include "control.h"
+#include "user_ops.h"
+#include "room_ops.h"
+
+struct floor *floorcache[MAXFLOORS];
+
+// Determine whether the currently logged in session has permission to read
+// messages in the current room.
+int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
+       if (    (!(CC->logged_in))
+               && (!(CC->internal_pgm))
+               && (!CtdlGetConfigInt("c_guest_logins"))
+       ) {
+               return(om_not_logged_in);
+       }
+       return(om_ok);
+}
+
+
+// Check to see whether we have permission to post a message in the current
+// room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
+// returns 0 on success.
+int CtdlDoIHavePermissionToPostInThisRoom(
+       char *errmsgbuf, 
+       size_t n, 
+       PostType PostPublic,
+       int is_reply
+) {
+       int ra;
+
+       if (!(CC->logged_in) && (PostPublic == POST_LOGGED_IN)) {
+               snprintf(errmsgbuf, n, "Not logged in.");
+               return (ERROR + NOT_LOGGED_IN);
+       }
+       else if (PostPublic == CHECK_EXIST) {
+               return (0);                                     // evaluate whether a recipient exists
+       }
+       else if (!(CC->logged_in)) {
+               if ((CC->room.QRflags & QR_READONLY)) {
+                       snprintf(errmsgbuf, n, "Not logged in.");
+                       return (ERROR + NOT_LOGGED_IN);
+               }
+               return (0);
+       }
+
+       if ((CC->user.axlevel < AxProbU) && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
+               snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
+               return (ERROR + HIGHER_ACCESS_REQUIRED);
+       }
+
+       CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
+
+       if (ra & UA_POSTALLOWED) {
+               strcpy(errmsgbuf, "OK to post or reply here");
+               return(0);
+       }
+
+       if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
+               // To be thorough, we ought to check to see if the message they are
+               // replying to is actually a valid one in this room, but unless this
+               // actually becomes a problem we'll go with high performance instead.
+               strcpy(errmsgbuf, "OK to reply here");
+               return(0);
+       }
+
+       if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
+               // Clarify what happened with a better error message
+               snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
+               return (ERROR + HIGHER_ACCESS_REQUIRED);
+       }
+
+       snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
+       return (ERROR + HIGHER_ACCESS_REQUIRED);
+
+}
+
+
+// Check whether the current user has permission to delete messages from
+// the current room (returns 1 for yes, 0 for no)
+int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
+       int ra;
+       CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
+       if (ra & UA_DELETEALLOWED) return(1);
+       return(0);
+}
+
+
+// Retrieve access control information for any user/room pair.
+// Yes, it has a couple of gotos.  If you don't like that, go die in a car fire.
+void CtdlRoomAccess(struct ctdlroom *roombuf, struct ctdluser *userbuf, int *result, int *view) {
+       int retval = 0;
+       visit vbuf;
+       int is_me = 0;
+       int is_guest = 0;
+
+       if (userbuf == &CC->user) {
+               is_me = 1;
+       }
+
+       if ((is_me) && (CtdlGetConfigInt("c_guest_logins")) && (!CC->logged_in)) {
+               is_guest = 1;
+       }
+
+       // for internal programs, always do everything
+       if (((CC->internal_pgm)) && (roombuf->QRflags & QR_INUSE)) {
+               retval = (UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED);
+               vbuf.v_view = 0;
+               goto SKIP_EVERYTHING;
+       }
+
+       // If guest mode is enabled, always grant access to the Lobby
+       if ((is_guest) && (!strcasecmp(roombuf->QRname, BASEROOM))) {
+               retval = (UA_KNOWN | UA_GOTOALLOWED);
+               vbuf.v_view = 0;
+               goto SKIP_EVERYTHING;
+       }
+
+       // Locate any applicable user/room relationships
+       if (is_guest) {
+               memset(&vbuf, 0, sizeof vbuf);
+       }
+       else {
+               CtdlGetRelationship(&vbuf, userbuf, roombuf);
+       }
+
+       // Force the properties of the Aide room
+       if (!strcasecmp(roombuf->QRname, CtdlGetConfigStr("c_aideroom"))) {
+               if (userbuf->axlevel >= AxAideU) {
+                       retval = UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED;
+               } else {
+                       retval = 0;
+               }
+               goto NEWMSG;
+       }
+
+       // If this is a public room, it's accessible...
+       if (    ((roombuf->QRflags & QR_PRIVATE) == 0) 
+               && ((roombuf->QRflags & QR_MAILBOX) == 0)
+       ) {
+               retval = retval | UA_KNOWN | UA_GOTOALLOWED;
+       }
+
+       // If this is a preferred users only room, check access level
+       if (roombuf->QRflags & QR_PREFONLY) {
+               if (userbuf->axlevel < AxPrefU) {
+                       retval = retval & ~UA_KNOWN & ~UA_GOTOALLOWED;
+               }
+       }
+
+       // For private rooms, check the generation number matchups
+       if (    (roombuf->QRflags & QR_PRIVATE) 
+               && ((roombuf->QRflags & QR_MAILBOX) == 0)
+       ) {
+
+               // An explicit match means the user belongs in this room
+               if (vbuf.v_flags & V_ACCESS) {
+                       retval = retval | UA_KNOWN | UA_GOTOALLOWED;
+               }
+               // Otherwise, check if this is a guess-name or passworded
+               // room.  If it is, a goto may at least be attempted
+               else if (       (roombuf->QRflags & QR_PRIVATE)
+                               || (roombuf->QRflags & QR_PASSWORDED)
+               ) {
+                       retval = retval & ~UA_KNOWN;
+                       retval = retval | UA_GOTOALLOWED;
+               }
+       }
+
+       // For mailbox rooms, also check the namespace.   Also, mailbox owners can delete their messages
+       if ( (roombuf->QRflags & QR_MAILBOX) && (atol(roombuf->QRname) != 0)) {
+               if (userbuf->usernum == atol(roombuf->QRname)) {
+                       retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED;
+               }
+               // An explicit match means the user belongs in this room
+               if (vbuf.v_flags & V_ACCESS) {
+                       retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED;
+               }
+       }
+
+       // For non-mailbox rooms...
+       else {
+               // User is allowed to post in the room unless:
+               // - User is not validated
+               // - User has no net privileges and it is a shared network room
+               // - It is a read-only room
+               // - It is a blog room (in which case we only allow replies to existing messages)
+               int post_allowed = 1;
+               int reply_allowed = 1;
+               if (userbuf->axlevel < AxProbU) {
+                       post_allowed = 0;
+                       reply_allowed = 0;
+               }
+               if ((userbuf->axlevel < AxNetU) && (roombuf->QRflags & QR_NETWORK)) {
+                       post_allowed = 0;
+                       reply_allowed = 0;
+               }
+               if (roombuf->QRflags & QR_READONLY) {
+                       post_allowed = 0;
+                       reply_allowed = 0;
+               }
+               if (roombuf->QRdefaultview == VIEW_BLOG) {
+                       post_allowed = 0;
+               }
+               if (post_allowed) {
+                       retval = retval | UA_POSTALLOWED | UA_REPLYALLOWED;
+               }
+               if (reply_allowed) {
+                       retval = retval | UA_REPLYALLOWED;
+               }
+
+               // If "collaborative deletion" is active for this room, any user who can post
+               // is also allowed to delete
+               if (roombuf->QRflags2 & QR2_COLLABDEL) {
+                       if (retval & UA_POSTALLOWED) {
+                               retval = retval | UA_DELETEALLOWED;
+                       }
+               }
+
+       }
+
+       // Check to see if the user has forgotten this room
+       if (vbuf.v_flags & V_FORGET) {
+               retval = retval & ~UA_KNOWN;
+               if (    ( ((roombuf->QRflags & QR_PRIVATE) == 0) 
+                       && ((roombuf->QRflags & QR_MAILBOX) == 0)
+               ) || (  (roombuf->QRflags & QR_MAILBOX) 
+                       && (atol(roombuf->QRname) == CC->user.usernum))
+               ) {
+                       retval = retval | UA_ZAPPED;
+               }
+       }
+
+       // If user is explicitly locked out of this room, deny everything
+       if (vbuf.v_flags & V_LOCKOUT) {
+               retval = retval & ~UA_KNOWN & ~UA_GOTOALLOWED & ~UA_POSTALLOWED & ~UA_REPLYALLOWED;
+       }
+
+       // Aides get access to all private rooms
+       if (    (userbuf->axlevel >= AxAideU)
+               && ((roombuf->QRflags & QR_MAILBOX) == 0)
+       ) {
+               if (vbuf.v_flags & V_FORGET) {
+                       retval = retval | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
+               }
+               else {
+                       retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
+               }
+       }
+
+       // Aides can gain access to mailboxes as well, but they don't show by default.
+       if (    (userbuf->axlevel >= AxAideU)
+               && (roombuf->QRflags & QR_MAILBOX)
+       ) {
+               retval = retval | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
+       }
+
+       // Aides and Room Aides have admin privileges
+       if (    (userbuf->axlevel >= AxAideU)
+               || (userbuf->usernum == roombuf->QRroomaide)
+       ) {
+               retval = retval | UA_ADMINALLOWED | UA_DELETEALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
+       }
+
+NEWMSG:        // By the way, we also check for the presence of new messages
+       if (is_msg_in_sequence_set(vbuf.v_seen, roombuf->QRhighest) == 0) {
+               retval = retval | UA_HASNEWMSGS;
+       }
+
+       // System rooms never show up in the list.
+       if (roombuf->QRflags2 & QR2_SYSTEM) {
+               retval = retval & ~UA_KNOWN;
+       }
+
+SKIP_EVERYTHING:
+       // Now give the caller the information it wants.
+       if (result != NULL) *result = retval;
+       if (view != NULL) *view = vbuf.v_view;
+}
+
+
+// Self-checking stuff for a room record read into memory
+void room_sanity_check(struct ctdlroom *qrbuf) {
+       // Mailbox rooms are always on the lowest floor
+       if (qrbuf->QRflags & QR_MAILBOX) {
+               qrbuf->QRfloor = 0;
+       }
+       // Listing order of 0 is illegal except for base rooms
+       if (qrbuf->QRorder == 0) {
+               if (    !(qrbuf->QRflags & QR_MAILBOX)
+                       && strncasecmp(qrbuf->QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)
+                       && strncasecmp(qrbuf->QRname, CtdlGetConfigStr("c_aideroom"), ROOMNAMELEN)
+               ) {
+                       qrbuf->QRorder = 64;
+               }
+       }
+}
+
+
+// CtdlGetRoom()  -  retrieve room data from disk
+int CtdlGetRoom(struct ctdlroom *qrbuf, const char *room_name) {
+       struct cdbdata *cdbqr;
+       char lowercase_name[ROOMNAMELEN];
+       char personal_lowercase_name[ROOMNAMELEN];
+       const char *sptr;
+       char *dptr, *eptr;
+
+       dptr = lowercase_name;
+       sptr = room_name;
+       eptr = (dptr + (sizeof lowercase_name - 1));
+       while (!IsEmptyStr(sptr) && (dptr < eptr)) {
+               *dptr = tolower(*sptr);
+               sptr++; dptr++;
+       }
+       *dptr = '\0';
+
+       memset(qrbuf, 0, sizeof(struct ctdlroom));
+
+       if (IsEmptyStr(lowercase_name)) {
+               return(1);                      // empty room name , not valid
+       }
+
+       // First, try the public namespace
+       cdbqr = cdb_fetch(CDB_ROOMS, lowercase_name, strlen(lowercase_name));
+
+       // If that didn't work, try the user's personal namespace
+       if (cdbqr == NULL) {
+               snprintf(personal_lowercase_name, sizeof personal_lowercase_name, "%010ld.%s", CC->user.usernum, lowercase_name);
+               cdbqr = cdb_fetch(CDB_ROOMS, personal_lowercase_name, strlen(personal_lowercase_name));
+       }
+       if (cdbqr != NULL) {
+               memcpy(qrbuf, cdbqr->ptr, ((cdbqr->len > sizeof(struct ctdlroom)) ?  sizeof(struct ctdlroom) : cdbqr->len));
+               cdb_free(cdbqr);
+               room_sanity_check(qrbuf);
+               return (0);
+       }
+       else {
+               return (1);
+       }
+}
+
+
+// CtdlGetRoomLock()  -  same as getroom() but locks the record (if supported)
+int CtdlGetRoomLock(struct ctdlroom *qrbuf, const char *room_name) {
+       register int retval;
+       retval = CtdlGetRoom(qrbuf, room_name);
+       if (retval == 0) begin_critical_section(S_ROOMS);
+       return(retval);
+}
+
+
+// b_putroom()  -  back end to putroom() and b_deleteroom()
+// (if the supplied buffer is NULL, delete the room record)
+void b_putroom(struct ctdlroom *qrbuf, char *room_name) {
+       char lowercase_name[ROOMNAMELEN];
+       char *aptr, *bptr;
+       long len;
+
+       aptr = room_name;
+       bptr = lowercase_name;
+       while (!IsEmptyStr(aptr)) {
+               *bptr = tolower(*aptr);
+               aptr++;
+               bptr++;
+       }
+       *bptr='\0';
+
+       len = bptr - lowercase_name;
+       if (qrbuf == NULL) {
+               cdb_delete(CDB_ROOMS, lowercase_name, len);
+       }
+       else {
+               time(&qrbuf->QRmtime);
+               cdb_store(CDB_ROOMS, lowercase_name, len, qrbuf, sizeof(struct ctdlroom));
+       }
+}
+
+
+// CtdlPutRoom()  -  store room data to disk
+void CtdlPutRoom(struct ctdlroom *qrbuf) {
+       b_putroom(qrbuf, qrbuf->QRname);
+}
+
+
+// b_deleteroom()  -  delete a room record from disk
+void b_deleteroom(char *room_name) {
+       b_putroom(NULL, room_name);
+}
+
+
+// CtdlPutRoomLock()  -  same as CtdlPutRoom() but unlocks the record (if supported)
+void CtdlPutRoomLock(struct ctdlroom *qrbuf) {
+       CtdlPutRoom(qrbuf);
+       end_critical_section(S_ROOMS);
+}
+
+
+// CtdlGetFloorByName()  -  retrieve the number of the named floor
+// return < 0 if not found else return floor number
+int CtdlGetFloorByName(const char *floor_name) {
+       int a;
+       struct floor *flbuf = NULL;
+
+       for (a = 0; a < MAXFLOORS; ++a) {
+               flbuf = CtdlGetCachedFloor(a);
+
+               // check to see if it already exists
+               if ((!strcasecmp(flbuf->f_name, floor_name)) && (flbuf->f_flags & F_INUSE)) {
+                       return a;
+               }
+       }
+       return -1;
+}
+
+
+// CtdlGetFloorByNameLock()  -  retrieve floor number for given floor and lock the floor list.
+int CtdlGetFloorByNameLock(const char *floor_name) {
+       begin_critical_section(S_FLOORTAB);
+       return CtdlGetFloorByName(floor_name);
+}
+
+
+/*
+ * CtdlGetAvailableFloor()  -  Return number of first unused floor
+ * return < 0 if none available
+ */
+int CtdlGetAvailableFloor(void) {
+       int a;
+       struct floor *flbuf = NULL;
+
+       for (a = 0; a < MAXFLOORS; a++) {
+               flbuf = CtdlGetCachedFloor(a);
+
+               /* check to see if it already exists */
+               if ((flbuf->f_flags & F_INUSE) == 0) {
+                       return a;
+               }
+       }
+       return -1;
+}
+
+
+/*
+ * CtdlGetFloor()  -  retrieve floor data from disk
+ */
+void CtdlGetFloor(struct floor *flbuf, int floor_num) {
+       struct cdbdata *cdbfl;
+
+       memset(flbuf, 0, sizeof(struct floor));
+       cdbfl = cdb_fetch(CDB_FLOORTAB, &floor_num, sizeof(int));
+       if (cdbfl != NULL) {
+               memcpy(flbuf, cdbfl->ptr, ((cdbfl->len > sizeof(struct floor)) ?  sizeof(struct floor) : cdbfl->len));
+               cdb_free(cdbfl);
+       } else {
+               if (floor_num == 0) {
+                       safestrncpy(flbuf->f_name, "Main Floor", sizeof flbuf->f_name);
+                       flbuf->f_flags = F_INUSE;
+                       flbuf->f_ref_count = 3;
+               }
+       }
+}
+
+
+/*
+ * lgetfloor()  -  same as CtdlGetFloor() but locks the record (if supported)
+ */
+void lgetfloor(struct floor *flbuf, int floor_num) {
+       begin_critical_section(S_FLOORTAB);
+       CtdlGetFloor(flbuf, floor_num);
+}
+
+
+/*
+ * CtdlGetCachedFloor()  -  Get floor record from *cache* (loads from disk if needed)
+ *    
+ * This is strictly a performance hack.
+ */
+struct floor *CtdlGetCachedFloor(int floor_num) {
+       static int initialized = 0;
+       int i;
+       int fetch_new = 0;
+       struct floor *fl = NULL;
+
+       begin_critical_section(S_FLOORCACHE);
+       if (initialized == 0) {
+               for (i=0; i<MAXFLOORS; ++i) {
+                       floorcache[floor_num] = NULL;
+               }
+       initialized = 1;
+       }
+       if (floorcache[floor_num] == NULL) {
+               fetch_new = 1;
+       }
+       end_critical_section(S_FLOORCACHE);
+
+       if (fetch_new) {
+               fl = malloc(sizeof(struct floor));
+               CtdlGetFloor(fl, floor_num);
+               begin_critical_section(S_FLOORCACHE);
+               if (floorcache[floor_num] != NULL) {
+                       free(floorcache[floor_num]);
+               }
+               floorcache[floor_num] = fl;
+               end_critical_section(S_FLOORCACHE);
+       }
+
+       return(floorcache[floor_num]);
+}
+
+
+/*
+ * CtdlPutFloor()  -  store floor data on disk
+ */
+void CtdlPutFloor(struct floor *flbuf, int floor_num) {
+       /* If we've cached this, clear it out, 'cuz it's WRONG now! */
+       begin_critical_section(S_FLOORCACHE);
+       if (floorcache[floor_num] != NULL) {
+               free(floorcache[floor_num]);
+               floorcache[floor_num] = malloc(sizeof(struct floor));
+               memcpy(floorcache[floor_num], flbuf, sizeof(struct floor));
+       }
+       end_critical_section(S_FLOORCACHE);
+
+       cdb_store(CDB_FLOORTAB, &floor_num, sizeof(int),
+                 flbuf, sizeof(struct floor));
+}
+
+
+/*
+ * CtdlPutFloorLock()  -  same as CtdlPutFloor() but unlocks the record (if supported)
+ */
+void CtdlPutFloorLock(struct floor *flbuf, int floor_num) {
+       CtdlPutFloor(flbuf, floor_num);
+       end_critical_section(S_FLOORTAB);
+
+}
+
+
+/*
+ * lputfloor()  -  same as CtdlPutFloor() but unlocks the record (if supported)
+ */
+void lputfloor(struct floor *flbuf, int floor_num) {
+       CtdlPutFloorLock(flbuf, floor_num);
+}
+
+
+/* 
+ * Iterate through the room table, performing a callback for each room.
+ */
+void CtdlForEachRoom(ForEachRoomCallBack callback_func, void *in_data) {
+       struct ctdlroom qrbuf;
+       struct cdbdata *cdbqr;
+
+       cdb_rewind(CDB_ROOMS);
+
+       while (cdbqr = cdb_next_item(CDB_ROOMS), cdbqr != NULL) {
+               memset(&qrbuf, 0, sizeof(struct ctdlroom));
+               memcpy(&qrbuf, cdbqr->ptr, ((cdbqr->len > sizeof(struct ctdlroom)) ?  sizeof(struct ctdlroom) : cdbqr->len) );
+               cdb_free(cdbqr);
+               room_sanity_check(&qrbuf);
+               if (qrbuf.QRflags & QR_INUSE) {
+                       callback_func(&qrbuf, in_data);
+               }
+       }
+}
+
+
+/*
+ * delete_msglist()  -  delete room message pointers
+ */
+void delete_msglist(struct ctdlroom *whichroom) {
+        struct cdbdata *cdbml;
+
+       /* Make sure the msglist we're deleting actually exists, otherwise
+        * libdb will complain when we try to delete an invalid record
+        */
+        cdbml = cdb_fetch(CDB_MSGLISTS, &whichroom->QRnumber, sizeof(long));
+        if (cdbml != NULL) {
+               cdb_free(cdbml);
+
+               /* Go ahead and delete it */
+               cdb_delete(CDB_MSGLISTS, &whichroom->QRnumber, sizeof(long));
+       }
+}
+
+
+/*
+ * Message pointer compare function for sort_msglist()
+ */
+int sort_msglist_cmp(const void *m1, const void *m2) {
+       if ((*(const long *)m1) > (*(const long *)m2)) return(1);
+       if ((*(const long *)m1) < (*(const long *)m2)) return(-1);
+       return(0);
+}
+
+
+/*
+ * sort message pointers
+ * (returns new msg count)
+ */
+int sort_msglist(long listptrs[], int oldcount) {
+       int numitems;
+       int i = 0;
+
+       numitems = oldcount;
+       if (numitems < 2) {
+               return (oldcount);
+       }
+
+       /* do the sort */
+       qsort(listptrs, numitems, sizeof(long), sort_msglist_cmp);
+
+       /* and yank any nulls */
+       while ((i < numitems) && (listptrs[i] == 0L)) i++;
+
+       if (i > 0) {
+               memmove(&listptrs[0], &listptrs[i], (sizeof(long) * (numitems - i)));
+               numitems-=i;
+       }
+
+       return (numitems);
+}
+
+
+/*
+ * Determine whether a given room is non-editable.
+ */
+int CtdlIsNonEditable(struct ctdlroom *qrbuf) {
+
+       /* Mail> rooms are non-editable */
+       if ( (qrbuf->QRflags & QR_MAILBOX) && (!strcasecmp(&qrbuf->QRname[11], MAILROOM)) ) {
+               return (1);
+       }
+
+       /* Everything else is editable */
+       return (0);
+}
+
+
+/*
+ * Make the specified room the current room for this session.  No validation
+ * or access control is done here -- the caller should make sure that the
+ * specified room exists and is ok to access.
+ */
+void CtdlUserGoto(char *where, int display_result, int transiently,
+               int *retmsgs, int *retnew, long *retoldest, long *retnewest)
+{
+       int a;
+       int new_messages = 0;
+       int old_messages = 0;
+       int total_messages = 0;
+       long oldest_message = 0;
+       long newest_message = 0;
+       int info = 0;
+       int rmailflag;
+       int raideflag;
+       int newmailcount = 0;
+       visit vbuf;
+       char truncated_roomname[ROOMNAMELEN];
+        struct cdbdata *cdbfr;
+       long *msglist = NULL;
+       int num_msgs = 0;
+       unsigned int original_v_flags;
+       int num_sets;
+       int s;
+       char setstr[128], lostr[64], histr[64];
+       long lo, hi;
+       int is_trash = 0;
+
+       /* If the supplied room name is NULL, the caller wants us to know that
+        * it has already copied the room record into CC->room, so
+        * we can skip the extra database fetch.
+        */
+       if (where != NULL) {
+               safestrncpy(CC->room.QRname, where, sizeof CC->room.QRname);
+               CtdlGetRoom(&CC->room, where);
+       }
+
+       /* Take care of all the formalities. */
+
+       begin_critical_section(S_USERS);
+       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+       original_v_flags = vbuf.v_flags;
+
+       /* Know the room ... but not if it's the page log room, or if the
+        * caller specified that we're only entering this room transiently.
+        */
+       int add_room_to_known_list = 1;
+       if (transiently == 1) {
+               add_room_to_known_list = 0;
+       }
+       char *c_logpages = CtdlGetConfigStr("c_logpages");
+       if ( (c_logpages != NULL) && (!strcasecmp(CC->room.QRname, c_logpages)) ) {
+               add_room_to_known_list = 0;
+       }
+       if (add_room_to_known_list) {
+               vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
+               vbuf.v_flags = vbuf.v_flags | V_ACCESS;
+       }
+       
+       /* Only rewrite the database record if we changed something */
+       if (vbuf.v_flags != original_v_flags) {
+               CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
+       }
+       end_critical_section(S_USERS);
+
+       /* Check for new mail */
+       newmailcount = NewMailCount();
+
+       /* Set info to 1 if the room banner is new since our last visit.
+        * Some clients only want to display it when it changes.
+        */
+       if (CC->room.msgnum_info > vbuf.v_lastseen) {
+               info = 1;
+       }
+
+        cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+        if (cdbfr != NULL) {
+               msglist = (long *) cdbfr->ptr;
+               cdbfr->ptr = NULL;      /* CtdlUserGoto() now owns this memory */
+               num_msgs = cdbfr->len / sizeof(long);
+               cdb_free(cdbfr);
+       }
+
+       total_messages = 0;
+       for (a=0; a<num_msgs; ++a) {
+               if (msglist[a] > 0L) ++total_messages;
+       }
+
+       if (total_messages > 0) {
+               oldest_message = msglist[0];
+               newest_message = msglist[num_msgs - 1];
+       }
+
+       num_sets = num_tokens(vbuf.v_seen, ',');
+       for (s=0; s<num_sets; ++s) {
+               extract_token(setstr, vbuf.v_seen, s, ',', sizeof setstr);
+
+               extract_token(lostr, setstr, 0, ':', sizeof lostr);
+               if (num_tokens(setstr, ':') >= 2) {
+                       extract_token(histr, setstr, 1, ':', sizeof histr);
+                       if (!strcmp(histr, "*")) {
+                               snprintf(histr, sizeof histr, "%ld", LONG_MAX);
+                       }
+               } 
+               else {
+                       strcpy(histr, lostr);
+               }
+               lo = atol(lostr);
+               hi = atol(histr);
+
+               for (a=0; a<num_msgs; ++a) if (msglist[a] > 0L) {
+                       if ((msglist[a] >= lo) && (msglist[a] <= hi)) {
+                               ++old_messages;
+                               msglist[a] = 0L;
+                       }
+               }
+       }
+       new_messages = total_messages - old_messages;
+
+       if (msglist != NULL) free(msglist);
+
+       if (CC->room.QRflags & QR_MAILBOX)
+               rmailflag = 1;
+       else
+               rmailflag = 0;
+
+       if ((CC->room.QRroomaide == CC->user.usernum) || (CC->user.axlevel >= AxAideU))
+               raideflag = 1;
+       else
+               raideflag = 0;
+
+       safestrncpy(truncated_roomname, CC->room.QRname, sizeof truncated_roomname);
+       if ( (CC->room.QRflags & QR_MAILBOX) && (atol(CC->room.QRname) == CC->user.usernum) ) {
+               safestrncpy(truncated_roomname, &truncated_roomname[11], sizeof truncated_roomname);
+       }
+
+       if (!strcasecmp(truncated_roomname, USERTRASHROOM)) {
+               is_trash = 1;
+       }
+
+       if (retmsgs != NULL) *retmsgs = total_messages;
+       if (retnew != NULL) *retnew = new_messages;
+       if (retoldest != NULL) *retoldest = oldest_message;
+       if (retnewest != NULL) *retnewest = newest_message;
+       syslog(LOG_DEBUG, "room_ops: %s : %d new of %d total messages, oldest=%ld, newest=%ld",
+                  CC->room.QRname, new_messages, total_messages, oldest_message, newest_message
+       );
+
+       CC->curr_view = (int)vbuf.v_view;
+
+       if (display_result) {
+               cprintf("%d%c%s|%d|%d|%d|%d|%ld|%ld|%d|%d|%d|%d|%d|%d|%d|%d|%ld|\n",
+                       CIT_OK, CtdlCheckExpress(),
+                       truncated_roomname,
+                       (int)new_messages,
+                       (int)total_messages,
+                       (int)info,
+                       (int)CC->room.QRflags,
+                       (long)CC->room.QRhighest,
+                       (long)vbuf.v_lastseen,
+                       (int)rmailflag,
+                       (int)raideflag,
+                       (int)newmailcount,
+                       (int)CC->room.QRfloor,
+                       (int)vbuf.v_view,
+                       (int)CC->room.QRdefaultview,
+                       (int)is_trash,
+                       (int)CC->room.QRflags2,
+                       (long)CC->room.QRmtime
+               );
+       }
+}
+
+
+/*
+ * Handle some of the macro named rooms
+ */
+void convert_room_name_macros(char *towhere, size_t maxlen) {
+       if (!strcasecmp(towhere, "_BASEROOM_")) {
+               safestrncpy(towhere, CtdlGetConfigStr("c_baseroom"), maxlen);
+       }
+       else if (!strcasecmp(towhere, "_MAIL_")) {
+               safestrncpy(towhere, MAILROOM, maxlen);
+       }
+       else if (!strcasecmp(towhere, "_TRASH_")) {
+               safestrncpy(towhere, USERTRASHROOM, maxlen);
+       }
+       else if (!strcasecmp(towhere, "_DRAFTS_")) {
+               safestrncpy(towhere, USERDRAFTROOM, maxlen);
+       }
+       else if (!strcasecmp(towhere, "_BITBUCKET_")) {
+               safestrncpy(towhere, CtdlGetConfigStr("c_twitroom"), maxlen);
+       }
+       else if (!strcasecmp(towhere, "_CALENDAR_")) {
+               safestrncpy(towhere, USERCALENDARROOM, maxlen);
+       }
+       else if (!strcasecmp(towhere, "_TASKS_")) {
+               safestrncpy(towhere, USERTASKSROOM, maxlen);
+       }
+       else if (!strcasecmp(towhere, "_CONTACTS_")) {
+               safestrncpy(towhere, USERCONTACTSROOM, maxlen);
+       }
+       else if (!strcasecmp(towhere, "_NOTES_")) {
+               safestrncpy(towhere, USERNOTESROOM, maxlen);
+       }
+}
+
+
+/*
+ * Back end function to rename a room.
+ * You can also specify which floor to move the room to, or specify -1 to
+ * keep the room on the same floor it was on.
+ *
+ * If you are renaming a mailbox room, you must supply the namespace prefix
+ * in *at least* the old name!
+ */
+int CtdlRenameRoom(char *old_name, char *new_name, int new_floor) {
+       int old_floor = 0;
+       struct ctdlroom qrbuf;
+       struct ctdlroom qrtmp;
+       int ret = 0;
+       struct floor *fl;
+       struct floor flbuf;
+       long owner = 0L;
+       char actual_old_name[ROOMNAMELEN];
+
+       syslog(LOG_DEBUG, "room_ops: CtdlRenameRoom(%s, %s, %d)", old_name, new_name, new_floor);
+
+       if (new_floor >= 0) {
+               fl = CtdlGetCachedFloor(new_floor);
+               if ((fl->f_flags & F_INUSE) == 0) {
+                       return(crr_invalid_floor);
+               }
+       }
+
+       begin_critical_section(S_ROOMS);
+
+       if ( (CtdlGetRoom(&qrtmp, new_name) == 0) && (strcasecmp(new_name, old_name)) ) {
+               ret = crr_already_exists;
+       }
+
+       else if (CtdlGetRoom(&qrbuf, old_name) != 0) {
+               ret = crr_room_not_found;
+       }
+
+       else if ( (CC->user.axlevel < AxAideU) && (!CC->internal_pgm)
+                 && (CC->user.usernum != qrbuf.QRroomaide)
+                 && ( (((qrbuf.QRflags & QR_MAILBOX) == 0) || (atol(qrbuf.QRname) != CC->user.usernum))) )  {
+               ret = crr_access_denied;
+       }
+
+       else if (CtdlIsNonEditable(&qrbuf)) {
+               ret = crr_noneditable;
+       }
+
+       else {
+               /* Rename it */
+               safestrncpy(actual_old_name, qrbuf.QRname, sizeof actual_old_name);
+               if (qrbuf.QRflags & QR_MAILBOX) {
+                       owner = atol(qrbuf.QRname);
+               }
+               if ( (owner > 0L) && (atol(new_name) == 0L) ) {
+                       snprintf(qrbuf.QRname, sizeof(qrbuf.QRname),
+                                       "%010ld.%s", owner, new_name);
+               }
+               else {
+                       safestrncpy(qrbuf.QRname, new_name,
+                                               sizeof(qrbuf.QRname));
+               }
+
+               /* Reject change of floor for baseroom/aideroom */
+               if (!strncasecmp(old_name, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN) ||
+                   !strncasecmp(old_name, CtdlGetConfigStr("c_aideroom"), ROOMNAMELEN))
+               {
+                       new_floor = 0;
+               }
+
+               /* Take care of floor stuff */
+               old_floor = qrbuf.QRfloor;
+               if (new_floor < 0) {
+                       new_floor = old_floor;
+               }
+               qrbuf.QRfloor = new_floor;
+               CtdlPutRoom(&qrbuf);
+
+               begin_critical_section(S_CONFIG);
+       
+               /* If baseroom/aideroom name changes, update config */
+               if (!strncasecmp(old_name, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
+                       CtdlSetConfigStr("c_baseroom", new_name);
+               }
+               if (!strncasecmp(old_name, CtdlGetConfigStr("c_aideroom"), ROOMNAMELEN)) {
+                       CtdlSetConfigStr("c_aideroom", new_name);
+               }
+       
+               end_critical_section(S_CONFIG);
+       
+               /* If the room name changed, then there are now two room
+                * records, so we have to delete the old one.
+                */
+               if (strcasecmp(new_name, old_name)) {
+                       b_deleteroom(actual_old_name);
+               }
+
+               ret = crr_ok;
+       }
+
+       end_critical_section(S_ROOMS);
+
+       /* Adjust the floor reference counts if necessary */
+       if (new_floor != old_floor) {
+               lgetfloor(&flbuf, old_floor);
+               --flbuf.f_ref_count;
+               lputfloor(&flbuf, old_floor);
+               syslog(LOG_DEBUG, "room_ops: reference count for floor %d is now %d", old_floor, flbuf.f_ref_count);
+               lgetfloor(&flbuf, new_floor);
+               ++flbuf.f_ref_count;
+               lputfloor(&flbuf, new_floor);
+               syslog(LOG_DEBUG, "room_ops: reference count for floor %d is now %d", new_floor, flbuf.f_ref_count);
+       }
+
+       /* ...and everybody say "YATTA!" */     
+       return(ret);
+}
+
+
+/*
+ * Asynchronously schedule a room for deletion.  By placing the room into an invalid private namespace,
+ * the room will appear deleted to the user(s), but the session doesn't need to block while waiting for
+ * database operations to complete.  Instead, the room gets purged when THE DREADED AUTO-PURGER makes
+ * its next run.  Aren't we so clever?!!
+ */
+void CtdlScheduleRoomForDeletion(struct ctdlroom *qrbuf) {
+       char old_name[ROOMNAMELEN];
+       static int seq = 0;
+
+       syslog(LOG_NOTICE, "room_ops: scheduling room <%s> for deletion", qrbuf->QRname);
+
+       safestrncpy(old_name, qrbuf->QRname, sizeof old_name);
+       CtdlGetRoom(qrbuf, qrbuf->QRname);
+
+       /* Turn the room into a private mailbox owned by a user who doesn't
+        * exist.  This will immediately make the room invisible to everyone,
+        * and qualify the room for purging.
+        */
+       snprintf(qrbuf->QRname, sizeof qrbuf->QRname, "9999999999.%08lx.%04d.%s",
+               time(NULL),
+               ++seq,
+               old_name
+       );
+       qrbuf->QRflags |= QR_MAILBOX;
+       time(&qrbuf->QRgen);    /* Use a timestamp as the new generation number  */
+       CtdlPutRoom(qrbuf);
+       b_deleteroom(old_name);
+}
+
+
+/*
+ * Back end processing to delete a room and everything associated with it
+ * (This one is synchronous and should only get called by THE DREADED
+ * AUTO-PURGER in serv_expire.c.  All user-facing code should call
+ * the asynchronous schedule_room_for_deletion() instead.)
+ */
+void CtdlDeleteRoom(struct ctdlroom *qrbuf) {
+       struct floor flbuf;
+       char configdbkeyname[25];
+
+       syslog(LOG_NOTICE, "room_ops: deleting room <%s>", qrbuf->QRname);
+
+       // Delete the room's network configdb entry
+       netcfg_keyname(configdbkeyname, qrbuf->QRnumber);
+       CtdlDelConfig(configdbkeyname);
+
+       // Delete the messages in the room
+       // (Careful: this opens an S_ROOMS critical section!)
+       CtdlDeleteMessages(qrbuf->QRname, NULL, 0, "");
+
+       // Flag the room record as not in use
+       CtdlGetRoomLock(qrbuf, qrbuf->QRname);
+       qrbuf->QRflags = 0;
+       CtdlPutRoomLock(qrbuf);
+
+       // then decrement the reference count for the floor
+       lgetfloor(&flbuf, (int) (qrbuf->QRfloor));
+       flbuf.f_ref_count = flbuf.f_ref_count - 1;
+       lputfloor(&flbuf, (int) (qrbuf->QRfloor));
+
+       // Delete the room record from the database!
+       b_deleteroom(qrbuf->QRname);
+}
+
+
+/*
+ * Check access control for deleting a room
+ */
+int CtdlDoIHavePermissionToDeleteThisRoom(struct ctdlroom *qr) {
+
+       if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
+               return(0);
+       }
+
+       if (CtdlIsNonEditable(qr)) {
+               return(0);
+       }
+
+       /*
+        * For mailboxes, check stuff
+        */
+       if (qr->QRflags & QR_MAILBOX) {
+
+               if (strlen(qr->QRname) < 12) return(0); /* bad name */
+
+               if (atol(qr->QRname) != CC->user.usernum) {
+                       return(0);      /* not my room */
+               }
+
+               /* Can't delete your Mail> room */
+               if (!strcasecmp(&qr->QRname[11], MAILROOM)) return(0);
+
+               /* Otherwise it's ok */
+               return(1);
+       }
+
+       /*
+        * For normal rooms, just check for admin or room admin status.
+        */
+       return(is_room_aide());
+}
+
+
+/*
+ * Internal code to create a new room (returns room flags)
+ *
+ * Room types:  0=public, 1=hidden, 2=passworded, 3=invitation-only,
+ *              4=mailbox, 5=mailbox, but caller supplies namespace
+ */
+unsigned CtdlCreateRoom(char *new_room_name,
+                    int new_room_type,
+                    char *new_room_pass,
+                    int new_room_floor,
+                    int really_create,
+                    int avoid_access,
+                    int new_room_view)
+{
+       struct ctdlroom qrbuf;
+       struct floor flbuf;
+       visit vbuf;
+
+       syslog(LOG_DEBUG, "room_ops: CtdlCreateRoom(name=%s, type=%d, view=%d)", new_room_name, new_room_type, new_room_view);
+
+       if (CtdlGetRoom(&qrbuf, new_room_name) == 0) {
+               syslog(LOG_DEBUG, "room_ops: cannot create room <%s> - already exists", new_room_name);
+               return(0);
+       }
+
+       memset(&qrbuf, 0, sizeof(struct ctdlroom));
+       safestrncpy(qrbuf.QRpasswd, new_room_pass, sizeof qrbuf.QRpasswd);
+       qrbuf.QRflags = QR_INUSE;
+       if (new_room_type > 0)
+               qrbuf.QRflags = (qrbuf.QRflags | QR_PRIVATE);
+       if (new_room_type == 1)
+               qrbuf.QRflags = (qrbuf.QRflags | QR_GUESSNAME);
+       if (new_room_type == 2)
+               qrbuf.QRflags = (qrbuf.QRflags | QR_PASSWORDED);
+       if ( (new_room_type == 4) || (new_room_type == 5) ) {
+               qrbuf.QRflags = (qrbuf.QRflags | QR_MAILBOX);
+               /* qrbuf.QRflags2 |= QR2_SUBJECTREQ; */
+       }
+
+       /* If the user is requesting a personal room, set up the room
+        * name accordingly (prepend the user number)
+        */
+       if (new_room_type == 4) {
+               CtdlMailboxName(qrbuf.QRname, sizeof qrbuf.QRname, &CC->user, new_room_name);
+       }
+       else {
+               safestrncpy(qrbuf.QRname, new_room_name, sizeof qrbuf.QRname);
+       }
+
+       /* If the room is private, and the system administrator has elected
+        * to automatically grant room admin privileges, do so now.
+        */
+       if ((qrbuf.QRflags & QR_PRIVATE) && (CREATAIDE == 1)) {
+               qrbuf.QRroomaide = CC->user.usernum;
+       }
+       /* Blog owners automatically become room admins of their blogs.
+        * (In the future we will offer a site-wide configuration setting to suppress this behavior.)
+        */
+       else if (new_room_view == VIEW_BLOG) {
+               qrbuf.QRroomaide = CC->user.usernum;
+       }
+       /* Otherwise, set the room admin to undefined.
+        */
+       else {
+               qrbuf.QRroomaide = (-1L);
+       }
+
+       /* 
+        * If the caller is only interested in testing whether this will work,
+        * return now without creating the room.
+        */
+       if (!really_create) return (qrbuf.QRflags);
+
+       qrbuf.QRnumber = get_new_room_number();
+       qrbuf.QRhighest = 0L;   /* No messages in this room yet */
+       time(&qrbuf.QRgen);     /* Use a timestamp as the generation number */
+       qrbuf.QRfloor = new_room_floor;
+       qrbuf.QRdefaultview = new_room_view;
+
+       /* save what we just did... */
+       CtdlPutRoom(&qrbuf);
+
+       /* bump the reference count on whatever floor the room is on */
+       lgetfloor(&flbuf, (int) qrbuf.QRfloor);
+       flbuf.f_ref_count = flbuf.f_ref_count + 1;
+       lputfloor(&flbuf, (int) qrbuf.QRfloor);
+
+       /* Grant the creator access to the room unless the avoid_access
+        * parameter was specified.
+        */
+       if ( (CC->logged_in) && (avoid_access == 0) ) {
+               CtdlGetRelationship(&vbuf, &CC->user, &qrbuf);
+               vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
+               vbuf.v_flags = vbuf.v_flags | V_ACCESS;
+               CtdlSetRelationship(&vbuf, &CC->user, &qrbuf);
+       }
+
+       /* resume our happy day */
+       return (qrbuf.QRflags);
+}
diff --git a/citadel/server/room_ops.h b/citadel/server/room_ops.h
new file mode 100644 (file)
index 0000000..ba1aaf6
--- /dev/null
@@ -0,0 +1,21 @@
+int is_known (struct ctdlroom *roombuf, int roomnum, struct ctdluser *userbuf);
+int has_newmsgs (struct ctdlroom *roombuf, int roomnum, struct ctdluser *userbuf);
+int is_zapped (struct ctdlroom *roombuf, int roomnum, struct ctdluser *userbuf);
+void b_putroom(struct ctdlroom *qrbuf, char *room_name);
+void b_deleteroom(char *);
+void lgetfloor (struct floor *flbuf, int floor_num);
+void lputfloor (struct floor *flbuf, int floor_num);
+int sort_msglist (long int *listptrs, int oldcount);
+void list_roomname(struct ctdlroom *qrbuf, int ra, int current_view, int default_view);
+void convert_room_name_macros(char *towhere, size_t maxlen);
+
+typedef enum _POST_TYPE{
+       POST_LOGGED_IN,
+       POST_EXTERNAL,
+       CHECK_EXIST,
+       POST_LMTP
+} PostType;
+
+int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n, PostType PostPublic, int is_reply);
+int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void);
+int CtdlDoIHavePermissionToReadMessagesInThisRoom(void);
diff --git a/citadel/server/serv_extensions.c b/citadel/server/serv_extensions.c
new file mode 100644 (file)
index 0000000..6197d28
--- /dev/null
@@ -0,0 +1,909 @@
+/*
+ * Citadel Extension Loader
+ * Originally written by Brian Costello <btx@calyx.net>
+ *
+ * Copyright (c) 1987-2021 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <libcitadel.h>
+#include "sysdep_decls.h"
+#include "modules/crypto/serv_crypto.h"        /* Needed until a universal crypto startup hook is implimented for CtdlStartTLS */
+#include "serv_extensions.h"
+#include "ctdl_module.h"
+#include "config.h"
+
+
+/*
+ * Structure defentitions for hook tables
+ */
+
+typedef struct FixedOutputHook FixedOutputHook;
+struct FixedOutputHook {
+       FixedOutputHook *next;
+       char content_type[64];
+       void (*h_function_pointer) (char *, int);
+};
+FixedOutputHook *FixedOutputTable = NULL;
+
+
+/*
+ * SessionFunctionHook extensions are used for any type of hook for which
+ * the context in which it's being called (which is determined by the event
+ * type) will make it obvious for the hook function to know where to look for
+ * pertinent data.
+ */
+typedef struct SessionFunctionHook SessionFunctionHook;
+struct SessionFunctionHook {
+       SessionFunctionHook *next;
+       int Priority;
+       void (*h_function_pointer) (void);
+       int eventtype;
+};
+SessionFunctionHook *SessionHookTable = NULL;
+
+
+/*
+ * UserFunctionHook extensions are used for any type of hook which implements
+ * an operation on a user or username (potentially) other than the one
+ * operating the current session.
+ */
+typedef struct UserFunctionHook UserFunctionHook;
+struct UserFunctionHook {
+       UserFunctionHook *next;
+       void (*h_function_pointer) (struct ctdluser *usbuf);
+       int eventtype;
+};
+UserFunctionHook *UserHookTable = NULL;
+
+
+/*
+ * MessageFunctionHook extensions are used for hooks which implement handlers
+ * for various types of message operations (save, read, etc.)
+ */
+typedef struct MessageFunctionHook MessageFunctionHook;
+struct MessageFunctionHook {
+       MessageFunctionHook *next;
+       int (*h_function_pointer) (struct CtdlMessage *msg, struct recptypes *recps);
+       int eventtype;
+};
+MessageFunctionHook *MessageHookTable = NULL;
+
+
+/*
+ * DeleteFunctionHook extensions are used for hooks which get called when a
+ * message is about to be deleted.
+ */
+typedef struct DeleteFunctionHook DeleteFunctionHook;
+struct DeleteFunctionHook {
+       DeleteFunctionHook *next;
+       void (*h_function_pointer) (char *target_room, long msgnum);
+};
+DeleteFunctionHook *DeleteHookTable = NULL;
+
+
+/*
+ * ExpressMessageFunctionHook extensions are used for hooks which implement
+ * the sending of an instant message through various channels.  Any function
+ * registered should return the number of recipients to whom the message was
+ * successfully transmitted.
+ */
+typedef struct XmsgFunctionHook XmsgFunctionHook;
+struct XmsgFunctionHook {
+       XmsgFunctionHook *next;
+       int (*h_function_pointer) (char *, char *, char *, char *);
+       int order;
+};
+XmsgFunctionHook *XmsgHookTable = NULL;
+
+
+/*
+ * RoomFunctionHook extensions are used for hooks which impliment room
+ * processing functions when new messages are added.
+ */
+typedef struct RoomFunctionHook RoomFunctionHook;
+struct RoomFunctionHook {
+       RoomFunctionHook *next;
+       int (*fcn_ptr) (struct ctdlroom *);
+};
+RoomFunctionHook *RoomHookTable = NULL;
+
+
+typedef struct SearchFunctionHook SearchFunctionHook;
+struct SearchFunctionHook {
+       SearchFunctionHook *next;
+       void (*fcn_ptr) (int *, long **, const char *);
+       char *name;
+};
+SearchFunctionHook *SearchFunctionHookTable = NULL;
+
+ServiceFunctionHook *ServiceHookTable = NULL;
+
+typedef struct ProtoFunctionHook ProtoFunctionHook;
+struct ProtoFunctionHook {
+       void (*handler) (char *cmdbuf);
+       const char *cmd;
+       const char *desc;
+};
+
+HashList *ProtoHookList = NULL;
+
+
+#define ERR_PORT (1 << 1)
+
+
+static StrBuf *portlist = NULL;
+static StrBuf *errormessages = NULL;
+
+
+long   DetailErrorFlags;
+ConstStr Empty = {HKEY("")};
+char *ErrSubject = "Startup Problems";
+ConstStr ErrGeneral[] = {
+       {HKEY("Citadel had trouble on starting up. ")},
+       {HKEY(" This means, Citadel won't be the service provider for a specific service you configured it to.\n\n"
+             "If you don't want Citadel to provide these services, turn them off in WebCit via: ")},
+       {HKEY("To make both ways actualy take place restart the citserver with \"sendcommand down\"\n\n"
+             "The errors returned by the system were:\n")},
+       {HKEY("You can recheck the above if you follow this faq item:\n"
+             "http://www.citadel.org/doku.php?id=faq:mastering_your_os:net#netstat")}
+};
+
+ConstStr ErrPortShort = { HKEY("We couldn't bind all ports you configured to be provided by Citadel Server.\n")};
+ConstStr ErrPortWhere = { HKEY("\"Admin->System Preferences->Network\".\n\nThe failed ports and sockets are: ")};
+ConstStr ErrPortHint  = { HKEY("If you want Citadel to provide you with that functionality, "
+                              "check the output of \"netstat -lnp\" on Linux, or \"netstat -na\" on BSD"
+                              " and disable the program that binds these ports.\n")};
+
+
+void LogPrintMessages(long err)
+{
+       StrBuf *Message;
+       StrBuf *List, *DetailList;
+       ConstStr *Short, *Where, *Hint; 
+
+       
+       Message = NewStrBufPlain(NULL, StrLength(portlist) + StrLength(errormessages));
+       
+       DetailErrorFlags = DetailErrorFlags & ~err;
+
+       switch (err)
+       {
+       case ERR_PORT:
+               Short = &ErrPortShort;
+               Where = &ErrPortWhere;
+               Hint  = &ErrPortHint;
+               List  = portlist;
+               DetailList = errormessages;
+               break;
+       default:
+               Short = &Empty;
+               Where = &Empty;
+               Hint  = &Empty;
+               List  = NULL;
+               DetailList = NULL;
+       }
+
+       StrBufAppendBufPlain(Message, CKEY(ErrGeneral[0]), 0);
+       StrBufAppendBufPlain(Message, CKEY(*Short), 0); 
+       StrBufAppendBufPlain(Message, CKEY(ErrGeneral[1]), 0);
+       StrBufAppendBufPlain(Message, CKEY(*Where), 0);
+       StrBufAppendBuf(Message, List, 0);
+       StrBufAppendBufPlain(Message, HKEY("\n\n"), 0);
+       StrBufAppendBufPlain(Message, CKEY(*Hint), 0);
+       StrBufAppendBufPlain(Message, HKEY("\n\n"), 0);
+       StrBufAppendBufPlain(Message, CKEY(ErrGeneral[2]), 0);
+       StrBufAppendBuf(Message, DetailList, 0);
+       StrBufAppendBufPlain(Message, HKEY("\n\n"), 0);
+       StrBufAppendBufPlain(Message, CKEY(ErrGeneral[3]), 0);
+
+       syslog(LOG_ERR, "extensions: %s", ChrPtr(Message));
+       syslog(LOG_ERR, "extensions: %s", ErrSubject);
+       quickie_message("Citadel", NULL, NULL, AIDEROOM, ChrPtr(Message), FMT_FIXED, ErrSubject);
+
+       FreeStrBuf(&Message);
+       FreeStrBuf(&List);
+       FreeStrBuf(&DetailList);
+}
+
+
+void AddPortError(char *Port, char *ErrorMessage)
+{
+       long len;
+
+       DetailErrorFlags |= ERR_PORT;
+
+       len = StrLength(errormessages);
+       if (len > 0) StrBufAppendBufPlain(errormessages, HKEY("; "), 0);
+       else errormessages = NewStrBuf();
+       StrBufAppendBufPlain(errormessages, ErrorMessage, -1, 0);
+
+
+       len = StrLength(portlist);
+       if (len > 0) StrBufAppendBufPlain(portlist, HKEY(";"), 0);
+       else portlist = NewStrBuf();
+       StrBufAppendBufPlain(portlist, Port, -1, 0);
+}
+
+
+int DLoader_Exec_Cmd(char *cmdbuf)
+{
+       void *vP;
+       ProtoFunctionHook *p;
+
+       if (GetHash(ProtoHookList, cmdbuf, 4, &vP) && (vP != NULL)) {
+               p = (ProtoFunctionHook*) vP;
+               p->handler(&cmdbuf[5]);
+               return 1;
+       }
+       return 0;
+}
+
+
+void CtdlRegisterProtoHook(void (*handler) (char *), char *cmd, char *desc)
+{
+       ProtoFunctionHook *p;
+
+       if (ProtoHookList == NULL)
+               ProtoHookList = NewHash (1, FourHash);
+
+
+       p = (ProtoFunctionHook *)
+               malloc(sizeof(ProtoFunctionHook));
+
+       if (p == NULL) {
+               fprintf(stderr, "can't malloc new ProtoFunctionHook\n");
+               exit(EXIT_FAILURE);
+       }
+       p->handler = handler;
+       p->cmd = cmd;
+       p->desc = desc;
+
+       Put(ProtoHookList, cmd, 4, p, NULL);
+       syslog(LOG_DEBUG, "extensions: registered server command %s (%s)", cmd, desc);
+}
+
+
+void CtdlRegisterSessionHook(void (*fcn_ptr) (void), int EventType, int Priority)
+{
+       SessionFunctionHook *newfcn;
+
+       newfcn = (SessionFunctionHook *)
+           malloc(sizeof(SessionFunctionHook));
+       newfcn->Priority = Priority;
+       newfcn->h_function_pointer = fcn_ptr;
+       newfcn->eventtype = EventType;
+
+       SessionFunctionHook **pfcn;
+       pfcn = &SessionHookTable;
+       while ((*pfcn != NULL) && 
+              ((*pfcn)->Priority < newfcn->Priority) &&
+              ((*pfcn)->next != NULL))
+               pfcn = &(*pfcn)->next;
+               
+       newfcn->next = *pfcn;
+       *pfcn = newfcn;
+       
+       syslog(LOG_DEBUG, "extensions: registered a new session function (type %d Priority %d)", EventType, Priority);
+}
+
+
+void CtdlUnregisterSessionHook(void (*fcn_ptr) (void), int EventType)
+{
+       SessionFunctionHook *cur, *p, *last;
+       last = NULL;
+       cur = SessionHookTable;
+       while  (cur != NULL) {
+               if ((fcn_ptr == cur->h_function_pointer) &&
+                   (EventType == cur->eventtype))
+               {
+                       syslog(LOG_DEBUG, "extensions: unregistered session function (type %d)", EventType);
+                       p = cur->next;
+
+                       free(cur);
+                       cur = NULL;
+
+                       if (last != NULL)
+                               last->next = p;
+                       else 
+                               SessionHookTable = p;
+                       cur = p;
+               }
+               else {
+                       last = cur;
+                       cur = cur->next;
+               }
+       }
+}
+
+
+void CtdlRegisterUserHook(void (*fcn_ptr) (ctdluser *), int EventType)
+{
+
+       UserFunctionHook *newfcn;
+
+       newfcn = (UserFunctionHook *)
+           malloc(sizeof(UserFunctionHook));
+       newfcn->next = UserHookTable;
+       newfcn->h_function_pointer = fcn_ptr;
+       newfcn->eventtype = EventType;
+       UserHookTable = newfcn;
+
+       syslog(LOG_DEBUG, "extensions: registered a new user function (type %d)",
+                  EventType);
+}
+
+
+void CtdlUnregisterUserHook(void (*fcn_ptr) (struct ctdluser *), int EventType)
+{
+       UserFunctionHook *cur, *p, *last;
+       last = NULL;
+       cur = UserHookTable;
+       while (cur != NULL) {
+               if ((fcn_ptr == cur->h_function_pointer) &&
+                   (EventType == cur->eventtype))
+               {
+                       syslog(LOG_DEBUG, "extensions: unregistered user function (type %d)", EventType);
+                       p = cur->next;
+
+                       free(cur);
+                       cur = NULL;
+
+                       if (last != NULL)
+                               last->next = p;
+                       else 
+                               UserHookTable = p;
+                       cur = p;
+               }
+               else {
+                       last = cur;
+                       cur = cur->next;
+               }
+       }
+}
+
+
+void CtdlRegisterMessageHook(int (*handler)(struct CtdlMessage *, struct recptypes *), int EventType)
+{
+
+       MessageFunctionHook *newfcn;
+
+       newfcn = (MessageFunctionHook *)
+           malloc(sizeof(MessageFunctionHook));
+       newfcn->next = MessageHookTable;
+       newfcn->h_function_pointer = handler;
+       newfcn->eventtype = EventType;
+       MessageHookTable = newfcn;
+
+       syslog(LOG_DEBUG, "extensions: registered a new message function (type %d)", EventType);
+}
+
+
+void CtdlUnregisterMessageHook(int (*handler)(struct CtdlMessage *, struct recptypes *), int EventType)
+{
+       MessageFunctionHook *cur, *p, *last;
+       last = NULL;
+       cur = MessageHookTable;
+       while (cur != NULL) {
+               if ((handler == cur->h_function_pointer) &&
+                   (EventType == cur->eventtype))
+               {
+                       syslog(LOG_DEBUG, "extensions: unregistered message function (type %d)", EventType);
+                       p = cur->next;
+                       free(cur);
+                       cur = NULL;
+
+                       if (last != NULL)
+                               last->next = p;
+                       else 
+                               MessageHookTable = p;
+                       cur = p;
+               }
+               else {
+                       last = cur;
+                       cur = cur->next;
+               }
+       }
+}
+
+
+void CtdlRegisterRoomHook(int (*fcn_ptr)(struct ctdlroom *))
+{
+       RoomFunctionHook *newfcn;
+
+       newfcn = (RoomFunctionHook *)
+           malloc(sizeof(RoomFunctionHook));
+       newfcn->next = RoomHookTable;
+       newfcn->fcn_ptr = fcn_ptr;
+       RoomHookTable = newfcn;
+
+       syslog(LOG_DEBUG, "extensions: registered a new room function");
+}
+
+
+void CtdlUnregisterRoomHook(int (*fcn_ptr)(struct ctdlroom *))
+{
+       RoomFunctionHook *cur, *p, *last;
+       last = NULL;
+       cur = RoomHookTable;
+       while (cur != NULL)
+       {
+               if (fcn_ptr == cur->fcn_ptr) {
+                       syslog(LOG_DEBUG, "extensions: unregistered room function");
+                       p = cur->next;
+
+                       free(cur);
+                       cur = NULL;
+
+                       if (last != NULL)
+                               last->next = p;
+                       else 
+                               RoomHookTable = p;
+                       cur = p;
+               }
+               else {
+                       last = cur;
+                       cur = cur->next;
+               }
+       }
+}
+
+
+void CtdlRegisterDeleteHook(void (*handler)(char *, long) )
+{
+       DeleteFunctionHook *newfcn;
+
+       newfcn = (DeleteFunctionHook *)
+           malloc(sizeof(DeleteFunctionHook));
+       newfcn->next = DeleteHookTable;
+       newfcn->h_function_pointer = handler;
+       DeleteHookTable = newfcn;
+
+       syslog(LOG_DEBUG, "extensions: registered a new delete function");
+}
+
+
+void CtdlUnregisterDeleteHook(void (*handler)(char *, long) )
+{
+       DeleteFunctionHook *cur, *p, *last;
+
+       last = NULL;
+       cur = DeleteHookTable;
+       while (cur != NULL) {
+               if (handler == cur->h_function_pointer )
+               {
+                       syslog(LOG_DEBUG, "extensions: unregistered delete function");
+                       p = cur->next;
+                       free(cur);
+
+                       if (last != NULL)
+                               last->next = p;
+                       else
+                               DeleteHookTable = p;
+
+                       cur = p;
+               }
+               else {
+                       last = cur;
+                       cur = cur->next;
+               }
+       }
+}
+
+
+void CtdlRegisterFixedOutputHook(char *content_type, void (*handler)(char *, int) )
+{
+       FixedOutputHook *newfcn;
+
+       newfcn = (FixedOutputHook *)
+           malloc(sizeof(FixedOutputHook));
+       newfcn->next = FixedOutputTable;
+       newfcn->h_function_pointer = handler;
+       safestrncpy(newfcn->content_type, content_type, sizeof newfcn->content_type);
+       FixedOutputTable = newfcn;
+
+       syslog(LOG_DEBUG, "extensions: registered a new fixed output function for %s", newfcn->content_type);
+}
+
+
+void CtdlUnregisterFixedOutputHook(char *content_type)
+{
+       FixedOutputHook *cur, *p, *last;
+
+       last = NULL;
+       cur = FixedOutputTable;
+       while (cur != NULL) {
+               /* This will also remove duplicates if any */
+               if (!strcasecmp(content_type, cur->content_type)) {
+                       syslog(LOG_DEBUG, "extensions: unregistered fixed output function for %s", content_type);
+                       p = cur->next;
+                       free(cur);
+
+                       if (last != NULL)
+                               last->next = p;
+                       else
+                               FixedOutputTable = p;
+                       
+                       cur = p;
+               }
+               else
+               {
+                       last = cur;
+                       cur = cur->next;
+               }
+       }
+}
+
+
+/* returns nonzero if we found a hook and used it */
+int PerformFixedOutputHooks(char *content_type, char *content, int content_length)
+{
+       FixedOutputHook *fcn;
+
+       for (fcn = FixedOutputTable; fcn != NULL; fcn = fcn->next) {
+               if (!strcasecmp(content_type, fcn->content_type)) {
+                       (*fcn->h_function_pointer) (content, content_length);
+                       return(1);
+               }
+       }
+       return(0);
+}
+
+
+void CtdlRegisterXmsgHook(int (*fcn_ptr) (char *, char *, char *, char *), int order)
+{
+
+       XmsgFunctionHook *newfcn;
+
+       newfcn = (XmsgFunctionHook *) malloc(sizeof(XmsgFunctionHook));
+       newfcn->next = XmsgHookTable;
+       newfcn->order = order;
+       newfcn->h_function_pointer = fcn_ptr;
+       XmsgHookTable = newfcn;
+       syslog(LOG_DEBUG, "extensions: registered a new x-msg function (priority %d)", order);
+}
+
+
+void CtdlUnregisterXmsgHook(int (*fcn_ptr) (char *, char *, char *, char *), int order)
+{
+       XmsgFunctionHook *cur, *p, *last;
+
+       last = NULL;
+       cur = XmsgHookTable;
+       while (cur != NULL) {
+               /* This will also remove duplicates if any */
+               if (fcn_ptr == cur->h_function_pointer &&
+                   order == cur->order) {
+                       syslog(LOG_DEBUG, "extensions: unregistered x-msg function (priority %d)", order);
+                       p = cur->next;
+                       free(cur);
+
+                       if (last != NULL) {
+                               last->next = p;
+                       }
+                       else {
+                               XmsgHookTable = p;
+                       }
+                       cur = p;
+               }
+               else {
+                       last = cur;
+                       cur = cur->next;
+               }
+       }
+}
+
+
+void CtdlRegisterServiceHook(int tcp_port,
+                            char *sockpath,
+                            void (*h_greeting_function) (void),
+                            void (*h_command_function) (void),
+                            void (*h_async_function) (void),
+                            const char *ServiceName)
+{
+       ServiceFunctionHook *newfcn;
+       char *message;
+
+       newfcn = (ServiceFunctionHook *) malloc(sizeof(ServiceFunctionHook));
+       message = (char*) malloc (SIZ + SIZ);
+       
+       newfcn->next = ServiceHookTable;
+       newfcn->tcp_port = tcp_port;
+       newfcn->sockpath = sockpath;
+       newfcn->h_greeting_function = h_greeting_function;
+       newfcn->h_command_function = h_command_function;
+       newfcn->h_async_function = h_async_function;
+       newfcn->ServiceName = ServiceName;
+
+       if (sockpath != NULL) {
+               newfcn->msock = ctdl_uds_server(sockpath, CtdlGetConfigInt("c_maxsessions"));
+               snprintf(message, SIZ, "extensions: unix domain socket '%s': ", sockpath);
+       }
+       else if (tcp_port <= 0) {       /* port -1 to disable */
+               syslog(LOG_INFO, "extensions: service %s has been manually disabled, skipping", ServiceName);
+               free (message);
+               free(newfcn);
+               return;
+       }
+       else {
+               newfcn->msock = ctdl_tcp_server(CtdlGetConfigStr("c_ip_addr"), tcp_port, CtdlGetConfigInt("c_maxsessions"));
+               snprintf(message, SIZ, "extensions: TCP port %s:%d: (%s) ", 
+                        CtdlGetConfigStr("c_ip_addr"), tcp_port, ServiceName);
+       }
+
+       if (newfcn->msock > 0) {
+               ServiceHookTable = newfcn;
+               strcat(message, "registered.");
+               syslog(LOG_INFO, "%s", message);
+       }
+       else {
+               AddPortError(message, "failed");
+               strcat(message, "FAILED.");
+               syslog(LOG_ERR, "%s", message);
+               free(newfcn);
+       }
+       free(message);
+}
+
+
+void CtdlUnregisterServiceHook(int tcp_port, char *sockpath,
+                       void (*h_greeting_function) (void),
+                       void (*h_command_function) (void),
+                       void (*h_async_function) (void)
+                       )
+{
+       ServiceFunctionHook *cur, *p, *last;
+
+       last = NULL;
+       cur = ServiceHookTable;
+       while (cur != NULL) {
+               /* This will also remove duplicates if any */
+               if (h_greeting_function == cur->h_greeting_function &&
+                   h_command_function == cur->h_command_function &&
+                   h_async_function == cur->h_async_function &&
+                   tcp_port == cur->tcp_port && 
+                   !(sockpath && cur->sockpath && strcmp(sockpath, cur->sockpath)) )
+               {
+                       if (cur->msock > 0)
+                               close(cur->msock);
+                       if (sockpath) {
+                               syslog(LOG_INFO, "extensions: closed UNIX domain socket %s", sockpath);
+                               unlink(sockpath);
+                       } else if (tcp_port) {
+                               syslog(LOG_INFO, "extensions: closed TCP port %d", tcp_port);
+                       } else {
+                               syslog(LOG_INFO, "extensions: unregistered service \"%s\"", cur->ServiceName);
+                       }
+                       p = cur->next;
+                       free(cur);
+                       if (last != NULL)
+                               last->next = p;
+                       else
+                               ServiceHookTable = p;
+                       cur = p;
+               }
+               else {
+                       last = cur;
+                       cur = cur->next;
+               }
+       }
+}
+
+
+void CtdlShutdownServiceHooks(void)
+{
+       /* sort of a duplicate of close_masters() but called earlier */
+       ServiceFunctionHook *cur;
+
+       cur = ServiceHookTable;
+       while (cur != NULL) 
+       {
+               if (cur->msock != -1)
+               {
+                       close(cur->msock);
+                       cur->msock = -1;
+                       if (cur->sockpath != NULL){
+                               syslog(LOG_INFO, "extensions: [%s] Closed UNIX domain socket %s", cur->ServiceName, cur->sockpath);
+                               unlink(cur->sockpath);
+                       } else {
+                               syslog(LOG_INFO, "extensions: [%s] closing service", cur->ServiceName);
+                       }
+               }
+               cur = cur->next;
+       }
+}
+
+
+void CtdlRegisterSearchFuncHook(void (*fcn_ptr)(int *, long **, const char *), char *name)
+{
+       SearchFunctionHook *newfcn;
+
+       if (!name || !fcn_ptr) {
+               return;
+       }
+       
+       newfcn = (SearchFunctionHook *)
+           malloc(sizeof(SearchFunctionHook));
+       newfcn->next = SearchFunctionHookTable;
+       newfcn->name = name;
+       newfcn->fcn_ptr = fcn_ptr;
+       SearchFunctionHookTable = newfcn;
+
+       syslog(LOG_DEBUG, "extensions: registered a new search function (%s)", name);
+}
+
+void CtdlUnregisterSearchFuncHook(void (*fcn_ptr)(int *, long **, const char *), char *name)
+{
+       SearchFunctionHook *cur, *p, *last;
+       
+       last = NULL;
+       cur = SearchFunctionHookTable;
+       while (cur != NULL) {
+               if (fcn_ptr &&
+                   (cur->fcn_ptr == fcn_ptr) &&
+                   name && !strcmp(name, cur->name))
+               {
+                       syslog(LOG_DEBUG, "extensions: unregistered search function(%s)", name);
+                       p = cur->next;
+                       free (cur);
+                       if (last != NULL)
+                               last->next = p;
+                       else
+                               SearchFunctionHookTable = p;
+                       cur = p;
+               }
+               else {
+                       last = cur;
+                       cur = cur->next;
+               }
+       }
+}
+
+
+void CtdlModuleDoSearch(int *num_msgs, long **search_msgs, const char *search_string, const char *func_name)
+{
+       SearchFunctionHook *fcn = NULL;
+
+       for (fcn = SearchFunctionHookTable; fcn != NULL; fcn = fcn->next) {
+               if (!func_name || !strcmp(func_name, fcn->name)) {
+                       (*fcn->fcn_ptr) (num_msgs, search_msgs, search_string);
+                       return;
+               }
+       }
+       *num_msgs = 0;
+}
+
+
+void PerformSessionHooks(int EventType)
+{
+       SessionFunctionHook *fcn = NULL;
+
+       for (fcn = SessionHookTable; fcn != NULL; fcn = fcn->next) {
+               if (fcn->eventtype == EventType) {
+                       if (EventType == EVT_TIMER) {
+                               pthread_setspecific(MyConKey, NULL);    /* for every hook */
+                       }
+                       (*fcn->h_function_pointer) ();
+               }
+       }
+}
+
+void PerformUserHooks(ctdluser *usbuf, int EventType)
+{
+       UserFunctionHook *fcn = NULL;
+
+       for (fcn = UserHookTable; fcn != NULL; fcn = fcn->next) {
+               if (fcn->eventtype == EventType) {
+                       (*fcn->h_function_pointer) (usbuf);
+               }
+       }
+}
+
+int PerformMessageHooks(struct CtdlMessage *msg, struct recptypes *recps, int EventType)
+{
+       MessageFunctionHook *fcn = NULL;
+       int total_retval = 0;
+
+       /* Other code may elect to protect this message from server-side
+        * handlers; if this is the case, don't do anything.
+        */
+       if (msg->cm_flags & CM_SKIP_HOOKS) {
+               return(0);
+       }
+
+       /* Otherwise, run all the hooks appropriate to this event type.
+        */
+       for (fcn = MessageHookTable; fcn != NULL; fcn = fcn->next) {
+               if (fcn->eventtype == EventType) {
+                       total_retval = total_retval + (*fcn->h_function_pointer) (msg, recps);
+               }
+       }
+
+       /* Return the sum of the return codes from the hook functions.  If
+        * this is an EVT_BEFORESAVE event, a nonzero return code will cause
+        * the save operation to abort.
+        */
+       return total_retval;
+}
+
+
+int PerformRoomHooks(struct ctdlroom *target_room)
+{
+       RoomFunctionHook *fcn;
+       int total_retval = 0;
+
+       syslog(LOG_DEBUG, "extensions: performing room hooks for <%s>", target_room->QRname);
+
+       for (fcn = RoomHookTable; fcn != NULL; fcn = fcn->next) {
+               total_retval = total_retval + (*fcn->fcn_ptr) (target_room);
+       }
+
+       /* Return the sum of the return codes from the hook functions.
+        */
+       return total_retval;
+}
+
+
+void PerformDeleteHooks(char *room, long msgnum)
+{
+       DeleteFunctionHook *fcn;
+
+       for (fcn = DeleteHookTable; fcn != NULL; fcn = fcn->next) {
+               (*fcn->h_function_pointer) (room, msgnum);
+       }
+}
+
+
+int PerformXmsgHooks(char *sender, char *sender_email, char *recp, char *msg)
+{
+       XmsgFunctionHook *fcn;
+       int total_sent = 0;
+       int p;
+
+       for (p=0; p<MAX_XMSG_PRI; ++p) {
+               for (fcn = XmsgHookTable; fcn != NULL; fcn = fcn->next) {
+                       if (fcn->order == p) {
+                               total_sent +=
+                                       (*fcn->h_function_pointer)
+                                               (sender, sender_email, recp, msg);
+                       }
+               }
+               /* Break out of the loop if a higher-priority function
+                * successfully delivered the message.  This prevents duplicate
+                * deliveries to local users simultaneously signed onto
+                * remote services.
+                */
+               if (total_sent) break;
+       }
+       return total_sent;
+}
+
+
+/*
+ * Dirty hack until we impliment a hook mechanism for this
+ */
+void CtdlModuleStartCryptoMsgs(char *ok_response, char *nosup_response, char *error_response)
+{
+#ifdef HAVE_OPENSSL
+       CtdlStartTLS (ok_response, nosup_response, error_response);
+#endif
+}
+
+
+CTDL_MODULE_INIT(modules)
+{
+       if (!threading) {
+       }
+       return "modules";
+}
diff --git a/citadel/server/serv_extensions.h b/citadel/server/serv_extensions.h
new file mode 100644 (file)
index 0000000..c8308cf
--- /dev/null
@@ -0,0 +1,48 @@
+
+#ifndef SERV_EXTENSIONS_H
+#define SERV_EXTENSIONS_H
+
+#include "server.h"
+
+/*
+ * ServiceFunctionHook extensions are used for hooks which implement various
+ * protocols (either on TCP or on unix domain sockets) directly in the Citadel server.
+ */
+typedef struct ServiceFunctionHook ServiceFunctionHook;
+struct ServiceFunctionHook {
+       ServiceFunctionHook *next;
+       int tcp_port;
+       char *sockpath;
+       void (*h_greeting_function) (void) ;
+       void (*h_command_function) (void) ;
+       void (*h_async_function) (void) ;
+       int msock;
+       const char* ServiceName; /* this is just for debugging and logging purposes. */
+};
+extern ServiceFunctionHook *ServiceHookTable;
+
+void initialize_server_extensions(void);
+int DLoader_Exec_Cmd(char *cmdbuf);
+char *Dynamic_Module_Init(void);
+
+void PerformSessionHooks(int EventType);
+
+void PerformUserHooks(struct ctdluser *usbuf, int EventType);
+
+int PerformXmsgHooks(char *, char *, char *, char *);
+
+int PerformMessageHooks(struct CtdlMessage *, struct recptypes *recps, int EventType);
+
+int PerformRoomHooks(struct ctdlroom *);
+
+void PerformDeleteHooks(char *, long);
+
+
+
+
+
+int PerformFixedOutputHooks(char *, char *, int);
+
+void netcfg_keyname(char *keybuf, long roomnum);
+
+#endif /* SERV_EXTENSIONS_H */
diff --git a/citadel/server/serv_vcard.h b/citadel/server/serv_vcard.h
new file mode 100644 (file)
index 0000000..5e4b68f
--- /dev/null
@@ -0,0 +1,8 @@
+void extract_inet_email_addrs(char *, size_t, char *, size_t, struct vCard *v, int local_addrs_only);
+struct vCard *vcard_get_user(struct ctdluser *u);
+
+enum {
+       V2L_WRITE,
+       V2L_DELETE
+};
+
diff --git a/citadel/server/server.h b/citadel/server/server.h
new file mode 100644 (file)
index 0000000..aa038f4
--- /dev/null
@@ -0,0 +1,331 @@
+/* 
+ * Main declarations file for the Citadel server
+ *
+ * Copyright (c) 1987-2022 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef SERVER_H
+#define SERVER_H
+
+#ifdef __GNUC__
+#define INLINE __inline__
+#else
+#define INLINE
+#endif
+
+#include "citadel.h"
+#ifdef HAVE_OPENSSL
+#define OPENSSL_NO_KRB5                /* work around redhat b0rken ssl headers */
+#include <openssl/ssl.h>
+#endif
+
+/*
+ * New format for a message in memory
+ */
+struct CtdlMessage {
+       int cm_magic;                   /* Self-check (NOT SAVED TO DISK) */
+       char cm_anon_type;              /* Anonymous or author-visible */
+       char cm_format_type;            /* Format type */
+       char *cm_fields[256];           /* Data fields */
+       long cm_lengths[256];           /* size of datafields */
+       unsigned int cm_flags;          /* How to handle (NOT SAVED TO DISK) */
+};
+
+#define        CTDLMESSAGE_MAGIC               0x159d
+#define        CM_SKIP_HOOKS   0x01            /* Don't run server-side handlers */
+
+
+/* Data structure returned by validate_recipients() */
+struct recptypes {
+       int recptypes_magic;
+        int num_local;
+        int num_internet;
+       int num_room;
+        int num_error;
+       char *errormsg;
+       char *recp_local;
+       char *recp_internet;
+       char *recp_room;
+       char *recp_orgroom;
+       char *display_recp;
+       char *bounce_to;
+       char *envelope_from;
+       char *sending_room;
+};
+
+#define RECPTYPES_MAGIC 0xfeeb
+
+#define CTDLEXIT_SHUTDOWN      0       // Normal shutdown; do NOT auto-restart
+
+/*
+ * Exit codes 101 through 109 are used for conditions in which
+ * we deliberately do NOT want the service to automatically
+ * restart.
+ */
+#define CTDLEXIT_CONFIG                101     // Could not read system configuration
+#define CTDLEXIT_HOME          103     // Citadel home directory not found
+#define CTDLEXIT_DB            105     // Unable to initialize database
+#define CTDLEXIT_LIBCITADEL    106     // Incorrect version of libcitadel
+#define CTDL_EXIT_UNSUP_AUTH   107     // Unsupported auth mode configured
+#define CTDLEXIT_UNUSER                108     // Could not determine uid to run as
+#define CTDLEXIT_CRYPTO                109     // Problem initializing SSL or TLS
+
+/*
+ * Reasons why a session would be terminated (set CC->kill_me to these values)
+ */
+enum {
+       KILLME_NOT,
+       KILLME_UNKNOWN,
+       KILLME_CLIENT_LOGGED_OUT,
+       KILLME_IDLE,
+       KILLME_CLIENT_DISCONNECTED,
+       KILLME_AUTHFAILED,
+       KILLME_SERVER_SHUTTING_DOWN,
+       KILLME_MAX_SESSIONS_EXCEEDED,
+       KILLME_ADMIN_TERMINATE,
+       KILLME_SELECT_INTERRUPTED,
+       KILLME_SELECT_FAILED,
+       KILLME_WRITE_FAILED,
+       KILLME_SIMULATION_WORKER,
+       KILLME_NOLOGIN,
+       KILLME_NO_CRYPTO,
+       KILLME_READSTRING_FAILED,
+       KILLME_MALLOC_FAILED,
+       KILLME_QUOTA,
+       KILLME_READ_FAILED,
+       KILLME_SPAMMER,
+       KILLME_XML_PARSER
+};
+
+
+#define CS_STEALTH     1       /* stealth mode */
+#define CS_CHAT                2       /* chat mode */
+#define CS_POSTING     4       /* Posting */
+
+extern int ScheduledShutdown;
+extern uid_t ctdluid;
+extern int sanity_diag_mode;
+
+struct ExpressMessage {
+       struct ExpressMessage *next;
+       time_t timestamp;       /* When this message was sent */
+       unsigned flags;         /* Special instructions */
+       char sender[256];       /* Name of sending user */
+       char sender_email[256]; /* Email or JID of sending user */
+       char *text;             /* Message text (if applicable) */
+};
+
+#define EM_BROADCAST   1       /* Broadcast message */
+#define EM_GO_AWAY     2       /* Server requests client log off */
+#define EM_CHAT                4       /* Server requests client enter chat */
+
+/*
+ * Various things we need to lock and unlock
+ */
+enum {
+       S_USERS,
+       S_ROOMS,
+       S_SESSION_TABLE,
+       S_FLOORTAB,
+       S_CHATQUEUE,
+       S_CONTROL,
+       S_NETDB,
+       S_SUPPMSGMAIN,
+       S_CONFIG,
+       S_HOUSEKEEPING,
+       S_DIRECTORY,
+       S_NETCONFIGS,
+       S_FLOORCACHE,
+       S_ATBF,
+       S_JOURNAL_QUEUE,
+       S_CHKPWD,
+       S_LOG,
+       S_NETSPOOL,
+       S_XMPP_QUEUE,
+       S_SCHEDULE_LIST,
+       S_SINGLE_USER,
+       S_LDAP,
+       S_IM_LOGS,
+       S_OPENSSL,
+       MAX_SEMAPHORES
+};
+
+
+/*
+ * message transfer formats
+ */
+enum {
+       MT_CITADEL,             /* Citadel proprietary */
+       MT_RFC822,              /* RFC822 */
+       MT_MIME,                /* MIME-formatted message */
+       MT_DOWNLOAD,            /* Download a component */
+       MT_SPEW_SECTION         /* Download a component in a single operation */
+};
+
+/*
+ * Message format types in the database
+ */
+#define FMT_CITADEL    0       /* Citadel vari-format (proprietary) */
+#define FMT_FIXED      1       /* Fixed format (proprietary)        */
+#define FMT_RFC822     4       /* Standard (headers are in M field) */
+
+
+/*
+ * Citadel DataBases (define one for each cdb we need to open)
+ */
+enum {
+       CDB_MSGMAIN,            /* message base                  */
+       CDB_USERS,              /* user file                     */
+       CDB_ROOMS,              /* room index                    */
+       CDB_FLOORTAB,           /* floor index                   */
+       CDB_MSGLISTS,           /* room message lists            */
+       CDB_VISIT,              /* user/room relationships       */
+       CDB_DIRECTORY,          /* address book directory        */
+       CDB_USETABLE,           /* network use table             */
+       CDB_BIGMSGS,            /* larger message bodies         */
+       CDB_FULLTEXT,           /* full text search index        */
+       CDB_EUIDINDEX,          /* locate msgs by EUID           */
+       CDB_USERSBYNUMBER,      /* index of users by number      */
+       CDB_EXTAUTH,            /* associates OpenIDs with users */
+       CDB_CONFIG,             /* system configuration database */
+       MAXCDB                  /* total number of CDB's defined */
+};
+
+struct cdbdata {
+       size_t len;
+       char *ptr;
+};
+
+
+/*
+ * Event types can't be enum'ed, because they must remain consistent between
+ * builds (to allow for binary modules built somewhere else)
+ */
+#define EVT_STOP       0       /* Session is terminating */
+#define EVT_START      1       /* Session is starting */
+#define EVT_LOGIN      2       /* A user is logging in */
+#define EVT_NEWROOM    3       /* Changing rooms */
+#define EVT_LOGOUT     4       /* A user is logging out */
+#define EVT_SETPASS    5       /* Setting or changing password */
+#define EVT_CMD                6       /* Called after each server command */
+#define EVT_RWHO       7       /* An RWHO command is being executed */
+#define EVT_ASYNC      8       /* Doing asynchronous messages */
+#define EVT_STEALTH    9       /* Entering stealth mode */
+#define EVT_UNSTEALTH  10      /* Exiting stealth mode */
+
+#define EVT_TIMER      50      /* Timer events are called once per minute
+                                  and are not tied to any session */
+#define EVT_HOUSE      51      /* as needed houskeeping stuff */
+#define EVT_SHUTDOWN   52      /* Server is shutting down */
+
+#define EVT_PURGEUSER  100     /* Deleting a user */
+#define EVT_NEWUSER    102     /* Creating a user */
+
+#define EVT_BEFORESAVE 201
+#define EVT_AFTERSAVE  202
+#define EVT_SMTPSCAN   203     /* called before submitting a msg from SMTP */
+#define EVT_AFTERUSRMBOXSAVE 204 /* called afte a message was saved into a users inbox */
+/* Priority levels for paging functions (lower is better) */
+enum {
+       XMSG_PRI_LOCAL,         /* Other users on -this- server */
+       XMSG_PRI_REMOTE,        /* Other users on a Citadel network (future) */
+       XMSG_PRI_FOREIGN,       /* Contacts on foreign instant message hosts */
+       MAX_XMSG_PRI
+};
+
+
+/* Defines the relationship of a user to a particular room */
+typedef struct __visit {
+       long v_roomnum;
+       long v_roomgen;
+       long v_usernum;
+       long v_lastseen;
+       unsigned int v_flags;
+       char v_seen[SIZ];
+       char v_answered[SIZ];
+       int v_view;
+} visit;
+
+#define V_FORGET       1       /* User has zapped this room        */
+#define V_LOCKOUT      2       /* User is locked out of this room  */
+#define V_ACCESS       4       /* Access is granted to this room   */
+
+
+/* Supplementary data for a message on disk
+ * These are kept separate from the message itself for one of two reasons:
+ * 1. Either their values may change at some point after initial save, or
+ * 2. They are merely caches of data which exist somewhere else, for speed.
+ * DO NOT PUT BIG DATA IN HERE ... we need this struct to be tiny for lots of quick r/w
+ */
+struct MetaData {
+       long meta_msgnum;               /* Message number in *local* message base */
+       int meta_refcount;              /* Number of rooms pointing to this msg */
+       char meta_content_type[64];     /* Cached MIME content-type */
+       long meta_rfc822_length;        /* Cache of RFC822-translated msg length */
+};
+
+
+/* Calls to AdjRefCount() are queued and deferred, so the user doesn't
+ * have to wait for various disk-intensive operations to complete synchronously.
+ * This is the record format.
+ */
+struct arcq {
+       long arcq_msgnum;               /* Message number being adjusted */
+       int arcq_delta;                 /* Adjustment ( usually 1 or -1 ) */
+};
+
+
+/*
+ * Serialization routines use this struct to return a pointer and a length
+ */
+struct ser_ret {
+       size_t len;
+       unsigned char *ser;
+};
+
+
+/*
+ * The S_USETABLE database is used in several modules now, so we define its format here.
+ */
+struct UseTable {
+       char ut_msgid[SIZ];
+       time_t ut_timestamp;
+};
+
+
+/*
+ * These one-byte field headers are found in the Citadel message store.
+ */
+typedef enum _MsgField {
+       eAuthor       = 'A',
+       eBig_message  = 'B',
+       eExclusiveID  = 'E',
+       erFc822Addr   = 'F',
+       emessageId    = 'I',
+       eJournal      = 'J',
+       eReplyTo      = 'K',
+       eListID       = 'L',
+       eMesageText   = 'M',
+       eOriginalRoom = 'O',
+       eMessagePath  = 'P',
+       eRecipient    = 'R',
+       eTimestamp    = 'T',
+       eMsgSubject   = 'U',
+       eenVelopeTo   = 'V',
+       eWeferences   = 'W',
+       eCarbonCopY   = 'Y',
+       eErrorMsg     = '0',
+       eSuppressIdx  = '1',
+       eExtnotify    = '2',
+       eVltMsgNum    = '3'
+} eMsgField;
+
+#endif /* SERVER_H */
diff --git a/citadel/server/server_main.c b/citadel/server/server_main.c
new file mode 100644 (file)
index 0000000..47f557c
--- /dev/null
@@ -0,0 +1,322 @@
+// citserver's main() function lives here.
+// 
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <grp.h>
+#include <sys/file.h>
+#include <libcitadel.h>
+#include "citserver.h"
+#include "modules_init.h"
+#include "config.h"
+#include "control.h"
+#include "serv_extensions.h"
+#include "citadel_dirs.h"
+#include "user_ops.h"
+
+uid_t ctdluid = 0;
+const char *CitadelServiceUDS="citadel-UDS";
+const char *CitadelServiceTCP="citadel-TCP";
+int sanity_diag_mode = 0;
+
+
+// Create or remove a lock file, so we only have one Citadel Server running at a time.
+// Set 'op' to nonzero to lock, zero to unlock.
+void ctdl_lockfile(int op) {
+       static char lockfilename[PATH_MAX];
+       static FILE *fp;
+
+       if (op) {
+               syslog(LOG_DEBUG, "main: creating lockfile");
+               snprintf(lockfilename, sizeof lockfilename, "%s/citadel.lock", ctdl_run_dir);
+               fp = fopen(lockfilename, "w");
+               if (!fp) {
+                       syslog(LOG_ERR, "%s: %m", lockfilename);
+                       exit(CTDLEXIT_DB);
+               }
+               if (flock(fileno(fp), (LOCK_EX|LOCK_NB)) != 0) {
+                       syslog(LOG_ERR, "main: cannot lock %s , is another citserver running?", lockfilename);
+                       exit(CTDLEXIT_DB);
+               }
+               return;
+       }
+
+       syslog(LOG_DEBUG, "main: removing lockfile");
+       unlink(lockfilename);
+       flock(fileno(fp), LOCK_UN);
+       fclose(fp);
+}
+
+
+// Here's where it all begins.
+int main(int argc, char **argv) {
+
+       char facility[32];
+       int a;                  // General-purpose variables
+       struct passwd pw, *pwp = NULL;
+       char pwbuf[SIZ];
+       int drop_root_perms = 1;
+       int max_log_level = LOG_INFO;
+       char *ctdldir = CTDLDIR;
+       int syslog_facility = LOG_DAEMON;
+       uid_t u = 0;
+       struct passwd *p = NULL;
+#ifdef HAVE_RUN_DIR
+       struct stat filestats;
+#endif
+
+       // Tell 'em who's in da house
+       syslog(LOG_INFO, " ");
+       syslog(LOG_INFO, " ");
+       syslog(LOG_INFO, "*** Citadel server engine ***\n");
+       syslog(LOG_INFO, "Version %d (build %s) ***", REV_LEVEL, BUILD_ID);
+       syslog(LOG_INFO, "Copyright (C) 1987-2022 by the Citadel development team.");
+       syslog(LOG_INFO, " ");
+       syslog(LOG_INFO, "This program is open source software: you can redistribute it and/or");
+       syslog(LOG_INFO, "modify it under the terms of the GNU General Public License, version 3.");
+       syslog(LOG_INFO, " ");
+       syslog(LOG_INFO, "This program is distributed in the hope that it will be useful,");
+       syslog(LOG_INFO, "but WITHOUT ANY WARRANTY; without even the implied warranty of");
+       syslog(LOG_INFO, "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the");
+       syslog(LOG_INFO, "GNU General Public License for more details.");
+       syslog(LOG_INFO, " ");
+       syslog(LOG_INFO, "%s", libcitadel_version_string());
+
+       // parse command-line arguments
+       while ((a=getopt(argc, argv, "cl:dh:x:t:B:Dru:s:")) != EOF) switch(a) {
+
+               // test this binary for compatibility and exit
+               case 'c':
+                       fprintf(stderr, "%s: binary compatibility confirmed\n", argv[0]);
+                       exit(0);
+                       break;
+
+               // identify the desired syslog facility
+               case 'l':
+                       safestrncpy(facility, optarg, sizeof(facility));
+                       syslog_facility = SyslogFacility(facility);
+                       break;
+
+               // run in the background if -d was specified
+               case 'd':
+                       running_as_daemon = 1;
+                       break;
+
+               // specify the data directory
+               case 'h':
+                       ctdldir = optarg;
+                       break;
+
+               // identify the desired logging severity level
+               case 'x':
+                       max_log_level = atoi(optarg);
+                       break;
+
+               // deprecated
+               case 't':
+                       break;
+
+               // deprecated
+                case 'B':
+                        break;
+
+               // deprecated
+               case 'D':
+                       break;
+
+               // -r tells the server not to drop root permissions.
+               // Don't use this unless you know what you're doing.
+               case 'r':
+                       drop_root_perms = 0;
+                       break;
+
+               // -u tells the server what uid to run under...
+               case 'u':
+                       u = atoi(optarg);
+                       if (u > 0) {
+                               ctdluid = u;
+                       }
+                       else {
+                               p = getpwnam(optarg);
+                               if (p) {
+                                       u = p->pw_uid;
+                               }
+                       }
+                       if (u > 0) {
+                               ctdluid = u;
+                       }
+                       break;
+
+               // -s tells the server to behave differently during sanity checks
+               case 's':
+                       sanity_diag_mode = atoi(optarg);
+                       break;
+
+               // any other parameter makes it crash and burn
+               default:
+                       fprintf(stderr, "citserver: usage: "
+                                       "citserver "
+                                       "[-l LogFacility] "
+                                       "[-x MaxLogLevel] "
+                                       "[-d] [-r] "
+                                       "[-u user] "
+                                       "[-h HomeDir]\n"
+                       );
+                       exit(1);
+       }
+
+       if (chdir(ctdldir) != 0) {
+               syslog(LOG_ERR, "main: unable to change directory to [%s]: %m", ctdldir);
+               exit(CTDLEXIT_HOME);
+       }
+       else {
+               syslog(LOG_INFO, "main: running in data directory %s", ctdldir);
+       }
+
+       if ((ctdluid == 0) && (drop_root_perms == 0)) {
+               fprintf(stderr, "citserver: cannot determine user to run as; please specify -r or -u options\n");
+               exit(CTDLEXIT_UNUSER);
+       }
+
+       // Last ditch effort to determine the user name ... if there's a user called "citadel" then use that
+       if (ctdluid == 0) {
+               p = getpwnam("citadel");
+               if (!p) {
+                       p = getpwnam("bbs");
+               }
+               if (!p) {
+                       p = getpwnam("guest");
+               }
+               if (p) {
+                       u = p->pw_uid;
+               }
+               if (u > 0) {
+                       ctdluid = u;
+               }
+       }
+
+       // initialize the master context
+       InitializeMasterCC();
+       InitializeMasterTSD();
+
+       setlogmask(LOG_UPTO(max_log_level));
+       openlog("citserver",
+               ( running_as_daemon ? (LOG_PID) : (LOG_PID | LOG_PERROR) ),
+               syslog_facility
+       );
+
+       // daemonize, if we were asked to
+       if (running_as_daemon) {
+               start_daemon(0);
+               drop_root_perms = 1;
+       }
+
+       if ((mkdir(ctdl_run_dir, 0755) != 0) && (errno != EEXIST)) {
+               syslog(LOG_ERR, "main: unable to create run directory [%s]: %m", ctdl_run_dir);
+       }
+
+       if (chown(ctdl_run_dir, ctdluid, (pwp==NULL)?-1:pw.pw_gid) != 0) {
+               syslog(LOG_ERR, "main: unable to set the access rights for [%s]: %m", ctdl_run_dir);
+       }
+
+       ctdl_lockfile(1);
+       init_sysdep();                                          // Initialize...
+       master_startup();                                       // Do non system dependent startup functions
+       check_control();                                        // Check/sanitize/initialize control record, fix user indexes
+       syslog(LOG_INFO, "main: upgrading modules");            // Run any upgrade entry points
+       pre_startup_upgrades();
+
+       // Load the user for the masterCC or create them if they don't exist
+       if (CtdlGetUser(&masterCC.user, "SYS_Citadel")) {
+               // User doesn't exist. We can't use create user here as the user number needs to be 0
+               strcpy (masterCC.user.fullname, "SYS_Citadel") ;
+               CtdlPutUser(&masterCC.user);
+               CtdlGetUser(&masterCC.user, "SYS_Citadel");     // Just to be safe
+       }
+       
+       // Bind the server to a Unix-domain socket (user client access)
+       CtdlRegisterServiceHook(0,
+                               file_citadel_socket,
+                               citproto_begin_session,
+                               do_command_loop,
+                               do_async_loop,
+                               CitadelServiceUDS);
+
+       // Bind the server to a Unix-domain socket (admin client access)
+       CtdlRegisterServiceHook(0,
+                               file_citadel_admin_socket,
+                               citproto_begin_admin_session,
+                               do_command_loop,
+                               do_async_loop,
+                               CitadelServiceUDS);
+       chmod(file_citadel_admin_socket, S_IRWXU);      // for your eyes only
+
+       // Bind the server to our favorite TCP port (usually 504).
+       CtdlRegisterServiceHook(CtdlGetConfigInt("c_port_number"),
+                               NULL,
+                               citproto_begin_session,
+                               do_command_loop,
+                               do_async_loop,
+                               CitadelServiceTCP);
+
+       // Load any server-side extensions available here.
+       syslog(LOG_INFO, "main: initializing server extensions");
+       initialize_modules(0);
+
+       // If we need host auth, start our chkpwd daemon.
+       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
+               start_chkpwd_daemon();
+       }
+
+       // check, whether we're fired up another time after a crash.
+       // if, post an aide message, so the admin has a chance to react.
+       checkcrash();
+
+       // Now that we've bound the sockets, change to the Citadel user id and its corresponding group ids
+       if (drop_root_perms) {
+               cdb_chmod_data();       // make sure we own our data files
+               getpwuid_r(ctdluid, &pw, pwbuf, sizeof(pwbuf), &pwp);
+               if (pwp == NULL)
+                       syslog(LOG_ERR, "main: WARNING, getpwuid(%ld): %m Group IDs will be incorrect.", (long)CTDLUID);
+               else {
+                       initgroups(pw.pw_name, pw.pw_gid);
+                       if (setgid(pw.pw_gid)) {
+                               syslog(LOG_ERR, "main: setgid(%ld): %m", (long)pw.pw_gid);
+                       }
+               }
+               syslog(LOG_INFO, "main: changing uid to %ld", (long)CTDLUID);
+               if (setuid(CTDLUID) != 0) {
+                       syslog(LOG_ERR, "main: setuid() failed: %m");
+               }
+#if defined (HAVE_SYS_PRCTL_H) && defined (PR_SET_DUMPABLE)
+               prctl(PR_SET_DUMPABLE, 1);
+#endif
+       }
+
+       // We want to check for idle sessions once per minute
+       CtdlRegisterSessionHook(terminate_idle_sessions, EVT_TIMER, PRIO_CLEANUP + 1);
+
+       // Go into multithreaded mode.  When this call exits, the server is stopping.
+       go_threading();
+       
+       // Get ready to shut down the server.
+       int exit_code = master_cleanup(exit_signal);
+       ctdl_lockfile(0);
+       if (restart_server) {
+               syslog(LOG_INFO, "main: *** CITADEL SERVER IS RESTARTING ***");
+               execv(argv[0], argv);
+       }
+       return(exit_code);
+}
diff --git a/citadel/server/support.c b/citadel/server/support.c
new file mode 100644 (file)
index 0000000..20937f2
--- /dev/null
@@ -0,0 +1,74 @@
+// Server-side utility functions
+
+#include "sysdep.h"
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <libcitadel.h>
+
+#include "citadel.h"
+#include "support.h"
+
+// strproc()  -  make a string 'nice'
+void strproc(char *string) {
+       int a, b;
+
+       if (string == NULL) return;
+       if (IsEmptyStr(string)) return;
+
+       // Convert non-printable characters to blanks
+       for (a=0; !IsEmptyStr(&string[a]); ++a) {
+               if (string[a]<32) string[a]=32;
+               if (string[a]>126) string[a]=32;
+       }
+
+       // a is now the length of our string.
+       // Remove leading and trailing blanks
+       while( (string[a-1]<33) && (!IsEmptyStr(string)) )
+               string[--a]=0;
+       b = 0;
+       while( (string[b]<33) && (!IsEmptyStr(&string[b])) )
+               b++;
+       if (b > 0)
+               memmove(string,&string[b], a - b + 1);
+
+       // Remove double blanks
+       for (a=0; !IsEmptyStr(&string[a]); ++a) {
+               if ((string[a]==32)&&(string[a+1]==32)) {
+                       strcpy(&string[a],&string[a+1]);
+                       a=0;
+               }
+       }
+
+       // remove characters which would interfere with the network
+       for (a=0; !IsEmptyStr(&string[a]); ++a) {
+               while (string[a]=='!') strcpy(&string[a],&string[a+1]);
+               while (string[a]=='@') strcpy(&string[a],&string[a+1]);
+               while (string[a]=='_') strcpy(&string[a],&string[a+1]);
+               while (string[a]==',') strcpy(&string[a],&string[a+1]);
+               while (string[a]=='%') strcpy(&string[a],&string[a+1]);
+               while (string[a]=='|') strcpy(&string[a],&string[a+1]);
+       }
+
+}
+
+
+// get a line of text from a file
+// ignores lines starting with #
+int getstring(FILE *fp, char *string) {
+       int a,c;
+       do {
+               strcpy(string,"");
+               a=0;
+               do {
+                       c=getc(fp);
+                       if (c<0) {
+                               string[a]=0;
+                               return(-1);
+                               }
+                       string[a++]=c;
+                       } while(c!=10);
+                       string[a-1]=0;
+               } while(string[0]=='#');
+       return(strlen(string));
+}
diff --git a/citadel/server/support.h b/citadel/server/support.h
new file mode 100644 (file)
index 0000000..fc7c2a6
--- /dev/null
@@ -0,0 +1,4 @@
+#include <stdio.h>
+
+void strproc (char *string);
+int getstring (FILE *fp, char *string);
diff --git a/citadel/server/sysconfig.h b/citadel/server/sysconfig.h
new file mode 100644 (file)
index 0000000..99947bc
--- /dev/null
@@ -0,0 +1,90 @@
+// Tuning of various parameters of the system.
+// Normally you don't want to mess with any of this.
+//
+// Copyright (c) 1987-2021 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+/*
+ * NLI is the string that shows up in a <W>ho's online listing for sessions
+ * that are active, but for which no user has yet authenticated.
+ */
+#define NLI    "(not logged in)"
+
+/*
+ * Maximum number of floors on the system.
+ * WARNING!  *Never* change this value once your system is up; THINGS WILL DIE!
+ * Also, do not set it higher than 127.
+ */
+#define MAXFLOORS      16
+
+/*
+ * Standard buffer size for string datatypes.  DO NOT CHANGE!  Not only does
+ * there exist a minimum buffer size for certain protocols (such as IMAP), but
+ * fixed-length buffers are now stored in some of the data structures on disk,
+ * so if you change the buffer size you'll fux0r your database.
+ */
+#define SIZ            4096
+
+/*
+ * If the body of a message is beyond this size, it will be stored in
+ * a separate table.
+ */
+#define BIGMSG         1024
+
+/*
+ * SMTP delivery timeouts (measured in seconds)
+ * If outbound SMTP deliveries cannot be completed due to transient errors
+ * within SMTP_DELIVER_WARN seconds, the sender will receive a warning message
+ * indicating that the message has not yet been delivered but Citadel will
+ * keep trying.  After SMTP_DELIVER_FAIL seconds, Citadel will advise the
+ * sender that the deliveries have failed.
+ */
+#define SMTP_DELIVER_WARN      14400           // warn after four hours
+#define SMTP_DELIVER_FAIL      432000          // fail after five days
+
+/*
+ * Who bounced messages appear to be from
+ */
+#define BOUNCESOURCE           "Citadel Mail Delivery Subsystem"
+
+/*
+ * The names of rooms which are automatically created by the system
+ */
+#define BASEROOM               "Lobby"
+#define MAILROOM               "Mail"
+#define SENTITEMS              "Sent Items"
+#define AIDEROOM               "Aide"
+#define USERCONFIGROOM         "My Citadel Config"
+#define USERCALENDARROOM       "Calendar"
+#define USERTASKSROOM          "Tasks"
+#define USERCONTACTSROOM       "Contacts"
+#define USERNOTESROOM          "Notes"
+#define USERDRAFTROOM          "Drafts"
+#define USERTRASHROOM          "Trash"
+#define PAGELOGROOM            "Sent/Received Pages"
+#define SYSCONFIGROOM          "Local System Configuration"
+#define SMTP_SPOOLOUT_ROOM     "__CitadelSMTPspoolout__"
+#define ADDRESS_BOOK_ROOM      "Global Address Book"
+
+/*
+ * How long (in seconds) to retain message entries in the use table
+ */
+#define USETABLE_RETAIN                864000L         /* 10 days */
+
+/*
+ * The size of per-thread stacks.  If set too low, citserver will randomly crash.
+ */
+#define THREADSTACKSIZE                0x100000
+
+/*
+ * How many messages may the full text indexer scan before flushing its
+ * tables to disk?
+ */
+#define FT_MAX_CACHE           25
diff --git a/citadel/server/sysdep.c b/citadel/server/sysdep.c
new file mode 100644 (file)
index 0000000..280aa91
--- /dev/null
@@ -0,0 +1,1116 @@
+// Citadel "system dependent" stuff.
+//
+// Here's where we (hopefully) have most parts of the Citadel server that
+// might need tweaking when run on different operating system variants.
+//
+// Copyright (c) 1987-2021 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <sys/syslog.h>
+#include <execinfo.h>
+#include <netdb.h>
+#include <sys/un.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#define SHOW_ME_VAPPEND_PRINTF
+#include <libcitadel.h>
+#include "citserver.h"
+#include "config.h"
+#include "ctdl_module.h"
+#include "sysdep_decls.h"
+#include "modules/crypto/serv_crypto.h"        // Needed for init_ssl, client_write_ssl, client_read_ssl
+#include "housekeeping.h"
+#include "context.h"
+
+// Signal handler to shut down the server.
+volatile int exit_signal = 0;
+volatile int shutdown_and_halt = 0;
+volatile int restart_server = 0;
+volatile int running_as_daemon = 0;
+
+
+static RETSIGTYPE signal_cleanup(int signum) {
+       syslog(LOG_DEBUG, "sysdep: caught signal %d", signum);
+       exit_signal = signum;
+       server_shutting_down = 1;
+}
+
+
+// Some initialization stuff...
+void init_sysdep(void) {
+       sigset_t set;
+
+       // Avoid vulnerabilities related to FD_SETSIZE if we can.
+#ifdef FD_SETSIZE
+#ifdef RLIMIT_NOFILE
+       struct rlimit rl;
+       getrlimit(RLIMIT_NOFILE, &rl);
+       rl.rlim_cur = FD_SETSIZE;
+       rl.rlim_max = FD_SETSIZE;
+       setrlimit(RLIMIT_NOFILE, &rl);
+#endif
+#endif
+
+       // If we've got OpenSSL, we're going to use it.
+#ifdef HAVE_OPENSSL
+       init_ssl();
+#endif
+
+       if (pthread_key_create(&ThreadKey, NULL) != 0) {                        // TSD for threads
+               syslog(LOG_ERR, "pthread_key_create() : %m");
+               abort();
+       }
+       
+       if (pthread_key_create(&MyConKey, NULL) != 0) {                         // TSD for sessions
+               syslog(LOG_CRIT, "sysdep: can't create TSD key: %m");
+               abort();
+       }
+
+       // Interript, hangup, and terminate signals should cause the server to shut down.
+       sigemptyset(&set);
+       sigaddset(&set, SIGINT);
+       sigaddset(&set, SIGHUP);
+       sigaddset(&set, SIGTERM);
+       sigprocmask(SIG_UNBLOCK, &set, NULL);
+
+       signal(SIGINT, signal_cleanup);
+       signal(SIGHUP, signal_cleanup);
+       signal(SIGTERM, signal_cleanup);
+
+       // Do not shut down the server on broken pipe signals, otherwise the
+       // whole Citadel service would come down whenever a single client
+       // socket breaks.
+       signal(SIGPIPE, SIG_IGN);
+}
+
+
+// This is a generic function to set up a master socket for listening on
+// a TCP port.  The server shuts down if the bind fails.  (IPv4/IPv6 version)
+//
+// ip_addr     IP address to bind
+// port_number port number to bind
+// queue_len   number of incoming connections to allow in the queue
+int ctdl_tcp_server(char *ip_addr, int port_number, int queue_len) {
+       struct protoent *p;
+       struct sockaddr_in6 sin6;
+       struct sockaddr_in sin4;
+       int s, i, b;
+       int ip_version = 6;
+
+       memset(&sin6, 0, sizeof(sin6));
+       memset(&sin4, 0, sizeof(sin4));
+       sin6.sin6_family = AF_INET6;
+       sin4.sin_family = AF_INET;
+
+       if (    (ip_addr == NULL)                                                       // any IPv6
+               || (IsEmptyStr(ip_addr))
+               || (!strcmp(ip_addr, "*"))
+       ) {
+               ip_version = 6;
+               sin6.sin6_addr = in6addr_any;
+       }
+       else if (!strcmp(ip_addr, "0.0.0.0"))                                           // any IPv4
+       {
+               ip_version = 4;
+               sin4.sin_addr.s_addr = INADDR_ANY;
+       }
+       else if ((strchr(ip_addr, '.')) && (!strchr(ip_addr, ':')))                     // specific IPv4
+       {
+               ip_version = 4;
+               if (inet_pton(AF_INET, ip_addr, &sin4.sin_addr) <= 0) {
+                       syslog(LOG_ALERT, "tcpserver: inet_pton: %m");
+                       return (-1);
+               }
+       }
+       else                                                                            // specific IPv6
+       {
+               ip_version = 6;
+               if (inet_pton(AF_INET6, ip_addr, &sin6.sin6_addr) <= 0) {
+                       syslog(LOG_ALERT, "tcpserver: inet_pton: %m");
+                       return (-1);
+               }
+       }
+
+       if (port_number == 0) {
+               syslog(LOG_ALERT, "tcpserver: no port number was specified");
+               return (-1);
+       }
+       sin6.sin6_port = htons((u_short) port_number);
+       sin4.sin_port = htons((u_short) port_number);
+
+       p = getprotobyname("tcp");
+       if (p == NULL) {
+               syslog(LOG_ALERT, "tcpserver: getprotobyname: %m");
+               return (-1);
+       }
+
+       s = socket( ((ip_version == 6) ? PF_INET6 : PF_INET), SOCK_STREAM, (p->p_proto));
+       if (s < 0) {
+               syslog(LOG_ALERT, "tcpserver: socket: %m");
+               return (-1);
+       }
+       // Set some socket options that make sense.
+       i = 1;
+       setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
+
+       if (ip_version == 6) {
+               b = bind(s, (struct sockaddr *) &sin6, sizeof(sin6));
+       }
+       else {
+               b = bind(s, (struct sockaddr *) &sin4, sizeof(sin4));
+       }
+
+       if (b < 0) {
+               syslog(LOG_ALERT, "tcpserver: bind: %m");
+               return (-1);
+       }
+
+       fcntl(s, F_SETFL, O_NONBLOCK);
+
+       if (listen(s, ((queue_len >= 5) ? queue_len : 5) ) < 0) {
+               syslog(LOG_ALERT, "tcpserver: listen: %m");
+               return (-1);
+       }
+       return (s);
+}
+
+
+// Create a Unix domain socket and listen on it
+int ctdl_uds_server(char *sockpath, int queue_len) {
+       struct sockaddr_un addr;
+       int s;
+       int i;
+       int actual_queue_len;
+#ifdef HAVE_STRUCT_UCRED
+       int passcred = 1;
+#endif
+
+       actual_queue_len = queue_len;
+       if (actual_queue_len < 5) actual_queue_len = 5;
+
+       i = unlink(sockpath);
+       if ((i != 0) && (errno != ENOENT)) {
+               syslog(LOG_ERR, "udsserver: %m");
+               return(-1);
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.sun_family = AF_UNIX;
+       safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
+
+       s = socket(AF_UNIX, SOCK_STREAM, 0);
+       if (s < 0) {
+               syslog(LOG_ERR, "udsserver: socket: %m");
+               return(-1);
+       }
+
+       if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+               syslog(LOG_ERR, "udsserver: bind: %m");
+               return(-1);
+       }
+
+       // set to nonblock - we need this for some obscure situations
+       if (fcntl(s, F_SETFL, O_NONBLOCK) < 0) {
+               syslog(LOG_ERR, "udsserver: fcntl: %m");
+               close(s);
+               return(-1);
+       }
+
+       if (listen(s, actual_queue_len) < 0) {
+               syslog(LOG_ERR, "udsserver: listen: %m");
+               return(-1);
+       }
+
+#ifdef HAVE_STRUCT_UCRED
+       setsockopt(s, SOL_SOCKET, SO_PASSCRED, &passcred, sizeof(passcred));
+#endif
+
+       chmod(sockpath, S_ISGID|S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH);
+       return(s);
+}
+
+
+// The following functions implement output buffering on operating systems which
+// support it (such as Linux and various BSD flavors).
+#ifndef HAVE_DARWIN
+#ifdef TCP_CORK
+#      define HAVE_TCP_BUFFERING
+#else
+#      ifdef TCP_NOPUSH
+#              define HAVE_TCP_BUFFERING
+#              define TCP_CORK TCP_NOPUSH
+#      endif
+#endif // TCP_CORK
+#endif // HAVE_DARWIN
+
+static unsigned on = 1, off = 0;
+
+void buffer_output(void) {
+#ifdef HAVE_TCP_BUFFERING
+#ifdef HAVE_OPENSSL
+       if (!CC->redirect_ssl)
+#endif
+               setsockopt(CC->client_socket, IPPROTO_TCP, TCP_CORK, &on, 4);
+#endif
+}
+
+void unbuffer_output(void) {
+#ifdef HAVE_TCP_BUFFERING
+#ifdef HAVE_OPENSSL
+       if (!CC->redirect_ssl)
+#endif
+               setsockopt(CC->client_socket, IPPROTO_TCP, TCP_CORK, &off, 4);
+#endif
+}
+
+void flush_output(void) {
+#ifdef HAVE_TCP_BUFFERING
+       struct CitContext *CCC = CC;
+       setsockopt(CCC->client_socket, IPPROTO_TCP, TCP_CORK, &off, 4);
+       setsockopt(CCC->client_socket, IPPROTO_TCP, TCP_CORK, &on, 4);
+#endif
+}
+
+
+// close the client socket
+void client_close(void) {
+       CitContext *CCC = CC;
+
+       if (!CCC) return;
+       if (CCC->client_socket <= 0) return;
+       syslog(LOG_DEBUG, "sysdep: closing socket %d", CCC->client_socket);
+       close(CCC->client_socket);
+       CCC->client_socket = -1 ;
+}
+
+
+// Send binary data to the client.
+int client_write(const char *buf, int nbytes)
+{
+       int bytes_written = 0;
+       int retval;
+#ifndef HAVE_TCP_BUFFERING
+       int old_buffer_len = 0;
+#endif
+       fd_set wset;
+       CitContext *Ctx;
+       int fdflags;
+
+       if (nbytes < 1) return(0);
+
+       Ctx = CC;
+
+       if (Ctx->redirect_buffer != NULL) {
+               StrBufAppendBufPlain(Ctx->redirect_buffer,
+                                    buf, nbytes, 0);
+               return 0;
+       }
+
+#ifdef HAVE_OPENSSL
+       if (Ctx->redirect_ssl) {
+               client_write_ssl(buf, nbytes);
+               return 0;
+       }
+#endif
+       if (Ctx->client_socket == -1) return -1;
+
+       fdflags = fcntl(Ctx->client_socket, F_GETFL);
+
+       while ((bytes_written < nbytes) && (Ctx->client_socket != -1)){
+               if ((fdflags & O_NONBLOCK) == O_NONBLOCK) {
+                       FD_ZERO(&wset);
+                       FD_SET(Ctx->client_socket, &wset);
+                       if (select(1, NULL, &wset, NULL, NULL) == -1) {
+                               if (errno == EINTR)
+                               {
+                                       syslog(LOG_DEBUG, "sysdep: client_write(%d bytes) select() interrupted.", nbytes-bytes_written);
+                                       if (server_shutting_down) {
+                                               CC->kill_me = KILLME_SELECT_INTERRUPTED;
+                                               return (-1);
+                                       } else {
+                                               // can't trust fd's and stuff so we need to re-create them
+                                               continue;
+                                       }
+                               } else {
+                                       syslog(LOG_ERR, "sysdep: client_write(%d bytes) select failed: %m", nbytes - bytes_written);
+                                       client_close();
+                                       Ctx->kill_me = KILLME_SELECT_FAILED;
+                                       return -1;
+                               }
+                       }
+               }
+
+               retval = write(Ctx->client_socket, &buf[bytes_written], nbytes - bytes_written);
+               if (retval < 1) {
+                       syslog(LOG_ERR, "sysdep: client_write(%d bytes) failed: %m", nbytes - bytes_written);
+                       client_close();
+                       Ctx->kill_me = KILLME_WRITE_FAILED;
+                       return -1;
+               }
+               bytes_written = bytes_written + retval;
+       }
+       return 0;
+}
+
+void cputbuf(const StrBuf *Buf) {   
+       client_write(ChrPtr(Buf), StrLength(Buf)); 
+}   
+
+
+// Send formatted printable data to the client.
+// Implemented in terms of client_write() so it's technically not sysdep...
+void cprintf(const char *format, ...) {   
+       va_list arg_ptr;   
+       char buf[1024];
+   
+       va_start(arg_ptr, format);   
+       if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1)
+               buf[sizeof buf - 2] = '\n';
+       client_write(buf, strlen(buf)); 
+       va_end(arg_ptr);
+}
+
+
+// Read data from the client socket.
+//
+// sock                socket fd to read from
+// buf         buffer to read into 
+// bytes       number of bytes to read
+// timeout     Number of seconds to wait before timing out
+//
+// Possible return values:
+//      1       Requested number of bytes has been read.
+//      0       Request timed out.
+//     -1      Connection is broken, or other error.
+int client_read_blob(StrBuf *Target, int bytes, int timeout) {
+       CitContext *CCC=CC;
+       const char *Error;
+       int retval = 0;
+
+#ifdef HAVE_OPENSSL
+       if (CCC->redirect_ssl) {
+               retval = client_read_sslblob(Target, bytes, timeout);
+               if (retval < 0) {
+                       syslog(LOG_ERR, "sysdep: client_read_blob() failed");
+               }
+       }
+       else 
+#endif
+       {
+               retval = StrBufReadBLOBBuffered(Target, 
+                                               CCC->RecvBuf.Buf,
+                                               &CCC->RecvBuf.ReadWritePointer,
+                                               &CCC->client_socket,
+                                               1, 
+                                               bytes,
+                                               O_TERM,
+                                               &Error
+               );
+               if (retval < 0) {
+                       syslog(LOG_ERR, "sysdep: client_read_blob() failed: %s", Error);
+                       client_close();
+                       return retval;
+               }
+       }
+       return retval;
+}
+
+
+// to make client_read_random_blob() more efficient, increase buffer size.
+// just use in greeting function, else your buffer may be flushed
+void client_set_inbound_buf(long N)
+{
+       CitContext *CCC=CC;
+       FlushStrBuf(CCC->RecvBuf.Buf);
+       ReAdjustEmptyBuf(CCC->RecvBuf.Buf, N * SIZ, N * SIZ);
+}
+
+int client_read_random_blob(StrBuf *Target, int timeout)
+{
+       CitContext *CCC=CC;
+       int rc;
+
+       rc =  client_read_blob(Target, 1, timeout);
+       if (rc > 0)
+       {
+               long len;
+               const char *pch;
+               
+               len = StrLength(CCC->RecvBuf.Buf);
+               pch = ChrPtr(CCC->RecvBuf.Buf);
+
+               if (len > 0)
+               {
+                       if (CCC->RecvBuf.ReadWritePointer != NULL) {
+                               len -= CCC->RecvBuf.ReadWritePointer - pch;
+                               pch = CCC->RecvBuf.ReadWritePointer;
+                       }
+                       StrBufAppendBufPlain(Target, pch, len, 0);
+                       FlushStrBuf(CCC->RecvBuf.Buf);
+                       CCC->RecvBuf.ReadWritePointer = NULL;
+                       return StrLength(Target);
+               }
+               return rc;
+       }
+       else
+               return rc;
+}
+
+int client_read_to(char *buf, int bytes, int timeout)
+{
+       CitContext *CCC=CC;
+       int rc;
+
+       rc = client_read_blob(CCC->MigrateBuf, bytes, timeout);
+       if (rc < 0)
+       {
+               *buf = '\0';
+               return rc;
+       }
+       else
+       {
+               memcpy(buf, 
+                      ChrPtr(CCC->MigrateBuf),
+                      StrLength(CCC->MigrateBuf) + 1);
+               FlushStrBuf(CCC->MigrateBuf);
+               return rc;
+       }
+}
+
+
+int HaveMoreLinesWaiting(CitContext *CCC) {
+       if ((CCC->kill_me != 0) ||
+           ( (CCC->RecvBuf.ReadWritePointer == NULL) && 
+             (StrLength(CCC->RecvBuf.Buf) == 0) && 
+             (CCC->client_socket != -1)) )
+               return 0;
+       else
+               return 1;
+}
+
+
+// Read data from the client socket with default timeout.
+// (This is implemented in terms of client_read_to() and could be
+// justifiably moved out of sysdep.c)
+INLINE int client_read(char *buf, int bytes) {
+       return(client_read_to(buf, bytes, CtdlGetConfigInt("c_sleeping")));
+}
+
+int CtdlClientGetLine(StrBuf *Target) {
+       CitContext *CCC=CC;
+       const char *Error;
+       int rc;
+
+       FlushStrBuf(Target);
+#ifdef HAVE_OPENSSL
+       if (CCC->redirect_ssl) {
+               rc = client_readline_sslbuffer(Target, CCC->RecvBuf.Buf, &CCC->RecvBuf.ReadWritePointer, 1);
+               return rc;
+       }
+       else 
+#endif
+       {
+               rc = StrBufTCP_read_buffered_line_fast(Target, 
+                                                      CCC->RecvBuf.Buf,
+                                                      &CCC->RecvBuf.ReadWritePointer,
+                                                      &CCC->client_socket,
+                                                      5,
+                                                      1,
+                                                      &Error
+               );
+               return rc;
+       }
+}
+
+
+// Get a LF-terminated line of text from the client.
+// (This is implemented in terms of client_read() and could be
+// justifiably moved out of sysdep.c)
+int client_getln(char *buf, int bufsize) {
+       int i, retval;
+       CitContext *CCC=CC;
+       const char *pCh;
+
+       retval = CtdlClientGetLine(CCC->MigrateBuf);
+       if (retval < 0)
+         return(retval >= 0);
+
+
+       i = StrLength(CCC->MigrateBuf);
+       pCh = ChrPtr(CCC->MigrateBuf);
+       // Strip the trailing LF, and the trailing CR if present.
+       if (bufsize <= i)
+               i = bufsize - 1;
+       while ( (i > 0)
+               && ( (pCh[i - 1]==13)
+                    || ( pCh[i - 1]==10)) ) {
+               i--;
+       }
+       memcpy(buf, pCh, i);
+       buf[i] = 0;
+
+       FlushStrBuf(CCC->MigrateBuf);
+       if (retval < 0) {
+               safestrncpy(&buf[i], "000", bufsize - i);
+       }
+       return(retval >= 0);
+}
+
+
+// Cleanup any contexts that are left lying around
+void close_masters(void) {
+       struct ServiceFunctionHook *serviceptr;
+       const char *Text;
+
+       // close all protocol master sockets
+       for (serviceptr = ServiceHookTable; serviceptr != NULL;
+           serviceptr = serviceptr->next ) {
+
+               if (serviceptr->tcp_port > 0) {
+                       if (serviceptr->msock == -1) {
+                               Text = "not closing again";
+                       }
+                       else {
+                               Text = "Closing";
+                       }
+                       syslog(LOG_INFO, "sysdep: %s %d listener on port %d",
+                              Text,
+                              serviceptr->msock,
+                              serviceptr->tcp_port
+                       );
+                       serviceptr->tcp_port = 0;
+               }
+               
+               if (serviceptr->sockpath != NULL) {
+                       if (serviceptr->msock == -1) {
+                               Text = "not closing again";
+                       }
+                       else {
+                               Text = "Closing";
+                       }
+                       syslog(LOG_INFO, "sysdep: %s %d listener on '%s'",
+                              Text,
+                              serviceptr->msock,
+                              serviceptr->sockpath
+                       );
+               }
+
+               if (serviceptr->msock != -1) {
+                       close(serviceptr->msock);
+                       serviceptr->msock = -1;
+               }
+
+               // If it's a Unix domain socket, remove the file.
+               if (serviceptr->sockpath != NULL) {
+                       unlink(serviceptr->sockpath);
+                       serviceptr->sockpath = NULL;
+               }
+       }
+}
+
+
+// The system-dependent part of master_cleanup() - close the master socket.
+void sysdep_master_cleanup(void) {
+       close_masters();
+       context_cleanup();
+}
+
+
+
+pid_t current_child;
+void graceful_shutdown(int signum) {
+       kill(current_child, signum);
+       unlink(file_pid_file);
+       exit(0);
+}
+
+int nFireUps = 0;
+int nFireUpsNonRestart = 0;
+pid_t ForkedPid = 1;
+
+// Start running as a daemon.
+void start_daemon(int unused) {
+       int status = 0;
+       pid_t child = 0;
+       FILE *fp;
+       int do_restart = 0;
+       current_child = 0;
+
+       // Close stdin/stdout/stderr and replace them with /dev/null.
+       // We don't just call close() because we don't want these fd's
+       // to be reused for other files.
+       child = fork();
+       if (child != 0) {
+               exit(0);
+       }
+       
+       signal(SIGHUP, SIG_IGN);
+       signal(SIGINT, SIG_IGN);
+       signal(SIGQUIT, SIG_IGN);
+
+       setsid();
+       umask(0);
+       if (    (freopen("/dev/null", "r", stdin) != stdin) || 
+               (freopen("/dev/null", "w", stdout) != stdout) || 
+               (freopen("/dev/null", "w", stderr) != stderr)
+       ) {
+               syslog(LOG_ERR, "sysdep: unable to reopen stdio: %m");
+       }
+
+       do {
+               current_child = fork();
+               signal(SIGTERM, graceful_shutdown);
+               if (current_child < 0) {
+                       perror("fork");
+                       exit(errno);
+               }
+               else if (current_child == 0) {
+                       return; // continue starting citadel.
+               }
+               else {
+                       fp = fopen(file_pid_file, "w");
+                       if (fp != NULL) {
+                               fprintf(fp, ""F_PID_T"\n", getpid());
+                               fclose(fp);
+                       }
+                       waitpid(current_child, &status, 0);
+               }
+               nFireUpsNonRestart = nFireUps;
+               
+               // Exit code 0 means the watcher should exit
+               if (WIFEXITED(status) && (WEXITSTATUS(status) == CTDLEXIT_SHUTDOWN)) {
+                       do_restart = 0;
+               }
+
+               // Exit code 101-109 means the watcher should exit
+               else if (WIFEXITED(status) && (WEXITSTATUS(status) >= 101) && (WEXITSTATUS(status) <= 109)) {
+                       do_restart = 0;
+               }
+
+               // Any other exit code, or no exit code, means we should restart.
+               else {
+                       do_restart = 1;
+                       nFireUps++;
+                       ForkedPid = current_child;
+               }
+
+       } while (do_restart);
+
+       unlink(file_pid_file);
+       exit(WEXITSTATUS(status));
+}
+
+
+void checkcrash(void) {
+       if (nFireUpsNonRestart != nFireUps) {
+               StrBuf *CrashMail;
+               CrashMail = NewStrBuf();
+               syslog(LOG_ALERT, "sysdep: posting crash message");
+               StrBufPrintf(CrashMail, 
+                       " \n"
+                       " The Citadel server process (citserver) terminated unexpectedly."
+                       "\n \n"
+                       " This could be the result of a bug in the server program, or some external "
+                       "factor.\n \n"
+                       " You can obtain more information about this by enabling core dumps.\n \n"
+                       " For more information, please see:\n \n"
+                       " http://citadel.org/doku.php?id=faq:mastering_your_os:gdb#how.do.i.make.my.system.produce.core-files"
+                       "\n \n"
+
+                       " If you have already done this, the core dump is likely to be found at %score.%d\n"
+                       ,
+                       ctdl_run_dir, ForkedPid);
+               CtdlAideMessage(ChrPtr(CrashMail), "Citadel server process terminated unexpectedly");
+               FreeStrBuf(&CrashMail);
+       }
+}
+
+
+// Generic routine to convert a login name to a full name (gecos)
+// Returns nonzero if a conversion took place
+int convert_login(char NameToConvert[]) {
+       struct passwd *pw;
+       unsigned int a;
+
+       pw = getpwnam(NameToConvert);
+       if (pw == NULL) {
+               return(0);
+       }
+       else {
+               strcpy(NameToConvert, pw->pw_gecos);
+               for (a=0; a<strlen(NameToConvert); ++a) {
+                       if (NameToConvert[a] == ',') NameToConvert[a] = 0;
+               }
+               return(1);
+       }
+}
+
+
+void HuntBadSession(void) {
+       int highest;
+       CitContext *ptr;
+       fd_set readfds;
+       struct timeval tv;
+       struct ServiceFunctionHook *serviceptr;
+
+       // Next, add all of the client sockets
+       begin_critical_section(S_SESSION_TABLE);
+       for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
+               if ((ptr->state == CON_SYS) && (ptr->client_socket == 0))
+                       continue;
+               // Initialize the fdset.
+               FD_ZERO(&readfds);
+               highest = 0;
+               tv.tv_sec = 0;          // wake up every second if no input
+               tv.tv_usec = 0;
+
+               // Don't select on dead sessions, only truly idle ones
+               if (    (ptr->state == CON_IDLE)
+                       && (ptr->kill_me == 0)
+                       && (ptr->client_socket > 0)
+               ) {
+                       FD_SET(ptr->client_socket, &readfds);
+                       if (ptr->client_socket > highest)
+                               highest = ptr->client_socket;
+                       
+                       if ((select(highest + 1, &readfds, NULL, NULL, &tv) < 0) && (errno == EBADF))
+                       {
+                               // Gotcha!
+                               syslog(LOG_ERR,
+                                      "sysdep: killing session CC[%d] bad FD: [%d] User[%s] Host[%s:%s]",
+                                       ptr->cs_pid,
+                                       ptr->client_socket,
+                                       ptr->curr_user,
+                                       ptr->cs_host,
+                                       ptr->cs_addr
+                               );
+                               ptr->kill_me = 1;
+                               ptr->client_socket = -1;
+                               break;
+                       }
+               }
+       }
+       end_critical_section(S_SESSION_TABLE);
+
+       // First, add the various master sockets to the fdset.
+       for (serviceptr = ServiceHookTable; serviceptr != NULL; serviceptr = serviceptr->next ) {
+
+               // Initialize the fdset.
+               highest = 0;
+               tv.tv_sec = 0;          // wake up every second if no input
+               tv.tv_usec = 0;
+
+               FD_SET(serviceptr->msock, &readfds);
+               if (serviceptr->msock > highest) {
+                       highest = serviceptr->msock;
+               }
+               if ((select(highest + 1, &readfds, NULL, NULL, &tv) < 0) &&
+                   (errno == EBADF))
+               {
+                       // Gotcha! server socket dead? commit suicide!
+                       syslog(LOG_ERR, "sysdep: found bad FD: %d and its a server socket! Shutting Down!", serviceptr->msock);
+                       server_shutting_down = 1;
+                       break;
+               }
+       }
+}
+
+
+// This loop just keeps going and going and going...
+void *worker_thread(void *blah) {
+       int highest;
+       CitContext *ptr;
+       CitContext *bind_me = NULL;
+       fd_set readfds;
+       int retval = 0;
+       struct timeval tv;
+       int force_purge = 0;
+       struct ServiceFunctionHook *serviceptr;
+       int ssock;                      // Descriptor for client socket
+       CitContext *con = NULL;         // Temporary context pointer
+       int i;
+
+       pthread_mutex_lock(&ThreadCountMutex);
+       ++num_workers;
+       pthread_mutex_unlock(&ThreadCountMutex);
+
+       while (!server_shutting_down) {
+
+               // make doubly sure we're not holding any stale db handles which might cause a deadlock
+               cdb_check_handles();
+do_select:     force_purge = 0;
+               bind_me = NULL;         // Which session shall we handle?
+
+               // Initialize the fdset
+               FD_ZERO(&readfds);
+               highest = 0;
+
+               // First, add the various master sockets to the fdset.
+               for (serviceptr = ServiceHookTable; serviceptr != NULL; serviceptr = serviceptr->next ) {
+                       FD_SET(serviceptr->msock, &readfds);
+                       if (serviceptr->msock > highest) {
+                               highest = serviceptr->msock;
+                       }
+               }
+
+               // Next, add all of the client sockets.
+               begin_critical_section(S_SESSION_TABLE);
+               for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
+                       if ((ptr->state == CON_SYS) && (ptr->client_socket == 0))
+                           continue;
+
+                       // Don't select on dead sessions, only truly idle ones
+                       if (    (ptr->state == CON_IDLE)
+                               && (ptr->kill_me == 0)
+                               && (ptr->client_socket > 0)
+                       ) {
+                               FD_SET(ptr->client_socket, &readfds);
+                               if (ptr->client_socket > highest)
+                                       highest = ptr->client_socket;
+                       }
+                       if ((bind_me == NULL) && (ptr->state == CON_READY)) {
+                               bind_me = ptr;
+                               ptr->state = CON_EXECUTING;
+                               break;
+                       }
+                       if ((bind_me == NULL) && (ptr->state == CON_GREETING)) {
+                               bind_me = ptr;
+                               ptr->state = CON_STARTING;
+                               break;
+                       }
+               }
+               end_critical_section(S_SESSION_TABLE);
+
+               if (bind_me) {
+                       goto SKIP_SELECT;
+               }
+
+               // If we got this far, it means that there are no sessions
+               // which a previous thread marked for attention, so we go
+               // ahead and get ready to select().
+
+               if (!server_shutting_down) {
+                       tv.tv_sec = 1;          // wake up every second if no input
+                       tv.tv_usec = 0;
+                       retval = select(highest + 1, &readfds, NULL, NULL, &tv);
+               }
+               else {
+                       --num_workers;
+                       return NULL;
+               }
+
+               // Now figure out who made this select() unblock.
+               // First, check for an error or exit condition.
+               if (retval < 0) {
+                       if (errno == EBADF) {
+                               syslog(LOG_ERR, "sysdep: select() failed: %m");
+                               HuntBadSession();
+                               goto do_select;
+                       }
+                       if (errno != EINTR) {
+                               syslog(LOG_ERR, "sysdep: exiting: %m");
+                               server_shutting_down = 1;
+                               continue;
+                       } else {
+                               if (server_shutting_down) {
+                                       --num_workers;
+                                       return(NULL);
+                               }
+                               goto do_select;
+                       }
+               }
+               else if (retval == 0) {
+                       if (server_shutting_down) {
+                               --num_workers;
+                               return(NULL);
+                       }
+               }
+
+               // Next, check to see if it's a new client connecting on a master socket.
+
+               else if ((retval > 0) && (!server_shutting_down)) for (serviceptr = ServiceHookTable; serviceptr != NULL; serviceptr = serviceptr->next) {
+
+                       if (FD_ISSET(serviceptr->msock, &readfds)) {
+                               ssock = accept(serviceptr->msock, NULL, 0);
+                               if (ssock >= 0) {
+                                       syslog(LOG_DEBUG, "sysdep: new client socket %d", ssock);
+
+                                       // The master socket is non-blocking but the client
+                                       // sockets need to be blocking, otherwise certain
+                                       // operations barf on FreeBSD.  Not a fatal error.
+                                       if (fcntl(ssock, F_SETFL, 0) < 0) {
+                                               syslog(LOG_ERR, "sysdep: Can't set socket to blocking: %m");
+                                       }
+
+                                       // New context will be created already
+                                       // set up in the CON_EXECUTING state.
+                                       con = CreateNewContext();
+
+                                       // Assign our new socket number to it.
+                                       con->tcp_port = serviceptr->tcp_port;
+                                       con->client_socket = ssock;
+                                       con->h_command_function = serviceptr->h_command_function;
+                                       con->h_async_function = serviceptr->h_async_function;
+                                       con->h_greeting_function = serviceptr->h_greeting_function;
+                                       con->ServiceName = serviceptr->ServiceName;
+                                       
+                                       // Connections on a local client are always from the same host
+                                       if (serviceptr->sockpath != NULL) {
+                                               con->is_local_client = 1;
+                                       }
+       
+                                       // Set the SO_REUSEADDR socket option
+                                       i = 1;
+                                       setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
+                                       con->state = CON_GREETING;
+                                       retval--;
+                                       if (retval == 0)
+                                               break;
+                               }
+                       }
+               }
+
+               // It must be a client socket.  Find a context that has data
+               // waiting on its socket *and* is in the CON_IDLE state.  Any
+               // active sockets other than our chosen one are marked as
+               // CON_READY so the next thread that comes around can just bind
+               // to one without having to select() again.
+               begin_critical_section(S_SESSION_TABLE);
+               for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
+                       int checkfd = ptr->client_socket;
+                       if ((checkfd != -1) && (ptr->state == CON_IDLE) ){
+                               if (FD_ISSET(checkfd, &readfds)) {
+                                       ptr->input_waiting = 1;
+                                       if (!bind_me) {
+                                               bind_me = ptr;  // I choose you!
+                                               bind_me->state = CON_EXECUTING;
+                                       }
+                                       else {
+                                               ptr->state = CON_READY;
+                                       }
+                               } else if ((ptr->is_async) && (ptr->async_waiting) && (ptr->h_async_function)) {
+                                       if (!bind_me) {
+                                               bind_me = ptr;  // I choose you!
+                                               bind_me->state = CON_EXECUTING;
+                                       }
+                                       else {
+                                               ptr->state = CON_READY;
+                                       }
+                               }
+                       }
+               }
+               end_critical_section(S_SESSION_TABLE);
+
+SKIP_SELECT:
+               // We're bound to a session
+               pthread_mutex_lock(&ThreadCountMutex);
+               ++active_workers;
+               pthread_mutex_unlock(&ThreadCountMutex);
+
+               if (bind_me != NULL) {
+                       become_session(bind_me);
+
+                       if (bind_me->state == CON_STARTING) {
+                               bind_me->state = CON_EXECUTING;
+                               begin_session(bind_me);
+                               bind_me->h_greeting_function();
+                       }
+                       // If the client has sent a command, execute it.
+                       if (CC->input_waiting) {
+                               CC->h_command_function();
+
+                               while (HaveMoreLinesWaiting(CC))
+                                      CC->h_command_function();
+
+                               CC->input_waiting = 0;
+                       }
+
+                       // If there are asynchronous messages waiting and the client supports it, do those now
+                       if ((CC->is_async) && (CC->async_waiting) && (CC->h_async_function != NULL)) {
+                               CC->h_async_function();
+                               CC->async_waiting = 0;
+                       }
+
+                       force_purge = CC->kill_me;
+                       become_session(NULL);
+                       bind_me->state = CON_IDLE;
+               }
+
+               dead_session_purge(force_purge);
+               do_housekeeping();
+
+               pthread_mutex_lock(&ThreadCountMutex);
+               --active_workers;
+               if ((active_workers + CtdlGetConfigInt("c_min_workers") < num_workers) &&
+                   (num_workers > CtdlGetConfigInt("c_min_workers")))
+               {
+                       num_workers--;
+                       pthread_mutex_unlock(&ThreadCountMutex);
+                       return (NULL);
+               }
+               pthread_mutex_unlock(&ThreadCountMutex);
+       }
+
+       // If control reaches this point, the server is shutting down
+       pthread_mutex_lock(&ThreadCountMutex);
+       --num_workers;
+       pthread_mutex_unlock(&ThreadCountMutex);
+       return(NULL);
+}
+
+
+// SyslogFacility()
+// Translate text facility name to syslog.h defined value.
+int SyslogFacility(char *name)
+{
+       int i;
+       struct
+       {
+               int facility;
+               char *name;
+       }   facTbl[] =
+       {
+               {   LOG_KERN,   "kern"          },
+               {   LOG_USER,   "user"          },
+               {   LOG_MAIL,   "mail"          },
+               {   LOG_DAEMON, "daemon"        },
+               {   LOG_AUTH,   "auth"          },
+               {   LOG_SYSLOG, "syslog"        },
+               {   LOG_LPR,    "lpr"           },
+               {   LOG_NEWS,   "news"          },
+               {   LOG_UUCP,   "uucp"          },
+               {   LOG_LOCAL0, "local0"        },
+               {   LOG_LOCAL1, "local1"        },
+               {   LOG_LOCAL2, "local2"        },
+               {   LOG_LOCAL3, "local3"        },
+               {   LOG_LOCAL4, "local4"        },
+               {   LOG_LOCAL5, "local5"        },
+               {   LOG_LOCAL6, "local6"        },
+               {   LOG_LOCAL7, "local7"        },
+               {   0,            NULL          }
+       };
+       for(i = 0; facTbl[i].name != NULL; i++) {
+               if(!strcasecmp(name, facTbl[i].name))
+                       return facTbl[i].facility;
+       }
+       return LOG_DAEMON;
+}
diff --git a/citadel/server/sysdep.h b/citadel/server/sysdep.h
new file mode 100644 (file)
index 0000000..5659b46
--- /dev/null
@@ -0,0 +1,349 @@
+
+
+/* define this to the Citadel home directory */
+#define CTDLDIR "/root/citadel/citadel"
+
+/* define, if the user suplied a data-directory to use. */
+/* #undef DATA_DIR */
+
+/* whether we have NLS support */
+#define ENABLE_NLS /**/
+
+/* whats the matching format string for pid_t? */
+#define F_PID_T "%d"
+
+/* whats the matching format string for uid_t? */
+#define F_UID_T "%d"
+
+/* whats the matching format string for xpid_t? */
+#define F_XPID_T "%x"
+
+/* Define to 1 if the `getpgrp' function requires zero arguments. */
+#define GETPGRP_VOID 1
+
+/* Define to 1 if you have the <arpa/nameser_compat.h> header file. */
+#define HAVE_ARPA_NAMESER_COMPAT_H 1
+
+/* Define to 1 if you have the <arpa/nameser.h> header file. */
+#define HAVE_ARPA_NAMESER_H 1
+
+/* Define to 1 if you have the `connect' function. */
+#define HAVE_CONNECT 1
+
+/* Define to 1 if you have the <db.h> header file. */
+#define HAVE_DB_H 1
+
+/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
+   */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `gethostbyname' function. */
+#define HAVE_GETHOSTBYNAME 1
+
+/* Define to 1 if you have the `getloadavg' function. */
+#define HAVE_GETLOADAVG 1
+
+/* Define to 1 if you have the `getpwnam_r' function. */
+#define HAVE_GETPWNAM_R 1
+
+/* Define to 1 if you have the `getpwuid_r' function. */
+#define HAVE_GETPWUID_R 1
+
+/* Define to 1 if you have the `getspnam' function. */
+#define HAVE_GETSPNAM 1
+
+/* Define to 1 if you have the `gettext' function. */
+#define HAVE_GETTEXT 1
+
+/* Define to 1 if you have the `getutxline' function. */
+#define HAVE_GETUTXLINE 1
+
+/* whether we have iconv for charset conversion */
+#define HAVE_ICONV /**/
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `nsl' library (-lnsl). */
+/* #undef HAVE_LIBNSL */
+
+/* Define to 1 if you have the `pthread' library (-lpthread). */
+#define HAVE_LIBPTHREAD 1
+
+/* Define to 1 if you have the `rt' library (-lrt). */
+#define HAVE_LIBRT 1
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <malloc.h> header file. */
+#define HAVE_MALLOC_H 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mkdir' function. */
+#define HAVE_MKDIR 1
+
+/* Define to 1 if you have the `mkfifo' function. */
+#define HAVE_MKFIFO 1
+
+/* Define to 1 if you have the `mktime' function. */
+#define HAVE_MKTIME 1
+
+/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the <netinet/in.h> header file. */
+#define HAVE_NETINET_IN_H 1
+
+/* Define to 1 if you have the <paths.h> header file. */
+#define HAVE_PATHS_H 1
+
+/* define this if you have the pthread_cancel() function */
+#define HAVE_PTHREAD_CANCEL /**/
+
+/* Define to 1 if you have the <pthread.h> header file. */
+#define HAVE_PTHREAD_H 1
+
+/* define this if you have the resolv.h header file. */
+#define HAVE_RESOLV_H /**/
+
+/* Define to 1 if you have the `rmdir' function. */
+#define HAVE_RMDIR 1
+
+/* Define to 1 if you have the `select' function. */
+#define HAVE_SELECT 1
+
+/* Define to 1 if you have the `socket' function. */
+#define HAVE_SOCKET 1
+
+/* enable alternate spool dir? */
+/* #undef HAVE_SPOOL_DIR */
+
+/* should we activate an alternate static text location? */
+/* #undef HAVE_STATICDATA_DIR */
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the `strftime_l' function. */
+#define HAVE_STRFTIME_L 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strncasecmp' function. */
+#define HAVE_STRNCASECMP 1
+
+/* Define to 1 if `tm_gmtoff' is a member of `struct tm'. */
+#define HAVE_STRUCT_TM_TM_GMTOFF 1
+
+/* Define if struct ucred is present from sys/socket.h */
+#define HAVE_STRUCT_UCRED 1
+
+/* Define to 1 if you have the <syscall.h> header file. */
+#define HAVE_SYSCALL_H 1
+
+/* Define to 1 if you have the <syslog.h> header file. */
+#define HAVE_SYSLOG_H 1
+
+/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'.
+   */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define to 1 if you have the <sys/ioctl.h> header file. */
+#define HAVE_SYS_IOCTL_H 1
+
+/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'.
+   */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define to 1 if you have the <sys/prctl.h> header file. */
+#define HAVE_SYS_PRCTL_H 1
+
+/* Define to 1 if you have the <sys/select.h> header file. */
+#define HAVE_SYS_SELECT_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/syscall.h> header file. */
+#define HAVE_SYS_SYSCALL_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define to 1 if you have the <termios.h> header file. */
+#define HAVE_TERMIOS_H 1
+
+/* Define if you don't have `tm_gmtoff' but do have the external variable
+   `timezone'. */
+/* #undef HAVE_TIMEZONE */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the `uselocale' function. */
+#define HAVE_USELOCALE 1
+
+/* should we put our helper binaries to another location? */
+/* #undef HAVE_UTILBIN_DIR */
+
+/* Define to 1 if you have the <utmpx.h> header file. */
+#define HAVE_UTMPX_H 1
+
+/* Define to 1 if you have the <utmp.h> header file. */
+#define HAVE_UTMP_H 1
+
+/* define this if struct utmp has an ut_host member */
+#define HAVE_UT_HOST /**/
+
+/* define this if struct utmp has an ut_type member */
+#define HAVE_UT_TYPE /**/
+
+/* Define to 1 if you have the `vprintf' function. */
+#define HAVE_VPRINTF 1
+
+/* Define to 1 if you have the `vw_printw' function. */
+/* #undef HAVE_VW_PRINTW */
+
+/* Define to 1 if you have the `wcolor_set' function. */
+/* #undef HAVE_WCOLOR_SET */
+
+/* Define to 1 if you have the `wresize' function. */
+/* #undef HAVE_WRESIZE */
+
+/* define, if the user suplied a helpfile-directory to use. */
+/* #undef HELP_DIR */
+
+/* where to find our pot files */
+#define LOCALEDIR "/root/citadel/citadel"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "http://uncensored.citadel.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "Citadel"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "Citadel 951"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "citadel"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "951"
+
+/* Define as the return type of signal handlers (`int' or `void'). */
+#define RETSIGTYPE void
+
+/* define, where the config should go in unix style */
+/* #undef RUN_DIR */
+
+/* The size of `char', as computed by sizeof. */
+#define SIZEOF_CHAR 1
+
+/* The size of `int', as computed by sizeof. */
+#define SIZEOF_INT 4
+
+/* The size of `loff_t', as computed by sizeof. */
+#define SIZEOF_LOFF_T 8
+
+/* The size of `long', as computed by sizeof. */
+#define SIZEOF_LONG 8
+
+/* The size of `short', as computed by sizeof. */
+#define SIZEOF_SHORT 2
+
+/* The size of `size_t', as computed by sizeof. */
+#define SIZEOF_SIZE_T 8
+
+/* do we need to use solaris call syntax? */
+/* #undef SOLARIS_GETPWUID */
+
+/* where do we place our spool dirs? */
+/* #undef SPOOL_DIR */
+
+/* were should we put our keys? */
+#define SSL_DIR "/root/citadel/citadel/keys"
+
+/* where do we put our static text data? */
+/* #undef STATICDATA_DIR */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#define TIME_WITH_SYS_TIME 1
+
+/* Define to 1 if your <sys/time.h> declares `struct tm'. */
+/* #undef TM_IN_SYS_TIME */
+
+/* Enable extensions on AIX 3, Interix.  */
+#ifndef _ALL_SOURCE
+# define _ALL_SOURCE 1
+#endif
+/* Enable GNU extensions on systems that have them.  */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+/* Enable threading extensions on Solaris.  */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+/* Enable extensions on HP NonStop.  */
+#ifndef _TANDEM_SOURCE
+# define _TANDEM_SOURCE 1
+#endif
+/* Enable general extensions on Solaris.  */
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1
+#endif
+
+
+/* were to put our helper programs */
+/* #undef UTILBIN_DIR */
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+   this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef pid_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */
diff --git a/citadel/server/sysdep_decls.h b/citadel/server/sysdep_decls.h
new file mode 100644 (file)
index 0000000..221640f
--- /dev/null
@@ -0,0 +1,75 @@
+
+#ifndef SYSDEP_DECLS_H
+#define SYSDEP_DECLS_H
+
+#include <stdarg.h>
+#include "sysdep.h"
+
+#ifdef HAVE_PTHREAD_H
+#include <pthread.h>
+#endif
+
+#include <db.h>
+
+#if DB_VERSION_MAJOR < 5
+#error Citadel requires Berkeley DB v5 or newer.  Please upgrade.
+#endif
+
+#include "server.h"
+#include "database.h"
+
+#if SIZEOF_SIZE_T == SIZEOF_INT 
+#define SIZE_T_FMT "%d"
+#else
+#define SIZE_T_FMT "%ld"
+#endif
+
+#if SIZEOF_LOFF_T == SIZEOF_LONG 
+#define LOFF_T_FMT "%ld"
+#else
+#define LOFF_T_FMT "%lld"
+#endif
+
+void cputbuf(const StrBuf *Buf);
+
+#ifdef __GNUC__
+void cprintf (const char *format, ...) __attribute__((__format__(__printf__,1,2)));
+#else
+void cprintf (const char *format, ...);
+#endif
+
+void init_sysdep (void);
+int ctdl_tcp_server(char *ip_addr, int port_number, int queue_len);
+int ctdl_uds_server(char *sockpath, int queue_len);
+void buffer_output(void);
+void unbuffer_output(void);
+void flush_output(void);
+int client_write (const char *buf, int nbytes);
+int client_read_to (char *buf, int bytes, int timeout);
+int client_read (char *buf, int bytes);
+int client_getln (char *buf, int maxbytes);
+int CtdlClientGetLine(StrBuf *Target);
+int client_read_blob(StrBuf *Target, int bytes, int timeout);
+void client_set_inbound_buf(long N);
+int client_read_random_blob(StrBuf *Target, int timeout);
+void client_close(void);
+void sysdep_master_cleanup (void);
+void kill_session (int session_to_kill);
+void start_daemon (int do_close_stdio);
+void checkcrash(void);
+int convert_login (char *NameToConvert);
+void init_master_fdset(void);
+void *worker_thread(void *);
+
+extern volatile int exit_signal;
+extern volatile int shutdown_and_halt;
+extern volatile int running_as_daemon;
+extern volatile int restart_server;
+
+extern int verbosity;
+extern int rescan[];
+
+
+extern int SyslogFacility(char *name);
+
+#endif /* SYSDEP_DECLS_H */
diff --git a/citadel/server/threads.c b/citadel/server/threads.c
new file mode 100644 (file)
index 0000000..7b404b0
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * Thread handling stuff for Citadel server
+ *
+ * Copyright (c) 1987-2021 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <libcitadel.h>
+#include "modules_init.h"
+#include "serv_extensions.h"
+#include "ctdl_module.h"
+#include "config.h"
+#include "context.h"
+#include "threads.h"
+
+int num_workers = 0;                           /* Current number of worker threads */
+int active_workers = 0;                                /* Number of ACTIVE worker threads */
+pthread_key_t ThreadKey;
+pthread_mutex_t Critters[MAX_SEMAPHORES];      /* Things needing locking */
+struct thread_tsd masterTSD;
+int server_shutting_down = 0;                  /* set to nonzero during shutdown */
+pthread_mutex_t ThreadCountMutex;
+
+void InitializeSemaphores(void)
+{
+       int i;
+
+       /* Set up a bunch of semaphores to be used for critical sections */
+       for (i=0; i<MAX_SEMAPHORES; ++i) {
+               pthread_mutex_init(&Critters[i], NULL);
+       }
+}
+
+
+/*
+ * Obtain a semaphore lock to begin a critical section.
+ * but only if no one else has one
+ */
+int try_critical_section(int which_one)
+{
+       /* For all types of critical sections except those listed here,
+        * ensure nobody ever tries to do a critical section within a
+        * transaction; this could lead to deadlock.
+        */
+       if (    (which_one != S_FLOORCACHE)
+               && (which_one != S_NETCONFIGS)
+       ) {
+               cdb_check_handles();
+       }
+       return (pthread_mutex_trylock(&Critters[which_one]));
+}
+
+
+/*
+ * Obtain a semaphore lock to begin a critical section.
+ */
+void begin_critical_section(int which_one)
+{
+       /* For all types of critical sections except those listed here,
+        * ensure nobody ever tries to do a critical section within a
+        * transaction; this could lead to deadlock.
+        */
+       if (    (which_one != S_FLOORCACHE)
+               && (which_one != S_NETCONFIGS)
+       ) {
+               cdb_check_handles();
+       }
+       pthread_mutex_lock(&Critters[which_one]);
+}
+
+
+/*
+ * Release a semaphore lock to end a critical section.
+ */
+void end_critical_section(int which_one)
+{
+       pthread_mutex_unlock(&Critters[which_one]);
+}
+
+
+/*
+ * Return a pointer to our thread-specific (not session-specific) data.
+ */ 
+struct thread_tsd *MyThread(void) {
+        struct thread_tsd *c = (struct thread_tsd *) pthread_getspecific(ThreadKey) ;
+       if (!c) {
+               return &masterTSD;
+       }
+       return c;
+}
+
+
+/* 
+ * Called by CtdlThreadCreate()
+ * We have to pass through here before starting our thread in order to create a set of data
+ * that is thread-specific rather than session-specific.
+ */
+void *CTC_backend(void *supplied_start_routine)
+{
+       struct thread_tsd *mytsd;
+       void *(*start_routine)(void*) = supplied_start_routine;
+
+       mytsd = (struct thread_tsd *) malloc(sizeof(struct thread_tsd));
+       memset(mytsd, 0, sizeof(struct thread_tsd));
+       pthread_setspecific(ThreadKey, (const void *) mytsd);
+
+       start_routine(NULL);
+       // free(mytsd);
+       return(NULL);
+}
+
+/*
+ * Function to create a thread.
+ */ 
+void CtdlThreadCreate(void *(*start_routine)(void*))
+{
+       pthread_t thread;
+       pthread_attr_t attr;
+       int ret = 0;
+
+       ret = pthread_attr_init(&attr);
+       ret = pthread_attr_setstacksize(&attr, THREADSTACKSIZE);
+       ret = pthread_create(&thread, &attr, CTC_backend, (void *)start_routine);
+       if (ret != 0) syslog(LOG_ERR, "pthread_create() : %m");
+}
+
+
+void InitializeMasterTSD(void) {
+       memset(&masterTSD, 0, sizeof(struct thread_tsd));
+}
+
+
+/*
+ * Initialize the thread system
+ */
+void go_threading(void) {
+       pthread_mutex_init(&ThreadCountMutex, NULL);
+
+       /* Second call to module init functions now that threading is up */
+       initialize_modules(1);
+
+       /* Begin with one worker thread.  We will expand the pool if necessary */
+       CtdlThreadCreate(worker_thread);
+
+       /* The supervisor thread monitors worker threads and spawns more of them if it finds that
+        * they are all in use.
+        */
+       while (!server_shutting_down) {
+               if ((active_workers == num_workers) && (num_workers < CtdlGetConfigInt("c_max_workers"))) {
+                       CtdlThreadCreate(worker_thread);
+               }
+               usleep(1000000);
+       }
+
+       /* When we get to this point we are getting ready to shut down our Citadel server */
+       terminate_all_sessions();               /* close all client sockets */
+       CtdlShutdownServiceHooks();             /* close all listener sockets to prevent new connections */
+       PerformSessionHooks(EVT_SHUTDOWN);      /* run any registered shutdown hooks */
+
+       /* We used to wait for all threads to exit.  Fuck that.  The only thing important is that the databases are
+        * cleanly unmounted.  After that, exit the whole program.
+        */
+}
diff --git a/citadel/server/threads.h b/citadel/server/threads.h
new file mode 100644 (file)
index 0000000..33b854e
--- /dev/null
@@ -0,0 +1,46 @@
+
+#ifndef THREADS_H
+#define THREADS_H
+
+#include "sysdep.h"
+
+#ifdef HAVE_PTHREAD_H
+#include <pthread.h>
+#endif
+
+#include <sys/time.h>
+#include <string.h>
+
+#include <db.h>
+
+#include "server.h"
+#include "sysdep_decls.h"
+
+/*
+ * Things we need to keep track of per-thread instead of per-session
+ */
+struct thread_tsd {
+       DB_TXN *tid;            /* Transaction handle */
+       DBC *cursors[MAXCDB];   /* Cursors, for traversals... */
+};
+
+extern pthread_key_t ThreadKey;
+extern struct thread_tsd masterTSD;
+#define TSD MyThread()
+
+extern int num_workers;
+extern int active_workers;
+extern int server_shutting_down;
+
+struct thread_tsd *MyThread(void);
+int try_critical_section (int which_one);
+void begin_critical_section (int which_one);
+void end_critical_section (int which_one);
+void go_threading(void);
+void InitializeMasterTSD(void);
+void CtdlThreadCreate(void *(*start_routine)(void*));
+
+
+extern pthread_mutex_t ThreadCountMutex;;
+
+#endif // THREADS_H
diff --git a/citadel/server/typesize.h b/citadel/server/typesize.h
new file mode 100644 (file)
index 0000000..3732c39
--- /dev/null
@@ -0,0 +1,69 @@
+
+/*
+   This file defines typedefs for 8, 16, and 32 bit integers.  They are:
+   cit_int8_t  default 8-bit int
+   cit_int16_t default 16-bit int
+   cit_int32_t default 32-bit int
+   cit_int64_t default 64-bit int (not implemented yet)
+   cit_sint8_t signed 8-bit int
+   cit_sint16_t        signed 16-bit int
+   cit_sint32_t        signed 32-bit int
+   cit_sint64_t        signed 64-bit int (not implemented yet)
+   cit_uint8_t unsigned 8-bit int
+   cit_uint16_t        unsigned 16-bit int
+   cit_uint32_t        unsigned 32-bit int
+   cit_uint64_t        unsigned 64-bit int (not implemented yet)
+
+   The sizes are determined during the configure process; see the 
+   AC_CHECK_SIZEOF macros in configure.in.  In no way do we assume that any
+   given datatype is any particular width, e.g. we don't assume short is two
+   bytes; we check for it specifically.
+
+   This might seem excessively paranoid, but I've seen some WEIRD systems
+   and some bizarre compilers (Domain/OS for instance) in my time.
+*/
+
+#ifndef _CITADEL_UX_TYPESIZE_H
+#define _CITADEL_UX_TYPESIZE_H
+
+/* Include sysdep.h if not already included */
+#ifndef CTDLDIR
+# include "sysdep.h"
+#endif
+
+/* 8-bit - If this fails, your compiler is broken */
+#if SIZEOF_CHAR == 1
+typedef char cit_int8_t;
+typedef signed char cit_sint8_t;
+typedef unsigned char cit_uint8_t;
+#else
+# error Unable to find an 8-bit integer datatype
+#endif
+
+/* 16-bit - If this fails, your compiler is broken */
+#if SIZEOF_SHORT == 2
+typedef short cit_int16_t;
+typedef signed short cit_sint16_t;
+typedef unsigned short cit_uint16_t;
+#elif SIZEOF_INT == 2
+typedef int cit_int16_t;
+typedef signed int cit_sint16_t;
+typedef unsigned int cit_uint16_t;
+#else
+# error Unable to find a 16-bit integer datatype
+#endif
+
+/* 32-bit - If this fails, your compiler is broken */
+#if SIZEOF_INT == 4
+typedef int cit_int32_t;
+typedef signed int cit_sint32_t;
+typedef unsigned int cit_uint32_t;
+#elif SIZEOF_LONG == 4
+typedef long cit_int32_t;
+typedef signed long cit_sint32_t;
+typedef unsigned long cit_uint32_t;
+#else
+# error Unable to find a 32-bit integer datatype
+#endif
+
+#endif /* _CITADEL_UX_TYPESIZE_H */
diff --git a/citadel/server/user_ops.c b/citadel/server/user_ops.c
new file mode 100644 (file)
index 0000000..0603fc7
--- /dev/null
@@ -0,0 +1,1169 @@
+// Server functions which perform operations on user objects.
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include "sysdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <libcitadel.h>
+#include "control.h"
+#include "support.h"
+#include "citserver.h"
+#include "config.h"
+#include "citadel_ldap.h"
+#include "ctdl_module.h"
+#include "user_ops.h"
+#include "internet_addressing.h"
+
+// These pipes are used to talk to the chkpwd daemon, which is forked during startup
+int chkpwd_write_pipe[2];
+int chkpwd_read_pipe[2];
+
+
+// makeuserkey() - convert a username into the format used as a database key
+//             "key" must be a buffer of at least USERNAME_SIZE
+//             (Key format is the username with all non-alphanumeric characters removed, and converted to lower case.)
+void makeuserkey(char *key, const char *username) {
+       int i;
+       int keylen = 0;
+
+       if (IsEmptyStr(username)) {
+               key[0] = 0;
+               return;
+       }
+
+       int len = strlen(username);
+       for (i=0; ((i<=len) && (i<USERNAME_SIZE-1)); ++i) {
+               if (isalnum((username[i]))) {
+                       key[keylen++] = tolower(username[i]);
+               }
+       }
+       key[keylen++] = 0;
+}
+
+
+// Compare two usernames to see if they are the same user after being keyed for the database
+// Usage is identical to strcmp()
+int CtdlUserCmp(char *s1, char *s2) {
+       char k1[USERNAME_SIZE];
+       char k2[USERNAME_SIZE];
+
+       makeuserkey(k1, s1);
+       makeuserkey(k2, s2);
+       return(strcmp(k1,k2));
+}
+
+
+// CtdlGetUser()       retrieve named user into supplied buffer.
+//                     returns 0 on success
+int CtdlGetUser(struct ctdluser *usbuf, char *name) {
+       char usernamekey[USERNAME_SIZE];
+       struct cdbdata *cdbus;
+
+       if (usbuf != NULL) {
+               memset(usbuf, 0, sizeof(struct ctdluser));
+       }
+
+       makeuserkey(usernamekey, name);
+       if (IsEmptyStr(usernamekey)) {
+               return(1);      // empty user name
+       }
+       cdbus = cdb_fetch(CDB_USERS, usernamekey, strlen(usernamekey));
+
+       if (cdbus == NULL) {    // user not found
+               return(1);
+       }
+       if (usbuf != NULL) {
+               memcpy(usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ?  sizeof(struct ctdluser) : cdbus->len));
+       }
+       cdb_free(cdbus);
+       return(0);
+}
+
+
+int CtdlLockGetCurrentUser(void) {
+       return CtdlGetUser(&CC->user, CC->curr_user);
+}
+
+
+// CtdlGetUserLock()  -  same as getuser() but locks the record
+int CtdlGetUserLock(struct ctdluser *usbuf, char *name) {
+       int retcode;
+
+       retcode = CtdlGetUser(usbuf, name);
+       if (retcode == 0) {
+               begin_critical_section(S_USERS);
+       }
+       return(retcode);
+}
+
+
+// CtdlPutUser()  -  write user buffer into the correct place on disk
+void CtdlPutUser(struct ctdluser *usbuf) {
+       char usernamekey[USERNAME_SIZE];
+       makeuserkey(usernamekey, usbuf->fullname);
+       usbuf->version = REV_LEVEL;
+       cdb_store(CDB_USERS, usernamekey, strlen(usernamekey), usbuf, sizeof(struct ctdluser));
+}
+
+
+void CtdlPutCurrentUserLock() {
+       CtdlPutUser(&CC->user);
+}
+
+
+// CtdlPutUserLock()  -  same as putuser() but locks the record
+void CtdlPutUserLock(struct ctdluser *usbuf) {
+       CtdlPutUser(usbuf);
+       end_critical_section(S_USERS);
+}
+
+
+// rename_user()  -  this is tricky because the user's display name is the database key
+// Returns 0 on success or nonzero if there was an error...
+int rename_user(char *oldname, char *newname) {
+       int retcode = RENAMEUSER_OK;
+       struct ctdluser usbuf;
+
+       char oldnamekey[USERNAME_SIZE];
+       char newnamekey[USERNAME_SIZE];
+
+       // Create the database keys...
+       makeuserkey(oldnamekey, oldname);
+       makeuserkey(newnamekey, newname);
+
+       // Lock up and get going
+       begin_critical_section(S_USERS);
+
+       // We cannot rename a user who is currently logged in
+       if (CtdlIsUserLoggedIn(oldname)) {
+               end_critical_section(S_USERS);
+               return RENAMEUSER_LOGGED_IN;
+       }
+
+       if (CtdlGetUser(&usbuf, newname) == 0) {
+               retcode = RENAMEUSER_ALREADY_EXISTS;
+       }
+       else {
+
+               if (CtdlGetUser(&usbuf, oldname) != 0) {
+                       retcode = RENAMEUSER_NOT_FOUND;
+               }
+               else {          // Sanity checks succeeded.  Now rename the user.
+                       if (usbuf.usernum == 0) {
+                               syslog(LOG_DEBUG, "user_ops: can not rename user \"Citadel\".");
+                               retcode = RENAMEUSER_NOT_FOUND;
+                       }
+                       else {
+                               syslog(LOG_DEBUG, "user_ops: renaming <%s> to <%s>", oldname, newname);
+                               cdb_delete(CDB_USERS, oldnamekey, strlen(oldnamekey));
+                               safestrncpy(usbuf.fullname, newname, sizeof usbuf.fullname);
+                               CtdlPutUser(&usbuf);
+                               cdb_store(CDB_USERSBYNUMBER, &usbuf.usernum, sizeof(long), usbuf.fullname, strlen(usbuf.fullname)+1 );
+                               retcode = RENAMEUSER_OK;
+                       }
+               }
+       
+       }
+
+       end_critical_section(S_USERS);
+       return(retcode);
+}
+
+
+// Convert a username into the format used as a database key prior to version 928
+// This only gets called by reindex_user_928()
+void makeuserkey_pre928(char *key, const char *username) {
+       int i;
+
+       int len = strlen(username);
+
+       if (len >= USERNAME_SIZE) {
+               syslog(LOG_INFO, "Username too long: %s", username);
+               len = USERNAME_SIZE - 1; 
+       }
+       for (i=0; i<=len; ++i) {
+               key[i] = tolower(username[i]);
+       }
+}
+
+
+// Read a user record using the pre-v928 index format, and write it back using the v928-and-higher index format.
+// This ONLY gets called during an upgrade from version <928 to version >=928.
+void reindex_user_928(char *username, void *out_data) {
+
+       char oldkey[USERNAME_SIZE];
+       char newkey[USERNAME_SIZE];
+       struct cdbdata *cdbus;
+       struct ctdluser usbuf;
+
+       makeuserkey_pre928(oldkey, username);
+       makeuserkey(newkey, username);
+
+       syslog(LOG_DEBUG, "user_ops: reindex_user_928: %s <%s> --> <%s>", username, oldkey, newkey);
+
+       // Fetch the user record using the old index format
+       cdbus = cdb_fetch(CDB_USERS, oldkey, strlen(oldkey));
+       if (cdbus == NULL) {
+               syslog(LOG_INFO, "user_ops: <%s> not found, were they already reindexed?", username);
+               return;
+       }
+       memcpy(&usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ? sizeof(struct ctdluser) : cdbus->len));
+       cdb_free(cdbus);
+
+       // delete the old record
+       cdb_delete(CDB_USERS, oldkey, strlen(oldkey));
+
+       // write the new record
+       cdb_store(CDB_USERS, newkey, strlen(newkey), &usbuf, sizeof(struct ctdluser));
+}
+
+
+// Index-generating function used by Ctdl[Get|Set]Relationship
+int GenerateRelationshipIndex(char *IndexBuf,
+                             long RoomID,
+                             long RoomGen,
+                             long UserID
+) {
+       struct {
+               long iRoomID;
+               long iRoomGen;
+               long iUserID;
+       } TheIndex;
+
+       TheIndex.iRoomID = RoomID;
+       TheIndex.iRoomGen = RoomGen;
+       TheIndex.iUserID = UserID;
+
+       memcpy(IndexBuf, &TheIndex, sizeof(TheIndex));
+       return(sizeof(TheIndex));
+}
+
+
+// Back end for CtdlSetRelationship()
+void put_visit(visit *newvisit) {
+       char IndexBuf[32];
+       int IndexLen = 0;
+
+       memset (IndexBuf, 0, sizeof (IndexBuf));
+       // Generate an index
+       IndexLen = GenerateRelationshipIndex(IndexBuf, newvisit->v_roomnum, newvisit->v_roomgen, newvisit->v_usernum);
+
+       // Store the record
+       cdb_store(CDB_VISIT, IndexBuf, IndexLen,
+                 newvisit, sizeof(visit)
+       );
+}
+
+
+// Define a relationship between a user and a room
+void CtdlSetRelationship(visit *newvisit, struct ctdluser *rel_user, struct ctdlroom *rel_room) {
+       // We don't use these in Citadel because they're implicit by the
+       // index, but they must be present if the database is exported.
+       newvisit->v_roomnum = rel_room->QRnumber;
+       newvisit->v_roomgen = rel_room->QRgen;
+       newvisit->v_usernum = rel_user->usernum;
+
+       put_visit(newvisit);
+}
+
+
+// Locate a relationship between a user and a room
+void CtdlGetRelationship(visit *vbuf, struct ctdluser *rel_user, struct ctdlroom *rel_room) {
+       char IndexBuf[32];
+       int IndexLen;
+       struct cdbdata *cdbvisit;
+
+       // Generate an index
+       IndexLen = GenerateRelationshipIndex(IndexBuf, rel_room->QRnumber, rel_room->QRgen, rel_user->usernum);
+
+       // Clear out the buffer
+       memset(vbuf, 0, sizeof(visit));
+
+       cdbvisit = cdb_fetch(CDB_VISIT, IndexBuf, IndexLen);
+       if (cdbvisit != NULL) {
+               memcpy(vbuf, cdbvisit->ptr, ((cdbvisit->len > sizeof(visit)) ?  sizeof(visit) : cdbvisit->len));
+               cdb_free(cdbvisit);
+       }
+       else {
+               // If this is the first time the user has seen this room, set the view to be the default for the room.
+               vbuf->v_view = rel_room->QRdefaultview;
+       }
+
+       // Set v_seen if necessary
+       if (vbuf->v_seen[0] == 0) {
+               snprintf(vbuf->v_seen, sizeof vbuf->v_seen, "*:%ld", vbuf->v_lastseen);
+       }
+}
+
+
+void CtdlMailboxName(char *buf, size_t n, const struct ctdluser *who, const char *prefix) {
+       snprintf(buf, n, "%010ld.%s", who->usernum, prefix);
+}
+
+
+// Check to see if the specified user has Internet mail permission
+// (returns nonzero if permission is granted)
+int CtdlCheckInternetMailPermission(struct ctdluser *who) {
+
+       // Do not allow twits to send Internet mail
+       if (who->axlevel <= AxProbU) return(0);
+
+       // Globally enabled?
+       if (CtdlGetConfigInt("c_restrict") == 0) return(1);
+
+       // User flagged ok?
+       if (who->flags & US_INTERNET) return(2);
+
+       // Admin level access?
+       if (who->axlevel >= AxAideU) return(3);
+
+       // No mail for you!
+       return(0);
+}
+
+
+// This is a convenience function which follows the Citadel protocol semantics for most commands.
+// If the current user does not have the requested access level, it outputs a protocol-friendly error message
+// and then returns (-1).  This allows calling functions to complete an access level check in one line of code.
+int CtdlAccessCheck(int required_level) {
+       if (CC->internal_pgm) return(0);
+       if (required_level >= ac_internal) {
+               cprintf("%d This is not a user-level command.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+               return(-1);
+       }
+
+       if ((required_level >= ac_logged_in_or_guest) && (CC->logged_in == 0) && (CtdlGetConfigInt("c_guest_logins") == 0)) {
+               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+               return(-1);
+       }
+
+       if ((required_level >= ac_logged_in) && (CC->logged_in == 0)) {
+               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+               return(-1);
+       }
+
+       if (CC->user.axlevel >= AxAideU) return(0);
+       if (required_level >= ac_aide) {
+               cprintf("%d This command requires Admin access.\n",
+                       ERROR + HIGHER_ACCESS_REQUIRED);
+               return(-1);
+       }
+
+       if (is_room_aide()) return(0);
+       if (required_level >= ac_room_aide) {
+               cprintf("%d This command requires Admin or Room Admin access.\n",
+                       ERROR + HIGHER_ACCESS_REQUIRED);
+               return(-1);
+       }
+
+       // Do not generate any output if we succeeded -- the calling function will handle that.
+       return(0);
+}
+
+
+// Is the user currently logged in an Admin?
+int is_aide(void) {
+       if (CC->user.axlevel >= AxAideU) {
+               return(1);
+       }
+       else {
+               return(0);
+       }
+}
+
+
+// Is the user currently logged in an Admin *or* the room Admin for this room?
+int is_room_aide(void) {
+       if (!CC->logged_in) {
+               return(0);
+       }
+
+       if ((CC->user.axlevel >= AxAideU) || (CC->room.QRroomaide == CC->user.usernum)) {
+               return(1);
+       }
+       else {
+               return(0);
+       }
+}
+
+
+// CtdlGetUserByNumber() - get user by number, returns 0 if user was found
+// Note: fetching a user this way requires one additional database operation.
+int CtdlGetUserByNumber(struct ctdluser *usbuf, long number) {
+       struct cdbdata *cdbun;
+       int r;
+
+       cdbun = cdb_fetch(CDB_USERSBYNUMBER, &number, sizeof(long));
+       if (cdbun == NULL) {
+               syslog(LOG_INFO, "user_ops: %ld not found", number);
+               return(-1);
+       }
+
+       syslog(LOG_INFO, "user_ops: %ld maps to %s", number, cdbun->ptr);
+       r = CtdlGetUser(usbuf, cdbun->ptr);
+       cdb_free(cdbun);
+       return(r);
+}
+
+
+// Helper function for rebuild_usersbynumber()
+void rebuild_ubn_for_user(char *username, void *data) {
+       struct ctdluser u;
+
+       syslog(LOG_DEBUG, "user_ops: rebuilding usersbynumber index for %s", username);
+       if (CtdlGetUser(&u, username) == 0) {
+               cdb_store(CDB_USERSBYNUMBER, &(u.usernum), sizeof(long), u.fullname, strlen(u.fullname)+1);
+       }
+}
+
+
+// Rebuild the users-by-number index
+void rebuild_usersbynumber(void) {
+       cdb_trunc(CDB_USERSBYNUMBER);                   // delete the old indices
+       ForEachUser(rebuild_ubn_for_user, NULL);        // enumerate the users
+}
+
+
+// getuserbyuid()      Get user by system uid (for PAM mode authentication)
+//                     Returns 0 if user was found
+//                     This now uses an extauth index.
+int getuserbyuid(struct ctdluser *usbuf, uid_t number) {
+       struct cdbdata *cdbextauth;
+       long usernum = 0;
+       StrBuf *claimed_id;
+
+       claimed_id = NewStrBuf();
+       StrBufPrintf(claimed_id, "uid:%d", number);
+       cdbextauth = cdb_fetch(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id));
+       FreeStrBuf(&claimed_id);
+       if (cdbextauth == NULL) {
+               return(-1);
+       }
+
+       memcpy(&usernum, cdbextauth->ptr, sizeof(long));
+       cdb_free(cdbextauth);
+
+       if (!CtdlGetUserByNumber(usbuf, usernum)) {
+               return(0);
+       }
+
+       return(-1);
+}
+
+
+// Back end for cmd_user() and its ilk
+int CtdlLoginExistingUser(const char *trythisname) {
+       char username[SIZ];
+       int found_user;
+
+       syslog(LOG_DEBUG, "user_ops: CtdlLoginExistingUser(%s)", trythisname);
+
+       if ((CC->logged_in)) {
+               return login_already_logged_in;
+       }
+
+       if (trythisname == NULL) return login_not_found;
+       
+       if (!strncasecmp(trythisname, "SYS_", 4)) {
+               syslog(LOG_DEBUG, "user_ops: system user \"%s\" is not allowed to log in.", trythisname);
+               return login_not_found;
+       }
+
+       // Continue attempting user validation...
+       safestrncpy(username, trythisname, sizeof (username));
+       striplt(username);
+
+       if (IsEmptyStr(username)) {
+               return login_not_found;
+       }
+
+       // host auth mode...
+       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
+               struct passwd pd;
+               struct passwd *tempPwdPtr;
+               char pwdbuffer[256];
+       
+               syslog(LOG_DEBUG, "user_ops: asking host about <%s>", username);
+#ifdef HAVE_GETPWNAM_R
+#ifdef SOLARIS_GETPWUID
+               syslog(LOG_DEBUG, "user_ops: calling getpwnam_r()");
+               tempPwdPtr = getpwnam_r(username, &pd, pwdbuffer, sizeof pwdbuffer);
+#else // SOLARIS_GETPWUID
+               syslog(LOG_DEBUG, "user_ops: calling getpwnam_r()");
+               getpwnam_r(username, &pd, pwdbuffer, sizeof pwdbuffer, &tempPwdPtr);
+#endif // SOLARIS_GETPWUID
+#else // HAVE_GETPWNAM_R
+               syslog(LOG_DEBUG, "user_ops: SHOULD NEVER GET HERE!!!");
+               tempPwdPtr = NULL;
+#endif // HAVE_GETPWNAM_R
+               if (tempPwdPtr == NULL) {
+                       syslog(LOG_DEBUG, "user_ops: no such user <%s>", username);
+                       return login_not_found;
+               }
+       
+               // Locate the associated Citadel account.  If not found, make one attempt to create it.
+               found_user = getuserbyuid(&CC->user, pd.pw_uid);
+               if (found_user != 0) {
+                       create_user(username, CREATE_USER_DO_NOT_BECOME_USER, pd.pw_uid);
+                       found_user = getuserbyuid(&CC->user, pd.pw_uid);
+               }
+               syslog(LOG_DEBUG, "user_ops: found it: uid=%ld, gecos=%s here: %d", (long)pd.pw_uid, pd.pw_gecos, found_user);
+
+       }
+
+       // LDAP auth mode...
+       else if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
+
+               uid_t ldap_uid;
+               char ldap_cn[256];
+               char ldap_dn[256];
+
+               found_user = CtdlTryUserLDAP(username, ldap_dn, sizeof ldap_dn, ldap_cn, sizeof ldap_cn, &ldap_uid);
+               if (found_user != 0) {
+                       return login_not_found;
+               }
+
+               found_user = getuserbyuid(&CC->user, ldap_uid);
+               if (found_user != 0) {
+                       create_user(ldap_cn, CREATE_USER_DO_NOT_BECOME_USER, ldap_uid);
+                       found_user = getuserbyuid(&CC->user, ldap_uid);
+               }
+
+               if (found_user == 0) {
+                       if (CC->ldap_dn != NULL) free(CC->ldap_dn);
+                       CC->ldap_dn = strdup(ldap_dn);
+               }
+
+       }
+
+       // native auth mode...
+       else {
+               struct recptypes *valid = NULL;
+       
+               // First, try to log in as if the supplied name is a display name
+               found_user = CtdlGetUser(&CC->user, username);
+       
+               // If that didn't work, try to log in as if the supplied name * is an e-mail address
+               if (found_user != 0) {
+                       valid = validate_recipients(username, NULL, 0);
+                       if (valid != NULL) {
+                               if (valid->num_local == 1) {
+                                       found_user = CtdlGetUser(&CC->user, valid->recp_local);
+                               }
+                               free_recipients(valid);
+                       }
+               }
+       }
+
+       // Did we find something?
+       if (found_user == 0) {
+               if (((CC->nologin)) && (CC->user.axlevel < AxAideU)) {
+                       return login_too_many_users;
+               }
+               else {
+                       safestrncpy(CC->curr_user, CC->user.fullname, sizeof CC->curr_user);
+                       return login_ok;
+               }
+       }
+       return login_not_found;
+}
+
+
+// session startup code which is common to both cmd_pass() and cmd_newu()
+void do_login(void) {
+       CC->logged_in = 1;
+       syslog(LOG_NOTICE, "user_ops: <%s> logged in", CC->curr_user);
+
+       CtdlGetUserLock(&CC->user, CC->curr_user);
+       ++(CC->user.timescalled);
+       CC->previous_login = CC->user.lastcall;
+       time(&CC->user.lastcall);
+
+       // If this user's name is the name of the system administrator
+       // (as specified in setup), automatically assign access level 6.
+       if ( (!IsEmptyStr(CtdlGetConfigStr("c_sysadm"))) && (!strcasecmp(CC->user.fullname, CtdlGetConfigStr("c_sysadm"))) ) {
+               CC->user.axlevel = AxAideU;
+       }
+
+       // If we're authenticating off the host system, automatically give root the highest level of access.
+       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
+               if (CC->user.uid == 0) {
+                       CC->user.axlevel = AxAideU;
+               }
+       }
+       CtdlPutUserLock(&CC->user);
+
+       // If we are using LDAP authentication, extract the user's email addresses from the directory.
+       if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
+               char new_emailaddrs[512];
+               if (CtdlGetConfigInt("c_ldap_sync_email_addrs") > 0) {
+                       if (extract_email_addresses_from_ldap(CC->ldap_dn, new_emailaddrs) == 0) {
+                               CtdlSetEmailAddressesForUser(CC->user.fullname, new_emailaddrs);
+                       }
+               }
+       }
+
+       // If the user does not have any email addresses assigned, generate one.
+       if (IsEmptyStr(CC->user.emailaddrs)) {
+               AutoGenerateEmailAddressForUser(&CC->user);
+       }
+
+       // Populate the user principal identity, which is consistent and never aliased
+       strcpy(CC->cs_principal_id, "");
+       makeuserkey(CC->cs_principal_id, CC->user.fullname);
+       strcat(CC->cs_principal_id, "@");
+       strcat(CC->cs_principal_id, CtdlGetConfigStr("c_fqdn"));
+
+       // Populate cs_inet_email and cs_inet_other_emails with valid email addresses from the user record
+       strcpy(CC->cs_inet_email, CC->user.emailaddrs);
+       char *firstsep = strstr(CC->cs_inet_email, "|");
+       if (firstsep) {
+               strcpy(CC->cs_inet_other_emails, firstsep+1);
+               *firstsep = 0;
+       }
+       else {
+               CC->cs_inet_other_emails[0] = 0;
+       }
+
+       // Create any personal rooms required by the system.
+       // (Technically, MAILROOM should be there already, but just in case...)
+       CtdlCreateRoom(MAILROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
+       CtdlCreateRoom(SENTITEMS, 4, "", 0, 1, 0, VIEW_MAILBOX);
+       CtdlCreateRoom(USERTRASHROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
+       CtdlCreateRoom(USERDRAFTROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
+
+       // Run any startup routines registered by loadable modules
+       PerformSessionHooks(EVT_LOGIN);
+
+       // Enter the lobby
+       CtdlUserGoto(CtdlGetConfigStr("c_baseroom"), 0, 0, NULL, NULL, NULL, NULL);
+}
+
+
+void logged_in_response(void) {
+       cprintf("%d %s|%d|%ld|%ld|%u|%ld|%ld\n",
+               CIT_OK, CC->user.fullname, CC->user.axlevel,
+               CC->user.timescalled, CC->user.posted,
+               CC->user.flags, CC->user.usernum,
+               CC->previous_login
+       );
+}
+
+
+void CtdlUserLogout(void) {
+
+       syslog(LOG_DEBUG, "user_ops: CtdlUserLogout() logging out <%s> from session %d", CC->curr_user, CC->cs_pid);
+
+       // Run any hooks registered by modules...
+       PerformSessionHooks(EVT_LOGOUT);
+       
+       // Clear out some session data.  Most likely, the CitContext for this
+       // session is about to get nuked when the session disconnects, but
+       // since it's possible to log in again without reconnecting, we cannot
+       // make that assumption.
+       CC->logged_in = 0;
+
+       // Check to see if the user was deleted while logged in and purge them if necessary
+       if ((CC->user.axlevel == AxDeleted) && (CC->user.usernum)) {
+               purge_user(CC->user.fullname);
+       }
+
+       // Clear out the user record in memory so we don't behave like a ghost
+       memset(&CC->user, 0, sizeof(struct ctdluser));
+       CC->curr_user[0] = 0;
+       CC->cs_inet_email[0] = 0;
+       CC->cs_inet_other_emails[0] = 0;
+       CC->cs_inet_fn[0] = 0;
+
+       // Free any output buffers
+       unbuffer_output();
+}
+
+
+// Validate a password on the host unix system by talking to the chkpwd daemon
+static int validpw(uid_t uid, const char *pass) {
+       char buf[256];
+       int rv = 0;
+
+       if (IsEmptyStr(pass)) {
+               syslog(LOG_DEBUG, "user_ops: refusing to chkpwd for uid=%d with empty password", uid);
+               return 0;
+       }
+
+       syslog(LOG_DEBUG, "user_ops: validating password for uid=%d using chkpwd...", uid);
+
+       begin_critical_section(S_CHKPWD);
+       rv = write(chkpwd_write_pipe[1], &uid, sizeof(uid_t));
+       if (rv == -1) {
+               syslog(LOG_ERR, "user_ops: communication with chkpwd broken: %m");
+               end_critical_section(S_CHKPWD);
+               return 0;
+       }
+       rv = write(chkpwd_write_pipe[1], pass, 256);
+       if (rv == -1) {
+               syslog(LOG_ERR, "user_ops: communication with chkpwd broken: %m");
+               end_critical_section(S_CHKPWD);
+               return 0;
+       }
+       rv = read(chkpwd_read_pipe[0], buf, 4);
+       if (rv == -1) {
+               syslog(LOG_ERR, "user_ops: ommunication with chkpwd broken: %m");
+               end_critical_section(S_CHKPWD);
+               return 0;
+       }
+       end_critical_section(S_CHKPWD);
+
+       if (!strncmp(buf, "PASS", 4)) {
+               syslog(LOG_DEBUG, "user_ops: chkpwd pass");
+               return(1);
+       }
+
+       syslog(LOG_DEBUG, "user_ops: chkpwd fail");
+       return 0;
+}
+
+
+// Start up the chkpwd daemon so validpw() has something to talk to
+void start_chkpwd_daemon(void) {
+       pid_t chkpwd_pid;
+       struct stat filestats;
+       int i;
+
+       syslog(LOG_DEBUG, "user_ops: starting chkpwd daemon for host authentication mode");
+
+       if ((stat(file_chkpwd, &filestats)==-1) || (filestats.st_size==0)) {
+               syslog(LOG_ERR, "user_ops: %s: %m", file_chkpwd);
+               abort();
+       }
+       if (pipe(chkpwd_write_pipe) != 0) {
+               syslog(LOG_ERR, "user_ops: unable to create pipe for chkpwd daemon: %m");
+               abort();
+       }
+       if (pipe(chkpwd_read_pipe) != 0) {
+               syslog(LOG_ERR, "user_ops: unable to create pipe for chkpwd daemon: %m");
+               abort();
+       }
+
+       chkpwd_pid = fork();
+       if (chkpwd_pid < 0) {
+               syslog(LOG_ERR, "user_ops: unable to fork chkpwd daemon: %m");
+               abort();
+       }
+       if (chkpwd_pid == 0) {
+               dup2(chkpwd_write_pipe[0], 0);
+               dup2(chkpwd_read_pipe[1], 1);
+               for (i=2; i<256; ++i) close(i);
+               execl(file_chkpwd, file_chkpwd, NULL);
+               syslog(LOG_ERR, "user_ops: unable to exec chkpwd daemon: %m");
+               abort();
+               exit(errno);
+       }
+}
+
+
+int CtdlTryPassword(const char *password, long len) {
+       int code;
+
+       if ((CC->logged_in)) {
+               syslog(LOG_WARNING, "user_ops: CtdlTryPassword: already logged in");
+               return pass_already_logged_in;
+       }
+       if (!strcmp(CC->curr_user, NLI)) {
+               syslog(LOG_WARNING, "user_ops: CtdlTryPassword: no user selected");
+               return pass_no_user;
+       }
+       if (CtdlGetUser(&CC->user, CC->curr_user)) {
+               syslog(LOG_ERR, "user_ops: CtdlTryPassword: internal error");
+               return pass_internal_error;
+       }
+       if (password == NULL) {
+               syslog(LOG_INFO, "user_ops: CtdlTryPassword: NULL password string supplied");
+               return pass_wrong_password;
+       }
+
+       // host auth mode...
+       else if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
+               if (validpw(CC->user.uid, password)) {
+                       code = 0;
+
+                       // sooper-seekrit hack: populate the password field in the
+                       // citadel database with the password that the user typed,
+                       // if it's correct.  This allows most sites to convert from
+                       // host auth to native auth if they want to.  If you think
+                       // this is a security hazard, comment it out.
+
+                       CtdlGetUserLock(&CC->user, CC->curr_user);
+                       safestrncpy(CC->user.password, password, sizeof CC->user.password);
+                       CtdlPutUserLock(&CC->user);
+
+                       // (sooper-seekrit hack ends here)
+               }
+               else {
+                       code = (-1);
+               }
+       }
+
+       // LDAP auth mode...
+       else if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
+
+               if ((CC->ldap_dn) && (!CtdlTryPasswordLDAP(CC->ldap_dn, password))) {
+                       code = 0;
+               }
+               else {
+                       code = (-1);
+               }
+       }
+
+       // native auth mode...
+       else {
+               char *pw;
+
+               pw = (char*) malloc(len + 1);
+               memcpy(pw, password, len + 1);
+               strproc(pw);
+               strproc(CC->user.password);
+               code = strcasecmp(CC->user.password, pw);
+               if (code != 0) {
+                       strproc(pw);
+                       strproc(CC->user.password);
+                       code = strcasecmp(CC->user.password, pw);
+               }
+               free (pw);
+       }
+
+       if (!code) {
+               do_login();
+               return pass_ok;
+       }
+       else {
+               syslog(LOG_WARNING, "user_ops: bad password specified for <%s> Service <%s> Port <%ld> Remote <%s / %s>",
+                       CC->curr_user,
+                       CC->ServiceName,
+                       CC->tcp_port,
+                       CC->cs_host,
+                       CC->cs_addr
+               );
+               return pass_wrong_password;
+       }
+}
+
+
+// Delete a user record *and* all of its related resources.
+int purge_user(char pname[]) {
+       struct ctdluser usbuf;
+       char usernamekey[USERNAME_SIZE];
+
+       makeuserkey(usernamekey, pname);
+
+       // If the name is empty we can't find them in the DB any way so just return
+       if (IsEmptyStr(pname)) {
+               return(ERROR + NO_SUCH_USER);
+       }
+
+       if (CtdlGetUser(&usbuf, pname) != 0) {
+               syslog(LOG_ERR, "user_ops: cannot purge user <%s> - not found", pname);
+               return(ERROR + NO_SUCH_USER);
+       }
+
+       // Don't delete a user who is currently logged in.  Instead, just
+       // set the access level to 0, and let the account get swept up
+       // during the next purge.
+       if (CtdlIsUserLoggedInByNum(usbuf.usernum)) {
+               syslog(LOG_WARNING, "user_ops: <%s> is logged in; not deleting", pname);
+               usbuf.axlevel = AxDeleted;
+               CtdlPutUser(&usbuf);
+               return(1);
+       }
+
+       syslog(LOG_NOTICE, "user_ops: deleting <%s>", pname);
+
+       // Perform any purge functions registered by server extensions
+       PerformUserHooks(&usbuf, EVT_PURGEUSER);
+
+       // delete any existing user/room relationships
+       cdb_delete(CDB_VISIT, &usbuf.usernum, sizeof(long));
+
+       // delete the users-by-number index record
+       cdb_delete(CDB_USERSBYNUMBER, &usbuf.usernum, sizeof(long));
+
+       // delete the user entry
+       cdb_delete(CDB_USERS, usernamekey, strlen(usernamekey));
+
+       return(0);
+}
+
+
+// This is the back end processing that happens when we create a new user account.
+int internal_create_user(char *username, struct ctdluser *usbuf, uid_t uid) {
+       if (!CtdlGetUser(usbuf, username)) {
+               return(ERROR + ALREADY_EXISTS);
+       }
+
+       // Go ahead and initialize a new user record
+       memset(usbuf, 0, sizeof(struct ctdluser));
+       safestrncpy(usbuf->fullname, username, sizeof usbuf->fullname);
+       strcpy(usbuf->password, "");
+       usbuf->uid = uid;
+
+       // These are the default flags on new accounts
+       usbuf->flags = US_LASTOLD | US_DISAPPEAR | US_PAGINATOR | US_FLOORS;
+
+       usbuf->timescalled = 0;
+       usbuf->posted = 0;
+       usbuf->axlevel = CtdlGetConfigInt("c_initax");
+       usbuf->lastcall = time(NULL);
+
+       // fetch a new user number
+       usbuf->usernum = get_new_user_number();
+
+       // add user to the database
+       CtdlPutUser(usbuf);
+       cdb_store(CDB_USERSBYNUMBER, &usbuf->usernum, sizeof(long), usbuf->fullname, strlen(usbuf->fullname)+1);
+
+       // If non-native auth, index by uid
+       if ((usbuf->uid > 0) && (usbuf->uid != NATIVE_AUTH_UID)) {
+               StrBuf *claimed_id = NewStrBuf();
+               StrBufPrintf(claimed_id, "uid:%d", usbuf->uid);
+               attach_extauth(usbuf, claimed_id);
+               FreeStrBuf(&claimed_id);
+       }
+
+       return(0);
+}
+
+
+// create_user()  -  back end processing to create a new user
+//
+// Set 'newusername' to the desired account name.
+// Set 'become_user' to CREATE_USER_BECOME_USER if this is self-service account creation and we want to
+//                   actually log in as the user we just created, otherwise set it to CREATE_USER_DO_NOT_BECOME_USER
+// Set 'uid' to some uid_t value to associate the account with an external auth user, or (-1) for native auth
+int create_user(char *username, int become_user, uid_t uid) {
+       struct ctdluser usbuf;
+       struct ctdlroom qrbuf;
+       char mailboxname[ROOMNAMELEN];
+       char buf[SIZ];
+       int retval;
+
+       strproc(username);
+       if ((retval = internal_create_user(username, &usbuf, uid)) != 0) {
+               return retval;
+       }
+
+       // Give the user a private mailbox and a configuration room.
+       // Make the latter an invisible system room.
+       CtdlMailboxName(mailboxname, sizeof mailboxname, &usbuf, MAILROOM);
+       CtdlCreateRoom(mailboxname, 5, "", 0, 1, 1, VIEW_MAILBOX);
+
+       CtdlMailboxName(mailboxname, sizeof mailboxname, &usbuf, USERCONFIGROOM);
+       CtdlCreateRoom(mailboxname, 5, "", 0, 1, 1, VIEW_BBS);
+       if (CtdlGetRoomLock(&qrbuf, mailboxname) == 0) {
+               qrbuf.QRflags2 |= QR2_SYSTEM;
+               CtdlPutRoomLock(&qrbuf);
+       }
+
+       // Perform any create functions registered by server extensions
+       PerformUserHooks(&usbuf, EVT_NEWUSER);
+
+       // Everything below this line can be bypassed if administratively
+       // creating a user, instead of doing self-service account creation
+
+       if (become_user == CREATE_USER_BECOME_USER) {
+               // Now become the user we just created
+               memcpy(&CC->user, &usbuf, sizeof(struct ctdluser));
+               safestrncpy(CC->curr_user, username, sizeof CC->curr_user);
+               do_login();
+       
+               // Check to make sure we're still who we think we are
+               if (CtdlGetUser(&CC->user, CC->curr_user)) {
+                       return(ERROR + INTERNAL_ERROR);
+               }
+       }
+       
+       snprintf(buf, SIZ, 
+               "New user account <%s> has been created, from host %s [%s].\n",
+               username,
+               CC->cs_host,
+               CC->cs_addr
+       );
+       CtdlAideMessage(buf, "User Creation Notice");
+       syslog(LOG_NOTICE, "user_ops: <%s> created", username);
+       return(0);
+}
+
+
+// set password - back end api code
+void CtdlSetPassword(char *new_pw) {
+       CtdlGetUserLock(&CC->user, CC->curr_user);
+       safestrncpy(CC->user.password, new_pw, sizeof(CC->user.password));
+       CtdlPutUserLock(&CC->user);
+       syslog(LOG_INFO, "user_ops: password changed for <%s>", CC->curr_user);
+       PerformSessionHooks(EVT_SETPASS);
+}
+
+
+// API function for cmd_invt_kick() and anything else that needs to
+// invite or kick out a user to/from a room.
+// 
+// Set iuser to the name of the user, and op to 1=invite or 0=kick
+int CtdlInvtKick(char *iuser, int op) {
+       struct ctdluser USscratch;
+       visit vbuf;
+       char bbb[SIZ];
+
+       if (CtdlGetUser(&USscratch, iuser) != 0) {
+               return(1);
+       }
+
+       CtdlGetRelationship(&vbuf, &USscratch, &CC->room);
+       if (op == 1) {
+               vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
+               vbuf.v_flags = vbuf.v_flags | V_ACCESS;
+       }
+       if (op == 0) {
+               vbuf.v_flags = vbuf.v_flags & ~V_ACCESS;
+               vbuf.v_flags = vbuf.v_flags | V_FORGET | V_LOCKOUT;
+       }
+       CtdlSetRelationship(&vbuf, &USscratch, &CC->room);
+
+       // post a message in Aide> saying what we just did
+       snprintf(bbb, sizeof bbb, "%s has been %s \"%s\" by %s.\n",
+               iuser,
+               ((op == 1) ? "invited to" : "kicked out of"),
+               CC->room.QRname,
+               (CC->logged_in ? CC->user.fullname : "an administrator")
+       );
+       CtdlAideMessage(bbb,"User Admin Message");
+       return(0);
+}
+
+
+// Forget (Zap) the current room (API call)
+// Returns 0 on success
+int CtdlForgetThisRoom(void) {
+       visit vbuf;
+
+       // On some systems, Admins are not allowed to forget rooms
+       if (is_aide() && (CtdlGetConfigInt("c_aide_zap") == 0)
+          && ((CC->room.QRflags & QR_MAILBOX) == 0)  ) {
+               return(1);
+       }
+
+       CtdlGetUserLock(&CC->user, CC->curr_user);
+       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+
+       vbuf.v_flags = vbuf.v_flags | V_FORGET;
+       vbuf.v_flags = vbuf.v_flags & ~V_ACCESS;
+
+       CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
+       CtdlPutUserLock(&CC->user);
+
+       // Return to the Lobby, so we don't end up in an undefined room
+       CtdlUserGoto(CtdlGetConfigStr("c_baseroom"), 0, 0, NULL, NULL, NULL, NULL);
+       return(0);
+}
+
+
+// Traverse the user file and perform a callback for each user record.
+// (New improved version that runs in two phases so that callbacks can perform writes without having a r/o cursor open)
+void ForEachUser(void (*CallBack) (char *, void *out_data), void *in_data) {
+       struct cdbdata *cdbus;
+       struct ctdluser *usptr;
+
+       struct feu {
+               struct feu *next;
+               char username[USERNAME_SIZE];
+       };
+       struct feu *ufirst = NULL;
+       struct feu *ulast = NULL;
+       struct feu *f = NULL;
+
+       cdb_rewind(CDB_USERS);
+
+       // Phase 1 : build a linked list of all our user account names
+       while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
+               usptr = (struct ctdluser *) cdbus->ptr;
+
+               if (strlen(usptr->fullname) > 0) {
+                       f = malloc(sizeof(struct feu));
+                       f->next = NULL;
+                       strncpy(f->username, usptr->fullname, USERNAME_SIZE);
+
+                       if (ufirst == NULL) {
+                               ufirst = f;
+                               ulast = f;
+                       }
+                       else {
+                               ulast->next = f;
+                               ulast = f;
+                       }
+               }
+       }
+
+       // Phase 2 : perform the callback for each user while de-allocating the list
+       while (ufirst != NULL) {
+               (*CallBack) (ufirst->username, in_data);
+               f = ufirst;
+               ufirst = ufirst->next;
+               free(f);
+       }
+}
+
+
+// Count the number of new mail messages the user has
+int NewMailCount() {
+       int num_newmsgs = 0;
+       num_newmsgs = CC->newmail;
+       CC->newmail = 0;
+       return(num_newmsgs);
+}
+
+
+// Count the number of new mail messages the user has
+int InitialMailCheck() {
+       int num_newmsgs = 0;
+       int a;
+       char mailboxname[ROOMNAMELEN];
+       struct ctdlroom mailbox;
+       visit vbuf;
+       struct cdbdata *cdbfr;
+       long *msglist = NULL;
+       int num_msgs = 0;
+
+       CtdlMailboxName(mailboxname, sizeof mailboxname, &CC->user, MAILROOM);
+       if (CtdlGetRoom(&mailbox, mailboxname) != 0)
+               return(0);
+       CtdlGetRelationship(&vbuf, &CC->user, &mailbox);
+
+       cdbfr = cdb_fetch(CDB_MSGLISTS, &mailbox.QRnumber, sizeof(long));
+
+       if (cdbfr != NULL) {
+               msglist = malloc(cdbfr->len);
+               memcpy(msglist, cdbfr->ptr, cdbfr->len);
+               num_msgs = cdbfr->len / sizeof(long);
+               cdb_free(cdbfr);
+       }
+       if (num_msgs > 0)
+               for (a = 0; a < num_msgs; ++a) {
+                       if (msglist[a] > 0L) {
+                               if (msglist[a] > vbuf.v_lastseen) {
+                                       ++num_newmsgs;
+                               }
+                       }
+               }
+       if (msglist != NULL)
+               free(msglist);
+
+       return(num_newmsgs);
+}
diff --git a/citadel/server/user_ops.h b/citadel/server/user_ops.h
new file mode 100644 (file)
index 0000000..1a1ba66
--- /dev/null
@@ -0,0 +1,65 @@
+// Header file for server functions which perform operations on user objects.
+//
+// Copyright (c) 1987-2022 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#ifndef __USER_OPS_H__
+#define __USER_OPS_H__
+
+#include <ctype.h>
+#include <syslog.h>
+
+int hash (char *str);
+int is_aide (void);
+int is_room_aide (void);
+int CtdlCheckInternetMailPermission(struct ctdluser *who);
+void rebuild_usersbynumber(void);
+void session_startup (void);
+void logged_in_response(void);
+int purge_user (char *pname);
+int getuserbyuid(struct ctdluser *usbuf, uid_t number);
+
+int create_user(char *newusername, int become_user, uid_t uid);
+enum {
+       CREATE_USER_DO_NOT_BECOME_USER,
+       CREATE_USER_BECOME_USER
+};
+#define NATIVE_AUTH_UID (-1)
+
+void do_login(void);
+int CtdlInvtKick(char *iuser, int op);
+void ForEachUser(void (*CallBack) (char *, void *out_data), void *in_data);
+int NewMailCount(void);
+int InitialMailCheck(void);
+void put_visit(visit *newvisit);
+int GenerateRelationshipIndex(char *IndexBuf, long RoomID, long RoomGen, long UserID);
+int CtdlAssociateSystemUser(char *screenname, char *loginname);
+
+void CtdlSetPassword(char *new_pw);
+
+int CtdlForgetThisRoom(void);
+
+void cmd_newu (char *cmdbuf);
+void start_chkpwd_daemon(void);
+
+
+#define RENAMEUSER_OK                  0       // Operation succeeded
+#define RENAMEUSER_LOGGED_IN           1       // Cannot rename a user who is currently logged in
+#define RENAMEUSER_NOT_FOUND           2       // The old user name does not exist
+#define RENAMEUSER_ALREADY_EXISTS      3       // An account with the desired new name already exists
+
+int rename_user(char *oldname, char *newname);
+void reindex_user_928(char *username, void *out_data);
+
+void makeuserkey(char *key, const char *username);
+int CtdlUserCmp(char *s1, char *s2);
+int internal_create_user(char *username, struct ctdluser *usbuf, uid_t uid);
+
+#endif
diff --git a/citadel/server_main.c b/citadel/server_main.c
deleted file mode 100644 (file)
index 601b458..0000000
+++ /dev/null
@@ -1,323 +0,0 @@
-// citserver's main() function lives here.
-// 
-// Copyright (c) 1987-2022 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <grp.h>
-#include <sys/file.h>
-#include <libcitadel.h>
-#include "citserver.h"
-#include "svn_revision.h"
-#include "modules_init.h"
-#include "config.h"
-#include "control.h"
-#include "serv_extensions.h"
-#include "citadel_dirs.h"
-#include "user_ops.h"
-
-uid_t ctdluid = 0;
-const char *CitadelServiceUDS="citadel-UDS";
-const char *CitadelServiceTCP="citadel-TCP";
-int sanity_diag_mode = 0;
-
-
-// Create or remove a lock file, so we only have one Citadel Server running at a time.
-// Set 'op' to nonzero to lock, zero to unlock.
-void ctdl_lockfile(int op) {
-       static char lockfilename[PATH_MAX];
-       static FILE *fp;
-
-       if (op) {
-               syslog(LOG_DEBUG, "main: creating lockfile");
-               snprintf(lockfilename, sizeof lockfilename, "%s/citadel.lock", ctdl_run_dir);
-               fp = fopen(lockfilename, "w");
-               if (!fp) {
-                       syslog(LOG_ERR, "%s: %m", lockfilename);
-                       exit(CTDLEXIT_DB);
-               }
-               if (flock(fileno(fp), (LOCK_EX|LOCK_NB)) != 0) {
-                       syslog(LOG_ERR, "main: cannot lock %s , is another citserver running?", lockfilename);
-                       exit(CTDLEXIT_DB);
-               }
-               return;
-       }
-
-       syslog(LOG_DEBUG, "main: removing lockfile");
-       unlink(lockfilename);
-       flock(fileno(fp), LOCK_UN);
-       fclose(fp);
-}
-
-
-// Here's where it all begins.
-int main(int argc, char **argv) {
-
-       char facility[32];
-       int a;                  // General-purpose variables
-       struct passwd pw, *pwp = NULL;
-       char pwbuf[SIZ];
-       int drop_root_perms = 1;
-       int max_log_level = LOG_INFO;
-       char *ctdldir = CTDLDIR;
-       int syslog_facility = LOG_DAEMON;
-       uid_t u = 0;
-       struct passwd *p = NULL;
-#ifdef HAVE_RUN_DIR
-       struct stat filestats;
-#endif
-
-       // Tell 'em who's in da house
-       syslog(LOG_INFO, " ");
-       syslog(LOG_INFO, " ");
-       syslog(LOG_INFO, "*** Citadel server engine ***\n");
-       syslog(LOG_INFO, "Version %d (build %s) ***", REV_LEVEL, svn_revision());
-       syslog(LOG_INFO, "Copyright (C) 1987-2022 by the Citadel development team.");
-       syslog(LOG_INFO, " ");
-       syslog(LOG_INFO, "This program is open source software: you can redistribute it and/or");
-       syslog(LOG_INFO, "modify it under the terms of the GNU General Public License, version 3.");
-       syslog(LOG_INFO, " ");
-       syslog(LOG_INFO, "This program is distributed in the hope that it will be useful,");
-       syslog(LOG_INFO, "but WITHOUT ANY WARRANTY; without even the implied warranty of");
-       syslog(LOG_INFO, "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the");
-       syslog(LOG_INFO, "GNU General Public License for more details.");
-       syslog(LOG_INFO, " ");
-       syslog(LOG_INFO, "%s", libcitadel_version_string());
-
-       // parse command-line arguments
-       while ((a=getopt(argc, argv, "cl:dh:x:t:B:Dru:s:")) != EOF) switch(a) {
-
-               // test this binary for compatibility and exit
-               case 'c':
-                       fprintf(stderr, "%s: binary compatibility confirmed\n", argv[0]);
-                       exit(0);
-                       break;
-
-               // identify the desired syslog facility
-               case 'l':
-                       safestrncpy(facility, optarg, sizeof(facility));
-                       syslog_facility = SyslogFacility(facility);
-                       break;
-
-               // run in the background if -d was specified
-               case 'd':
-                       running_as_daemon = 1;
-                       break;
-
-               // specify the data directory
-               case 'h':
-                       ctdldir = optarg;
-                       break;
-
-               // identify the desired logging severity level
-               case 'x':
-                       max_log_level = atoi(optarg);
-                       break;
-
-               // deprecated
-               case 't':
-                       break;
-
-               // deprecated
-                case 'B':
-                        break;
-
-               // deprecated
-               case 'D':
-                       break;
-
-               // -r tells the server not to drop root permissions.
-               // Don't use this unless you know what you're doing.
-               case 'r':
-                       drop_root_perms = 0;
-                       break;
-
-               // -u tells the server what uid to run under...
-               case 'u':
-                       u = atoi(optarg);
-                       if (u > 0) {
-                               ctdluid = u;
-                       }
-                       else {
-                               p = getpwnam(optarg);
-                               if (p) {
-                                       u = p->pw_uid;
-                               }
-                       }
-                       if (u > 0) {
-                               ctdluid = u;
-                       }
-                       break;
-
-               // -s tells the server to behave differently during sanity checks
-               case 's':
-                       sanity_diag_mode = atoi(optarg);
-                       break;
-
-               // any other parameter makes it crash and burn
-               default:
-                       fprintf(stderr, "citserver: usage: "
-                                       "citserver "
-                                       "[-l LogFacility] "
-                                       "[-x MaxLogLevel] "
-                                       "[-d] [-r] "
-                                       "[-u user] "
-                                       "[-h HomeDir]\n"
-                       );
-                       exit(1);
-       }
-
-       if (chdir(ctdldir) != 0) {
-               syslog(LOG_ERR, "main: unable to change directory to [%s]: %m", ctdldir);
-               exit(CTDLEXIT_HOME);
-       }
-       else {
-               syslog(LOG_INFO, "main: running in data directory %s", ctdldir);
-       }
-
-       if ((ctdluid == 0) && (drop_root_perms == 0)) {
-               fprintf(stderr, "citserver: cannot determine user to run as; please specify -r or -u options\n");
-               exit(CTDLEXIT_UNUSER);
-       }
-
-       // Last ditch effort to determine the user name ... if there's a user called "citadel" then use that
-       if (ctdluid == 0) {
-               p = getpwnam("citadel");
-               if (!p) {
-                       p = getpwnam("bbs");
-               }
-               if (!p) {
-                       p = getpwnam("guest");
-               }
-               if (p) {
-                       u = p->pw_uid;
-               }
-               if (u > 0) {
-                       ctdluid = u;
-               }
-       }
-
-       // initialize the master context
-       InitializeMasterCC();
-       InitializeMasterTSD();
-
-       setlogmask(LOG_UPTO(max_log_level));
-       openlog("citserver",
-               ( running_as_daemon ? (LOG_PID) : (LOG_PID | LOG_PERROR) ),
-               syslog_facility
-       );
-
-       // daemonize, if we were asked to
-       if (running_as_daemon) {
-               start_daemon(0);
-               drop_root_perms = 1;
-       }
-
-       if ((mkdir(ctdl_run_dir, 0755) != 0) && (errno != EEXIST)) {
-               syslog(LOG_ERR, "main: unable to create run directory [%s]: %m", ctdl_run_dir);
-       }
-
-       if (chown(ctdl_run_dir, ctdluid, (pwp==NULL)?-1:pw.pw_gid) != 0) {
-               syslog(LOG_ERR, "main: unable to set the access rights for [%s]: %m", ctdl_run_dir);
-       }
-
-       ctdl_lockfile(1);
-       init_sysdep();                                          // Initialize...
-       master_startup();                                       // Do non system dependent startup functions
-       check_control();                                        // Check/sanitize/initialize control record, fix user indexes
-       syslog(LOG_INFO, "main: upgrading modules");            // Run any upgrade entry points
-       upgrade_modules();
-
-       // Load the user for the masterCC or create them if they don't exist
-       if (CtdlGetUser(&masterCC.user, "SYS_Citadel")) {
-               // User doesn't exist. We can't use create user here as the user number needs to be 0
-               strcpy (masterCC.user.fullname, "SYS_Citadel") ;
-               CtdlPutUser(&masterCC.user);
-               CtdlGetUser(&masterCC.user, "SYS_Citadel");     // Just to be safe
-       }
-       
-       // Bind the server to a Unix-domain socket (user client access)
-       CtdlRegisterServiceHook(0,
-                               file_citadel_socket,
-                               citproto_begin_session,
-                               do_command_loop,
-                               do_async_loop,
-                               CitadelServiceUDS);
-
-       // Bind the server to a Unix-domain socket (admin client access)
-       CtdlRegisterServiceHook(0,
-                               file_citadel_admin_socket,
-                               citproto_begin_admin_session,
-                               do_command_loop,
-                               do_async_loop,
-                               CitadelServiceUDS);
-       chmod(file_citadel_admin_socket, S_IRWXU);      // for your eyes only
-
-       // Bind the server to our favorite TCP port (usually 504).
-       CtdlRegisterServiceHook(CtdlGetConfigInt("c_port_number"),
-                               NULL,
-                               citproto_begin_session,
-                               do_command_loop,
-                               do_async_loop,
-                               CitadelServiceTCP);
-
-       // Load any server-side extensions available here.
-       syslog(LOG_INFO, "main: initializing server extensions");
-       initialise_modules(0);
-
-       // If we need host auth, start our chkpwd daemon.
-       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
-               start_chkpwd_daemon();
-       }
-
-       // check, whether we're fired up another time after a crash.
-       // if, post an aide message, so the admin has a chance to react.
-       checkcrash();
-
-       // Now that we've bound the sockets, change to the Citadel user id and its corresponding group ids
-       if (drop_root_perms) {
-               cdb_chmod_data();       // make sure we own our data files
-               getpwuid_r(ctdluid, &pw, pwbuf, sizeof(pwbuf), &pwp);
-               if (pwp == NULL)
-                       syslog(LOG_ERR, "main: WARNING, getpwuid(%ld): %m Group IDs will be incorrect.", (long)CTDLUID);
-               else {
-                       initgroups(pw.pw_name, pw.pw_gid);
-                       if (setgid(pw.pw_gid)) {
-                               syslog(LOG_ERR, "main: setgid(%ld): %m", (long)pw.pw_gid);
-                       }
-               }
-               syslog(LOG_INFO, "main: changing uid to %ld", (long)CTDLUID);
-               if (setuid(CTDLUID) != 0) {
-                       syslog(LOG_ERR, "main: setuid() failed: %m");
-               }
-#if defined (HAVE_SYS_PRCTL_H) && defined (PR_SET_DUMPABLE)
-               prctl(PR_SET_DUMPABLE, 1);
-#endif
-       }
-
-       // We want to check for idle sessions once per minute
-       CtdlRegisterSessionHook(terminate_idle_sessions, EVT_TIMER, PRIO_CLEANUP + 1);
-
-       // Go into multithreaded mode.  When this call exits, the server is stopping.
-       go_threading();
-       
-       // Get ready to shut down the server.
-       int exit_code = master_cleanup(exit_signal);
-       ctdl_lockfile(0);
-       if (restart_server) {
-               syslog(LOG_INFO, "main: *** CITADEL SERVER IS RESTARTING ***");
-               execv(argv[0], argv);
-       }
-       return(exit_code);
-}
diff --git a/citadel/support.c b/citadel/support.c
deleted file mode 100644 (file)
index 20937f2..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-// Server-side utility functions
-
-#include "sysdep.h"
-#include <stdio.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <libcitadel.h>
-
-#include "citadel.h"
-#include "support.h"
-
-// strproc()  -  make a string 'nice'
-void strproc(char *string) {
-       int a, b;
-
-       if (string == NULL) return;
-       if (IsEmptyStr(string)) return;
-
-       // Convert non-printable characters to blanks
-       for (a=0; !IsEmptyStr(&string[a]); ++a) {
-               if (string[a]<32) string[a]=32;
-               if (string[a]>126) string[a]=32;
-       }
-
-       // a is now the length of our string.
-       // Remove leading and trailing blanks
-       while( (string[a-1]<33) && (!IsEmptyStr(string)) )
-               string[--a]=0;
-       b = 0;
-       while( (string[b]<33) && (!IsEmptyStr(&string[b])) )
-               b++;
-       if (b > 0)
-               memmove(string,&string[b], a - b + 1);
-
-       // Remove double blanks
-       for (a=0; !IsEmptyStr(&string[a]); ++a) {
-               if ((string[a]==32)&&(string[a+1]==32)) {
-                       strcpy(&string[a],&string[a+1]);
-                       a=0;
-               }
-       }
-
-       // remove characters which would interfere with the network
-       for (a=0; !IsEmptyStr(&string[a]); ++a) {
-               while (string[a]=='!') strcpy(&string[a],&string[a+1]);
-               while (string[a]=='@') strcpy(&string[a],&string[a+1]);
-               while (string[a]=='_') strcpy(&string[a],&string[a+1]);
-               while (string[a]==',') strcpy(&string[a],&string[a+1]);
-               while (string[a]=='%') strcpy(&string[a],&string[a+1]);
-               while (string[a]=='|') strcpy(&string[a],&string[a+1]);
-       }
-
-}
-
-
-// get a line of text from a file
-// ignores lines starting with #
-int getstring(FILE *fp, char *string) {
-       int a,c;
-       do {
-               strcpy(string,"");
-               a=0;
-               do {
-                       c=getc(fp);
-                       if (c<0) {
-                               string[a]=0;
-                               return(-1);
-                               }
-                       string[a++]=c;
-                       } while(c!=10);
-                       string[a-1]=0;
-               } while(string[0]=='#');
-       return(strlen(string));
-}
diff --git a/citadel/support.h b/citadel/support.h
deleted file mode 100644 (file)
index fc7c2a6..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-#include <stdio.h>
-
-void strproc (char *string);
-int getstring (FILE *fp, char *string);
diff --git a/citadel/svn_revision.h b/citadel/svn_revision.h
deleted file mode 100644 (file)
index 8c3428e..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-const char *svn_revision(void);
-
-
diff --git a/citadel/sysconfig.h b/citadel/sysconfig.h
deleted file mode 100644 (file)
index 99947bc..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-// Tuning of various parameters of the system.
-// Normally you don't want to mess with any of this.
-//
-// Copyright (c) 1987-2021 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-/*
- * NLI is the string that shows up in a <W>ho's online listing for sessions
- * that are active, but for which no user has yet authenticated.
- */
-#define NLI    "(not logged in)"
-
-/*
- * Maximum number of floors on the system.
- * WARNING!  *Never* change this value once your system is up; THINGS WILL DIE!
- * Also, do not set it higher than 127.
- */
-#define MAXFLOORS      16
-
-/*
- * Standard buffer size for string datatypes.  DO NOT CHANGE!  Not only does
- * there exist a minimum buffer size for certain protocols (such as IMAP), but
- * fixed-length buffers are now stored in some of the data structures on disk,
- * so if you change the buffer size you'll fux0r your database.
- */
-#define SIZ            4096
-
-/*
- * If the body of a message is beyond this size, it will be stored in
- * a separate table.
- */
-#define BIGMSG         1024
-
-/*
- * SMTP delivery timeouts (measured in seconds)
- * If outbound SMTP deliveries cannot be completed due to transient errors
- * within SMTP_DELIVER_WARN seconds, the sender will receive a warning message
- * indicating that the message has not yet been delivered but Citadel will
- * keep trying.  After SMTP_DELIVER_FAIL seconds, Citadel will advise the
- * sender that the deliveries have failed.
- */
-#define SMTP_DELIVER_WARN      14400           // warn after four hours
-#define SMTP_DELIVER_FAIL      432000          // fail after five days
-
-/*
- * Who bounced messages appear to be from
- */
-#define BOUNCESOURCE           "Citadel Mail Delivery Subsystem"
-
-/*
- * The names of rooms which are automatically created by the system
- */
-#define BASEROOM               "Lobby"
-#define MAILROOM               "Mail"
-#define SENTITEMS              "Sent Items"
-#define AIDEROOM               "Aide"
-#define USERCONFIGROOM         "My Citadel Config"
-#define USERCALENDARROOM       "Calendar"
-#define USERTASKSROOM          "Tasks"
-#define USERCONTACTSROOM       "Contacts"
-#define USERNOTESROOM          "Notes"
-#define USERDRAFTROOM          "Drafts"
-#define USERTRASHROOM          "Trash"
-#define PAGELOGROOM            "Sent/Received Pages"
-#define SYSCONFIGROOM          "Local System Configuration"
-#define SMTP_SPOOLOUT_ROOM     "__CitadelSMTPspoolout__"
-#define ADDRESS_BOOK_ROOM      "Global Address Book"
-
-/*
- * How long (in seconds) to retain message entries in the use table
- */
-#define USETABLE_RETAIN                864000L         /* 10 days */
-
-/*
- * The size of per-thread stacks.  If set too low, citserver will randomly crash.
- */
-#define THREADSTACKSIZE                0x100000
-
-/*
- * How many messages may the full text indexer scan before flushing its
- * tables to disk?
- */
-#define FT_MAX_CACHE           25
diff --git a/citadel/sysdep.c b/citadel/sysdep.c
deleted file mode 100644 (file)
index 280aa91..0000000
+++ /dev/null
@@ -1,1116 +0,0 @@
-// Citadel "system dependent" stuff.
-//
-// Here's where we (hopefully) have most parts of the Citadel server that
-// might need tweaking when run on different operating system variants.
-//
-// Copyright (c) 1987-2021 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <signal.h>
-#include <stdio.h>
-#include <syslog.h>
-#include <sys/syslog.h>
-#include <execinfo.h>
-#include <netdb.h>
-#include <sys/un.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <netinet/tcp.h>
-#include <arpa/inet.h>
-#define SHOW_ME_VAPPEND_PRINTF
-#include <libcitadel.h>
-#include "citserver.h"
-#include "config.h"
-#include "ctdl_module.h"
-#include "sysdep_decls.h"
-#include "modules/crypto/serv_crypto.h"        // Needed for init_ssl, client_write_ssl, client_read_ssl
-#include "housekeeping.h"
-#include "context.h"
-
-// Signal handler to shut down the server.
-volatile int exit_signal = 0;
-volatile int shutdown_and_halt = 0;
-volatile int restart_server = 0;
-volatile int running_as_daemon = 0;
-
-
-static RETSIGTYPE signal_cleanup(int signum) {
-       syslog(LOG_DEBUG, "sysdep: caught signal %d", signum);
-       exit_signal = signum;
-       server_shutting_down = 1;
-}
-
-
-// Some initialization stuff...
-void init_sysdep(void) {
-       sigset_t set;
-
-       // Avoid vulnerabilities related to FD_SETSIZE if we can.
-#ifdef FD_SETSIZE
-#ifdef RLIMIT_NOFILE
-       struct rlimit rl;
-       getrlimit(RLIMIT_NOFILE, &rl);
-       rl.rlim_cur = FD_SETSIZE;
-       rl.rlim_max = FD_SETSIZE;
-       setrlimit(RLIMIT_NOFILE, &rl);
-#endif
-#endif
-
-       // If we've got OpenSSL, we're going to use it.
-#ifdef HAVE_OPENSSL
-       init_ssl();
-#endif
-
-       if (pthread_key_create(&ThreadKey, NULL) != 0) {                        // TSD for threads
-               syslog(LOG_ERR, "pthread_key_create() : %m");
-               abort();
-       }
-       
-       if (pthread_key_create(&MyConKey, NULL) != 0) {                         // TSD for sessions
-               syslog(LOG_CRIT, "sysdep: can't create TSD key: %m");
-               abort();
-       }
-
-       // Interript, hangup, and terminate signals should cause the server to shut down.
-       sigemptyset(&set);
-       sigaddset(&set, SIGINT);
-       sigaddset(&set, SIGHUP);
-       sigaddset(&set, SIGTERM);
-       sigprocmask(SIG_UNBLOCK, &set, NULL);
-
-       signal(SIGINT, signal_cleanup);
-       signal(SIGHUP, signal_cleanup);
-       signal(SIGTERM, signal_cleanup);
-
-       // Do not shut down the server on broken pipe signals, otherwise the
-       // whole Citadel service would come down whenever a single client
-       // socket breaks.
-       signal(SIGPIPE, SIG_IGN);
-}
-
-
-// This is a generic function to set up a master socket for listening on
-// a TCP port.  The server shuts down if the bind fails.  (IPv4/IPv6 version)
-//
-// ip_addr     IP address to bind
-// port_number port number to bind
-// queue_len   number of incoming connections to allow in the queue
-int ctdl_tcp_server(char *ip_addr, int port_number, int queue_len) {
-       struct protoent *p;
-       struct sockaddr_in6 sin6;
-       struct sockaddr_in sin4;
-       int s, i, b;
-       int ip_version = 6;
-
-       memset(&sin6, 0, sizeof(sin6));
-       memset(&sin4, 0, sizeof(sin4));
-       sin6.sin6_family = AF_INET6;
-       sin4.sin_family = AF_INET;
-
-       if (    (ip_addr == NULL)                                                       // any IPv6
-               || (IsEmptyStr(ip_addr))
-               || (!strcmp(ip_addr, "*"))
-       ) {
-               ip_version = 6;
-               sin6.sin6_addr = in6addr_any;
-       }
-       else if (!strcmp(ip_addr, "0.0.0.0"))                                           // any IPv4
-       {
-               ip_version = 4;
-               sin4.sin_addr.s_addr = INADDR_ANY;
-       }
-       else if ((strchr(ip_addr, '.')) && (!strchr(ip_addr, ':')))                     // specific IPv4
-       {
-               ip_version = 4;
-               if (inet_pton(AF_INET, ip_addr, &sin4.sin_addr) <= 0) {
-                       syslog(LOG_ALERT, "tcpserver: inet_pton: %m");
-                       return (-1);
-               }
-       }
-       else                                                                            // specific IPv6
-       {
-               ip_version = 6;
-               if (inet_pton(AF_INET6, ip_addr, &sin6.sin6_addr) <= 0) {
-                       syslog(LOG_ALERT, "tcpserver: inet_pton: %m");
-                       return (-1);
-               }
-       }
-
-       if (port_number == 0) {
-               syslog(LOG_ALERT, "tcpserver: no port number was specified");
-               return (-1);
-       }
-       sin6.sin6_port = htons((u_short) port_number);
-       sin4.sin_port = htons((u_short) port_number);
-
-       p = getprotobyname("tcp");
-       if (p == NULL) {
-               syslog(LOG_ALERT, "tcpserver: getprotobyname: %m");
-               return (-1);
-       }
-
-       s = socket( ((ip_version == 6) ? PF_INET6 : PF_INET), SOCK_STREAM, (p->p_proto));
-       if (s < 0) {
-               syslog(LOG_ALERT, "tcpserver: socket: %m");
-               return (-1);
-       }
-       // Set some socket options that make sense.
-       i = 1;
-       setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
-
-       if (ip_version == 6) {
-               b = bind(s, (struct sockaddr *) &sin6, sizeof(sin6));
-       }
-       else {
-               b = bind(s, (struct sockaddr *) &sin4, sizeof(sin4));
-       }
-
-       if (b < 0) {
-               syslog(LOG_ALERT, "tcpserver: bind: %m");
-               return (-1);
-       }
-
-       fcntl(s, F_SETFL, O_NONBLOCK);
-
-       if (listen(s, ((queue_len >= 5) ? queue_len : 5) ) < 0) {
-               syslog(LOG_ALERT, "tcpserver: listen: %m");
-               return (-1);
-       }
-       return (s);
-}
-
-
-// Create a Unix domain socket and listen on it
-int ctdl_uds_server(char *sockpath, int queue_len) {
-       struct sockaddr_un addr;
-       int s;
-       int i;
-       int actual_queue_len;
-#ifdef HAVE_STRUCT_UCRED
-       int passcred = 1;
-#endif
-
-       actual_queue_len = queue_len;
-       if (actual_queue_len < 5) actual_queue_len = 5;
-
-       i = unlink(sockpath);
-       if ((i != 0) && (errno != ENOENT)) {
-               syslog(LOG_ERR, "udsserver: %m");
-               return(-1);
-       }
-
-       memset(&addr, 0, sizeof(addr));
-       addr.sun_family = AF_UNIX;
-       safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
-
-       s = socket(AF_UNIX, SOCK_STREAM, 0);
-       if (s < 0) {
-               syslog(LOG_ERR, "udsserver: socket: %m");
-               return(-1);
-       }
-
-       if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
-               syslog(LOG_ERR, "udsserver: bind: %m");
-               return(-1);
-       }
-
-       // set to nonblock - we need this for some obscure situations
-       if (fcntl(s, F_SETFL, O_NONBLOCK) < 0) {
-               syslog(LOG_ERR, "udsserver: fcntl: %m");
-               close(s);
-               return(-1);
-       }
-
-       if (listen(s, actual_queue_len) < 0) {
-               syslog(LOG_ERR, "udsserver: listen: %m");
-               return(-1);
-       }
-
-#ifdef HAVE_STRUCT_UCRED
-       setsockopt(s, SOL_SOCKET, SO_PASSCRED, &passcred, sizeof(passcred));
-#endif
-
-       chmod(sockpath, S_ISGID|S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH);
-       return(s);
-}
-
-
-// The following functions implement output buffering on operating systems which
-// support it (such as Linux and various BSD flavors).
-#ifndef HAVE_DARWIN
-#ifdef TCP_CORK
-#      define HAVE_TCP_BUFFERING
-#else
-#      ifdef TCP_NOPUSH
-#              define HAVE_TCP_BUFFERING
-#              define TCP_CORK TCP_NOPUSH
-#      endif
-#endif // TCP_CORK
-#endif // HAVE_DARWIN
-
-static unsigned on = 1, off = 0;
-
-void buffer_output(void) {
-#ifdef HAVE_TCP_BUFFERING
-#ifdef HAVE_OPENSSL
-       if (!CC->redirect_ssl)
-#endif
-               setsockopt(CC->client_socket, IPPROTO_TCP, TCP_CORK, &on, 4);
-#endif
-}
-
-void unbuffer_output(void) {
-#ifdef HAVE_TCP_BUFFERING
-#ifdef HAVE_OPENSSL
-       if (!CC->redirect_ssl)
-#endif
-               setsockopt(CC->client_socket, IPPROTO_TCP, TCP_CORK, &off, 4);
-#endif
-}
-
-void flush_output(void) {
-#ifdef HAVE_TCP_BUFFERING
-       struct CitContext *CCC = CC;
-       setsockopt(CCC->client_socket, IPPROTO_TCP, TCP_CORK, &off, 4);
-       setsockopt(CCC->client_socket, IPPROTO_TCP, TCP_CORK, &on, 4);
-#endif
-}
-
-
-// close the client socket
-void client_close(void) {
-       CitContext *CCC = CC;
-
-       if (!CCC) return;
-       if (CCC->client_socket <= 0) return;
-       syslog(LOG_DEBUG, "sysdep: closing socket %d", CCC->client_socket);
-       close(CCC->client_socket);
-       CCC->client_socket = -1 ;
-}
-
-
-// Send binary data to the client.
-int client_write(const char *buf, int nbytes)
-{
-       int bytes_written = 0;
-       int retval;
-#ifndef HAVE_TCP_BUFFERING
-       int old_buffer_len = 0;
-#endif
-       fd_set wset;
-       CitContext *Ctx;
-       int fdflags;
-
-       if (nbytes < 1) return(0);
-
-       Ctx = CC;
-
-       if (Ctx->redirect_buffer != NULL) {
-               StrBufAppendBufPlain(Ctx->redirect_buffer,
-                                    buf, nbytes, 0);
-               return 0;
-       }
-
-#ifdef HAVE_OPENSSL
-       if (Ctx->redirect_ssl) {
-               client_write_ssl(buf, nbytes);
-               return 0;
-       }
-#endif
-       if (Ctx->client_socket == -1) return -1;
-
-       fdflags = fcntl(Ctx->client_socket, F_GETFL);
-
-       while ((bytes_written < nbytes) && (Ctx->client_socket != -1)){
-               if ((fdflags & O_NONBLOCK) == O_NONBLOCK) {
-                       FD_ZERO(&wset);
-                       FD_SET(Ctx->client_socket, &wset);
-                       if (select(1, NULL, &wset, NULL, NULL) == -1) {
-                               if (errno == EINTR)
-                               {
-                                       syslog(LOG_DEBUG, "sysdep: client_write(%d bytes) select() interrupted.", nbytes-bytes_written);
-                                       if (server_shutting_down) {
-                                               CC->kill_me = KILLME_SELECT_INTERRUPTED;
-                                               return (-1);
-                                       } else {
-                                               // can't trust fd's and stuff so we need to re-create them
-                                               continue;
-                                       }
-                               } else {
-                                       syslog(LOG_ERR, "sysdep: client_write(%d bytes) select failed: %m", nbytes - bytes_written);
-                                       client_close();
-                                       Ctx->kill_me = KILLME_SELECT_FAILED;
-                                       return -1;
-                               }
-                       }
-               }
-
-               retval = write(Ctx->client_socket, &buf[bytes_written], nbytes - bytes_written);
-               if (retval < 1) {
-                       syslog(LOG_ERR, "sysdep: client_write(%d bytes) failed: %m", nbytes - bytes_written);
-                       client_close();
-                       Ctx->kill_me = KILLME_WRITE_FAILED;
-                       return -1;
-               }
-               bytes_written = bytes_written + retval;
-       }
-       return 0;
-}
-
-void cputbuf(const StrBuf *Buf) {   
-       client_write(ChrPtr(Buf), StrLength(Buf)); 
-}   
-
-
-// Send formatted printable data to the client.
-// Implemented in terms of client_write() so it's technically not sysdep...
-void cprintf(const char *format, ...) {   
-       va_list arg_ptr;   
-       char buf[1024];
-   
-       va_start(arg_ptr, format);   
-       if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1)
-               buf[sizeof buf - 2] = '\n';
-       client_write(buf, strlen(buf)); 
-       va_end(arg_ptr);
-}
-
-
-// Read data from the client socket.
-//
-// sock                socket fd to read from
-// buf         buffer to read into 
-// bytes       number of bytes to read
-// timeout     Number of seconds to wait before timing out
-//
-// Possible return values:
-//      1       Requested number of bytes has been read.
-//      0       Request timed out.
-//     -1      Connection is broken, or other error.
-int client_read_blob(StrBuf *Target, int bytes, int timeout) {
-       CitContext *CCC=CC;
-       const char *Error;
-       int retval = 0;
-
-#ifdef HAVE_OPENSSL
-       if (CCC->redirect_ssl) {
-               retval = client_read_sslblob(Target, bytes, timeout);
-               if (retval < 0) {
-                       syslog(LOG_ERR, "sysdep: client_read_blob() failed");
-               }
-       }
-       else 
-#endif
-       {
-               retval = StrBufReadBLOBBuffered(Target, 
-                                               CCC->RecvBuf.Buf,
-                                               &CCC->RecvBuf.ReadWritePointer,
-                                               &CCC->client_socket,
-                                               1, 
-                                               bytes,
-                                               O_TERM,
-                                               &Error
-               );
-               if (retval < 0) {
-                       syslog(LOG_ERR, "sysdep: client_read_blob() failed: %s", Error);
-                       client_close();
-                       return retval;
-               }
-       }
-       return retval;
-}
-
-
-// to make client_read_random_blob() more efficient, increase buffer size.
-// just use in greeting function, else your buffer may be flushed
-void client_set_inbound_buf(long N)
-{
-       CitContext *CCC=CC;
-       FlushStrBuf(CCC->RecvBuf.Buf);
-       ReAdjustEmptyBuf(CCC->RecvBuf.Buf, N * SIZ, N * SIZ);
-}
-
-int client_read_random_blob(StrBuf *Target, int timeout)
-{
-       CitContext *CCC=CC;
-       int rc;
-
-       rc =  client_read_blob(Target, 1, timeout);
-       if (rc > 0)
-       {
-               long len;
-               const char *pch;
-               
-               len = StrLength(CCC->RecvBuf.Buf);
-               pch = ChrPtr(CCC->RecvBuf.Buf);
-
-               if (len > 0)
-               {
-                       if (CCC->RecvBuf.ReadWritePointer != NULL) {
-                               len -= CCC->RecvBuf.ReadWritePointer - pch;
-                               pch = CCC->RecvBuf.ReadWritePointer;
-                       }
-                       StrBufAppendBufPlain(Target, pch, len, 0);
-                       FlushStrBuf(CCC->RecvBuf.Buf);
-                       CCC->RecvBuf.ReadWritePointer = NULL;
-                       return StrLength(Target);
-               }
-               return rc;
-       }
-       else
-               return rc;
-}
-
-int client_read_to(char *buf, int bytes, int timeout)
-{
-       CitContext *CCC=CC;
-       int rc;
-
-       rc = client_read_blob(CCC->MigrateBuf, bytes, timeout);
-       if (rc < 0)
-       {
-               *buf = '\0';
-               return rc;
-       }
-       else
-       {
-               memcpy(buf, 
-                      ChrPtr(CCC->MigrateBuf),
-                      StrLength(CCC->MigrateBuf) + 1);
-               FlushStrBuf(CCC->MigrateBuf);
-               return rc;
-       }
-}
-
-
-int HaveMoreLinesWaiting(CitContext *CCC) {
-       if ((CCC->kill_me != 0) ||
-           ( (CCC->RecvBuf.ReadWritePointer == NULL) && 
-             (StrLength(CCC->RecvBuf.Buf) == 0) && 
-             (CCC->client_socket != -1)) )
-               return 0;
-       else
-               return 1;
-}
-
-
-// Read data from the client socket with default timeout.
-// (This is implemented in terms of client_read_to() and could be
-// justifiably moved out of sysdep.c)
-INLINE int client_read(char *buf, int bytes) {
-       return(client_read_to(buf, bytes, CtdlGetConfigInt("c_sleeping")));
-}
-
-int CtdlClientGetLine(StrBuf *Target) {
-       CitContext *CCC=CC;
-       const char *Error;
-       int rc;
-
-       FlushStrBuf(Target);
-#ifdef HAVE_OPENSSL
-       if (CCC->redirect_ssl) {
-               rc = client_readline_sslbuffer(Target, CCC->RecvBuf.Buf, &CCC->RecvBuf.ReadWritePointer, 1);
-               return rc;
-       }
-       else 
-#endif
-       {
-               rc = StrBufTCP_read_buffered_line_fast(Target, 
-                                                      CCC->RecvBuf.Buf,
-                                                      &CCC->RecvBuf.ReadWritePointer,
-                                                      &CCC->client_socket,
-                                                      5,
-                                                      1,
-                                                      &Error
-               );
-               return rc;
-       }
-}
-
-
-// Get a LF-terminated line of text from the client.
-// (This is implemented in terms of client_read() and could be
-// justifiably moved out of sysdep.c)
-int client_getln(char *buf, int bufsize) {
-       int i, retval;
-       CitContext *CCC=CC;
-       const char *pCh;
-
-       retval = CtdlClientGetLine(CCC->MigrateBuf);
-       if (retval < 0)
-         return(retval >= 0);
-
-
-       i = StrLength(CCC->MigrateBuf);
-       pCh = ChrPtr(CCC->MigrateBuf);
-       // Strip the trailing LF, and the trailing CR if present.
-       if (bufsize <= i)
-               i = bufsize - 1;
-       while ( (i > 0)
-               && ( (pCh[i - 1]==13)
-                    || ( pCh[i - 1]==10)) ) {
-               i--;
-       }
-       memcpy(buf, pCh, i);
-       buf[i] = 0;
-
-       FlushStrBuf(CCC->MigrateBuf);
-       if (retval < 0) {
-               safestrncpy(&buf[i], "000", bufsize - i);
-       }
-       return(retval >= 0);
-}
-
-
-// Cleanup any contexts that are left lying around
-void close_masters(void) {
-       struct ServiceFunctionHook *serviceptr;
-       const char *Text;
-
-       // close all protocol master sockets
-       for (serviceptr = ServiceHookTable; serviceptr != NULL;
-           serviceptr = serviceptr->next ) {
-
-               if (serviceptr->tcp_port > 0) {
-                       if (serviceptr->msock == -1) {
-                               Text = "not closing again";
-                       }
-                       else {
-                               Text = "Closing";
-                       }
-                       syslog(LOG_INFO, "sysdep: %s %d listener on port %d",
-                              Text,
-                              serviceptr->msock,
-                              serviceptr->tcp_port
-                       );
-                       serviceptr->tcp_port = 0;
-               }
-               
-               if (serviceptr->sockpath != NULL) {
-                       if (serviceptr->msock == -1) {
-                               Text = "not closing again";
-                       }
-                       else {
-                               Text = "Closing";
-                       }
-                       syslog(LOG_INFO, "sysdep: %s %d listener on '%s'",
-                              Text,
-                              serviceptr->msock,
-                              serviceptr->sockpath
-                       );
-               }
-
-               if (serviceptr->msock != -1) {
-                       close(serviceptr->msock);
-                       serviceptr->msock = -1;
-               }
-
-               // If it's a Unix domain socket, remove the file.
-               if (serviceptr->sockpath != NULL) {
-                       unlink(serviceptr->sockpath);
-                       serviceptr->sockpath = NULL;
-               }
-       }
-}
-
-
-// The system-dependent part of master_cleanup() - close the master socket.
-void sysdep_master_cleanup(void) {
-       close_masters();
-       context_cleanup();
-}
-
-
-
-pid_t current_child;
-void graceful_shutdown(int signum) {
-       kill(current_child, signum);
-       unlink(file_pid_file);
-       exit(0);
-}
-
-int nFireUps = 0;
-int nFireUpsNonRestart = 0;
-pid_t ForkedPid = 1;
-
-// Start running as a daemon.
-void start_daemon(int unused) {
-       int status = 0;
-       pid_t child = 0;
-       FILE *fp;
-       int do_restart = 0;
-       current_child = 0;
-
-       // Close stdin/stdout/stderr and replace them with /dev/null.
-       // We don't just call close() because we don't want these fd's
-       // to be reused for other files.
-       child = fork();
-       if (child != 0) {
-               exit(0);
-       }
-       
-       signal(SIGHUP, SIG_IGN);
-       signal(SIGINT, SIG_IGN);
-       signal(SIGQUIT, SIG_IGN);
-
-       setsid();
-       umask(0);
-       if (    (freopen("/dev/null", "r", stdin) != stdin) || 
-               (freopen("/dev/null", "w", stdout) != stdout) || 
-               (freopen("/dev/null", "w", stderr) != stderr)
-       ) {
-               syslog(LOG_ERR, "sysdep: unable to reopen stdio: %m");
-       }
-
-       do {
-               current_child = fork();
-               signal(SIGTERM, graceful_shutdown);
-               if (current_child < 0) {
-                       perror("fork");
-                       exit(errno);
-               }
-               else if (current_child == 0) {
-                       return; // continue starting citadel.
-               }
-               else {
-                       fp = fopen(file_pid_file, "w");
-                       if (fp != NULL) {
-                               fprintf(fp, ""F_PID_T"\n", getpid());
-                               fclose(fp);
-                       }
-                       waitpid(current_child, &status, 0);
-               }
-               nFireUpsNonRestart = nFireUps;
-               
-               // Exit code 0 means the watcher should exit
-               if (WIFEXITED(status) && (WEXITSTATUS(status) == CTDLEXIT_SHUTDOWN)) {
-                       do_restart = 0;
-               }
-
-               // Exit code 101-109 means the watcher should exit
-               else if (WIFEXITED(status) && (WEXITSTATUS(status) >= 101) && (WEXITSTATUS(status) <= 109)) {
-                       do_restart = 0;
-               }
-
-               // Any other exit code, or no exit code, means we should restart.
-               else {
-                       do_restart = 1;
-                       nFireUps++;
-                       ForkedPid = current_child;
-               }
-
-       } while (do_restart);
-
-       unlink(file_pid_file);
-       exit(WEXITSTATUS(status));
-}
-
-
-void checkcrash(void) {
-       if (nFireUpsNonRestart != nFireUps) {
-               StrBuf *CrashMail;
-               CrashMail = NewStrBuf();
-               syslog(LOG_ALERT, "sysdep: posting crash message");
-               StrBufPrintf(CrashMail, 
-                       " \n"
-                       " The Citadel server process (citserver) terminated unexpectedly."
-                       "\n \n"
-                       " This could be the result of a bug in the server program, or some external "
-                       "factor.\n \n"
-                       " You can obtain more information about this by enabling core dumps.\n \n"
-                       " For more information, please see:\n \n"
-                       " http://citadel.org/doku.php?id=faq:mastering_your_os:gdb#how.do.i.make.my.system.produce.core-files"
-                       "\n \n"
-
-                       " If you have already done this, the core dump is likely to be found at %score.%d\n"
-                       ,
-                       ctdl_run_dir, ForkedPid);
-               CtdlAideMessage(ChrPtr(CrashMail), "Citadel server process terminated unexpectedly");
-               FreeStrBuf(&CrashMail);
-       }
-}
-
-
-// Generic routine to convert a login name to a full name (gecos)
-// Returns nonzero if a conversion took place
-int convert_login(char NameToConvert[]) {
-       struct passwd *pw;
-       unsigned int a;
-
-       pw = getpwnam(NameToConvert);
-       if (pw == NULL) {
-               return(0);
-       }
-       else {
-               strcpy(NameToConvert, pw->pw_gecos);
-               for (a=0; a<strlen(NameToConvert); ++a) {
-                       if (NameToConvert[a] == ',') NameToConvert[a] = 0;
-               }
-               return(1);
-       }
-}
-
-
-void HuntBadSession(void) {
-       int highest;
-       CitContext *ptr;
-       fd_set readfds;
-       struct timeval tv;
-       struct ServiceFunctionHook *serviceptr;
-
-       // Next, add all of the client sockets
-       begin_critical_section(S_SESSION_TABLE);
-       for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
-               if ((ptr->state == CON_SYS) && (ptr->client_socket == 0))
-                       continue;
-               // Initialize the fdset.
-               FD_ZERO(&readfds);
-               highest = 0;
-               tv.tv_sec = 0;          // wake up every second if no input
-               tv.tv_usec = 0;
-
-               // Don't select on dead sessions, only truly idle ones
-               if (    (ptr->state == CON_IDLE)
-                       && (ptr->kill_me == 0)
-                       && (ptr->client_socket > 0)
-               ) {
-                       FD_SET(ptr->client_socket, &readfds);
-                       if (ptr->client_socket > highest)
-                               highest = ptr->client_socket;
-                       
-                       if ((select(highest + 1, &readfds, NULL, NULL, &tv) < 0) && (errno == EBADF))
-                       {
-                               // Gotcha!
-                               syslog(LOG_ERR,
-                                      "sysdep: killing session CC[%d] bad FD: [%d] User[%s] Host[%s:%s]",
-                                       ptr->cs_pid,
-                                       ptr->client_socket,
-                                       ptr->curr_user,
-                                       ptr->cs_host,
-                                       ptr->cs_addr
-                               );
-                               ptr->kill_me = 1;
-                               ptr->client_socket = -1;
-                               break;
-                       }
-               }
-       }
-       end_critical_section(S_SESSION_TABLE);
-
-       // First, add the various master sockets to the fdset.
-       for (serviceptr = ServiceHookTable; serviceptr != NULL; serviceptr = serviceptr->next ) {
-
-               // Initialize the fdset.
-               highest = 0;
-               tv.tv_sec = 0;          // wake up every second if no input
-               tv.tv_usec = 0;
-
-               FD_SET(serviceptr->msock, &readfds);
-               if (serviceptr->msock > highest) {
-                       highest = serviceptr->msock;
-               }
-               if ((select(highest + 1, &readfds, NULL, NULL, &tv) < 0) &&
-                   (errno == EBADF))
-               {
-                       // Gotcha! server socket dead? commit suicide!
-                       syslog(LOG_ERR, "sysdep: found bad FD: %d and its a server socket! Shutting Down!", serviceptr->msock);
-                       server_shutting_down = 1;
-                       break;
-               }
-       }
-}
-
-
-// This loop just keeps going and going and going...
-void *worker_thread(void *blah) {
-       int highest;
-       CitContext *ptr;
-       CitContext *bind_me = NULL;
-       fd_set readfds;
-       int retval = 0;
-       struct timeval tv;
-       int force_purge = 0;
-       struct ServiceFunctionHook *serviceptr;
-       int ssock;                      // Descriptor for client socket
-       CitContext *con = NULL;         // Temporary context pointer
-       int i;
-
-       pthread_mutex_lock(&ThreadCountMutex);
-       ++num_workers;
-       pthread_mutex_unlock(&ThreadCountMutex);
-
-       while (!server_shutting_down) {
-
-               // make doubly sure we're not holding any stale db handles which might cause a deadlock
-               cdb_check_handles();
-do_select:     force_purge = 0;
-               bind_me = NULL;         // Which session shall we handle?
-
-               // Initialize the fdset
-               FD_ZERO(&readfds);
-               highest = 0;
-
-               // First, add the various master sockets to the fdset.
-               for (serviceptr = ServiceHookTable; serviceptr != NULL; serviceptr = serviceptr->next ) {
-                       FD_SET(serviceptr->msock, &readfds);
-                       if (serviceptr->msock > highest) {
-                               highest = serviceptr->msock;
-                       }
-               }
-
-               // Next, add all of the client sockets.
-               begin_critical_section(S_SESSION_TABLE);
-               for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
-                       if ((ptr->state == CON_SYS) && (ptr->client_socket == 0))
-                           continue;
-
-                       // Don't select on dead sessions, only truly idle ones
-                       if (    (ptr->state == CON_IDLE)
-                               && (ptr->kill_me == 0)
-                               && (ptr->client_socket > 0)
-                       ) {
-                               FD_SET(ptr->client_socket, &readfds);
-                               if (ptr->client_socket > highest)
-                                       highest = ptr->client_socket;
-                       }
-                       if ((bind_me == NULL) && (ptr->state == CON_READY)) {
-                               bind_me = ptr;
-                               ptr->state = CON_EXECUTING;
-                               break;
-                       }
-                       if ((bind_me == NULL) && (ptr->state == CON_GREETING)) {
-                               bind_me = ptr;
-                               ptr->state = CON_STARTING;
-                               break;
-                       }
-               }
-               end_critical_section(S_SESSION_TABLE);
-
-               if (bind_me) {
-                       goto SKIP_SELECT;
-               }
-
-               // If we got this far, it means that there are no sessions
-               // which a previous thread marked for attention, so we go
-               // ahead and get ready to select().
-
-               if (!server_shutting_down) {
-                       tv.tv_sec = 1;          // wake up every second if no input
-                       tv.tv_usec = 0;
-                       retval = select(highest + 1, &readfds, NULL, NULL, &tv);
-               }
-               else {
-                       --num_workers;
-                       return NULL;
-               }
-
-               // Now figure out who made this select() unblock.
-               // First, check for an error or exit condition.
-               if (retval < 0) {
-                       if (errno == EBADF) {
-                               syslog(LOG_ERR, "sysdep: select() failed: %m");
-                               HuntBadSession();
-                               goto do_select;
-                       }
-                       if (errno != EINTR) {
-                               syslog(LOG_ERR, "sysdep: exiting: %m");
-                               server_shutting_down = 1;
-                               continue;
-                       } else {
-                               if (server_shutting_down) {
-                                       --num_workers;
-                                       return(NULL);
-                               }
-                               goto do_select;
-                       }
-               }
-               else if (retval == 0) {
-                       if (server_shutting_down) {
-                               --num_workers;
-                               return(NULL);
-                       }
-               }
-
-               // Next, check to see if it's a new client connecting on a master socket.
-
-               else if ((retval > 0) && (!server_shutting_down)) for (serviceptr = ServiceHookTable; serviceptr != NULL; serviceptr = serviceptr->next) {
-
-                       if (FD_ISSET(serviceptr->msock, &readfds)) {
-                               ssock = accept(serviceptr->msock, NULL, 0);
-                               if (ssock >= 0) {
-                                       syslog(LOG_DEBUG, "sysdep: new client socket %d", ssock);
-
-                                       // The master socket is non-blocking but the client
-                                       // sockets need to be blocking, otherwise certain
-                                       // operations barf on FreeBSD.  Not a fatal error.
-                                       if (fcntl(ssock, F_SETFL, 0) < 0) {
-                                               syslog(LOG_ERR, "sysdep: Can't set socket to blocking: %m");
-                                       }
-
-                                       // New context will be created already
-                                       // set up in the CON_EXECUTING state.
-                                       con = CreateNewContext();
-
-                                       // Assign our new socket number to it.
-                                       con->tcp_port = serviceptr->tcp_port;
-                                       con->client_socket = ssock;
-                                       con->h_command_function = serviceptr->h_command_function;
-                                       con->h_async_function = serviceptr->h_async_function;
-                                       con->h_greeting_function = serviceptr->h_greeting_function;
-                                       con->ServiceName = serviceptr->ServiceName;
-                                       
-                                       // Connections on a local client are always from the same host
-                                       if (serviceptr->sockpath != NULL) {
-                                               con->is_local_client = 1;
-                                       }
-       
-                                       // Set the SO_REUSEADDR socket option
-                                       i = 1;
-                                       setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
-                                       con->state = CON_GREETING;
-                                       retval--;
-                                       if (retval == 0)
-                                               break;
-                               }
-                       }
-               }
-
-               // It must be a client socket.  Find a context that has data
-               // waiting on its socket *and* is in the CON_IDLE state.  Any
-               // active sockets other than our chosen one are marked as
-               // CON_READY so the next thread that comes around can just bind
-               // to one without having to select() again.
-               begin_critical_section(S_SESSION_TABLE);
-               for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
-                       int checkfd = ptr->client_socket;
-                       if ((checkfd != -1) && (ptr->state == CON_IDLE) ){
-                               if (FD_ISSET(checkfd, &readfds)) {
-                                       ptr->input_waiting = 1;
-                                       if (!bind_me) {
-                                               bind_me = ptr;  // I choose you!
-                                               bind_me->state = CON_EXECUTING;
-                                       }
-                                       else {
-                                               ptr->state = CON_READY;
-                                       }
-                               } else if ((ptr->is_async) && (ptr->async_waiting) && (ptr->h_async_function)) {
-                                       if (!bind_me) {
-                                               bind_me = ptr;  // I choose you!
-                                               bind_me->state = CON_EXECUTING;
-                                       }
-                                       else {
-                                               ptr->state = CON_READY;
-                                       }
-                               }
-                       }
-               }
-               end_critical_section(S_SESSION_TABLE);
-
-SKIP_SELECT:
-               // We're bound to a session
-               pthread_mutex_lock(&ThreadCountMutex);
-               ++active_workers;
-               pthread_mutex_unlock(&ThreadCountMutex);
-
-               if (bind_me != NULL) {
-                       become_session(bind_me);
-
-                       if (bind_me->state == CON_STARTING) {
-                               bind_me->state = CON_EXECUTING;
-                               begin_session(bind_me);
-                               bind_me->h_greeting_function();
-                       }
-                       // If the client has sent a command, execute it.
-                       if (CC->input_waiting) {
-                               CC->h_command_function();
-
-                               while (HaveMoreLinesWaiting(CC))
-                                      CC->h_command_function();
-
-                               CC->input_waiting = 0;
-                       }
-
-                       // If there are asynchronous messages waiting and the client supports it, do those now
-                       if ((CC->is_async) && (CC->async_waiting) && (CC->h_async_function != NULL)) {
-                               CC->h_async_function();
-                               CC->async_waiting = 0;
-                       }
-
-                       force_purge = CC->kill_me;
-                       become_session(NULL);
-                       bind_me->state = CON_IDLE;
-               }
-
-               dead_session_purge(force_purge);
-               do_housekeeping();
-
-               pthread_mutex_lock(&ThreadCountMutex);
-               --active_workers;
-               if ((active_workers + CtdlGetConfigInt("c_min_workers") < num_workers) &&
-                   (num_workers > CtdlGetConfigInt("c_min_workers")))
-               {
-                       num_workers--;
-                       pthread_mutex_unlock(&ThreadCountMutex);
-                       return (NULL);
-               }
-               pthread_mutex_unlock(&ThreadCountMutex);
-       }
-
-       // If control reaches this point, the server is shutting down
-       pthread_mutex_lock(&ThreadCountMutex);
-       --num_workers;
-       pthread_mutex_unlock(&ThreadCountMutex);
-       return(NULL);
-}
-
-
-// SyslogFacility()
-// Translate text facility name to syslog.h defined value.
-int SyslogFacility(char *name)
-{
-       int i;
-       struct
-       {
-               int facility;
-               char *name;
-       }   facTbl[] =
-       {
-               {   LOG_KERN,   "kern"          },
-               {   LOG_USER,   "user"          },
-               {   LOG_MAIL,   "mail"          },
-               {   LOG_DAEMON, "daemon"        },
-               {   LOG_AUTH,   "auth"          },
-               {   LOG_SYSLOG, "syslog"        },
-               {   LOG_LPR,    "lpr"           },
-               {   LOG_NEWS,   "news"          },
-               {   LOG_UUCP,   "uucp"          },
-               {   LOG_LOCAL0, "local0"        },
-               {   LOG_LOCAL1, "local1"        },
-               {   LOG_LOCAL2, "local2"        },
-               {   LOG_LOCAL3, "local3"        },
-               {   LOG_LOCAL4, "local4"        },
-               {   LOG_LOCAL5, "local5"        },
-               {   LOG_LOCAL6, "local6"        },
-               {   LOG_LOCAL7, "local7"        },
-               {   0,            NULL          }
-       };
-       for(i = 0; facTbl[i].name != NULL; i++) {
-               if(!strcasecmp(name, facTbl[i].name))
-                       return facTbl[i].facility;
-       }
-       return LOG_DAEMON;
-}
diff --git a/citadel/sysdep_decls.h b/citadel/sysdep_decls.h
deleted file mode 100644 (file)
index 221640f..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-
-#ifndef SYSDEP_DECLS_H
-#define SYSDEP_DECLS_H
-
-#include <stdarg.h>
-#include "sysdep.h"
-
-#ifdef HAVE_PTHREAD_H
-#include <pthread.h>
-#endif
-
-#include <db.h>
-
-#if DB_VERSION_MAJOR < 5
-#error Citadel requires Berkeley DB v5 or newer.  Please upgrade.
-#endif
-
-#include "server.h"
-#include "database.h"
-
-#if SIZEOF_SIZE_T == SIZEOF_INT 
-#define SIZE_T_FMT "%d"
-#else
-#define SIZE_T_FMT "%ld"
-#endif
-
-#if SIZEOF_LOFF_T == SIZEOF_LONG 
-#define LOFF_T_FMT "%ld"
-#else
-#define LOFF_T_FMT "%lld"
-#endif
-
-void cputbuf(const StrBuf *Buf);
-
-#ifdef __GNUC__
-void cprintf (const char *format, ...) __attribute__((__format__(__printf__,1,2)));
-#else
-void cprintf (const char *format, ...);
-#endif
-
-void init_sysdep (void);
-int ctdl_tcp_server(char *ip_addr, int port_number, int queue_len);
-int ctdl_uds_server(char *sockpath, int queue_len);
-void buffer_output(void);
-void unbuffer_output(void);
-void flush_output(void);
-int client_write (const char *buf, int nbytes);
-int client_read_to (char *buf, int bytes, int timeout);
-int client_read (char *buf, int bytes);
-int client_getln (char *buf, int maxbytes);
-int CtdlClientGetLine(StrBuf *Target);
-int client_read_blob(StrBuf *Target, int bytes, int timeout);
-void client_set_inbound_buf(long N);
-int client_read_random_blob(StrBuf *Target, int timeout);
-void client_close(void);
-void sysdep_master_cleanup (void);
-void kill_session (int session_to_kill);
-void start_daemon (int do_close_stdio);
-void checkcrash(void);
-int convert_login (char *NameToConvert);
-void init_master_fdset(void);
-void *worker_thread(void *);
-
-extern volatile int exit_signal;
-extern volatile int shutdown_and_halt;
-extern volatile int running_as_daemon;
-extern volatile int restart_server;
-
-extern int verbosity;
-extern int rescan[];
-
-
-extern int SyslogFacility(char *name);
-
-#endif /* SYSDEP_DECLS_H */
diff --git a/citadel/threads.c b/citadel/threads.c
deleted file mode 100644 (file)
index d995c39..0000000
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Thread handling stuff for Citadel server
- *
- * Copyright (c) 1987-2021 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include <errno.h>
-#include <stdio.h>
-#include <syslog.h>
-#include <libcitadel.h>
-#include "modules_init.h"
-#include "serv_extensions.h"
-#include "ctdl_module.h"
-#include "config.h"
-#include "context.h"
-#include "threads.h"
-
-int num_workers = 0;                           /* Current number of worker threads */
-int active_workers = 0;                                /* Number of ACTIVE worker threads */
-pthread_key_t ThreadKey;
-pthread_mutex_t Critters[MAX_SEMAPHORES];      /* Things needing locking */
-struct thread_tsd masterTSD;
-int server_shutting_down = 0;                  /* set to nonzero during shutdown */
-pthread_mutex_t ThreadCountMutex;
-
-void InitializeSemaphores(void)
-{
-       int i;
-
-       /* Set up a bunch of semaphores to be used for critical sections */
-       for (i=0; i<MAX_SEMAPHORES; ++i) {
-               pthread_mutex_init(&Critters[i], NULL);
-       }
-}
-
-
-/*
- * Obtain a semaphore lock to begin a critical section.
- * but only if no one else has one
- */
-int try_critical_section(int which_one)
-{
-       /* For all types of critical sections except those listed here,
-        * ensure nobody ever tries to do a critical section within a
-        * transaction; this could lead to deadlock.
-        */
-       if (    (which_one != S_FLOORCACHE)
-               && (which_one != S_NETCONFIGS)
-       ) {
-               cdb_check_handles();
-       }
-       return (pthread_mutex_trylock(&Critters[which_one]));
-}
-
-
-/*
- * Obtain a semaphore lock to begin a critical section.
- */
-void begin_critical_section(int which_one)
-{
-       /* For all types of critical sections except those listed here,
-        * ensure nobody ever tries to do a critical section within a
-        * transaction; this could lead to deadlock.
-        */
-       if (    (which_one != S_FLOORCACHE)
-               && (which_one != S_NETCONFIGS)
-       ) {
-               cdb_check_handles();
-       }
-       pthread_mutex_lock(&Critters[which_one]);
-}
-
-
-/*
- * Release a semaphore lock to end a critical section.
- */
-void end_critical_section(int which_one)
-{
-       pthread_mutex_unlock(&Critters[which_one]);
-}
-
-
-/*
- * Return a pointer to our thread-specific (not session-specific) data.
- */ 
-struct thread_tsd *MyThread(void) {
-        struct thread_tsd *c = (struct thread_tsd *) pthread_getspecific(ThreadKey) ;
-       if (!c) {
-               return &masterTSD;
-       }
-       return c;
-}
-
-
-/* 
- * Called by CtdlThreadCreate()
- * We have to pass through here before starting our thread in order to create a set of data
- * that is thread-specific rather than session-specific.
- */
-void *CTC_backend(void *supplied_start_routine)
-{
-       struct thread_tsd *mytsd;
-       void *(*start_routine)(void*) = supplied_start_routine;
-
-       mytsd = (struct thread_tsd *) malloc(sizeof(struct thread_tsd));
-       memset(mytsd, 0, sizeof(struct thread_tsd));
-       pthread_setspecific(ThreadKey, (const void *) mytsd);
-
-       start_routine(NULL);
-       // free(mytsd);
-       return(NULL);
-}
-
-/*
- * Function to create a thread.
- */ 
-void CtdlThreadCreate(void *(*start_routine)(void*))
-{
-       pthread_t thread;
-       pthread_attr_t attr;
-       int ret = 0;
-
-       ret = pthread_attr_init(&attr);
-       ret = pthread_attr_setstacksize(&attr, THREADSTACKSIZE);
-       ret = pthread_create(&thread, &attr, CTC_backend, (void *)start_routine);
-       if (ret != 0) syslog(LOG_ERR, "pthread_create() : %m");
-}
-
-
-void InitializeMasterTSD(void) {
-       memset(&masterTSD, 0, sizeof(struct thread_tsd));
-}
-
-
-/*
- * Initialize the thread system
- */
-void go_threading(void) {
-       pthread_mutex_init(&ThreadCountMutex, NULL);
-
-       /* Second call to module init functions now that threading is up */
-       initialise_modules(1);
-
-       /* Begin with one worker thread.  We will expand the pool if necessary */
-       CtdlThreadCreate(worker_thread);
-
-       /* The supervisor thread monitors worker threads and spawns more of them if it finds that
-        * they are all in use.
-        */
-       while (!server_shutting_down) {
-               if ((active_workers == num_workers) && (num_workers < CtdlGetConfigInt("c_max_workers"))) {
-                       CtdlThreadCreate(worker_thread);
-               }
-               usleep(1000000);
-       }
-
-       /* When we get to this point we are getting ready to shut down our Citadel server */
-       terminate_all_sessions();               /* close all client sockets */
-       CtdlShutdownServiceHooks();             /* close all listener sockets to prevent new connections */
-       PerformSessionHooks(EVT_SHUTDOWN);      /* run any registered shutdown hooks */
-
-       /* We used to wait for all threads to exit.  Fuck that.  The only thing important is that the databases are
-        * cleanly unmounted.  After that, exit the whole program.
-        */
-}
diff --git a/citadel/threads.h b/citadel/threads.h
deleted file mode 100644 (file)
index 33b854e..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-
-#ifndef THREADS_H
-#define THREADS_H
-
-#include "sysdep.h"
-
-#ifdef HAVE_PTHREAD_H
-#include <pthread.h>
-#endif
-
-#include <sys/time.h>
-#include <string.h>
-
-#include <db.h>
-
-#include "server.h"
-#include "sysdep_decls.h"
-
-/*
- * Things we need to keep track of per-thread instead of per-session
- */
-struct thread_tsd {
-       DB_TXN *tid;            /* Transaction handle */
-       DBC *cursors[MAXCDB];   /* Cursors, for traversals... */
-};
-
-extern pthread_key_t ThreadKey;
-extern struct thread_tsd masterTSD;
-#define TSD MyThread()
-
-extern int num_workers;
-extern int active_workers;
-extern int server_shutting_down;
-
-struct thread_tsd *MyThread(void);
-int try_critical_section (int which_one);
-void begin_critical_section (int which_one);
-void end_critical_section (int which_one);
-void go_threading(void);
-void InitializeMasterTSD(void);
-void CtdlThreadCreate(void *(*start_routine)(void*));
-
-
-extern pthread_mutex_t ThreadCountMutex;;
-
-#endif // THREADS_H
diff --git a/citadel/typesize.h b/citadel/typesize.h
deleted file mode 100644 (file)
index 3732c39..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-
-/*
-   This file defines typedefs for 8, 16, and 32 bit integers.  They are:
-   cit_int8_t  default 8-bit int
-   cit_int16_t default 16-bit int
-   cit_int32_t default 32-bit int
-   cit_int64_t default 64-bit int (not implemented yet)
-   cit_sint8_t signed 8-bit int
-   cit_sint16_t        signed 16-bit int
-   cit_sint32_t        signed 32-bit int
-   cit_sint64_t        signed 64-bit int (not implemented yet)
-   cit_uint8_t unsigned 8-bit int
-   cit_uint16_t        unsigned 16-bit int
-   cit_uint32_t        unsigned 32-bit int
-   cit_uint64_t        unsigned 64-bit int (not implemented yet)
-
-   The sizes are determined during the configure process; see the 
-   AC_CHECK_SIZEOF macros in configure.in.  In no way do we assume that any
-   given datatype is any particular width, e.g. we don't assume short is two
-   bytes; we check for it specifically.
-
-   This might seem excessively paranoid, but I've seen some WEIRD systems
-   and some bizarre compilers (Domain/OS for instance) in my time.
-*/
-
-#ifndef _CITADEL_UX_TYPESIZE_H
-#define _CITADEL_UX_TYPESIZE_H
-
-/* Include sysdep.h if not already included */
-#ifndef CTDLDIR
-# include "sysdep.h"
-#endif
-
-/* 8-bit - If this fails, your compiler is broken */
-#if SIZEOF_CHAR == 1
-typedef char cit_int8_t;
-typedef signed char cit_sint8_t;
-typedef unsigned char cit_uint8_t;
-#else
-# error Unable to find an 8-bit integer datatype
-#endif
-
-/* 16-bit - If this fails, your compiler is broken */
-#if SIZEOF_SHORT == 2
-typedef short cit_int16_t;
-typedef signed short cit_sint16_t;
-typedef unsigned short cit_uint16_t;
-#elif SIZEOF_INT == 2
-typedef int cit_int16_t;
-typedef signed int cit_sint16_t;
-typedef unsigned int cit_uint16_t;
-#else
-# error Unable to find a 16-bit integer datatype
-#endif
-
-/* 32-bit - If this fails, your compiler is broken */
-#if SIZEOF_INT == 4
-typedef int cit_int32_t;
-typedef signed int cit_sint32_t;
-typedef unsigned int cit_uint32_t;
-#elif SIZEOF_LONG == 4
-typedef long cit_int32_t;
-typedef signed long cit_sint32_t;
-typedef unsigned long cit_uint32_t;
-#else
-# error Unable to find a 32-bit integer datatype
-#endif
-
-#endif /* _CITADEL_UX_TYPESIZE_H */
diff --git a/citadel/user_ops.c b/citadel/user_ops.c
deleted file mode 100644 (file)
index 0603fc7..0000000
+++ /dev/null
@@ -1,1169 +0,0 @@
-// Server functions which perform operations on user objects.
-//
-// Copyright (c) 1987-2022 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#include <stdlib.h>
-#include <unistd.h>
-#include "sysdep.h"
-#include <stdio.h>
-#include <sys/stat.h>
-#include <libcitadel.h>
-#include "control.h"
-#include "support.h"
-#include "citserver.h"
-#include "config.h"
-#include "citadel_ldap.h"
-#include "ctdl_module.h"
-#include "user_ops.h"
-#include "internet_addressing.h"
-
-// These pipes are used to talk to the chkpwd daemon, which is forked during startup
-int chkpwd_write_pipe[2];
-int chkpwd_read_pipe[2];
-
-
-// makeuserkey() - convert a username into the format used as a database key
-//             "key" must be a buffer of at least USERNAME_SIZE
-//             (Key format is the username with all non-alphanumeric characters removed, and converted to lower case.)
-void makeuserkey(char *key, const char *username) {
-       int i;
-       int keylen = 0;
-
-       if (IsEmptyStr(username)) {
-               key[0] = 0;
-               return;
-       }
-
-       int len = strlen(username);
-       for (i=0; ((i<=len) && (i<USERNAME_SIZE-1)); ++i) {
-               if (isalnum((username[i]))) {
-                       key[keylen++] = tolower(username[i]);
-               }
-       }
-       key[keylen++] = 0;
-}
-
-
-// Compare two usernames to see if they are the same user after being keyed for the database
-// Usage is identical to strcmp()
-int CtdlUserCmp(char *s1, char *s2) {
-       char k1[USERNAME_SIZE];
-       char k2[USERNAME_SIZE];
-
-       makeuserkey(k1, s1);
-       makeuserkey(k2, s2);
-       return(strcmp(k1,k2));
-}
-
-
-// CtdlGetUser()       retrieve named user into supplied buffer.
-//                     returns 0 on success
-int CtdlGetUser(struct ctdluser *usbuf, char *name) {
-       char usernamekey[USERNAME_SIZE];
-       struct cdbdata *cdbus;
-
-       if (usbuf != NULL) {
-               memset(usbuf, 0, sizeof(struct ctdluser));
-       }
-
-       makeuserkey(usernamekey, name);
-       if (IsEmptyStr(usernamekey)) {
-               return(1);      // empty user name
-       }
-       cdbus = cdb_fetch(CDB_USERS, usernamekey, strlen(usernamekey));
-
-       if (cdbus == NULL) {    // user not found
-               return(1);
-       }
-       if (usbuf != NULL) {
-               memcpy(usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ?  sizeof(struct ctdluser) : cdbus->len));
-       }
-       cdb_free(cdbus);
-       return(0);
-}
-
-
-int CtdlLockGetCurrentUser(void) {
-       return CtdlGetUser(&CC->user, CC->curr_user);
-}
-
-
-// CtdlGetUserLock()  -  same as getuser() but locks the record
-int CtdlGetUserLock(struct ctdluser *usbuf, char *name) {
-       int retcode;
-
-       retcode = CtdlGetUser(usbuf, name);
-       if (retcode == 0) {
-               begin_critical_section(S_USERS);
-       }
-       return(retcode);
-}
-
-
-// CtdlPutUser()  -  write user buffer into the correct place on disk
-void CtdlPutUser(struct ctdluser *usbuf) {
-       char usernamekey[USERNAME_SIZE];
-       makeuserkey(usernamekey, usbuf->fullname);
-       usbuf->version = REV_LEVEL;
-       cdb_store(CDB_USERS, usernamekey, strlen(usernamekey), usbuf, sizeof(struct ctdluser));
-}
-
-
-void CtdlPutCurrentUserLock() {
-       CtdlPutUser(&CC->user);
-}
-
-
-// CtdlPutUserLock()  -  same as putuser() but locks the record
-void CtdlPutUserLock(struct ctdluser *usbuf) {
-       CtdlPutUser(usbuf);
-       end_critical_section(S_USERS);
-}
-
-
-// rename_user()  -  this is tricky because the user's display name is the database key
-// Returns 0 on success or nonzero if there was an error...
-int rename_user(char *oldname, char *newname) {
-       int retcode = RENAMEUSER_OK;
-       struct ctdluser usbuf;
-
-       char oldnamekey[USERNAME_SIZE];
-       char newnamekey[USERNAME_SIZE];
-
-       // Create the database keys...
-       makeuserkey(oldnamekey, oldname);
-       makeuserkey(newnamekey, newname);
-
-       // Lock up and get going
-       begin_critical_section(S_USERS);
-
-       // We cannot rename a user who is currently logged in
-       if (CtdlIsUserLoggedIn(oldname)) {
-               end_critical_section(S_USERS);
-               return RENAMEUSER_LOGGED_IN;
-       }
-
-       if (CtdlGetUser(&usbuf, newname) == 0) {
-               retcode = RENAMEUSER_ALREADY_EXISTS;
-       }
-       else {
-
-               if (CtdlGetUser(&usbuf, oldname) != 0) {
-                       retcode = RENAMEUSER_NOT_FOUND;
-               }
-               else {          // Sanity checks succeeded.  Now rename the user.
-                       if (usbuf.usernum == 0) {
-                               syslog(LOG_DEBUG, "user_ops: can not rename user \"Citadel\".");
-                               retcode = RENAMEUSER_NOT_FOUND;
-                       }
-                       else {
-                               syslog(LOG_DEBUG, "user_ops: renaming <%s> to <%s>", oldname, newname);
-                               cdb_delete(CDB_USERS, oldnamekey, strlen(oldnamekey));
-                               safestrncpy(usbuf.fullname, newname, sizeof usbuf.fullname);
-                               CtdlPutUser(&usbuf);
-                               cdb_store(CDB_USERSBYNUMBER, &usbuf.usernum, sizeof(long), usbuf.fullname, strlen(usbuf.fullname)+1 );
-                               retcode = RENAMEUSER_OK;
-                       }
-               }
-       
-       }
-
-       end_critical_section(S_USERS);
-       return(retcode);
-}
-
-
-// Convert a username into the format used as a database key prior to version 928
-// This only gets called by reindex_user_928()
-void makeuserkey_pre928(char *key, const char *username) {
-       int i;
-
-       int len = strlen(username);
-
-       if (len >= USERNAME_SIZE) {
-               syslog(LOG_INFO, "Username too long: %s", username);
-               len = USERNAME_SIZE - 1; 
-       }
-       for (i=0; i<=len; ++i) {
-               key[i] = tolower(username[i]);
-       }
-}
-
-
-// Read a user record using the pre-v928 index format, and write it back using the v928-and-higher index format.
-// This ONLY gets called during an upgrade from version <928 to version >=928.
-void reindex_user_928(char *username, void *out_data) {
-
-       char oldkey[USERNAME_SIZE];
-       char newkey[USERNAME_SIZE];
-       struct cdbdata *cdbus;
-       struct ctdluser usbuf;
-
-       makeuserkey_pre928(oldkey, username);
-       makeuserkey(newkey, username);
-
-       syslog(LOG_DEBUG, "user_ops: reindex_user_928: %s <%s> --> <%s>", username, oldkey, newkey);
-
-       // Fetch the user record using the old index format
-       cdbus = cdb_fetch(CDB_USERS, oldkey, strlen(oldkey));
-       if (cdbus == NULL) {
-               syslog(LOG_INFO, "user_ops: <%s> not found, were they already reindexed?", username);
-               return;
-       }
-       memcpy(&usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ? sizeof(struct ctdluser) : cdbus->len));
-       cdb_free(cdbus);
-
-       // delete the old record
-       cdb_delete(CDB_USERS, oldkey, strlen(oldkey));
-
-       // write the new record
-       cdb_store(CDB_USERS, newkey, strlen(newkey), &usbuf, sizeof(struct ctdluser));
-}
-
-
-// Index-generating function used by Ctdl[Get|Set]Relationship
-int GenerateRelationshipIndex(char *IndexBuf,
-                             long RoomID,
-                             long RoomGen,
-                             long UserID
-) {
-       struct {
-               long iRoomID;
-               long iRoomGen;
-               long iUserID;
-       } TheIndex;
-
-       TheIndex.iRoomID = RoomID;
-       TheIndex.iRoomGen = RoomGen;
-       TheIndex.iUserID = UserID;
-
-       memcpy(IndexBuf, &TheIndex, sizeof(TheIndex));
-       return(sizeof(TheIndex));
-}
-
-
-// Back end for CtdlSetRelationship()
-void put_visit(visit *newvisit) {
-       char IndexBuf[32];
-       int IndexLen = 0;
-
-       memset (IndexBuf, 0, sizeof (IndexBuf));
-       // Generate an index
-       IndexLen = GenerateRelationshipIndex(IndexBuf, newvisit->v_roomnum, newvisit->v_roomgen, newvisit->v_usernum);
-
-       // Store the record
-       cdb_store(CDB_VISIT, IndexBuf, IndexLen,
-                 newvisit, sizeof(visit)
-       );
-}
-
-
-// Define a relationship between a user and a room
-void CtdlSetRelationship(visit *newvisit, struct ctdluser *rel_user, struct ctdlroom *rel_room) {
-       // We don't use these in Citadel because they're implicit by the
-       // index, but they must be present if the database is exported.
-       newvisit->v_roomnum = rel_room->QRnumber;
-       newvisit->v_roomgen = rel_room->QRgen;
-       newvisit->v_usernum = rel_user->usernum;
-
-       put_visit(newvisit);
-}
-
-
-// Locate a relationship between a user and a room
-void CtdlGetRelationship(visit *vbuf, struct ctdluser *rel_user, struct ctdlroom *rel_room) {
-       char IndexBuf[32];
-       int IndexLen;
-       struct cdbdata *cdbvisit;
-
-       // Generate an index
-       IndexLen = GenerateRelationshipIndex(IndexBuf, rel_room->QRnumber, rel_room->QRgen, rel_user->usernum);
-
-       // Clear out the buffer
-       memset(vbuf, 0, sizeof(visit));
-
-       cdbvisit = cdb_fetch(CDB_VISIT, IndexBuf, IndexLen);
-       if (cdbvisit != NULL) {
-               memcpy(vbuf, cdbvisit->ptr, ((cdbvisit->len > sizeof(visit)) ?  sizeof(visit) : cdbvisit->len));
-               cdb_free(cdbvisit);
-       }
-       else {
-               // If this is the first time the user has seen this room, set the view to be the default for the room.
-               vbuf->v_view = rel_room->QRdefaultview;
-       }
-
-       // Set v_seen if necessary
-       if (vbuf->v_seen[0] == 0) {
-               snprintf(vbuf->v_seen, sizeof vbuf->v_seen, "*:%ld", vbuf->v_lastseen);
-       }
-}
-
-
-void CtdlMailboxName(char *buf, size_t n, const struct ctdluser *who, const char *prefix) {
-       snprintf(buf, n, "%010ld.%s", who->usernum, prefix);
-}
-
-
-// Check to see if the specified user has Internet mail permission
-// (returns nonzero if permission is granted)
-int CtdlCheckInternetMailPermission(struct ctdluser *who) {
-
-       // Do not allow twits to send Internet mail
-       if (who->axlevel <= AxProbU) return(0);
-
-       // Globally enabled?
-       if (CtdlGetConfigInt("c_restrict") == 0) return(1);
-
-       // User flagged ok?
-       if (who->flags & US_INTERNET) return(2);
-
-       // Admin level access?
-       if (who->axlevel >= AxAideU) return(3);
-
-       // No mail for you!
-       return(0);
-}
-
-
-// This is a convenience function which follows the Citadel protocol semantics for most commands.
-// If the current user does not have the requested access level, it outputs a protocol-friendly error message
-// and then returns (-1).  This allows calling functions to complete an access level check in one line of code.
-int CtdlAccessCheck(int required_level) {
-       if (CC->internal_pgm) return(0);
-       if (required_level >= ac_internal) {
-               cprintf("%d This is not a user-level command.\n", ERROR + HIGHER_ACCESS_REQUIRED);
-               return(-1);
-       }
-
-       if ((required_level >= ac_logged_in_or_guest) && (CC->logged_in == 0) && (CtdlGetConfigInt("c_guest_logins") == 0)) {
-               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
-               return(-1);
-       }
-
-       if ((required_level >= ac_logged_in) && (CC->logged_in == 0)) {
-               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
-               return(-1);
-       }
-
-       if (CC->user.axlevel >= AxAideU) return(0);
-       if (required_level >= ac_aide) {
-               cprintf("%d This command requires Admin access.\n",
-                       ERROR + HIGHER_ACCESS_REQUIRED);
-               return(-1);
-       }
-
-       if (is_room_aide()) return(0);
-       if (required_level >= ac_room_aide) {
-               cprintf("%d This command requires Admin or Room Admin access.\n",
-                       ERROR + HIGHER_ACCESS_REQUIRED);
-               return(-1);
-       }
-
-       // Do not generate any output if we succeeded -- the calling function will handle that.
-       return(0);
-}
-
-
-// Is the user currently logged in an Admin?
-int is_aide(void) {
-       if (CC->user.axlevel >= AxAideU) {
-               return(1);
-       }
-       else {
-               return(0);
-       }
-}
-
-
-// Is the user currently logged in an Admin *or* the room Admin for this room?
-int is_room_aide(void) {
-       if (!CC->logged_in) {
-               return(0);
-       }
-
-       if ((CC->user.axlevel >= AxAideU) || (CC->room.QRroomaide == CC->user.usernum)) {
-               return(1);
-       }
-       else {
-               return(0);
-       }
-}
-
-
-// CtdlGetUserByNumber() - get user by number, returns 0 if user was found
-// Note: fetching a user this way requires one additional database operation.
-int CtdlGetUserByNumber(struct ctdluser *usbuf, long number) {
-       struct cdbdata *cdbun;
-       int r;
-
-       cdbun = cdb_fetch(CDB_USERSBYNUMBER, &number, sizeof(long));
-       if (cdbun == NULL) {
-               syslog(LOG_INFO, "user_ops: %ld not found", number);
-               return(-1);
-       }
-
-       syslog(LOG_INFO, "user_ops: %ld maps to %s", number, cdbun->ptr);
-       r = CtdlGetUser(usbuf, cdbun->ptr);
-       cdb_free(cdbun);
-       return(r);
-}
-
-
-// Helper function for rebuild_usersbynumber()
-void rebuild_ubn_for_user(char *username, void *data) {
-       struct ctdluser u;
-
-       syslog(LOG_DEBUG, "user_ops: rebuilding usersbynumber index for %s", username);
-       if (CtdlGetUser(&u, username) == 0) {
-               cdb_store(CDB_USERSBYNUMBER, &(u.usernum), sizeof(long), u.fullname, strlen(u.fullname)+1);
-       }
-}
-
-
-// Rebuild the users-by-number index
-void rebuild_usersbynumber(void) {
-       cdb_trunc(CDB_USERSBYNUMBER);                   // delete the old indices
-       ForEachUser(rebuild_ubn_for_user, NULL);        // enumerate the users
-}
-
-
-// getuserbyuid()      Get user by system uid (for PAM mode authentication)
-//                     Returns 0 if user was found
-//                     This now uses an extauth index.
-int getuserbyuid(struct ctdluser *usbuf, uid_t number) {
-       struct cdbdata *cdbextauth;
-       long usernum = 0;
-       StrBuf *claimed_id;
-
-       claimed_id = NewStrBuf();
-       StrBufPrintf(claimed_id, "uid:%d", number);
-       cdbextauth = cdb_fetch(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id));
-       FreeStrBuf(&claimed_id);
-       if (cdbextauth == NULL) {
-               return(-1);
-       }
-
-       memcpy(&usernum, cdbextauth->ptr, sizeof(long));
-       cdb_free(cdbextauth);
-
-       if (!CtdlGetUserByNumber(usbuf, usernum)) {
-               return(0);
-       }
-
-       return(-1);
-}
-
-
-// Back end for cmd_user() and its ilk
-int CtdlLoginExistingUser(const char *trythisname) {
-       char username[SIZ];
-       int found_user;
-
-       syslog(LOG_DEBUG, "user_ops: CtdlLoginExistingUser(%s)", trythisname);
-
-       if ((CC->logged_in)) {
-               return login_already_logged_in;
-       }
-
-       if (trythisname == NULL) return login_not_found;
-       
-       if (!strncasecmp(trythisname, "SYS_", 4)) {
-               syslog(LOG_DEBUG, "user_ops: system user \"%s\" is not allowed to log in.", trythisname);
-               return login_not_found;
-       }
-
-       // Continue attempting user validation...
-       safestrncpy(username, trythisname, sizeof (username));
-       striplt(username);
-
-       if (IsEmptyStr(username)) {
-               return login_not_found;
-       }
-
-       // host auth mode...
-       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
-               struct passwd pd;
-               struct passwd *tempPwdPtr;
-               char pwdbuffer[256];
-       
-               syslog(LOG_DEBUG, "user_ops: asking host about <%s>", username);
-#ifdef HAVE_GETPWNAM_R
-#ifdef SOLARIS_GETPWUID
-               syslog(LOG_DEBUG, "user_ops: calling getpwnam_r()");
-               tempPwdPtr = getpwnam_r(username, &pd, pwdbuffer, sizeof pwdbuffer);
-#else // SOLARIS_GETPWUID
-               syslog(LOG_DEBUG, "user_ops: calling getpwnam_r()");
-               getpwnam_r(username, &pd, pwdbuffer, sizeof pwdbuffer, &tempPwdPtr);
-#endif // SOLARIS_GETPWUID
-#else // HAVE_GETPWNAM_R
-               syslog(LOG_DEBUG, "user_ops: SHOULD NEVER GET HERE!!!");
-               tempPwdPtr = NULL;
-#endif // HAVE_GETPWNAM_R
-               if (tempPwdPtr == NULL) {
-                       syslog(LOG_DEBUG, "user_ops: no such user <%s>", username);
-                       return login_not_found;
-               }
-       
-               // Locate the associated Citadel account.  If not found, make one attempt to create it.
-               found_user = getuserbyuid(&CC->user, pd.pw_uid);
-               if (found_user != 0) {
-                       create_user(username, CREATE_USER_DO_NOT_BECOME_USER, pd.pw_uid);
-                       found_user = getuserbyuid(&CC->user, pd.pw_uid);
-               }
-               syslog(LOG_DEBUG, "user_ops: found it: uid=%ld, gecos=%s here: %d", (long)pd.pw_uid, pd.pw_gecos, found_user);
-
-       }
-
-       // LDAP auth mode...
-       else if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
-
-               uid_t ldap_uid;
-               char ldap_cn[256];
-               char ldap_dn[256];
-
-               found_user = CtdlTryUserLDAP(username, ldap_dn, sizeof ldap_dn, ldap_cn, sizeof ldap_cn, &ldap_uid);
-               if (found_user != 0) {
-                       return login_not_found;
-               }
-
-               found_user = getuserbyuid(&CC->user, ldap_uid);
-               if (found_user != 0) {
-                       create_user(ldap_cn, CREATE_USER_DO_NOT_BECOME_USER, ldap_uid);
-                       found_user = getuserbyuid(&CC->user, ldap_uid);
-               }
-
-               if (found_user == 0) {
-                       if (CC->ldap_dn != NULL) free(CC->ldap_dn);
-                       CC->ldap_dn = strdup(ldap_dn);
-               }
-
-       }
-
-       // native auth mode...
-       else {
-               struct recptypes *valid = NULL;
-       
-               // First, try to log in as if the supplied name is a display name
-               found_user = CtdlGetUser(&CC->user, username);
-       
-               // If that didn't work, try to log in as if the supplied name * is an e-mail address
-               if (found_user != 0) {
-                       valid = validate_recipients(username, NULL, 0);
-                       if (valid != NULL) {
-                               if (valid->num_local == 1) {
-                                       found_user = CtdlGetUser(&CC->user, valid->recp_local);
-                               }
-                               free_recipients(valid);
-                       }
-               }
-       }
-
-       // Did we find something?
-       if (found_user == 0) {
-               if (((CC->nologin)) && (CC->user.axlevel < AxAideU)) {
-                       return login_too_many_users;
-               }
-               else {
-                       safestrncpy(CC->curr_user, CC->user.fullname, sizeof CC->curr_user);
-                       return login_ok;
-               }
-       }
-       return login_not_found;
-}
-
-
-// session startup code which is common to both cmd_pass() and cmd_newu()
-void do_login(void) {
-       CC->logged_in = 1;
-       syslog(LOG_NOTICE, "user_ops: <%s> logged in", CC->curr_user);
-
-       CtdlGetUserLock(&CC->user, CC->curr_user);
-       ++(CC->user.timescalled);
-       CC->previous_login = CC->user.lastcall;
-       time(&CC->user.lastcall);
-
-       // If this user's name is the name of the system administrator
-       // (as specified in setup), automatically assign access level 6.
-       if ( (!IsEmptyStr(CtdlGetConfigStr("c_sysadm"))) && (!strcasecmp(CC->user.fullname, CtdlGetConfigStr("c_sysadm"))) ) {
-               CC->user.axlevel = AxAideU;
-       }
-
-       // If we're authenticating off the host system, automatically give root the highest level of access.
-       if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
-               if (CC->user.uid == 0) {
-                       CC->user.axlevel = AxAideU;
-               }
-       }
-       CtdlPutUserLock(&CC->user);
-
-       // If we are using LDAP authentication, extract the user's email addresses from the directory.
-       if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
-               char new_emailaddrs[512];
-               if (CtdlGetConfigInt("c_ldap_sync_email_addrs") > 0) {
-                       if (extract_email_addresses_from_ldap(CC->ldap_dn, new_emailaddrs) == 0) {
-                               CtdlSetEmailAddressesForUser(CC->user.fullname, new_emailaddrs);
-                       }
-               }
-       }
-
-       // If the user does not have any email addresses assigned, generate one.
-       if (IsEmptyStr(CC->user.emailaddrs)) {
-               AutoGenerateEmailAddressForUser(&CC->user);
-       }
-
-       // Populate the user principal identity, which is consistent and never aliased
-       strcpy(CC->cs_principal_id, "");
-       makeuserkey(CC->cs_principal_id, CC->user.fullname);
-       strcat(CC->cs_principal_id, "@");
-       strcat(CC->cs_principal_id, CtdlGetConfigStr("c_fqdn"));
-
-       // Populate cs_inet_email and cs_inet_other_emails with valid email addresses from the user record
-       strcpy(CC->cs_inet_email, CC->user.emailaddrs);
-       char *firstsep = strstr(CC->cs_inet_email, "|");
-       if (firstsep) {
-               strcpy(CC->cs_inet_other_emails, firstsep+1);
-               *firstsep = 0;
-       }
-       else {
-               CC->cs_inet_other_emails[0] = 0;
-       }
-
-       // Create any personal rooms required by the system.
-       // (Technically, MAILROOM should be there already, but just in case...)
-       CtdlCreateRoom(MAILROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
-       CtdlCreateRoom(SENTITEMS, 4, "", 0, 1, 0, VIEW_MAILBOX);
-       CtdlCreateRoom(USERTRASHROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
-       CtdlCreateRoom(USERDRAFTROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
-
-       // Run any startup routines registered by loadable modules
-       PerformSessionHooks(EVT_LOGIN);
-
-       // Enter the lobby
-       CtdlUserGoto(CtdlGetConfigStr("c_baseroom"), 0, 0, NULL, NULL, NULL, NULL);
-}
-
-
-void logged_in_response(void) {
-       cprintf("%d %s|%d|%ld|%ld|%u|%ld|%ld\n",
-               CIT_OK, CC->user.fullname, CC->user.axlevel,
-               CC->user.timescalled, CC->user.posted,
-               CC->user.flags, CC->user.usernum,
-               CC->previous_login
-       );
-}
-
-
-void CtdlUserLogout(void) {
-
-       syslog(LOG_DEBUG, "user_ops: CtdlUserLogout() logging out <%s> from session %d", CC->curr_user, CC->cs_pid);
-
-       // Run any hooks registered by modules...
-       PerformSessionHooks(EVT_LOGOUT);
-       
-       // Clear out some session data.  Most likely, the CitContext for this
-       // session is about to get nuked when the session disconnects, but
-       // since it's possible to log in again without reconnecting, we cannot
-       // make that assumption.
-       CC->logged_in = 0;
-
-       // Check to see if the user was deleted while logged in and purge them if necessary
-       if ((CC->user.axlevel == AxDeleted) && (CC->user.usernum)) {
-               purge_user(CC->user.fullname);
-       }
-
-       // Clear out the user record in memory so we don't behave like a ghost
-       memset(&CC->user, 0, sizeof(struct ctdluser));
-       CC->curr_user[0] = 0;
-       CC->cs_inet_email[0] = 0;
-       CC->cs_inet_other_emails[0] = 0;
-       CC->cs_inet_fn[0] = 0;
-
-       // Free any output buffers
-       unbuffer_output();
-}
-
-
-// Validate a password on the host unix system by talking to the chkpwd daemon
-static int validpw(uid_t uid, const char *pass) {
-       char buf[256];
-       int rv = 0;
-
-       if (IsEmptyStr(pass)) {
-               syslog(LOG_DEBUG, "user_ops: refusing to chkpwd for uid=%d with empty password", uid);
-               return 0;
-       }
-
-       syslog(LOG_DEBUG, "user_ops: validating password for uid=%d using chkpwd...", uid);
-
-       begin_critical_section(S_CHKPWD);
-       rv = write(chkpwd_write_pipe[1], &uid, sizeof(uid_t));
-       if (rv == -1) {
-               syslog(LOG_ERR, "user_ops: communication with chkpwd broken: %m");
-               end_critical_section(S_CHKPWD);
-               return 0;
-       }
-       rv = write(chkpwd_write_pipe[1], pass, 256);
-       if (rv == -1) {
-               syslog(LOG_ERR, "user_ops: communication with chkpwd broken: %m");
-               end_critical_section(S_CHKPWD);
-               return 0;
-       }
-       rv = read(chkpwd_read_pipe[0], buf, 4);
-       if (rv == -1) {
-               syslog(LOG_ERR, "user_ops: ommunication with chkpwd broken: %m");
-               end_critical_section(S_CHKPWD);
-               return 0;
-       }
-       end_critical_section(S_CHKPWD);
-
-       if (!strncmp(buf, "PASS", 4)) {
-               syslog(LOG_DEBUG, "user_ops: chkpwd pass");
-               return(1);
-       }
-
-       syslog(LOG_DEBUG, "user_ops: chkpwd fail");
-       return 0;
-}
-
-
-// Start up the chkpwd daemon so validpw() has something to talk to
-void start_chkpwd_daemon(void) {
-       pid_t chkpwd_pid;
-       struct stat filestats;
-       int i;
-
-       syslog(LOG_DEBUG, "user_ops: starting chkpwd daemon for host authentication mode");
-
-       if ((stat(file_chkpwd, &filestats)==-1) || (filestats.st_size==0)) {
-               syslog(LOG_ERR, "user_ops: %s: %m", file_chkpwd);
-               abort();
-       }
-       if (pipe(chkpwd_write_pipe) != 0) {
-               syslog(LOG_ERR, "user_ops: unable to create pipe for chkpwd daemon: %m");
-               abort();
-       }
-       if (pipe(chkpwd_read_pipe) != 0) {
-               syslog(LOG_ERR, "user_ops: unable to create pipe for chkpwd daemon: %m");
-               abort();
-       }
-
-       chkpwd_pid = fork();
-       if (chkpwd_pid < 0) {
-               syslog(LOG_ERR, "user_ops: unable to fork chkpwd daemon: %m");
-               abort();
-       }
-       if (chkpwd_pid == 0) {
-               dup2(chkpwd_write_pipe[0], 0);
-               dup2(chkpwd_read_pipe[1], 1);
-               for (i=2; i<256; ++i) close(i);
-               execl(file_chkpwd, file_chkpwd, NULL);
-               syslog(LOG_ERR, "user_ops: unable to exec chkpwd daemon: %m");
-               abort();
-               exit(errno);
-       }
-}
-
-
-int CtdlTryPassword(const char *password, long len) {
-       int code;
-
-       if ((CC->logged_in)) {
-               syslog(LOG_WARNING, "user_ops: CtdlTryPassword: already logged in");
-               return pass_already_logged_in;
-       }
-       if (!strcmp(CC->curr_user, NLI)) {
-               syslog(LOG_WARNING, "user_ops: CtdlTryPassword: no user selected");
-               return pass_no_user;
-       }
-       if (CtdlGetUser(&CC->user, CC->curr_user)) {
-               syslog(LOG_ERR, "user_ops: CtdlTryPassword: internal error");
-               return pass_internal_error;
-       }
-       if (password == NULL) {
-               syslog(LOG_INFO, "user_ops: CtdlTryPassword: NULL password string supplied");
-               return pass_wrong_password;
-       }
-
-       // host auth mode...
-       else if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
-               if (validpw(CC->user.uid, password)) {
-                       code = 0;
-
-                       // sooper-seekrit hack: populate the password field in the
-                       // citadel database with the password that the user typed,
-                       // if it's correct.  This allows most sites to convert from
-                       // host auth to native auth if they want to.  If you think
-                       // this is a security hazard, comment it out.
-
-                       CtdlGetUserLock(&CC->user, CC->curr_user);
-                       safestrncpy(CC->user.password, password, sizeof CC->user.password);
-                       CtdlPutUserLock(&CC->user);
-
-                       // (sooper-seekrit hack ends here)
-               }
-               else {
-                       code = (-1);
-               }
-       }
-
-       // LDAP auth mode...
-       else if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
-
-               if ((CC->ldap_dn) && (!CtdlTryPasswordLDAP(CC->ldap_dn, password))) {
-                       code = 0;
-               }
-               else {
-                       code = (-1);
-               }
-       }
-
-       // native auth mode...
-       else {
-               char *pw;
-
-               pw = (char*) malloc(len + 1);
-               memcpy(pw, password, len + 1);
-               strproc(pw);
-               strproc(CC->user.password);
-               code = strcasecmp(CC->user.password, pw);
-               if (code != 0) {
-                       strproc(pw);
-                       strproc(CC->user.password);
-                       code = strcasecmp(CC->user.password, pw);
-               }
-               free (pw);
-       }
-
-       if (!code) {
-               do_login();
-               return pass_ok;
-       }
-       else {
-               syslog(LOG_WARNING, "user_ops: bad password specified for <%s> Service <%s> Port <%ld> Remote <%s / %s>",
-                       CC->curr_user,
-                       CC->ServiceName,
-                       CC->tcp_port,
-                       CC->cs_host,
-                       CC->cs_addr
-               );
-               return pass_wrong_password;
-       }
-}
-
-
-// Delete a user record *and* all of its related resources.
-int purge_user(char pname[]) {
-       struct ctdluser usbuf;
-       char usernamekey[USERNAME_SIZE];
-
-       makeuserkey(usernamekey, pname);
-
-       // If the name is empty we can't find them in the DB any way so just return
-       if (IsEmptyStr(pname)) {
-               return(ERROR + NO_SUCH_USER);
-       }
-
-       if (CtdlGetUser(&usbuf, pname) != 0) {
-               syslog(LOG_ERR, "user_ops: cannot purge user <%s> - not found", pname);
-               return(ERROR + NO_SUCH_USER);
-       }
-
-       // Don't delete a user who is currently logged in.  Instead, just
-       // set the access level to 0, and let the account get swept up
-       // during the next purge.
-       if (CtdlIsUserLoggedInByNum(usbuf.usernum)) {
-               syslog(LOG_WARNING, "user_ops: <%s> is logged in; not deleting", pname);
-               usbuf.axlevel = AxDeleted;
-               CtdlPutUser(&usbuf);
-               return(1);
-       }
-
-       syslog(LOG_NOTICE, "user_ops: deleting <%s>", pname);
-
-       // Perform any purge functions registered by server extensions
-       PerformUserHooks(&usbuf, EVT_PURGEUSER);
-
-       // delete any existing user/room relationships
-       cdb_delete(CDB_VISIT, &usbuf.usernum, sizeof(long));
-
-       // delete the users-by-number index record
-       cdb_delete(CDB_USERSBYNUMBER, &usbuf.usernum, sizeof(long));
-
-       // delete the user entry
-       cdb_delete(CDB_USERS, usernamekey, strlen(usernamekey));
-
-       return(0);
-}
-
-
-// This is the back end processing that happens when we create a new user account.
-int internal_create_user(char *username, struct ctdluser *usbuf, uid_t uid) {
-       if (!CtdlGetUser(usbuf, username)) {
-               return(ERROR + ALREADY_EXISTS);
-       }
-
-       // Go ahead and initialize a new user record
-       memset(usbuf, 0, sizeof(struct ctdluser));
-       safestrncpy(usbuf->fullname, username, sizeof usbuf->fullname);
-       strcpy(usbuf->password, "");
-       usbuf->uid = uid;
-
-       // These are the default flags on new accounts
-       usbuf->flags = US_LASTOLD | US_DISAPPEAR | US_PAGINATOR | US_FLOORS;
-
-       usbuf->timescalled = 0;
-       usbuf->posted = 0;
-       usbuf->axlevel = CtdlGetConfigInt("c_initax");
-       usbuf->lastcall = time(NULL);
-
-       // fetch a new user number
-       usbuf->usernum = get_new_user_number();
-
-       // add user to the database
-       CtdlPutUser(usbuf);
-       cdb_store(CDB_USERSBYNUMBER, &usbuf->usernum, sizeof(long), usbuf->fullname, strlen(usbuf->fullname)+1);
-
-       // If non-native auth, index by uid
-       if ((usbuf->uid > 0) && (usbuf->uid != NATIVE_AUTH_UID)) {
-               StrBuf *claimed_id = NewStrBuf();
-               StrBufPrintf(claimed_id, "uid:%d", usbuf->uid);
-               attach_extauth(usbuf, claimed_id);
-               FreeStrBuf(&claimed_id);
-       }
-
-       return(0);
-}
-
-
-// create_user()  -  back end processing to create a new user
-//
-// Set 'newusername' to the desired account name.
-// Set 'become_user' to CREATE_USER_BECOME_USER if this is self-service account creation and we want to
-//                   actually log in as the user we just created, otherwise set it to CREATE_USER_DO_NOT_BECOME_USER
-// Set 'uid' to some uid_t value to associate the account with an external auth user, or (-1) for native auth
-int create_user(char *username, int become_user, uid_t uid) {
-       struct ctdluser usbuf;
-       struct ctdlroom qrbuf;
-       char mailboxname[ROOMNAMELEN];
-       char buf[SIZ];
-       int retval;
-
-       strproc(username);
-       if ((retval = internal_create_user(username, &usbuf, uid)) != 0) {
-               return retval;
-       }
-
-       // Give the user a private mailbox and a configuration room.
-       // Make the latter an invisible system room.
-       CtdlMailboxName(mailboxname, sizeof mailboxname, &usbuf, MAILROOM);
-       CtdlCreateRoom(mailboxname, 5, "", 0, 1, 1, VIEW_MAILBOX);
-
-       CtdlMailboxName(mailboxname, sizeof mailboxname, &usbuf, USERCONFIGROOM);
-       CtdlCreateRoom(mailboxname, 5, "", 0, 1, 1, VIEW_BBS);
-       if (CtdlGetRoomLock(&qrbuf, mailboxname) == 0) {
-               qrbuf.QRflags2 |= QR2_SYSTEM;
-               CtdlPutRoomLock(&qrbuf);
-       }
-
-       // Perform any create functions registered by server extensions
-       PerformUserHooks(&usbuf, EVT_NEWUSER);
-
-       // Everything below this line can be bypassed if administratively
-       // creating a user, instead of doing self-service account creation
-
-       if (become_user == CREATE_USER_BECOME_USER) {
-               // Now become the user we just created
-               memcpy(&CC->user, &usbuf, sizeof(struct ctdluser));
-               safestrncpy(CC->curr_user, username, sizeof CC->curr_user);
-               do_login();
-       
-               // Check to make sure we're still who we think we are
-               if (CtdlGetUser(&CC->user, CC->curr_user)) {
-                       return(ERROR + INTERNAL_ERROR);
-               }
-       }
-       
-       snprintf(buf, SIZ, 
-               "New user account <%s> has been created, from host %s [%s].\n",
-               username,
-               CC->cs_host,
-               CC->cs_addr
-       );
-       CtdlAideMessage(buf, "User Creation Notice");
-       syslog(LOG_NOTICE, "user_ops: <%s> created", username);
-       return(0);
-}
-
-
-// set password - back end api code
-void CtdlSetPassword(char *new_pw) {
-       CtdlGetUserLock(&CC->user, CC->curr_user);
-       safestrncpy(CC->user.password, new_pw, sizeof(CC->user.password));
-       CtdlPutUserLock(&CC->user);
-       syslog(LOG_INFO, "user_ops: password changed for <%s>", CC->curr_user);
-       PerformSessionHooks(EVT_SETPASS);
-}
-
-
-// API function for cmd_invt_kick() and anything else that needs to
-// invite or kick out a user to/from a room.
-// 
-// Set iuser to the name of the user, and op to 1=invite or 0=kick
-int CtdlInvtKick(char *iuser, int op) {
-       struct ctdluser USscratch;
-       visit vbuf;
-       char bbb[SIZ];
-
-       if (CtdlGetUser(&USscratch, iuser) != 0) {
-               return(1);
-       }
-
-       CtdlGetRelationship(&vbuf, &USscratch, &CC->room);
-       if (op == 1) {
-               vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
-               vbuf.v_flags = vbuf.v_flags | V_ACCESS;
-       }
-       if (op == 0) {
-               vbuf.v_flags = vbuf.v_flags & ~V_ACCESS;
-               vbuf.v_flags = vbuf.v_flags | V_FORGET | V_LOCKOUT;
-       }
-       CtdlSetRelationship(&vbuf, &USscratch, &CC->room);
-
-       // post a message in Aide> saying what we just did
-       snprintf(bbb, sizeof bbb, "%s has been %s \"%s\" by %s.\n",
-               iuser,
-               ((op == 1) ? "invited to" : "kicked out of"),
-               CC->room.QRname,
-               (CC->logged_in ? CC->user.fullname : "an administrator")
-       );
-       CtdlAideMessage(bbb,"User Admin Message");
-       return(0);
-}
-
-
-// Forget (Zap) the current room (API call)
-// Returns 0 on success
-int CtdlForgetThisRoom(void) {
-       visit vbuf;
-
-       // On some systems, Admins are not allowed to forget rooms
-       if (is_aide() && (CtdlGetConfigInt("c_aide_zap") == 0)
-          && ((CC->room.QRflags & QR_MAILBOX) == 0)  ) {
-               return(1);
-       }
-
-       CtdlGetUserLock(&CC->user, CC->curr_user);
-       CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
-
-       vbuf.v_flags = vbuf.v_flags | V_FORGET;
-       vbuf.v_flags = vbuf.v_flags & ~V_ACCESS;
-
-       CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
-       CtdlPutUserLock(&CC->user);
-
-       // Return to the Lobby, so we don't end up in an undefined room
-       CtdlUserGoto(CtdlGetConfigStr("c_baseroom"), 0, 0, NULL, NULL, NULL, NULL);
-       return(0);
-}
-
-
-// Traverse the user file and perform a callback for each user record.
-// (New improved version that runs in two phases so that callbacks can perform writes without having a r/o cursor open)
-void ForEachUser(void (*CallBack) (char *, void *out_data), void *in_data) {
-       struct cdbdata *cdbus;
-       struct ctdluser *usptr;
-
-       struct feu {
-               struct feu *next;
-               char username[USERNAME_SIZE];
-       };
-       struct feu *ufirst = NULL;
-       struct feu *ulast = NULL;
-       struct feu *f = NULL;
-
-       cdb_rewind(CDB_USERS);
-
-       // Phase 1 : build a linked list of all our user account names
-       while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
-               usptr = (struct ctdluser *) cdbus->ptr;
-
-               if (strlen(usptr->fullname) > 0) {
-                       f = malloc(sizeof(struct feu));
-                       f->next = NULL;
-                       strncpy(f->username, usptr->fullname, USERNAME_SIZE);
-
-                       if (ufirst == NULL) {
-                               ufirst = f;
-                               ulast = f;
-                       }
-                       else {
-                               ulast->next = f;
-                               ulast = f;
-                       }
-               }
-       }
-
-       // Phase 2 : perform the callback for each user while de-allocating the list
-       while (ufirst != NULL) {
-               (*CallBack) (ufirst->username, in_data);
-               f = ufirst;
-               ufirst = ufirst->next;
-               free(f);
-       }
-}
-
-
-// Count the number of new mail messages the user has
-int NewMailCount() {
-       int num_newmsgs = 0;
-       num_newmsgs = CC->newmail;
-       CC->newmail = 0;
-       return(num_newmsgs);
-}
-
-
-// Count the number of new mail messages the user has
-int InitialMailCheck() {
-       int num_newmsgs = 0;
-       int a;
-       char mailboxname[ROOMNAMELEN];
-       struct ctdlroom mailbox;
-       visit vbuf;
-       struct cdbdata *cdbfr;
-       long *msglist = NULL;
-       int num_msgs = 0;
-
-       CtdlMailboxName(mailboxname, sizeof mailboxname, &CC->user, MAILROOM);
-       if (CtdlGetRoom(&mailbox, mailboxname) != 0)
-               return(0);
-       CtdlGetRelationship(&vbuf, &CC->user, &mailbox);
-
-       cdbfr = cdb_fetch(CDB_MSGLISTS, &mailbox.QRnumber, sizeof(long));
-
-       if (cdbfr != NULL) {
-               msglist = malloc(cdbfr->len);
-               memcpy(msglist, cdbfr->ptr, cdbfr->len);
-               num_msgs = cdbfr->len / sizeof(long);
-               cdb_free(cdbfr);
-       }
-       if (num_msgs > 0)
-               for (a = 0; a < num_msgs; ++a) {
-                       if (msglist[a] > 0L) {
-                               if (msglist[a] > vbuf.v_lastseen) {
-                                       ++num_newmsgs;
-                               }
-                       }
-               }
-       if (msglist != NULL)
-               free(msglist);
-
-       return(num_newmsgs);
-}
diff --git a/citadel/user_ops.h b/citadel/user_ops.h
deleted file mode 100644 (file)
index 1a1ba66..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-// Header file for server functions which perform operations on user objects.
-//
-// Copyright (c) 1987-2022 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-#ifndef __USER_OPS_H__
-#define __USER_OPS_H__
-
-#include <ctype.h>
-#include <syslog.h>
-
-int hash (char *str);
-int is_aide (void);
-int is_room_aide (void);
-int CtdlCheckInternetMailPermission(struct ctdluser *who);
-void rebuild_usersbynumber(void);
-void session_startup (void);
-void logged_in_response(void);
-int purge_user (char *pname);
-int getuserbyuid(struct ctdluser *usbuf, uid_t number);
-
-int create_user(char *newusername, int become_user, uid_t uid);
-enum {
-       CREATE_USER_DO_NOT_BECOME_USER,
-       CREATE_USER_BECOME_USER
-};
-#define NATIVE_AUTH_UID (-1)
-
-void do_login(void);
-int CtdlInvtKick(char *iuser, int op);
-void ForEachUser(void (*CallBack) (char *, void *out_data), void *in_data);
-int NewMailCount(void);
-int InitialMailCheck(void);
-void put_visit(visit *newvisit);
-int GenerateRelationshipIndex(char *IndexBuf, long RoomID, long RoomGen, long UserID);
-int CtdlAssociateSystemUser(char *screenname, char *loginname);
-
-void CtdlSetPassword(char *new_pw);
-
-int CtdlForgetThisRoom(void);
-
-void cmd_newu (char *cmdbuf);
-void start_chkpwd_daemon(void);
-
-
-#define RENAMEUSER_OK                  0       // Operation succeeded
-#define RENAMEUSER_LOGGED_IN           1       // Cannot rename a user who is currently logged in
-#define RENAMEUSER_NOT_FOUND           2       // The old user name does not exist
-#define RENAMEUSER_ALREADY_EXISTS      3       // An account with the desired new name already exists
-
-int rename_user(char *oldname, char *newname);
-void reindex_user_928(char *username, void *out_data);
-
-void makeuserkey(char *key, const char *username);
-int CtdlUserCmp(char *s1, char *s2);
-int internal_create_user(char *username, struct ctdluser *usbuf, uid_t uid);
-
-#endif