* Formalized the 'Internet Configuration' logistics. Added new API call
[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 #include "sysdep.h"
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <stdio.h>
12 #include <fcntl.h>
13 #include <ctype.h>
14 #include <signal.h>
15 #include <pwd.h>
16 #include <errno.h>
17 #include <sys/types.h>
18 #include <sys/time.h>
19 #include <sys/wait.h>
20 #include <string.h>
21 #include <limits.h>
22 #include "citadel.h"
23 #include "server.h"
24 #include <time.h>
25 #include "sysdep_decls.h"
26 #include "citserver.h"
27 #include "support.h"
28 #include "config.h"
29 #include "tools.h"
30 #include "internet_addressing.h"
31 #include "user_ops.h"
32 #include "room_ops.h"
33 #include "parsedate.h"
34
35
36 struct trynamebuf {
37         char buffer1[256];
38         char buffer2[256];
39 };
40
41 char *inetcfg = NULL;
42
43
44
45 /*
46  * Return nonzero if the supplied name is an alias for this host.
47  */
48 int CtdlLocalHost(char *fqdn) {
49         int config_lines;
50         int i;
51         char buf[256];
52         char host[256], type[256];
53
54         if (!strcasecmp(fqdn, config.c_fqdn)) return(1);
55         if (inetcfg == NULL) return(0);
56
57         config_lines = num_tokens(inetcfg, '\n');
58         for (i=0; i<config_lines; ++i) {
59                 extract_token(buf, inetcfg, i, '\n');
60                 extract_token(host, buf, 0, '|');
61                 extract_token(type, buf, 1, '|');
62                 if ( (!strcasecmp(type, "localhost"))
63                    && (!strcasecmp(fqdn, host)))  return(1);
64         }
65
66         return(0);
67 }
68
69
70
71
72
73
74
75 /*
76  * Return 0 if a given string fuzzy-matches a Citadel user account
77  *
78  * FIX ... this needs to be updated to match any and all address syntaxes.
79  */
80 int fuzzy_match(struct usersupp *us, char *matchstring) {
81         int a;
82
83         for (a=0; a<strlen(us->fullname); ++a) {
84                 if (!strncasecmp(&us->fullname[a],
85                    matchstring, strlen(matchstring))) {
86                         return 0;
87                 }
88         }
89         return -1;
90 }
91
92
93 /*
94  * Unfold a multi-line field into a single line, removing multi-whitespaces
95  */
96 void unfold_rfc822_field(char *field) {
97         int i;
98         int quote = 0;
99
100         striplt(field);         /* remove leading/trailing whitespace */
101
102         /* convert non-space whitespace to spaces, and remove double blanks */
103         for (i=0; i<strlen(field); ++i) {
104                 if (field[i]=='\"') quote = 1 - quote;
105                 if (!quote) {
106                         if (isspace(field[i])) field[i] = ' ';
107                         while (isspace(field[i]) && isspace(field[i+1])) {
108                                 strcpy(&field[i+1], &field[i+2]);
109                         }
110                 }
111         }
112 }
113
114
115
116 /*
117  * Split an RFC822-style address into userid, host, and full name
118  * (Originally from citmail.c, and unchanged so far)
119  *
120  */
121 void process_rfc822_addr(char *rfc822, char *user, char *node, char *name)
122 {
123         int a;
124
125         strcpy(user, "");
126         strcpy(node, config.c_fqdn);
127         strcpy(name, "");
128
129         /* extract full name - first, it's From minus <userid> */
130         strcpy(name, rfc822);
131         for (a = 0; a < strlen(name); ++a) {
132                 if (name[a] == '<') {
133                         do {
134                                 strcpy(&name[a], &name[a + 1]);
135                         } while ((strlen(name) > 0) && (name[a] != '>'));
136                         strcpy(&name[a], &name[a + 1]);
137                 }
138         }
139         /* strip anything to the left of a bang */
140         while ((strlen(name) > 0) && (haschar(name, '!') > 0))
141                 strcpy(name, &name[1]);
142
143         /* and anything to the right of a @ or % */
144         for (a = 0; a < strlen(name); ++a) {
145                 if (name[a] == '@')
146                         name[a] = 0;
147                 if (name[a] == '%')
148                         name[a] = 0;
149         }
150
151         /* but if there are parentheses, that changes the rules... */
152         if ((haschar(rfc822, '(') == 1) && (haschar(rfc822, ')') == 1)) {
153                 strcpy(name, rfc822);
154                 while ((strlen(name) > 0) && (name[0] != '(')) {
155                         strcpy(&name[0], &name[1]);
156                 }
157                 strcpy(&name[0], &name[1]);
158                 for (a = 0; a < strlen(name); ++a) {
159                         if (name[a] == ')') {
160                                 name[a] = 0;
161                         }
162                 }
163         }
164         /* but if there are a set of quotes, that supersedes everything */
165         if (haschar(rfc822, 34) == 2) {
166                 strcpy(name, rfc822);
167                 while ((strlen(name) > 0) && (name[0] != 34)) {
168                         strcpy(&name[0], &name[1]);
169                 }
170                 strcpy(&name[0], &name[1]);
171                 for (a = 0; a < strlen(name); ++a)
172                         if (name[a] == 34)
173                                 name[a] = 0;
174         }
175         /* extract user id */
176         strcpy(user, rfc822);
177
178         /* first get rid of anything in parens */
179         for (a = 0; a < strlen(user); ++a)
180                 if (user[a] == '(') {
181                         do {
182                                 strcpy(&user[a], &user[a + 1]);
183                         } while ((strlen(user) > 0) && (user[a] != ')'));
184                         strcpy(&user[a], &user[a + 1]);
185                 }
186         /* if there's a set of angle brackets, strip it down to that */
187         if ((haschar(user, '<') == 1) && (haschar(user, '>') == 1)) {
188                 while ((strlen(user) > 0) && (user[0] != '<')) {
189                         strcpy(&user[0], &user[1]);
190                 }
191                 strcpy(&user[0], &user[1]);
192                 for (a = 0; a < strlen(user); ++a)
193                         if (user[a] == '>')
194                                 user[a] = 0;
195         }
196         /* strip anything to the left of a bang */
197         while ((strlen(user) > 0) && (haschar(user, '!') > 0))
198                 strcpy(user, &user[1]);
199
200         /* and anything to the right of a @ or % */
201         for (a = 0; a < strlen(user); ++a) {
202                 if (user[a] == '@')
203                         user[a] = 0;
204                 if (user[a] == '%')
205                         user[a] = 0;
206         }
207
208
209         /* extract node name */
210         strcpy(node, rfc822);
211
212         /* first get rid of anything in parens */
213         for (a = 0; a < strlen(node); ++a)
214                 if (node[a] == '(') {
215                         do {
216                                 strcpy(&node[a], &node[a + 1]);
217                         } while ((strlen(node) > 0) && (node[a] != ')'));
218                         strcpy(&node[a], &node[a + 1]);
219                 }
220         /* if there's a set of angle brackets, strip it down to that */
221         if ((haschar(node, '<') == 1) && (haschar(node, '>') == 1)) {
222                 while ((strlen(node) > 0) && (node[0] != '<')) {
223                         strcpy(&node[0], &node[1]);
224                 }
225                 strcpy(&node[0], &node[1]);
226                 for (a = 0; a < strlen(node); ++a)
227                         if (node[a] == '>')
228                                 node[a] = 0;
229         }
230         /* strip anything to the left of a @ */
231         while ((strlen(node) > 0) && (haschar(node, '@') > 0))
232                 strcpy(node, &node[1]);
233
234         /* strip anything to the left of a % */
235         while ((strlen(node) > 0) && (haschar(node, '%') > 0))
236                 strcpy(node, &node[1]);
237
238         /* reduce multiple system bang paths to node!user */
239         while ((strlen(node) > 0) && (haschar(node, '!') > 1))
240                 strcpy(node, &node[1]);
241
242         /* now get rid of the user portion of a node!user string */
243         for (a = 0; a < strlen(node); ++a)
244                 if (node[a] == '!')
245                         node[a] = 0;
246
247         /* strip leading and trailing spaces in all strings */
248         striplt(user);
249         striplt(node);
250         striplt(name);
251 }
252
253
254
255 /*
256  * Back end for convert_internet_address()
257  * (Compares an internet name [buffer1] and stores in [buffer2] if found)
258  */
259 void try_name(struct usersupp *us, void *data) {
260         struct trynamebuf *tnb;
261         tnb = (struct trynamebuf *)data;
262         
263         if (!strncasecmp(tnb->buffer1, "cit", 3))
264                 if (atol(&tnb->buffer1[3]) == us->usernum)
265                         strcpy(tnb->buffer2, us->fullname);
266
267         if (!collapsed_strcmp(tnb->buffer1, us->fullname)) 
268                         strcpy(tnb->buffer2, us->fullname);
269
270         if (us->uid != BBSUID)
271                 if (!strcasecmp(tnb->buffer1, getpwuid(us->uid)->pw_name))
272                         strcpy(tnb->buffer2, us->fullname);
273 }
274
275
276 /*
277  * Convert an Internet email address to a Citadel user/host combination
278  */
279 int convert_internet_address(char *destuser, char *desthost, char *source)
280 {
281         char user[256];
282         char node[256];
283         char name[256];
284         struct quickroom qrbuf;
285         int i;
286         struct trynamebuf tnb;
287
288         /* Split it up */
289         process_rfc822_addr(source, user, node, name);
290
291         /* Map the FQDN to a Citadel node name
292          * FIX ... we have to check for gateway domains
293          */
294         if (CtdlLocalHost(node)) {
295                 strcpy(node, config.c_nodename);
296         }
297
298         /* Now try to resolve the name
299          * FIX ... do the multiple-addresses thing
300          */
301         if (!strcasecmp(node, config.c_nodename)) {
302                 /* Try all local rooms */
303                 if (!strncasecmp(user, "room_", 5)) {
304                         strcpy(name, &user[5]);
305                         for (i=0; i<strlen(name); ++i) 
306                                 if (name[i]=='_') name[i]=' ';
307                         if (getroom(&qrbuf, name) == 0) {
308                                 strcpy(destuser, qrbuf.QRname);
309                                 strcpy(desthost, config.c_nodename);
310                                 return rfc822_room_delivery;
311                         }
312                 }
313
314                 /* Try all local users */
315                 strcpy(destuser, user);
316                 strcpy(desthost, config.c_nodename);
317                 strcpy(tnb.buffer1, user);
318                 strcpy(tnb.buffer2, "");
319                 ForEachUser(try_name, &tnb);
320                 if (strlen(tnb.buffer2) == 0) return(rfc822_no_such_user);
321                 strcpy(destuser, tnb.buffer2);
322                 return(rfc822_address_locally_validated);
323         }
324
325         strcpy(destuser, user);
326         strcpy(desthost, node);
327         return(rfc822_address_invalid); /* unknown error */
328 }
329
330
331
332
333 /*
334  * convert_field() is a helper function for convert_internet_message().
335  * Given start/end positions for an rfc822 field, it converts it to a Citadel
336  * field if it wants to, and unfolds it if necessary.
337  *
338  * Returns 1 if the field was converted and inserted into the Citadel message
339  * structure, implying that the source field should be removed from the
340  * message text.
341  */
342 int convert_field(struct CtdlMessage *msg, int beg, int end) {
343         char *rfc822;
344         char *key, *value;
345         int i;
346         int colonpos = (-1);
347         int processed = 0;
348         char buf[256];
349         char *user, *node, *name;
350
351         rfc822 = msg->cm_fields['M'];   /* M field contains rfc822 text */
352         for (i = end; i >= beg; --i) {
353                 if (rfc822[i] == ':') colonpos = i;
354         }
355
356         if (colonpos < 0) return(0);    /* no colon? not a valid header line */
357
358         key = mallok((end - beg) + 2);
359         safestrncpy(key, &rfc822[beg], (end-beg)+1);
360         key[colonpos - beg] = 0;
361         value = &key[(colonpos - beg) + 1];
362         unfold_rfc822_field(value);
363
364         lprintf(9, "Key=<%s> Value=<%s>\n", key, value);
365
366         /* Here's the big rfc822-to-citadel loop. */
367
368         if (!strcasecmp(key, "Date")) {
369                 sprintf(buf, "%ld", parsedate(value) );
370                 if (msg->cm_fields['T'] == NULL)
371                         msg->cm_fields['T'] = strdoop(buf);
372                 processed = 1;
373         }
374
375         else if (!strcasecmp(key, "From")) {
376                 user = mallok(1024);
377                 node = mallok(1024);
378                 name = mallok(1024);
379                 process_rfc822_addr(value, user, node, name);
380                 lprintf(9, "Converted to <%s@%s> (%s)\n", user, node, name);
381                 if (msg->cm_fields['A'] == NULL)
382                         msg->cm_fields['A'] = user;
383                 else
384                         phree(user);
385                 if (msg->cm_fields['N'] == NULL)
386                         msg->cm_fields['N'] = node;
387                 else
388                         phree(node);
389                 if (msg->cm_fields['H'] == NULL)
390                         msg->cm_fields['H'] = name;
391                 else
392                         phree(name);
393                 processed = 1;
394         }
395
396         else if (!strcasecmp(key, "Subject")) {
397                 if (msg->cm_fields['U'] == NULL)
398                         msg->cm_fields['U'] = strdoop(value);
399                 processed = 1;
400         }
401
402         /* Clean up and move on. */
403         phree(key);     /* Don't free 'value', it's actually the same buffer */
404         return(processed);
405 }
406
407
408 /*
409  * Convert an RFC822 message (headers + body) to a CtdlMessage structure.
410  */
411 struct CtdlMessage *convert_internet_message(char *rfc822) {
412
413         struct CtdlMessage *msg;
414         int pos, beg, end;
415         int done;
416         char buf[256];
417         int converted;
418
419         msg = mallok(sizeof(struct CtdlMessage));
420         if (msg == NULL) return msg;
421
422         memset(msg, 0, sizeof(struct CtdlMessage));
423         msg->cm_magic = CTDLMESSAGE_MAGIC;      /* self check */
424         msg->cm_anon_type = 0;                  /* never anonymous */
425         msg->cm_format_type = FMT_RFC822;       /* internet message */
426         msg->cm_fields['M'] = rfc822;
427
428         lprintf(9, "Unconverted RFC822 message length = %d\n", strlen(rfc822));
429         pos = 0;
430         done = 0;
431
432         while (!done) {
433
434                 /* Locate beginning and end of field, keeping in mind that
435                  * some fields might be multiline
436                  */
437                 beg = pos;
438                 end = (-1);
439                 for (pos=beg; ((pos<=strlen(rfc822))&&(end<0)); ++pos) {
440                         if ((rfc822[pos]=='\n')
441                            && (!isspace(rfc822[pos+1]))) {
442                                 end = pos;
443                         }
444                         if ( (rfc822[pos]=='\n')        /* done w. headers? */
445                            && ( (rfc822[pos+1]=='\n')
446                               ||(rfc822[pos+1]=='\r'))) {
447                                 end = pos;
448                                 done = 1;
449                         }
450
451                 }
452
453                 /* At this point we have a field.  Are we interested in it? */
454                 converted = convert_field(msg, beg, end);
455
456                 /******
457                 if (converted) {
458                         strcpy(&rfc822[beg], &rfc822[pos]);
459                         pos = beg;
460                 }
461                 ********/
462
463                 /* If we've hit the end of the message, bail out */
464                 if (pos > strlen(rfc822)) done = 1;
465         }
466
467         /* Follow-up sanity checks... */
468
469         /* If there's no timestamp on this message, set it to now. */
470         if (msg->cm_fields['T'] == NULL) {
471                 sprintf(buf, "%ld", time(NULL));
472                 msg->cm_fields['T'] = strdoop(buf);
473         }
474
475         lprintf(9, "RFC822 length remaining after conversion = %d\n",
476                 strlen(rfc822));
477         return msg;
478 }
479