* Removed some protocol commands and writeups that are no longer necessary
[citadel.git] / citadel / internet_addressing.c
1 /*
2  * $Id$
3  *
4  * This file contains functions which handle the mapping of Internet addresses
5  * to users on the Citadel system.
6  */
7
8 #ifdef DLL_EXPORT
9 #define IN_LIBCIT
10 #endif
11
12 #include "sysdep.h"
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <stdio.h>
16 #include <fcntl.h>
17 #include <ctype.h>
18 #include <signal.h>
19 #include <pwd.h>
20 #include <errno.h>
21 #include <sys/types.h>
22
23 #if TIME_WITH_SYS_TIME
24 # include <sys/time.h>
25 # include <time.h>
26 #else
27 # if HAVE_SYS_TIME_H
28 #  include <sys/time.h>
29 # else
30 #  include <time.h>
31 # endif
32 #endif
33
34 #include <sys/wait.h>
35 #include <string.h>
36 #include <limits.h>
37 #include "citadel.h"
38 #include "server.h"
39 #include "dynloader.h"
40 #include "sysdep_decls.h"
41 #include "citserver.h"
42 #include "support.h"
43 #include "config.h"
44 #include "tools.h"
45 #include "msgbase.h"
46 #include "internet_addressing.h"
47 #include "user_ops.h"
48 #include "room_ops.h"
49 #include "parsedate.h"
50
51
52 #ifndef HAVE_SNPRINTF
53 #include "snprintf.h"
54 #endif
55
56
57 struct trynamebuf {
58         char buffer1[SIZ];
59         char buffer2[SIZ];
60 };
61
62 char *inetcfg = NULL;
63
64
65
66 /*
67  * Return nonzero if the supplied name is an alias for this host.
68  */
69 int CtdlHostAlias(char *fqdn) {
70         int config_lines;
71         int i;
72         char buf[SIZ];
73         char host[SIZ], type[SIZ];
74
75         if (!strcasecmp(fqdn, config.c_fqdn)) return(hostalias_localhost);
76         if (!strcasecmp(fqdn, config.c_nodename)) return(hostalias_localhost);
77         if (inetcfg == NULL) return(hostalias_nomatch);
78
79         config_lines = num_tokens(inetcfg, '\n');
80         for (i=0; i<config_lines; ++i) {
81                 extract_token(buf, inetcfg, i, '\n');
82                 extract_token(host, buf, 0, '|');
83                 extract_token(type, buf, 1, '|');
84
85                 if ( (!strcasecmp(type, "localhost"))
86                    && (!strcasecmp(fqdn, host)))
87                         return(hostalias_localhost);
88
89                 if ( (!strcasecmp(type, "gatewaydomain"))
90                    && (!strcasecmp(&fqdn[strlen(fqdn)-strlen(host)], host)))
91                         return(hostalias_gatewaydomain);
92
93                 if ( (!strcasecmp(type, "directory"))
94                    && (!strcasecmp(&fqdn[strlen(fqdn)-strlen(host)], host)))
95                         return(hostalias_directory);
96
97         }
98
99         return(hostalias_nomatch);
100 }
101
102
103
104
105
106
107
108 /*
109  * Return 0 if a given string fuzzy-matches a Citadel user account
110  *
111  * FIXME ... this needs to be updated to handle aliases.
112  */
113 int fuzzy_match(struct usersupp *us, char *matchstring) {
114         int a;
115
116         if ( (!strncasecmp(matchstring, "cit", 3)) 
117            && (atol(&matchstring[3]) == us->usernum)) {
118                 return 0;
119         }
120
121
122         for (a=0; a<strlen(us->fullname); ++a) {
123                 if (!strncasecmp(&us->fullname[a],
124                    matchstring, strlen(matchstring))) {
125                         return 0;
126                 }
127         }
128         return -1;
129 }
130
131
132 /*
133  * Unfold a multi-line field into a single line, removing multi-whitespaces
134  */
135 void unfold_rfc822_field(char *field) {
136         int i;
137         int quote = 0;
138
139         striplt(field);         /* remove leading/trailing whitespace */
140
141         /* convert non-space whitespace to spaces, and remove double blanks */
142         for (i=0; i<strlen(field); ++i) {
143                 if (field[i]=='\"') quote = 1 - quote;
144                 if (!quote) {
145                         if (isspace(field[i])) field[i] = ' ';
146                         while (isspace(field[i]) && isspace(field[i+1])) {
147                                 strcpy(&field[i+1], &field[i+2]);
148                         }
149                 }
150         }
151 }
152
153
154
155 /*
156  * Split an RFC822-style address into userid, host, and full name
157  *
158  */
159 void process_rfc822_addr(char *rfc822, char *user, char *node, char *name)
160 {
161         int a;
162
163         strcpy(user, "");
164         strcpy(node, config.c_fqdn);
165         strcpy(name, "");
166
167         /* extract full name - first, it's From minus <userid> */
168         strcpy(name, rfc822);
169         stripout(name, '<', '>');
170
171         /* strip anything to the left of a bang */
172         while ((strlen(name) > 0) && (haschar(name, '!') > 0))
173                 strcpy(name, &name[1]);
174
175         /* and anything to the right of a @ or % */
176         for (a = 0; a < strlen(name); ++a) {
177                 if (name[a] == '@')
178                         name[a] = 0;
179                 if (name[a] == '%')
180                         name[a] = 0;
181         }
182
183         /* but if there are parentheses, that changes the rules... */
184         if ((haschar(rfc822, '(') == 1) && (haschar(rfc822, ')') == 1)) {
185                 strcpy(name, rfc822);
186                 stripallbut(name, '(', ')');
187         }
188
189         /* but if there are a set of quotes, that supersedes everything */
190         if (haschar(rfc822, 34) == 2) {
191                 strcpy(name, rfc822);
192                 while ((strlen(name) > 0) && (name[0] != 34)) {
193                         strcpy(&name[0], &name[1]);
194                 }
195                 strcpy(&name[0], &name[1]);
196                 for (a = 0; a < strlen(name); ++a)
197                         if (name[a] == 34)
198                                 name[a] = 0;
199         }
200         /* extract user id */
201         strcpy(user, rfc822);
202
203         /* first get rid of anything in parens */
204         stripout(user, '(', ')');
205
206         /* if there's a set of angle brackets, strip it down to that */
207         if ((haschar(user, '<') == 1) && (haschar(user, '>') == 1)) {
208                 stripallbut(user, '<', '>');
209         }
210
211         /* strip anything to the left of a bang */
212         while ((strlen(user) > 0) && (haschar(user, '!') > 0))
213                 strcpy(user, &user[1]);
214
215         /* and anything to the right of a @ or % */
216         for (a = 0; a < strlen(user); ++a) {
217                 if (user[a] == '@')
218                         user[a] = 0;
219                 if (user[a] == '%')
220                         user[a] = 0;
221         }
222
223
224         /* extract node name */
225         strcpy(node, rfc822);
226
227         /* first get rid of anything in parens */
228         stripout(node, '(', ')');
229
230         /* if there's a set of angle brackets, strip it down to that */
231         if ((haschar(node, '<') == 1) && (haschar(node, '>') == 1)) {
232                 stripallbut(node, '<', '>');
233         }
234
235         /* If no node specified, tack ours on instead */
236         if (
237                 (haschar(node, '@')==0)
238                 && (haschar(node, '%')==0)
239                 && (haschar(node, '!')==0)
240         ) {
241                 strcpy(node, config.c_nodename);
242         }
243
244         else {
245
246                 /* strip anything to the left of a @ */
247                 while ((strlen(node) > 0) && (haschar(node, '@') > 0))
248                         strcpy(node, &node[1]);
249         
250                 /* strip anything to the left of a % */
251                 while ((strlen(node) > 0) && (haschar(node, '%') > 0))
252                         strcpy(node, &node[1]);
253         
254                 /* reduce multiple system bang paths to node!user */
255                 while ((strlen(node) > 0) && (haschar(node, '!') > 1))
256                         strcpy(node, &node[1]);
257         
258                 /* now get rid of the user portion of a node!user string */
259                 for (a = 0; a < strlen(node); ++a)
260                         if (node[a] == '!')
261                                 node[a] = 0;
262         }
263
264         /* strip leading and trailing spaces in all strings */
265         striplt(user);
266         striplt(node);
267         striplt(name);
268 }
269
270
271
272 /*
273  * Back end for convert_internet_address()
274  * (Compares an internet name [buffer1] and stores in [buffer2] if found)
275  */
276 void try_name(struct usersupp *us, void *data) {
277         struct passwd *pw;
278         struct trynamebuf *tnb;
279         tnb = (struct trynamebuf *)data;
280
281         if (!strncasecmp(tnb->buffer1, "cit", 3))
282                 if (atol(&tnb->buffer1[3]) == us->usernum)
283                         strcpy(tnb->buffer2, us->fullname);
284
285         if (!collapsed_strcmp(tnb->buffer1, us->fullname)) 
286                         strcpy(tnb->buffer2, us->fullname);
287
288         if (us->uid != BBSUID) {
289                 pw = getpwuid(us->uid);
290                 if (pw != NULL) {
291                         if (!strcasecmp(tnb->buffer1, pw->pw_name)) {
292                                 strcpy(tnb->buffer2, us->fullname);
293                         }
294                 }
295         }
296 }
297
298
299 /*
300  * Convert an Internet email address to a Citadel user/host combination
301  */
302 int convert_internet_address(char *destuser, char *desthost, char *source)
303 {
304         char user[SIZ];
305         char node[SIZ];
306         char name[SIZ];
307         struct quickroom qrbuf;
308         int i;
309         int hostalias;
310         struct trynamebuf tnb;
311         char buf[SIZ];
312         int passes = 0;
313         char sourcealias[1024];
314
315         safestrncpy(sourcealias, source, sizeof(sourcealias) );
316         alias(sourcealias);
317
318 REALIAS:
319         /* Split it up */
320         process_rfc822_addr(sourcealias, user, node, name);
321         lprintf(9, "process_rfc822_addr() converted to <%s@%s> (%s)\n",
322                 user, node, name);
323
324         /* Map the FQDN to a Citadel node name
325          */
326         hostalias =  CtdlHostAlias(node);
327         switch(hostalias) {
328                 case hostalias_localhost:
329                         strcpy(node, config.c_nodename);
330                         break;
331
332                 case hostalias_gatewaydomain:
333                         extract_token(buf, node, 0, '.');
334                         safestrncpy(node, buf, sizeof buf);
335         }
336
337         /* Now try to resolve the name
338          * FIXME ... do the multiple-addresses thing
339          */
340         if (!strcasecmp(node, config.c_nodename)) {
341
342
343                 /* First, see if we hit an alias.  Don't do this more than
344                  * a few times, in case we accidentally hit an alias loop
345                  */
346                 strcpy(sourcealias, user);
347                 alias(user);
348                 if ( (strcasecmp(user, sourcealias)) && (++passes < 3) )
349                         goto REALIAS;
350
351                 /* Try all local rooms */
352                 if (!strncasecmp(user, "room_", 5)) {
353                         strcpy(name, &user[5]);
354                         for (i=0; i<strlen(name); ++i) 
355                                 if (name[i]=='_') name[i]=' ';
356                         if (getroom(&qrbuf, name) == 0) {
357                                 strcpy(destuser, qrbuf.QRname);
358                                 strcpy(desthost, config.c_nodename);
359                                 return rfc822_room_delivery;
360                         }
361                 }
362
363                 /* Try all local users */
364                 strcpy(destuser, user);
365                 strcpy(desthost, config.c_nodename);
366                 strcpy(tnb.buffer1, user);
367                 strcpy(tnb.buffer2, "");
368                 ForEachUser(try_name, &tnb);
369                 if (strlen(tnb.buffer2) == 0) return(rfc822_no_such_user);
370                 strcpy(destuser, tnb.buffer2);
371                 return(rfc822_address_locally_validated);
372         }
373
374         strcpy(destuser, user);
375         strcpy(desthost, node);
376         if (hostalias == hostalias_gatewaydomain)
377                 return(rfc822_address_on_citadel_network);
378         return(rfc822_address_nonlocal);
379 }
380
381
382
383
384 /*
385  * convert_field() is a helper function for convert_internet_message().
386  * Given start/end positions for an rfc822 field, it converts it to a Citadel
387  * field if it wants to, and unfolds it if necessary.
388  *
389  * Returns 1 if the field was converted and inserted into the Citadel message
390  * structure, implying that the source field should be removed from the
391  * message text.
392  */
393 int convert_field(struct CtdlMessage *msg, int beg, int end) {
394         char *rfc822;
395         char *key, *value;
396         int i;
397         int colonpos = (-1);
398         int processed = 0;
399         char buf[SIZ];
400         char user[1024];
401         char node[1024];
402         char name[1024];
403         char addr[1024];
404         time_t parsed_date;
405
406         rfc822 = msg->cm_fields['M'];   /* M field contains rfc822 text */
407         for (i = end; i >= beg; --i) {
408                 if (rfc822[i] == ':') colonpos = i;
409         }
410
411         if (colonpos < 0) return(0);    /* no colon? not a valid header line */
412
413         key = mallok((end - beg) + 2);
414         safestrncpy(key, &rfc822[beg], (end-beg)+1);
415         key[colonpos - beg] = 0;
416         value = &key[(colonpos - beg) + 1];
417         unfold_rfc822_field(value);
418
419         /*
420          * Here's the big rfc822-to-citadel loop.
421          */
422
423         /* Date/time is converted into a unix timestamp.  If the conversion
424          * fails, we replace it with the time the message arrived locally.
425          */
426         if (!strcasecmp(key, "Date")) {
427                 parsed_date = parsedate(value);
428                 if (parsed_date < 0L) parsed_date = time(NULL);
429                 snprintf(buf, sizeof buf, "%ld", (long)parsed_date );
430                 if (msg->cm_fields['T'] == NULL)
431                         msg->cm_fields['T'] = strdoop(buf);
432                 processed = 1;
433         }
434
435         else if (!strcasecmp(key, "From")) {
436                 process_rfc822_addr(value, user, node, name);
437                 lprintf(9, "Converted to <%s@%s> (%s)\n", user, node, name);
438                 snprintf(addr, sizeof addr, "%s@%s", user, node);
439                 if (msg->cm_fields['A'] == NULL)
440                         msg->cm_fields['A'] = strdoop(name);
441                 processed = 1;
442                 if (msg->cm_fields['F'] == NULL)
443                         msg->cm_fields['F'] = strdoop(addr);
444                 processed = 1;
445         }
446
447         else if (!strcasecmp(key, "Subject")) {
448                 if (msg->cm_fields['U'] == NULL)
449                         msg->cm_fields['U'] = strdoop(value);
450                 processed = 1;
451         }
452
453         else if (!strcasecmp(key, "Message-ID")) {
454                 if (msg->cm_fields['I'] != NULL) {
455                         lprintf(5, "duplicate message id\n");
456                 }
457
458                 if (msg->cm_fields['I'] == NULL) {
459                         msg->cm_fields['I'] = strdoop(value);
460
461                         /* Strip angle brackets */
462                         while (haschar(msg->cm_fields['I'], '<') > 0) {
463                                 strcpy(&msg->cm_fields['I'][0],
464                                         &msg->cm_fields['I'][1]);
465                         }
466                         for (i = 0; i<strlen(msg->cm_fields['I']); ++i)
467                                 if (msg->cm_fields['I'][i] == '>')
468                                         msg->cm_fields['I'][i] = 0;
469                 }
470
471                 processed = 1;
472         }
473
474         /* Clean up and move on. */
475         phree(key);     /* Don't free 'value', it's actually the same buffer */
476         return(processed);
477 }
478
479
480 /*
481  * Convert an RFC822 message (headers + body) to a CtdlMessage structure.
482  * NOTE: the supplied buffer becomes part of the CtdlMessage structure, and
483  * will be deallocated when CtdlFreeMessage() is called.  Therefore, the
484  * supplied buffer should be DEREFERENCED.  It should not be freed or used
485  * again.
486  */
487 struct CtdlMessage *convert_internet_message(char *rfc822) {
488
489         struct CtdlMessage *msg;
490         int pos, beg, end, msglen;
491         int done;
492         char buf[SIZ];
493         int converted;
494
495         msg = mallok(sizeof(struct CtdlMessage));
496         if (msg == NULL) return msg;
497
498         memset(msg, 0, sizeof(struct CtdlMessage));
499         msg->cm_magic = CTDLMESSAGE_MAGIC;      /* self check */
500         msg->cm_anon_type = 0;                  /* never anonymous */
501         msg->cm_format_type = FMT_RFC822;       /* internet message */
502         msg->cm_fields['M'] = rfc822;
503
504         lprintf(9, "Unconverted RFC822 message length = %ld\n", (long)strlen(rfc822));
505         pos = 0;
506         done = 0;
507
508         while (!done) {
509
510                 /* Locate beginning and end of field, keeping in mind that
511                  * some fields might be multiline
512                  */
513                 beg = pos;
514                 end = (-1);
515
516                 msglen = strlen(rfc822);        
517                 while ( (end < 0) && (done == 0) ) {
518
519                         if ((rfc822[pos]=='\n')
520                            && (!isspace(rfc822[pos+1]))) {
521                                 end = pos;
522                         }
523
524                         /* done with headers? */
525                         if (   ((rfc822[pos]=='\n')
526                               ||(rfc822[pos]=='\r') )
527                            && ( (rfc822[pos+1]=='\n')
528                               ||(rfc822[pos+1]=='\r')) ) {
529                                 end = pos;
530                                 done = 1;
531                         }
532
533                         if (pos >= (msglen-1) ) {
534                                 end = pos;
535                                 done = 1;
536                         }
537
538                         ++pos;
539
540                 }
541
542                 /* At this point we have a field.  Are we interested in it? */
543                 converted = convert_field(msg, beg, end);
544
545                 /* Strip the field out of the RFC822 header if we used it */
546                 if (converted) {
547                         strcpy(&rfc822[beg], &rfc822[pos]);
548                         pos = beg;
549                 }
550
551                 /* If we've hit the end of the message, bail out */
552                 if (pos > strlen(rfc822)) done = 1;
553         }
554
555         /* Follow-up sanity checks... */
556
557         /* If there's no timestamp on this message, set it to now. */
558         if (msg->cm_fields['T'] == NULL) {
559                 snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
560                 msg->cm_fields['T'] = strdoop(buf);
561         }
562
563         lprintf(9, "RFC822 length remaining after conversion = %ld\n",
564                 (long)strlen(rfc822));
565         return msg;
566 }
567
568
569
570 /*
571  * Look for a particular header field in an RFC822 message text.  If the
572  * requested field is found, it is unfolded (if necessary) and returned to
573  * the caller.  The field name is stripped out, leaving only its contents.
574  * The caller is responsible for freeing the returned buffer.  If the requested
575  * field is not present, or anything else goes wrong, it returns NULL.
576  */
577 char *rfc822_fetch_field(char *rfc822, char *fieldname) {
578         int pos = 0;
579         int beg, end;
580         int done = 0;
581         int colonpos, i;
582         char *fieldbuf = NULL;
583
584         /* Should never happen, but sometimes we get stupid */
585         if (rfc822 == NULL) return(NULL);
586         if (fieldname == NULL) return(NULL);
587
588         while (!done) {
589
590                 /* Locate beginning and end of field, keeping in mind that
591                  * some fields might be multiline
592                  */
593                 beg = pos;
594                 end = (-1);
595                 for (pos=beg; ((pos<=strlen(rfc822))&&(end<0)); ++pos) {
596                         if ((rfc822[pos]=='\n')
597                            && (!isspace(rfc822[pos+1]))) {
598                                 end = pos;
599                         }
600                         if ( (rfc822[pos]=='\n')        /* done w. headers? */
601                            && ( (rfc822[pos+1]=='\n')
602                               ||(rfc822[pos+1]=='\r'))) {
603                                 end = pos;
604                                 done = 1;
605                         }
606
607                 }
608
609                 /* At this point we have a field.  Is it The One? */
610                 if (end > beg) {
611                         fieldbuf = mallok((end-beg)+3);
612                         if (fieldbuf == NULL) return(NULL);
613                         safestrncpy(fieldbuf, &rfc822[beg], (end-beg)+1);
614                         unfold_rfc822_field(fieldbuf);
615                         colonpos = (-1);
616                         for (i = strlen(fieldbuf); i >= 0; --i) {
617                                 if (fieldbuf[i] == ':') colonpos = i;
618                         }
619                         if (colonpos > 0) {
620                                 fieldbuf[colonpos] = 0;
621                                 if (!strcasecmp(fieldbuf, fieldname)) {
622                                         strcpy(fieldbuf, &fieldbuf[colonpos+1]);
623                                         striplt(fieldbuf);
624                                         return(fieldbuf);
625                                 }
626                         }
627                         phree(fieldbuf);
628                 }
629
630                 /* If we've hit the end of the message, bail out */
631                 if (pos > strlen(rfc822)) done = 1;
632         }
633         return(NULL);
634 }