Chop networker into handy bits; related functions now live in their own file.
[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 "ctdl_module.h"
92
93
94 /*
95  * Deliver digest messages
96  */
97 void network_deliver_digest(SpoolControl *sc) {
98         char buf[SIZ];
99         int i;
100         struct CtdlMessage *msg = NULL;
101         long msglen;
102         char *recps = NULL;
103         size_t recps_len = SIZ;
104         struct recptypes *valid;
105         namelist *nptr;
106         char bounce_to[256];
107
108         if (sc->num_msgs_spooled < 1) {
109                 fclose(sc->digestfp);
110                 sc->digestfp = NULL;
111                 return;
112         }
113
114         msg = malloc(sizeof(struct CtdlMessage));
115         memset(msg, 0, sizeof(struct CtdlMessage));
116         msg->cm_magic = CTDLMESSAGE_MAGIC;
117         msg->cm_format_type = FMT_RFC822;
118         msg->cm_anon_type = MES_NORMAL;
119
120         sprintf(buf, "%ld", time(NULL));
121         msg->cm_fields['T'] = strdup(buf);
122         msg->cm_fields['A'] = strdup(CC->room.QRname);
123         snprintf(buf, sizeof buf, "[%s]", CC->room.QRname);
124         msg->cm_fields['U'] = strdup(buf);
125         sprintf(buf, "room_%s@%s", CC->room.QRname, config.c_fqdn);
126         for (i=0; buf[i]; ++i) {
127                 if (isspace(buf[i])) buf[i]='_';
128                 buf[i] = tolower(buf[i]);
129         }
130         msg->cm_fields['F'] = strdup(buf);
131         msg->cm_fields['R'] = strdup(buf);
132
133         /* Set the 'List-ID' header */
134         msg->cm_fields['L'] = malloc(1024);
135         snprintf(msg->cm_fields['L'], 1024,
136                 "%s <%ld.list-id.%s>",
137                 CC->room.QRname,
138                 CC->room.QRnumber,
139                 config.c_fqdn
140         );
141
142         /*
143          * Go fetch the contents of the digest
144          */
145         fseek(sc->digestfp, 0L, SEEK_END);
146         msglen = ftell(sc->digestfp);
147
148         msg->cm_fields['M'] = malloc(msglen + 1);
149         fseek(sc->digestfp, 0L, SEEK_SET);
150         fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp);
151         msg->cm_fields['M'][msglen] = '\0';
152
153         fclose(sc->digestfp);
154         sc->digestfp = NULL;
155
156         /* Now generate the delivery instructions */
157
158         /* 
159          * Figure out how big a buffer we need to allocate
160          */
161         for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
162                 recps_len = recps_len + strlen(nptr->name) + 2;
163         }
164         
165         recps = malloc(recps_len);
166
167         if (recps == NULL) {
168                 syslog(LOG_EMERG, "Cannot allocate %ld bytes for recps...\n", (long)recps_len);
169                 abort();
170         }
171
172         strcpy(recps, "");
173
174         /* Each recipient */
175         for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
176                 if (nptr != sc->digestrecps) {
177                         strcat(recps, ",");
178                 }
179                 strcat(recps, nptr->name);
180         }
181
182         /* Where do we want bounces and other noise to be heard?  Surely not the list members! */
183         snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
184
185         /* Now submit the message */
186         valid = validate_recipients(recps, NULL, 0);
187         free(recps);
188         if (valid != NULL) {
189                 valid->bounce_to = strdup(bounce_to);
190                 valid->envelope_from = strdup(bounce_to);
191                 CtdlSubmitMsg(msg, valid, NULL, 0);
192         }
193         CtdlFreeMessage(msg);
194         free_recipients(valid);
195 }
196
197
198 /*
199  * Deliver list messages to everyone on the list ... efficiently
200  */
201 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc) {
202         char *recps = NULL;
203         size_t recps_len = SIZ;
204         struct recptypes *valid;
205         namelist *nptr;
206         char bounce_to[256];
207
208         /* Don't do this if there were no recipients! */
209         if (sc->listrecps == NULL) return;
210
211         /* Now generate the delivery instructions */
212
213         /* 
214          * Figure out how big a buffer we need to allocate
215          */
216         for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
217                 recps_len = recps_len + strlen(nptr->name) + 2;
218         }
219         
220         recps = malloc(recps_len);
221
222         if (recps == NULL) {
223                 syslog(LOG_EMERG, "Cannot allocate %ld bytes for recps...\n", (long)recps_len);
224                 abort();
225         }
226
227         strcpy(recps, "");
228
229         /* Each recipient */
230         for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
231                 if (nptr != sc->listrecps) {
232                         strcat(recps, ",");
233                 }
234                 strcat(recps, nptr->name);
235         }
236
237         /* Where do we want bounces and other noise to be heard?  Surely not the list members! */
238         snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
239
240         /* Now submit the message */
241         valid = validate_recipients(recps, NULL, 0);
242         free(recps);
243         if (valid != NULL) {
244                 valid->bounce_to = strdup(bounce_to);
245                 valid->envelope_from = strdup(bounce_to);
246                 CtdlSubmitMsg(msg, valid, NULL, 0);
247                 free_recipients(valid);
248         }
249         /* Do not call CtdlFreeMessage(msg) here; the caller will free it. */
250 }
251
252
253
254
255 /*
256  * Spools out one message from the list.
257  */
258 void network_spool_msg(long msgnum, void *userdata) {
259         SpoolControl *sc;
260         int i;
261         char *newpath = NULL;
262         struct CtdlMessage *msg = NULL;
263         namelist *nptr;
264         maplist *mptr;
265         struct ser_ret sermsg;
266         FILE *fp;
267         char filename[PATH_MAX];
268         char buf[SIZ];
269         int bang = 0;
270         int send = 1;
271         int delete_after_send = 0;      /* Set to 1 to delete after spooling */
272         int ok_to_participate = 0;
273         struct recptypes *valid;
274
275         sc = (SpoolControl *)userdata;
276
277         /*
278          * Process mailing list recipients
279          */
280         if (sc->listrecps != NULL) {
281                 /* Fetch the message.  We're going to need to modify it
282                  * in order to insert the [list name] in it, etc.
283                  */
284                 msg = CtdlFetchMessage(msgnum, 1);
285                 if (msg != NULL) {
286                         int rlen;
287                         char *pCh;
288                         StrBuf *Subject, *FlatSubject;
289
290                         if (msg->cm_fields['V'] == NULL){
291                                 /* local message, no enVelope */
292                                 StrBuf *Buf;
293                                 Buf = NewStrBuf();
294                                 StrBufAppendBufPlain(Buf, msg->cm_fields['O'], -1, 0);
295                                 StrBufAppendBufPlain(Buf, HKEY("@"), 0);
296                                 StrBufAppendBufPlain(Buf, config.c_fqdn, -1, 0);
297                                 
298                                 msg->cm_fields['K'] = SmashStrBuf(&Buf);
299                         }
300                         else {
301                                 msg->cm_fields['K'] = strdup (msg->cm_fields['V']);
302                         }
303                         /* Set the 'List-ID' header */
304                         if (msg->cm_fields['L'] != NULL) {
305                                 free(msg->cm_fields['L']);
306                         }
307                         msg->cm_fields['L'] = malloc(1024);
308                         snprintf(msg->cm_fields['L'], 1024,
309                                 "%s <%ld.list-id.%s>",
310                                 CC->room.QRname,
311                                 CC->room.QRnumber,
312                                 config.c_fqdn
313                         );
314
315                         /* Prepend "[List name]" to the subject */
316                         if (msg->cm_fields['U'] == NULL) {
317                                 Subject = NewStrBufPlain(HKEY("(no subject)"));
318                         }
319                         else {
320                                 Subject = NewStrBufPlain(msg->cm_fields['U'], -1);
321                         }
322                         FlatSubject = NewStrBufPlain(NULL, StrLength(Subject));
323                         StrBuf_RFC822_to_Utf8(FlatSubject, Subject, NULL, NULL);
324
325                         rlen = strlen(CC->room.QRname);
326                         pCh  = strstr(ChrPtr(FlatSubject), CC->room.QRname);
327                         if ((pCh == NULL) ||
328                             (*(pCh + rlen) != ']') ||
329                             (pCh == ChrPtr(FlatSubject)) ||
330                             (*(pCh - 1) != '[')
331                                 )
332                         {
333                                 StrBuf *tmp;
334                                 StrBufPlain(Subject, HKEY("["));
335                                 StrBufAppendBufPlain(Subject, CC->room.QRname, rlen, 0);
336                                 StrBufAppendBufPlain(Subject, HKEY("] "), 0);
337                                 StrBufAppendBuf(Subject, FlatSubject, 0);
338                                 tmp = Subject;  Subject = FlatSubject;  FlatSubject = tmp; /* so we can free the right one... */
339                                 StrBufRFC2047encode(&Subject, FlatSubject);
340                         }
341                         
342                         if (msg->cm_fields['U'] != NULL)
343                                 free (msg->cm_fields['U']);
344                         msg->cm_fields['U'] = SmashStrBuf(&Subject);
345
346                         FreeStrBuf(&FlatSubject);
347
348                         /* else we won't modify the buffer, since the roomname is already here. */
349
350                         /* Set the recipient of the list message to the
351                          * email address of the room itself.
352                          * FIXME ... I want to be able to pick any address
353                          */
354                         if (msg->cm_fields['R'] != NULL) {
355                                 free(msg->cm_fields['R']);
356                         }
357                         msg->cm_fields['R'] = malloc(256);
358                         snprintf(msg->cm_fields['R'], 256,
359                                 "room_%s@%s", CC->room.QRname,
360                                 config.c_fqdn);
361                         for (i=0; msg->cm_fields['R'][i]; ++i) {
362                                 if (isspace(msg->cm_fields['R'][i])) {
363                                         msg->cm_fields['R'][i] = '_';
364                                 }
365                         }
366
367                         /* Handle delivery */
368                         network_deliver_list(msg, sc);
369                         CtdlFreeMessage(msg);
370                 }
371         }
372
373         /*
374          * Process digest recipients
375          */
376         if ((sc->digestrecps != NULL) && (sc->digestfp != NULL)) {
377                 msg = CtdlFetchMessage(msgnum, 1);
378                 if (msg != NULL) {
379                         fprintf(sc->digestfp,   " -----------------------------------"
380                                                 "------------------------------------"
381                                                 "-------\n");
382                         fprintf(sc->digestfp, "From: ");
383                         if (msg->cm_fields['A'] != NULL) {
384                                 fprintf(sc->digestfp, "%s ", msg->cm_fields['A']);
385                         }
386                         if (msg->cm_fields['F'] != NULL) {
387                                 fprintf(sc->digestfp, "<%s> ", msg->cm_fields['F']);
388                         }
389                         else if (msg->cm_fields['N'] != NULL) {
390                                 fprintf(sc->digestfp, "@%s ", msg->cm_fields['N']);
391                         }
392                         fprintf(sc->digestfp, "\n");
393                         if (msg->cm_fields['U'] != NULL) {
394                                 fprintf(sc->digestfp, "Subject: %s\n", msg->cm_fields['U']);
395                         }
396
397                         CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
398                         
399                         safestrncpy(CC->preferred_formats, "text/plain", sizeof CC->preferred_formats);
400                         CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_NONE, 0, 0, 0);
401
402                         StrBufTrim(CC->redirect_buffer);
403                         fwrite(HKEY("\n"), 1, sc->digestfp);
404                         fwrite(SKEY(CC->redirect_buffer), 1, sc->digestfp);
405                         fwrite(HKEY("\n"), 1, sc->digestfp);
406
407                         FreeStrBuf(&CC->redirect_buffer);
408
409                         sc->num_msgs_spooled += 1;
410                         free(msg);
411                 }
412         }
413
414         /*
415          * Process client-side list participations for this room
416          */
417         if (sc->participates != NULL) {
418                 msg = CtdlFetchMessage(msgnum, 1);
419                 if (msg != NULL) {
420
421                         /* Only send messages which originated on our own Citadel
422                          * network, otherwise we'll end up sending the remote
423                          * mailing list's messages back to it, which is rude...
424                          */
425                         ok_to_participate = 0;
426                         if (msg->cm_fields['N'] != NULL) {
427                                 if (!strcasecmp(msg->cm_fields['N'], config.c_nodename)) {
428                                         ok_to_participate = 1;
429                                 }
430                                 if (is_valid_node(NULL, NULL, msg->cm_fields['N']) == 0) {
431                                         ok_to_participate = 1;
432                                 }
433                         }
434                         if (ok_to_participate) {
435                                 if (msg->cm_fields['F'] != NULL) {
436                                         free(msg->cm_fields['F']);
437                                 }
438                                 msg->cm_fields['F'] = malloc(SIZ);
439                                 /* Replace the Internet email address of the actual
440                                 * author with the email address of the room itself,
441                                 * so the remote listserv doesn't reject us.
442                                 * FIXME ... I want to be able to pick any address
443                                 */
444                                 snprintf(msg->cm_fields['F'], SIZ,
445                                         "room_%s@%s", CC->room.QRname,
446                                         config.c_fqdn);
447                                 for (i=0; msg->cm_fields['F'][i]; ++i) {
448                                         if (isspace(msg->cm_fields['F'][i])) {
449                                                 msg->cm_fields['F'][i] = '_';
450                                         }
451                                 }
452
453                                 /* 
454                                  * Figure out how big a buffer we need to allocate
455                                  */
456                                 for (nptr = sc->participates; nptr != NULL; nptr = nptr->next) {
457
458                                         if (msg->cm_fields['R'] == NULL) {
459                                                 free(msg->cm_fields['R']);
460                                         }
461                                         msg->cm_fields['R'] = strdup(nptr->name);
462         
463                                         valid = validate_recipients(nptr->name, NULL, 0);
464                                         CtdlSubmitMsg(msg, valid, "", 0);
465                                         free_recipients(valid);
466                                 }
467                         
468                         }
469                         CtdlFreeMessage(msg);
470                 }
471         }
472         
473         /*
474          * Process IGnet push shares
475          */
476         msg = CtdlFetchMessage(msgnum, 1);
477         if (msg != NULL) {
478                 size_t newpath_len;
479
480                 /* Prepend our node name to the Path field whenever
481                  * sending a message to another IGnet node
482                  */
483                 if (msg->cm_fields['P'] == NULL) {
484                         msg->cm_fields['P'] = strdup("username");
485                 }
486                 newpath_len = strlen(msg->cm_fields['P']) +
487                          strlen(config.c_nodename) + 2;
488                 newpath = malloc(newpath_len);
489                 snprintf(newpath, newpath_len, "%s!%s",
490                          config.c_nodename, msg->cm_fields['P']);
491                 free(msg->cm_fields['P']);
492                 msg->cm_fields['P'] = newpath;
493
494                 /*
495                  * Determine if this message is set to be deleted
496                  * after sending out on the network
497                  */
498                 if (msg->cm_fields['S'] != NULL) {
499                         if (!strcasecmp(msg->cm_fields['S'], "CANCEL")) {
500                                 delete_after_send = 1;
501                         }
502                 }
503
504                 /* Now send it to every node */
505                 if (sc->ignet_push_shares != NULL)
506                   for (mptr = sc->ignet_push_shares; mptr != NULL;
507                     mptr = mptr->next) {
508
509                         send = 1;
510
511                         /* Check for valid node name */
512                         if (is_valid_node(NULL, NULL, mptr->remote_nodename) != 0) {
513                                 syslog(LOG_ERR, "Invalid node <%s>\n", mptr->remote_nodename);
514                                 send = 0;
515                         }
516
517                         /* Check for split horizon */
518                         syslog(LOG_DEBUG, "Path is %s\n", msg->cm_fields['P']);
519                         bang = num_tokens(msg->cm_fields['P'], '!');
520                         if (bang > 1) for (i=0; i<(bang-1); ++i) {
521                                 extract_token(buf, msg->cm_fields['P'], i, '!', sizeof buf);
522                                 syslog(LOG_DEBUG, "Compare <%s> to <%s>\n",
523                                         buf, mptr->remote_nodename) ;
524                                 if (!strcasecmp(buf, mptr->remote_nodename)) {
525                                         send = 0;
526                                         syslog(LOG_DEBUG, "Not sending to %s\n",
527                                                 mptr->remote_nodename);
528                                 }
529                                 else {
530                                         syslog(LOG_DEBUG, "Sending to %s\n", mptr->remote_nodename);
531                                 }
532                         }
533
534                         /* Send the message */
535                         if (send == 1) {
536
537                                 /*
538                                  * Force the message to appear in the correct room
539                                  * on the far end by setting the C field correctly
540                                  */
541                                 if (msg->cm_fields['C'] != NULL) {
542                                         free(msg->cm_fields['C']);
543                                 }
544                                 if (!IsEmptyStr(mptr->remote_roomname)) {
545                                         msg->cm_fields['C'] = strdup(mptr->remote_roomname);
546                                 }
547                                 else {
548                                         msg->cm_fields['C'] = strdup(CC->room.QRname);
549                                 }
550
551                                 /* serialize it for transmission */
552                                 serialize_message(&sermsg, msg);
553                                 if (sermsg.len > 0) {
554
555                                         /* write it to a spool file */
556                                         snprintf(filename, sizeof filename,"%s/%s@%lx%x",
557                                                 ctdl_netout_dir,
558                                                 mptr->remote_nodename,
559                                                 time(NULL),
560                                                 rand()
561                                         );
562                                         syslog(LOG_DEBUG, "Appending to %s\n", filename);
563                                         fp = fopen(filename, "ab");
564                                         if (fp != NULL) {
565                                                 fwrite(sermsg.ser,
566                                                         sermsg.len, 1, fp);
567                                                 fclose(fp);
568                                         }
569                                         else {
570                                                 syslog(LOG_ERR, "%s: %s\n", filename, strerror(errno));
571                                         }
572         
573                                         /* free the serialized version */
574                                         free(sermsg.ser);
575                                 }
576
577                         }
578                 }
579                 CtdlFreeMessage(msg);
580         }
581
582         /* update lastsent */
583         sc->lastsent = msgnum;
584
585         /* Delete this message if delete-after-send is set */
586         if (delete_after_send) {
587                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
588         }
589
590 }