summaryrefslogtreecommitdiffstats
path: root/src/global/deliver_request.c
blob: 7bc5553d08a8f000abe2c31f6a2d3e25aba3eb37 (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
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
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
/*++
/* NAME
/*	deliver_request 3
/* SUMMARY
/*	mail delivery request protocol, server side
/* SYNOPSIS
/*	#include <deliver_request.h>
/*
/*	typedef struct DELIVER_REQUEST {
/* .in +5
/*		VSTREAM	*fp;
/*		int	flags;
/*		char	*queue_name;
/*		char	*queue_id;
/*		long	data_offset;
/*		long	data_size;
/*		char	*nexthop;
/*		char	*encoding;
/*		char	*sender;
/*		MSG_STATS msg_stats;
/*		RECIPIENT_LIST rcpt_list;
/*		DSN	*hop_status;
/*		char	*client_name;
/*		char	*client_addr;
/*		char	*client_port;
/*		char	*client_proto;
/*		char	*client_helo;
/*		char	*sasl_method;
/*		char	*sasl_username;
/*		char	*sasl_sender;
/*		char	*log_ident;
/*		char	*rewrite_context;
/*		char	*dsn_envid;
/*		int	dsn_ret;
/* .in -5
/*	} DELIVER_REQUEST;
/*
/*	DELIVER_REQUEST *deliver_request_read(stream)
/*	VSTREAM *stream;
/*
/*	void	deliver_request_done(stream, request, status)
/*	VSTREAM *stream;
/*	DELIVER_REQUEST *request;
/*	int	status;
/* DESCRIPTION
/*	This module implements the delivery agent side of the `queue manager
/*	to delivery agent' protocol. In this game, the queue manager is
/*	the client, while the delivery agent is the server.
/*
/*	deliver_request_read() reads a client message delivery request,
/*	opens the queue file, and acquires a shared lock.
/*	A null result means that the client sent bad information or that
/*	it went away unexpectedly.
/*
/*	The \fBflags\fR structure member is the bit-wise OR of zero or more
/*	of the following:
/* .IP \fBDEL_REQ_FLAG_SUCCESS\fR
/*	Delete successful recipients from the queue file.
/*
/*	Note: currently, this also controls whether bounced recipients
/*	are deleted.
/*
/*	Note: the deliver_completed() function ignores this request
/*	when the recipient queue file offset is -1.
/* .IP \fBDEL_REQ_FLAG_BOUNCE\fR
/*	Delete bounced recipients from the queue file. Currently,
/*	this flag is non-functional.
/* .PP
/*	The \fBDEL_REQ_FLAG_DEFLT\fR constant provides a convenient shorthand
/*	for the most common case: delete successful and bounced recipients.
/*
/*	The \fIhop_status\fR member must be updated by the caller
/*	when all delivery to the destination in \fInexthop\fR should
/*	be deferred. This member is passed to dsn_free().
/*
/*	deliver_request_done() reports the delivery status back to the
/*	client, including the optional \fIhop_status\fR etc. information,
/*	closes the queue file,
/*	and destroys the DELIVER_REQUEST structure. The result is
/*	non-zero when the status could not be reported to the client.
/* DIAGNOSTICS
/*	Warnings: bad data sent by the client. Fatal errors: out of
/*	memory, queue file open errors.
/* SEE ALSO
/*	attr_scan(3) low-level intra-mail input routines
/* 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/stat.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

/* Utility library. */

#include <msg.h>
#include <vstream.h>
#include <vstring.h>
#include <mymalloc.h>
#include <iostuff.h>
#include <myflock.h>

/* Global library. */

#include "mail_queue.h"
#include "mail_proto.h"
#include "mail_open_ok.h"
#include "recipient_list.h"
#include "dsn.h"
#include "dsn_print.h"
#include "deliver_request.h"
#include "rcpt_buf.h"

/* deliver_request_initial - send initial status code */

static int deliver_request_initial(VSTREAM *stream)
{
    int     err;

    /*
     * The master processes runs a finite number of delivery agent processes
     * to handle service requests. Thus, a delivery agent process must send
     * something to inform the queue manager that it is ready to receive a
     * delivery request; otherwise the queue manager could block in write().
     */
    if (msg_verbose)
	msg_info("deliver_request_initial: send initial response");
    attr_print(stream, ATTR_FLAG_NONE,
	       SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_DELIVER),
	       ATTR_TYPE_END);
    if ((err = vstream_fflush(stream)) != 0)
	if (msg_verbose)
	    msg_warn("send initial response: %m");
    return (err);
}

