/* ** 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* libsm includes */ #include #include #if POPAUTH /* system includes */ # include /* libdb includes */ # include #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'; }