diff -ruN qpopper4.0.18/config.h.in qpopper4.0.18-mysql-0.16/config.h.in --- qpopper4.0.18/config.h.in 2009-07-12 18:50:41.000000000 -0600 +++ qpopper4.0.18-mysql-0.16/config.h.in 2009-07-23 16:47:33.787790090 -0600 @@ -328,12 +328,26 @@ /* Define to enable SCRAM */ #undef SCRAM +/* Define to enable MYSQL_CONF */ +#undef MYSQL_CONF + /* Define the POPUID as pop user ID which acts as admin for POPUSERS */ #undef POPUID /* Define if you want successful authentications to be logged */ #undef LOG_LOGIN +/* + * Use mysql database authentication + */ +#undef MYSQLAUTH + +/* + * Define if you want successful authentications to be + * logged to a mysql database + */ +#undef LOG_LOGIN_MYSQL + /* * Define if you want to automatically delete RETRd messages. * Caution: This could cause lost mail. Be sure users are @@ -640,4 +654,20 @@ */ #undef USE_TEMPNAM +/* + * Define if the user new, cur, tmp maildir directories already exist. + * Qpopper will not check for or create the directories when this is defined. + */ +#undef DONT_CHECK_MAILDIR_DIR + +#define MBOX_DROP 0 +#define MAILDIR_DROP 1 +/* + * Define to set the type of maildrop we will use. + * MBOX_DROP is mbox type + * MAILDIR_DROP is Maildir type + * default is MBOX_DROP + */ +#undef MAILDROP_TYPE + #include "conf.h" /* some macros */ diff -ruN qpopper4.0.18/configure qpopper4.0.18-mysql-0.16/configure --- qpopper4.0.18/configure 2009-07-13 15:20:36.000000000 -0600 +++ qpopper4.0.18-mysql-0.16/configure 2009-07-23 17:00:20.534800454 -0600 @@ -1280,6 +1280,8 @@ --enable-new-bulls=count Specify the maximum number of bulletins for new users 10 --enable-popbulldir=path Specify alternate directory for popbull files --enable-specialauth Enable secure crypt or shadow passwords + --enable-mysql Compile in mysql authentication + --enable-log-login-mysql Log successful user authentications into mysql database --enable-apop=path Set the pop.auth file path /etc/pop.auth --enable-scram=path Include scram capability with AUTHDB file /etc/pop.auth --enable-popuid=pop Set the owner of the pop.auth file. pop @@ -1288,6 +1290,8 @@ --enable-shy Hide qpopper version number --enable-warnings Enable additional compiler warnings --enable-hash-spool=1|2 Enable hashed spool directories 2 + --enable-maildrop-type=mbox|maildir + Set the mailbox type we read from [mbox] --enable-home-dir-mail=spool file Mail spool file is in home directory .mail --enable-home-dir-misc .pop and .cache files are in home directory @@ -1322,6 +1326,8 @@ --disable-update-abort Don't enter UPDATE state on abort --enable-fast-update Reduce I/O during server-mode updates --disable-hash-dir-check Don't check if hashed spool dirs exist + --disable-maildir-dir-check + Don't check if maildir dirs exist --enable-chunky-writes=0|1|2 Set default network write pooling 0 --enable-poppassd Generate poppassd password-change daemon @@ -1341,6 +1347,10 @@ Crypto library to use with SSL Plus securitybuilder --with-openssl=path Use OpenSSL /usr/local/ssl --with-gdbm=path Use GDBM + --with-mysqllibpath=path Set the mysql library path [/usr/lib/mysql] + --with-mysqlincludepath=path + Set the mysql include path [/usr/include/mysql] + --with-mysqlconfig=path Set the mysql-popper.conf file path [/etc/mysql-popper.conf] Some influential environment variables: CC C compiler command @@ -3947,6 +3957,187 @@ CFLAGS=`echo "$CFLAGS" | sed 's/-O2//'` fi +# Check whether --with-mysqllibpath or --without-mysqllibpath was given. +if test "${with_mysqllibpath+set}" = set; then + withval="$with_mysqllibpath" + mysqllibpath=$withval +else + mysqllibpath="/usr/lib/mysql" +fi + +# Check whether --with-mysqlincludepath or --without-mysqlincludepath was given. +if test "${with_mysqlincludepath+set}" = set; then + withval="$with_mysqlincludepath" + mysqlincludepath=$withval +else + mysqlincludepath="/usr/include/mysql" +fi + + +# Check whether --enable-mysql or --disable-mysql was given. +if test "${enable_mysql+set}" = set; then + enableval="$enable_mysql" + mysql="$enableval" +else + mysql="no" +fi + +if test "$mysql" != "no"; then + + OS_DEFS="$OS_DEFS -I$mysqlincludepath" + LDFLAGS="$LDFLAGS -L$mysqllibpath" + echo $ECHO_N "checking for mysql_real_connect in -lmysqlclient""... $ECHO_C" 1>&6 +echo "$as_me:$LINENO: checking for mysql_real_connect in -lmysqlclient" >&5 +ac_lib_var=`echo mysqlclient'_'mysql_real_connect | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ECHO_N "(cached) $ECHO_C" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lmysqlclient "-lm" $LIBS" +cat >conftest.$ac_ext <<_ACEOF +#line $LINENO "configure" +#include "confdefs.h" +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char mysql_real_connect(); + +int main() { +mysql_real_connect() +; return 0; } +_ACEOF +if { (eval echo $as_me:$LINENO: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "$as_me: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "${ECHO_T}""yes" 1>&6 + LIBS="$LIBS -L$mysqllibpath -lmysqlclient" +else + echo "${ECHO_T}""no" 1>&6 +{ echo "$as_me:$LINENO: result: "libmysqlclient not found"" >&5; + echo "Try specifying your mysql lib path with --with-mysqllibpath=path" >&6; + exit 1; } +fi + + echo $ECHO_N "checking for sin in -lm""... $ECHO_C" 1>&6 +echo "$as_me:$LINENO: checking for sin in -lm" >&5 +ac_lib_var=`echo m'_'sin | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ECHO_N "(cached) $ECHO_C" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lm $LIBS" +cat >conftest.$ac_ext <<_ACEOF +#line $LINENO "configure" +#include "confdefs.h" +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char sin(); + +int main() { +sin() +; return 0; } +_ACEOF +if { (eval echo $as_me:$LINENO: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "$as_me: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo m | sed -e 's/[^a-zA-Z0-9_]/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <<_ACEOF +#define $ac_tr_lib 1 +_ACEOF + + LIBS="-lm $LIBS" + +else + echo "$ac_t""no" 1>&6 +fi + +# end of enable-mysql checks +fi + +# Check whether --with-mysqlconfig or --without-mysqlconfig was given. +if test "${with_mysqlconfig+set}" = set; then + withval="$with_mysqlconfig" + mysqlconfig=$withval +else + mysqlconfig="no" +fi + +if test "$mysqlconfig" != "no"; then + cat >>confdefs.h <<_ACEOF +#define MYSQL_CONF "$mysqlconfig" +_ACEOF + +else + cat >>confdefs.h <<_ACEOF +#define MYSQL_CONF "/etc/mysql-popper.conf" +_ACEOF + +fi + + + +# Check whether --enable-log-login-mysql or --disable-log-login-mysql was given. +if test "${enable_log_login_mysql+set}" = set; then + enableval="$enable_log_login_mysql" + logloginmysqlmode="$enableval" +else + logloginmysqlmode="no" +fi + +if test "$logloginmysqlmode" != "no"; then + echo "$ac_t""Enabled LOG-LOGIN-MYSQL" 1>&6 + cat >>confdefs.h <<\_ACEOF +#define LOG_LOGIN_MYSQL 1 +_ACEOF + +fi + + +# Check whether --enable-maildrop-type was given. +if test "${enable_maildrop_type+set}" = set; then + enableval="$enable_maildrop_type" + maildroptype="$enableval" +else + maildroptype="mbox" +fi + +if test "$maildroptype" = "maildir"; then + echo "$ac_t""Enabled Maildir mailbox type" 1>&6 + cat >>confdefs.h <<\_ACEOF +#define MAILDROP_TYPE MAILDIR_DROP +_ACEOF +else + echo "$ac_t""Enabled mbox mailbox type" 1>&6 + cat >>confdefs.h <<\_ACEOF +#define MAILDROP_TYPE MBOX_DROP +_ACEOF +fi + # Check whether --enable-servermode was given. if test "${enable_servermode+set}" = set; then @@ -5171,6 +5362,23 @@ fi +# Check whether --enable-maildir-dir-check or --disable-maildir-dir-check was given. +if test "${enable_maildir_dir_check+set}" = set; then + enableval="$enable_maildir_dir_check" + checkmaildirdir="$enableval" +else + checkmaildirdir="yes" +fi + +if test "$checkmaildirdir" != "yes"; then + echo "$ac_t""Will NOT check for or create maildir spool directories" 1>&6 + cat >>confdefs.h <<\_ACEOF +#define DONT_CHECK_MAILDIR_DIR 1 +_ACEOF + +fi + + # Check whether --enable-chunky-writes was given. if test "${enable_chunky_writes+set}" = set; then enableval=$enable_chunky_writes; chunky_writes="$enableval" @@ -7473,7 +7681,6 @@ GDBM_H="no" fi - if test "${ac_cv_header_dbm_h+set}" = set; then { echo "$as_me:$LINENO: checking for dbm.h" >&5 echo $ECHO_N "checking for dbm.h... $ECHO_C" >&6; } @@ -7608,9 +7815,64 @@ DBM_H="no" fi +# check for mysql.h + +if test "$mysql" != "no"; then + +ac_hdr="$mysqlincludepath/mysql.h" +ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'` +echo $ECHO_N "checking for $ac_hdr... $ECHO_C" 1>&6 +echo "$as_me:$LINENO: checking for $ac_hdr" >&5 +if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then + echo $ECHO_N "(cached) $ECHO_C" 1>&6 +else + cat >conftest.$ac_ext <<_ACEOF +#line $LINENO "configure" +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include <$ac_hdr> +_ACEOF +rm -f conftest.$ac_objext +if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && + { ac_try='test -s conftest.$ac_objext' + { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 + (eval $ac_try) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + eval "$as_ac_Header=yes" +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 +eval "$as_ac_Header=no" +fi +rm -f conftest.$ac_objext conftest.$ac_ext +fi +echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_Header'}'`" >&5 +echo "${ECHO_T}`eval echo '${'$as_ac_Header'}'`" >&6 +if test `eval echo '${'$as_ac_Header'}'` = yes; then + echo "${ECHO_T}Turning on mysql authentication" >&6 + cat >>confdefs.h <<_ACEOF +#define MYSQLAUTH 1 +_ACEOF +ac_header_dirent=$ac_hdr; break +else + echo "$as_me:$LINENO: result: Can't use MYSQL: can't find mysql.h" >&5; + echo "${ECHO_T}Can't use MYSQL: can't find mysql.h" >&6; + echo "Try specifying your mysql include path with --with-mysqlincludepath=path" >&6;exit 1 +fi +fi +# end check for mysql.h { echo "$as_me:$LINENO: checking for t_accept in -lnsl" >&5 echo $ECHO_N "checking for t_accept in -lnsl... $ECHO_C" >&6; } @@ -9268,6 +9530,8 @@ echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 +rm -f core core.* *.core conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext +rm -f conftest.val ( exit $ac_status ) ac_cv_func_memcmp_working=no fi diff -ruN qpopper4.0.18/configure.in qpopper4.0.18-mysql-0.16/configure.in --- qpopper4.0.18/configure.in 2009-07-13 11:19:09.000000000 -0600 +++ qpopper4.0.18-mysql-0.16/configure.in 2009-07-23 16:47:33.877786720 -0600 @@ -411,6 +411,38 @@ fi +AC_ARG_WITH(mysqllibpath, [ --with-mysqllibpath=path Set the mysql library path [/usr/lib/mysql]], mysqllibpath=$withval, mysqllibpath="/usr/lib/mysql") +AC_ARG_WITH(mysqlincludepath, [ --with-mysqlincludepath=path Set the mysql include path [/usr/include/mysql]], mysqlincludepath=$withval, mysqlincludepath="/usr/include/mysql") + +AC_ARG_ENABLE(mysql, [ --enable-mysql compile in mysql authentication], + mysql="$enableval", mysql="no") +if test "$mysql" != "no"; then + OS_DEFS="$OS_DEFS -I$mysqlincludepath" + LDFLAGS="$LDFLAGS -L$mysqllibpath" + AC_CHECK_LIB(mysqlclient, mysql_connect, + LIBS="$LIBS -L$mysqllibpath -lmysqlclient", + AC_MSG_ERROR("libmysqlclient not found"),"-lm") + AC_CHECK_LIB(m, sin) + AC_MSG_RESULT(Turning on mysql authentication) + AC_DEFINE(MYSQLAUTH) +fi + +AC_ARG_WITH(mysqlconfig, [ --with-mysqlconfig=path Set the mysql-popper.conf file path [/etc/mysql-popper.conf]], mysqlconfig=$withval, mysqlconfig="no") +if test "$mysqlconfig" != "no"; then + AC_DEFINE_UNQUOTED(MYSQL_CONF,"$mysqlconfig") +else + AC_DEFINE_UNQUOTED(MYSQL_CONF,"/etc/mysql-popper.conf") +fi + + +AC_ARG_ENABLE(log-login-mysql, [ --enable-log-login-mysql Log successful user authentications into mysql database ], + logloginmysqlmode="$enableval", logloginmysqlmode="no") +if test "$logloginmysqlmode" != "no"; then + AC_MSG_RESULT(Enabled LOG-LOGIN-MYSQL) + AC_DEFINE(LOG_LOGIN_MYSQL) +fi + + AC_ARG_ENABLE(servermode, [ --enable-servermode Enable SERVER_MODE ], servermode="$enableval", servermode="no") if test "$servermode" != "no"; then @@ -570,6 +602,18 @@ fi +AC_ARG_ENABLE(maildrop-type, [ --enable-maildrop-type=mbox|maildir + Set the mailbox type we read from [mbox] ], + maildroptype="$enableval", maildroptype="mbox") +if test "$maildroptype" = "maildir"; then + AC_MSG_RESULT(Using Maildir mailbox type) + AC_DEFINE_UNQUOTED(MAILDROP_TYPE, "MAILDIR_DROP") +else + AC_MSG_RESULT(Using mbox mailbox type) + AC_DEFINE_UNQUOTED(MAILDROP_TYPE, "MBOX_DROP") +fi + + AC_ARG_ENABLE(hash-spool, [ --enable-hash-spool=1|2 Enable hashed spool directories [2] ], hashspool="$enableval", hashspool="no") if test "$hashspool" != "no"; then @@ -909,6 +953,14 @@ fi +AC_ARG_ENABLE(maildir-dir-check, [ --disable-maildir-dir-check Don't check if maildir spool dirs exist ], + checkmaildirdir="$enableval", checkmaildirdir="yes") +if test "$checkmaildirdir" != "yes"; then + AC_MSG_RESULT(Will NOT check for or create maildir spool directories) + AC_DEFINE(DONT_CHECK_MAILDIR_DIR) +fi + + AC_ARG_ENABLE(chunky-writes, [ --enable-chunky-writes=0|1|2 Set default network write pooling [0] ], chunky_writes="$enableval", chunky_writes="yes") diff -ruN qpopper4.0.18/doc/Changes.MAILDIR qpopper4.0.18-mysql-0.16/doc/Changes.MAILDIR --- qpopper4.0.18/doc/Changes.MAILDIR 1969-12-31 17:00:00.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/doc/Changes.MAILDIR 2009-07-23 17:07:43.162779568 -0600 @@ -0,0 +1,81 @@ + Last updated: 23 Jul 2009 + +Changes from 0.15 to 0.16: +--------------------------- +Updated patch for Qpopper 4.0.18 + +Changes from 0.14 to 0.15: +--------------------------- +Updated patch for Qpopper 4.0.7, 4.0.8, 4.0.9 + +Changes from 0.13 to 0.14: +--------------------------- +None + +Changes from 0.12 to 0.13: +--------------------------- + 1. Fixed a bug where the message list would be empty if we encountered + a message file with a size of 0. We skip the file now. + 2. Introduced functions maildir_get_flags(), maildir_put_flags(), + and maildir_has_flags() to read, set, and check for maildir + status flags in message filenames. This lets the LAST command + work with a Maildir drop, for clients that won't get message + status from UIDLs. + We currently only use and set the 'S' flag, but will keep other + retrieved flags intact. --disable-status will be honored to not + set the 'S' flag. + 3. If we encounter a Maildir/ message with no body, we make sure to reset the + inheader flag, so the next message doesn't show as corrupted. Some MTAs + do not add a blank line (\n) onto their Maildir/ format messages, which + triggers this. + +Changes from 0.11 to 0.12: +--------------------------- + 1. Changed file move in maildir_updt from rename() to link()/unlink() + instead. File copy and rename are now fallbacks, and stat checking + is done for success/failure. This was because Maildir with some + NFS implementations does not like to make a newly rename()d file + visible to all clients. + 2. Introduced maildir_sort function to sort messages of a Maildir/ + style mailbox by modification time (oldest->newest), as this + is how an mbox style mailbox works too. Otherwise, messages would + be sorted by directory order, which may vary. + 3. Added ability to msg_ptr to skip hidden messages that are not the + first one. + 4. Updated README.MAILDIR + +Changes from 0.10 to 0.11: +--------------------------- + 1. Replaced all instances of PATH_MAX variable with hard-coded 256, + as PATH_MAX doesn't get included by all platforms, esp. Solaris, + causing failed compiles. + 2. Updating README.MYSQL nad README.MAILDIR with info on how to + report a problem or bug. + +Changes from 0.9 to 0.10: +--------------------------- + 1. Updated patch to work against qpopper 4.0.5 + +Changes from 0.8 to 0.9: +--------------------------- + 1. Fixed bug in Maildir support, where LISTing a message after + RETRing a message would crash qpopper. Set p->drop to NULL + after all our Maildir fclose()s. + +Changes from 0.7 to 0.8: +--------------------------- + 1. Fixed bug in "LIST x" where x is a msg number, would kill the + process, because of trying to close an invalid file handle. + 2. Changed message listing (LIST) to listing cur/ message entries + before new/ ones. Ideally, should be listed by modification time, + using a sort, but I haven't figured this out yet. + +Changes from 0.6 to 0.7: +--------------------------- + 1. Fixed bug where enabling Maildir support without MySQL support + would crash qpopper. + 2. Added some Maildir ./configure examples in README.MAILDIR + +Changes from 0.4 to 0.5: +--------------------------- + 1. Added preliminary support for Maildir maildrops. diff -ruN qpopper4.0.18/doc/Changes.MYSQL qpopper4.0.18-mysql-0.16/doc/Changes.MYSQL --- qpopper4.0.18/doc/Changes.MYSQL 1969-12-31 17:00:00.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/doc/Changes.MYSQL 2009-07-23 17:07:53.645783675 -0600 @@ -0,0 +1,113 @@ + Last updated: 23 Jul 2009 + +Changes from 0.15 to 0.16: +--------------------------- +Updated patch for Qpopper 4.0.18 + +Changes from 0.14 to 0.15: +--------------------------- +Updated patch for Qpopper 4.0.7, 4.0.8, 4.0.9 + +Changes from 0.13 to 0.14: +--------------------------- + 1. Renamed mysql hash_password function to make_hash_password. Added + bounds checking to the password input field. + +Changes from 0.12 to 0.13: +--------------------------- + 1. Increased the size of the password field in the example mysql table + structure from 32 to 64. + 2. Added a note to the README.MYSQL file, that states the mysql-popper.conf + file should NOT be specified on the command line with -f. + +Changes from 0.11 to 0.12: +--------------------------- + 1. Added a new Mysql field option MysqlLoginHostsField. Can restrict + certain users to certain hosts on multiple pop box systems. See file + mysql-popper.conf for details on use. + 2. Have configure bail if we fail to get size for an unsigned long int. + Previously, configure would finish successfully, and then a make + would fail on macros using that variable. + 3. Changed flow of errored logins, so that even if a mysql error + occurs (such as the user doesn't exist), qpopper still asks for + password, like normal qpopper. This takes away the problem of + account probing. + 4. Fixed some strict compiler warnings. + 5. Updated README.MYSQL + +Changes from 0.10 to 0.11: +--------------------------- + 1. Replaced all instances of PATH_MAX variable with hard-coded 256, + as PATH_MAX doesn't get included by all platforms, esp. Solaris, + causing failed compiles. + 2. Updating README.MYSQL nad README.MAILDIR with info on how to + report a problem or bug. + +Changes from 0.9 to 0.10: +--------------------------- + 1. Updated patch to work against qpopper 4.0.5 + +Changes from 0.5 to 0.6: +--------------------------- + 1. Fixed problem where the configure script would not find + mysql_connect with MySQL 4.x. Changed it to check for + mysql_real_connect instead. + 2. Added support for defining the mail spool per-user in mysql table. + Use optional "MysqlSpoolField" variable in mysql-popper.conf, or + optional "spool-file" variable in user config + Added new p-> struct option p->spool_file to hold this. + See README.MYSQL for more details + 3. Put a check in the configure script for mysql.h + 4. Changed the sample mysql row insert in Changes.MYSQL to show that + you CAN give the domain, uid, gid, status, and spool fields NULL + data values (for future use), but don't expect qpopper not to choke + if you tell it to look for data in these fields, and no data is + present. Except for spool. If you define the spool field and leave + the field's value NULL for a user, the server will generate the spool + path, while it will still use a value if it's not NULL/specified. + 5. Changed the defaults for --with-mysqllibpath and + --with-mysqlincludepath, to /usr/lib/mysql and /usr/include/mysql, + respectively + +Changes from 0.4 to 0.5: +--------------------------- + 1. Added prelim. support for reading from Maildir-style mailboxes + 2. Fixed --with-mysqlincludepath= parsing in configure.in + 3. Gave mysql pop_log()s a POP_PRIORITY level instead of POP_FAILURE + +Changes from 0.3 to 0.4: +--------------------------- + 1. Added ability to do HOMEDIRMAIL with mysql authentication. + "/home" is the default base path for the user's home directory + (since we're not using /etc/passwd). + You can change this by modifying MYSQL_DEFAULT_HOMEDIR in pop_user.c + You can use this with a virtual setup and hashing. + Example directory paths are /home/user, /home/u/s/user, /home/domain.com/ + user, /home/domain.com/u/s/user (see README.MYSQL for all of them) + 2. Changed error message for bad sql fetch to say "Authentication query + failed, account may not exist." instead of saying that the password for + that account is incorrect. + 3. Added "md5" and "mysql" methods for password checking + (for the MysqlAuthPasswordMethod option). Also added "any" method which + will check all methods (since now there are more than 2). The "both" + method can still be used. I left it in for compatibility. + 4. Added hashing and virtual cases for user-opt/spool-opt paths + in pop_config.c + 5. Updated README.MYSQL + +Changes from 0.2 to 0.3: +--------------------------- + 1. Fixed glitch in pop_user.c, where string copied to p->user for MYSQLAUTH + contained domain name, when it shouldn't have. + 2. Fixed compile problem in genpath.c, when qpopper not compiled with + MYSQLAUTH + +Changes from 0.1 to 0.2: +--------------------------- + 1. Added debugging calls for mysql auth. + 2. Added config variables MysqlAuthDomainField and MysqlAuthDefaultDomain + to mysql-popper.conf + 3. Added pre-release virtual domain support. + 4. Updated README.MYSQL + 5. Created Changes.MYSQL + diff -ruN qpopper4.0.18/example-maildir-configure.txt qpopper4.0.18-mysql-0.16/example-maildir-configure.txt --- qpopper4.0.18/example-maildir-configure.txt 1969-12-31 17:00:00.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/example-maildir-configure.txt 2009-07-23 16:47:33.888794760 -0600 @@ -0,0 +1,11 @@ +# For Maildir delivery under the normal system spool +# e.g. /var/mail/user/new, /var/mail/user/cur, etc.. try +./configure --enable-standalone --enable-maildrop-type=maildir \ +--enable-debugging + +# For Maildir delivery under a user's home directory +# e.g. /home/user/Maildir/new, /home/user/Maildir/cur, etc.. try +./configure --enable-standalone --enable-maildrop-type=maildir \ +--enable-debugging --enable-home-dir-mail=Maildir + + diff -ruN qpopper4.0.18/example-mysql-configure.txt qpopper4.0.18-mysql-0.16/example-mysql-configure.txt --- qpopper4.0.18/example-mysql-configure.txt 1969-12-31 17:00:00.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/example-mysql-configure.txt 2009-07-23 16:47:33.890790203 -0600 @@ -0,0 +1,5 @@ +./configure --enable-servermode --enable-shy --enable-specialauth \ +--enable-temp-drop-dir=/var/spool/mail/.pop --disable-check-pw-max \ +--enable-fast-update --prefix=/usr --disable-hash-dir-check \ +--enable-standalone --enable-mysql --enable-log-login-mysql \ +--with-mysqlconfig=/etc/mysql-popper.conf diff -ruN qpopper4.0.18/mysql-popper.conf qpopper4.0.18-mysql-0.16/mysql-popper.conf --- qpopper4.0.18/mysql-popper.conf 1969-12-31 17:00:00.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/mysql-popper.conf 2009-07-23 16:47:33.893787698 -0600 @@ -0,0 +1,97 @@ +# Host where your Mysql server is. Defaults to "localhost" +MysqlAuthHost 127.0.0.1 + +# Port on which Mysql daemon runs. Defaults to "3306" +MysqlAuthPort 3306 + +# Database to auth off. Defaults to "authdb" +MysqlAuthDb authdb + +# Username and password used to connect to Mysql daemon +# REQUIRED +MysqlUsername root +MysqlPassword mypassword +# aliases for the above +#MysqlAuthUsername root +#MysqlAuthPassword mypassword + +# The table we auth off of +# Defaults to "qpopper" +MysqlAuthTable email + +# The method we will use to compare the user-entered +# password against their database password +# "cleartext" if your db passwords are in cleartext +# "crypt" if your db passwords are unix crypted +# "md5" if your db passwords are MD5() encoded +# "mysql" if your db passwords are PASSWORD() encoded (MySQL's method) +# "both" to check both cleartext and crypt +# "any" to check all methods +# Defaults to "cleartext" +MysqlAuthPasswordMethod cleartext + +# Field where user username is +# Defaults to "username" +MysqlAuthUsernameField username + +# VIRTUAL DOMAIN SETUP +# In a virtual setup, the mail spool DIRECTORY will be constructed +# by using the POP_MAILDIR variable + the user's domain +# e.g. /var/spool/mail/yourdomain.com/ +# If hashing is enabled, it is done AFTER this is constructed. +# e.g. for a hash setting of 2, /var/spool/mail/yourdomain.com/u/s/ +# (for a username of "user") +# If using HOMEDIRMAIL, refer to the path structures explained +# in the README.MYSQL +# +# YOU MUST DEFINE *BOTH* OF THE FOLLOWING 2 DOMAIN OPTIONS +# IN A VIRUTAL DOMAIN SETUP. +# +# Field where user domain is +# Define this for a virtual domain setup +# DONT define it for a non-virtual domain setup +#MysqlAuthDomainField domain +# +# If you defined MysqlAuthDomainField and a user does not +# specify a domain in his username, i.e. "USER username" +# this will get used as the default domain..so it will be as +# if he did "USER username@yourdomain.com" +#MysqlAuthDefaultDomain yourdomain.com +# +# END VIRTUAL DOMAIN SETUP + +# Field where user password is +# Defaults to "password" +MysqlAuthPasswordField password + +# Options to set user UID. +# REQUIRED - MUST specify/uncomment one +MysqlAuthUidField uid +#MysqlAuthUid 65534 +#MysqlAuthUidName nobody + +# Options to set user GID. +# REQUIRED - MUST specify/uncomment one +#MysqlAuthGidField gid +MysqlAuthGid 12 +#MysqlAuthGidName mail + +# If defined, specifies a field to check for account status +# where when field value = 1, account is active. +# 0 is disabled, 2 is suspended, 3 is on-hold. +MysqlAuthAcctStatusField status + +# If defined, specifies a field to check for the user's shell. +# If not defined, default shell given to all users is /bin/false +#MysqlShellField shell + +# If defined, specifies a field to check for the user's mail spool +# If not defined, the spool is generated normally by the server. +#MysqlSpoolField spool + +# If defined, specifies a field to check for the qpopper hosts the +# user can login TO. Used if you have multple pop3 servers and +# only want certain users logging in via certain servers. +# Field can contain a '*' to allow login to any host, or a space- +# delimited list of hosts (NOT ips). +#MysqlLoginHostsField loginhosts diff -ruN qpopper4.0.18/popper/genpath.c qpopper4.0.18-mysql-0.16/popper/genpath.c --- qpopper4.0.18/popper/genpath.c 2009-01-09 17:28:25.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/popper/genpath.c 2009-07-23 16:47:33.898793206 -0600 @@ -103,9 +103,16 @@ genpath ( POP *p, char *pszDrop, int iDropLen, GNPH_WHICH iWhich ) { char *pszUser = NULL; /* user name */ + char *userdomain = NULL; /* user name and domain */ + char *prev = NULL; /* temp ptr */ + char *pos = NULL; /* temp ptr */ int len1 = 0; /* used with strlcpy/strlcat */ int len2 = 0; /* used with strlcat */ int len3 = 0; /* used with strlcat */ + int len4 = 0; /* used with strlcat */ + int len5 = 0; /* used with strlcat */ + int len6 = 0; /* used with strlcat */ + int len7 = 0; /* used with strlcat */ int iChunk = 0; /* used with Qsnprintf */ struct passwd *pw = &p->pw; @@ -157,20 +164,53 @@ DEBUG_LOG2 ( p, "...built: (%d) '%.100s'", len1 + len2, pszDrop ); +#ifdef MYSQLAUTH + /* we dont add the domain on the path when creating the temp_drop */ + /* we dont add the domain on the path when creating the cache */ + /* we will do user@mydomain.com.pop and user@mydomain.com.cache instead */ + if (iWhich != GNPH_POP && iWhich != GNPH_CACHE) { + if (!strcmp(p->spool_file,"NULL") && strcmp(p->domain,"NULL")) { + len4 = strlcat ( pszDrop, p->domain, iDropLen ); + len5 = strlcat ( pszDrop, "/", iDropLen ); + DEBUG_LOG2 ( p, "...built: (%d) '%.100s'", len1 + len2, pszDrop ); + } + } /* iWhich */ +#endif + /* * Next, the hashed directory (if in use) */ if ( p->hash_spool != 0 ) { + if (strcmp(p->spool_file,"NULL") && + (iWhich==GNPH_PATH || iWhich==GNPH_SPOOL)) { + /* don't hash for specified spool */ + } len3 = strlcat ( pszDrop, get_hash_dir ( pszUser, p->hash_spool ), iDropLen ); DEBUG_LOG2 ( p, "...built: (%d) '%.100s'", - len1 + len2 + len3, pszDrop ); + len1 + len2 + len3 + len4 + len5, pszDrop ); + } + +#ifdef MYSQLAUTH + /* If we are doing mysql auth AND user is using HOMEDIRMAIL */ + /* then we have to put the username on the end here since pw_dir */ + /* only hold the base home directory, so domain and hashing can */ + /* be tacked on, if needed */ + if (!strcmp(p->spool_file,"NULL") && p->pHome_dir_mail != NULL) { + if (iWhich == GNPH_PATH || iWhich == GNPH_SPOOL) { + len6 = strlcat ( pszDrop, p->user, iDropLen ); + len7 = strlcat ( pszDrop, "/", iDropLen ); + } } +#endif + /* * Check for truncation */ - if ( len1 > iDropLen || len2 > iDropLen || len3 > iDropLen ) { + if ( len1 > iDropLen || len2 > iDropLen || len3 > iDropLen || + len4 > iDropLen || len5 > iDropLen || len6 > iDropLen || + len7 > iDropLen) { pop_log ( p, POP_PRIORITY, HERE, @@ -187,16 +227,30 @@ */ switch ( iWhich ) { case GNPH_SPOOL: /* spool file */ - if ( p->pHome_dir_mail != NULL ) + if (strcmp(p->spool_file, "NULL")) + len1 = strlcpy ( pszDrop, p->spool_file, iDropLen ); + else if ( p->pHome_dir_mail != NULL ) len1 = strlcat ( pszDrop, p->pHome_dir_mail, iDropLen ); else len1 = strlcat ( pszDrop, pszUser, iDropLen ); break; case GNPH_POP: /* .pop file */ +#ifdef MYSQLAUTH + userdomain = (char *)malloc(strlen(pszUser)+strlen(p->domain)+2); + if (strcmp(p->domain,"NULL")) { + strcpy(userdomain,pszUser); + strcat(userdomain,"@"); + strcat(userdomain,p->domain); + } /* mysqlauth_domain_field */ + else strcpy(userdomain,pszUser); +#else + userdomain = (char *)malloc(strlen(pszUser)+2); + strcpy(userdomain,pszUser); +#endif iChunk = Qsprintf ( pszDrop + strlen(pszDrop), p->pCfg_temp_name, - pszUser ); + userdomain ); break; case GNPH_TMP: /* tmpxxxx */ @@ -216,13 +270,34 @@ break; case GNPH_CACHE: /* .cache file */ +#ifdef MYSQLAUTH + userdomain = (char *)malloc(strlen(pszUser)+strlen(p->domain)+2); + if (strcmp(p->domain,"NULL")) { + strcpy(userdomain,pszUser); + strcat(userdomain,"@"); + strcat(userdomain,p->domain); + } /* mysqlauth_domain_field */ + else strcpy(userdomain,pszUser); +#else + userdomain = (char *)malloc(strlen(pszUser)+2); + strcpy(userdomain,pszUser); +#endif iChunk = Qsprintf ( pszDrop + strlen(pszDrop), p->pCfg_cache_name, - pszUser ); + userdomain ); break; case GNPH_PATH: /* Just the path, M'am */ - pszDrop [ strlen(pszDrop) -1 ] = '\0'; /* erase trailing '/' */ + if (strcmp(p->spool_file, "NULL")) { + len1 = strlcpy ( pszDrop, p->spool_file, iDropLen ); + /* get the base path of the spool file */ + prev = pszDrop; + while ( (pos=strchr(prev,'/')) != NULL ) { + prev = pos+1; + } + *(prev) = '\0'; + } + if (strlen(pszDrop) > 1) pszDrop [ strlen(pszDrop) -1 ] = '\0'; /* erase trailing '/' */ break; } /* switch on iWhich */ diff -ruN qpopper4.0.18/popper/maildir.c qpopper4.0.18-mysql-0.16/popper/maildir.c --- qpopper4.0.18/popper/maildir.c 1969-12-31 17:00:00.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/popper/maildir.c 2009-07-23 16:47:33.903787820 -0600 @@ -0,0 +1,463 @@ +/* + * maildir.c - 04/05/2002 + * Anthony J. Biacco - thelittleprince@asteroid-b612.org + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#if HAVE_STRINGS_H +# include +#endif + +#ifdef HAVE_DIRENT_H +# include +# define NAMLEN(dirent) strlen((dirent)->d_name) +#else /* ifdef HAVE_DIRENT_H */ +# define dirent direct +# define NAMLEN(dirent) (dirent)->d_namlen + +# ifdef HAVE_SYS_NDIR_H +# include +# endif /* HAVE_SYS_NDIR_H */ + +# ifdef HAVE_SYS_DIR_H +# include +# endif /* HAVE_SYS_DIR_H */ + +# if HAVE_NDIR_H +# include +# endif /* HAVE_NDIR_H */ +#endif /* else of ifdef HAVE_DIRENT_H */ + +#ifdef HAVE_SYS_UNISTD_H +# include +#endif /* HAVE_SYS_UNISTD_H */ + +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ + +#include "genpath.h" +#include "popper.h" +#include "maildir.h" +#include "snprintf.h" + +/* delete maildir tmp files this many hours old during cleanup */ +#define HOURS_OLD 36 + + +int maildir_clean(p) +POP * p; +{ +int rslt; +char dirname[256]; +char filename[256]; +time_t tm; +DIR *dirp; +struct dirent *dp; +struct stat stat_buf; + + if ((strlen(p->drop_name) + 7) > sizeof(dirname)) return ( POP_FAILURE ); +strcpy(dirname, p->drop_name); +strcat(dirname,"/tmp/"); +tm=time(0); + + if ((dirp = opendir(dirname)) == NULL) { + pop_log ( p, POP_PRIORITY, HERE, + "Unable to open tmp maildir directory '%.128s': %s (%d)", + dirname, strerror(errno), errno ); + return ( POP_FAILURE ); + } /* opendir */ + + while ((dp = readdir(dirp)) != NULL) { + if (dp->d_name[0]=='.') continue; + rslt = Qsnprintf(filename,sizeof(filename),"%s%s",dirname,dp->d_name); + if (rslt == -1) break; + if (stat(filename, &stat_buf) < 0) continue; + if (tm > (stat_buf.st_atime + (HOURS_OLD*3600))) unlink(filename); + } /* readdir */ + (void) closedir (dirp); + +return ( POP_SUCCESS ); +} /* maildir_clean */ + + +int maildir_create(p, temp_mode, spool_mode, spool_owner, spool_group) +POP * p; +mode_t temp_mode; +mode_t spool_mode; +uid_t spool_owner; +gid_t spool_group; +{ +int curdir = 1; +char buffer2 [ MAXLINELEN ]; +struct stat mybuf; + +strcpy(buffer2, p->drop_name); + + while (curdir < 4) { + if ( stat ( buffer2, &mybuf ) == -1 && errno == ENOENT ) { + /* The directory doesn't exit -- create it */ + if ( mkdir ( buffer2, temp_mode ) == -1 ) + return pop_msg ( p, POP_FAILURE, HERE, + "[SYS/TEMP] Unable to create spool directory " + "%s (%04o): %s (%d)", + buffer2, (int) temp_mode, STRERROR(errno), errno ); + if ( chmod ( buffer2, spool_mode ) == -1 ) + return pop_msg ( p, POP_FAILURE, HERE, + "[SYS/TEMP] Unable to chmod of %s to %04o: " + "%s (%d)", + buffer2, (int) spool_mode, STRERROR(errno), errno ); + if ( chown ( buffer2, spool_owner, spool_group ) == -1 ) + return pop_msg ( p, POP_FAILURE, HERE, + "[SYS/TEMP] Unable to set owner/group of spool " + "directory %s to %d/%d: %s (%d)", + buffer2, (int) spool_owner, (int) spool_group, + STRERROR(errno), errno ); + + if ( DEBUGGING ) { + if ( stat ( buffer2, &mybuf ) != 0 ) + return pop_msg ( p, POP_FAILURE, HERE, + "[SYS/TEMP] Unable to access newly-created " + "dir %s: %s (%d)", + buffer2, STRERROR(errno), errno ); + DEBUG_LOG4 ( p, "Created %s; mode=%04o; owner=%d; group=%d", + buffer2, (unsigned int) mybuf.st_mode, + (int) mybuf.st_uid, (int) mybuf.st_gid ); + } /* DEBUGGING */ + } /* directory doesn't exist */ + if (curdir==1) Qsnprintf(buffer2,sizeof(buffer2),"%s/new",p->drop_name); + else if (curdir==2) Qsnprintf(buffer2,sizeof(buffer2),"%s/cur",p->drop_name); + else if (curdir==3) Qsnprintf(buffer2,sizeof(buffer2),"%s/tmp",p->drop_name); + curdir++; + } /* while */ + +return ( POP_SUCCESS ); +} /* maildir_create */ + + +int maildir_updt(p) +POP * p; +{ + MsgInfoList * mp = NULL; /* Pointer to message + info list */ + register int msg_num; /* Current message + counter */ + char filename[256]; + char filename1[256]; + char filename2[256]; + int ch; + time_t my_timer = time(0); /* For timing */ + FILE *fp; + FILE *wfp; + struct stat srcstat, dststat; + + + if ( p->bAuto_delete && p->bUpdate_on_abort == FALSE ) + { + /* + bAuto_delete causes messages which have been RETRd by the client to be + unconditionally and automatically marked deleted. This prevents clients + from leaving mail on the server. + + CAUTION: Be sure users are informed of this before enabling it, to + prevent lost mail. + + Note: bUpdate_on_abort must be FALSE; otherwise users may lose mail + if they try and RETR it but the connection closes for any reason, the + client aborts, etc. + */ + if ( p->msgs_deleted < p->msg_count ) + { /* there are msgs available for deletion */ + DEBUG_LOG0 ( p, "Auto-deleting messages which have been RETRd..." ); + for ( msg_num = 0; msg_num < p->msg_count; ++msg_num ) + { /* msg loop */ + mp = &p->mlp [ msg_num ]; + if ( mp->retr_flag && !mp->del_flag ) + { + Qsnprintf(filename,sizeof(filename),"%s/%s",p->drop_name,mp->filename); + unlink ( filename ); + DEBUG_LOG2 ( p, ">>> msg %i %s automatically flagged as DELETED <<<", + msg_num, mp->filename ); + } + + } /* msg loop */ + } /* there are msgs available for deletion */ + } /* bAuto_delete && bUpdate_on_abort == FALSE */ + + DEBUG_LOG0 ( p, "Performing maildrop update..." ); + DEBUG_LOG0 ( p, "Checking to see if all messages were deleted" ); + + if ( p->bStats ) { + pop_log ( p, POP_PRIORITY, HERE, "Stats: %s %d %ld %d %ld %s %s", + p->user, p->msgs_deleted, p->bytes_deleted, + p->msg_count - p->msgs_deleted, + p->drop_size - p->bytes_deleted, + p->client, p->ipaddr ); + } + +if ( p->msgs_deleted == p->msg_count ) { +/* Delete all messages */ + + for ( msg_num = 0; msg_num < p->msg_count; ++msg_num ) + { /* msg loop */ + mp = &p->mlp [ msg_num ]; + Qsnprintf(filename,sizeof(filename),"%s/%s",p->drop_name,mp->filename); + unlink ( filename ); + DEBUG_LOG2 ( p, ">>> msg %i %s DELETED for deletion of all <<<", + msg_num, mp->filename ); + } /* msg loop */ + + if ( p->bDo_timing ) + p->clean_time = time(0) - my_timer; + + return ( POP_SUCCESS ); +} /* p->msgs_deleted == p->msg_count */ + +if ( p->msgs_deleted != p->msg_count ) { + DEBUG_LOG3 ( p, "Server mode=%i; %i out of %i msgs deleted; " + "copying msgs from new/ to cur/", + p->server_mode, p->msgs_deleted, p->msg_count ); + + for ( msg_num = 0; msg_num < p->msg_count; ++msg_num ) { + + /* + * Get a pointer to the message information list + */ + mp = &p->mlp [ msg_num ]; + +Qsnprintf(filename,sizeof(filename),"%s/%s",p->drop_name,mp->filename); + +if ( mp->del_flag ) { +DEBUG_LOG1 ( p, "Message %d flagged for deletion.", mp->number ); +unlink ( filename ); +} +else { +/* Don't rename file if the new flags are the same as the retrieved ones */ + DEBUG_LOG0 ( p, "Checking if maildir status flags are the same.." ); +if (!strcmp(mp->flags, maildir_put_flags(p, mp)) && (strncmp(mp->filename, "new/", 4) != 0)) { + DEBUG_LOG0 ( p, "Maildir status flags are same. Doing nothing to message.." ); + continue; +} +if (strcmp(mp->flags, maildir_put_flags(p, mp))) { + DEBUG_LOG2 ( p, "Maildir status flags are different. Updating flags.. Old: \"%s\" New: \"%s\"", mp->flags, maildir_put_flags(p, mp) ); +} +else { + DEBUG_LOG0 ( p, "Moving new message.." ); +} +strncpy(filename1, maildir_calc_msg_uidl(mp->filename), 256); +Qsnprintf(filename2,sizeof(filename2),"%s/cur/%s:2,%s",p->drop_name,filename1+4,maildir_put_flags(p, mp)); + DEBUG_LOG1 ( p, "Old filename: %s", filename); + DEBUG_LOG1 ( p, "New filename: %s", filename2); + +/* rename() doesn't work reliably over all NFS implementations, */ +/* so we'll use link to make a hard link of the src file. if it */ +/* fails, we'll copy the src file, byte by byte, as a backup */ +if (link (filename, filename2) != 0) { + if (errno==EXDEV || errno==EMLINK) { + pop_log ( p, POP_PRIORITY, HERE, + "maildir_updt: link() failed on file '%.128s', will try copying: %s (%d)", + filename, strerror(errno), errno ); + if ( ( fp = fopen(filename, "rb") ) == NULL ) { + pop_log ( p, POP_PRIORITY, HERE, + "maildir_updt: fopen() failed on file '%.128s' cannot copy: %s (%d)", + filename, strerror(errno), errno ); + continue; + } /* if fopen */ + if ( ( wfp = fopen(filename2, "wb") ) == NULL ) { + fclose(fp); + pop_log ( p, POP_PRIORITY, HERE, + "maildir_updt: fopen() failed on file '%.128s' cannot copy: %s (%d)", + filename2, strerror(errno), errno ); + continue; + } /* if fopen */ + while((ch = fgetc(fp)) != EOF) { + fputc(ch, wfp); + } + fclose(fp); + fclose(wfp); + } /* if errno */ + else { + pop_log ( p, POP_PRIORITY, HERE, + "maildir_updt: link() failed on file '%.128s': %s (%d)", + filename, strerror(errno), errno ); + if (rename(filename, filename2) == -1) { + pop_log ( p, POP_PRIORITY, HERE, + "maildir_updt: rename() failed on file '%.128s': %s (%d)", + filename, strerror(errno), errno ); + } /* if rename */ + continue; + } /* else */ +} /* if link */ + +/* the link or copyfile may have actually returned success in */ +/* the copy op, but we need to do a stat to be sure */ +if (stat (filename, &srcstat) == -1) { + pop_log ( p, POP_PRIORITY, HERE, + "maildir_updt: stat() failed on src file '%.128s': %s (%d)", + filename, strerror(errno), errno ); + continue; +} /* if stat */ + +if (stat (filename2, &dststat) == -1) { + pop_log ( p, POP_PRIORITY, HERE, + "maildir_updt: stat() failed on dst file '%.128s': %s (%d)", + filename2, strerror(errno), errno ); + continue; +} /* if stat */ + +/* the src and dst files don't match. the copy op failed */ +if (srcstat.st_dev != dststat.st_dev || + srcstat.st_ino != dststat.st_ino || + srcstat.st_rdev != dststat.st_rdev) { + pop_log ( p, POP_PRIORITY, HERE, + "maildir_updt: stat() compare failed for src and dst files: %s (%d)", + strerror(errno), errno ); + continue; +} /* if compare */ + +/* it's all good, remove the src file */ +if ( unlink (filename) == -1 ) { + pop_log ( p, POP_PRIORITY, HERE, + "maildir_updt: unlink() failed on src file '%.128s': %s (%d)", + filename, strerror(errno), errno ); +} + +} /* else */ + +} /* for */ + +} /* p->msgs_deleted != p->msg_count */ + +return ( POP_SUCCESS ); +} /* maildir_updt */ + + +char *maildir_calc_msg_uidl(char *str) +{ +char *tmpptr; +char tmpptr2[strlen(str)+1]; + + +memset(tmpptr2, 0, sizeof(tmpptr2)); + +if ((tmpptr = strchr(str, ':')) != NULL) { + strncpy(tmpptr2, str, strlen(str)-strlen(tmpptr)); + str=tmpptr2; +} + +return str; +} /* maildir_get_msg_uidl */ + +void maildir_sort(MsgInfoList *data, int N) +{ + int i, j; + MsgInfoList v, t; + time_t v_time; + + if(N<=1) return; + + // Partition elements + v = data[0]; + v_time = data[0].msg_time; + i = 0; + j = N; + for(;;) + { + while(data[++i].msg_time < v_time && i < N) { } + while(data[--j].msg_time > v_time) { } + if(i >= j) break; + + t = data[i]; + data[i] = data[j]; + data[j] = t; + } + t = data[i-1]; + data[i-1] = data[0]; + data[0] = t; + + maildir_sort(data, i-1); + maildir_sort(data+i, N-i); + + /* Update message number positions */ + for ( i = 0, j = 0; i < N; i++) { + data[i].number = i+1; + if (data[i].visible_num) { + data[i].visible_num = j+1; + j++; + } /* if */ + } /* for */ + + +} /* maildir_sort */ + + +char *maildir_get_flags(MsgInfoList *msgs) { + char *tmpptr; + char *tmpptr2; + static char tmpptr3[sizeof(msgs->flags)]; + + memset(tmpptr3, 0, sizeof(tmpptr3)); + + if ((tmpptr = strchr(msgs->filename, ':')) != NULL) { + tmpptr++; + tmpptr2 = &msgs->filename[strlen(msgs->filename) - 1]; + while (tmpptr < tmpptr2) { + for ( ; tmpptr[0] != ',' && tmpptr <= tmpptr2; tmpptr++); + tmpptr++; + if (tmpptr > tmpptr2) + break; + for ( ; tmpptr[0] != ',' && tmpptr <= tmpptr2; tmpptr++) { + strncat(tmpptr3, tmpptr, 1); + } + if (tmpptr > tmpptr2) + break; + } /* while */ + } + + return tmpptr3; +} /* maildir_get_flags */ + + +char *maildir_put_flags(POP *p, MsgInfoList *msgs) { + static char tmpptr[sizeof(msgs->flags)]; + + memset(tmpptr, 0, sizeof(tmpptr)); + + if (maildir_has_flags(msgs, 'D') != 0) + strcat(tmpptr, "D"); + if (maildir_has_flags(msgs, 'F') != 0) + strcat(tmpptr, "F"); + if (maildir_has_flags(msgs, 'P') != 0) + strcat(tmpptr, "P"); + if (maildir_has_flags(msgs, 'R') != 0) + strcat(tmpptr, "R"); + if ((maildir_has_flags(msgs, 'S') != 0) || + (msgs->retr_flag && p->bUpdate_status_hdrs)) { + strcat(tmpptr, "S"); + } + if (maildir_has_flags(msgs, 'T') != 0) + strcat(tmpptr, "T"); + + return tmpptr; +} /* maildir_put_flags */ + + +int maildir_has_flags(MsgInfoList *msgs, int flag) { + + if (index(msgs->flags, flag) != NULL) return 1; + + return 0; +} /* maildir_has_flags */ diff -ruN qpopper4.0.18/popper/maildir.h qpopper4.0.18-mysql-0.16/popper/maildir.h --- qpopper4.0.18/popper/maildir.h 1969-12-31 17:00:00.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/popper/maildir.h 2009-07-23 16:47:33.906787829 -0600 @@ -0,0 +1,15 @@ +/* + * maildir.h - 04/05/2002 + * Anthony J. Biacco - thelittleprince@asteroid-b612.org + */ + +#include "config.h" + +int maildir_clean(POP *p); +int maildir_create(POP *p, mode_t temp_mode, mode_t spool_mode, uid_t spool_owner, gid_t spool_group); +int maildir_updt(POP *p); +char *maildir_calc_msg_uidl(char *str); +void maildir_sort(MsgInfoList *data, int N); +char *maildir_get_flags(MsgInfoList *msgs); +char *maildir_put_flags(POP *p, MsgInfoList *msgs); +int maildir_has_flags(MsgInfoList *msgs, int flag); diff -ruN qpopper4.0.18/popper/Makefile.in qpopper4.0.18-mysql-0.16/popper/Makefile.in --- qpopper4.0.18/popper/Makefile.in 2009-07-14 15:30:54.000000000 -0600 +++ qpopper4.0.18-mysql-0.16/popper/Makefile.in 2009-07-23 16:47:33.910792963 -0600 @@ -124,7 +124,7 @@ pop_extend.o scram.o hmac.o base64.o pop_util.o \ get_sub_opt.o msg_ptr.o drac.o pop_config.o pop_tls.o \ pop_tls_openssl.o pop_tls_sslplus.o sslplus_utils.o \ - main.o pop_cache.o genpath.o + main.o pop_cache.o genpath.o pop_conf.o maildir.o SRCS = pop_dele.c pop_dropcopy.c \ pop_get_command.c pop_get_subcommand.c pop_init.c \ @@ -137,7 +137,7 @@ pop_extend.c scram.c hmac.c base64.c pop_util.c \ get_sub_opt.c msg_ptr.c drac.c pop_config.c pop_tls.c \ pop_tls_openssl.c pop_tls_sslplus.c sslplus_utils.c \ - main.c pop_cache.c genpath.c + main.c pop_cache.c genpath.c pop_conf.c maildir.c POPAUTHOBJS = base64.o scram.o md5.o \ hmac.o popauth.o @@ -152,6 +152,7 @@ ${srcdir}/pop_tls.h \ ${srcdir}/genpath.h \ ${srcdir}/sslplus_utils.h \ + ${srcdir}/maildir.h \ ${base_dir}/config.h \ ${top_srcdir}/conf.h \ ${common_dir}/flock.h \ diff -ruN qpopper4.0.18/popper/msg_ptr.c qpopper4.0.18-mysql-0.16/popper/msg_ptr.c --- qpopper4.0.18/popper/msg_ptr.c 2009-01-09 17:28:25.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/popper/msg_ptr.c 2009-07-23 16:47:33.916785158 -0600 @@ -21,6 +21,11 @@ MsgInfoList * msg_ptr ( POP *p, int msg_num ) { +/* CYGNUS */ +int i = 0; +int last_visible = 0; +/* CYGNUS */ + /* * Is requested message out of range? */ @@ -43,9 +48,26 @@ * compare our msg_num parameter with the messages visible_number, and skip * forward until they match. */ +/* CYGNUS */ + /* Find the matching visible message they asked for */ + /* If we cannot, return the last visible message. */ + if (msg_num > p->visible_msg_count) + return NULL; + for (i = 0 ; i < p->msg_count ; ++i) { + if (p->mlp[i].visible_num) last_visible = i; + if (msg_num != p->mlp[i].visible_num) continue; + else { + msg_num = i; + /* + * Get a pointer to the message in the message list + */ + return ( &p->mlp [ msg_num ] ); + } + } - /* - * Get a pointer to the message in the message list - */ - return ( &p->mlp [ msg_num -1 ] ); + if (last_visible) + return ( &p->mlp [ last_visible ] ); + + return NULL; +/* CYGNUS */ } diff -ruN qpopper4.0.18/popper/pop_conf.c qpopper4.0.18-mysql-0.16/popper/pop_conf.c --- qpopper4.0.18/popper/pop_conf.c 1969-12-31 17:00:00.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/popper/pop_conf.c 2009-07-23 16:47:33.919786563 -0600 @@ -0,0 +1,173 @@ +/* + * pop_conf.c - 06/20/2001 + * Anthony J. Biacco - thelittleprince@asteroid-b612.org + */ + + +#include "config.h" + +#ifdef MYSQLAUTH + +#include +#include +#include +#include +#include +#if HAVE_STRINGS_H +# include +#endif + +#include +#include +#include "popper.h" + +#define BUFSIZE 128 + +struct config_options { + char *name; + char **value; + char *default_value; +}; + + +char *mysqlauth_uid_name; +char *mysqlauth_gid_name; +char *mysqlauth_uid_s; +char *mysqlauth_gid_s; + +struct config_options options[] = { + /* Host user for authentication */ + {"mysqlauthhost", &mysqlauth_host, "localhost"}, + /* Mysql port */ + {"mysqlauthport", &mysqlauth_port, "3306"}, + /* Database used for authentication */ + {"mysqlauthdb", &mysqlauth_db, "authdb"}, + /* Username to connect to db */ + {"mysqlusername", &mysqlauth_username, NULL}, + /* Password to connect to db */ + {"mysqlpassword", &mysqlauth_password, NULL}, + /* Alias for mysqlusername */ + {"mysqlauthusername", &mysqlauth_username, NULL}, + /* Alias for mysqlpassword */ + {"mysqlauthpassword", &mysqlauth_password, NULL}, + /* Table used for authentication */ + {"mysqlauthtable", &mysqlauth_table, "qpopper"}, + /* One of "cleartext", "crypt", "md5", "mysql", "all", or "both" */ + {"mysqlauthpasswordmethod", &mysqlauth_password_method, "cleartext"}, + {"mysqlauthusernamefield", &mysqlauth_username_field, "username"}, + {"mysqlauthpasswordfield", &mysqlauth_password_field, "password"}, + {"mysqlauthdomainfield", &mysqlauth_domain_field, NULL}, + /* Domain to be used if none specified */ + {"mysqlauthdefaultdomain", &mysqlauth_default_domain, ""}, + {"mysqlauthuidfield", &mysqlauth_uid_field, NULL}, + {"mysqlauthgidfield", &mysqlauth_gid_field, NULL}, + {"mysqlauthuid", &mysqlauth_uid_s, NULL}, + {"mysqlauthuidname", &mysqlauth_uid_name, NULL}, + {"mysqlauthgid", &mysqlauth_gid_s, NULL}, + {"mysqlauthgidname", &mysqlauth_gid_name, NULL}, + {"mysqlauthacctstatusfield", &mysqlauth_acct_status_field, NULL}, + {"mysqlshellfield", &mysqlauth_shell_field, NULL}, + {"mysqlspoolfield", &mysqlauth_spool_field, NULL}, + {"mysqlloginhostsfield", &mysqlauth_loginhosts_field, NULL}, + {(char *)NULL, (char **)NULL, NULL} +}; + +int +load_popper_conf(p) +POP * p; +{ + FILE *config; + char buf[BUFSIZE]; + struct config_options *c_options; + char *tkp; + char key[BUFSIZE], value[BUFSIZE]; + + config = fopen (MYSQL_CONF,"r"); + if(!config) { + pop_log(p, POP_PRIORITY, HERE, + "Unable to open mysql config file %s", MYSQL_CONF); + return POP_FAILURE; + } + + c_options = options; + while(c_options->name) { + *(c_options->value) = c_options->default_value; + c_options++; + } + + + + while (fgets(buf, BUFSIZE, config)) { + buf[strlen(buf)-1]=0; + if (buf[0] == '#' || buf[0] == '\0') { + continue; + } + sscanf(buf, "%s %s", key, value); + + /* Lowercase string */ + for(tkp=key; *tkp; tkp++) *tkp = tolower(*tkp); + + c_options = options; + while(c_options->name) { + if(!strcmp(key, c_options->name)) { + *(c_options->value) = (char *)malloc(strlen(value)+1); + strcpy(*(c_options->value),value); + break; + } + c_options++; + } + } + + fclose(config); + +/* Check for config dependencies */ + + if(!(mysqlauth_uid_field || mysqlauth_uid_s || mysqlauth_uid_name)) { + pop_log(p, POP_PRIORITY, HERE, + "Must specify one of MySqlAuthUidField or MysqlAuthUid " + "or MysqlAuthUidName" + ); + return POP_FAILURE; + } + if(!mysqlauth_uid_s && mysqlauth_uid_name) { + struct passwd *pw; + if(!(pw = getpwnam(mysqlauth_uid_name))) { + pop_log(p, POP_PRIORITY, HERE, + "MysqlAuthUidName %s cannot be found", mysqlauth_uid_name); + return POP_FAILURE; + } + mysqlauth_uid = pw->pw_uid; + + } else { + if(mysqlauth_uid_s) mysqlauth_uid = atoi(mysqlauth_uid_s); + else mysqlauth_uid = -1; + } + + if(!(mysqlauth_gid_field || mysqlauth_gid_s || mysqlauth_gid_name)) { + pop_log(p, POP_PRIORITY, HERE, + "Must specify one of MySqlAuthGidField or MysqlAuthGid " + "or MysqlAuthGidName"); + return POP_FAILURE; + } + if(!mysqlauth_gid_s && mysqlauth_gid_name) { + struct group *gr; + if(!(gr = getgrnam(mysqlauth_gid_name))) { + pop_log(p, POP_PRIORITY, HERE, + "MysqlAuthUidName %s cannot be found", mysqlauth_gid_name); + return POP_FAILURE; + } + mysqlauth_gid = gr->gr_gid; + } else { + if(mysqlauth_gid_s) mysqlauth_gid = atoi(mysqlauth_gid_s); + else mysqlauth_gid = -1; + } + if (!mysqlauth_domain_field) { + pop_log(p, POP_PRIORITY, HERE, "NOTE: Virtual domain authentication is disabled"); + } + + return POP_SUCCESS; + + +} + +#endif diff -ruN qpopper4.0.18/popper/pop_config.c qpopper4.0.18-mysql-0.16/popper/pop_config.c --- qpopper4.0.18/popper/pop_config.c 2009-01-09 17:28:25.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/popper/pop_config.c 2009-07-23 16:47:33.928786311 -0600 @@ -265,6 +265,9 @@ kSASL_NO_ANONYMOUS, /* (no flag) */ kXTND_XMIT, /* -x */ kPAM_SESSION, /* (no flag) */ + kCHECK_MAILDIR_DIR, /* (no flag) - CYGNUS */ + kMAILDROP_TYPE, /* (no flag) - CYGNUS */ + kSPOOL_FILE, /* (no flag) - CYGNUS */ LAST_OPT_VAL @@ -341,6 +344,9 @@ { "UW-kludge" , CfgBool , CfgResNone, kUW_KLUGE }, { "xtnd-xmit" , CfgBool , CfgResUser, kXTND_XMIT }, { "pam-session" , CfgMnem , CfgResUser, kPAM_SESSION }, + { "check-maildir-dir" , CfgBool , CfgResNone, kCHECK_MAILDIR_DIR }, + { "maildrop-type" , CfgMnem , CfgResUser, kMAILDROP_TYPE }, + { "spool-file" , CfgStr , CfgResUser, kSPOOL_FILE }, { NULL , CfgBad , CfgResNone, LAST_OPT_VAL } }; @@ -452,6 +458,16 @@ }; +/* ...maildrop-type */ +static mnemonic_map mnem_map_maildrop_type [] = +{ + { "default" , QPOP_MBOX_DROP }, + { "mbox" , QPOP_MBOX_DROP }, + { "maildir" , QPOP_MAILDIR_DROP }, + + { NULL , QPOP_MBOX_DROP } +}; + /* * Map each option to its specific table @@ -464,6 +480,7 @@ { kCHUNKY_WRITES , mnem_map_chunky_writes }, { kLOG_FACILITY , mnem_map_log_facility }, { kPAM_SESSION , mnem_map_pam_session }, + { kMAILDROP_TYPE , mnem_map_maildrop_type }, { -1 , NULL } }; @@ -510,7 +527,7 @@ static void skip_token (config_opt_type ttype, char **token, long *token_len, long *line_len); static void show_result (POP *p, config_table *opt, int iVal, const char *sVal, WHENCE); static error_code_type handle_value (POP *p, config_table *opt, char *pval, int plen, config_call_type CallTime); - +static char *get_hash_dir ( char *pszUser, int iMethod ); /* * Log an error message. @@ -970,6 +987,9 @@ #if 0 /* can't use this until we have a way to call pam_close_session with privs */ case kPAM_SESSION: R__MNM ( &p->call_pam_session ); #endif + case kCHECK_MAILDIR_DIR: R__BOO ( &p->bCheck_maildir_dir ); + case kMAILDROP_TYPE: R__MNM ( &p->MaildropType ); + case kSPOOL_FILE: R__PTR ( &p->spool_file ); default: R__PTR ( NULL ); } /* switch ( item ) */ @@ -1594,6 +1614,7 @@ int rslt_spl = -1; char buf_usr [ 256 ] = ""; char buf_spl [ 256 ] = ""; + char tempbuf [ 256 ] = ""; struct stat stat_buf; UID_T uid_save = 0; BOOL bUser = FALSE; @@ -1604,9 +1625,29 @@ * See if we should process a user config file */ if ( p->bUser_opts ) { + #ifdef MYSQLAUTH + /* assumes home directory? */ + strcpy(tempbuf,pwp->pw_dir); + /* tack on domain */ + if (strcmp(p->domain,"NULL")) { + strlcat ( tempbuf, p->domain, sizeof(tempbuf) ); + strlcat ( tempbuf, "/", sizeof(tempbuf) ); + } + /* tack on hashing */ + if ( p->hash_spool != 0 ) { + strlcat ( tempbuf, get_hash_dir ( p->user, p->hash_spool ), sizeof(tempbuf) ); + } + /* tack on user */ + strlcat ( tempbuf, p->user, sizeof(tempbuf) ); + rslt_usr = Qsnprintf ( buf_usr, sizeof(buf_usr), + "%s/.qpopper-options", + tempbuf ); + #else + rslt_usr = Qsnprintf ( buf_usr, sizeof(buf_usr), "%s/.qpopper-options", pwp->pw_dir ); + #endif if ( rslt_usr == -1 ) pop_log ( p, POP_PRIORITY, HERE, "Unable to build user options file name for user %s", @@ -1625,9 +1666,22 @@ * See if we should process a spool config file */ if ( p->bSpool_opts ) { + #ifdef MYSQLAUTH + if (strcmp(p->domain,"NULL")) { + rslt_spl = Qsnprintf ( buf_spl, sizeof(buf_spl), + "%s/.%s@%s.qpopper-options", + p->pCfg_spool_dir, p->user, p->domain ); + } + else { rslt_spl = Qsnprintf ( buf_spl, sizeof(buf_spl), "%s/.%s.qpopper-options", p->pCfg_spool_dir, p->user ); + } + #else + rslt_spl = Qsnprintf ( buf_spl, sizeof(buf_spl), + "%s/.%s.qpopper-options", + p->pCfg_spool_dir, p->user ); + #endif if ( rslt_spl == -1 ) pop_log ( p, POP_PRIORITY, HERE, "Unable to build spool options file name for user %s", @@ -1709,3 +1763,72 @@ return rslt; } + +/* + * Hashing to a spool directory helps reduce the lookup time for sites + * with thousands of mail spool files. Unix uses a linear list to + * save directory information and the following methods attempt to + * improve the performance. + * + * Method 1: add the value of the first 5 chars mod 26 and open the + * spool file in the directory 'a' - 'z'/user. + * Brian Buhrow + * + * Method 2: Use the first 2 characters to determine which mail spool + * to open. E.g., /usr/spool/u/s/user. + * Larry Schwimmer + * (if only one character it is repeated.) + * + * All these methods require that local mail delivery and client programs + * use the same algorithm. Only one method to a customer :-) + */ + +static char hash_buf [ 5 ]; + +static char * +get_hash_dir ( char *pszUser, int iMethod ) +{ + if ( iMethod == 1 ) { + int seed = 0; + + /*Now, perform the hash*/ + + switch ( strlen(pszUser) ) { + case 1: + seed = ( pszUser[0] ); + break; + case 2: + seed = ( pszUser[0] + pszUser[1] ); + break; + case 3: + seed = ( pszUser[0] + pszUser[1] + pszUser[2] ); + break; + case 4: + seed = ( pszUser[0] + pszUser[1] + pszUser[2]+ pszUser[3] ); + break; + default: + seed = ( pszUser[0] + pszUser[1] + pszUser[2] + + pszUser[3] + pszUser[4] ); + break; + } /* switch on pszUser length */ + + hash_buf [ 0 ] = (char) ( (seed % 26) + 'a' ); + hash_buf [ 1 ] = '/'; + hash_buf [ 2 ] = '\0'; + + return hash_buf; + } /* hash_spool == 1 */ + else + if ( iMethod == 2 ) { + hash_buf [ 0 ] = *pszUser; + hash_buf [ 1 ] = '/'; + hash_buf [ 2 ] = ( *(pszUser + 1) ? *(pszUser + 1) : *pszUser ); + hash_buf [ 3 ] = '/'; + hash_buf [ 4 ] = '\0'; + return hash_buf; + } /* hash_spool == 2 */ + else + return NULL; +} + + diff -ruN qpopper4.0.18/popper/pop_dropcopy.c qpopper4.0.18-mysql-0.16/popper/pop_dropcopy.c --- qpopper4.0.18/popper/pop_dropcopy.c 2009-07-13 00:34:28.000000000 -0600 +++ qpopper4.0.18-mysql-0.16/popper/pop_dropcopy.c 2009-07-23 16:47:33.951786844 -0600 @@ -171,6 +171,26 @@ #include "config.h" #include "md5.h" +#ifdef HAVE_DIRENT_H +# include +# define NAMLEN(dirent) strlen((dirent)->d_name) +#else /* ifdef HAVE_DIRENT_H */ +# define dirent direct +# define NAMLEN(dirent) (dirent)->d_namlen + +# ifdef HAVE_SYS_NDIR_H +# include +# endif /* HAVE_SYS_NDIR_H */ + +# ifdef HAVE_SYS_DIR_H +# include +# endif /* HAVE_SYS_DIR_H */ + +# if HAVE_NDIR_H +# include +# endif /* HAVE_NDIR_H */ +#endif /* else of ifdef HAVE_DIRENT_H */ + #if HAVE_UNISTD_H # include #endif /* HAVE_UNISTD_H */ @@ -199,6 +219,8 @@ #include "misc.h" #include "maillock.h" #include "string_util.h" +#include "maildir.h" +#include "snprintf.h" /* @@ -292,7 +314,6 @@ static char *mfgets(char *s, int size, FILE *stream); static void doze(POP *p, int atmpt); - /* * Globals */ @@ -425,7 +446,7 @@ MsgInfoList *mp; /* Pointer to message info list */ int msg_num; /* Current message number */ int visible_msg_num; /* Current visible message number */ - int nchar; + int nchar = 0; BOOL bInHeader; BOOL bInContHeader; int uidl_found; @@ -442,9 +463,412 @@ int uw_hint = 0; BOOL bNewUIDs = p->bUpdate_status_hdrs == TRUE && p->bOld_style_uid == FALSE; + char dirname[256]; + char filename[256]; + char ouruidl[256]; + char curdir[4]; + int countdir = 0; + int rslt; + FILE *fp = 0; + DIR *dirp; + struct dirent *dp; +/* CYGNUS */ + struct stat mybuf; +/* CYGNUS */ - DEBUG_LOG1 ( p, "DROPINFO Checking file %s", fname ); +if (p->MaildropType == MAILDIR_DROP) { +if ((strlen(fname) + 7) > sizeof(dirname)) return POP_FAILURE; + + mp = p->mlp - 1; + newline = 1; + + bInHeader = FALSE; + bInContHeader = FALSE; + msg_num = 0; + visible_msg_num = 0; + uidl_found = 0; + expecting_trailer = 0; + content_length = 0; + content_nchar = 0; + cont_len = 0; + p->msg_count = ALLOC_MSGS; + +while (countdir < 2) { +if (!countdir) strcpy(curdir,"new"); +else if (countdir==1) strcpy(curdir,"cur"); +Qsnprintf(dirname,sizeof(dirname),"%s/%s",fname,curdir); +countdir++; + + DEBUG_LOG1 ( p, "DROPINFO Checking maildir directory %s", dirname ); + + if ((dirp = opendir(dirname)) == NULL) { + pop_log ( p, POP_PRIORITY, HERE, + "Unable to open maildir directory '%.128s': %s (%d)", + dirname, strerror(errno), errno ); + return POP_FAILURE; + } /* opendir */ + + while ((dp = readdir(dirp)) != NULL) { + if (dp->d_name[0]=='.') continue; + rslt = Qsnprintf(filename,sizeof(filename),"%s/%s",dirname,dp->d_name); + if (rslt == -1) break; + + if (!(fp = fopen(filename, "r"))) { + (void) closedir (dirp); + p->msg_count = 0; + pop_log ( p, POP_PRIORITY, HERE, + "Unable to open maildir file '%.128s': %s (%d)", + filename, strerror(errno), errno ); + return POP_FAILURE; + } /* if open */ + + if ( mfgets ( buffer, MAXMSGLINELEN, fp ) == NULL ) { + fclose(fp); + DEBUG_LOG1 ( p, "Empty mail file! Skipping! : %s", filename ); + continue; + } + +/* + if ( !isfromline(buffer) ) { + fclose(fp); + (void) closedir (dirp); + DEBUG_LOG5 ( p, "newline=%d; isOflow=%d; wasOflow=%d; buffer(%u): %.256s", + newline, isOflow, wasOflow, strlen(buffer), buffer ); + return pop_msg ( p, POP_FAILURE, HERE, + "[SYS/PERM] Unable to process From lines " + "(envelopes) in %s; change recognition " + "mode or check for corrupted mail drop.", + filename ); + } +*/ + +/* Preserve From envelope */ +/* strlcpy(frombuf, buffer, sizeof(frombuf)); */ + + /* + * ------ Get ready for the next message ----- + */ + MD5Init ( &mdContext ); +/* + MD5Update ( &mdContext, (unsigned char *)buffer, + strlen(buffer) ); +*/ + + if ( bNewUIDs ) { + /* + * Include a unique number in case everything else is not. + * + * We don't do this unless bUpdate_status_hdrs is set, because + * in that case the UIDs need to be deterministic so they stay + * the same when recalculated each session. + */ +/* + unique_num++; + MD5Update ( &mdContext, (unsigned char *)&unique_num, + sizeof(long) ); +*/ + } + + if ( bInHeader == FALSE ) { + if ( ++msg_num > p->msg_count ) { + p->mlp = (MsgInfoList *) realloc ( p->mlp, + (p->msg_count += ALLOC_MSGS) + * sizeof(MsgInfoList) ); + if ( p->mlp == NULL ) { + p->msg_count = 0; + fclose(fp); + (void) closedir (dirp); + return pop_msg ( p, POP_FAILURE, HERE, + "[SYS/TEMP] Can't build message list " + "for '%s': Out of memory", + p->user ); + } + mp = p->mlp + msg_num - 2; + } + + if ( msg_num != 1 ) { + DEBUG_LOG5 ( p, "Msg %d uidl '%s' at offset %ld is %ld " + "octets long and has %u lines.", + mp->number, mp->uidl_str, mp->offset, + mp->length, mp->lines ); + } + else { + DEBUG_LOG0 ( p, "Found top of first message" ); + } + ++mp; + + } + else { /* still in header */ + pop_log ( p, POP_PRIORITY, HERE, + "Msg %d corrupted; ignoring previous header" + " information.", + mp->number ); + } + mp->number = msg_num; + mp->length = 0; + mp->lines = 0; + mp->body_lines = 0; +/* + mp->offset = ftell(fp) - nchar; +*/ + mp->offset = 0; + mp->del_flag = FALSE; + mp->hide_flag = FALSE; + mp->retr_flag = FALSE; + mp->orig_retr_state = FALSE; + mp->uidl_str[0]= '\n'; + mp->uidl_str[1]= '\0'; + mp->filename[0]= 0; + mp->flags[0] = 0; + Qsnprintf(mp->filename,sizeof(mp->filename),"%s/%s",curdir,dp->d_name); + DEBUG_LOG3 ( p, "Msg %d file %s being added to list %p", mp->number, + mp->filename, p->mlp ); +/* CYGNUS */ + if (stat(filename, &mybuf) < 0) { + (void) closedir (dirp); + p->msg_count = 0; + pop_log ( p, POP_PRIORITY, HERE, + "Unable to stat maildir file '%.128s': %s (%d)", + filename, strerror(errno), errno ); + return POP_FAILURE; + } /* if stat */ + + mp->msg_time = mybuf.st_mtime; + /* Get any maildir flags from the filename */ + strcpy(mp->flags, maildir_get_flags(mp)); + DEBUG_LOG1 ( p, "Maildir flags: \"%s\"", mp->flags); + /* Set any variables because of said flags */ + if (maildir_has_flags(mp, 'S') != 0) { + DEBUG_LOG0 ( p, "Maildir flag S is set" ); + mp->retr_flag = TRUE; + mp->orig_retr_state = TRUE; + } +/* pop_log ( p, POP_PRIORITY, HERE, + "Got msg_time %ld for msg %d", + mp->msg_time, mp->number ); */ +/* CYGNUS */ + bInHeader = TRUE; + bInContHeader = FALSE; + uidl_found = 0; + content_nchar = 0; + content_length = 0; + cont_len = 0; + +rewind ( fp ); + + while ( mfgets ( buffer, MAXMSGLINELEN, fp ) != NULL ) { + nchar = strlen(buffer); + + if ( bInHeader ) { + if ( bInContHeader && ( *buffer != ' ' || *buffer != '\t' ) ) + bInContHeader = FALSE; + if ( *buffer == '\n' ) { /* End of headers */ + bInHeader = FALSE; + bInContHeader = FALSE; + content_length = cont_len; + mp->body_lines = 1; /* Count newline as the first body line */ + if ( mp->hide_flag == 0 ) + mp->visible_num = ++visible_msg_num; + + if ( !uidl_found ) { + char *cp; +/* + if ( p->bOld_style_uid == FALSE ) + MD5Update ( &mdContext, (unsigned char *)frombuf, + strlen(frombuf) ); +*/ + + /* Figure out our UIDL for this msg from the filename */ + ouruidl[0] = 0; +/* DEBUG_LOG1 (p, "Got UIDL string : %s\n", ouruidl); */ + strncpy(ouruidl, maildir_calc_msg_uidl(dp->d_name), 256); +/* DEBUG_LOG1 (p, "Got UIDL string2: %s\n", ouruidl); */ + MD5Update ( &mdContext, (unsigned char *)ouruidl, + strlen(ouruidl) ); + + MD5Final ( digest, &mdContext ); + cp = mp->uidl_str; + cp = encode_uid_hash ( cp, digest, p->bOld_style_uid ); + *cp++ = '\n'; + *cp = '\0'; + + DEBUG_LOG2 ( p, "UID not found; generated UID(%d): %s", + strlen(mp->uidl_str), mp->uidl_str ); + + mp->length += strlen("X-UIDL: ") + strlen(mp->uidl_str) +1; + p->drop_size += strlen("X-UIDL: ") + strlen(mp->uidl_str) +1; + /* New UIDs do not dirty the mailspool unless + * bUpdate_status_hdrs is set. They are just recalculated + * each time Qpopper is run. + */ + if ( p->bUpdate_status_hdrs ) + p->dirty = TRUE; + } /* !uidl_found */ + + } else if ( CONTENT_LENGTH && + !strncmp(buffer, "Content-Length:", 15) ) { + cont_len = atoi(buffer + 15); +/* + MD5Update ( &mdContext, (unsigned char *)buffer, strlen(buffer) ); +*/ + mp->lines++; + continue; /* not part of the message size */ + } else if ( !uidl_found && + ( !strncasecmp ( "Received:", buffer, 9 ) || + !strncasecmp ( "Date:", buffer, 5 ) || + !strncasecmp ( "Message-Id:", buffer, 11 ) || + !strncasecmp ( "Subject:", buffer, 8 ) || + ( bInContHeader && bNewUIDs ) + ) + ) { +/* + MD5Update ( &mdContext, (unsigned char *)buffer, + strlen(buffer) ); +*/ + } else if ( !strncasecmp("X-UIDL:", buffer, 7) ) { + if ( !uidl_found ) { + char *cp; + int len; + + /* + * Skip over header string + */ + cp = index ( buffer, ':' ); + if ( cp != NULL ) { + cp++; + while ( *cp && ( *cp == ' ' || *cp == '\t') ) + cp++; + } else + cp = ""; + + len = strlen ( cp ); + if ( len >= MIN_UIDL_LENGTH && + len <= MAX_UIDL_LENGTH ) { + uidl_found++; + strlcpy ( mp->uidl_str, cp, sizeof(mp->uidl_str) ); + mp->length += nchar + 1; + p->drop_size += nchar + 1; + DEBUG_LOG1 ( p, "Found UIDL header: %s", buffer ); + } + else { + DEBUG_LOG4 ( p, "Ignoring UIDL header: %s " + "(len=%d; min=%d; max=%d)", + buffer, len, MIN_UIDL_LENGTH, + MAX_UIDL_LENGTH ); + } + } /* !uidl_found */ + + mp->lines++; + continue; /* Do not include this value in the message size */ + } else if ( (strncasecmp(buffer, "Status:", 7) == 0) ) { + if ( index(buffer, 'R') != NULL ) { + mp->retr_flag = TRUE; + mp->orig_retr_state = TRUE; + } + } +/* CYGNUS */ +/* if ( p->bUW_kluge && msg_num == 1 ) { */ +/* CYGNUS */ + if ( p->bUW_kluge ) { + if ( strncasecmp ( buffer, "Subject: ", 9 ) == 0 && + ( strstr ( buffer, "DO NOT DELETE" ) != NULL || + strstr ( buffer, "DON'T DELETE THIS MESSAGE" ) != NULL || + strstr ( buffer, "FOLDER INTERNAL DATA" ) != NULL + ) + ) { + uw_hint++; + DEBUG_LOG2 ( p, "uw_hint now %i; UW_KLUDGE matched '%.64s'", + uw_hint, buffer ); + } else + if ( strncasecmp ( buffer, "X-IMAP: ", 8 ) == 0 ) { + uw_hint++; + DEBUG_LOG2 ( p, "uw_hint now %i; UW_KLUDGE matched '%.64s'", + uw_hint, buffer ); + } else + if ( strncasecmp ( buffer, "From: Mail System Internal Data", 31 ) == 0 ) { + uw_hint++; + DEBUG_LOG2 ( p, "uw_hint now %i; UW_KLUDGE matched '%.64s'", + uw_hint, buffer ); + } + if ( uw_hint == 2 ) { + mp->hide_flag = 1; +/* CYGNUS */ +/* p->first_msg_hidden = 1; */ +/* CYGNUS */ + DEBUG_LOG0 ( p, "UW_KLUDGE check matched 2 conditions; msg hidden" ); + } + } /* UW_KLUDGE && msg_num == 1 */ + } /* bInHeader */ + else { /* not bInHeader */ + content_nchar += nchar; + mp->body_lines++; + } + + mp->length += nchar + 1; + p->drop_size += nchar + 1; + mp->lines++; + + } /* mfgets */ + fclose(fp); + + if ( bInHeader == TRUE ) { + /* no body?? */ + DEBUG_LOG0 (p, "No LF found after headers. Message may very well have no body!\n"); + bInHeader = FALSE; + content_length = cont_len; + mp->body_lines = 0; + if ( mp->hide_flag == 0 ) + mp->visible_num = ++visible_msg_num; + + if ( !uidl_found ) { + char *cp; + /* Figure out our UIDL for this msg from the filename */ + ouruidl[0] = 0; +/* DEBUG_LOG1 (p, "Got UIDL string : %s\n", ouruidl); */ + strncpy(ouruidl, maildir_calc_msg_uidl(dp->d_name), 256); +/* DEBUG_LOG1 (p, "Got UIDL string2: %s\n", ouruidl); */ + MD5Update ( &mdContext, (unsigned char *)ouruidl, + strlen(ouruidl) ); + + MD5Final ( digest, &mdContext ); + cp = mp->uidl_str; + cp = encode_uid_hash ( cp, digest, p->bOld_style_uid ); + *cp++ = '\n'; + *cp = '\0'; + + DEBUG_LOG2 ( p, "UID not found; generated UID(%d): %s", + strlen(mp->uidl_str), mp->uidl_str ); + + mp->length += strlen("X-UIDL: ") + strlen(mp->uidl_str) +1; + p->drop_size += strlen("X-UIDL: ") + strlen(mp->uidl_str) +1; + + /* New UIDs do not dirty the mailspool unless + * bUpdate_status_hdrs is set. They are just recalculated + * each time Qpopper is run. + */ + if ( p->bUpdate_status_hdrs ) + p->dirty = TRUE; + } /* !uidl_found */ + } /* bInHeader == TRUE */ + + } /* readdir */ + + (void) closedir (dirp); + +} /* countdir */ + +/* CYGNUS */ + if ( msg_num ) { + maildir_sort(p->mlp, msg_num); + } /* if msg_num */ + +/* CYGNUS */ + +} /* if MAILDIR_DROP */ +else { + + DEBUG_LOG1 ( p, "DROPINFO Checking file %s", fname ); #ifdef NULLKLUDGE { @@ -586,8 +1010,9 @@ mp->number, mp->uidl_str, mp->offset, mp->length, mp->lines ); } - else + else { DEBUG_LOG0 ( p, "Found top of first message" ); + } ++mp; } @@ -700,11 +1125,12 @@ p->drop_size += nchar + 1; DEBUG_LOG1 ( p, "Found UIDL header: %s", buffer ); } - else + else { DEBUG_LOG4 ( p, "Ignoring UIDL header: %s " "(len=%d; min=%d; max=%d)", buffer, len, MIN_UIDL_LENGTH, MAX_UIDL_LENGTH ); + } } /* !uidl_found */ mp->lines++; @@ -753,6 +1179,8 @@ mp->lines++; } /* for loop */ +} /* else != MAILDIR_DROP */ + p->msg_count = msg_num; p->visible_msg_count = visible_msg_num; @@ -955,11 +1383,12 @@ } mp = p->mlp + msg_num - 2; } /* ++msg_num > p->msg_count */ - if ( msg_num != 1 ) + if ( msg_num != 1 ) { DEBUG_LOG5 (p, "Msg %d uidl '%s' at offset %ld is %ld octets long" " and has %u lines.", mp->number, mp->uidl_str, mp->offset, mp->length, mp->lines ); + } ++mp; } else { /* still in header */ @@ -1091,11 +1520,12 @@ p->drop_size += nchar + 1; DEBUG_LOG1 ( p, "Found UIDL header: %s", buffer ); } - else + else { DEBUG_LOG4 ( p, "Ignoring UIDL header: %s " "(len=%d; min=%d; max=%d)", buffer, len, MIN_UIDL_LENGTH, MAX_UIDL_LENGTH ); + } } /* !uidl_found */ mp->lines++; continue; /* Do not include this value in the message size */ @@ -1207,6 +1637,14 @@ int rslt; time_t time_locked = 0; time_t my_timer = 0; + char buffer [ MAXLINELEN ]; + mode_t my_umask; + mode_t spool_mode; + mode_t temp_mode = 0x1C0; + uid_t spool_owner; + gid_t spool_group; + char *ptr; + if ( p->bDo_timing ) @@ -1216,6 +1654,7 @@ return pop_msg ( p, POP_FAILURE, HERE, "[SYS/TEMP] Unable to get spool name" ); + if (p->MaildropType != MAILDIR_DROP) { if ( ( p->hash_spool > 0 || p->pHome_dir_mail != NULL ) && p->bCheck_old_spool_loc ) { /* @@ -1257,6 +1696,7 @@ } DEBUG_LOG1 ( p, "Temporary maildrop name: '%s'", p->temp_drop ); + } /* != MAILDIR_DROP */ #ifdef BULLDB if ( p->bulldir != NULL ) { @@ -1309,18 +1749,9 @@ } /* if (p->bulldir) */ #endif /* BULLDB */ - if ( p->hash_spool > 0 && p->bCheck_hash_dir ) { - /* - * Make sure the path to the spool file exists - */ - char buffer [ MAXLINELEN ]; - mode_t my_umask; - mode_t spool_mode; - mode_t temp_mode = 0x1C0; - uid_t spool_owner; - gid_t spool_group; - char *ptr; - +/* we do this here because we need it for VIRTUAL DOMAIN setup */ +/* AND/OR hashed directories */ + /* * Get the path name. */ @@ -1358,6 +1789,50 @@ (unsigned int) my_umask, 0000, (int) getuid(), (int) geteuid() ); +#ifdef MYSQLAUTH +/* we need to create the virtual domain directory if it doesn't exist */ + if (strcmp(p->domain,"NULL")) { + if (p->hash_spool > 0) { + ptr=strdup(buffer); + ptr[strlen(ptr)-(p->hash_spool*2)]='\0'; + } /* p->hash_spool */ + else ptr=strdup(buffer); + + if ( stat ( ptr, &mybuf ) == -1 && errno == ENOENT ) { + /* The directory doesn't exit -- create it */ + if ( mkdir ( ptr, temp_mode ) == -1 ) + return pop_msg ( p, POP_FAILURE, HERE, + "[SYS/TEMP] Unable to create virtual spool directory " + "%s (%04o): %s (%d)", + ptr, (int) temp_mode, STRERROR(errno), errno ); + if ( chmod ( ptr, spool_mode ) == -1 ) + return pop_msg ( p, POP_FAILURE, HERE, + "[SYS/TEMP] Unable to chmod of %s to %04o: " + "%s (%d)", + ptr, (int) spool_mode, STRERROR(errno), errno ); + if ( chown ( ptr, spool_owner, spool_group ) == -1 ) + return pop_msg ( p, POP_FAILURE, HERE, + "[SYS/TEMP] Unable to set owner/group of spool " + "directory %s to %d/%d: %s (%d)", + ptr, (int) spool_owner, (int) spool_group, + STRERROR(errno), errno ); + + if ( DEBUGGING ) { + if ( stat ( ptr, &mybuf ) != 0 ) + return pop_msg ( p, POP_FAILURE, HERE, + "[SYS/TEMP] Unable to access newly-created " + "virtual dir %s: %s (%d)", + ptr, STRERROR(errno), errno ); + DEBUG_LOG4 ( p, "Created %s; mode=%04o; owner=%d; group=%d", + ptr, (unsigned int) mybuf.st_mode, + (int) mybuf.st_uid, (int) mybuf.st_gid ); + } /* DEBUGGING */ + } /* directory doesn't exit */ + *ptr = '/'; + } /* virtual domain exists */ +#endif + + if ( p->hash_spool > 0 && p->bCheck_hash_dir ) { /* * In theory, we could just create the new directories with mkdir(2) * or mknod(2), and pass in the desired mode. But this causes @@ -1413,7 +1888,7 @@ buffer ); } } /* HASH_SPOOL == 2 */ - + if ( stat ( buffer, &mybuf ) == -1 && errno == ENOENT ) { /* The directory doesn't exit -- create it */ DEBUG_LOG2 ( p, "HASH_SPOOL; Creating spool path: %s (mode %04o)", @@ -1452,6 +1927,12 @@ DEBUG_LOG1 ( p, "umask now %04o", (unsigned int) my_umask ); } /* p->hash_spool > 0 && bCheck_hash_dir */ + if ( p->MaildropType == MAILDIR_DROP && p->bCheck_maildir_dir) { + /* In a maildir setup the GNPH_SPOOL is actually a directory */ + /* not a file, so, if requested, we need to check if that needs */ + /* creation too, as well as new, cur, and tmp */ + maildir_create(p, temp_mode, spool_mode, spool_owner, spool_group); + } /* p->MaildropType == MAILDIR_DROP && bCheck_maildir_dir */ /* * Here we work to make sure the user doesn't cause us to remove or @@ -1461,14 +1942,25 @@ p->orig_group = pwp->pw_gid; /* save original group */ + DEBUG_LOG1 ( p, "saving old gid %d", p->orig_group); + +#ifdef MYSQLAUTH + /* dont do anything, we want to keep our pw_gid */ +#else #ifdef BINMAIL_IS_SETGID # if BINMAIL_IS_SETGID > 1 pwp->pw_gid = (gid_t)BINMAIL_IS_SETGID; + DEBUG_LOG1 ( p, "setting new gid to binmail gid of %d", pwp->pw_gid); # else - if ( !stat(p->pCfg_spool_dir, &mybuf) ) + if ( !stat(p->pCfg_spool_dir, &mybuf) ) { pwp->pw_gid = mybuf.st_gid; + DEBUG_LOG1 ( p, "setting new gid to spooldir gid of %d", pwp->pw_gid); + } # endif /* BINMAIL_IS_SETGID > 1 */ +#else + DEBUG_LOG1 ( p, "setting new gid to old saved gid of %d", pwp->pw_gid); #endif /* BINMAIL_IS_SETGID */ +#endif /* MYSQLAUTH */ /* * Now we run as the user. @@ -1489,6 +1981,48 @@ (long unsigned) geteuid(), (long unsigned) getegid() ); +if (p->MaildropType == MAILDIR_DROP) { + /* + * Allocate memory for message information structures and this is + * not deleted since a failure, for some reason in this function + * would result in process death. + */ + p->mlp = (MsgInfoList *) calloc ( (unsigned) ALLOC_MSGS, sizeof(MsgInfoList) ); + if ( p->mlp == NULL ){ + return pop_msg ( p, POP_FAILURE, HERE, + "[SYS/TEMP] Can't allocate memory for message list." ); + } + + p->msg_count = 0; + p->visible_msg_count = 0; + p->drop_size = 0; + p->drop = NULL; + time_locked = time(0); + + rslt = init_dropinfo ( p, p->drop_name, p->drop, time_locked ); + if ( rslt != POP_SUCCESS ) return ( POP_FAILURE ); + + if ( DEBUGGING && p->debug && p->msg_count > 0 ) { + register int i; + MsgInfoList *mp; + + for ( i = 0, mp = p->mlp; i < p->msg_count; i++, mp++ ) + pop_log ( p, POP_DEBUG, HERE, + "Msg %d (%d) uidl '%s' at offset %ld is %ld octets " + "long%.*s and has %d lines.", + mp->number, mp->visible_num, + mp->uidl_str, mp->offset, mp->length, + (mp->hide_flag ? 9 : 0), " (hidden)", + mp->lines ); + } + + if ( p->bDo_timing ) + p->init_time = time(0) - my_timer; + + return ( POP_SUCCESS ); +} /* == MAILDIR_DROP */ + + dfd = open ( p->temp_drop, O_RDWR | O_CREAT, 0660 ); if ( dfd == -1 ) { pop_log ( p, POP_PRIORITY, HERE, diff -ruN qpopper4.0.18/popper/pop_init.c qpopper4.0.18-mysql-0.16/popper/pop_init.c --- qpopper4.0.18/popper/pop_init.c 2009-01-09 17:41:06.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/popper/pop_init.c 2009-07-23 16:47:33.962789577 -0600 @@ -502,6 +502,8 @@ p->check_lock_refresh = 5000; /* just a wild guess */ p->expire = -1; /* default to EXPIRE NEVER */ p->bXtnd_Xmit = TRUE; /* XTND XMIT enabled by default */ + p->spool_file = (char *) malloc(257); + strcpy(p->spool_file,"NULL"); #ifdef CYRUS_SASL p->sasl.sasl_no_plaintext = TRUE; @@ -561,6 +563,22 @@ DEBUG_LOG0 ( p, "Omitting check for hashed spool directories" ); #endif /* not DONT_CHECK_HASH_SPOOL_DIR */ +/* CYGNUS */ +#ifndef DONT_CHECK_MAILDIR_DIR + p->bCheck_maildir_dir = TRUE; +#else + DEBUG_LOG0 ( p, "Omitting check for maildir spool directories" ); +#endif /* not DONT_CHECK_MAILDIR_DIR */ + +/* CYGNUS */ +#ifdef MAILDROP_TYPE + p->MaildropType = (maildrop_type) MAILDROP_TYPE; + DEBUG_LOG1 ( p, "Maildrop type is %d", MAILDROP_TYPE); +#else + /* default to mbox style drops */ + p->MaildropType = (maildrop_type) MBOX_DROP; +#endif + #ifdef CHECK_UW_KLUDGE p->bUW_kluge = TRUE; DEBUG_LOG0 ( p, "Checking for and hiding UW folder status messages" ); diff -ruN qpopper4.0.18/popper/pop_list.c qpopper4.0.18-mysql-0.16/popper/pop_list.c --- qpopper4.0.18/popper/pop_list.c 2009-01-09 17:28:25.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/popper/pop_list.c 2009-07-23 16:47:33.967786984 -0600 @@ -37,6 +37,7 @@ #include "popper.h" #include "mmangle/mime.h" #include "mmangle/mangle.h" +#include "snprintf.h" void mangle_count ( void *mstate, char *buf, long len ) @@ -69,6 +70,8 @@ char buffer [ MAXMSGLINELEN ]; MimeParsePtr mimeParse; ManglerStateType mangleState; + char filename[256]; + /* * Were arguments provided ? @@ -129,6 +132,15 @@ return ( pop_msg ( p, POP_FAILURE, HERE, "Syntax error in x-mangle" ) ); } + if (p->MaildropType == MAILDIR_DROP) { + Qsnprintf(filename,sizeof(filename),"%s/%s",p->drop_name,mp->filename); + if (!(p->drop = fopen(filename, "r"))) { + DEBUG_LOG1 ( p, "Cannot open message file %s", filename); + return ( pop_msg ( p, POP_FAILURE, HERE, + "Cannot open message file" ) ); + } + } + mimeParse = MimeInit ( MangleMapper, &mangleState, p->drop ); fseek ( p->drop, mp->offset, 0 ); /* @@ -143,6 +155,11 @@ MimeFinish ( mimeParse ); } + if (p->MaildropType == MAILDIR_DROP && p->drop) { + fclose ( p->drop ); + p->drop=NULL; + } + /* * Display message information */ @@ -181,6 +198,14 @@ mangleState.outFnConstructor = mangleState.outFnDestructor = NULL; FillMangleInfo ( p->pop_parm[1], 0, &mangleState.rqInfo ); + if (p->MaildropType == MAILDIR_DROP) { + Qsnprintf(filename,sizeof(filename),"%s/%s",p->drop_name,mp->filename); + if (!(p->drop = fopen(filename, "r"))) { + DEBUG_LOG1 ( p, "Cannot open message file %s", filename); + return ( pop_msg ( p, POP_FAILURE, HERE, + "Cannot open message file" ) ); + } + } mimeParse = MimeInit ( MangleMapper, &mangleState, p->drop ); fseek ( p->drop, mp->offset, 0 ); /* @@ -193,6 +218,10 @@ } FreeMangleInfo ( &mangleState.rqInfo ); MimeFinish ( mimeParse ); + if (p->MaildropType == MAILDIR_DROP && p->drop) { + fclose ( p->drop ); + p->drop=NULL; + } } /* show_mangled_length */ pop_write_fmt ( p, "%u %lu\r\n", diff -ruN qpopper4.0.18/popper/pop_pass.c qpopper4.0.18-mysql-0.16/popper/pop_pass.c --- qpopper4.0.18/popper/pop_pass.c 2009-01-09 17:28:25.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/popper/pop_pass.c 2009-07-23 16:47:33.980791585 -0600 @@ -165,6 +165,26 @@ #include #include "popper.h" +/* + * Some old OSes don't have srandom, but do have srand + */ +#ifndef HAVE_SRANDOM +# include +# define srandom srand +# define random rand +#else + extern void srandom(); +#endif /* HAVE_SRANDOM */ + +#ifdef MYSQLAUTH +# include "md5.h" +# define MYSQL_CRYPT_LEN 65 +#endif /* MYSQLAUTH */ + +#ifdef LOG_LOGIN_MYSQL +# include +#endif + #ifdef KERBEROS # ifdef KRB5 # include @@ -1150,6 +1170,26 @@ *secretkey = '\0'; #endif /* SECURENISPLUS */ +#ifdef MYSQLAUTH + int crypto_crypt = 0, crypto_mysql = 0, crypto_md5 = 0, crypto_plain = 0; + register char *cp2; + char buffer[BUFSIZ]; + register unsigned char *dp; + unsigned char *ep, digest[16]; + char *scrambled_password = NULL; + char crypted_pw[65]; + char *userdomain; + MD5_CTX mdContext; +#endif /* MYSQLAUTH */ + +#ifdef LOG_LOGIN_MYSQL + MYSQL mysql; + #define QBUF_LEN 255 + char qbuf[QBUF_LEN]; + MYSQL_RES *result; + unsigned int num_rows; +#endif + /* * Is the user not authorized to use POP? */ @@ -1177,6 +1217,132 @@ return ( pop_msg ( p, POP_FAILURE, HERE, ERRMSG_PW, p->user ) ); } +#ifdef MYSQLAUTH + DEBUG_LOG6 ( p, + "%s: pop_pass sql data: pw_name=\"%s\" pw_passwd=\"xxx\" pw_dir=\"%s\" pw_uid=\"%d\" pw_gid=\"%d\" pw_shell=\"%s\"", + p->user, + pwp->pw_name, + pwp->pw_dir, + pwp->pw_uid, + pwp->pw_gid, + pwp->pw_shell + ); + + userdomain = (char *)malloc(strlen(p->user)+strlen(p->domain)+2); + strcpy(userdomain,p->user); + if (strcmp(p->domain,"NULL")) { + strcat(userdomain,"@"); + strcat(userdomain,p->domain); + } + +/* Set all of the relevant authentication methods to try */ + if (!strcmp(mysqlauth_password_method,"any")) { + crypto_crypt++; + crypto_mysql++; + crypto_md5++; + crypto_plain++; + } + else if (!strcmp(mysqlauth_password_method,"both")) { + /* for backwards compatibility */ + crypto_crypt++; + crypto_plain++; + } + else if (!strcmp(mysqlauth_password_method,"crypt")) { + crypto_crypt++; + } + else if (!strcmp(mysqlauth_password_method,"mysql")) { + crypto_mysql++; + } + else if (!strcmp(mysqlauth_password_method,"md5")) { + crypto_md5++; + } + else if (!strcmp(mysqlauth_password_method,"cleartext")) { + crypto_plain++; + } + else { + /* default to plaintext */ + crypto_plain++; + } + + if (crypto_crypt != 0) { + strcpy(crypted_pw, crypt(p->pop_parm[1], pwp->pw_passwd)); + + if (!strcmp(pwp->pw_passwd,crypted_pw)) { + DEBUG_LOG3 ( p, "%s: succcessful crypt sql authentication for user %s (%s)", + p->user, userdomain, pwp->pw_name); + goto auth_ok; + } + } /* crypto_crypt */ + if (crypto_mysql != 0) { + unsigned int t; + unsigned char t1, t2; + + if ((scrambled_password = + malloc((size_t) (MYSQL_CRYPT_LEN + 2))) == NULL) { + pop_log(p, POP_FAILURE, HERE, + "%s: crypto_mysql: bad malloc for scrambled_password!",p->user); + goto auth_bad; + } + /* seed random with the current time to nearest second */ + srandom( (unsigned int) time((TIME_T *)0) ); + + t = random(); + t1 = t & 0xff; + t2 = (t >> 8) & 0xff; + scrambled_password[MYSQL_CRYPT_LEN] = (char) t1; + scrambled_password[MYSQL_CRYPT_LEN + 1] = (char) t2; + make_scrambled_password(scrambled_password, p->pop_parm[1]); + if ((unsigned char) scrambled_password[MYSQL_CRYPT_LEN] != t1 || + (unsigned char) scrambled_password[MYSQL_CRYPT_LEN + 1] != t2) { + for (;;) { + *scrambled_password++ = 0; + } + } + + if (!strcmp(pwp->pw_passwd,scrambled_password)) { + DEBUG_LOG3 ( p, "%s: succcessful mysql sql authentication for user %s (%s)", + p->user, userdomain, pwp->pw_name); + goto auth_ok; + } + } /* crypto_mysql */ + if (crypto_md5 != 0) { + + MD5Init ( &mdContext ); + if (p->pop_parm[1] != NULL && *p->pop_parm[1] != 0) { + MD5Update ( &mdContext, (unsigned char *) p->pop_parm[1], strlen(p->pop_parm[1]) ); + } + MD5Final ( digest, &mdContext ); + + cp2 = buffer; + for (ep = (dp = digest) + sizeof digest / sizeof digest[0]; + dp < ep; + cp2 += 2) + (void) sprintf (cp2, "%02x", *dp++ & 0xff); + *cp2 = '\0'; + + if (!strcmp(pwp->pw_passwd, buffer)) { + DEBUG_LOG3 ( p, "%s: succcessful md5 sql authentication for user %s (%s)", + p->user, userdomain, pwp->pw_name); + goto auth_ok; + } + } /* crypto_md5 */ + if (crypto_plain != 0) { + if (!strcmp(pwp->pw_passwd,p->pop_parm[1])) { + DEBUG_LOG3 ( p, "%s: succcessful cleartext sql authentication for user %s (%s)", + p->user, userdomain, pwp->pw_name); + goto auth_ok; + } + } /* crypto_plain */ + /* if we get here, authentication failed */ + auth_bad: + sleep(SLEEP_SECONDS); + return (pop_msg(p,POP_FAILURE, HERE, ERRMSG_PW, userdomain)); + +auth_ok: + DEBUG_LOG3 ( p, "%s: end of sql authentication for user %s (%s)", + p->user, userdomain, pwp->pw_name); +#else /* not MYSQLAUTH */ + #ifdef SECURENISPLUS /* * we must do this keyserv stuff (as well as auth_user()!) as the user @@ -1276,6 +1442,8 @@ SETEUID ( uid_save ); #endif /* SECURENISPLUS */ +#endif /* MYSQLAUTH */ + /* * Check if server mode should be set or reset based on group membership. */ @@ -1324,6 +1492,81 @@ * Authorization completed successfully */ +#ifdef LOG_LOGIN_MYSQL + DEBUG_LOG1 ( p, "%s: Doing log-login sql", p->user); + mysql_init(&mysql); + if(!mysql_real_connect(&mysql,mysqlauth_host,mysqlauth_username, + mysqlauth_password, mysqlauth_db, + atoi(mysqlauth_port), NULL, 0 )) { + pop_log(p, POP_FAILURE, HERE, + "Couldn't connect to the authentication database (%s) ", + mysql_error(&mysql)); + return(pop_msg(p, POP_FAILURE, HERE, + "Couldn't connect to the authentication database")); + } + else { + DEBUG_LOG5 ( p, "%s: Connected to %s:%s db %s as %s for log-login", + p->user,mysqlauth_host,mysqlauth_port,mysqlauth_db,mysqlauth_username); + } + snprintf(qbuf, QBUF_LEN, + "SELECT ip FROM relay_ip WHERE ip = '%s'",p->ipaddr); + if (mysql_query(&mysql, (char *)qbuf) < 0) { + pop_log(p, POP_FAILURE, HERE, "query (%s) failed (%s)", qbuf, + mysql_error(&mysql)); + mysql_close(&mysql); + return(pop_msg(p, POP_FAILURE, HERE, "Logging query check failure")); + } + else { + DEBUG_LOG3 ( p, "%s: Ran log-login sql query \"%s\" against db %s", + p->user, qbuf, mysqlauth_db); + } + if(!(result = mysql_store_result(&mysql))) { + pop_log(p, POP_FAILURE, HERE, "Logging query check result failed (%s)", mysql_error(&mysql)); + mysql_close(&mysql); + return(pop_msg(p, POP_FAILURE, HERE, "Logging query check result failed")); + } + else { + DEBUG_LOG2 ( p, "%s: Successfully got log-login sql result from db %s", + p->user, mysqlauth_db); + } + num_rows = mysql_num_rows(result); + mysql_free_result(result); + if (num_rows==0) { + /* make new row */ + snprintf(qbuf, QBUF_LEN, + "INSERT INTO relay_ip (ip, ts) VALUES ('%s','%ld')", + p->ipaddr,time(0)); + if (mysql_query(&mysql, (char *)qbuf) < 0) { + pop_log(p, POP_FAILURE, HERE, "query (%s) failed (%s)", qbuf, + mysql_error(&mysql)); + mysql_close(&mysql); + return(pop_msg(p, POP_FAILURE, HERE, "Logging query new failure")); + } + else { + DEBUG_LOG3 ( p, "%s: Ran log-login sql query \"%s\" against db %s", + p->user, qbuf, mysqlauth_db); + } + } /* if !num_rows */ + else { + /* update row with current ts */ + snprintf(qbuf, QBUF_LEN, + "UPDATE relay_ip SET ts = '%ld' WHERE ip = '%s'", + time(0),p->ipaddr); + if (mysql_query(&mysql, (char *)qbuf) < 0) { + pop_log(p, POP_FAILURE, HERE, "query (%s) failed (%s)", qbuf, + mysql_error(&mysql)); + mysql_close(&mysql); + return(pop_msg(p, POP_FAILURE, HERE, "Logging query update failure")); + } + else { + DEBUG_LOG3 ( p, "%s: Ran log-login sql query \"%s\" against db %s", + p->user, qbuf, mysqlauth_db); + } + } /* else */ + mysql_close(&mysql); + DEBUG_LOG1 ( p, "%s: Done doing log-login sql", p->user); +#endif + if ( p->pLog_login != NULL ) do_log_login ( p ); @@ -1397,3 +1640,27 @@ return POP_SUCCESS; } + +void make_hash_password(unsigned long *result, const char *password, unsigned int password_len) { + register unsigned long nr=1345345333L, add=7, nr2=0x12345671L; + unsigned long tmp; + const char *password_end= password + password_len; + for (; password < password_end; password++) { + if (*password == ' ' || *password == '\t') + continue; + tmp= (unsigned long) (unsigned char) *password; + nr^= (((nr & 63)+add)*tmp)+ (nr << 8); + nr2+=(nr2 << 8) ^ nr; + add+=tmp; + } + result[0]=nr & (((unsigned long) 1L << 31) -1L); /* Don't use sign bit (str2int) */; + result[1]=nr2 & (((unsigned long) 1L << 31) -1L); + return; +} + +void make_scrambled_password(char *to, const char *password) { + unsigned long hash_res[2]; + make_hash_password(hash_res,password,strlen(password)); + sprintf(to,"%08lx%08lx",hash_res[0],hash_res[1]); +} + diff -ruN qpopper4.0.18/popper/popper.c qpopper4.0.18-mysql-0.16/popper/popper.c --- qpopper4.0.18/popper/popper.c 2009-01-09 17:28:26.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/popper/popper.c 2009-07-23 16:47:33.986789088 -0600 @@ -132,6 +132,28 @@ int pop_timeout = POP_TIMEOUT; +#ifdef MYSQLAUTH +char *mysqlauth_username; +char *mysqlauth_password; +char *mysqlauth_host; +char *mysqlauth_port; +char *mysqlauth_db; +char *mysqlauth_table; +char *mysqlauth_username_field; +char *mysqlauth_password_field; +char *mysqlauth_password_method; +char *mysqlauth_gid_field; +char *mysqlauth_uid_field; +uid_t mysqlauth_uid; +gid_t mysqlauth_gid; +char *mysqlauth_domain_field; +char *mysqlauth_default_domain; +char *mysqlauth_acct_status_field; +char *mysqlauth_shell_field; +char *mysqlauth_spool_field; +char *mysqlauth_loginhosts_field; +#endif + #ifdef _DEBUG POP *global_debug_p = NULL; #endif @@ -192,6 +214,11 @@ if ( pop_init ( &p, argc, argv ) != POP_SUCCESS ) EXIT ( 1 ); +#ifdef MYSQLAUTH + if (load_popper_conf(&p) != POP_SUCCESS) + EXIT( 1 ); +#endif + DEBUG_LOG1 ( &p, "before TLS; tls_support==%d", p.tls_support ); diff -ruN qpopper4.0.18/popper/popper.h qpopper4.0.18-mysql-0.16/popper/popper.h --- qpopper4.0.18/popper/popper.h 2009-07-12 18:50:41.000000000 -0600 +++ qpopper4.0.18-mysql-0.16/popper/popper.h 2009-07-23 16:47:34.001793881 -0600 @@ -61,6 +61,28 @@ #include /* this needs to be after system .h files */ +#ifdef MYSQLAUTH +extern char *mysqlauth_username; +extern char *mysqlauth_password; +extern char *mysqlauth_host; +extern char *mysqlauth_port; +extern char *mysqlauth_db; +extern char *mysqlauth_table; +extern char *mysqlauth_username_field; +extern char *mysqlauth_password_field; +extern char *mysqlauth_password_method; +extern char *mysqlauth_domain_field; +extern char *mysqlauth_default_domain; +extern char *mysqlauth_gid_field; +extern char *mysqlauth_uid_field; +extern uid_t mysqlauth_uid; +extern gid_t mysqlauth_gid; +extern char *mysqlauth_acct_status_field; +extern char *mysqlauth_shell_field; +extern char *mysqlauth_spool_field; +extern char *mysqlauth_loginhosts_field; +#endif + #ifdef BULLDB # undef DBM /* used by mts.c and ndbm.h */ # ifdef GDBM @@ -106,7 +128,12 @@ #define TAB 9 #define NEWLINE '\n' -#define MAXUSERNAMELEN 65 +#ifdef MYSQLAUTH +# define MAXUSERNAMELEN 128 +# define MAXDOMAINNAMELEN 64 +#else +# define MAXUSERNAMELEN 65 +#endif #define MAXDROPLEN 256 #define MAXLINELEN 1024 #define MAXMSGLINELEN MAXLINELEN @@ -433,6 +460,14 @@ QPOP_SSLv23 /* TLSv1, SSLv3, and SSLv2 */ } tls_vers_type; +/* + * Enumeration for which type of maildrop we are using + */ +typedef enum { + QPOP_MBOX_DROP = 0, + QPOP_MAILDIR_DROP +} maildrop_type; + /* * Enumeration for configuration option types @@ -537,6 +572,11 @@ Used for RSET cmd. */ char uidl_str [ (DIG_SIZE * 2) + 2 ];/* Cache of the UIDL str for faster access */ + char filename[256]; /* CYGNUS - Filename of the + file this is, if maildir */ + time_t msg_time; /* CYGNUS - Modification time + of a maildir-style file */ + char flags[7]; /* CYGNUS - Maildir status flags */ } MsgInfoList; typedef struct _pop POP; @@ -571,6 +611,9 @@ BOOL bDowncase_user; /* TRUE to downcase user name */ BOOL bTrim_domain; /* TRUE to trim domain from user name */ char user[MAXUSERNAMELEN]; /* Name of the POP user */ +#ifdef MYSQLAUTH + char domain[MAXDOMAINNAMELEN]; /* Name of domain user logs in with */ +#endif #if defined(__bsdi__) && _BSDI_VERSION >= 199608 char * style; /* style of auth used */ @@ -695,6 +738,8 @@ BOOL bAuto_delete; /* Delete msgs */ BOOL bGroup_bulls; /* Bulletins go to groups */ int hash_spool; /* Using hashed spools? */ + maildrop_type MaildropType; /* CYGNUS - What kind of maildrop? mbox/maildir */ + BOOL bCheck_maildir_dir; /* CYGNUS - Check and create maildir dirs */ char *pHome_dir_mail; /* Home dir is spool loc */ BOOL bHome_dir_misc; /* Home dir is .cache/.pop loc */ BOOL bOld_style_uid; /* Generate pre-3.x UIDs */ @@ -721,6 +766,7 @@ #if 0 /* can't use this until we have a way to call pam_close_session with privs */ pam_session_type call_pam_session; /* When to call pam_session_* */ #endif + char *spool_file; /* CYGNUS - The full path to the spool file, if given */ }; @@ -847,6 +893,10 @@ int qpopper ( int argc, char *argv[] ); +#ifdef MYSQLAUTH +int load_popper_conf (POP *p); +#endif + extern char *pwerrmsg; #define pop_auth_fail pop_msg @@ -863,6 +913,10 @@ char *get_time(); extern char *ZONE; +void remove_first(char *inpstr); +void make_hash_password(unsigned long *result, const char *password, unsigned int password_len); +void make_scrambled_password(char *to, const char *password); + struct topper { int lines_to_output; void **out_state; diff -ruN qpopper4.0.18/popper/pop_send.c qpopper4.0.18-mysql-0.16/popper/pop_send.c --- qpopper4.0.18/popper/pop_send.c 2009-07-12 18:50:41.000000000 -0600 +++ qpopper4.0.18-mysql-0.16/popper/pop_send.c 2009-07-23 16:47:34.010789159 -0600 @@ -282,6 +282,7 @@ int check_body = 0; int check_bytes = 0; long check_total = total_octets_sent; + char filename[256]; /* @@ -375,6 +376,15 @@ mp->body_lines - 1 ); +if (p->MaildropType == MAILDIR_DROP) { + Qsnprintf(filename,sizeof(filename),"%s/%s",p->drop_name,mp->filename); + if (!(p->drop = fopen(filename, "r"))) { + DEBUG_LOG1 ( p, "Cannot open message file %s", filename); + return ( pop_msg ( p, POP_FAILURE, HERE, + "Cannot open message file" ) ); + } +} + /* * Set up the header mucker and possibly the Mime mangler */ @@ -413,7 +423,8 @@ /* * Skip the first line (the sendmail "From" or MMDF line) */ - (void) fgets ( buffer, MAXMSGLINELEN, p->drop ); + if (p->MaildropType != MAILDIR_DROP) + (void) fgets ( buffer, MAXMSGLINELEN, p->drop ); /* Some poorly-written clients (reported to include Netscape Messenger) * expect the octet count to be in the OK response to RETR, which is of @@ -499,8 +510,13 @@ */ MimeFinish ( mimeParse ); } - if ( hangup ) + if ( hangup ) { + if (p->MaildropType == MAILDIR_DROP && p->drop) { + fclose( p->drop ); + p->drop=NULL; + } return ( pop_msg ( p, POP_FAILURE, HERE, "SIGHUP or SIGPIPE flagged" ) ); + } /* * Make sure message ends with a CRLF @@ -540,6 +556,10 @@ mp->length, check_bytes, mp->number, p->drop_name ); } + if (p->MaildropType == MAILDIR_DROP && p->drop) { + fclose( p->drop ); + p->drop=NULL; + } return ( POP_SUCCESS ); } diff -ruN qpopper4.0.18/popper/pop_uidl.c qpopper4.0.18-mysql-0.16/popper/pop_uidl.c --- qpopper4.0.18/popper/pop_uidl.c 2009-01-09 17:28:25.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/popper/pop_uidl.c 2009-07-23 16:47:34.015792153 -0600 @@ -51,6 +51,7 @@ #endif #include "popper.h" +#include "snprintf.h" /* @@ -154,6 +155,16 @@ { char *cp; char *nl; + char filename[256]; + + +if (p->MaildropType == MAILDIR_DROP) { + Qsnprintf(filename,sizeof(filename),"%s/%s",p->drop_name,mp->filename); + if (!(p->drop = fopen(filename, "r"))) { + DEBUG_LOG1 ( p, "Cannot open message file %s", filename); + return ( "" ); + } +} fseek ( p->drop, mp->offset, 0 ); while ( fgets ( buf, len, p->drop ) != NULL ) { @@ -165,9 +176,17 @@ nl = index ( cp, NEWLINE ); if ( nl != NULL ) *nl = 0; + if (p->MaildropType == MAILDIR_DROP && p->drop) { + fclose( p->drop ); + p->drop=NULL; + } return ( cp ); } } + if (p->MaildropType == MAILDIR_DROP && p->drop) { + fclose( p->drop ); + p->drop=NULL; + } return ( "" ); } diff -ruN qpopper4.0.18/popper/pop_updt.c qpopper4.0.18-mysql-0.16/popper/pop_updt.c --- qpopper4.0.18/popper/pop_updt.c 2009-01-09 17:28:25.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/popper/pop_updt.c 2009-07-23 16:47:34.021787980 -0600 @@ -124,6 +124,7 @@ #endif #include "misc.h" +#include "maildir.h" extern int errno; @@ -263,6 +264,8 @@ time_t my_timer = time(0); /* For timing */ + if ( p->MaildropType == MAILDIR_DROP ) return ( maildir_updt ( p ) ); + if ( p->bAuto_delete && p->bUpdate_on_abort == FALSE ) { /* @@ -898,6 +901,8 @@ DEBUG_LOG0 ( p, "Performing maildrop restoration..." ); + if ( p->MaildropType == MAILDIR_DROP ) return ( POP_SUCCESS ); + /* * Undelete any deleted messages so cache is correct. */ diff -ruN qpopper4.0.18/popper/pop_user.c qpopper4.0.18-mysql-0.16/popper/pop_user.c --- qpopper4.0.18/popper/pop_user.c 2009-01-09 17:28:25.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/popper/pop_user.c 2009-07-23 16:47:34.029790704 -0600 @@ -111,6 +111,11 @@ #include "popper.h" #include "string_util.h" +#ifdef MYSQLAUTH +# include +# define SLEEP_SECONDS 10 +#endif + /* * When AUTHON is defined, SCRAM and/or APOP authentication is available. */ @@ -127,6 +132,14 @@ extern char *ERRMSG_PW; +/* + * MYSQLAUTH STUFF + */ +char *ERRMSG_ACCT = "[AUTH] \"%s\": %s account"; +char *ERRMSG_NULL = "[AUTH] \"%s\": %s field is NULL!"; +char *ERRMSG_QUERY = "[AUTH] \"%s\": Authentication query failed. Account may not exist."; +#define MYSQL_DEFAULT_HOMEDIR "/home" +#define AUTH_DEBUG 0 /* * user: Prompt for the user name at the start of a POP session @@ -154,7 +167,27 @@ int i; int bFoundUser = FALSE; #endif /* AUTHON */ +#ifdef MYSQLAUTH + MYSQL mysql; + #define QBUF_LEN 255 + char qbuf[QBUF_LEN]; + char *mqbuf; + MYSQL_RES *result; + MYSQL_ROW row; + int a=0; + int uid_field_num=0, gid_field_num=0, shell_field_num=0; + int acct_status_field_num=0, spool_field_num=0, acct_status; + int loginhosts_field_num=0, loginhosts_status=0; + int qbufleft; + int slen; + unsigned long *lengths; + char *userdomain; + char loginhosts[512]; + char loginhosts_temp[65]; + char qpop_hostname[65]; +#endif + char *atpos = NULL; size_t user_name_len = 0; struct passwd *pw = NULL; @@ -172,9 +205,13 @@ /* * Trim domain name */ - if ( p->bTrim_domain ) + if ( p->bTrim_domain ) { +#ifdef MYSQLAUTH + if (!mysqlauth_domain_field) +#endif trim_domain ( p, p->pop_parm[1] ); - + } + #if defined(KERBEROS) && defined(KRB4) && !defined(KUSEROK) if ( p->bKerberos && strcmp(p->pop_parm[1], p->user) ) { pop_log ( p, POP_WARNING, HERE, @@ -214,7 +251,34 @@ p->pop_parm[1] ); user_name_len = sizeof(p->user) -1; } + +#ifdef MYSQLAUTH + if(mysqlauth_domain_field) + { + atpos=strrchr(p->pop_parm[1],'@'); + if(!atpos) atpos=strrchr(p->pop_parm[1],'#'); + if(atpos) { + int domain_name_len; + atpos++; /* Move passed the '@' */ + domain_name_len = p->pop_parm[1]+strlen(p->pop_parm[1])-atpos; + strncpy(p->domain, atpos, domain_name_len); + pop_lower(p->domain); + user_name_len -= domain_name_len; + p->domain[domain_name_len] = 0; + } else { + /* No domain part - use default */ + strcpy(p->domain, mysqlauth_default_domain); + } + } + else strcpy(p->domain, "NULL"); + + DEBUG_LOG3 ( p, "%s: Got virtual domain \"%s\" from user \"%s\"", + p->pop_parm[1], p->domain, p->pop_parm[1]); +#endif +if (atpos) + strlcpy ( p->user, p->pop_parm[1], user_name_len ); +else strlcpy ( p->user, p->pop_parm[1], sizeof(p->user) ); #ifdef SCRAM @@ -225,6 +289,339 @@ * Cache passwd struct for use later; this memory gets freed at the end * of the session. */ +#ifdef MYSQLAUTH + userdomain = (char *)malloc(strlen(p->user)+strlen(p->domain)+2); + strcpy(userdomain,p->user); + if (strcmp(p->domain,"NULL")) { + strcat(userdomain,"@"); + strcat(userdomain,p->domain); + } + + mysql_init(&mysql); + if(!mysql_real_connect(&mysql,mysqlauth_host,mysqlauth_username, + mysqlauth_password, mysqlauth_db, + atoi(mysqlauth_port), NULL, 0 )) { + pop_log(p, POP_PRIORITY, HERE, + "%s: ERROR: Couldn't connect to the authentication database (%s) ", + userdomain, mysql_error(&mysql)); + if (!AUTH_DEBUG) goto auth_bad; + return(pop_msg(p, POP_FAILURE, HERE, + "Couldn't connect to the authentication database")); + } + else { + DEBUG_LOG5 ( p, "%s: Connected to %s:%s db %s as %s for authentication", + userdomain,mysqlauth_host,mysqlauth_port,mysqlauth_db,mysqlauth_username); + } + +#define MYSTRCAT(STRING) do { strncpy(mqbuf, STRING, qbufleft); \ + slen = strlen(STRING); \ + qbufleft -= slen; \ + mqbuf += slen; } while(0) + +#define MYSTRCATCHR(C) do { \ + if(qbufleft>0) { \ + *mqbuf++=C; \ + *mqbuf = '\0'; \ + qbufleft--; \ + } \ + } while(0) + +#define MYSTRCATESC(STRING) do { \ + slen = strlen(STRING); \ + slen = mysql_escape_string(mqbuf, STRING, \ + (qbufleft/2 < slen ? qbufleft/2 : slen)); \ + mqbuf += slen; \ + qbufleft -= slen; \ + } while(0) + + + mqbuf = qbuf; + qbufleft = QBUF_LEN-1; + + MYSTRCAT("SELECT "); + MYSTRCAT(mysqlauth_password_field); + if(mysqlauth_uid_field) { + uid_field_num = ++a; + MYSTRCATCHR(','); + MYSTRCAT(mysqlauth_uid_field); + } + if(mysqlauth_gid_field) { + gid_field_num = ++a; + MYSTRCATCHR(','); + MYSTRCAT(mysqlauth_gid_field); + } + if(mysqlauth_shell_field) { + shell_field_num = ++a; + MYSTRCATCHR(','); + MYSTRCAT(mysqlauth_shell_field); + } + if(mysqlauth_acct_status_field) { + acct_status_field_num = ++a; + MYSTRCATCHR(','); + MYSTRCAT(mysqlauth_acct_status_field); + } + if(mysqlauth_spool_field) { + spool_field_num = ++a; + MYSTRCATCHR(','); + MYSTRCAT(mysqlauth_spool_field); + } + if(mysqlauth_loginhosts_field) { + loginhosts_field_num = ++a; + MYSTRCATCHR(','); + MYSTRCAT(mysqlauth_loginhosts_field); + } + + + MYSTRCAT(" FROM "); + MYSTRCAT(mysqlauth_table); + MYSTRCAT(" WHERE "); + MYSTRCAT(mysqlauth_username_field); + MYSTRCAT("= '"); + MYSTRCATESC(p->user); + MYSTRCATCHR('\''); + if(mysqlauth_domain_field) { + MYSTRCAT(" AND "); + MYSTRCAT(mysqlauth_domain_field); + MYSTRCAT(" = '"); + MYSTRCATESC(p->domain); + MYSTRCATCHR('\''); + } + + if (mysql_query(&mysql,(char *)qbuf) < 0) { + pop_log(p, POP_PRIORITY, HERE, "%s: ERROR: sql query (%s) failed (%s)", qbuf, + userdomain, mysql_error(&mysql)); + mysql_close(&mysql); + if (!AUTH_DEBUG) goto auth_bad; + return(pop_msg(p, POP_FAILURE, HERE, "Authentication query failed")); + } + else { + DEBUG_LOG3 ( p, "%s: Ran sql query \"%s\" against db %s", + userdomain, qbuf, mysqlauth_db); + } + + if(!(result = mysql_use_result(&mysql))) { + pop_log(p, POP_PRIORITY, HERE, "%s: ERROR: sql query result failed (%s)", + userdomain, mysql_error(&mysql)); + mysql_close(&mysql); + if (!AUTH_DEBUG) goto auth_bad; + return (pop_msg(p,POP_FAILURE, HERE, ERRMSG_QUERY, userdomain)); + } + else { + DEBUG_LOG2 ( p, "%s: Successfully got sql result from db %s", + userdomain, mysqlauth_db); + } + + if(!(row = mysql_fetch_row(result))) { + pop_log(p, POP_PRIORITY, HERE, "%s: ERROR: sql query fetch row failed (%s). User may not exist", + userdomain, mysql_error(&mysql)); + mysql_free_result(result); + mysql_close(&mysql); + if (!AUTH_DEBUG) goto auth_bad; + return (pop_msg(p,POP_FAILURE, HERE, ERRMSG_QUERY, userdomain)); + } + else { + DEBUG_LOG1 ( p, "%s: Successfully got sql row from result", userdomain); + } + lengths = mysql_fetch_lengths(result); + + if (mysqlauth_acct_status_field) { + /* check account status */ + if (row[acct_status_field_num] != NULL) + acct_status=atoi(row[acct_status_field_num]); + else { + mysql_free_result(result); + mysql_close(&mysql); + if (!AUTH_DEBUG) goto auth_bad; + return (pop_msg(p,POP_FAILURE, HERE, ERRMSG_NULL, userdomain, "status")); + } + + DEBUG_LOG2 ( p, "%s: sql acct_status is \"%d\"", + userdomain, acct_status); + + if (acct_status != 1) { + mysql_free_result(result); + mysql_close(&mysql); + sleep(SLEEP_SECONDS); + switch(acct_status) { + case 0: + return (pop_msg(p,POP_FAILURE, HERE, ERRMSG_ACCT, userdomain, "disabled")); + break; + case 2: + return (pop_msg(p,POP_FAILURE, HERE, ERRMSG_ACCT, userdomain, "suspended")); + break; + case 3: + return (pop_msg(p,POP_FAILURE, HERE, ERRMSG_ACCT, userdomain, "on-hold")); + break; + } /* switch */ + } /* acct_status bad */ + } /* check status */ + + if (mysqlauth_loginhosts_field) { + if (row[loginhosts_field_num] != NULL) { + if (strcmp(row[loginhosts_field_num],"*")) { + loginhosts[0]=0; + loginhosts_temp[0]=0; + qpop_hostname[0]=0; +#if defined(HAVE_GETHOSTNAME) + if (gethostname(qpop_hostname,64) != 0) { + DEBUG_LOG1 ( p, "%s: WARNING: Could not get hostname for this machine", userdomain); + strcpy(qpop_hostname,""); + } +#else + DEBUG_LOG1 ( p, "%s: WARNING: Could not get hostname for this machine", userdomain); + strcpy(qpop_hostname,""); +#endif + + strcpy(loginhosts,row[loginhosts_field_num]); + while (strlen(loginhosts)) { + sscanf(loginhosts,"%64s",loginhosts_temp); + if (!strcmp(qpop_hostname,loginhosts_temp)) { + loginhosts_status=1; + break; + } + remove_first(loginhosts); + } /* while */ + if (loginhosts_status != 1) { + mysql_free_result(result); + mysql_close(&mysql); + pop_log(p, POP_PRIORITY, HERE, "%s: ERROR: This user cannot log into this host. (this_hostname:%s valid_loginhosts:%s)", + userdomain, qpop_hostname, row[loginhosts_field_num]); + if (!AUTH_DEBUG) goto auth_bad; + return (pop_msg(p,POP_FAILURE, HERE, ERRMSG_ACCT, userdomain, "loginhosts-restricted")); + } + } /* if not * */ + } /* if not null */ + else { + mysql_free_result(result); + mysql_close(&mysql); + pop_log(p, POP_PRIORITY, HERE, "%s: sql loginhosts is NULL", userdomain); + if (!AUTH_DEBUG) goto auth_bad; + return (pop_msg(p,POP_FAILURE, HERE, ERRMSG_NULL, userdomain, "loginhosts")); + } + + DEBUG_LOG2 ( p, "%s: sql loginhosts is \"%s\"", + userdomain, row[loginhosts_field_num]); + } /* check login host */ + + if (mysqlauth_shell_field) { + /* check shell field */ + if (row[shell_field_num] == NULL) { + mysql_free_result(result); + mysql_close(&mysql); + pop_log(p, POP_PRIORITY, HERE, "%s: sql shell is NULL", userdomain); + if (!AUTH_DEBUG) goto auth_bad; + return (pop_msg(p,POP_FAILURE, HERE, ERRMSG_NULL, userdomain, "shell")); + } + else { + DEBUG_LOG2 ( p, "%s: sql shell is \"%s\"", userdomain, + row[shell_field_num]); + } + } /* check shell */ + + if (mysqlauth_spool_field) { + /* check spool field */ + if (row[spool_field_num] == NULL) { + DEBUG_LOG1 ( p, "%s: spool will be determined by server", userdomain); + } + else { + DEBUG_LOG2 ( p, "%s: sql spool is \"%s\"", userdomain, + row[spool_field_num]); + } + } /* check spool */ + + /* make sure we can get a password for the check in pop_pass() later */ + if (row[0] == NULL) { + mysql_free_result(result); + mysql_close(&mysql); + pop_log(p, POP_PRIORITY, HERE, "%s: sql password is NULL", userdomain); + if (!AUTH_DEBUG) goto auth_bad; + return (pop_msg(p,POP_FAILURE, HERE, ERRMSG_NULL, userdomain, "password")); + } + else { + DEBUG_LOG1 ( p, "%s: sql password is (hidden)", userdomain); + } + + pw = (struct passwd *)malloc(sizeof(struct passwd)); + +#if defined(__bsdi__) && _BSDI_VERSION >= 199608 + pw->pw_class = malloc(1); + pw->pw_class='\0'; +#endif + + pw->pw_name = malloc(strlen(p->user)+2); + strcpy(pw->pw_name, p->user); + + pw->pw_passwd = malloc(strlen(row[0])+2); + strcpy(pw->pw_passwd, row[0]); + + /* non-virtual setup will return just POP_MAILDIR */ + /* i.e. /var/spool/mail/ */ + /* virtual setup will return POP_MAILDIR + domain */ + /* i.e. /var/spool/mail/mydomain.com/ */ + /* NOTE: hashing is done afterwards! */ + /* so a virtual setup with a hash of 2 might result in */ + /* /var/spool/mail/mydomain.com/u/s/user */ + if ( p->pHome_dir_mail != NULL ) { + pw->pw_dir = malloc(strlen(MYSQL_DEFAULT_HOMEDIR)+2); + strcpy(pw->pw_dir, MYSQL_DEFAULT_HOMEDIR); + strcat(pw->pw_dir, "/"); + } + else if (strcmp(p->domain,"NULL")) { + pw->pw_dir = malloc(strlen(POP_MAILDIR)+strlen(p->domain)+4); + strcpy(pw->pw_dir, POP_MAILDIR); + strcat(pw->pw_dir, "/"); + strcat(pw->pw_dir,p->domain); + strcat(pw->pw_dir, "/"); + } + else { + pw->pw_dir = malloc(strlen(POP_MAILDIR)+2); + strcpy(pw->pw_dir, POP_MAILDIR); + } + + if(mysqlauth_shell_field) { + pw->pw_shell = malloc(strlen(row[shell_field_num])+2); + strcpy(pw->pw_shell, row[shell_field_num]); + } else { + pw->pw_shell = malloc(12); + strcpy(pw->pw_shell, "/bin/false"); + } + if(mysqlauth_spool_field && row[spool_field_num] != NULL) { + strncpy(p->spool_file, row[spool_field_num], 255); + if (p->spool_file[strlen(p->spool_file)-1]=='/') + p->spool_file[strlen(p->spool_file)-1]='\0'; + } + if(mysqlauth_uid_field) { + pw->pw_uid = atoi(row[uid_field_num]); + } else { + pw->pw_uid = mysqlauth_uid; + } + if(mysqlauth_gid_field) { + pw->pw_gid = atoi(row[gid_field_num]); + } else { + pw->pw_gid = mysqlauth_gid; + } + + mysql_free_result(result); + mysql_close(&mysql); + p->pw = *pw; + + DEBUG_LOG6 ( p, + "%s: pop_user sql data: pw_name=\"%s\" pw_passwd=\"xxx\" pw_dir=\"%s\" pw_uid=\"%d\" pw_gid=\"%d\" pw_shell=\"%s\"", + p->user, + p->pw.pw_name, + p->pw.pw_dir, + p->pw.pw_uid, + p->pw.pw_gid, + p->pw.pw_shell + ); + + DEBUG_LOG3 ( p, "%s: home via mysql (%d): '%s'", + p->user, strlen(p->pw.pw_dir), p->pw.pw_dir ); + +auth_bad: +#else /* not MYSQLAUTH */ + p->spool_file = malloc(6); + strcpy(p->spool_file, "NULL"); pw = getpwnam ( p->user ); /* get pointer to info */ if ( pw != NULL ) { p->pw = *pw; /* copy it */ @@ -238,6 +635,7 @@ DEBUG_LOG2 ( p, "name (%lu): '%s'", strlen(p->pw.pw_name), p->pw.pw_name ); } +#endif /* MYSQLAUTH */ #ifdef SCRAM_ONLY return ( pop_auth_fail ( p, POP_FAILURE, HERE, diff -ruN qpopper4.0.18/popper/pop_util.c qpopper4.0.18-mysql-0.16/popper/pop_util.c --- qpopper4.0.18/popper/pop_util.c 2009-01-09 17:28:25.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/popper/pop_util.c 2009-07-23 16:47:34.033783825 -0600 @@ -114,3 +114,29 @@ free(ltm); return rfc822_time; } + +void remove_first(char *inpstr) +{ +int newpos,oldpos; + +newpos=0; oldpos=0; +/* find first word */ +while(inpstr[oldpos]==' ') { + if (!inpstr[oldpos]) { inpstr[0]=0; return; } + oldpos++; + } +/* find end of first word */ +while(inpstr[oldpos]!=' ') { + if (!inpstr[oldpos]) { inpstr[0]=0; return; } + oldpos++; + } +/* find second word */ +while(inpstr[oldpos]==' ') { + if (!inpstr[oldpos]) { inpstr[0]=0; return; } + oldpos++; + } +while(inpstr[oldpos]!=0) + inpstr[newpos++]=inpstr[oldpos++]; +inpstr[newpos]='\0'; +} + diff -ruN qpopper4.0.18/popper/version.h qpopper4.0.18-mysql-0.16/popper/version.h --- qpopper4.0.18/popper/version.h 2009-07-13 18:51:45.000000000 -0600 +++ qpopper4.0.18-mysql-0.16/popper/version.h 2009-07-23 17:15:47.985782144 -0600 @@ -26,7 +26,13 @@ # endif /* KRB5 */ #else /* not KERBEROS */ -# define VERS_SUF1 "" + +# ifdef MYSQLAUTH +# define VERS_SUF1 "-mysql-0.16" +# else +# define VERS_SUF1 "" +# endif + #endif /* KERBEROS */ #ifdef _DEBUG diff -ruN qpopper4.0.18/popper/xtnd_xlst.c qpopper4.0.18-mysql-0.16/popper/xtnd_xlst.c --- qpopper4.0.18/popper/xtnd_xlst.c 2009-01-09 17:28:26.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/popper/xtnd_xlst.c 2009-07-23 16:47:34.039786636 -0600 @@ -27,6 +27,7 @@ #include "config.h" #include "popper.h" +#include "snprintf.h" /* * xlst: POP XTND function to list headers from messages @@ -40,6 +41,8 @@ MsgInfoList * mp; /* Pointer to message info list */ int min,max; int len = strlen(p->pop_parm[2]); + char filename[256]; + /* Convert the first parameter into an integer */ if (p->parm_count==3) @@ -80,6 +83,15 @@ if ( ( mp->del_flag ) || ( mp->hide_flag ) ) continue; + if (p->MaildropType == MAILDIR_DROP) { + Qsnprintf(filename,sizeof(filename),"%s/%s",p->drop_name,mp->filename); + if (!(p->drop = fopen(filename, "r"))) { + DEBUG_LOG1 ( p, "Cannot open message file %s", filename); + return ( pop_msg ( p, POP_FAILURE, HERE, + "Cannot open message file" ) ); + } + } + /* * Position to the start of the message */ @@ -118,6 +130,10 @@ (void) fputs ( ".\r\n", p->output ); (void) fflush ( p->output ); + if (p->MaildropType == MAILDIR_DROP && p->drop) { + fclose ( p->drop ); + p->drop=NULL; + } return ( POP_SUCCESS ); } diff -ruN qpopper4.0.18/README.MAILDIR qpopper4.0.18-mysql-0.16/README.MAILDIR --- qpopper4.0.18/README.MAILDIR 1969-12-31 17:00:00.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/README.MAILDIR 2009-07-23 16:47:34.042786086 -0600 @@ -0,0 +1,117 @@ +This is a quick readme for my maildir support in Qpopper 4.0.x + +QPOPPER WITH MAILDIR +-------------------- + +SETUP +----- +Pass "--enable-maildrop-type=maildir" to the configure program +If you want qpopper to NOT check for missing new/, cur/, and tmp/ diretories, +and create them, also pass "--disable-maildir-dir-check" to configure + + OR + +In relevant config file set: + set maildrop-type = maildir + set check-maildir-dir = true|false + + +EXAMPLES +-------- +See file: example-maildir-configure.txt + + +NOTES +----- +The Maildir feature is under development! I'm not a Maildir or qpopper +programming guru, so until I mark it as stable, use at your own risk! +Although it should be noted that users have reported it working fine in +small and large production environments. Both with and without NFS. + +UIDLs + If a X-UIDL header is found, it will be used. If one is not found, a UIDL + will be generated from the message's filename. UIDL and STATUS headers will + NOT be written to the message file no matter what. Maildir protocol requires + that a message NOT be changed after it is delivered. +p->drop + This was a file descriptor to the maildrop for the mbox setup. Under + a Maildir setup, it is not used in the same way. It is only defined when a + function directly asks for it. i.e. it is set/open to the current filename/ + messaage wishing to be accessed, and immediately closed when finished. +BULLETINS + These are not implemented yet for a Maildir setup. + + +REPORTING A PROBLEM +------------------- +Before reporting a problem to me, you should configure and run qpopper +in debugging mode, and look through the generated logfile. This can +potentially be VERY helpful solving your problem. +To do this: + +1. Pass the --enable-debugging option to the ./configure script +2. Compile qpopper as usual with make +3. Run qpopper with the -t switch, and the path to a logfile your want + qpopper to write to. i.e. qpopper -t /var/log/qpopper.log +4. Connect to qpopper and cause the problem to happen. +5. Look through the log for debugging info. + +If reporting a problem to me, at thelittleprince@asteroid-b612.org +make sure to: + +1. Include a detailed description of the problem. +2. Your ./configure line +3. Your operating system/platform and version +4. Your version of qpopper +5. Your version of my patch +6. If you're using MySQL, the contents of your mysql-popper.conf file + (be sure to mask out your mysql host and access info) +7. If you're using MySQL, your version of MySQL. +8. If NOT a configure/compiling problem, include all of, or relevant + portions of your trace/debugging log file specified above, to me. +9. If IS a configure/compiling problem, include your config.log file + + +OTHER STUFF +----------- + +New p-> struct options + + /* CYGNUS - What kind of maildrop? mbox/maildir */ + maildrop_type MaildropType; + /* CYGNUS - Check and create maildir dirs */ + BOOL bCheck_maildir_dir; + +New MsgInfoList struct options + + char filename[PATH_MAX]; + time_t msg_time; + +offset flag will be always 0 under a maildir setup + +Added 2 config file options: + { "check-maildir-dir" , CfgBool , CfgResNone, kCHECK_MAILDIR_DIR }, + { "maildrop-type" , CfgMnem , CfgResUser, kMAILDROP_TYPE }, + + +New configure options + +--enable-maildrop-type=mbox + sets MAILDROP_TYPE to MBOX_DROP +--enable-maildrop-type=maildir + sets MAILDROP_TYPE to MAILDIR_DROP +if none specified + set MAILDROP_TYPE to MBOX_DROP +--disable-maildir-dir-check + set DONT_CHECK_MAILDIR_DIR + +add to config.h.in + +#define MBOX_DROP 0 +#define MAILDIR_DROP 1 + + + +-Tony +thelittleprince@asteroid-b612.org + diff -ruN qpopper4.0.18/README.MYSQL qpopper4.0.18-mysql-0.16/README.MYSQL --- qpopper4.0.18/README.MYSQL 1969-12-31 17:00:00.000000000 -0700 +++ qpopper4.0.18-mysql-0.16/README.MYSQL 2009-07-23 16:47:34.047789639 -0600 @@ -0,0 +1,291 @@ +This is a quick readme for my mysql support in Qpopper 4.0.x + +Sample mysql config directives are stored in the mysql-popper.conf +file in the main directory. + +Configure directives relative to mysql support are: + --with-mysqllibpath=path + Set the mysql library path [/usr/lib/mysql] + This is where your libmysqlclient.so files are + --with-mysqlincludepath=path + Set the mysql include path [/usr/include/mysql] + This is where your mysql.h files are + --enable-mysql + compile in mysql authentication + --with-mysqlconfig=path + Set the mysql-popper.conf file path [/etc/mysql-popper.conf] + --enable-log-login-mysql + Log successful user authentications into mysql database + + +EXAMPLES +-------- +See file: example-mysql-configure.txt + + +SETUP +----- +* Configure, compile, and install qpopper normally with the desired +options from above. + +* Copy the config file. +Copy mysql-popper.conf into /etc/mysql-popper.conf or whatever you +set --with-mysqlconfig= to +Edit mysql-popper.conf with your mysql db info, table names, and fields +you will use. + +* Create the mysql tables. +This patch does NOT create mysql tables for you. + +For --enable-log-login-mysql, currently the table and field definitions +are hardcoded. It should be defined/created in mysql as follows, where +ip is the client IP and ts is the current timestamp in unix time: + +CREATE TABLE relay_ip ( + ip char(15) NOT NULL default '', + ts int(11) NOT NULL default '0', + KEY ip(ip) +); + +The mysql authentication table should look something like this: + +CREATE TABLE email ( + username char(128) NOT NULL, + domain char(64), + uid int(10), + gid int(10), + status int(2) DEFAULT '0', + shell char(127), + password char(64) NOT NULL, + spool char(255), + loginhosts char(255) NOT NULL +); + +username : The login name for the pop3 account +domain : In a virtual domain setup, the domain the user is logging + into. e.g. if the user is logging in as user@domain.com + the username field should contain 'user' and the domain field + should contain 'domain.com' +uid : The UID the logged-in user's process will run as (if specified + to check for in the .conf file). This should be a UID that + has access to the user's mail spool. +gid : The GID the logged-in user's process will run as (if specified + to check for in the .conf file). This should be a GID that + has access to the user's mail spool. +status : The status of the user account. This determines if the user + can login or not. Use '0' for disabled, '1' for enabled, '2' + for suspended, '3' for on-hold. 1 is the only status that + does NOT deny access. 2 and 3 are more of an 'informational' + disabled status. +shell : The shell a user will get when logged-in. This is mostly just + for satisfying some requirements. You should set this to a + non-active/non-existant shell. e.g. /bin/false +password : The password for the user account, in one of the 4 types + (as you designate in the .conf file). Namely, + cleartext (just plain text in mysql), + crypt (made with ENCRYPT('userpassword') in mysql), + md5 (made with MD5('userpassword') in mysql), and + mysql (made with PASSWORD('userpassword') in mysql). + Set this to the actual password, not the TYPE of password! + e.g. don't set this value to 'cleartext'. +spool : The path to the location of the user's mail spool (if specified + to check for in the .conf file, otherwise calculated). +loginhosts : For multiple-pop3 server installations, a space delimited list + of hosts the user can log into. To allow them to log into all + hosts, specify a * (use this for single-server installations). + To allow them to log into, say, host1.domain.com and + host2.domain.com, you'd set this to: + 'host1.domain.com host2.domain.com' + +* Run the qpopper daemon +DO NOT specify the mysql-popper.conf file on the command line with the +-f switch. Qpopper already knows where the file is, and to read it on +startup. + + +NOTES: +The only REQUIRED fields are username and password. +The other fields you only need to create if you query for said fields in +your mysql-popper.conf configuration. + +* Populate the mysql table(s) with data. +How to populate the auth. table with users is beyond the scope of this +document. Reference your MySQL docs for the SQL commands to do this. +But here's a quick example for an email of thelittleprince@asteroid-b612.org +using ALL mysql fields. + INSERT into email VALUES ( + 'thelittleprince', + 'asteroid-b612.org', + '500', + '100', + '1', + '/bin/false', + ENCRYPT('userpassword'), + '/var/spool/mail/asteroid-b612.org/thelittleprince', + '*' + ); + + +DIRECTORY STRUCTURES (PLEASE READ!) +----------------------------------- +When you use mysql, either with virtual or non-virtual set ups, +spool directory or HOMEDIRMAIL set ups, some of the directory +paths are constructed differently. Here is a run-down of how paths +are constructed with this patch: +In these examples POP_DROP_DIR is represented as /var/tmp/.pop +POP_CACHE_DIR as /var/tmp/.cache +POP_MAILDIR as /var/spool/mail +and MYSQL_DEFAULT_HOMEDIR as /home + +NON-VIRTUAL (i.e. USER user) + POP_MAILDIR SETUP: + GNPH_PATH : /var/spool/mail + GNPH_PATH (HASHED): /var/spool/mail/u/s + GNPH_SPOOL : /var/spool/mail/user + GNPH_SPOOL (HASHED): /var/spool/mail/u/s/user + GNPH_OLDPOP : /var/spool/mail/.user.pop + GNPH_OLDPOP (HASHED): /var/spool/mail/u/s/.user.pop + GNPH_POP : /var/tmp/.pop/.user.pop + GNPH_POP (HASHED): /var/tmp/.pop/u/s/.user.pop + GNPH_CACHE : /var/tmp/.cache/.user.cache + GNPH_CACHE (HASHED): /var/tmp/.cache/u/s/.user.cache + HOMEDIRMAIL SETUP: + GNPH_PATH : /home/user + GNPH_PATH (HASHED): /home/u/s/user + GNPH_SPOOL : /home/user/mbox + GNPH_SPOOL (HASHED): /home/u/s/user/mbox + GNPH_OLDPOP : /var/spool/mail/.user.pop + GNPH_OLDPOP (HASHED): /var/spool/mail/u/s/.user.pop + GNPH_POP : /var/tmp/.pop/.user.pop + GNPH_POP (HASHED): /var/tmp/.pop/u/s/.user.pop + GNPH_CACHE : /var/tmp/.cache/.user.cache + GNPH_CACHE (HASHED): /var/tmp/.cache/u/s/.user.cache + SPECIFIEDSPOOL SETUP (MysqlSpoolField/spool-file): + In this setup hashed and non-hashed are the same for GNPH_PATH + and GNPH_SPOOL, beacuse you are specifying in the db's table field + the hashing structure yourself, so it is not calculated. + GNPH_PATH : specifiedspool minus last /'d path arg + GNPH_SPOOL : specifiedspool + GNPH_OLDPOP : /var/spool/mail/.user.pop + GNPH_OLDPOP (HASHED): /var/spool/mail/u/s/.user.pop + GNPH_POP : /var/tmp/.pop/.user.pop + GNPH_POP (HASHED): /var/tmp/.pop/u/s/.user.pop + GNPH_CACHE : /var/tmp/.cache/.user.cache + GNPH_CACHE (HASHED): /var/tmp/.cache/u/s/.user.cache + +VIRTUAL SETUP (i.e. USER user@domain.com, you set one of the + virtual domain options in mysql-popper.conf) + POP_MAILDIR SETUP: + GNPH_PATH : /var/spool/mail/domain.com + GNPH_PATH (HASHED): /var/spool/mail/domain.com/u/s + GNPH_SPOOL : /var/spool/mail/domain.com/user + GNPH_SPOOL (HASHED): /var/spool/mail/domain.com/u/s/user + GNPH_OLDPOP : /var/spool/mail/domain.com/.user.pop + GNPH_OLDPOP (HASHED): /var/spool/mail/domain.com/u/s/.user.pop + GNPH_POP : /var/tmp/.pop/.user@domain.com.pop + GNPH_POP (HASHED): /var/tmp/.pop/u/s/.user@domain.com.pop + GNPH_CACHE : /var/tmp/.cache/.user@domain.com.cache + GNPH_CACHE (HASHED): /var/tmp/.cache/u/s/.user@domain.com.cache + HOMEDIRMAIL SETUP: + GNPH_PATH : /home/domain.com/user + GNPH_PATH (HASHED): /home/domain.com/u/s/user + GNPH_SPOOL : /home/domain.com/user/mbox + GNPH_SPOOL (HASHED): /home/domain.com/u/s/user/mbox + GNPH_OLDPOP : /var/spool/mail/domain.com/.user.pop + GNPH_OLDPOP (HASHED): /var/spool/mail/domain.com/u/s/.user.pop + GNPH_POP : /var/tmp/.pop/.user@domain.com.pop + GNPH_POP (HASHED): /var/tmp/.pop/u/s/.user@domain.com.pop + GNPH_CACHE : /var/tmp/.cache/.user@domain.com.cache + GNPH_CACHE (HASHED): /var/tmp/.cache/u/s/.user@domain.com.cache + SPECIFIEDSPOOL SETUP (MysqlSpoolField/spool-file): + In this setup hashed and non-hashed are the same for GNPH_PATH + and GNPH_SPOOL, beacuse you are specifying in the db's table field + the hashing structure yourself, so it is not calculated. + GNPH_PATH : specifiedspool minus last /'d path arg + GNPH_SPOOL : specifiedspool + GNPH_OLDPOP : /var/spool/mail/domain.com/.user.pop + GNPH_OLDPOP (HASHED): /var/spool/mail/domain.com/u/s/.user.pop + GNPH_POP : /var/tmp/.pop/.user@domain.com.pop + GNPH_POP (HASHED): /var/tmp/.pop/u/s/.user@domain.com.pop + GNPH_CACHE : /var/tmp/.cache/.user@domain.com.cache + GNPH_CACHE (HASHED): /var/tmp/.cache/u/s/.user@domain.com.cache + +USER CONFIG OPTIONS: + POP_MAILDIR SETUP: + NON-VIRTUAL: + /var/spool/mail/.user.qpopper-options + VIRTUAL: + /var/spool/mail/.user@domain.com.qpopper-options + HOMEDIRMAIL SETUP: + NON-VIRTUAL: + /home/user/.qpopper-options + /home/u/s/user/.qpopper-options + VIRTUAL: + /home/domain.com/user/.qpopper-options + /home/domain.com/u/s/user/.qpopper-options + +If you're using HOMEDIRMAIL, the MYSQL_DEFAULT_HOMEDIR variable +in pop_user.c controls the base home directory for users. The default +is "/home" + +HOW THE MYSQL AUTHENTICATION WORKS +---------------------------------- +In pop_user() a mysql connection and query is made with the username +entered as the search field (and the user's domain in a virtual setup). +The resulting data is stored in the passwd structure that would normally +be populated by getpwnam(). +In pop_pass() the cached DB password in the passwd structure is compared +against the entered password, using whatever method is specified in the +MysqlAuthPasswordMethod config file option. +On success and enabling of log-login-mysql, an additional mysql connection +is made to the above table and appended/updated with the client's ip +and the current unix timestamp. + +REPORTING A PROBLEM +------------------- +Before reporting a problem to me, you should configure and run qpopper +in debugging mode, and look through the generated logfile. This can +potentially be VERY helpful solving your problem. +To do this: + +1. Pass the --enable-debugging option to the ./configure script +2. Compile qpopper as usual with make +3. Run qpopper with the -t switch, and the path to a logfile your want + qpopper to write to. i.e. qpopper -t /var/log/qpopper.log +4. Connect to qpopper and cause the problem to happen. +5. Look through the log for debugging info. + +If reporting a problem to me, at thelittleprince@asteroid-b612.org +make sure to: + +1. Include a detailed description of the problem. +2. Your ./configure line +3. Your operating system/platform and version +4. Your version of qpopper +5. Your version of my patch +6. If you're using MySQL, the contents of your mysql-popper.conf file + (be sure to mask out your mysql host and access info) +7. If you're using MySQL, your version of MySQL. +8. If NOT a configure/compiling problem, include all of, or relevant + portions of your trace/debugging log file specified above, to me. +9. If IS a configure/compiling problem, include your config.log file + +EXTRA NOTES +----------- +If using the log-login-mysql option in conjunction with your MTA for +host relay checking, i'm told this is the proper relay config for Exim: + +host_accept_relay = "localhost:mysql;SELECT ip FROM +relay_ip WHERE ip='${sender_host_address}'" + +or if you want to only select on ips less than 180 secs (3 min) old.. + +host_accept_relay = "localhost:mysql;SELECT ip FROM +relay_ip WHERE ip='${sender_host_address}' AND +((UNIX_TIMESTAMP() - 180) < ts)" + + + +-Tony +thelittleprince@asteroid-b612.org +