/* deliver_request_final - send final delivery request status */

static int deliver_request_final(VSTREAM *stream, DELIVER_REQUEST *request,
				         int status)
{
    DSN    *hop_status;
    int     err;

    /* XXX This DSN structure initialization bypasses integrity checks. */
    static DSN dummy_dsn = {"", "", "", "", "", "", ""};

    /*
     * Send the status and the optional reason.
     */
    if ((hop_status = request->hop_status) == 0)
	hop_status = &dummy_dsn;
    if (msg_verbose)
	msg_info("deliver_request_final: send: \"%s\" %d",
		 hop_status->reason, status);
    attr_print(stream, ATTR_FLAG_NONE,
	       SEND_ATTR_FUNC(dsn_print, (const void *) hop_status),
	       SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
	       ATTR_TYPE_END);
    if ((err = vstream_fflush(stream)) != 0)
	if (msg_verbose)
	    msg_warn("send final status: %m");

    /*
     * With some UNIX systems, stream sockets lose data when you close them
     * immediately after writing to them. That is not how sockets are
     * supposed to behave! The workaround is to wait until the receiver
     * closes the connection. Calling VSTREAM_GETC() has the benefit of using
     * whatever timeout is specified in the ipc_timeout parameter.
     */
    (void) VSTREAM_GETC(stream);
    return (err);
}

/* deliver_request_get - receive message delivery request */

