1 // Copyright 2009 The Archiveopteryx Developers <info@aox.org>
2
3 #include "recipient.h"
4
5 #include "date.h"
6 #include "address.h"
7 #include "mailbox.h"
8 #include "estringlist.h"
9 #include "configuration.h"
10
11
12 class RecipientData
13 : public Garbage
14 {
15 public:
RecipientData()16 RecipientData()
17 : originalRecipient( 0 ),
18 finalRecipient( 0 ),
19 action( Recipient::Unknown ),
20 lastAttemptDate( 0 ),
21 mailbox( 0 )
22 {}
23
24 Address * originalRecipient;
25 Address * finalRecipient;
26 Recipient::Action action;
27 EString status;
28 EString remoteMta;
29 EString diagnosticCode;
30 Date * lastAttemptDate;
31 EString finalLogId;
32 Mailbox * mailbox;
33 };
34
35
36 /*! \class Recipient recipient.h
37
38 The Recipient class holds information about a particular
39 recipient, collected during a delivery attempt and optionally used
40 for sending DSNs.
41 */
42
43
44 /*! Constructs a Recipient containing no data. The object must
45 be completed using e.g. setFinalRecipient().
46 */
47
Recipient()48 Recipient::Recipient()
49 : d( new RecipientData )
50 {
51 // what a pity that zero-line functions may have more than zero bugs
52 }
53
54
55 /*! Constructs a Recipient object whose mailbox() is set to \a m. This
56 function is provided for the convenience of code that creates an
57 Injector.
58 */
59
Recipient(Mailbox * m)60 Recipient::Recipient( Mailbox * m )
61 : d( new RecipientData )
62 {
63 // Fortunately, one-line functions are guaranteed bug-free.
64 d->mailbox = m;
65 }
66
67
68 /*! Constructs a Recipient object whose finalRecipient() is set to \a a
69 and whose mailbox() is set to \a m. This function is provided only
70 as a convenience.
71 */
72
Recipient(Address * a,Mailbox * m)73 Recipient::Recipient( Address * a, Mailbox * m )
74 : d( new RecipientData )
75 {
76 d->finalRecipient = a;
77 d->mailbox = m;
78 }
79
80
81 /*! Records that the message was originally sent to \a a. */
82
setOriginalRecipient(class Address * a)83 void Recipient::setOriginalRecipient( class Address * a )
84 {
85 d->originalRecipient = a;
86 }
87
88
89 /*! Returns a pointer to the original recipient's address, or a null
90 pointer if none is recorded.
91 */
92
originalRecipient() const93 class Address * Recipient::originalRecipient() const
94 {
95 return d->originalRecipient;
96 }
97
98
99 /*! Records that the message was finally sent to \a a.
100
101 Calling both setFinalRecipient() and setOriginalRecipient() with
102 the same address is discouraged.
103 */
104
setFinalRecipient(class Address * a)105 void Recipient::setFinalRecipient( class Address * a )
106 {
107 d->finalRecipient = a;
108 }
109
110
111 /*! Returns a pointer to the final recipient's address, or a the
112 originalRecipient() if none is recorded. If neither
113 setFinalRecipient() nor setOriginalRecipient() has been called,
114 finalRecipient() returns null.
115 */
116
finalRecipient() const117 class Address * Recipient::finalRecipient() const
118 {
119 if ( d->finalRecipient )
120 return d->finalRecipient;
121 return d->originalRecipient;
122 }
123
124
125 /*! Records that \a a is the action taken wrt. this recipient, and the
126 resulting status \a s . The initial action() is Unknown and the
127 initial status() an empty string.
128
129 \a s must be a string containing three numbers separated by dots,
130 e.g. "1.2.3" or "1000.2000.3000". The meaning of the numbers is as
131 defined in RFC 3463.
132 */
133
setAction(Action a,const EString & s)134 void Recipient::setAction( Action a, const EString & s )
135 {
136 d->action = a;
137 d->status = s;
138 }
139
140
141 /*! Returns the action recorded by setAction(). */
142
action() const143 Recipient::Action Recipient::action() const
144 {
145 return d->action;
146 }
147
148
149 /*! Returns the status recorded by setAction(). */
150
status() const151 EString Recipient::status() const
152 {
153 return d->status;
154 }
155
156
157 /*! Records that \a mta is the MTA to which we attempted to deliver
158 this message the last time. The initial value is empty, which
159 means that we didn't try to deliver the message to any remote MTA.
160 */
161
setRemoteMTA(const EString & mta)162 void Recipient::setRemoteMTA( const EString & mta )
163 {
164 d->remoteMta = mta;
165 }
166
167
168 /*! Returns the MTA recorded by setRemoteMTA(). */
169
remoteMTA() const170 EString Recipient::remoteMTA() const
171 {
172 return d->remoteMta;
173 }
174
175
176 /*! Records that \a code is the diagnostic code resulting from the
177 last delivery attempt. This must be an SMTP code (ie. the RFC 3464
178 diagnostic-type is always smtp), and if empty, it means that there
179 is no such code. The initial value is empty.
180 */
181
setDiagnosticCode(const EString & code)182 void Recipient::setDiagnosticCode( const EString & code )
183 {
184 d->diagnosticCode = code;
185 }
186
187
188 /*! Records the diagnostic code recorded by setDiagnosticCode(). */
189
diagnosticCode() const190 EString Recipient::diagnosticCode() const
191 {
192 return d->diagnosticCode;
193 }
194
195
196 /*! Records that the last delivery attempt for this recipient happened
197 at \a date. The initial value, null, means that no deliveries have
198 been attempted.
199 */
200
setLastAttempt(class Date * date)201 void Recipient::setLastAttempt( class Date * date )
202 {
203 d->lastAttemptDate = date;
204 }
205
206
207 /*! Returns the last attempt date for this recipient, or a null
208 pointer if no deliveries have been attempted.
209 */
210
lastAttempt() const211 Date * Recipient::lastAttempt() const
212 {
213 return d->lastAttemptDate;
214 }
215
216
217 /*! Records that during the last delivery attempt, the remote server
218 issued \a id as its final log ID. If \a id is empty, no ID was
219 reported and none will be reported by Recipient.
220 */
221
setFinalLogId(const EString & id)222 void Recipient::setFinalLogId( const EString & id )
223 {
224 d->finalLogId = id;
225 }
226
227
228 /*! Returns whatever was set by setFinalLogId(), or an empty string if
229 setFinalLogId() has not been called.
230
231 */
232
finalLogId() const233 EString Recipient::finalLogId() const
234 {
235 return d->finalLogId;
236 }
237
238
239 /*! Returns a pararaph (as single line) describing the fate of this
240 Recipient.
241 */
242
plainTextParagraph() const243 EString Recipient::plainTextParagraph() const
244 {
245 if ( !valid() )
246 return "";
247
248 EString s;
249 EString a;
250
251 if ( finalRecipient() && originalRecipient() &&
252 finalRecipient()->toString(false) !=
253 originalRecipient()->toString(false) ) {
254 a.append( finalRecipient()->lpdomain() );
255 a.append( " (forwarded from " );
256 a.append( originalRecipient()->lpdomain() );
257 a.append( ")" );
258 }
259 else if ( finalRecipient() ) {
260 a.append( finalRecipient()->lpdomain() );
261 }
262 else if ( originalRecipient() ) {
263 a.append( originalRecipient()->lpdomain() );
264 }
265 else {
266 return "";
267 }
268
269 switch( action() ) {
270 case Unknown:
271 // we do not report on this recipient.
272 return "";
273 break;
274 case Failed:
275 s = "Your message could not be delivered to ";
276 s.append( a );
277 s.append( "." );
278 if ( !status().isEmpty() &&
279 !remoteMTA().isEmpty() ) {
280 s.append( " " );
281 if ( lastAttempt() ) {
282 s.append( "At " );
283 s.append( lastAttempt()->isoDate() );
284 s.append( ", " );
285 s.append( lastAttempt()->isoTime() );
286 s.append( ", the " );
287 }
288 else {
289 s.append( "The " );
290 }
291 s.append( "next-hop server (" );
292 s.append( remoteMTA() );
293 s.append( ") returned the following error code: " );
294 s.append( status() );
295 s.append( ". This is a fatal error. Sorry." );
296 }
297 break;
298 case Delayed:
299 s = "Delivery to ";
300 s.append( a );
301 s.append( " is unexpectedly delayed. Delivery attempts continue." );
302 // here, we want to say "the next attempt is in 25 minutes" or
303 // words to that effect. Maybe we need setNextAttempt()?
304 break;
305 case Delivered:
306 s = "Your message was delivered to ";
307 s.append( a );
308 s.append( "." );
309 break;
310 case Relayed:
311 s = "While delivering to ";
312 s.append( a );
313 s.append( ", your message was forwarded to " );
314 if ( !remoteMTA().isEmpty() ) {
315 s.append( remoteMTA() );
316 s.append( "," );
317 }
318 else {
319 s.append( "a host" );
320 }
321 s.append( " which cannot send reports such as this one."
322 " Unless you receive an error report, you can assume"
323 " that your message arrived safely." );
324 break;
325 case Expanded:
326 s = "Your message was delivered to ";
327 s.append( a );
328 s.append( ", and resent to several other addresses from there." );
329 break;
330 }
331
332 return s;
333 }
334
335
336 /*! Returns a paragraph containin the DSN for this Recipient. The
337 returned string contains a series of LF-separated lines, but no
338 trailing LF.
339 */
340
dsnParagraph() const341 EString Recipient::dsnParagraph() const
342 {
343 if ( !valid() )
344 return "";
345
346 EStringList l;
347 EString s;
348
349 // [ original-recipient-field CRLF ]
350 if ( originalRecipient() && originalRecipient() != finalRecipient() )
351 l.append( "Original-Recipient: rfc822;" +
352 originalRecipient()->lpdomain() );
353
354 // final-recipient-field CRLF
355 if ( finalRecipient() )
356 l.append( "Final-Recipient: rfc822;" +
357 finalRecipient()->lpdomain() );
358
359 // action-field CRLF
360 switch ( action() ) {
361 case Unknown:
362 l.append( "Action: unknown" );
363 break;
364 case Failed:
365 l.append( "Action: failed" );
366 break;
367 case Delayed:
368 l.append( "Action: delayed" );
369 break;
370 case Delivered:
371 l.append( "Action: delivered" );
372 break;
373 case Relayed:
374 l.append( "Action: relayed" );
375 break;
376 case Expanded:
377 l.append( "Action: expanded" );
378 break;
379 }
380
381 // status-field CRLF
382 if ( !status().isEmpty() )
383 l.append( "Status: " + status() );
384 // [ remote-mta-field CRLF ]
385 if ( !remoteMTA().isEmpty() )
386 l.append( "Remote-Mta: dns;" + remoteMTA() );
387
388
389 // [ diagnostic-code-field CRLF ]
390 if ( !diagnosticCode().isEmpty() )
391 l.append( "Diagnostic-Code: smtp;" + diagnosticCode() );
392
393 // [ last-attempt-date-field CRLF ]
394 if ( lastAttempt() )
395 l.append( "Last-Attempt-Date: " + lastAttempt()->rfc822() );
396
397 // [ final-log-id-field CRLF ]
398 if ( !finalLogId().isEmpty() )
399 l.append( "Final-Log-Id: smtp;" + finalLogId() );
400
401 // we don't set will-retry-until. it only applies to delay dsns,
402 // which we don't send.
403
404 return l.join( "\n" );
405 }
406
407
408 /*! Sets this recipient's mailbox to \a m. */
409
setMailbox(Mailbox * m)410 void Recipient::setMailbox( Mailbox * m )
411 {
412 d->mailbox = m;
413 }
414
415
416 /*! Returns a pointer to this Recipient's mailbox, or 0 if one hasn't
417 been set with setMailbox().
418 */
419
mailbox() const420 Mailbox * Recipient::mailbox() const
421 {
422 return d->mailbox;
423 }
424
425
426 /*! Returns true if this Recipient has enough data to return a
427 dsnParagraph() and a plainTextParagraph(), and false if not.
428 */
429
valid() const430 bool Recipient::valid() const
431 {
432 if ( action() == Unknown )
433 return false;
434
435 if ( status().isEmpty() )
436 return false;
437
438 if ( !finalRecipient() )
439 return false;
440
441 return true;
442 }
443
444
445 /*! This function is defined so that SMTP and the Injector may create a
446 SortedList of Recipients. It compares this Recipient to \a b based
447 on the mailbox() id. If either Recipient has no mailbox() defined,
448 the results are meaningless.
449 */
450
operator <=(const Recipient & b)451 bool Recipient::operator <=( const Recipient &b )
452 {
453 if ( mailbox() && b.mailbox() )
454 return mailbox()->id() <= b.mailbox()->id();
455 return false;
456 }
457