#include <sys_defs.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
#include <msg.h>
#include <vstream.h>
#include <vstring.h>
#include <split_at.h>
#include <mymalloc.h>
#include <iostuff.h>
#include <timed_connect.h>
#include <stringops.h>
#include <host_port.h>
#include <mail_params.h>
#include <mail_proto.h>
#include <dns.h>
#include "lmtp.h"
#include "lmtp_addr.h"
static LMTP_SESSION *lmtp_connect_sock(int, struct sockaddr *, int,
const char *, const char *,
const char *, VSTRING *);
static LMTP_SESSION *lmtp_connect_unix(const char *addr,
const char *destination, VSTRING *why)
{
#undef sun
char *myname = "lmtp_connect_unix";
struct sockaddr_un sun;
int len = strlen(addr);
int sock;
if (len >= (int) sizeof(sun.sun_path)) {
msg_warn("unix-domain name too long: %s", addr);
lmtp_errno = LMTP_RETRY;
return (0);
}
memset((char *) &sun, 0, sizeof(sun));
sun.sun_family = AF_UNIX;
#ifdef HAS_SUN_LEN
sun.sun_len = len + 1;
#endif
memcpy(sun.sun_path, addr, len + 1);
if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
msg_fatal("%s: socket: %m", myname);
if (msg_verbose)
msg_info("%s: trying: %s...", myname, addr);
return (lmtp_connect_sock(sock, (struct sockaddr *) & sun, sizeof(sun),
addr, addr, destination, why));
}
static LMTP_SESSION *lmtp_connect_addr(DNS_RR *addr, unsigned port,
const char *destination, VSTRING *why)
{
char *myname = "lmtp_connect_addr";
struct sockaddr_in sin;
int sock;
if (addr->data_len > sizeof(sin.sin_addr)) {
msg_warn("%s: skip address with length %d", myname, addr->data_len);
lmtp_errno = LMTP_RETRY;
return (0);
}
memset((char *) &sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
if ((sock = socket(sin.sin_family, SOCK_STREAM, 0)) < 0)
msg_fatal("%s: socket: %m", myname);
sin.sin_port = port;
memcpy((char *) &sin.sin_addr, addr->data, sizeof(sin.sin_addr));
if (msg_verbose)
msg_info("%s: trying: %s[%s] port %d...",
myname, addr->name, inet_ntoa(sin.sin_addr), ntohs(port));
return (lmtp_connect_sock(sock, (struct sockaddr *) & sin, sizeof(sin),
addr->name, inet_ntoa(sin.sin_addr),
destination, why));
}
static LMTP_SESSION *lmtp_connect_sock(int sock, struct sockaddr * sa, int len,
const char *name, const char *addr,
const char *destination, VSTRING *why)
{
int conn_stat;
int saved_errno;
VSTREAM *stream;
int ch;
if (var_lmtp_conn_tmout > 0) {
non_blocking(sock, NON_BLOCKING);
conn_stat = timed_connect(sock, sa, len, var_lmtp_conn_tmout);
saved_errno = errno;
non_blocking(sock, BLOCKING);
errno = saved_errno;
} else {
conn_stat = connect(sock, sa, len);
}
if (conn_stat < 0) {
vstring_sprintf(why, "connect to %s[%s]: %m",
name, addr);
lmtp_errno = LMTP_RETRY;
close(sock);
return (0);
}
if (read_wait(sock, var_lmtp_lhlo_tmout) < 0) {
vstring_sprintf(why, "connect to %s[%s]: read timeout",
name, addr);
lmtp_errno = LMTP_RETRY;
close(sock);
return (0);
}
stream = vstream_fdopen(sock, O_RDWR);
if ((ch = VSTREAM_GETC(stream)) == VSTREAM_EOF) {
vstring_sprintf(why, "connect to %s[%s]: server dropped connection without sending the initial greeting",
name, addr);
lmtp_errno = LMTP_RETRY;
vstream_fclose(stream);
return (0);
}
vstream_ungetc(stream, ch);
if (ch == '4' || ch == '5') {
vstring_sprintf(why, "connect to %s[%s]: server refused mail service",
name, addr);
lmtp_errno = LMTP_RETRY;
vstream_fclose(stream);
return (0);
}
return (lmtp_session_alloc(stream, name, addr, destination));
}
static LMTP_SESSION *lmtp_connect_host(char *host, unsigned port,
const char *destination, VSTRING *why)
{
LMTP_SESSION *session = 0;
DNS_RR *addr_list;
DNS_RR *addr;
addr_list = lmtp_host_addr(host, why);
for (addr = addr_list; addr; addr = addr->next) {
if ((session = lmtp_connect_addr(addr, port, destination, why)) != 0) {
break;
}
}
dns_rr_free(addr_list);
return (session);
}
static char *lmtp_parse_destination(const char *destination, char *def_service,
char **hostp, unsigned *portp)
{
char *myname = "lmtp_parse_destination";
char *buf = mystrdup(destination);
char *service;
struct servent *sp;
char *protocol = "tcp";
unsigned port;
const char *err;
if (msg_verbose)
msg_info("%s: %s %s", myname, destination, def_service);
if ((err = host_port(buf, hostp, &service, def_service)) != 0)
msg_fatal("%s in LMTP server description: %s", err, destination);
if ((port = atoi(service)) != 0)
*portp = htons(port);
else if ((sp = getservbyname(service, protocol)) != 0)
*portp = sp->s_port;
else
*portp = htons(var_lmtp_tcp_port);
return (buf);
}
LMTP_SESSION *lmtp_connect(const char *destination, VSTRING *why)
{
char *myname = "lmtp_connect";
LMTP_SESSION *session;
char *dest_buf;
char *host;
unsigned port;
char *def_service = "lmtp";
if (strncmp(destination, "unix:", 5) == 0)
return (lmtp_connect_unix(destination + 5, destination, why));
if (strncmp(destination, "inet:", 5) == 0)
dest_buf = lmtp_parse_destination(destination + 5, def_service,
&host, &port);
else
dest_buf = lmtp_parse_destination(destination, def_service,
&host, &port);
if (msg_verbose)
msg_info("%s: connecting to %s port %d", myname, host, ntohs(port));
session = lmtp_connect_host(host, port, destination, why);
myfree(dest_buf);
return (session);
}