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