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