static int deliver_request_get(VSTREAM *stream, DELIVER_REQUEST *request)
{
    const char *myname = "deliver_request_get";
    const char *path;
    struct stat st;
    static VSTRING *queue_name;
    static VSTRING *queue_id;
    static VSTRING *nexthop;
    static VSTRING *encoding;
    static VSTRING *address;
    static VSTRING *client_name;
    static VSTRING *client_addr;
    static VSTRING *client_port;
    static VSTRING *client_proto;
    static VSTRING *client_helo;
    static VSTRING *sasl_method;
    static VSTRING *sasl_username;
    static VSTRING *sasl_sender;
    static VSTRING *log_ident;
    static VSTRING *rewrite_context;
    static VSTRING *dsn_envid;
    static RCPT_BUF *rcpt_buf;
    int     rcpt_count;
    int     smtputf8;
    int     dsn_ret;

    /*
     * Initialize. For some reason I wanted to allow for multiple instances
     * of a deliver_request structure, thus the hoopla with string
     * initialization and copying.
     */
    if (queue_name == 0) {
	queue_name = vstring_alloc(10);
	queue_id = vstring_alloc(10);
	nexthop = vstring_alloc(10);
	encoding = vstring_alloc(10);
	address = vstring_alloc(10);
	client_name = vstring_alloc(10);
	client_addr = vstring_alloc(10);
	client_port = vstring_alloc(10);
	client_proto = vstring_alloc(10);
	client_helo = vstring_alloc(10);
	sasl_method = vstring_alloc(10);
	sasl_username = vstring_alloc(10);
	sasl_sender = vstring_alloc(10);
	log_ident = vstring_alloc(10);
	rewrite_context = vstring_alloc(10);
	dsn_envid = vstring_alloc(10);
	rcpt_buf = rcpb_create();
    }

    /*
     * Extract the queue file name, data offset, and sender address. Abort
     * the conversation when they send bad information.
     */
    if (attr_scan(stream, ATTR_FLAG_STRICT,
		  RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request->flags),
		  RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name),
		  RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
		  RECV_ATTR_LONG(MAIL_ATTR_OFFSET, &request->data_offset),
		  RECV_ATTR_LONG(MAIL_ATTR_SIZE, &request->data_size),
		  RECV_ATTR_STR(MAIL_ATTR_NEXTHOP, nexthop),
		  RECV_ATTR_STR(MAIL_ATTR_ENCODING, encoding),
		  RECV_ATTR_INT(MAIL_ATTR_SMTPUTF8, &smtputf8),
		  RECV_ATTR_STR(MAIL_ATTR_SENDER, address),
		  RECV_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid),
		  RECV_ATTR_INT(MAIL_ATTR_DSN_RET, &dsn_ret),
	       RECV_ATTR_FUNC(msg_stats_scan, (void *) &request->msg_stats),
    /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */
		  RECV_ATTR_STR(MAIL_ATTR_LOG_CLIENT_NAME, client_name),
		  RECV_ATTR_STR(MAIL_ATTR_LOG_CLIENT_ADDR, client_addr),
		  RECV_ATTR_STR(MAIL_ATTR_LOG_CLIENT_PORT, client_port),
		  RECV_ATTR_STR(MAIL_ATTR_LOG_PROTO_NAME, client_proto),
		  RECV_ATTR_STR(MAIL_ATTR_LOG_HELO_NAME, client_helo),
    /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */
		  RECV_ATTR_STR(MAIL_ATTR_SASL_METHOD, sasl_method),
		  RECV_ATTR_STR(MAIL_ATTR_SASL_USERNAME, sasl_username),
		  RECV_ATTR_STR(MAIL_ATTR_SASL_SENDER, sasl_sender),
    /* XXX Ditto if we want to pass TLS certificate info. */
		  RECV_ATTR_STR(MAIL_ATTR_LOG_IDENT, log_ident),
		  RECV_ATTR_STR(MAIL_ATTR_RWR_CONTEXT, rewrite_context),
		  RECV_ATTR_INT(MAIL_ATTR_RCPT_COUNT, &rcpt_count),
		  ATTR_TYPE_END) != 23) {
	msg_warn("%s: error receiving common attributes", myname);
	return (-1);
    }
    if (mail_open_ok(vstring_str(queue_name),
		     vstring_str(queue_id), &st, &path) == 0)
	return (-1);

    /* Don't override hand-off time after deliver_pass() delegation. */
    if (request->msg_stats.agent_handoff.tv_sec == 0)
	GETTIMEOFDAY(&request->msg_stats.agent_handoff);

    request->queue_name = mystrdup(vstring_str(queue_name));
    request->queue_id = mystrdup(vstring_str(queue_id));
    request->nexthop = mystrdup(vstring_str(nexthop));
    request->encoding = mystrdup(vstring_str(encoding));
    /* Fix 20140708: dedicated smtputf8 attribute with its own flags. */
    request->smtputf8 = smtputf8;
    request->sender = mystrdup(vstring_str(address));
    request->client_name = mystrdup(vstring_str(client_name));
    request->client_addr = mystrdup(vstring_str(client_addr));
    request->client_port = mystrdup(vstring_str(client_port));
    request->client_proto = mystrdup(vstring_str(client_proto));
    request->client_helo = mystrdup(vstring_str(client_helo));
    request->sasl_method = mystrdup(vstring_str(sasl_method));
    request->sasl_username = mystrdup(vstring_str(sasl_username));
    request->sasl_sender = mystrdup(vstring_str(sasl_sender));
    request->log_ident = mystrdup(vstring_str(log_ident));
    request->rewrite_context = mystrdup(vstring_str(rewrite_context));
    request->dsn_envid = mystrdup(vstring_str(dsn_envid));
    request->dsn_ret = dsn_ret;

    /*
     * Extract the recipient offset and address list. Skip over any
     * attributes from the sender that we do not understand.
     */
    while (rcpt_count-- > 0) {
	if (attr_scan(stream, ATTR_FLAG_STRICT,
		      RECV_ATTR_FUNC(rcpb_scan, (void *) rcpt_buf),
		      ATTR_TYPE_END) != 1) {
	    msg_warn("%s: error receiving recipient attributes", myname);
	    return (-1);
	}
	recipient_list_add(&request->rcpt_list, rcpt_buf->offset,
			   vstring_str(rcpt_buf->dsn_orcpt),
			   rcpt_buf->dsn_notify,
			   vstring_str(rcpt_buf->orig_addr),
			   vstring_str(rcpt_buf->address));
    }
    if (request->rcpt_list.len <= 0) {
	msg_warn("%s: no recipients in delivery request for destination %s",
		 request->queue_id, request->nexthop);
	return (-1);
    }

    /*
     * Open the queue file and set a shared lock, in order to prevent
     * duplicate deliveries when the queue is flushed immediately after queue
     * manager restart.
     * 
     * The queue manager locks the file exclusively when it enters the active
     * queue, and releases the lock before starting deliveries from that
     * file. The queue manager does not lock the file again when reading more
     * recipients into memory. When the queue manager is restarted, the new
     * process moves files from the active queue to the incoming queue to
     * cool off for a while. Delivery agents should therefore never try to
     * open a file that is locked by a queue manager process.
     * 
     * Opening the queue file can fail for a variety of reasons, such as the
     * system running out of resources. Instead of throwing away mail, we're
     * raising a fatal error which forces the mail system to back off, and
     * retry later.
     */
