Fix memory leaks in the networker
[citadel.git] / citadel / modules / network / serv_netmail.c
1 /*
2  * This module handles shared rooms, inter-Citadel mail, and outbound
3  * mailing list processing.
4  *
5  * Copyright (c) 2000-2011 by the citadel.org team
6  *
7  *  This program is open source software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  * ** NOTE **   A word on the S_NETCONFIGS semaphore:
22  * This is a fairly high-level type of critical section.  It ensures that no
23  * two threads work on the netconfigs files at the same time.  Since we do
24  * so many things inside these, here are the rules:
25  *  1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others.
26  *  2. Do *not* perform any I/O with the client during these sections.
27  *
28  */
29
30 /*
31  * Duration of time (in seconds) after which pending list subscribe/unsubscribe
32  * requests that have not been confirmed will be deleted.
33  */
34 #define EXP     259200  /* three days */
35
36 #include "sysdep.h"
37 #include <stdlib.h>
38 #include <unistd.h>
39 #include <stdio.h>
40 #include <fcntl.h>
41 #include <ctype.h>
42 #include <signal.h>
43 #include <pwd.h>
44 #include <errno.h>
45 #include <sys/stat.h>
46 #include <sys/types.h>
47 #include <dirent.h>
48 #if TIME_WITH_SYS_TIME
49 # include <sys/time.h>
50 # include <time.h>
51 #else
52 # if HAVE_SYS_TIME_H
53 #  include <sys/time.h>
54 # else
55 #  include <time.h>
56 # endif
57 #endif
58 #ifdef HAVE_SYSCALL_H
59 # include <syscall.h>
60 #else 
61 # if HAVE_SYS_SYSCALL_H
62 #  include <sys/syscall.h>
63 # endif
64 #endif
65
66 #include <sys/wait.h>
67 #include <string.h>
68 #include <limits.h>
69 #include <libcitadel.h>
70 #include "citadel.h"
71 #include "server.h"
72 #include "citserver.h"
73 #include "support.h"
74 #include "config.h"
75 #include "user_ops.h"
76 #include "database.h"
77 #include "msgbase.h"
78 #include "internet_addressing.h"
79 #include "serv_network.h"
80 #include "clientsocket.h"
81 #include "file_ops.h"
82 #include "citadel_dirs.h"
83 #include "threads.h"
84
85 #ifndef HAVE_SNPRINTF
86 #include "snprintf.h"
87 #endif
88
89 #include "context.h"
90 #include "netconfig.h"
91 #include "netspool.h"
92 #include "netmail.h"
93 #include "ctdl_module.h"
94
95
96 /*
97  * Deliver digest messages
98  */
99 void network_deliver_digest(SpoolControl *sc) {
100         char buf[SIZ];
101         int i;
102         struct CtdlMessage *msg = NULL;
103         long msglen;
104         char *recps = NULL;
105         size_t recps_len = SIZ;
106         struct recptypes *valid;
107         namelist *nptr;
108         char bounce_to[256];
109
110         if (sc->num_msgs_spooled < 1) {
111                 fclose(sc->digestfp);
112                 sc->digestfp = NULL;
113                 return;
114         }
115
116         msg = malloc(sizeof(struct CtdlMessage));
117         memset(msg, 0, sizeof(struct CtdlMessage));
118         msg->cm_magic = CTDLMESSAGE_MAGIC;
119         msg->cm_format_type = FMT_RFC822;
120         msg->cm_anon_type = MES_NORMAL;
121
122         sprintf(buf, "%ld", time(NULL));
123         msg->cm_fields['T'] = strdup(buf);
124         msg->cm_fields['A'] = strdup(CC->room.QRname);
125         snprintf(buf, sizeof buf, "[%s]", CC->room.QRname);
126         msg->cm_fields['U'] = strdup(buf);
127         sprintf(buf, "room_%s@%s", CC->room.QRname, config.c_fqdn);
128         for (i=0; buf[i]; ++i) {
129                 if (isspace(buf[i])) buf[i]='_';
130                 buf[i] = tolower(buf[i]);
131         }
132         msg->cm_fields['F'] = strdup(buf);
133         msg->cm_fields['R'] = strdup(buf);
134
135         /* Set the 'List-ID' header */
136         msg->cm_fields['L'] = malloc(1024);
137         snprintf(msg->cm_fields['L'], 1024,
138                 "%s <%ld.list-id.%s>",
139                 CC->room.QRname,
140                 CC->room.QRnumber,
141                 config.c_fqdn
142         );
143
144         /*
145          * Go fetch the contents of the digest
146          */
147         fseek(sc->digestfp, 0L, SEEK_END);
148         msglen = ftell(sc->digestfp);
149
150         msg->cm_fields['M'] = malloc(msglen + 1);
151         fseek(sc->digestfp, 0L, SEEK_SET);
152         fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp);
153         msg->cm_fields['M'][msglen] = '\0';
154
155         fclose(sc->digestfp);
156         sc->digestfp = NULL;
157
158         /* Now generate the delivery instructions */
159
160         /* 
161          * Figure out how big a buffer we need to allocate
162          */
163         for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
164                 recps_len = recps_len + strlen(nptr->name) + 2;
165         }
166         
167         recps = malloc(recps_len);
168
169         if (recps == NULL) {
170                 syslog(LOG_EMERG, "Cannot allocate %ld bytes for recps...\n", (long)recps_len);
171                 abort();
172         }
173
174         strcpy(recps, "");
175
176         /* Each recipient */
177         for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
178                 if (nptr != sc->digestrecps) {
179                         strcat(recps, ",");
180                 }
181                 strcat(recps, nptr->name);
182         }
183
184         /* Where do we want bounces and other noise to be heard?  Surely not the list members! */
185         snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
186
187         /* Now submit the message */
188         valid = validate_recipients(recps, NULL, 0);
189         free(recps);
190         if (valid != NULL) {
191                 valid->bounce_to = strdup(bounce_to);
192                 valid->envelope_from = strdup(bounce_to);
193                 CtdlSubmitMsg(msg, valid, NULL, 0);
194         }
195         CtdlFreeMessage(msg);
196         free_recipients(valid);
197 }
198
199
200 /*
201  * Deliver list messages to everyone on the list ... efficiently
202  */
203 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc) {
204         char *recps = NULL;
205         size_t recps_len = SIZ;
206         struct recptypes *valid;
207         namelist *nptr;
208         char bounce_to[256];
209
210         /* Don't do this if there were no recipients! */
211         if (sc->listrecps == NULL) return;
212
213         /* Now generate the delivery instructions */
214
215         /* 
216          * Figure out how big a buffer we need to allocate
217          */
218         for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
219                 recps_len = recps_len + strlen(nptr->name) + 2;
220         }
221         
222         recps = malloc(recps_len);
223
224         if (recps == NULL) {
225                 syslog(LOG_EMERG, "Cannot allocate %ld bytes for recps...\n", (long)recps_len);
226                 abort();
227         }
228
229         strcpy(recps, "");
230
231         /* Each recipient */
232         for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
233                 if (nptr != sc->listrecps) {
234                         strcat(recps, ",");
235                 }
236                 strcat(recps, nptr->name);
237         }
238
239         /* Where do we want bounces and other noise to be heard?  Surely not the list members! */
240         snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
241
242         /* Now submit the message */
243         valid = validate_recipients(recps, NULL, 0);
244         free(recps);
245         if (valid != NULL) {
246                 valid->bounce_to = strdup(bounce_to);
247                 valid->envelope_from = strdup(bounce_to);
248                 CtdlSubmitMsg(msg, valid, NULL, 0);
249                 free_recipients(valid);
250         }
251         /* Do not call CtdlFreeMessage(msg) here; the caller will free it. */
252 }
253
254
255
256
257 /*
258  * Spools out one message from the list.
259  */
260 void network_spool_msg(long msgnum, 
261                        void *userdata)
262 {
263         SpoolControl *sc;
264         int i;
265         char *newpath = NULL;
266         struct CtdlMessage *msg = NULL;
267         namelist *nptr;
268         maplist *mptr;
269         struct ser_ret sermsg;
270         FILE *fp;
271         char filename[PATH_MAX];
272         char buf[SIZ];
273         int bang = 0;
274         int send = 1;
275         int delete_after_send = 0;      /* Set to 1 to delete after spooling */
276         int ok_to_participate = 0;
277         struct recptypes *valid;
278
279         sc = (SpoolControl *)userdata;
280
281         /*
282          * Process mailing list recipients
283          */
284         if (sc->listrecps != NULL) {
285                 /* Fetch the message.  We're going to need to modify it
286                  * in order to insert the [list name] in it, etc.
287                  */
288                 msg = CtdlFetchMessage(msgnum, 1);
289                 if (msg != NULL) {
290                         int rlen;
291                         char *pCh;
292                         StrBuf *Subject, *FlatSubject;
293
294                         if (msg->cm_fields['K'] != NULL)
295                                 free(msg->cm_fields['K']);
296                         if (msg->cm_fields['V'] == NULL){
297                                 /* local message, no enVelope */
298                                 StrBuf *Buf;
299                                 Buf = NewStrBuf();
300                                 StrBufAppendBufPlain(Buf, msg->cm_fields['O'], -1, 0);
301                                 StrBufAppendBufPlain(Buf, HKEY("@"), 0);
302                                 StrBufAppendBufPlain(Buf, config.c_fqdn, -1, 0);
303                                 
304                                 msg->cm_fields['K'] = SmashStrBuf(&Buf);
305                         }
306                         else {
307                                 msg->cm_fields['K'] = strdup (msg->cm_fields['V']);
308                         }
309                         /* Set the 'List-ID' header */
310                         if (msg->cm_fields['L'] != NULL) {
311                                 free(msg->cm_fields['L']);
312                         }
313                         msg->cm_fields['L'] = malloc(1024);
314                         snprintf(msg->cm_fields['L'], 1024,
315                                 "%s <%ld.list-id.%s>",
316                                 CC->room.QRname,
317                                 CC->room.QRnumber,
318                                 config.c_fqdn
319                         );
320
321                         /* Prepend "[List name]" to the subject */
322                         if (msg->cm_fields['U'] == NULL) {
323                                 Subject = NewStrBufPlain(HKEY("(no subject)"));
324                         }
325                         else {
326                                 Subject = NewStrBufPlain(msg->cm_fields['U'], -1);
327                         }
328                         FlatSubject = NewStrBufPlain(NULL, StrLength(Subject));
329                         StrBuf_RFC822_to_Utf8(FlatSubject, Subject, NULL, NULL);
330
331                         rlen = strlen(CC->room.QRname);
332                         pCh  = strstr(ChrPtr(FlatSubject), CC->room.QRname);
333                         if ((pCh == NULL) ||
334                             (*(pCh + rlen) != ']') ||
335                             (pCh == ChrPtr(FlatSubject)) ||
336                             (*(pCh - 1) != '[')
337                                 )
338                         {
339                                 StrBuf *tmp;
340                                 StrBufPlain(Subject, HKEY("["));
341                                 StrBufAppendBufPlain(Subject, CC->room.QRname, rlen, 0);
342                                 StrBufAppendBufPlain(Subject, HKEY("] "), 0);
343                                 StrBufAppendBuf(Subject, FlatSubject, 0);
344                                 tmp = Subject;  Subject = FlatSubject;  FlatSubject = tmp; /* so we can free the right one... */
345                                 StrBufRFC2047encode(&Subject, FlatSubject);
346                         }
347                         
348                         if (msg->cm_fields['U'] != NULL)
349                                 free (msg->cm_fields['U']);
350                         msg->cm_fields['U'] = SmashStrBuf(&Subject);
351
352                         FreeStrBuf(&FlatSubject);
353
354                         /* else we won't modify the buffer, since the roomname is already here. */
355
356                         /* Set the recipient of the list message to the
357                          * email address of the room itself.
358                          * FIXME ... I want to be able to pick any address
359                          */
360                         if (msg->cm_fields['R'] != NULL) {
361                                 free(msg->cm_fields['R']);
362                         }
363                         msg->cm_fields['R'] = malloc(256);
364                         snprintf(msg->cm_fields['R'], 256,
365                                 "room_%s@%s", CC->room.QRname,
366                                 config.c_fqdn);
367                         for (i=0; msg->cm_fields['R'][i]; ++i) {
368                                 if (isspace(msg->cm_fields['R'][i])) {
369                                         msg->cm_fields['R'][i] = '_';
370                                 }
371                         }
372
373                         /* Handle delivery */
374                         network_deliver_list(msg, sc);
375                         CtdlFreeMessage(msg);
376                 }
377         }
378
379         /*
380          * Process digest recipients
381          */
382         if ((sc->digestrecps != NULL) && (sc->digestfp != NULL)) {
383                 msg = CtdlFetchMessage(msgnum, 1);
384                 if (msg != NULL) {
385                         fprintf(sc->digestfp,   " -----------------------------------"
386                                                 "------------------------------------"
387                                                 "-------\n");
388                         fprintf(sc->digestfp, "From: ");
389                         if (msg->cm_fields['A'] != NULL) {
390                                 fprintf(sc->digestfp, "%s ", msg->cm_fields['A']);
391                         }
392                         if (msg->cm_fields['F'] != NULL) {
393                                 fprintf(sc->digestfp, "<%s> ", msg->cm_fields['F']);
394                         }
395                         else if (msg->cm_fields['N'] != NULL) {
396                                 fprintf(sc->digestfp, "@%s ", msg->cm_fields['N']);
397                         }
398                         fprintf(sc->digestfp, "\n");
399                         if (msg->cm_fields['U'] != NULL) {
400                                 fprintf(sc->digestfp, "Subject: %s\n", msg->cm_fields['U']);
401                         }
402
403                         CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
404                         
405                         safestrncpy(CC->preferred_formats, "text/plain", sizeof CC->preferred_formats);
406                         CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_NONE, 0, 0, 0);
407
408                         StrBufTrim(CC->redirect_buffer);
409                         fwrite(HKEY("\n"), 1, sc->digestfp);
410                         fwrite(SKEY(CC->redirect_buffer), 1, sc->digestfp);
411                         fwrite(HKEY("\n"), 1, sc->digestfp);
412
413                         FreeStrBuf(&CC->redirect_buffer);
414
415                         sc->num_msgs_spooled += 1;
416                         free(msg);
417                 }
418         }
419
420         /*
421          * Process client-side list participations for this room
422          */
423         if (sc->participates != NULL) {
424                 msg = CtdlFetchMessage(msgnum, 1);
425                 if (msg != NULL) {
426
427                         /* Only send messages which originated on our own Citadel
428                          * network, otherwise we'll end up sending the remote
429                          * mailing list's messages back to it, which is rude...
430                          */
431                         ok_to_participate = 0;
432                         if (msg->cm_fields['N'] != NULL) {
433                                 if (!strcasecmp(msg->cm_fields['N'], config.c_nodename)) {
434                                         ok_to_participate = 1;
435                                 }
436                                 if (is_valid_node(NULL, 
437                                                   NULL, 
438                                                   msg->cm_fields['N'], 
439                                                   sc->working_ignetcfg, 
440                                                   sc->the_netmap) == 0)
441                                 {
442                                         ok_to_participate = 1;
443                                 }
444                         }
445                         if (ok_to_participate) {
446                                 if (msg->cm_fields['F'] != NULL) {
447                                         free(msg->cm_fields['F']);
448                                 }
449                                 msg->cm_fields['F'] = malloc(SIZ);
450                                 /* Replace the Internet email address of the actual
451                                 * author with the email address of the room itself,
452                                 * so the remote listserv doesn't reject us.
453                                 * FIXME ... I want to be able to pick any address
454                                 */
455                                 snprintf(msg->cm_fields['F'], SIZ,
456                                         "room_%s@%s", CC->room.QRname,
457                                         config.c_fqdn);
458                                 for (i=0; msg->cm_fields['F'][i]; ++i) {
459                                         if (isspace(msg->cm_fields['F'][i])) {
460                                                 msg->cm_fields['F'][i] = '_';
461                                         }
462                                 }
463
464                                 /* 
465                                  * Figure out how big a buffer we need to allocate
466                                  */
467                                 for (nptr = sc->participates; nptr != NULL; nptr = nptr->next) {
468
469                                         if (msg->cm_fields['R'] != NULL) {
470                                                 free(msg->cm_fields['R']);
471                                         }
472                                         msg->cm_fields['R'] = strdup(nptr->name);
473         
474                                         valid = validate_recipients(nptr->name, NULL, 0);
475                                         CtdlSubmitMsg(msg, valid, "", 0);
476                                         free_recipients(valid);
477                                 }
478                         
479                         }
480                         CtdlFreeMessage(msg);
481                 }
482         }
483         
484         /*
485          * Process IGnet push shares
486          */
487         msg = CtdlFetchMessage(msgnum, 1);
488         if (msg != NULL) {
489                 size_t newpath_len;
490
491                 /* Prepend our node name to the Path field whenever
492                  * sending a message to another IGnet node
493                  */
494                 if (msg->cm_fields['P'] == NULL) {
495                         msg->cm_fields['P'] = strdup("username");
496                 }
497                 newpath_len = strlen(msg->cm_fields['P']) +
498                          strlen(config.c_nodename) + 2;
499                 newpath = malloc(newpath_len);
500                 snprintf(newpath, newpath_len, "%s!%s",
501                          config.c_nodename, msg->cm_fields['P']);
502                 free(msg->cm_fields['P']);
503                 msg->cm_fields['P'] = newpath;
504
505                 /*
506                  * Determine if this message is set to be deleted
507                  * after sending out on the network
508                  */
509                 if (msg->cm_fields['S'] != NULL) {
510                         if (!strcasecmp(msg->cm_fields['S'], "CANCEL")) {
511                                 delete_after_send = 1;
512                         }
513                 }
514
515                 /* Now send it to every node */
516                 if (sc->ignet_push_shares != NULL)
517                   for (mptr = sc->ignet_push_shares; mptr != NULL;
518                     mptr = mptr->next) {
519
520                         send = 1;
521
522                         /* Check for valid node name */
523                         if (is_valid_node(NULL, 
524                                           NULL, 
525                                           mptr->remote_nodename, 
526                                           sc->working_ignetcfg, 
527                                           sc->the_netmap) != 0)
528                         {
529                                 syslog(LOG_ERR, "Invalid node <%s>\n", mptr->remote_nodename);
530                                 send = 0;
531                         }
532
533                         /* Check for split horizon */
534                         syslog(LOG_DEBUG, "Path is %s\n", msg->cm_fields['P']);
535                         bang = num_tokens(msg->cm_fields['P'], '!');
536                         if (bang > 1) for (i=0; i<(bang-1); ++i) {
537                                 extract_token(buf, msg->cm_fields['P'], i, '!', sizeof buf);
538                                 syslog(LOG_DEBUG, "Compare <%s> to <%s>\n",
539                                         buf, mptr->remote_nodename) ;
540                                 if (!strcasecmp(buf, mptr->remote_nodename)) {
541                                         send = 0;
542                                         syslog(LOG_DEBUG, "Not sending to %s\n",
543                                                 mptr->remote_nodename);
544                                 }
545                                 else {
546                                         syslog(LOG_DEBUG, "Sending to %s\n", mptr->remote_nodename);
547                                 }
548                         }
549
550                         /* Send the message */
551                         if (send == 1) {
552
553                                 /*
554                                  * Force the message to appear in the correct room
555                                  * on the far end by setting the C field correctly
556                                  */
557                                 if (msg->cm_fields['C'] != NULL) {
558                                         free(msg->cm_fields['C']);
559                                 }
560                                 if (!IsEmptyStr(mptr->remote_roomname)) {
561                                         msg->cm_fields['C'] = strdup(mptr->remote_roomname);
562                                 }
563                                 else {
564                                         msg->cm_fields['C'] = strdup(CC->room.QRname);
565                                 }
566
567                                 /* serialize it for transmission */
568                                 serialize_message(&sermsg, msg);
569                                 if (sermsg.len > 0) {
570
571                                         /* write it to a spool file */
572                                         snprintf(filename, sizeof filename,"%s/%s@%lx%x",
573                                                 ctdl_netout_dir,
574                                                 mptr->remote_nodename,
575                                                 time(NULL),
576                                                 rand()
577                                         );
578                                         syslog(LOG_DEBUG, "Appending to %s\n", filename);
579                                         fp = fopen(filename, "ab");
580                                         if (fp != NULL) {
581                                                 fwrite(sermsg.ser,
582                                                         sermsg.len, 1, fp);
583                                                 fclose(fp);
584                                         }
585                                         else {
586                                                 syslog(LOG_ERR, "%s: %s\n", filename, strerror(errno));
587                                         }
588         
589                                         /* free the serialized version */
590                                         free(sermsg.ser);
591                                 }
592
593                         }
594                 }
595                 CtdlFreeMessage(msg);
596         }
597
598         /* update lastsent */
599         sc->lastsent = msgnum;
600
601         /* Delete this message if delete-after-send is set */
602         if (delete_after_send) {
603                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
604         }
605
606 }