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