365 lines
10 KiB
C
365 lines
10 KiB
C
/*++
|
|
/* NAME
|
|
/* smtp_connect 3
|
|
/* SUMMARY
|
|
/* connect to SMTP server
|
|
/* SYNOPSIS
|
|
/* #include "smtp.h"
|
|
/*
|
|
/* SMTP_SESSION *smtp_connect(destination, why)
|
|
/* char *destination;
|
|
/* VSTRING *why;
|
|
/* AUXILIARY FUNCTIONS
|
|
/* SMTP_SESSION *smtp_connect_domain(name, port, why)
|
|
/* char *name;
|
|
/* unsigned port;
|
|
/* VSTRING *why;
|
|
/*
|
|
/* SMTP_SESSION *smtp_connect_host(name, port, why)
|
|
/* char *name;
|
|
/* unsigned port;
|
|
/* VSTRING *why;
|
|
/* DESCRIPTION
|
|
/* This module implements SMTP connection management.
|
|
/*
|
|
/* smtp_connect() attempts to establish an SMTP session with a host
|
|
/* that represents the named domain.
|
|
/*
|
|
/* The destination is either a host (or domain) name or a numeric
|
|
/* address. Symbolic or numeric service port information may be
|
|
/* appended, separated by a colon (":").
|
|
/*
|
|
/* By default, the Internet domain name service is queried for mail
|
|
/* exchanger hosts. Quote the destination with `[' and `]' to
|
|
/* suppress mail exchanger lookups.
|
|
/*
|
|
/* Numerical address information should always be quoted with `[]'.
|
|
/*
|
|
/* smtp_connect_domain() attempts to make an SMTP connection to
|
|
/* the named host or domain and network port (network byte order).
|
|
/* \fIname\fR is used to look up mail exchanger information via
|
|
/* the Internet domain name system (DNS).
|
|
/* When no mail exchanger is listed for \fIname\fR, the request
|
|
/* is passed to smtp_connect_host().
|
|
/* Otherwise, mail exchanger hosts are tried in order of preference,
|
|
/* until one is found that responds. In order to avoid mailer loops,
|
|
/* the search for mail exchanger hosts stops when a host is found
|
|
/* that has the same preference as the sending machine.
|
|
/*
|
|
/* smtp_connect_host() makes an SMTP connection without looking up
|
|
/* mail exchanger information. The host can be specified as an
|
|
/* Internet network address or as a symbolic host name.
|
|
/* DIAGNOSTICS
|
|
/* All routines either return an SMTP_SESSION pointer, or
|
|
/* return a null pointer and set the \fIsmtp_errno\fR
|
|
/* global variable accordingly:
|
|
/* .IP SMTP_RETRY
|
|
/* The connection attempt failed, but should be retried later.
|
|
/* .IP SMTP_FAIL
|
|
/* The connection attempt failed.
|
|
/* .PP
|
|
/* In addition, a textual description of the error is made available
|
|
/* via the \fIwhy\fR argument.
|
|
/* SEE ALSO
|
|
/* smtp_proto(3) SMTP client protocol
|
|
/* LICENSE
|
|
/* .ad
|
|
/* .fi
|
|
/* The Secure Mailer license must be distributed with this software.
|
|
/* AUTHOR(S)
|
|
/* Wietse Venema
|
|
/* IBM T.J. Watson Research
|
|
/* P.O. Box 704
|
|
/* Yorktown Heights, NY 10598, USA
|
|
/*--*/
|
|
|
|
/* System library. */
|
|
|
|
#include <sys_defs.h>
|
|
#include <sys/socket.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
|
|
|
|
/* Utility library. */
|
|
|
|
#include <msg.h>
|
|
#include <vstream.h>
|
|
#include <vstring.h>
|
|
#include <split_at.h>
|
|
#include <mymalloc.h>
|
|
#include <inet_addr_list.h>
|
|
#include <iostuff.h>
|
|
#include <timed_connect.h>
|
|
|
|
/* Global library. */
|
|
|
|
#include <mail_params.h>
|
|
#include <own_inet_addr.h>
|
|
|
|
/* DNS library. */
|
|
|
|
#include <dns.h>
|
|
|
|
/* Application-specific. */
|
|
|
|
#include "smtp.h"
|
|
#include "smtp_addr.h"
|
|
|
|
/* smtp_connect_addr - connect to explicit address */
|
|
|
|
static SMTP_SESSION *smtp_connect_addr(DNS_RR *addr, unsigned port,
|
|
VSTRING *why)
|
|
{
|
|
char *myname = "smtp_connect_addr";
|
|
struct sockaddr_in sin;
|
|
int sock;
|
|
INET_ADDR_LIST *addr_list;
|
|
int conn_stat;
|
|
int saved_errno;
|
|
VSTREAM *stream;
|
|
int ch;
|
|
unsigned long inaddr;
|
|
|
|
/*
|
|
* Sanity checks.
|
|
*/
|
|
if (addr->data_len > sizeof(sin.sin_addr)) {
|
|
msg_warn("%s: skip address with length %d", myname, addr->data_len);
|
|
smtp_errno = SMTP_RETRY;
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Initialize.
|
|
*/
|
|
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);
|
|
|
|
/*
|
|
* When running as a virtual host, bind to the virtual interface so that
|
|
* the mail appears to come from the "right" machine address.
|
|
*/
|
|
addr_list = own_inet_addr_list();
|
|
if (addr_list->used == 1) {
|
|
sin.sin_port = 0;
|
|
memcpy((char *) &sin.sin_addr, addr_list->addrs, sizeof(sin.sin_addr));
|
|
inaddr = ntohl(sin.sin_addr.s_addr);
|
|
if (!IN_CLASSA(inaddr)
|
|
|| !(((inaddr & IN_CLASSA_NET) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)) {
|
|
if (bind(sock, (struct sockaddr *) & sin, sizeof(sin)) < 0)
|
|
msg_warn("%s: bind %s: %m", myname, inet_ntoa(sin.sin_addr));
|
|
if (msg_verbose)
|
|
msg_info("%s: bind %s", myname, inet_ntoa(sin.sin_addr));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Connect to the SMTP server.
|
|
*/
|
|
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));
|
|
if (var_smtp_conn_tmout > 0) {
|
|
non_blocking(sock, NON_BLOCKING);
|
|
conn_stat = timed_connect(sock, (struct sockaddr *) & sin,
|
|
sizeof(sin), var_smtp_conn_tmout);
|
|
saved_errno = errno;
|
|
non_blocking(sock, BLOCKING);
|
|
errno = saved_errno;
|
|
} else {
|
|
conn_stat = connect(sock, (struct sockaddr *) & sin, sizeof(sin));
|
|
}
|
|
if (conn_stat < 0) {
|
|
vstring_sprintf(why, "connect to %s[%s]: %m",
|
|
addr->name, inet_ntoa(sin.sin_addr));
|
|
smtp_errno = SMTP_RETRY;
|
|
close(sock);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Skip this host if it takes no action within some time limit.
|
|
*/
|
|
if (read_wait(sock, var_smtp_helo_tmout) < 0) {
|
|
vstring_sprintf(why, "connect to %s[%s]: read timeout",
|
|
addr->name, inet_ntoa(sin.sin_addr));
|
|
smtp_errno = SMTP_RETRY;
|
|
close(sock);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Skip this host if it disconnects without talking to us.
|
|
*/
|
|
stream = vstream_fdopen(sock, O_RDWR);
|
|
if ((ch = VSTREAM_GETC(stream)) == VSTREAM_EOF) {
|
|
vstring_sprintf(why, "connect to %s[%s]: server dropped connection",
|
|
addr->name, inet_ntoa(sin.sin_addr));
|
|
smtp_errno = SMTP_RETRY;
|
|
vstream_fclose(stream);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Skip this host if it sends a 4xx greeting.
|
|
*/
|
|
if (ch == '4' && var_smtp_skip_4xx_greeting) {
|
|
vstring_sprintf(why, "connect to %s[%s]: server refused mail service",
|
|
addr->name, inet_ntoa(sin.sin_addr));
|
|
smtp_errno = SMTP_RETRY;
|
|
vstream_fclose(stream);
|
|
return (0);
|
|
}
|
|
vstream_ungetc(stream, ch);
|
|
return (smtp_session_alloc(stream, addr->name, inet_ntoa(sin.sin_addr)));
|
|
}
|
|
|
|
/* smtp_connect_host - direct connection to host */
|
|
|
|
SMTP_SESSION *smtp_connect_host(char *host, unsigned port, VSTRING *why)
|
|
{
|
|
SMTP_SESSION *session = 0;
|
|
DNS_RR *addr_list;
|
|
DNS_RR *addr;
|
|
|
|
/*
|
|
* Try each address in the specified order until we find one that works.
|
|
* The addresses belong to the same A record, so we have no information
|
|
* on what address is "best".
|
|
*/
|
|
addr_list = smtp_host_addr(host, why);
|
|
for (addr = addr_list; addr; addr = addr->next) {
|
|
if ((session = smtp_connect_addr(addr, port, why)) != 0) {
|
|
session->best = 1;
|
|
break;
|
|
}
|
|
msg_info("%s (port %d)", vstring_str(why), ntohs(port));
|
|
}
|
|
dns_rr_free(addr_list);
|
|
return (session);
|
|
}
|
|
|
|
/* smtp_connect_domain - connect to smtp server for domain */
|
|
|
|
SMTP_SESSION *smtp_connect_domain(char *name, unsigned port, VSTRING *why)
|
|
{
|
|
SMTP_SESSION *session = 0;
|
|
DNS_RR *addr_list;
|
|
DNS_RR *addr;
|
|
|
|
/*
|
|
* Try each mail exchanger in order of preference until we find one that
|
|
* responds. Once we find a server that responds we never try
|
|
* alternative mail exchangers. The benefit of this is that we will use
|
|
* backup hosts only when we are unable to reach the primary MX host. If
|
|
* the primary MX host is reachable but does not want to receive our
|
|
* mail, there is no point in trying the backup hosts.
|
|
*/
|
|
addr_list = smtp_domain_addr(name, why);
|
|
for (addr = addr_list; addr; addr = addr->next) {
|
|
if ((session = smtp_connect_addr(addr, port, why)) != 0) {
|
|
session->best = (addr->pref == addr_list->pref);
|
|
break;
|
|
}
|
|
msg_info("%s (port %d)", vstring_str(why), ntohs(port));
|
|
}
|
|
dns_rr_free(addr_list);
|
|
return (session);
|
|
}
|
|
|
|
/* smtp_parse_destination - parse destination */
|
|
|
|
static char *smtp_parse_destination(char *destination, char *def_service,
|
|
char **hostp, unsigned *portp)
|
|
{
|
|
char *buf = mystrdup(destination);
|
|
char *host = buf;
|
|
char *service;
|
|
struct servent *sp;
|
|
char *protocol = "tcp"; /* XXX configurable? */
|
|
unsigned port;
|
|
|
|
if (msg_verbose)
|
|
msg_info("smtp_parse_destination: %s %s", destination, def_service);
|
|
|
|
/*
|
|
* Strip quoting. We're working with a copy of the destination argument
|
|
* so the stripping can be destructive.
|
|
*/
|
|
if (*host == '[') {
|
|
host++;
|
|
host[strcspn(host, "]")] = 0;
|
|
}
|
|
|
|
/*
|
|
* Separate host and service information, or use the default service
|
|
* specified by the caller. XXX the ":" character is used in the IPV6
|
|
* address notation, so using split_at_right() is not sufficient. We'd
|
|
* have to count the number of ":" instances.
|
|
*/
|
|
if ((service = split_at_right(host, ':')) == 0)
|
|
service = def_service;
|
|
if (*service == 0)
|
|
msg_fatal("empty service name: %s", destination);
|
|
*hostp = host;
|
|
|
|
/*
|
|
* Convert service to port number, network byte order.
|
|
*/
|
|
if ((port = atoi(service)) != 0) {
|
|
*portp = htons(port);
|
|
} else {
|
|
if ((sp = getservbyname(service, protocol)) == 0)
|
|
msg_fatal("unknown service: %s/%s", service, protocol);
|
|
*portp = sp->s_port;
|
|
}
|
|
return (buf);
|
|
}
|
|
|
|
/* smtp_connect - establish SMTP connection */
|
|
|
|
SMTP_SESSION *smtp_connect(char *destination, VSTRING *why)
|
|
{
|
|
SMTP_SESSION *session;
|
|
char *dest_buf;
|
|
char *host;
|
|
unsigned port;
|
|
char *def_service = "smtp"; /* XXX configurable? */
|
|
|
|
/*
|
|
* Parse the destination specification. Default is to use the SMTP port.
|
|
*/
|
|
dest_buf = smtp_parse_destination(destination, def_service, &host, &port);
|
|
|
|
/*
|
|
* Connect to an SMTP server. Skip mail exchanger lookups when a quoted
|
|
* host is specified, or when DNS lookups are disabled.
|
|
*/
|
|
if (msg_verbose)
|
|
msg_info("connecting to %s port %d", host, ntohs(port));
|
|
if (var_disable_dns || *destination == '[') {
|
|
session = smtp_connect_host(host, port, why);
|
|
} else {
|
|
session = smtp_connect_domain(host, port, why);
|
|
}
|
|
myfree(dest_buf);
|
|
return (session);
|
|
}
|