/*
** Copyright (c) 2004, 2005, 2007 Sendmail, Inc. and its suppliers.
** All rights reserved.
**
** $Id: util.c,v 1.46 2007/05/31 18:58:42 msk Exp $
*/
#ifndef lint
static char util_c_id[] = "@(#)$Id: util.c,v 1.46 2007/05/31 18:58:42 msk Exp $";
#endif /* !lint */
/* system includes */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/file.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <syslog.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <netdb.h>
#include <fcntl.h>
/* libsm includes */
#include <sm/gen.h>
#include <sm/string.h>
#if POPAUTH
/* system includes */
# include <pthread.h>
/* libdb includes */
# include <db.h>
#endif /* POPAUTH */
/* dk-filter includes */
#include "dk-filter.h"
#include "util.h"
/* globals */
#if POPAUTH
static pthread_mutex_t pop_lock;
#endif /* POPAUTH */
static char *optlist[] =
{
#if POPAUTH
"POPAUTH",
#endif /* POPAUTH */
#if _FFR_ANTICIPATE_SENDMAIL_MUNGE
"_FFR_ANTICIPATE_SENDMAIL_MUNGE",
#endif /* _FFR_ANTICIPATE_SENDMAIL_MUNGE */
#if _FFR_FLUSH_HEADERS
"_FFR_FLUSH_HEADERS",
#endif /* _FFR_FLUSH_HEADERS */
#if _FFR_MULTIPLE_KEYS
"_FFR_MULTIPLE_KEYS",
#endif /* _FFR_MULTIPLE_KEYS */
#if _FFR_REQUIRED_HEADERS
"_FFR_REQUIRED_HEADERS",
#endif /* _FFR_REQUIRED_HEADERS */
#if _FFR_SELECT_CANONICALIZATION
"_FFR_SELECT_CANONICALIZATION",
#endif /* _FFR_SELECT_CANONICALIZATION */
NULL
};
/*
** DKF_OPTLIST -- print active options
**
** Parameters:
** where -- where to write the list
**
** Return value:
** None.
*/
void
dkf_optlist(FILE *where)
{
bool first = TRUE;
int c;
assert(where != NULL);
for (c = 0; optlist[c] != NULL; c++)
{
if (first)
{
fprintf(where, "Active code options:\n");
first = FALSE;
}
fprintf(where, "\t%s\n", optlist[c]);
}
}
/*
** DKF_SETMAXFD -- increase the file descriptor limit as much as possible
**
** Parameters:
** None.
**
** Return value:
** None.
*/
void
dkf_setmaxfd(void)
{
struct rlimit rlp;
if (getrlimit(RLIMIT_NOFILE, &rlp) != 0)
{
syslog(LOG_WARNING, "getrlimit(): %s", strerror(errno));
}
else
{
rlp.rlim_cur = rlp.rlim_max;
if (setrlimit(RLIMIT_NOFILE, &rlp) != 0)
{
syslog(LOG_WARNING, "setrlimit(): %s",
strerror(errno));
}
}
}
/*
** DKF_STRIPBRACKETS -- remove angle brackets from the sender address
**
** Parameters:
** addr -- address to be processed
**
** Return value:
** None.
*/
void
dkf_stripbrackets(char *addr)
{
char *p, *q;
assert(addr != NULL);
p = addr;
q = addr + strlen(addr) - 1;
while (*p == '<' && *q == '>')
{
p++;
*q-- = '\0';
}
if (p != addr)
{
for (q = addr; *p != '\0'; p++, q++)
*q = *p;
*q = '\0';
}
}
/*
** DKF_LOWERCASE -- lowercase-ize a string
**
** Parameters:
** str -- string to convert
**
** Return value:
** None.
*/
void
dkf_lowercase(char *str)
{
char *p;
assert(str != NULL);
for (p = str; *p != '\0'; p++)
{
if (isascii(*p) && isupper(*p))
*p = tolower(*p);
}
}
/*
** DKF_LIST_LOOKUP -- look up a name in a peerlist
**
** Parameters:
** list -- list of records to check
** data -- record to find
**
** Return value:
** TRUE if found, FALSE otherwise
*/
static bool
dkf_list_lookup(Peer list, char *data)
{
Peer current;
assert(data != NULL);
for (current = list; current != NULL; current = current->peer_next)
{
if (strcasecmp(data, current->peer_info) == 0)
return TRUE;
}
return FALSE;
}
/*
** DKF_CHECKHOST -- check the peerlist for a host and its wildcards
**
** Parameters:
** list -- list of records to check
** host -- hostname to find
**
** Return value:
** TRUE if there's a match, FALSE otherwise.
*/
bool
dkf_checkhost(Peer list, char *host)
{
char *p;
assert(host != NULL);
/* short circuit */
if (list == NULL)
return FALSE;
if (dkf_list_lookup(list, host))
return TRUE;
for (p = strchr(host, '.');
p != NULL;
p = strchr(p + 1, '.'))
{
if (dkf_list_lookup(list, p))
return TRUE;
}
return FALSE;
}
/*
** DKF_CHECKIP -- check a peerlist table for an IP address or its matching
** wildcards
**
** Parameters:
** list -- list to check
** ip -- IP address to find
**
** Return value:
** TRUE if there's a match, FALSE otherwise.
*/
bool
dkf_checkip(Peer list, struct sockaddr *ip)
{
int c;
char tmpbuf[MAXHOSTNAMELEN + 1];
char ipbuf[MAXHOSTNAMELEN + 1];
struct in_addr addr;
struct in_addr mask;
assert(ip != NULL);
/* short circuit */
if (list == NULL)
return FALSE;
#if NETINET6
if (ip->sa_family == AF_INET6)
{
struct in6_addr addr;
memcpy(&addr, &((struct sockaddr_in6 *)ip)->sin6_addr,
sizeof addr);
if (IN6_IS_ADDR_V4MAPPED(&addr))
{
inet_ntop(AF_INET,
&addr.s6_addr[INET6_ADDRSTRLEN - INET_ADDRSTRLEN],
tmpbuf, sizeof tmpbuf);
}
else
{
char *d;
char *dst;
size_t sz;
size_t dst_len;
dst = tmpbuf;
d = dst;
dst_len = sizeof tmpbuf;
memset(tmpbuf, '\0', sizeof tmpbuf);
sz = sm_strlcpy(tmpbuf, "IPv6:", sizeof tmpbuf);
if (sz >= sizeof tmpbuf)
return FALSE;
dst += sz;
dst_len -= sz;
inet_ntop(AF_INET6, &addr, dst, dst_len);
}
return (dkf_list_lookup(list, tmpbuf));
}
#endif /* NETINET6 */
memcpy(&addr.s_addr, &((struct sockaddr_in *)ip)->sin_addr,
sizeof(addr.s_addr));
/* test the IP by itself */
(void) dkf_inet_ntoa(addr, ipbuf, sizeof ipbuf);
if (dkf_list_lookup(list, ipbuf))
return TRUE;
/* test the IP/32 */
sm_strlcat(ipbuf, "/32", sizeof ipbuf);
if (dkf_list_lookup(list, ipbuf))
return TRUE;
/*
** Prepare to cycle through the other IP/bits possibilities.
** Turn on all the possible network bits in our bitmask.
*/
mask.s_addr = 0;
for (c = 0; c < 32; c++)
mask.s_addr |= htonl(1 << (31 - c));
/* decompose the address bit-by-bit and try the appropriate entry */
for (c = 0; c < 32; c++)
{
mask.s_addr ^= htonl(1 << c);
addr.s_addr = (addr.s_addr & mask.s_addr);
dkf_inet_ntoa(addr, tmpbuf, sizeof tmpbuf);
snprintf(ipbuf, sizeof ipbuf, "%s/%d", tmpbuf, 31 - c);
if (dkf_list_lookup(list, ipbuf))
return TRUE;
}
return FALSE;
}
#if POPAUTH
/*
** DKF_INITPOPAUTH -- initialize POPAUTH stuff
**
** Parameters:
** None.
**
** Return value:
** 0 on success, an error code on failure. See pthread_mutex_init().
*/
int
dkf_initpopauth(void)
{
return pthread_mutex_init(&pop_lock, NULL);
}
/*
** DKF_CHECKPOPAUTH -- check a POP before SMTP database for client
** authentication
**
** Parameters:
** db -- DB handle to use for searching
** ip -- IP address to find
**
** Return value:
** TRUE iff the database could be opened and the client was verified.
**
** Notes:
** - should be a shared handle rather than a per-thread handle to
** reduce descriptor consumption
** - needs locking
** - should be able to return TRUE/FALSE as well as errors
** - path should be runtime-configurable rather than hard-coded
** - does the key contain anything meaningful, like an expiry time?
*/
bool
dkf_checkpopauth(DB *db, struct sockaddr *ip)
{
bool ret = FALSE;
int status;
int fd;
struct sockaddr_in *sin;
struct in_addr addr;
DBT d;
DBT q;
char ipbuf[MAXHOSTNAMELEN + 1];
assert(ip != NULL);
if (db == NULL)
return FALSE;
/* skip anything not IPv4 (for now) */
if (ip->sa_family != AF_INET)
return FALSE;
else
sin = (struct sockaddr_in *) ip;
memset(&d, 0, sizeof d);
memset(&q, 0, sizeof q);
/* we don't care what the value is for now */
d.flags = DB_DBT_USERMEM|DB_DBT_PARTIAL;
memcpy(&addr.s_addr, &sin->sin_addr, sizeof addr.s_addr);
dkf_inet_ntoa(addr, ipbuf, sizeof ipbuf);
q.data = ipbuf;
q.size = strlen(q.data);
/* establish read-lock */
fd = -1;
# if DB_VERSION_MAJOR >= 2
status = -1;
status = db->fd(db, &fd);
# else /* DB_VERSION_MAJOR >= 2 */
status = 0;
fd = db->fd(db);
# endif /* DB_VERSION_MAJOR >= 2 */
/* XXX -- allow multiple readers? */
(void) pthread_mutex_lock(&pop_lock);
if (status == 0 && fd != -1)
{
# ifdef LOCK_SH
status = flock(fd, LOCK_SH);
if (status != 0 && dolog)
{
syslog(LOG_WARNING, "flock(LOCK_SH): %s",
strerror(errno));
}
# else /* LOCK_SH */
struct flock l;
l.l_start = 0;
l.l_len = 0;
l.l_type = F_RDLCK;
l.l_whence = SEEK_SET;
status = fcntl(fd, F_SETLKW, &l);
if (status != 0 && dolog)
{
syslog(LOG_WARNING, "fcntl(F_RDLCK): %s",
strerror(errno));
}
# endif /* LOCK_SH */
}
# if DB_VERSION_MAJOR < 2
status = db->get(db, &q, &d, 0);
# else /* DB_VERSION_MAJOR < 2 */
status = db->get(db, NULL, &q, &d, 0);
# endif /* DB_VERSION_MAJOR */
if (status == 0)
{
ret = TRUE;
}
else if (status != DB_NOTFOUND && dolog)
{
syslog(LOG_ERR, "db->get(): %s", db_strerror(status));
}
/* surrender read-lock */
if (fd != -1)
{
# ifdef LOCK_SH
status = flock(fd, LOCK_UN);
if (status != 0 && dolog)
{
syslog(LOG_WARNING, "flock(LOCK_UN): %s",
strerror(errno));
}
# else /* LOCK_SH */
struct flock l;
l.l_start = 0;
l.l_len = 0;
l.l_type = F_UNLCK;
l.l_whence = SEEK_SET;
status = fcntl(fd, F_SETLKW, &l);
if (status != 0 && dolog)
{
syslog(LOG_WARNING, "fcntl(F_UNLCK): %s",
strerror(errno));
}
# endif /* LOCK_SH */
}
(void) pthread_mutex_unlock(&pop_lock);
return ret;
}
#endif /* POPAUTH */
/*
** DKF_LOAD_LIST -- load a list of hosts/CIDR blocks into memory
**
** Parameters:
** in -- input stream (must already be open)
** list -- list to be updated
**
** Return value:
** TRUE if successful, FALSE otherwise
**
** Side effects:
** Prints an error message when appropriate.
*/
bool
dkf_load_list(FILE *in, struct Peer **list)
{
char *p;
struct Peer *newpeer;
char peer[BUFRSZ + 1];
assert(in != NULL);
assert(list != NULL);
memset(peer, '\0', sizeof peer);
while (fgets(peer, sizeof(peer) - 1, in) != NULL)
{
for (p = peer; *p != '\0'; p++)
{
if (*p == '\n')
{
*p = '\0';
break;
}
}
newpeer = (struct Peer *) malloc(sizeof(struct Peer));
if (newpeer == NULL)
{
fprintf(stderr, "%s: malloc(): %s\n", progname,
strerror(errno));
return FALSE;
}
newpeer->peer_next = *list;
*list = newpeer;
newpeer->peer_info = strdup(peer);
if (newpeer->peer_info == NULL)
{
fprintf(stderr, "%s: strdup(): %s\n", progname,
strerror(errno));
return FALSE;
}
}
return TRUE;
}
/*
** DKF_INET_NTOA -- thread-safe inet_ntoa()
**
** Parameters:
** a -- (struct in_addr) to be converted
** buf -- destination buffer
** buflen -- number of bytes at buf
**
** Return value:
** Size of the resultant string. If the result is greater than buflen,
** then buf does not contain the complete result.
*/
size_t
dkf_inet_ntoa(struct in_addr a, char *buf, size_t buflen)
{
in_addr_t addr;
assert(buf != NULL);
addr = ntohl(a.s_addr);
return snprintf(buf, buflen, "%d.%d.%d.%d",
(addr >> 24), (addr >> 16) & 0xff,
(addr >> 8) & 0xff, addr & 0xff);
}
/*
** DKF_SPLITHDRS -- split up a "h=" stanza to look pretty
**
** Parameters:
** buf -- input string
** buflen -- number of bytes at "buf"
**
** Return value:
** None.
*/
void
dkf_splithdrs(char *buf, size_t buflen)
{
bool first = TRUE;
size_t len = 0;
char *last;
char *start;
char *p;
char tmp[MAXHEADERS];
assert(buf != NULL);
memset(tmp, '\0', sizeof tmp);
len = 8;
last = NULL;
start = buf;
for (p = buf; *p != '\0'; p++)
{
switch (*p)
{
case ':':
if (len + (size_t) (p - start) > MAXHDRLEN)
{
sm_strlcat(tmp, ":\n\t", sizeof tmp);
len = 8;
}
else if (!first)
{
sm_strlcat(tmp, ":", sizeof tmp);
len++;
}
first = FALSE;
*p = '\0';
sm_strlcat(tmp, start, sizeof tmp);
*p = ':';
start = NULL;
break;
case '=':
*p = '\0';
sm_strlcat(tmp, start, sizeof tmp);
*p = '=';
sm_strlcat(tmp, "=", sizeof tmp);
start = NULL;
len++;
break;
default:
if (start == NULL)
start = p;
len++;
break;
}
}
if (start != NULL)
{
if (!first)
sm_strlcat(tmp, ":", sizeof tmp);
sm_strlcat(tmp, start, sizeof tmp);
}
sm_strlcpy(buf, tmp, buflen);
}
/*
** DKF_TRIMSPACES -- trim trailing whitespace
**
** Parameters:
** str -- string to modify
**
** Return value:
** None.
*/
void
dkf_trimspaces(char *str)
{
char *p;
char *last;
assert(str != NULL);
last = NULL;
for (p = str; *p != '\0'; p++)
{
if (isascii(*p) && isspace(*p) && last == NULL)
{
last = p;
continue;
}
if (!isascii(*p) || !isspace(*p))
last = NULL;
}
if (last != NULL)
*last = '\0';
}
syntax highlighted by Code2HTML, v. 0.9.1