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