Index: sendmail/sendmail/sendmail/README diff -u sendmail/sendmail/sendmail/README:1.1.1.2 sendmail/sendmail/sendmail/README:1.3 --- sendmail/README Thu Jan 23 11:50:26 2003 +++ sendmail/README Tue Jan 28 16:55:41 2003 @@ -127,6 +127,8 @@ PH_MAP PH map support. You will need the libphclient library from the nph package (http://www-dev.cso.uiuc.edu/ph/nph/). MAP_NSD nsd map support (IRIX 6.5 and later). +SOCKETMAP Support for a trivial query protocol over UNIX domain or TCP + sockets. >>> NOTE WELL for NEWDB support: If you want to get ndbm support, for >>> Berkeley DB versions under 2.0, it is CRITICAL that you remove @@ -180,6 +182,50 @@ check_* rule-set, you can block a certain range of addresses that would otherwise be considered valid. +The socket map uses a simple request/reply protocol over TCP or UNIX domain +sockets to query an external server. Both requests and replies are text +based and encoded as D.J. Bernsteins netstrings. E.g., a string +"hello there" becomes: +11:hello there, + +NB. neither requests nor replies end with CRLF. + +The request consists of the database map name and the lookup key separated +by a space character: + + ' ' + +The server responds with a status indicator and the result (if any): + + ' ' + +The status indicator is one of the following upper case words: +OK (the key was found, result contains the looked up value) +NOTFOUND (the key was not found, the result is empty) +TEMP (a temporary failure occured) +TIMEOUT (a timeout occured on the server side) +PERM (a permanent failure occured) + +In case of errors (status TEMP, TIMEOUT or PERM) the result fied may +contain an explanatory message. + +Example replies: +30:OK resolved.addess@example.com, + +in case of a successful lookup, or: +7:NOTFOUND, + +in case the key was not found, or: +54:TEMP this text explains that we had a temporary failure, + +in case of a failure. + +The socket map uses the same syntax as milters the specify the remote +endpoint. E.g.: +Ksocket mySocketMap inet:12345@127.0.0.1 + +If multiple socket maps define the same remote endpoint, they will share +a single connection to this endpoint. +---------------+ | COMPILE FLAGS | Index: sendmail/sendmail/sendmail/conf.c diff -u sendmail/sendmail/sendmail/conf.c:1.1.1.2 sendmail/sendmail/sendmail/conf.c:1.6 --- sendmail/conf.c Thu Jan 23 11:50:27 2003 +++ sendmail/conf.c Fri Jan 24 15:31:59 2003 @@ -622,6 +622,13 @@ dequote_init, null_map_open, null_map_close, arith_map_lookup, null_map_store); +#if SOCKETMAP + /* arbitrary daemons */ + MAPDEF("socket", NULL, MCF_ALIASOK, + map_parseargs, socket_map_open, socket_map_close, + socket_map_lookup, null_map_store); +#endif /* SOCKETMAP */ + if (tTd(38, 2)) { /* bogus map -- always return tempfail */ Index: sendmail/sendmail/sendmail/map.c diff -u sendmail/sendmail/sendmail/map.c:1.1.1.2 sendmail/sendmail/sendmail/map.c:1.25 --- sendmail/map.c Thu Jan 23 11:50:27 2003 +++ sendmail/map.c Tue Feb 25 14:57:14 2003 @@ -66,6 +66,9 @@ static bool ni_getcanonname __P((char *, int, int *)); #endif /* NETINFO */ static bool text_getcanonname __P((char *, int, int *)); +#ifdef SOCKETMAP +static STAB * socket_map_findconn __P((const char*)); +#endif /* SOCKETMAP */ /* default error message for trying to open a map in write mode */ #ifdef ENOSYS @@ -7395,3 +7398,646 @@ *statp = EX_CONFIG; return NULL; } + +#ifdef SOCKETMAP + +# if NETINET || NETINET6 +# include +# endif /* NETINET || NETINET6 */ + +#define socket_map_next map_stack[0] +#define socket_map_previous map_stack[1] + +/* +** SOCKET_MAP_OPEN -- open socket table +*/ + +bool +socket_map_open(map, mode) + MAP *map; + int mode; +{ + STAB *s; + + int sock = 0; + SOCKADDR_LEN_T addrlen = 0; + int addrno = 0; + int save_errno; + char *p; + char *colon; + char *at; + struct hostent *hp = NULL; + SOCKADDR addr; + + if (tTd(38, 2)) + sm_dprintf("socket_map_open(%s, %s, %d)\n", + map->map_mname, map->map_file, mode); + + mode &= O_ACCMODE; + + /* sendmail doesn't have the ability to write to SOCKET (yet) */ + if (mode != O_RDONLY) + { + /* issue a pseudo-error message */ + errno = SM_EMAPCANTWRITE; + return false; + } + + if (*map->map_file == '\0') + { + syserr("socket map \"%s\": empty or missing socket information", + map->map_mname); + return false; + } + + s = socket_map_findconn(map->map_file); + if (s->s_socketmap != NULL) + { + /* Copy open connection */ + map->map_db1 = s->s_socketmap->map_db1; + + /* Add this map as head of linked list */ + map->socket_map_next = s->s_socketmap; + s->s_socketmap = map; + + if (tTd(38, 2)) + sm_dprintf("using cached connection\n"); + return true; + } + + if (tTd(38, 2)) + sm_dprintf("opening new connection\n"); + + + + /* following code is ripped from milter.c */ + + /* protocol:filename or protocol:port@host */ + memset(&addr, '\0', sizeof addr); + p = map->map_file; + colon = strchr(p, ':'); + if (colon != NULL) + { + *colon = '\0'; + + if (*p == '\0') + { +# if NETUNIX + /* default to AF_UNIX */ + addr.sa.sa_family = AF_UNIX; +# else /* NETUNIX */ +# if NETINET + /* default to AF_INET */ + addr.sa.sa_family = AF_INET; +# else /* NETINET */ +# if NETINET6 + /* default to AF_INET6 */ + addr.sa.sa_family = AF_INET6; +# else /* NETINET6 */ + /* no protocols available */ + syserr("socket map \"%s\": no valid socket protocols available", + map->map_mname); + return false; +# endif /* NETINET6 */ +# endif /* NETINET */ +# endif /* NETUNIX */ + } +# if NETUNIX + else if (sm_strcasecmp(p, "unix") == 0 || + sm_strcasecmp(p, "local") == 0) + addr.sa.sa_family = AF_UNIX; +# endif /* NETUNIX */ +# if NETINET + else if (sm_strcasecmp(p, "inet") == 0) + addr.sa.sa_family = AF_INET; +# endif /* NETINET */ +# if NETINET6 + else if (sm_strcasecmp(p, "inet6") == 0) + addr.sa.sa_family = AF_INET6; +# endif /* NETINET6 */ + else + { +# ifdef EPROTONOSUPPORT + errno = EPROTONOSUPPORT; +# else /* EPROTONOSUPPORT */ + errno = EINVAL; +# endif /* EPROTONOSUPPORT */ + syserr("socket map \"%s\": unknown socket type %s", + map->map_mname, p); + return false; + } + *colon++ = ':'; + } + else + { + /* default to AF_UNIX */ + addr.sa.sa_family = AF_UNIX; + colon = p; + } + +# if NETUNIX + if (addr.sa.sa_family == AF_UNIX) + { + long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_EXECOK; + + at = colon; + if (strlen(colon) >= sizeof addr.sunix.sun_path) + { + syserr("socket map \"%s\": local socket name %s too long", + map->map_mname, colon); + return false; + } + errno = safefile(colon, RunAsUid, RunAsGid, RunAsUserName, sff, + S_IRUSR|S_IWUSR, NULL); + + if (errno != 0) + { + /* if not safe, don't create */ + syserr("socket map \"%s\": local socket name %s unsafe", + map->map_mname, colon); + return false; + } + + (void) sm_strlcpy(addr.sunix.sun_path, colon, + sizeof addr.sunix.sun_path); + addrlen = sizeof (struct sockaddr_un); + } + else +# endif /* NETUNIX */ +# if NETINET || NETINET6 + if (false +# if NETINET + || addr.sa.sa_family == AF_INET +# endif /* NETINET */ +# if NETINET6 + || addr.sa.sa_family == AF_INET6 +# endif /* NETINET6 */ + ) + { + unsigned short port; + + /* Parse port@host */ + at = strchr(colon, '@'); + if (at == NULL) + { + syserr("socket map \"%s\": bad address %s (expected port@host)", + map->map_mname, colon); + return false; + } + *at = '\0'; + if (isascii(*colon) && isdigit(*colon)) + port = htons((unsigned short) atoi(colon)); + else + { +# ifdef NO_GETSERVBYNAME + syserr("socket map \"%s\": invalid port number %s", + map->map_mname, colon); + return false; +# else /* NO_GETSERVBYNAME */ + register struct servent *sp; + + sp = getservbyname(colon, "tcp"); + if (sp == NULL) + { + syserr("socket map \"%s\": unknown port name %s", + map->map_mname, colon); + return false; + } + port = sp->s_port; +# endif /* NO_GETSERVBYNAME */ + } + *at++ = '@'; + if (*at == '[') + { + char *end; + + end = strchr(at, ']'); + if (end != NULL) + { + bool found = false; +# if NETINET + unsigned long hid = INADDR_NONE; +# endif /* NETINET */ +# if NETINET6 + struct sockaddr_in6 hid6; +# endif /* NETINET6 */ + + *end = '\0'; +# if NETINET + if (addr.sa.sa_family == AF_INET && + (hid = inet_addr(&at[1])) != INADDR_NONE) + { + addr.sin.sin_addr.s_addr = hid; + addr.sin.sin_port = port; + found = true; + } +# endif /* NETINET */ +# if NETINET6 + (void) memset(&hid6, '\0', sizeof hid6); + if (addr.sa.sa_family == AF_INET6 && + anynet_pton(AF_INET6, &at[1], + &hid6.sin6_addr) == 1) + { + addr.sin6.sin6_addr = hid6.sin6_addr; + addr.sin6.sin6_port = port; + found = true; + } +# endif /* NETINET6 */ + *end = ']'; + if (!found) + { + syserr("socket map \"%s\": Invalid numeric domain spec \"%s\"", + map->map_mname, at); + return false; + } + } + else + { + syserr("socket map \"%s\": Invalid numeric domain spec \"%s\"", + map->map_mname, at); + return false; + } + } + else + { + hp = sm_gethostbyname(at, addr.sa.sa_family); + if (hp == NULL) + { + syserr("socket map \"%s\": Unknown host name %s", + map->map_mname, at); + return false; + } + addr.sa.sa_family = hp->h_addrtype; + switch (hp->h_addrtype) + { +# if NETINET + case AF_INET: + memmove(&addr.sin.sin_addr, + hp->h_addr, INADDRSZ); + addr.sin.sin_port = port; + addrlen = sizeof (struct sockaddr_in); + addrno = 1; + break; +# endif /* NETINET */ + +# if NETINET6 + case AF_INET6: + memmove(&addr.sin6.sin6_addr, + hp->h_addr, IN6ADDRSZ); + addr.sin6.sin6_port = port; + addrlen = sizeof (struct sockaddr_in6); + addrno = 1; + break; +# endif /* NETINET6 */ + + default: + syserr("socket map \"%s\": Unknown protocol for %s (%d)", + map->map_mname, at, hp->h_addrtype); +# if NETINET6 + freehostent(hp); +# endif /* NETINET6 */ + return false; + } + } + } + else +# endif /* NETINET || NETINET6 */ + { + syserr("socket map \"%s\": unknown socket protocol", map->map_mname); + return false; + } + + /* nope, actually connecting */ + for (;;) + { + sock = socket(addr.sa.sa_family, SOCK_STREAM, 0); + if (sock < 0) + { + save_errno = errno; + if (tTd(38, 5)) + sm_dprintf("socket map \"%s\": error creating socket: %s\n", + map->map_mname, + sm_errstring(save_errno)); +# if NETINET6 + if (hp != NULL) + freehostent(hp); +# endif /* NETINET6 */ + return false; + } + + if (connect(sock, (struct sockaddr *) &addr, addrlen) >= 0) + break; + + /* couldn't connect.... try next address */ + save_errno = errno; + p = CurHostName; + CurHostName = at; + if (tTd(38, 5)) + sm_dprintf("socket_open (%s): open %s failed: %s\n", + map->map_mname, at, sm_errstring(save_errno)); + CurHostName = p; + (void) close(sock); + + /* try next address */ + if (hp != NULL && hp->h_addr_list[addrno] != NULL) + { + switch (addr.sa.sa_family) + { +# if NETINET + case AF_INET: + memmove(&addr.sin.sin_addr, + hp->h_addr_list[addrno++], + INADDRSZ); + break; +# endif /* NETINET */ + +# if NETINET6 + case AF_INET6: + memmove(&addr.sin6.sin6_addr, + hp->h_addr_list[addrno++], + IN6ADDRSZ); + break; +# endif /* NETINET6 */ + + default: + if (tTd(38, 5)) + sm_dprintf("socket map \"%s\": Unknown protocol for %s (%d)\n", + map->map_mname, at, + hp->h_addrtype); +# if NETINET6 + freehostent(hp); +# endif /* NETINET6 */ + return false; + } + continue; + } + p = CurHostName; + CurHostName = at; + if (tTd(38, 5)) + sm_dprintf("socket map \"%s\": error connecting to socket map: %s\n", + map->map_mname, sm_errstring(save_errno)); + CurHostName = p; +# if NETINET6 + if (hp != NULL) + freehostent(hp); +# endif /* NETINET6 */ + return false; + } +# if NETINET6 + if (hp != NULL) + { + freehostent(hp); + hp = NULL; + } +# endif /* NETINET6 */ + if ((map->map_db1 = (ARBPTR_T) sm_io_open(SmFtStdiofd, + SM_TIME_DEFAULT, + (void *) &sock, + SM_IO_RDWR, + NULL)) == NULL) + { + close(sock); + if (tTd(38, 2)) + sm_dprintf("socket_open (%s): failed to create stream: %s\n", + map->map_mname, sm_errstring(errno)); + return false; + } + + /* Save connection for reuse */ + s->s_socketmap = map; + return true; +} + +/* +** SOCKET_MAP_FINDCONN -- find a SOCKET connection to the server +** +** Cache SOCKET connections based on the connection specifier +** and PID so we don't have multiple connections open to +** the same server for different maps. Need a separate connection +** per PID since a parent process may close the map before the +** child is done with it. +** +** Parameters: +** conn -- SOCKET map connection specifier +** +** Returns: +** Symbol table entry for the SOCKET connection. +*/ + +static STAB * +socket_map_findconn(conn) + const char *conn; +{ + char *format; + char *nbuf; + STAB *SM_NONVOLATILE s = NULL; + + format = "%s%c%d"; + nbuf = sm_stringf_x(format, + conn, + CONDELSE, + (int) CurrentPid); + if (tTd(38, 20)) + sm_dprintf("socket_find_conn '%s'\n", nbuf); + SM_TRY + s = stab(nbuf, ST_SOCKETMAP, ST_ENTER); + SM_FINALLY + sm_free(nbuf); + SM_END_TRY + return s; +} + +/* +** SOCKET_MAP_CLOSE -- close the socket +*/ + +void +socket_map_close(map) + MAP *map; +{ + STAB *s; + MAP *smap; + + if (tTd(38, 20)) + sm_dprintf("socket_map_close(%s), pid=%d\n", map->map_file,(int) CurrentPid); + + /* Check if already closed */ + if (!map->map_db1) + { + if (tTd(38, 20)) + sm_dprintf("socket_map_close(%s) already closed\n", map->map_file); + return; + } + + sm_io_close((SM_FILE_T *)map->map_db1, SM_TIME_DEFAULT); + + /* Mark all the maps that share the connection as closed */ + s = socket_map_findconn(map->map_file); + + smap = s->s_socketmap; + + while (smap != NULL) + { + MAP *next; + + if (tTd(38, 2) && smap != map) + sm_dprintf("socket_map_close(%s): closed %s (shared SOCKET connection)\n", + map->map_mname, smap->map_mname); + + smap->map_mflags &= ~(MF_OPEN|MF_WRITABLE); + smap->map_db1 = NULL; + next = smap->socket_map_next; + smap->socket_map_next = NULL; + smap = next; + } + + s->s_socketmap = NULL; +} + +/* +** SOCKET_MAP_LOOKUP -- look up a datum in a SOCKET table +*/ + +char * +socket_map_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + size_t nettolen; + char *rval = NULL; + SM_FILE_T *f = (SM_FILE_T *)map->map_db1; + + if (tTd(38, 20)) + sm_dprintf("socket_map_lookup(%s, %s) %s\n", + map->map_mname, name, map->map_file); + + nettolen = strlen(map->map_mname) + 1 + strlen(name); + if ((sm_io_fprintf(f, SM_TIME_DEFAULT, "%u:%s %s,", + nettolen, map->map_mname, name) == SM_IO_EOF) || (sm_io_flush(f, SM_TIME_DEFAULT) != 0) || + (sm_io_error(f))) + { + syserr("socket_map_lookup(%s): failed to send lookup request", + map->map_mname); + *statp = EX_TEMPFAIL; + socket_map_close(map); + } + else + { + size_t replylen; + + if (sm_io_fscanf(f, SM_TIME_DEFAULT, "%9u", &replylen) != 1) + { + syserr("socket_map_lookup(%s): failed to read length parameter of reply", + map->map_mname); + *statp = EX_TEMPFAIL; + socket_map_close(map); + } + else + { + /* XXX arbitrary limit for sanity */ + if (replylen > 1000000) + { + syserr("socket_map_lookup(%s): reply too long: %u", + map->map_mname, replylen); + /* *statp = EX_PROTOCOL; */ + *statp = EX_TEMPFAIL; + socket_map_close(map); + } + else + { + if (sm_io_getc(f, SM_TIME_DEFAULT) != ':') + { + syserr("socket_map_lookup(%s): missing ':' in reply", + map->map_mname); + /* *statp = EX_PROTOCOL; */ + *statp = EX_TEMPFAIL; + } + else + { + size_t recvlen; + char *replybuf = (char *) sm_malloc(replylen + 1); + recvlen = sm_io_read(f, SM_TIME_DEFAULT, + replybuf, replylen); + if (recvlen < replylen) + { + syserr("socket_map_lookup(%s): received only %u of %u reply characters", + map->map_mname, recvlen, replylen); + *statp = EX_TEMPFAIL; + socket_map_close(map); + } + else + { + if (sm_io_getc(f, SM_TIME_DEFAULT) != ',') + { + syserr("socket_map_lookup(%s): missing ',' in reply", + map->map_mname); + /* *statp = EX_PROTOCOL; */ + *statp = EX_TEMPFAIL; + socket_map_close(map); + } + else + { + char *value; + char *status = replybuf; + + replybuf[recvlen] = '\0'; + value = strchr(replybuf, ' '); + if (value != NULL) + { + *value = '\0'; + value++; + } + + if (strcmp(status, "OK") == 0) + { + *statp = EX_OK; + + /* collect the return value */ + if (bitset(MF_MATCHONLY, map->map_mflags)) + rval = map_rewrite(map, name, strlen(name), NULL); + else + rval = map_rewrite(map, value, strlen(value), av); + } + else if(strcmp(status, "NOTFOUND") == 0) + { + *statp = EX_NOTFOUND; + if (tTd(38, 20)) + sm_dprintf("socket_map_lookup(%s): %s not found\n", + map->map_mname, name); + } + else + { + if (tTd(38, 5)) + sm_dprintf("socket_map_lookup(%s, %s): server returned error: type=%s, reason=%s\n", + map->map_mname, name, status, + value ? value : ""); + if ((strcmp(status, "TEMP") == 0) || + (strcmp(status, "TIMEOUT") == 0)) + { + *statp = EX_TEMPFAIL; + } + else if(strcmp(status, "PERM") == 0) + { + *statp = EX_UNAVAILABLE; + } + else + { + *statp = EX_PROTOCOL; + } + } + } + } + + sm_free(replybuf); + } + } + } + } + + return rval; +} + + +#endif /* SOCKETMAP */ Index: sendmail/sendmail/sendmail/sendmail.h diff -u sendmail/sendmail/sendmail/sendmail.h:1.1.1.2 sendmail/sendmail/sendmail/sendmail.h:1.3 --- sendmail/sendmail.h Thu Jan 23 11:50:27 2003 +++ sendmail/sendmail.h Thu Jan 23 11:51:38 2003 @@ -1382,6 +1382,9 @@ #if LDAPMAP MAP *sv_lmap; /* Maps for LDAP connection */ #endif /* LDAPMAP */ +#if SOCKETMAP + MAP *sv_socketmap; /* Maps for SOCKET connection */ +#endif /* SOCKETMAP */ #if MILTER struct milter *sv_milter; /* milter filter name */ #endif /* MILTER */ @@ -1413,8 +1416,12 @@ #endif /* MILTER */ #define ST_QUEUE 15 /* a queue entry */ +#if SOCKETMAP +# define ST_SOCKETMAP 16 /* List head of maps for SOCKET connection */ +#endif /* SOCKETMAP */ + /* This entry must be last */ -#define ST_MCI 16 /* mailer connection info (offset) */ +#define ST_MCI 17 /* mailer connection info (offset) */ #define s_class s_value.sv_class #define s_address s_value.sv_addr @@ -1432,6 +1439,9 @@ #if LDAPMAP # define s_lmap s_value.sv_lmap #endif /* LDAPMAP */ +#if SOCKETMAP +# define s_socketmap s_value.sv_socketmap +#endif /* SOCKETMAP */ #if MILTER # define s_milter s_value.sv_milter #endif /* MILTER */ Index: sendmail/sendmail/sendmail/stab.c diff -u sendmail/sendmail/sendmail/stab.c:1.1.1.1 sendmail/sendmail/sendmail/stab.c:1.2 --- sendmail/stab.c Fri Oct 11 14:44:04 2002 +++ sendmail/stab.c Wed Jan 22 18:57:22 2003 @@ -173,6 +173,12 @@ len = sizeof s->s_quegrp; break; +#if SOCKETMAP + case ST_SOCKETMAP: + len = sizeof s->s_socketmap; + break; +#endif /* SOCKETMAP */ + default: /* ** Each mailer has its own MCI stab entry: