/* ======================================================================== * Copyright 2008-2010 Mark Crispin * ======================================================================== */ /* * Program: UNIX environment routines * * Author: Mark Crispin * * Date: 1 August 1988 * Last Edited: 15 November 2010 * * Previous versions of this file were * * 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 * */ #include #include #include /* 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 char *sslCAfile = NIL; /* non-standard CA container */ static char *sslAppCApath = NIL; /* App SSL CA path */ static char *sslAppCAfile = NIL; /* App SSL CA container */ 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 long 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_SSLCAFILE: /* this can be set null */ if (sslCAfile) fs_give ((void **) &sslCAfile); sslCAfile = value ? cpystr ((char *) value) : value; break; case GET_SSLCAFILE: ret = (void *) sslCAfile; break; case SET_SSLAPPCAPATH: /* this can be set null */ if (sslAppCApath) fs_give ((void **) &sslAppCApath); sslAppCApath = value ? cpystr ((char *) value) : value; break; case GET_SSLAPPCAPATH: ret = (void *) sslAppCApath; break; case SET_SSLAPPCAFILE: /* this can be set null */ if (sslAppCAfile) fs_give ((void **) &sslAppCAfile); sslAppCAfile = value ? cpystr ((char *) value) : value; break; case GET_SSLAPPCAFILE: ret = (void *) sslAppCAfile; 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_RESTRICTIONS: restrictBox = (long) value; case GET_RESTRICTIONS: ret = (void *) restrictBox; 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) != 0) 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) { int err; fd_set rfd,efd; struct timeval tmo; do { FD_ZERO (&rfd); FD_ZERO (&efd); FD_SET (0,&rfd); FD_SET (0,&efd); tmo.tv_sec = seconds; tmo.tv_usec = 0; } while (((err = select (1,&rfd,0,&efd,&tmo)) < 0) && (errno = EINTR)); return err ? 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)) != NULL) { 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)) != NULL) {/* 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]; 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) myLocalHost = tcp_canonical (tmp); } } return myLocalHost ? myLocalHost : "unknown"; } /* 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 { size_t i = strlen (mymailboxdir ()); if ((restrictBox & RESTRICTROOT) && strcmp (name,sysinbox ()) && (!i || strncmp (name,mymailboxdir (),i) || (name[i] !='/'))) dst = NIL; /* restricted and not sysinbox or home */ 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) && stat (lockpgm = LOCKPGM4,&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,'/')) != NULL) { *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)) != NULL) { 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)) != NULL) 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 CA-certificate-container")) sslCAfile = 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 */ sprintf (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; } void env_end(void) { if(myMailboxDir) fs_give((void **) &myMailboxDir); if(myUserName) fs_give((void **)&myUserName); if(myHomeDir) fs_give((void **)&myHomeDir); if(ftpHome) fs_give((void **)&ftpHome); if(myServerName) fs_give((void **)&myServerName); if(myLocalHost) fs_give((void **)&myLocalHost); if(myNewsrc) fs_give((void **)&myNewsrc); if(mailsubdir) fs_give((void **)&mailsubdir); if(sysInbox) fs_give((void **)&sysInbox); if(newsActive) fs_give((void **)&newsActive); if(newsSpool) fs_give((void **)&newsSpool); if(blackBoxDir) fs_give((void **)&blackBoxDir); if(blackBoxDefaultHome) fs_give((void **)&blackBoxDefaultHome); if(sslCApath) fs_give((void **)&sslCApath); if(sslCAfile) fs_give((void **)&sslCAfile); if(userFlags){ int i; for(i = 0; i < NUSERFLAGS; i++) if(userFlags[i]) fs_give((void **)&userFlags[i]); } }