File: smtp_connect.c

package info (click to toggle)
postfix 0.0.19991231pl11-2
  • links: PTS
  • area: main
  • in suites: potato
  • size: 5,044 kB
  • ctags: 4,401
  • sloc: ansic: 33,767; makefile: 5,099; sh: 1,790; awk: 19
file content (370 lines) | stat: -rw-r--r-- 10,682 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
/*++
/* 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);
    }
    if (session == 0
	&& smtp_errno == SMTP_FAIL
	&& strcmp(host, var_relayhost) == 0) {
	msg_warn("relayhost configuration problem: %s", var_relayhost);
	smtp_errno = SMTP_RETRY;
    }
    myfree(dest_buf);
    return (session);
}