summaryrefslogtreecommitdiffstats
path: root/src/qmqpd/qmqpd_peer.c
blob: 41cd009532d716416991a06f4e1461f1722a0d9d (plain)
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
/*++
/* NAME
/*	qmqpd_peer 3
/* SUMMARY
/*	look up peer name/address information
/* SYNOPSIS
/*	#include "qmqpd.h"
/*
/*	void	qmqpd_peer_init(state)
/*	QMQPD_STATE *state;
/*
/*	void	qmqpd_peer_reset(state)
/*	QMQPD_STATE *state;
/* DESCRIPTION
/*	The qmqpd_peer_init() routine attempts to produce a printable
/*	version of the peer name and address of the specified socket.
/*	Where information is unavailable, the name and/or address
/*	are set to "unknown".
/*
/*	qmqpd_peer_init() updates the following fields:
/* .IP name
/*	The client hostname. An unknown name is represented by the
/*	string "unknown".
/* .IP addr
/*	Printable representation of the client address.
/* .IP namaddr
/*	String of the form: "name[addr]:port".
/* .PP
/*	qmqpd_peer_reset() releases memory allocated by qmqpd_peer_init().
/* 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
/*
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, 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 <string.h>

/* Utility library. */

#include <msg.h>
#include <mymalloc.h>
#include <stringops.h>
#include <myaddrinfo.h>
#include <sock_addr.h>
#include <inet_proto.h>
#include <split_at.h>

/* Global library. */

#include <mail_proto.h>
#include <valid_mailhost_addr.h>
#include <mail_params.h>

/* Application-specific. */

#include "qmqpd.h"

/* qmqpd_peer_init - initialize peer information */

