77cd53e1472972ac1e2a4a81861dd713a749b389
[citadel.git] / citadel / server / journaling.c
1 // Message journaling functions.
2 //
3 // Copyright (c) 1987-2023 by the citadel.org team
4 //
5 // This program is open source software.  Use, duplication, or disclosure
6 // is subject to the terms of the GNU General Public License, version 3.
7
8 #include <stdio.h>
9 #include <libcitadel.h>
10 #include "ctdl_module.h"
11 #include "citserver.h"
12 #include "config.h"
13 #include "user_ops.h"
14 #include "serv_vcard.h"                 // Needed for vcard_getuser and extract_inet_email_addrs
15 #include "internet_addressing.h"
16 #include "journaling.h"
17
18 struct jnlq *jnlq = NULL;       // journal queue
19
20 /*
21  * Hand off a copy of a message to be journalized.
22  */
23 void JournalBackgroundSubmit(struct CtdlMessage *msg,
24                         StrBuf *saved_rfc822_version,
25                         struct recptypes *recps) {
26
27         struct jnlq *jptr = NULL;
28
29         /* Avoid double journaling! */
30         if (!CM_IsEmpty(msg, eJournal)) {
31                 FreeStrBuf(&saved_rfc822_version);
32                 return;
33         }
34
35         jptr = (struct jnlq *)malloc(sizeof(struct jnlq));
36         if (jptr == NULL) {
37                 FreeStrBuf(&saved_rfc822_version);
38                 return;
39         }
40         memset(jptr, 0, sizeof(struct jnlq));
41         if (recps != NULL) memcpy(&jptr->recps, recps, sizeof(struct recptypes));
42         if (!CM_IsEmpty(msg, eAuthor)) jptr->from = strdup(msg->cm_fields[eAuthor]);
43         if (!CM_IsEmpty(msg, erFc822Addr)) jptr->rfca = strdup(msg->cm_fields[erFc822Addr]);
44         if (!CM_IsEmpty(msg, eMsgSubject)) jptr->subj = strdup(msg->cm_fields[eMsgSubject]);
45         if (!CM_IsEmpty(msg, emessageId)) jptr->msgn = strdup(msg->cm_fields[emessageId]);
46         jptr->rfc822 = SmashStrBuf(&saved_rfc822_version);
47
48         /* Add to the queue */
49         begin_critical_section(S_JOURNAL_QUEUE);
50         jptr->next = jnlq;
51         jnlq = jptr;
52         end_critical_section(S_JOURNAL_QUEUE);
53 }
54
55
56 /*
57  * Convert a local user name to an internet email address for the journal
58  * FIXME - grab the user's Internet email address from the user record, not from vCard !!!!
59  */
60 void local_to_inetemail(char *inetemail, char *localuser, size_t inetemail_len) {
61         struct ctdluser us;
62         struct vCard *v;
63
64         strcpy(inetemail, "");
65         if (CtdlGetUser(&us, localuser) != 0) {
66                 return;
67         }
68
69         v = vcard_get_user(&us);
70         if (v == NULL) {
71                 return;
72         }
73
74         extract_inet_email_addrs(inetemail, inetemail_len, NULL, 0, v, 1);
75         vcard_free(v);
76 }
77
78
79 /*
80  * Called by JournalRunQueue() to send an individual message.
81  */
82 void JournalRunQueueMsg(struct jnlq *jmsg) {
83
84         struct CtdlMessage *journal_msg = NULL;
85         struct recptypes *journal_recps = NULL;
86         StrBuf *message_text = NULL;
87         char mime_boundary[256];
88         long mblen;
89         long rfc822len;
90         char recipient[256];
91         char inetemail[256];
92         static int seq = 0;
93         int i;
94
95         if (jmsg == NULL)
96                 return;
97         journal_recps = validate_recipients(CtdlGetConfigStr("c_journal_dest"), NULL, 0);
98         if (journal_recps != NULL) {
99
100                 if (  (journal_recps->num_local > 0)
101                    || (journal_recps->num_internet > 0)
102                    || (journal_recps->num_room > 0)
103                 ) {
104
105                         /*
106                          * Construct journal message.
107                          * Note that we are transferring ownership of some of the memory here.
108                          */
109                         journal_msg = malloc(sizeof(struct CtdlMessage));
110                         memset(journal_msg, 0, sizeof(struct CtdlMessage));
111                         journal_msg->cm_magic = CTDLMESSAGE_MAGIC;
112                         journal_msg->cm_anon_type = MES_NORMAL;
113                         journal_msg->cm_format_type = FMT_RFC822;
114                         CM_SetField(journal_msg, eJournal, "is journal");
115
116                         if (!IsEmptyStr(jmsg->from)) {
117                                 CM_SetField(journal_msg, eAuthor, jmsg->from);
118                         }
119
120                         if (!IsEmptyStr(jmsg->rfca)) {
121                                 CM_SetField(journal_msg, erFc822Addr, jmsg->rfca);
122                         }
123
124                         if (!IsEmptyStr(jmsg->subj)) {
125                                 CM_SetField(journal_msg, eMsgSubject, jmsg->subj);
126                         }
127
128                         mblen = snprintf(mime_boundary, sizeof(mime_boundary),
129                                          "--Citadel-Journal-%08lx-%04x--", time(NULL), ++seq);
130
131                         if (!IsEmptyStr(jmsg->rfc822)) {
132                                 rfc822len = strlen(jmsg->rfc822);
133                         }
134                         else {
135                                 rfc822len = 0;
136                         }
137
138                         message_text = NewStrBufPlain(NULL, rfc822len + sizeof(struct recptypes) + 1024);
139
140                         /*
141                          * Here is where we begin to compose the journalized message.
142                          * (The "ExJournalReport" header is consumed by some email retention services which assume the journaling agent is Exchange.)
143                          */
144                         StrBufAppendBufPlain(
145                                 message_text, 
146                                 HKEY("Content-type: multipart/mixed; boundary=\""),
147                                 0
148                         );
149
150                         StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
151
152                         StrBufAppendBufPlain(
153                                 message_text, 
154                                 HKEY("\"\r\n"
155                                      "Content-Identifier: ExJournalReport\r\n"
156                                      "MIME-Version: 1.0\r\n"
157                                      "\n"
158                                      "--"),
159                                 0
160                         );
161
162                         StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
163
164                         StrBufAppendBufPlain(
165                                 message_text, 
166                                 HKEY("\r\n"
167                                      "Content-type: text/plain\r\n"
168                                      "\r\n"
169                                      "Sender: "), 0);
170
171                         if (CM_IsEmpty(journal_msg, eAuthor))
172                                 StrBufAppendBufPlain(
173                                         message_text, 
174                                         journal_msg->cm_fields[eAuthor], -1, 0);
175                         else
176                                 StrBufAppendBufPlain(
177                                         message_text, 
178                                         HKEY("(null)"), 0);
179
180                         if (!CM_IsEmpty(journal_msg, erFc822Addr)) {
181                                 StrBufAppendPrintf(message_text, " <%s>",
182                                                    journal_msg->cm_fields[erFc822Addr]);
183                         }
184
185                         StrBufAppendBufPlain(message_text, HKEY("\r\nMessage-ID: <"), 0);
186                         StrBufAppendBufPlain(message_text, jmsg->msgn, -1, 0);
187                         StrBufAppendBufPlain(message_text, HKEY(">\r\nRecipients:\r\n"), 0);
188
189                         if (jmsg->recps.num_local > 0) {
190                                 for (i=0; i<jmsg->recps.num_local; ++i) {
191                                         extract_token(recipient, jmsg->recps.recp_local, i, '|', sizeof recipient);
192                                         local_to_inetemail(inetemail, recipient, sizeof inetemail);
193                                         StrBufAppendPrintf(message_text, "      %s <%s>\r\n", recipient, inetemail);
194                                 }
195                         }
196
197                         if (jmsg->recps.num_internet > 0) {
198                                 for (i=0; i<jmsg->recps.num_internet; ++i) {
199                                         extract_token(recipient, jmsg->recps.recp_internet, i, '|', sizeof recipient);
200                                         StrBufAppendPrintf(message_text, "      %s\r\n", recipient);
201                                 }
202                         }
203
204                         StrBufAppendBufPlain(message_text, HKEY("\r\n" "--"), 0);
205                         StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
206                         StrBufAppendBufPlain(message_text, HKEY("\r\nContent-type: message/rfc822\r\n\r\n"), 0);
207                         StrBufAppendBufPlain(message_text, jmsg->rfc822, rfc822len, 0);
208                         StrBufAppendBufPlain(message_text, HKEY("--"), 0);
209                         StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
210                         StrBufAppendBufPlain(message_text, HKEY("--\r\n"), 0);
211
212                         CM_SetAsFieldSB(journal_msg, eMesageText, &message_text);
213                         free(jmsg->rfc822);
214                         free(jmsg->msgn);
215                         jmsg->rfc822 = NULL;
216                         jmsg->msgn = NULL;
217                         
218                         /* Submit journal message */
219                         CtdlSubmitMsg(journal_msg, journal_recps, "");
220                         CM_Free(journal_msg);
221                 }
222
223                 free_recipients(journal_recps);
224         }
225
226         /* We are responsible for freeing this memory. */
227         free(jmsg);
228 }
229
230
231 /*
232  * Run the queue.
233  */
234 void JournalRunQueue(void) {
235         struct jnlq *jptr = NULL;
236
237         while (jnlq != NULL) {
238                 begin_critical_section(S_JOURNAL_QUEUE);
239                 if (jnlq != NULL) {
240                         jptr = jnlq;
241                         jnlq = jnlq->next;
242                 }
243                 end_critical_section(S_JOURNAL_QUEUE);
244                 JournalRunQueueMsg(jptr);
245         }
246 }