#define DELIVER_LOCK_MODE (MYFLOCK_OP_SHARED | MYFLOCK_OP_NOWAIT)

    request->fp =
	mail_queue_open(request->queue_name, request->queue_id, O_RDWR, 0);
    if (request->fp == 0) {
	if (errno != ENOENT)
	    msg_fatal("open %s %s: %m", request->queue_name, request->queue_id);
	msg_warn("open %s %s: %m", request->queue_name, request->queue_id);
	return (-1);
    }
    if (msg_verbose)
	msg_info("%s: file %s", myname, VSTREAM_PATH(request->fp));
    if (myflock(vstream_fileno(request->fp), INTERNAL_LOCK, DELIVER_LOCK_MODE) < 0)
	msg_fatal("shared lock %s: %m", VSTREAM_PATH(request->fp));
    close_on_exec(vstream_fileno(request->fp), CLOSE_ON_EXEC);

    return (0);
}

/* deliver_request_alloc - allocate delivery request structure */

static DELIVER_REQUEST *deliver_request_alloc(void)
{
    DELIVER_REQUEST *request;

    request = (DELIVER_REQUEST *) mymalloc(sizeof(*request));
    request->fp = 0;
    request->queue_name = 0;
    request->queue_id = 0;
    request->nexthop = 0;
    request->encoding = 0;
    request->sender = 0;
    request->data_offset = 0;
    request->data_size = 0;
    recipient_list_init(&request->rcpt_list, RCPT_LIST_INIT_STATUS);
    request->hop_status = 0;
    request->client_name = 0;
    request->client_addr = 0;
    request->client_port = 0;
    request->client_proto = 0;
    request->client_helo = 0;
    request->sasl_method = 0;
    request->sasl_username = 0;
    request->sasl_sender = 0;
    request->log_ident = 0;
    request->rewrite_context = 0;
    request->dsn_envid = 0;
    return (request);
}

/* deliver_request_free - clean up delivery request structure */

static void deliver_request_free(DELIVER_REQUEST *request)
{
    if (request->fp)
	vstream_fclose(request->fp);
    if (request->queue_name)
	myfree(request->queue_name);
    if (request->queue_id)
	myfree(request->queue_id);
    if (request->nexthop)
	myfree(request->nexthop);
    if (request->encoding)
	myfree(request->encoding);
    if (request->sender)
	myfree(request->sender);
    recipient_list_free(&request->rcpt_list);
    if (request->hop_status)
	dsn_free(request->hop_status);
    if (request->client_name)
	myfree(request->client_name);
    if (request->client_addr)
	myfree(request->client_addr);
    if (request->client_port)
	myfree(request->client_port);
    if (request->client_proto)
	myfree(request->client_proto);
    if (request->client_helo)
	myfree(request->client_helo);
    if (request->sasl_method)
	myfree(request->sasl_method);
    if (request->sasl_username)
	myfree(request->sasl_username);
    if (request->sasl_sender)
	myfree(request->sasl_sender);
    if (request->log_ident)
	myfree(request->log_ident);
    if (request->rewrite_context)
	myfree(request->rewrite_context);
    if (request->dsn_envid)
	myfree(request->dsn_envid);
    myfree((void *) request);
}

/* deliver_request_read - create and read delivery request */

DELIVER_REQUEST *deliver_request_read(VSTREAM *stream)
{
    DELIVER_REQUEST *request;

    /*
     * Tell the queue manager that we are ready for this request.
     */
    if (deliver_request_initial(stream) != 0)
	return (0);

    /*
     * Be prepared for the queue manager to change its mind after contacting
     * us. This can happen when a transport or host goes bad.
     */
    (void) read_wait(vstream_fileno(stream), -1);
    if (peekfd(vstream_fileno(stream)) <= 0)
	return (0);

    /*
     * Allocate and read the queue manager's delivery request.
     */
#define XXX_DEFER_STATUS	-1

    request = deliver_request_alloc();
    if (deliver_request_get(stream, request) < 0) {
	deliver_request_done(stream, request, XXX_DEFER_STATUS);
	request = 0;
    }
    return (request);
}

/* deliver_request_done - finish delivery request */

int     deliver_request_done(VSTREAM *stream, DELIVER_REQUEST *request, int status)
{
    int     err;

    err = deliver_request_final(stream, request, status);
    deliver_request_free(request);
    return (err);
}