void    qmqpd_peer_init(QMQPD_STATE *state)
{
    const char *myname = "qmqpd_peer_init";
    struct sockaddr_storage ss;
    struct sockaddr *sa;
    SOCKADDR_SIZE sa_length;
    const INET_PROTO_INFO *proto_info = inet_proto_info();

    sa = (struct sockaddr *) &ss;
    sa_length = sizeof(ss);

    /*
     * Look up the peer address information.
     */
    if (getpeername(vstream_fileno(state->client), sa, &sa_length) >= 0) {
	errno = 0;
    }

    /*
     * If peer went away, give up.
     */
    if (errno != 0 && errno != ENOTSOCK) {
	state->name = mystrdup(CLIENT_NAME_UNKNOWN);
	state->addr = mystrdup(CLIENT_ADDR_UNKNOWN);
	state->rfc_addr = mystrdup(CLIENT_ADDR_UNKNOWN);
	state->addr_family = AF_UNSPEC;
	state->port = mystrdup(CLIENT_PORT_UNKNOWN);
    }

    /*
     * Convert the client address to printable address and hostname.
     * 
     * XXX If we're given an IPv6 (or IPv4) connection from, e.g., inetd, while
     * Postfix IPv6 (or IPv4) support is turned off, don't (skip to the final
     * else clause, pretend the origin is localhost[127.0.0.1], and become an
     * open relay).
     */
    else if (errno == 0
	     && (sa->sa_family == AF_INET
#ifdef AF_INET6
		 || sa->sa_family == AF_INET6
#endif
		 )) {
	MAI_HOSTNAME_STR client_name;
	MAI_HOSTADDR_STR client_addr;
	MAI_SERVPORT_STR client_port;
	int     aierr;
	char   *colonp;

	/*
	 * Sanity check: we can't use sockets that we're not configured for.
	 */
	if (strchr((char *) proto_info->sa_family_list, sa->sa_family) == 0)
	    msg_fatal("cannot handle socket type %s with \"%s = %s\"",
#ifdef AF_INET6
		      sa->sa_family == AF_INET6 ? "AF_INET6" :
#endif
		      sa->sa_family == AF_INET ? "AF_INET" :
		      "other", VAR_INET_PROTOCOLS, var_inet_protocols);

	/*
	 * Sorry, but there are some things that we just cannot do while
	 * connected to the network.
	 */
	if (geteuid() != var_owner_uid || getuid() != var_owner_uid) {
	    msg_error("incorrect QMQP server privileges: uid=%lu euid=%lu",
		      (unsigned long) getuid(), (unsigned long) geteuid());
	    msg_fatal("the Postfix QMQP server must run with $%s privileges",
		      VAR_MAIL_OWNER);
	}

	/*
	 * Convert the client address to printable form.
	 */
	if ((aierr = sockaddr_to_hostaddr(sa, sa_length, &client_addr,
					  &client_port, 0)) != 0)
	    msg_fatal("%s: cannot convert client address/port to string: %s",
		      myname, MAI_STRERROR(aierr));
	state->port = mystrdup(client_port.buf);

	/*
	 * XXX Require that the infrastructure strips off the IPv6 datalink
	 * suffix to avoid false alarms with strict address syntax checks.
	 */
#ifdef HAS_IPV6
	if (strchr(client_addr.buf, '%') != 0)
	    msg_panic("%s: address %s has datalink suffix",
		      myname, client_addr.buf);
#endif

	/*
	 * We convert IPv4-in-IPv6 address to 'true' IPv4 address early on,
	 * but only if IPv4 support is enabled (why would anyone want to turn
	 * it off)? With IPv4 support enabled we have no need for the IPv6
	 * form in logging, hostname verification and access checks.
	 */
#ifdef HAS_IPV6
	if (sa->sa_family == AF_INET6) {
	    if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0
		&& IN6_IS_ADDR_V4MAPPED(&SOCK_ADDR_IN6_ADDR(sa))
		&& (colonp = strrchr(client_addr.buf, ':')) != 0) {
		struct addrinfo *res0;

		if (msg_verbose > 1)
		    msg_info("%s: rewriting V4-mapped address \"%s\" to \"%s\"",
			     myname, client_addr.buf, colonp + 1);

		state->addr = mystrdup(colonp + 1);
		state->rfc_addr = mystrdup(colonp + 1);
		state->addr_family = AF_INET;
		aierr = hostaddr_to_sockaddr(state->addr, (char *) 0, 0, &res0);
		if (aierr)
		    msg_fatal("%s: cannot convert %s from string to binary: %s",
			      myname, state->addr, MAI_STRERROR(aierr));
		sa_length = res0->ai_addrlen;
		if (sa_length > sizeof(ss))
		    sa_length = sizeof(ss);
		memcpy((void *) sa, res0->ai_addr, sa_length);
		freeaddrinfo(res0);
	    }

	    /*
	     * Following RFC 2821 section 4.1.3, an IPv6 address literal gets
	     * a prefix of 'IPv6:'. We do this consistently for all IPv6
	     * addresses that appear in headers or envelopes. The fact
	     * that valid_mailhost_addr() enforces the form helps of course.
	     * We use the form without IPV6: prefix when doing access
	     * control, or when accessing the connection cache.
	     */
	    else {
		state->addr = mystrdup(client_addr.buf);
		state->rfc_addr =
		    concatenate(IPV6_COL, client_addr.buf, (char *) 0);
		state->addr_family = sa->sa_family;
	    }
	}

	/*
	 * An IPv4 address is in dotted quad decimal form.
	 */
	else
#endif
	{
	    state->addr = mystrdup(client_addr.buf);
	    state->rfc_addr = mystrdup(client_addr.buf);
	    state->addr_family = sa->sa_family;
	}

	/*
	 * Look up and sanity check the client hostname.
	 * 
	 * It is unsafe to allow numeric hostnames, especially because there
	 * exists pressure to turn off the name->addr double check. In that
	 * case an attacker could trivally bypass access restrictions.
	 * 
	 * sockaddr_to_hostname() already rejects malformed or numeric names.
	 */
#define REJECT_PEER_NAME(state) { \
	myfree(state->name); \
	state->name = mystrdup(CLIENT_NAME_UNKNOWN); \
    }

	if ((aierr = sockaddr_to_hostname(sa, sa_length, &client_name,
					  (MAI_SERVNAME_STR *) 0, 0)) != 0) {
	    state->name = mystrdup(CLIENT_NAME_UNKNOWN);
	} else {
	    struct addrinfo *res0;
	    struct addrinfo *res;

	    state->name = mystrdup(client_name.buf);

	    /*
	     * Reject the hostname if it does not list the peer address.
	     */
	    aierr = hostname_to_sockaddr_pf(state->name, state->addr_family,
					    (char *) 0, 0, &res0);
	    if (aierr) {
		msg_warn("hostname %s does not resolve to address %s: %s",
			 state->name, state->addr, MAI_STRERROR(aierr));
		REJECT_PEER_NAME(state);
	    } else {
		for (res = res0; /* void */ ; res = res->ai_next) {
		    if (res == 0) {
			msg_warn("hostname %s does not resolve to address %s",
				 state->addr, state->name);
			REJECT_PEER_NAME(state);
			break;
		    }
		    if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
			msg_info("skipping address family %d for host %s",
				 res->ai_family, state->name);
			continue;
		    }
		    if (sock_addr_cmp_addr(res->ai_addr, sa) == 0)
			break;			/* keep peer name */
		}
		freeaddrinfo(res0);
	    }
	}
    }

    /*
     * If it's not Internet, assume the client is local, and avoid using the
     * naming service because that can hang when the machine is disconnected.
     */
    else {
	state->name = mystrdup("localhost");
	state->addr = mystrdup("127.0.0.1");	/* XXX bogus. */
	state->rfc_addr = mystrdup("127.0.0.1");/* XXX bogus. */
	state->addr_family = AF_UNSPEC;
	state->port = mystrdup("0");		/* XXX bogus. */
    }

    /*
     * Do the name[addr]:port formatting for pretty reports.
     */
    state->namaddr =
	concatenate(state->name, "[", state->addr, "]",
		    var_qmqpd_client_port_log ? ":" : (char *) 0,
		    state->port, (char *) 0);
}

/* qmqpd_peer_reset - destroy peer information */

void    qmqpd_peer_reset(QMQPD_STATE *state)
{
    myfree(state->name);
    myfree(state->addr);
    myfree(state->namaddr);
    myfree(state->rfc_addr);
    myfree(state->port);
}