/* ========================================================================
* Copyright 1988-2008 University of Washington
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* ========================================================================
*/
/*
* Program: UNIX environment routines
*
* Author: Mark Crispin
* UW Technology
* University of Washington
* Seattle, WA 98195
* Internet: MRC@Washington.EDU
*
* Date: 1 August 1988
* Last Edited: 15 May 2008
*/
#include <grp.h>
#include <signal.h>
#include <sys/wait.h>
/* in case stat.h is ancient */
#ifndef S_IRUSR
#define S_IRUSR S_IREAD
#endif
#ifndef S_IWUSR
#define S_IWUSR S_IWRITE
#endif
#ifndef S_IXUSR
#define S_IXUSR S_IEXEC
#endif
#ifndef S_IRGRP
#define S_IRGRP (S_IREAD >> 3)
#endif
#ifndef S_IWGRP
#define S_IWGRP (S_IWRITE >> 3)
#endif
#ifndef S_IXGRP
#define S_IXGRP (S_IEXEC >> 3)
#endif
#ifndef S_IROTH
#define S_IROTH (S_IREAD >> 6)
#endif
#ifndef S_IWOTH
#define S_IWOTH (S_IWRITE >> 6)
#endif
#ifndef S_IXOTH
#define S_IXOTH (S_IEXEC >> 6)
#endif
/* c-client environment parameters */
static char *myUserName = NIL; /* user name */
static char *myHomeDir = NIL; /* home directory name */
static char *myServerName = NIL;/* server name */
static char *myLocalHost = NIL; /* local host name */
static char *myNewsrc = NIL; /* newsrc file name */
static char *mailsubdir = NIL; /* mailbox subdirectory name */
static char *sysInbox = NIL; /* system inbox name */
static char *newsActive = NIL; /* news active file */
static char *newsSpool = NIL; /* news spool */
static char *blackBoxDir = NIL; /* black box directory name */
/* black box default home directory */
static char *blackBoxDefaultHome = NIL;
static char *sslCApath = NIL; /* non-standard CA path */
static short anonymous = NIL; /* is anonymous */
static short blackBox = NIL; /* is a black box */
static short closedBox = NIL; /* is a closed box (uses chroot() jail) */
static short restrictBox = NIL; /* is a restricted box */
static short has_no_life = NIL; /* is a cretin with no life */
/* block environment init */
static short block_env_init = NIL;
static short hideDotFiles = NIL;/* hide files whose names start with . */
/* advertise filesystem root */
static short advertisetheworld = NIL;
/* only advertise own mailboxes and #shared */
static short limitedadvertise = NIL;
/* disable automatic shared namespaces */
static short noautomaticsharedns = NIL;
static short no822tztext = NIL; /* disable RFC [2]822 timezone text */
/* client principals include service name */
static short kerb_cp_svr_name = NIL;
static long locktimeout = 5; /* default lock timeout in minutes */
/* default prototypes */
static MAILSTREAM *createProto = NIL;
static MAILSTREAM *appendProto = NIL;
/* default user flags */
static char *userFlags[NUSERFLAGS] = {NIL};
static NAMESPACE *nslist[3]; /* namespace list */
static int logtry = 3; /* number of server login tries */
/* block notification */
static blocknotify_t mailblocknotify = mm_blocknotify;
/* logout function */
static logouthook_t maillogouthook = NIL;
/* logout data */
static void *maillogoutdata = NIL;
/* allow user config files */
static short allowuserconfig = NIL;
/* 1 = disable plaintext, 2 = if not SSL */
static long disablePlaintext = NIL;
static long list_max_level = 20;/* maximum level of list recursion */
/* facility for syslog */
static int syslog_facility = LOG_MAIL;
/* Path of the privileged system lock program (mlock). Normally set by
* logic test.
*/
static char *lockpgm = LOCKPGM;
/* Directory used for shared locks. MUST be the same for all users of the
* system, and MUST be protected 1777. /var/tmp may be preferable on some
* systems.
*/
static const char *tmpdir = "/tmp";
/* Do not change shlock_mode. Doing so can cause mailbox corruption and
* denial of service. It also defeats the entire purpose of the shared
* lock mechanism. The right way to avoid shared locks is to set up a
* closed box (see the closedBox setting).
*/
/* shared lock mode */
static const int shlock_mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
/* It is STRONGLY recommended that you do not change dotlock_mode. Doing so
* can cause denial of service with old dot-lock files left lying around.
* However, since dot-locks are only used with traditional UNIX and MMDF
* formats which are not normally shared, it is much less harmful to tamper
* with this than with shlock_mode.
*/
/* dot-lock mode */
static long dotlock_mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
/* File/directory access and protection policies */
/* Unlike shlock_mode, the ????_protection modes are intended to be fully
* customizable according to site policy. The values here are recommended
* settings, based upon the documented purposes of the namespaces.
*/
/* user space - only owner can read/write */
static char *myMailboxDir = NIL;/* user space directory name */
/* default file protection */
static long mbx_protection = S_IRUSR|S_IWUSR;
/* default directory protection */
static long dir_protection = S_IRUSR|S_IWUSR|S_IXUSR;
/* user space for user "anonymous" */
/* anonymous home directory */
static char *anonymousHome = NIL;
/* #ftp - everybody can read, only owner can write */
static char *ftpHome = NIL; /* ftp export home directory */
/* default ftp file protection */
static long ftp_protection = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;
static long ftp_dir_protection =/* default ftp directory protection */
S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH;
/* #public - everybody can read/write */
static char *publicHome = NIL; /* public home directory */
static long public_protection = /* default public file protection */
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
/* default public directory protection */
static long public_dir_protection =
S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH;
/* #shared/ - owner and group members can read/write */
static char *sharedHome = NIL; /* shared home directory */
/* default shared file protection */
static long shared_protection = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP;
/* default shared directory protection */
static long shared_dir_protection =
S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP;
/* OS bug workarounds - should be avoided at all cost */
/* Don't set fcntlhangbug unless you really have to, since it risks mailbox
* corruption. The flocksim.c mechanism is designed to detect NFS access
* and no-op in that cases only, so this flag should be unnecessary.
*/
static short fcntlhangbug = NIL;/* flock() emulator using fcntl() is a no-op */
/* Don't set netfsstatbug unless you really have to, since it dramatically
* slows down traditional UNIX and MMDF mailbox performance.
*/
static short netfsstatbug = NIL;/* compensate for broken stat() on network
* filesystems (AFS and old NFS)
*/
/* Note: setting disableLockWarning means that you assert that the
* so-modified copy of this software will NEVER be used:
* 1) in conjunction with any software which expects .lock files
* 2) to access NFS-mounted files and directories
*
* Unless both of these conditions apply, then do not set this flag.
* Instead, read the FAQ (item 7.10) and either use 1777 protection
* on the mail spool, or install mlock.
*
* In addition, by setting this flag you also agree that you are fully
* legally and morally responsible when (not if) mail files are damaged
* as the result of your choice.
*
* The mlock tool exists for a reason. Use it.
*/
/* disable warning if can't make .lock file */
static short disableLockWarning = NIL;
/* UNIX Namespaces */
/* personal mh namespace */
static NAMESPACE nsmhf = {"#mh/",'/',NIL,NIL};
static NAMESPACE nsmh = {"#mhinbox",NIL,NIL,&nsmhf};
/* home namespace */
static NAMESPACE nshome = {"",'/',NIL,&nsmh};
/* UNIX other user namespace */
static NAMESPACE nsunixother = {"~",'/',NIL,NIL};
/* black box other user namespace */
static NAMESPACE nsblackother = {"/",'/',NIL,NIL};
/* public (anonymous OK) namespace */
static NAMESPACE nspublic = {"#public/",'/',NIL,NIL};
/* netnews namespace */
static NAMESPACE nsnews = {"#news.",'.',NIL,&nspublic};
/* FTP export namespace */
static NAMESPACE nsftp = {"#ftp/",'/',NIL,&nsnews};
/* shared (no anonymous) namespace */
static NAMESPACE nsshared = {"#shared/",'/',NIL,&nsftp};
/* world namespace */
static NAMESPACE nsworld = {"/",'/',NIL,&nsshared};
/* only shared and public namespaces */
static NAMESPACE nslimited = {"#shared/",'/',NIL,&nspublic};
#include "write.c" /* include safe writing routines */
#include "crexcl.c" /* include exclusive create */
#include "pmatch.c" /* include wildcard pattern matcher */
/* Get all authenticators */
#include "auths.c"
/* Environment manipulate parameters
* Accepts: function code
* function-dependent value
* Returns: function-dependent return value
*/
void *env_parameters (long function,void *value)
{
void *ret = NIL;
switch ((int) function) {
case GET_NAMESPACE:
ret = (void *) nslist;
break;
case SET_USERNAME:
if (myUserName) fs_give ((void **) &myUserName);
myUserName = cpystr ((char *) value);
case GET_USERNAME:
ret = (void *) myUserName;
break;
case SET_HOMEDIR:
if (myHomeDir) fs_give ((void **) &myHomeDir);
myHomeDir = cpystr ((char *) value);
case GET_HOMEDIR:
ret = (void *) myHomeDir;
break;
case SET_LOCALHOST:
if (myLocalHost) fs_give ((void **) &myLocalHost);
myLocalHost = cpystr ((char *) value);
case GET_LOCALHOST:
ret = (void *) myLocalHost;
break;
case SET_NEWSRC:
if (myNewsrc) fs_give ((void **) &myNewsrc);
myNewsrc = cpystr ((char *) value);
case GET_NEWSRC:
ret = (void *) myNewsrc;
break;
case SET_NEWSACTIVE:
if (newsActive) fs_give ((void **) &newsActive);
newsActive = cpystr ((char *) value);
case GET_NEWSACTIVE:
ret = (void *) newsActive;
break;
case SET_NEWSSPOOL:
if (newsSpool) fs_give ((void **) &newsSpool);
newsSpool = cpystr ((char *) value);
case GET_NEWSSPOOL:
ret = (void *) newsSpool;
break;
case SET_ANONYMOUSHOME:
if (anonymousHome) fs_give ((void **) &anonymousHome);
anonymousHome = cpystr ((char *) value);
case GET_ANONYMOUSHOME:
if (!anonymousHome) anonymousHome = cpystr (ANONYMOUSHOME);
ret = (void *) anonymousHome;
break;
case SET_FTPHOME:
if (ftpHome) fs_give ((void **) &ftpHome);
ftpHome = cpystr ((char *) value);
case GET_FTPHOME:
ret = (void *) ftpHome;
break;
case SET_PUBLICHOME:
if (publicHome) fs_give ((void **) &publicHome);
publicHome = cpystr ((char *) value);
case GET_PUBLICHOME:
ret = (void *) publicHome;
break;
case SET_SHAREDHOME:
if (sharedHome) fs_give ((void **) &sharedHome);
sharedHome = cpystr ((char *) value);
case GET_SHAREDHOME:
ret = (void *) sharedHome;
break;
case SET_SYSINBOX:
if (sysInbox) fs_give ((void **) &sysInbox);
sysInbox = cpystr ((char *) value);
case GET_SYSINBOX:
ret = (void *) sysInbox;
break;
case SET_SSLCAPATH: /* this can be set null */
if (sslCApath) fs_give ((void **) &sslCApath);
sslCApath = value ? cpystr ((char *) value) : value;
break;
case GET_SSLCAPATH:
ret = (void *) sslCApath;
break;
case SET_LISTMAXLEVEL:
list_max_level = (long) value;
case GET_LISTMAXLEVEL:
ret = (void *) list_max_level;
break;
case SET_MBXPROTECTION:
mbx_protection = (long) value;
case GET_MBXPROTECTION:
ret = (void *) mbx_protection;
break;
case SET_DIRPROTECTION:
dir_protection = (long) value;
case GET_DIRPROTECTION:
ret = (void *) dir_protection;
break;
case SET_LOCKPROTECTION:
dotlock_mode = (long) value;
case GET_LOCKPROTECTION:
ret = (void *) dotlock_mode;
break;
case SET_FTPPROTECTION:
ftp_protection = (long) value;
case GET_FTPPROTECTION:
ret = (void *) ftp_protection;
break;
case SET_PUBLICPROTECTION:
public_protection = (long) value;
case GET_PUBLICPROTECTION:
ret = (void *) public_protection;
break;
case SET_SHAREDPROTECTION:
shared_protection = (long) value;
case GET_SHAREDPROTECTION:
ret = (void *) shared_protection;
break;
case SET_FTPDIRPROTECTION:
ftp_dir_protection = (long) value;
case GET_FTPDIRPROTECTION:
ret = (void *) ftp_dir_protection;
break;
case SET_PUBLICDIRPROTECTION:
public_dir_protection = (long) value;
case GET_PUBLICDIRPROTECTION:
ret = (void *) public_dir_protection;
break;
case SET_SHAREDDIRPROTECTION:
shared_dir_protection = (long) value;
case GET_SHAREDDIRPROTECTION:
ret = (void *) shared_dir_protection;
break;
case SET_LOCKTIMEOUT:
locktimeout = (long) value;
case GET_LOCKTIMEOUT:
ret = (void *) locktimeout;
break;
case SET_DISABLEFCNTLLOCK:
fcntlhangbug = value ? T : NIL;
case GET_DISABLEFCNTLLOCK:
ret = (void *) (fcntlhangbug ? VOIDT : NIL);
break;
case SET_LOCKEACCESERROR:
disableLockWarning = value ? NIL : T;
case GET_LOCKEACCESERROR:
ret = (void *) (disableLockWarning ? NIL : VOIDT);
break;
case SET_HIDEDOTFILES:
hideDotFiles = value ? T : NIL;
case GET_HIDEDOTFILES:
ret = (void *) (hideDotFiles ? VOIDT : NIL);
break;
case SET_DISABLEPLAINTEXT:
disablePlaintext = (long) value;
case GET_DISABLEPLAINTEXT:
ret = (void *) disablePlaintext;
break;
case SET_CHROOTSERVER:
closedBox = value ? T : NIL;
case GET_CHROOTSERVER:
ret = (void *) (closedBox ? VOIDT : NIL);
break;
case SET_ADVERTISETHEWORLD:
advertisetheworld = value ? T : NIL;
case GET_ADVERTISETHEWORLD:
ret = (void *) (advertisetheworld ? VOIDT : NIL);
break;
case SET_LIMITEDADVERTISE:
limitedadvertise = value ? T : NIL;
case GET_LIMITEDADVERTISE:
ret = (void *) (limitedadvertise ? VOIDT : NIL);
break;
case SET_DISABLEAUTOSHAREDNS:
noautomaticsharedns = value ? T : NIL;
case GET_DISABLEAUTOSHAREDNS:
ret = (void *) (noautomaticsharedns ? VOIDT : NIL);
break;
case SET_DISABLE822TZTEXT:
no822tztext = value ? T : NIL;
case GET_DISABLE822TZTEXT:
ret = (void *) (no822tztext ? VOIDT : NIL);
break;
case SET_USERHASNOLIFE:
has_no_life = value ? T : NIL;
case GET_USERHASNOLIFE:
ret = (void *) (has_no_life ? VOIDT : NIL);
break;
case SET_KERBEROS_CP_SVR_NAME:
kerb_cp_svr_name = value ? T : NIL;
case GET_KERBEROS_CP_SVR_NAME:
ret = (void *) (kerb_cp_svr_name ? VOIDT : NIL);
break;
case SET_NETFSSTATBUG:
netfsstatbug = value ? T : NIL;
case GET_NETFSSTATBUG:
ret = (void *) (netfsstatbug ? VOIDT : NIL);
break;
case SET_BLOCKENVINIT:
block_env_init = value ? T : NIL;
case GET_BLOCKENVINIT:
ret = (void *) (block_env_init ? VOIDT : NIL);
break;
case SET_BLOCKNOTIFY:
mailblocknotify = (blocknotify_t) value;
case GET_BLOCKNOTIFY:
ret = (void *) mailblocknotify;
break;
case SET_LOGOUTHOOK:
maillogouthook = (logouthook_t) value;
case GET_LOGOUTHOOK:
ret = maillogouthook;
break;
case SET_LOGOUTDATA:
maillogoutdata = (void *) value;
case GET_LOGOUTDATA:
ret = maillogoutdata;
}
return ret;
}
/* Write current time
* Accepts: destination string
* optional format of day-of-week prefix
* format of date and time
* flag whether to append symbolic timezone
*/
static void do_date (char *date,char *prefix,char *fmt,int suffix)
{
time_t tn = time (0);
struct tm *t = gmtime (&tn);
int zone = t->tm_hour * 60 + t->tm_min;
int julian = t->tm_yday;
t = localtime (&tn); /* get local time now */
/* minus UTC minutes since midnight */
zone = t->tm_hour * 60 + t->tm_min - zone;
/* julian can be one of:
* 36x local time is December 31, UTC is January 1, offset -24 hours
* 1 local time is 1 day ahead of UTC, offset +24 hours
* 0 local time is same day as UTC, no offset
* -1 local time is 1 day behind UTC, offset -24 hours
* -36x local time is January 1, UTC is December 31, offset +24 hours
*/
if (julian = t->tm_yday -julian)
zone += ((julian < 0) == (abs (julian) == 1)) ? -24*60 : 24*60;
if (prefix) { /* want day of week? */
sprintf (date,prefix,days[t->tm_wday]);
date += strlen (date); /* make next sprintf append */
}
/* output the date */
sprintf (date,fmt,t->tm_mday,months[t->tm_mon],t->tm_year+1900,
t->tm_hour,t->tm_min,t->tm_sec,zone/60,abs (zone) % 60);
/* append timezone suffix if desired */
if (suffix) rfc822_timezone (date,(void *) t);
}
/* Write current time in RFC 822 format
* Accepts: destination string
*/
void rfc822_date (char *date)
{
do_date (date,"%s, ","%d %s %d %02d:%02d:%02d %+03d%02d",
no822tztext ? NIL : T);
}
/* Write current time in fixed-width RFC 822 format
* Accepts: destination string
*/
void rfc822_fixed_date (char *date)
{
do_date (date,NIL,"%02d %s %4d %02d:%02d:%02d %+03d%02d",NIL);
}
/* Write current time in internal format
* Accepts: destination string
*/
void internal_date (char *date)
{
do_date (date,NIL,"%02d-%s-%d %02d:%02d:%02d %+03d%02d",NIL);
}
/* Initialize server
* Accepts: server name for syslog or NIL
* /etc/services service name or NIL
* alternate /etc/services service name or NIL
* clock interrupt handler
* kiss-of-death interrupt handler
* hangup interrupt handler
* termination interrupt handler
*/
void server_init (char *server,char *service,char *sslservice,
void *clkint,void *kodint,void *hupint,void *trmint,
void *staint)
{
int onceonly = server && service && sslservice;
if (onceonly) { /* set server name in syslog */
int mask;
openlog (myServerName = cpystr (server),LOG_PID,syslog_facility);
fclose (stderr); /* possibly save a process ID */
dorc (NIL,NIL); /* do systemwide configuration */
switch (mask = umask (022)){/* check old umask */
case 0: /* definitely unreasonable */
case 022: /* don't need to change it */
break;
default: /* already was a reasonable value */
umask (mask); /* so change it back */
}
}
arm_signal (SIGALRM,clkint); /* prepare for clock interrupt */
arm_signal (SIGUSR2,kodint); /* prepare for Kiss Of Death */
arm_signal (SIGHUP,hupint); /* prepare for hangup */
arm_signal (SIGPIPE,hupint); /* alternative hangup */
arm_signal (SIGTERM,trmint); /* prepare for termination */
/* status dump */
if (staint) arm_signal (SIGUSR1,staint);
if (onceonly) { /* set up network and maybe SSL */
long port;
struct servent *sv;
/* Use SSL if SSL service, or if server starts with "s" and not service */
if (((port = tcp_serverport ()) >= 0)) {
if ((sv = getservbyname (service,"tcp")) && (port == ntohs (sv->s_port)))
syslog (LOG_DEBUG,"%s service init from %s",service,tcp_clientaddr ());
else if ((sv = getservbyname (sslservice,"tcp")) &&
(port == ntohs (sv->s_port))) {
syslog (LOG_DEBUG,"%s SSL service init from %s",sslservice,
tcp_clientaddr ());
ssl_server_init (server);
}
else { /* not service or SSL service port */
syslog (LOG_DEBUG,"port %ld service init from %s",port,
tcp_clientaddr ());
if (*server == 's') ssl_server_init (server);
}
}
}
}
/* Wait for stdin input
* Accepts: timeout in seconds
* Returns: T if have input on stdin, else NIL
*/
long server_input_wait (long seconds)
{
fd_set rfd,efd;
struct timeval tmo;
FD_ZERO (&rfd);
FD_ZERO (&efd);
FD_SET (0,&rfd);
FD_SET (0,&efd);
tmo.tv_sec = seconds; tmo.tv_usec = 0;
return select (1,&rfd,0,&efd,&tmo) ? LONGT : NIL;
}
/* Return UNIX password entry for user name
* Accepts: user name string
* Returns: password entry
*
* Tries all-lowercase form of user name if given user name fails
*/
static struct passwd *pwuser (unsigned char *user)
{
unsigned char *s;
struct passwd *pw = getpwnam (user);
if (!pw) { /* failed, see if any uppercase characters */
for (s = user; *s && ((*s < 'A') || (*s > 'Z')); s++);
if (*s) { /* yes, try all lowercase form */
pw = getpwnam (s = lcase (cpystr (user)));
fs_give ((void **) &s);
}
}
return pw;
}
/* Validate password for user name
* Accepts: user name string
* password string
* argument count
* argument vector
* Returns: password entry if validated
*
* Tries password+1 if password fails and starts with space
*/
static struct passwd *valpwd (char *user,char *pwd,int argc,char *argv[])
{
char *s;
struct passwd *pw;
struct passwd *ret = NIL;
if (auth_md5.server) { /* using CRAM-MD5 authentication? */
if (s = auth_md5_pwd (user)) {
if (!strcmp (s,pwd) || ((*pwd == ' ') && pwd[1] && !strcmp (s,pwd+1)))
ret = pwuser (user); /* validated, get passwd entry for user */
memset (s,0,strlen (s)); /* erase sensitive information */
fs_give ((void **) &s);
}
}
else if (pw = pwuser (user)) {/* can get user? */
s = cpystr (pw->pw_name); /* copy returned name in case we need it */
if (*pwd && !(ret = checkpw (pw,pwd,argc,argv)) &&
(*pwd == ' ') && pwd[1] && (ret = pwuser (s)))
ret = checkpw (pw,pwd+1,argc,argv);
fs_give ((void **) &s); /* don't need copy of name any more */
}
return ret;
}
/* Server log in
* Accepts: user name string
* password string
* authenticating user name string
* argument count
* argument vector
* Returns: T if password validated, NIL otherwise
*/
long server_login (char *user,char *pwd,char *authuser,int argc,char *argv[])
{
struct passwd *pw = NIL;
int level = LOG_NOTICE;
char *err = "failed";
/* cretins still haven't given up */
if ((strlen (user) >= NETMAXUSER) ||
(authuser && (strlen (authuser) >= NETMAXUSER))) {
level = LOG_ALERT; /* escalate this alert */
err = "SYSTEM BREAK-IN ATTEMPT";
logtry = 0; /* render this session useless */
}
else if (logtry-- <= 0) err = "excessive login failures";
else if (disablePlaintext) err = "disabled";
else if (!(authuser && *authuser)) pw = valpwd (user,pwd,argc,argv);
else if (valpwd (authuser,pwd,argc,argv)) pw = pwuser (user);
if (pw && pw_login (pw,authuser,pw->pw_name,NIL,argc,argv)) return T;
syslog (level|LOG_AUTH,"Login %s user=%.64s auth=%.64s host=%.80s",err,
user,(authuser && *authuser) ? authuser : user,tcp_clienthost ());
sleep (3); /* slow down possible cracker */
return NIL;
}
/* Authenticated server log in
* Accepts: user name string
* authenticating user name string
* argument count
* argument vector
* Returns: T if password validated, NIL otherwise
*/
long authserver_login (char *user,char *authuser,int argc,char *argv[])
{
return pw_login (pwuser (user),authuser,user,NIL,argc,argv);
}
/* Log in as anonymous daemon
* Accepts: argument count
* argument vector
* Returns: T if successful, NIL if error
*/
long anonymous_login (int argc,char *argv[])
{
/* log in Mr. A. N. Onymous */
return pw_login (getpwnam (ANONYMOUSUSER),NIL,NIL,
(char *) mail_parameters (NIL,GET_ANONYMOUSHOME,NIL),
argc,argv);
}
/* Finish log in and environment initialization
* Accepts: passwd struct for loginpw()
* optional authentication user name
* user name (NIL for anonymous)
* home directory (NIL to use directory from passwd struct)
* argument count
* argument vector
* Returns: T if successful, NIL if error
*/
long pw_login (struct passwd *pw,char *auser,char *user,char *home,int argc,
char *argv[])
{
struct group *gr;
char **t;
long ret = NIL;
if (pw && pw->pw_uid) { /* must have passwd struct for non-UID 0 */
/* make safe copies of user and home */
if (user) user = cpystr (pw->pw_name);
home = cpystr (home ? home : pw->pw_dir);
/* authorization ID .NE. authentication ID? */
if (user && auser && *auser && compare_cstring (auser,user)) {
/* scan list of mail administrators */
if ((gr = getgrnam (ADMINGROUP)) && (t = gr->gr_mem)) while (*t && !ret)
if (!compare_cstring (auser,*t++))
ret = pw_login (pw,NIL,user,home,argc,argv);
syslog (LOG_NOTICE|LOG_AUTH,"%s %.80s override of user=%.80s host=%.80s",
ret ? "Admin" : "Failed",auser,user,tcp_clienthost ());
}
else if (closedBox) { /* paranoid site, lock out other directories */
if (chdir (home) || chroot (home))
syslog (LOG_NOTICE|LOG_AUTH,
"Login %s failed: unable to set chroot=%.80s host=%.80s",
pw->pw_name,home,tcp_clienthost ());
else if (loginpw (pw,argc,argv)) ret = env_init (user,NIL);
else fatal ("Login failed after chroot");
}
/* normal login */
else if (((pw->pw_uid == geteuid ()) || loginpw (pw,argc,argv)) &&
(ret = env_init (user,home))) chdir (myhomedir ());
fs_give ((void **) &home); /* clean up */
if (user) fs_give ((void **) &user);
}
endpwent (); /* in case shadow passwords in pw data */
return ret; /* return status */
}
/* Initialize environment
* Accepts: user name (NIL for anonymous)
* home directory name
* Returns: T, always
*/
long env_init (char *user,char *home)
{
extern MAILSTREAM CREATEPROTO;
extern MAILSTREAM EMPTYPROTO;
struct passwd *pw;
struct stat sbuf;
char tmp[MAILTMPLEN];
/* don't init if blocked */
if (block_env_init) return LONGT;
if (myUserName) fatal ("env_init called twice!");
/* initially nothing in namespace list */
nslist[0] = nslist[1] = nslist[2] = NIL;
/* myUserName must be set before dorc() call */
myUserName = cpystr (user ? user : ANONYMOUSUSER);
/* force default prototypes to be set */
if (!createProto) createProto = &CREATEPROTO;
if (!appendProto) appendProto = &EMPTYPROTO;
dorc (NIL,NIL); /* do systemwide configuration */
if (!home) { /* closed box server */
/* standard user can only reference home */
if (user) nslist[0] = &nshome;
else { /* anonymous user */
nslist[0] = &nsblackother; /* set root */
anonymous = T; /* flag as anonymous */
}
myHomeDir = cpystr (""); /* home directory is root */
sysInbox = cpystr ("INBOX");/* make system INBOX */
}
else { /* open or black box */
closedBox = NIL; /* definitely not a closed box */
if (user) { /* remember user name and home directory */
if (blackBoxDir) { /* build black box directory name */
sprintf (tmp,"%s/%s",blackBoxDir,myUserName);
/* must exist */
if (!((!stat (home = tmp,&sbuf) && (sbuf.st_mode & S_IFDIR)) ||
(blackBoxDefaultHome &&
!stat (home = blackBoxDefaultHome,&sbuf) &&
(sbuf.st_mode & S_IFDIR)))) fatal ("no home");
sysInbox = (char *) fs_get (strlen (home) + 7);
/* set system INBOX */
sprintf (sysInbox,"%s/INBOX",home);
blackBox = T; /* mark that it's a black box */
/* mbox meaningless if black box */
mail_parameters (NIL,DISABLE_DRIVER,(void *) "mbox");
}
nslist[0] = &nshome; /* home namespace */
/* limited advertise namespaces */
if (limitedadvertise) nslist[2] = &nslimited;
else if (blackBox) { /* black box namespaces */
nslist[1] = &nsblackother;
nslist[2] = &nsshared;
}
else { /* open box namespaces */
nslist[1] = &nsunixother;
nslist[2] = advertisetheworld ? &nsworld : &nsshared;
}
}
else {
nslist[2] = &nsftp; /* anonymous user */
sprintf (tmp,"%s/INBOX",
home = (char *) mail_parameters (NIL,GET_ANONYMOUSHOME,NIL));
sysInbox = cpystr (tmp); /* make system INBOX */
anonymous = T; /* flag as anonymous */
}
myHomeDir = cpystr (home); /* set home directory */
}
if (allowuserconfig) { /* allow user config files */
dorc (strcat (strcpy (tmp,myHomeDir),"/.mminit"),T);
dorc (strcat (strcpy (tmp,myHomeDir),"/.imaprc"),NIL);
}
if (!closedBox && !noautomaticsharedns) {
/* #ftp namespace */
if (!ftpHome && (pw = getpwnam ("ftp"))) ftpHome = cpystr (pw->pw_dir);
/* #public namespace */
if (!publicHome && (pw = getpwnam ("imappublic")))
publicHome = cpystr (pw->pw_dir);
/* #shared namespace */
if (!anonymous && !sharedHome && (pw = getpwnam ("imapshared")))
sharedHome = cpystr (pw->pw_dir);
}
if (!myLocalHost) mylocalhost ();
if (!myNewsrc) myNewsrc = cpystr(strcat (strcpy (tmp,myHomeDir),"/.newsrc"));
if (!newsActive) newsActive = cpystr (ACTIVEFILE);
if (!newsSpool) newsSpool = cpystr (NEWSSPOOL);
/* re-do open action to get flags */
(*createProto->dtb->open) (NIL);
endpwent (); /* close pw database */
return T;
}
/* Return my user name
* Accepts: pointer to optional flags
* Returns: my user name
*/
char *myusername_full (unsigned long *flags)
{
struct passwd *pw;
struct stat sbuf;
char *s;
unsigned long euid;
char *ret = UNLOGGEDUSER;
/* no user name yet and not root? */
if (!myUserName && (euid = geteuid ())) {
/* yes, look up getlogin() user name or EUID */
if (((s = (char *) getlogin ()) && *s && (strlen (s) < NETMAXUSER) &&
(pw = getpwnam (s)) && (pw->pw_uid == euid)) ||
(pw = getpwuid (euid))) {
if (block_env_init) { /* don't env_init if blocked */
if (flags) *flags = MU_LOGGEDIN;
return pw->pw_name;
}
env_init (pw->pw_name,
((s = getenv ("HOME")) && *s && (strlen (s) < NETMAXMBX) &&
!stat (s,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) ?
s : pw->pw_dir);
}
else fatal ("Unable to look up user name");
}
if (myUserName) { /* logged in? */
if (flags) *flags = anonymous ? MU_ANONYMOUS : MU_LOGGEDIN;
ret = myUserName; /* return user name */
}
else if (flags) *flags = MU_NOTLOGGEDIN;
return ret;
}
/* Return my local host name
* Returns: my local host name
*/
char *mylocalhost ()
{
if (!myLocalHost) {
char *s,tmp[MAILTMPLEN];
char *t = "unknown";
tmp[0] = tmp[MAILTMPLEN-1] = '\0';
if (!gethostname (tmp,MAILTMPLEN-1) && tmp[0]) {
/* sanity check of name */
for (s = tmp; (*s > 0x20) && (*s < 0x7f); ++s);
if (!*s) t = tcp_canonical (tmp);
}
myLocalHost = cpystr (t);
}
return myLocalHost;
}
/* Return my home directory name
* Returns: my home directory name
*/
char *myhomedir ()
{
if (!myHomeDir) myusername ();/* initialize if first time */
return myHomeDir ? myHomeDir : "";
}
/* Return my home mailbox name
* Returns: my home directory name
*/
static char *mymailboxdir ()
{
char *home = myhomedir ();
/* initialize if first time */
if (!myMailboxDir && myHomeDir) {
if (mailsubdir) {
char tmp[MAILTMPLEN];
sprintf (tmp,"%s/%s",home,mailsubdir);
myMailboxDir = cpystr (tmp);/* use pre-defined subdirectory of home */
}
else myMailboxDir = cpystr (home);
}
return myMailboxDir ? myMailboxDir : "";
}
/* Return system standard INBOX
* Accepts: buffer string
*/
char *sysinbox ()
{
char tmp[MAILTMPLEN];
if (!sysInbox) { /* initialize if first time */
sprintf (tmp,"%s/%s",MAILSPOOL,myusername ());
sysInbox = cpystr (tmp); /* system inbox is from mail spool */
}
return sysInbox;
}
/* Return mailbox directory name
* Accepts: destination buffer
* directory prefix
* name in directory
* Returns: file name or NIL if error
*/
char *mailboxdir (char *dst,char *dir,char *name)
{
char tmp[MAILTMPLEN];
if (dir || name) { /* if either argument provided */
if (dir) {
if (strlen (dir) > NETMAXMBX) return NIL;
strcpy (tmp,dir); /* write directory prefix */
}
else tmp[0] = '\0'; /* otherwise null string */
if (name) {
if (strlen (name) > NETMAXMBX) return NIL;
strcat (tmp,name); /* write name in directory */
}
/* validate name, return its name */
if (!mailboxfile (dst,tmp)) return NIL;
}
/* no arguments, wants mailbox directory */
else strcpy (dst,mymailboxdir ());
return dst; /* return the name */
}
/* Return mailbox file name
* Accepts: destination buffer
* mailbox name
* Returns: file name or empty string for driver-selected INBOX or NIL if error
*/
char *mailboxfile (char *dst,char *name)
{
struct passwd *pw;
char *s;
if (!name || !*name || (*name == '{') || (strlen (name) > NETMAXMBX) ||
((anonymous || blackBox || restrictBox || (*name == '#')) &&
(strstr (name,"..") || strstr (name,"//") || strstr (name,"/~"))))
dst = NIL; /* invalid name */
else switch (*name) { /* determine mailbox type based upon name */
case '#': /* namespace name */
/* #ftp/ namespace */
if (((name[1] == 'f') || (name[1] == 'F')) &&
((name[2] == 't') || (name[2] == 'T')) &&
((name[3] == 'p') || (name[3] == 'P')) &&
(name[4] == '/') && ftpHome) sprintf (dst,"%s/%s",ftpHome,name+5);
/* #public/ and #shared/ namespaces */
else if ((((name[1] == 'p') || (name[1] == 'P')) &&
((name[2] == 'u') || (name[2] == 'U')) &&
((name[3] == 'b') || (name[3] == 'B')) &&
((name[4] == 'l') || (name[4] == 'L')) &&
((name[5] == 'i') || (name[5] == 'I')) &&
((name[6] == 'c') || (name[6] == 'C')) &&
(name[7] == '/') && (s = publicHome)) ||
(!anonymous && ((name[1] == 's') || (name[1] == 'S')) &&
((name[2] == 'h') || (name[2] == 'H')) &&
((name[3] == 'a') || (name[3] == 'A')) &&
((name[4] == 'r') || (name[4] == 'R')) &&
((name[5] == 'e') || (name[5] == 'E')) &&
((name[6] == 'd') || (name[6] == 'D')) &&
(name[7] == '/') && (s = sharedHome)))
sprintf (dst,"%s/%s",s,compare_cstring (name+8,"INBOX") ?
name+8 : "INBOX");
else dst = NIL; /* unknown namespace */
break;
case '/': /* root access */
if (anonymous) dst = NIL; /* anonymous forbidden to do this */
else if (blackBox) { /* other user access if blackbox */
if (restrictBox & RESTRICTOTHERUSER) dst = NIL;
/* see if other user INBOX */
else if ((s = strchr (name+1,'/')) && !compare_cstring (s+1,"INBOX")) {
*s = '\0'; /* temporarily tie off string */
sprintf (dst,"%s/%s/INBOX",blackBoxDir,name+1);
*s = '/'; /* in case caller cares */
}
else sprintf (dst,"%s/%s",blackBoxDir,name+1);
}
else if ((restrictBox & RESTRICTROOT) && strcmp (name,sysinbox ()))
dst = NIL; /* restricted and not access to sysinbox */
else strcpy (dst,name); /* unrestricted, copy root name */
break;
case '~': /* other user access */
/* bad syntax or anonymous can't win */
if (!*++name || anonymous) dst = NIL;
/* ~/ equivalent to ordinary name */
else if (*name == '/') sprintf (dst,"%s/%s",mymailboxdir (),name+1);
/* other user forbidden if closed/restricted */
else if (closedBox || (restrictBox & RESTRICTOTHERUSER)) dst = NIL;
else if (blackBox) { /* black box form of other user */
/* see if other user INBOX */
if ((s = strchr (name,'/')) && compare_cstring (s+1,"INBOX")) {
*s = '\0'; /* temporarily tie off string */
sprintf (dst,"%s/%s/INBOX",blackBoxDir,name);
*s = '/'; /* in case caller cares */
}
else sprintf (dst,"%s/%s",blackBoxDir,name);
}
else { /* clear box other user */
/* copy user name */
for (s = dst; *name && (*name != '/'); *s++ = *name++);
*s++ = '\0'; /* tie off user name, look up in passwd file */
if ((pw = getpwnam (dst)) && pw->pw_dir) {
if (*name) name++; /* skip past the slash */
/* canonicalize case of INBOX */
if (!compare_cstring (name,"INBOX")) name = "INBOX";
/* remove trailing / from directory */
if ((s = strrchr (pw->pw_dir,'/')) && !s[1]) *s = '\0';
/* don't allow ~root/ if restricted root */
if ((restrictBox & RESTRICTROOT) && !*pw->pw_dir) dst = NIL;
/* build final name w/ subdir if needed */
else if (mailsubdir) sprintf (dst,"%s/%s/%s",pw->pw_dir,mailsubdir,name);
else sprintf (dst,"%s/%s",pw->pw_dir,name);
}
else dst = NIL; /* no such user */
}
break;
case 'I': case 'i': /* possible INBOX */
if (!compare_cstring (name+1,"NBOX")) {
/* if restricted, use INBOX in mailbox dir */
if (anonymous || blackBox || closedBox)
sprintf (dst,"%s/INBOX",mymailboxdir ());
else *dst = '\0'; /* otherwise driver selects the name */
break;
}
/* drop into to ordinary name case */
default: /* ordinary name is easy */
sprintf (dst,"%s/%s",mymailboxdir (),name);
break;
}
return dst; /* return final name */
}
/* Dot-lock file locker
* Accepts: file name to lock
* destination buffer for lock file name
* open file description on file name to lock
* Returns: T if success, NIL if failure
*/
long dotlock_lock (char *file,DOTLOCK *base,int fd)
{
int i = locktimeout * 60;
int j,mask,retry,pi[2],po[2];
char *s,tmp[MAILTMPLEN];
struct stat sb;
/* flush absurd file name */
if (strlen (file) > 512) return NIL;
/* build lock filename */
sprintf (base->lock,"%s.lock",file);
/* assume no pipe */
base->pipei = base->pipeo = -1;
do { /* make sure not symlink */
if (!(j = chk_notsymlink (base->lock,&sb))) return NIL;
/* time out if file older than 5 minutes */
if ((j > 0) && ((time (0)) >= (sb.st_ctime + locktimeout * 60))) i = 0;
/* try to create the lock */
switch (retry = crexcl (base->lock)) {
case -1: /* OK to retry */
if (!(i%15)) { /* time to notify? */
sprintf (tmp,"Mailbox %.80s is locked, will override in %d seconds...",
file,i);
MM_LOG (tmp,WARN);
}
sleep (1); /* wait 1 second before next try */
break;
case NIL: /* failure, can't retry */
i = 0;
break;
case T: /* success, make sure others can break lock */
chmod (base->lock,(int) dotlock_mode);
return LONGT;
}
} while (i--); /* until out of retries */
if (retry < 0) { /* still returning retry after locktimeout? */
if (!(j = chk_notsymlink (base->lock,&sb))) return NIL;
if ((j > 0) && ((time (0)) < (sb.st_ctime + locktimeout * 60))) {
sprintf (tmp,"Mailbox vulnerable - seizing %ld second old lock",
(long) (time (0) - sb.st_ctime));
MM_LOG (tmp,WARN);
}
mask = umask (0); /* want our lock protection */
unlink (base->lock); /* try to remove the old file */
/* seize the lock */
if ((i = open (base->lock,O_WRONLY|O_CREAT,(int) dotlock_mode)) >= 0) {
close (i); /* don't need descriptor any more */
sprintf (tmp,"Mailbox %.80s lock overridden",file);
MM_LOG (tmp,NIL);
chmod (base->lock,(int) dotlock_mode);
umask (mask); /* restore old umask */
return LONGT;
}
umask (mask); /* restore old umask */
}
if (fd >= 0) switch (errno) {
case EACCES: /* protection failure? */
MM_CRITICAL (NIL); /* go critical */
if (closedBox || !lockpgm); /* can't do on closed box or disabled */
else if ((*lockpgm && stat (lockpgm,&sb)) ||
(!*lockpgm && stat (lockpgm = LOCKPGM1,&sb) &&
stat (lockpgm = LOCKPGM2,&sb) && stat (lockpgm = LOCKPGM3,&sb)))
lockpgm = NIL; /* disable if can't find lockpgm */
else if (pipe (pi) >= 0) { /* make command pipes */
long cf;
char *argv[4],arg[20];
/* if input pipes usable create output pipes */
if ((pi[0] < FD_SETSIZE) && (pi[1] < FD_SETSIZE) && (pipe (po) >= 0)) {
/* make sure output pipes are usable */
if ((po[0] >= FD_SETSIZE) || (po[1] >= FD_SETSIZE));
/* all is good, make inferior process */
else if (!(j = fork ())) {
if (!fork ()) { /* make grandchild so it's inherited by init */
/* prepare argument vector */
sprintf (arg,"%d",fd);
argv[0] = lockpgm; argv[1] = arg;
argv[2] = file; argv[3] = NIL;
/* set parent's I/O to my O/I */
dup2 (pi[1],1); dup2 (pi[1],2); dup2 (po[0],0);
/* close all unnecessary descriptors */
for (cf = max (20,max (max (pi[0],pi[1]),max(po[0],po[1])));
cf >= 3; --cf) if (cf != fd) close (cf);
/* be our own process group */
setpgrp (0,getpid ());
/* now run it */
_exit (execv (argv[0],argv));
}
_exit (1); /* child is done */
}
else if (j > 0) { /* parent process */
fd_set rfd;
struct timeval tmo;
FD_ZERO (&rfd);
FD_SET (pi[0],&rfd);
tmo.tv_sec = locktimeout * 60;
grim_pid_reap (j,NIL);/* reap child; grandchild now owned by init */
/* read response from locking program */
if (select (pi[0]+1,&rfd,0,0,&tmo) &&
(read (pi[0],tmp,1) == 1) && (tmp[0] == '+')) {
/* success, record pipes */
base->pipei = pi[0]; base->pipeo = po[1];
/* close child's side of the pipes */
close (pi[1]); close (po[0]);
MM_NOCRITICAL (NIL);/* no longer critical */
return LONGT;
}
}
close (po[0]); close (po[1]);
}
close (pi[0]); close (pi[1]);
}
MM_NOCRITICAL (NIL); /* no longer critical */
/* find directory/file delimiter */
if (s = strrchr (base->lock,'/')) {
*s = '\0'; /* tie off at directory */
sprintf(tmp, /* generate default message */
"Mailbox vulnerable - directory %.80s must have 1777 protection",
base->lock);
/* definitely not 1777 if can't stat */
mask = stat (base->lock,&sb) ? 0 : (sb.st_mode & 1777);
*s = '/'; /* restore lock name */
if (mask != 1777) { /* default warning if not 1777 */
if (!disableLockWarning) MM_LOG (tmp,WARN);
break;
}
}
default:
sprintf (tmp,"Mailbox vulnerable - error creating %.80s: %s",
base->lock,strerror (errno));
if (!disableLockWarning) MM_LOG (tmp,WARN);
break;
}
base->lock[0] = '\0'; /* don't use lock files */
return NIL;
}
/* Dot-lock file unlocker
* Accepts: lock file name
* Returns: T if success, NIL if failure
*/
long dotlock_unlock (DOTLOCK *base)
{
long ret = LONGT;
if (base && base->lock[0]) {
if (base->pipei >= 0) { /* if running through a pipe unlocker */
ret = (write (base->pipeo,"+",1) == 1);
/* nuke the pipes */
close (base->pipei); close (base->pipeo);
}
else ret = !unlink (base->lock);
}
return ret;
}
/* Lock file name
* Accepts: scratch buffer
* file name
* type of locking operation (LOCK_SH or LOCK_EX)
* pointer to return PID of locker
* Returns: file descriptor of lock or negative if error
*/
int lockname (char *lock,char *fname,int op,long *pid)
{
struct stat sbuf;
*pid = 0; /* no locker PID */
return stat (fname,&sbuf) ? -1 : lock_work (lock,&sbuf,op,pid);
}
/* Lock file descriptor
* Accepts: file descriptor
* lock file name buffer
* type of locking operation (LOCK_SH or LOCK_EX)
* Returns: file descriptor of lock or negative if error
*/
int lockfd (int fd,char *lock,int op)
{
struct stat sbuf;
return fstat (fd,&sbuf) ? -1 : lock_work (lock,&sbuf,op,NIL);
}
/* Lock file name worker
* Accepts: lock file name
* pointer to stat() buffer
* type of locking operation (LOCK_SH or LOCK_EX)
* pointer to return PID of locker
* Returns: file descriptor of lock or negative if error
*/
int lock_work (char *lock,void *sb,int op,long *pid)
{
struct stat lsb,fsb;
struct stat *sbuf = (struct stat *) sb;
char tmp[MAILTMPLEN];
long i;
int fd;
int mask = umask (0);
if (pid) *pid = 0; /* initialize return PID */
/* make temporary lock file name */
sprintf (lock,"%s/.%lx.%lx",closedBox ? "" : tmpdir,
(unsigned long) sbuf->st_dev,(unsigned long) sbuf->st_ino);
while (T) { /* until get a good lock */
do switch ((int) chk_notsymlink (lock,&lsb)) {
case 1: /* exists just once */
if (((fd = open (lock,O_RDWR,shlock_mode)) >= 0) ||
(errno != ENOENT) || (chk_notsymlink (lock,&lsb) >= 0)) break;
case -1: /* name doesn't exist */
fd = open (lock,O_RDWR|O_CREAT|O_EXCL,shlock_mode);
break;
default: /* multiple hard links */
MM_LOG ("hard link to lock name",ERROR);
syslog (LOG_CRIT,"SECURITY PROBLEM: hard link to lock name: %.80s",lock);
case 0: /* symlink (already did syslog) */
umask (mask); /* restore old mask */
return -1; /* fail: no lock file */
} while ((fd < 0) && (errno == EEXIST));
if (fd < 0) { /* failed to get file descriptor */
syslog (LOG_INFO,"Mailbox lock file %s open failure: %s",lock,
strerror (errno));
if (!closedBox) { /* more explicit snarl for bad configuration */
if (stat (tmpdir,&lsb))
syslog (LOG_CRIT,"SYSTEM ERROR: no %s: %s",tmpdir,strerror (errno));
else if ((lsb.st_mode & 01777) != 01777) {
sprintf (tmp,"Can't lock for write: %.80s must have 1777 protection",
tmpdir);
MM_LOG (tmp,WARN);
}
}
umask (mask); /* restore old mask */
return -1; /* fail: can't open lock file */
}
/* non-blocking form */
if (op & LOCK_NB) i = flock (fd,op);
else { /* blocking form */
(*mailblocknotify) (BLOCK_FILELOCK,NIL);
i = flock (fd,op);
(*mailblocknotify) (BLOCK_NONE,NIL);
}
if (i) { /* failed, get other process' PID */
if (pid && !fstat (fd,&fsb) && (i = min (fsb.st_size,MAILTMPLEN-1)) &&
(read (fd,tmp,i) == i) && !(tmp[i] = 0) && ((i = atol (tmp)) > 0))
*pid = i;
close (fd); /* failed, give up on lock */
umask (mask); /* restore old mask */
return -1; /* fail: can't lock */
}
/* make sure this lock is good for us */
if (!lstat (lock,&lsb) && ((lsb.st_mode & S_IFMT) != S_IFLNK) &&
!fstat (fd,&fsb) && (lsb.st_dev == fsb.st_dev) &&
(lsb.st_ino == fsb.st_ino) && (fsb.st_nlink == 1)) break;
close (fd); /* lock not right, drop fd and try again */
}
chmod (lock,shlock_mode); /* make sure mode OK (don't use fchmod()) */
umask (mask); /* restore old mask */
return fd; /* success */
}
/* Check to make sure not a symlink
* Accepts: file name
* stat buffer
* Returns: -1 if doesn't exist, NIL if symlink, else number of hard links
*/
long chk_notsymlink (char *name,void *sb)
{
struct stat *sbuf = (struct stat *) sb;
/* name exists? */
if (lstat (name,sbuf)) return -1;
/* forbid symbolic link */
if ((sbuf->st_mode & S_IFMT) == S_IFLNK) {
MM_LOG ("symbolic link on lock name",ERROR);
syslog (LOG_CRIT,"SECURITY PROBLEM: symbolic link on lock name: %.80s",
name);
return NIL;
}
return (long) sbuf->st_nlink; /* return number of hard links */
}
/* Unlock file descriptor
* Accepts: file descriptor
* lock file name from lockfd()
*/
void unlockfd (int fd,char *lock)
{
/* delete the file if no sharers */
if (!flock (fd,LOCK_EX|LOCK_NB)) unlink (lock);
flock (fd,LOCK_UN); /* unlock it */
close (fd); /* close it */
}
/* Set proper file protection for mailbox
* Accepts: mailbox name
* actual file path name
* Returns: T, always
*/
long set_mbx_protections (char *mailbox,char *path)
{
struct stat sbuf;
int mode = (int) mbx_protection;
if (*mailbox == '#') { /* possible namespace? */
if (((mailbox[1] == 'f') || (mailbox[1] == 'F')) &&
((mailbox[2] == 't') || (mailbox[2] == 'T')) &&
((mailbox[3] == 'p') || (mailbox[3] == 'P')) &&
(mailbox[4] == '/')) mode = (int) ftp_protection;
else if (((mailbox[1] == 'p') || (mailbox[1] == 'P')) &&
((mailbox[2] == 'u') || (mailbox[2] == 'U')) &&
((mailbox[3] == 'b') || (mailbox[3] == 'B')) &&
((mailbox[4] == 'l') || (mailbox[4] == 'L')) &&
((mailbox[5] == 'i') || (mailbox[5] == 'I')) &&
((mailbox[6] == 'c') || (mailbox[6] == 'C')) &&
(mailbox[7] == '/')) mode = (int) public_protection;
else if (((mailbox[1] == 's') || (mailbox[1] == 'S')) &&
((mailbox[2] == 'h') || (mailbox[2] == 'H')) &&
((mailbox[3] == 'a') || (mailbox[3] == 'A')) &&
((mailbox[4] == 'r') || (mailbox[4] == 'R')) &&
((mailbox[5] == 'e') || (mailbox[5] == 'E')) &&
((mailbox[6] == 'd') || (mailbox[6] == 'D')) &&
(mailbox[7] == '/')) mode = (int) shared_protection;
}
/* if a directory */
if (!stat (path,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) {
/* set owner search if allow read or write */
if (mode & 0600) mode |= 0100;
if (mode & 060) mode |= 010;/* set group search if allow read or write */
if (mode & 06) mode |= 01; /* set world search if allow read or write */
/* preserve directory SGID bit */
if (sbuf.st_mode & S_ISGID) mode |= S_ISGID;
}
chmod (path,mode); /* set the new protection, ignore failure */
return LONGT;
}
/* Get proper directory protection
* Accepts: mailbox name
* Returns: directory mode, always
*/
long get_dir_protection (char *mailbox)
{
if (*mailbox == '#') { /* possible namespace? */
if (((mailbox[1] == 'f') || (mailbox[1] == 'F')) &&
((mailbox[2] == 't') || (mailbox[2] == 'T')) &&
((mailbox[3] == 'p') || (mailbox[3] == 'P')) &&
(mailbox[4] == '/')) return ftp_dir_protection;
else if (((mailbox[1] == 'p') || (mailbox[1] == 'P')) &&
((mailbox[2] == 'u') || (mailbox[2] == 'U')) &&
((mailbox[3] == 'b') || (mailbox[3] == 'B')) &&
((mailbox[4] == 'l') || (mailbox[4] == 'L')) &&
((mailbox[5] == 'i') || (mailbox[5] == 'I')) &&
((mailbox[6] == 'c') || (mailbox[6] == 'C')) &&
(mailbox[7] == '/')) return public_dir_protection;
else if (((mailbox[1] == 's') || (mailbox[1] == 'S')) &&
((mailbox[2] == 'h') || (mailbox[2] == 'H')) &&
((mailbox[3] == 'a') || (mailbox[3] == 'A')) &&
((mailbox[4] == 'r') || (mailbox[4] == 'R')) &&
((mailbox[5] == 'e') || (mailbox[5] == 'E')) &&
((mailbox[6] == 'd') || (mailbox[6] == 'D')) &&
(mailbox[7] == '/')) return shared_dir_protection;
}
return dir_protection;
}
/* Determine default prototype stream to user
* Accepts: type (NIL for create, T for append)
* Returns: default prototype stream
*/
MAILSTREAM *default_proto (long type)
{
myusername (); /* make sure initialized */
/* return default driver's prototype */
return type ? appendProto : createProto;
}
/* Set up user flags for stream
* Accepts: MAIL stream
* Returns: MAIL stream with user flags set up
*/
MAILSTREAM *user_flags (MAILSTREAM *stream)
{
int i;
myusername (); /* make sure initialized */
for (i = 0; i < NUSERFLAGS && userFlags[i]; ++i)
if (!stream->user_flags[i]) stream->user_flags[i] = cpystr (userFlags[i]);
return stream;
}
/* Return nth user flag
* Accepts: user flag number
* Returns: flag
*/
char *default_user_flag (unsigned long i)
{
myusername (); /* make sure initialized */
return userFlags[i];
}
/* Process rc file
* Accepts: file name
* .mminit flag
* Don't use this feature.
*/
void dorc (char *file,long flag)
{
int i;
char *s,*t,*k,*r,tmp[MAILTMPLEN],tmpx[MAILTMPLEN];
extern MAILSTREAM CREATEPROTO;
extern MAILSTREAM EMPTYPROTO;
DRIVER *d;
FILE *f;
if ((f = fopen (file ? file : SYSCONFIG,"r")) &&
(s = fgets (tmp,MAILTMPLEN,f)) && (t = strchr (s,'\n'))) do {
*t++ = '\0'; /* tie off line, find second space */
if ((k = strchr (s,' ')) && (k = strchr (++k,' '))) {
*k++ = '\0'; /* tie off two words */
if (!compare_cstring (s,"set keywords") && !userFlags[0]) {
/* yes, get first keyword */
k = strtok_r (k,", ",&r);
/* copy keyword list */
for (i = 0; k && i < NUSERFLAGS; ++i) if (strlen (k) <= MAXUSERFLAG) {
if (userFlags[i]) fs_give ((void **) &userFlags[i]);
userFlags[i] = cpystr (k);
k = strtok_r (NIL,", ",&r);
}
if (flag) break; /* found "set keywords" in .mminit */
}
else if (!flag) { /* none of these valid in .mminit */
if (myUserName) { /* only valid if logged in */
if (!compare_cstring (s,"set new-mailbox-format") ||
!compare_cstring (s,"set new-folder-format")) {
if (!compare_cstring (k,"same-as-inbox")) {
if (d = mail_valid (NIL,"INBOX",NIL)) {
if (!compare_cstring (d->name,"mbox"))
d = (DRIVER *) mail_parameters (NIL,GET_DRIVER,
(void *) "unix");
else if (!compare_cstring (d->name,"dummy")) d = NIL;
}
createProto = d ? ((*d->open) (NIL)) : &CREATEPROTO;
}
else if (!compare_cstring (k,"system-standard"))
createProto = &CREATEPROTO;
else { /* canonicalize mbox to unix */
if (!compare_cstring (k,"mbox")) k = "unix";
/* see if a driver name */
if (d = (DRIVER *) mail_parameters (NIL,GET_DRIVER,(void *) k))
createProto = (*d->open) (NIL);
else { /* duh... */
sprintf (tmpx,"Unknown new mailbox format in %s: %s",
file ? file : SYSCONFIG,k);
MM_LOG (tmpx,WARN);
}
}
}
if (!compare_cstring (s,"set empty-mailbox-format") ||
!compare_cstring (s,"set empty-folder-format")) {
if (!compare_cstring (k,"invalid")) appendProto = NIL;
else if (!compare_cstring (k,"same-as-inbox"))
appendProto = ((d = mail_valid (NIL,"INBOX",NIL)) &&
compare_cstring (d->name,"dummy")) ?
((*d->open) (NIL)) : &EMPTYPROTO;
else if (!compare_cstring (k,"system-standard"))
appendProto = &EMPTYPROTO;
else { /* see if a driver name */
for (d = (DRIVER *) mail_parameters (NIL,GET_DRIVERS,NIL);
d && compare_cstring (d->name,k); d = d->next);
if (d) appendProto = (*d->open) (NIL);
else { /* duh... */
sprintf (tmpx,"Unknown empty mailbox format in %s: %s",
file ? file : SYSCONFIG,k);
MM_LOG (tmpx,WARN);
}
}
}
}
if (!compare_cstring (s,"set local-host")) {
fs_give ((void **) &myLocalHost);
myLocalHost = cpystr (k);
}
else if (!compare_cstring (s,"set news-active-file")) {
fs_give ((void **) &newsActive);
newsActive = cpystr (k);
}
else if (!compare_cstring (s,"set news-spool-directory")) {
fs_give ((void **) &newsSpool);
newsSpool = cpystr (k);
}
else if (!compare_cstring (s,"set mh-path"))
mail_parameters (NIL,SET_MHPATH,(void *) k);
else if (!compare_cstring (s,"set mh-allow-inbox"))
mail_parameters (NIL,SET_MHALLOWINBOX,(void *) atol (k));
else if (!compare_cstring (s,"set news-state-file")) {
fs_give ((void **) &myNewsrc);
myNewsrc = cpystr (k);
}
else if (!compare_cstring (s,"set ftp-export-directory")) {
fs_give ((void **) &ftpHome);
ftpHome = cpystr (k);
}
else if (!compare_cstring (s,"set public-home-directory")) {
fs_give ((void **) &publicHome);
publicHome = cpystr (k);
}
else if (!compare_cstring (s,"set shared-home-directory")) {
fs_give ((void **) &sharedHome);
sharedHome = cpystr (k);
}
else if (!compare_cstring (s,"set system-inbox")) {
fs_give ((void **) &sysInbox);
sysInbox = cpystr (k);
}
else if (!compare_cstring (s,"set mail-subdirectory")) {
fs_give ((void **) &mailsubdir);
mailsubdir = cpystr (k);
}
else if (!compare_cstring (s,"set from-widget"))
mail_parameters (NIL,SET_FROMWIDGET,
compare_cstring (k,"header-only") ?
VOIDT : NIL);
else if (!compare_cstring (s,"set rsh-command"))
mail_parameters (NIL,SET_RSHCOMMAND,(void *) k);
else if (!compare_cstring (s,"set rsh-path"))
mail_parameters (NIL,SET_RSHPATH,(void *) k);
else if (!compare_cstring (s,"set ssh-command"))
mail_parameters (NIL,SET_SSHCOMMAND,(void *) k);
else if (!compare_cstring (s,"set ssh-path"))
mail_parameters (NIL,SET_SSHPATH,(void *) k);
else if (!compare_cstring (s,"set tcp-open-timeout"))
mail_parameters (NIL,SET_OPENTIMEOUT,(void *) atol (k));
else if (!compare_cstring (s,"set tcp-read-timeout"))
mail_parameters (NIL,SET_READTIMEOUT,(void *) atol (k));
else if (!compare_cstring (s,"set tcp-write-timeout"))
mail_parameters (NIL,SET_WRITETIMEOUT,(void *) atol (k));
else if (!compare_cstring (s,"set rsh-timeout"))
mail_parameters (NIL,SET_RSHTIMEOUT,(void *) atol (k));
else if (!compare_cstring (s,"set ssh-timeout"))
mail_parameters (NIL,SET_SSHTIMEOUT,(void *) atol (k));
else if (!compare_cstring (s,"set maximum-login-trials"))
mail_parameters (NIL,SET_MAXLOGINTRIALS,(void *) atol (k));
else if (!compare_cstring (s,"set lookahead"))
mail_parameters (NIL,SET_LOOKAHEAD,(void *) atol (k));
else if (!compare_cstring (s,"set prefetch"))
mail_parameters (NIL,SET_PREFETCH,(void *) atol (k));
else if (!compare_cstring (s,"set close-on-error"))
mail_parameters (NIL,SET_CLOSEONERROR,(void *) atol (k));
else if (!compare_cstring (s,"set imap-port"))
mail_parameters (NIL,SET_IMAPPORT,(void *) atol (k));
else if (!compare_cstring (s,"set pop3-port"))
mail_parameters (NIL,SET_POP3PORT,(void *) atol (k));
else if (!compare_cstring (s,"set uid-lookahead"))
mail_parameters (NIL,SET_UIDLOOKAHEAD,(void *) atol (k));
else if (!compare_cstring (s,"set try-ssl-first"))
mail_parameters (NIL,SET_TRYSSLFIRST,(void *) atol (k));
else if (!compare_cstring (s,"set mailbox-protection"))
mbx_protection = atol (k);
else if (!compare_cstring (s,"set directory-protection"))
dir_protection = atol (k);
else if (!compare_cstring (s,"set lock-protection"))
dotlock_mode = atol (k);
else if (!compare_cstring (s,"set ftp-protection"))
ftp_protection = atol (k);
else if (!compare_cstring (s,"set public-protection"))
public_protection = atol (k);
else if (!compare_cstring (s,"set shared-protection"))
shared_protection = atol (k);
else if (!compare_cstring (s,"set ftp-directory-protection"))
ftp_dir_protection = atol (k);
else if (!compare_cstring (s,"set public-directory-protection"))
public_dir_protection = atol (k);
else if (!compare_cstring (s,"set shared-directory-protection"))
shared_dir_protection = atol (k);
else if (!compare_cstring (s,"set dot-lock-file-timeout"))
locktimeout = atoi (k);
else if (!compare_cstring (s,"set disable-fcntl-locking"))
fcntlhangbug = atoi (k);
else if (!compare_cstring (s,"set disable-lock-warning"))
disableLockWarning = atoi (k);
else if (!compare_cstring (s,"set disable-unix-UIDs-and-keywords"))
has_no_life = atoi (k);
else if (!compare_cstring (s,"set hide-dot-files"))
hideDotFiles = atoi (k);
else if (!compare_cstring (s,"set list-maximum-level"))
list_max_level = atol (k);
else if (!compare_cstring (s,"set trust-dns"))
mail_parameters (NIL,SET_TRUSTDNS,(void *) atol (k));
else if (!compare_cstring (s,"set sasl-uses-ptr-name"))
mail_parameters (NIL,SET_SASLUSESPTRNAME,(void *) atol (k));
else if (!compare_cstring (s,"set network-filesystem-stat-bug"))
netfsstatbug = atoi (k);
else if (!compare_cstring (s,"set nntp-range"))
mail_parameters (NIL,SET_NNTPRANGE,(void *) atol (k));
else if (!file) { /* only allowed in system init */
if (!compare_cstring (s,"set black-box-directory") &&
!blackBoxDir) blackBoxDir = cpystr (k);
else if (!compare_cstring(s,"set black-box-default-home-directory")&&
blackBoxDir && !blackBoxDefaultHome)
blackBoxDefaultHome = cpystr (k);
else if (!compare_cstring (s,"set anonymous-home-directory") &&
!anonymousHome) anonymousHome = cpystr (k);
/* It's tempting to allow setting the CA path
* in a user init. However, that opens up a
* vector of attack big enough to drive a
* truck through... Resist the temptation.
*/
else if (!compare_cstring (s,"set CA-certificate-path"))
sslCApath = cpystr (k);
else if (!compare_cstring (s,"set disable-plaintext"))
disablePlaintext = atoi (k);
else if (!compare_cstring (s,"set allowed-login-attempts"))
logtry = atoi (k);
else if (!compare_cstring (s,"set chroot-server"))
closedBox = atoi (k);
else if (!compare_cstring (s,"set restrict-mailbox-access"))
for (k = strtok_r (k,", ",&r); k; k = strtok_r (NIL,", ",&r)) {
if (!compare_cstring (k,"root")) restrictBox |= RESTRICTROOT;
else if (!compare_cstring (k,"otherusers"))
restrictBox |= RESTRICTOTHERUSER;
else if (!compare_cstring (k,"all")) restrictBox = -1;
}
else if (!compare_cstring (s,"set advertise-the-world"))
advertisetheworld = atoi (k);
else if (!compare_cstring (s,"set limited-advertise"))
limitedadvertise = atoi (k);
else if (!compare_cstring
(s,"set disable-automatic-shared-namespaces"))
noautomaticsharedns = atoi (k);
else if (!compare_cstring (s,"set allow-user-config"))
allowuserconfig = atoi (k);
else if (!compare_cstring (s,"set allow-reverse-dns"))
mail_parameters (NIL,SET_ALLOWREVERSEDNS,(void *) atol (k));
else if (!compare_cstring (s,"set k5-cp-uses-service-name"))
kerb_cp_svr_name = atoi (k);
/* must appear in file after any
* "set disable-plaintext" command! */
else if (!compare_cstring (s,"set plaintext-allowed-clients")) {
for (k = strtok_r (k,", ",&r); k && !tcp_isclienthost (k);
k = strtok_r (NIL,", ",&r));
if (k) disablePlaintext = 0;
}
}
}
}
} while ((s = fgets (tmp,MAILTMPLEN,f)) && (t = strchr (s,'\n')));
if (f) fclose (f); /* flush the file */
}
/* INBOX create function for tmail/dmail use only
* Accepts: mail stream
* path name buffer, preloaded with driver-dependent path
* Returns: T on success, NIL on failure
*
* This routine is evil and a truly incredible kludge. It is private for
* tmail/dmail and is not supported for any other application.
*/
long path_create (MAILSTREAM *stream,char *path)
{
long ret;
short rsave = restrictBox;
restrictBox = NIL; /* can't restrict */
if (blackBox) { /* if black box */
/* toss out driver dependent names */
printf (path,"%s/INBOX",mymailboxdir ());
blackBox = NIL; /* well that's evil - evil is going on */
ret = mail_create (stream,path);
blackBox = T; /* restore the box */
}
/* easy thing otherwise */
else ret = mail_create (stream,path);
restrictBox = rsave; /* restore restrictions */
return ret;
}
/* Default block notify routine
* Accepts: reason for calling
* data
* Returns: data
*/
void *mm_blocknotify (int reason,void *data)
{
void *ret = data;
switch (reason) {
case BLOCK_SENSITIVE: /* entering sensitive code */
ret = (void *) (unsigned long) alarm (0);
break;
case BLOCK_NONSENSITIVE: /* exiting sensitive code */
if ((unsigned long) data) alarm ((unsigned long) data);
break;
default: /* ignore all other reasons */
break;
}
return ret;
}