/*++ /* NAME /* lmtp_connect 3 /* SUMMARY /* connect to LMTP server /* SYNOPSIS /* #include "lmtp.h" /* /* LMTP_SESSION *lmtp_connect(destination, why) /* char *destination; /* VSTRING *why; /* DESCRIPTION /* This module implements LMTP connection management. /* /* lmtp_connect() attempts to establish an LMTP session. /* /* The destination is one of the following: /* .IP unix:address /* Connect to a UNIX-domain service. The destination is a pathname. /* Beware: UNIX-domain sockets are flakey on Solaris, at last up to /* and including Solaris 7.0. /* .IP inet:address /* Connect to an IPV4 service. /* The destination is either a host name or a numeric address. /* Symbolic or numeric service port information may be appended, /* separated by a colon (":"). /* /* Numerical address information should always be quoted with `[]'. /* .PP /* When no transport is specified, "inet" is assumed. /* DIAGNOSTICS /* This routine either returns an LMTP_SESSION pointer, or /* returns a null pointer and set the \fIlmtp_errno\fR /* global variable accordingly: /* .IP LMTP_RETRY /* The connection attempt failed, but should be retried later. /* .IP LMTP_FAIL /* The connection attempt failed. /* .PP /* In addition, a textual description of the error is made available /* via the \fIwhy\fR argument. /* SEE ALSO /* lmtp_proto(3) LMTP 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 /* /* Alterations for LMTP by: /* Philip A. Prindeville /* Mirapoint, Inc. /* USA. /* /* Additional work on LMTP by: /* Amos Gouaux /* University of Texas at Dallas /* P.O. Box 830688, MC34 /* Richardson, TX 75083, USA /*--*/ /* System library. */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef STRCASECMP_IN_STRINGS_H #include #endif /* Utility library. */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* Global library. */ #include #include #include /* DNS library. */ #include /* Application-specific. */ #include "lmtp.h" #include "lmtp_addr.h" /* * Forward declaration. */ static LMTP_SESSION *lmtp_connect_sock(int, struct sockaddr *, int, const char *, const char *, const char *, VSTRING *); /* lmtp_connect_unix - connect to UNIX-domain address */ 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; /* * Sanity checks. */ if (len >= (int) sizeof(sun.sun_path)) { msg_warn("unix-domain name too long: %s", addr); lmtp_errno = LMTP_RETRY; return (0); } /* * Initialize. */ 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); /* * Create a client socket. */ if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) msg_fatal("%s: socket: %m", myname); /* * Connect to the LMTP server. */ if (msg_verbose) msg_info("%s: trying: %s...", myname, addr); return (lmtp_connect_sock(sock, (struct sockaddr *) & sun, sizeof(sun), addr, addr, destination, why)); } /* lmtp_connect_addr - connect to explicit address */ static LMTP_SESSION *lmtp_connect_addr(DNS_RR *addr, unsigned port, const char *destination, VSTRING *why) { char *myname = "lmtp_connect_addr"; struct sockaddr_storage ss; struct sockaddr *sa = SOCK_ADDR_PTR(&ss); SOCKADDR_SIZE salen = sizeof(ss); MAI_HOSTADDR_STR hostaddr; int sock; /* * Sanity checks. */ if (dns_rr_to_sa(addr, port, sa, &salen) != 0) { msg_warn("%s: skip address type %s: %m", myname, dns_strtype(addr->type)); lmtp_errno = LMTP_RETRY; return (0); } /* * Initialize. */ if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) msg_fatal("%s: socket: %m", myname); /* * Connect to the LMTP server. */ SOCKADDR_TO_HOSTADDR(sa, salen, &hostaddr, (MAI_SERVPORT_STR *) 0, 0); if (msg_verbose) msg_info("%s: trying: %s[%s] port %d...", myname, addr->name, hostaddr.buf, ntohs(port)); return (lmtp_connect_sock(sock, sa, salen, addr->name, hostaddr.buf, destination, why)); } /* lmtp_connect_sock - connect a socket over some transport */ 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 = sane_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); } /* * Skip this host if it takes no action within some time limit. */ 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); } /* * 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 without sending the initial greeting", name, addr); lmtp_errno = LMTP_RETRY; vstream_fclose(stream); return (0); } vstream_ungetc(stream, ch); /* * Skip this host if it sends a 4xx or 5xx greeting. */ 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)); } /* lmtp_connect_host - direct connection to host */ 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; /* * 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 = 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); } /* lmtp_parse_destination - parse destination */ 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"; /* XXX configurable? */ unsigned port; const char *err; if (msg_verbose) msg_info("%s: %s %s", myname, destination, def_service); /* * Parse the host/port information. We're working with a copy of the * destination argument so the parsing can be destructive. */ if ((err = host_port(buf, hostp, (char *) 0, &service, def_service)) != 0) msg_fatal("%s in LMTP server description: %s", err, destination); /* * Convert service to port number, network byte order. Since most folks * aren't going to have lmtp defined as a service, use a default value * instead of just blowing up. */ if (alldig(service)) { if ((port = atoi(service)) >= 65536) msg_fatal("bad numeric port in destination: %s", destination); *portp = htons(port); } else if ((sp = getservbyname(service, protocol)) != 0) *portp = sp->s_port; else *portp = htons(var_lmtp_tcp_port); return (buf); } /* lmtp_connect - establish LMTP connection */ 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"; /* XXX configurable? */ /* * Connect to the LMTP server. * * XXX Ad-hoc transport parsing and connection management. Some or all * should be moved away to a reusable library routine so that every * program benefits from it. * * XXX Should transform destination into canonical form (unix:/path or * inet:host:port before entering it into the connection cache. See also * the connection cache lookup code in lmtp.c. */ 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); }