* More code for the Global Address Book
[citadel.git] / citadel / serv_vcard.c
1 /*
2  * $Id$
3  * 
4  * A server-side module for Citadel which supports address book information
5  * using the standard vCard format.
6  * 
7  * Copyright (c) 1999-2001 / released under the GNU General Public License
8  */
9
10 /*
11  * Where we keep messages containing the vCards that source our directory.  It
12  * makes no sense to change this, because you'd have to change it on every
13  * system on the network.  That would be stupid.
14  */
15 #define ADDRESS_BOOK_ROOM       "Global Address Book"
16
17 /*
18  * Format of the "Extended ID" field of the message containing a user's
19  * vCard.  Doesn't matter what it really looks like as long as it's both
20  * unique and consistent (because we use it for replication checking to
21  * delete the old vCard network-wide when the user enters a new one).
22  */
23 #define VCARD_EXT_FORMAT        "Citadel vCard: personal card for %s at %s"
24
25
26 #include "sysdep.h"
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <stdio.h>
30 #include <fcntl.h>
31 #include <signal.h>
32 #include <pwd.h>
33 #include <errno.h>
34 #include <sys/types.h>
35
36 #if TIME_WITH_SYS_TIME
37 # include <sys/time.h>
38 # include <time.h>
39 #else
40 # if HAVE_SYS_TIME_H
41 #  include <sys/time.h>
42 # else
43 #  include <time.h>
44 # endif
45 #endif
46
47 #include <sys/wait.h>
48 #include <string.h>
49 #include <limits.h>
50 #include "citadel.h"
51 #include "server.h"
52 #include "sysdep_decls.h"
53 #include "citserver.h"
54 #include "support.h"
55 #include "config.h"
56 #include "control.h"
57 #include "dynloader.h"
58 #include "room_ops.h"
59 #include "user_ops.h"
60 #include "policy.h"
61 #include "database.h"
62 #include "msgbase.h"
63 #include "internet_addressing.h"
64 #include "tools.h"
65 #include "vcard.h"
66
67 struct vcard_internal_info {
68         long msgnum;
69 };
70
71 /* Message number symbol used internally by these functions */
72 unsigned long SYM_VCARD;
73 #define VC ((struct vcard_internal_info *)CtdlGetUserData(SYM_VCARD))
74
75
76 /*
77  * Extract Internet e-mail addresses from a message containing a vCard, and
78  * perform a callback for any found.
79  */
80 void vcard_extract_internet_addresses(struct CtdlMessage *msg,
81                                 void (*callback)(char *, char *) ) {
82         struct vCard *v;
83         char *s;
84         char *addr;
85         char citadel_address[SIZ];
86
87         if (msg->cm_fields['A'] == NULL) return;
88         if (msg->cm_fields['N'] == NULL) return;
89         sprintf(citadel_address, "%s @ %s",
90                 msg->cm_fields['A'], msg->cm_fields['N']);
91
92         v = vcard_load(msg->cm_fields['M']);
93         if (v == NULL) return;
94
95         s = vcard_get_prop(v, "email;internet", 0); /* FIXME handle multiples */
96         if (s != NULL) {
97                 addr = strdoop(s);
98                 striplt(addr);
99                 if (strlen(addr) > 0) {
100                         if (callback != NULL) {
101                                 callback(addr, citadel_address);
102                         }
103                 }
104                 phree(addr);
105         }
106
107         vcard_free(v);
108 }
109
110 /*
111  * Back end function for cmd_igab()
112  */
113 void vcard_add_to_directory(long msgnum, void *data) {
114         struct CtdlMessage *msg;
115
116         msg = CtdlFetchMessage(msgnum);
117         if (msg != NULL) {
118                 vcard_extract_internet_addresses(msg, CtdlDirectoryAddUser);
119         }
120
121         CtdlFreeMessage(msg);
122 }
123
124
125 /*
126  * Initialize Global Adress Book
127  */
128 void cmd_igab(char *argbuf) {
129         char hold_rm[ROOMNAMELEN];
130
131         if (CtdlAccessCheck(ac_aide)) return;
132
133         strcpy(hold_rm, CC->quickroom.QRname);  /* save current room */
134
135         if (getroom(&CC->quickroom, ADDRESS_BOOK_ROOM) != 0) {
136                 getroom(&CC->quickroom, hold_rm);
137                 cprintf("%d cannot get address book room\n", ERROR);
138                 return;
139         }
140
141         /* Empty the existing database first.
142          */
143         CtdlDirectoryInit();
144
145         /* We want the last (and probably only) vcard in this room */
146         CtdlForEachMessage(MSGS_ALL, 0, (-127), "text/x-vcard",
147                 NULL, vcard_add_to_directory, NULL);
148
149         getroom(&CC->quickroom, hold_rm);       /* return to saved room */
150         cprintf("%d Directory has been rebuilt.\n", OK);
151 }
152
153
154
155 /*
156  * This handler detects whether the user is attempting to save a new
157  * vCard as part of his/her personal configuration, and handles the replace
158  * function accordingly (delete the user's existing vCard in the config room
159  * and in the global address book).
160  */
161 int vcard_upload_beforesave(struct CtdlMessage *msg) {
162         char *ptr;
163         int linelen;
164         char config_rm[ROOMNAMELEN];
165         char buf[SIZ];
166
167         if (!CC->logged_in) return(0);  /* Only do this if logged in. */
168
169         /* If this isn't the configuration room, or if this isn't a MIME
170          * message, don't bother.  (Check for NULL room first, otherwise
171          * some messages will cause it to crash!!)
172          */
173         if (msg->cm_fields['O'] == NULL) return(0);
174         if (strcasecmp(msg->cm_fields['O'], USERCONFIGROOM)) return(0);
175         if (msg->cm_format_type != 4) return(0);
176
177         ptr = msg->cm_fields['M'];
178         if (ptr == NULL) return(0);
179         while (ptr != NULL) {
180         
181                 linelen = strcspn(ptr, "\n");
182                 if (linelen == 0) return(0);    /* end of headers */    
183                 
184                 if (!strncasecmp(ptr, "Content-type: text/x-vcard", 26)) {
185                         /* Bingo!  The user is uploading a new vCard, so
186                          * delete the old one.
187                          */
188
189                         /* Delete the user's old vCard.  This would probably
190                          * get taken care of by the replication check, but we
191                          * want to make sure there is absolutely only one
192                          * vCard in the user's config room at all times.
193                          * 
194                          * FIXME ... this needs to be tweaked to allow an admin
195                          * to make changes to another user's vCard instead of
196                          * assuming that it's always the user saving his own.
197                          */
198                         MailboxName(config_rm, &CC->usersupp, USERCONFIGROOM);
199                         CtdlDeleteMessages(config_rm, 0L, "text/x-vcard");
200
201                         /* Set the Extended-ID to a standardized one so the
202                          * replication always works correctly
203                          */
204                         if (msg->cm_fields['E'] != NULL)
205                                 phree(msg->cm_fields['E']);
206
207                         sprintf(buf, VCARD_EXT_FORMAT,
208                                 msg->cm_fields['A'], NODENAME);
209                         msg->cm_fields['E'] = strdoop(buf);
210
211                         /* Now allow the save to complete. */
212                         return(0);
213                 }
214
215                 ptr = strchr((char *)ptr, '\n');
216                 if (ptr != NULL) ++ptr;
217         }
218
219         return(0);
220 }
221
222
223
224 /*
225  * This handler detects whether the user is attempting to save a new
226  * vCard as part of his/her personal configuration, and handles the replace
227  * function accordingly (copy the vCard from the config room to the global
228  * address book).
229  */
230 int vcard_upload_aftersave(struct CtdlMessage *msg) {
231         char *ptr;
232         int linelen;
233         long I;
234
235         if (!CC->logged_in) return(0);  /* Only do this if logged in. */
236
237         /* If this isn't the configuration room, or if this isn't a MIME
238          * message, don't bother.
239          */
240         if (msg->cm_fields['O'] == NULL) return(0);
241         if (strcasecmp(msg->cm_fields['O'], USERCONFIGROOM)) return(0);
242         if (msg->cm_format_type != 4) return(0);
243
244         ptr = msg->cm_fields['M'];
245         if (ptr == NULL) return(0);
246         while (ptr != NULL) {
247         
248                 linelen = strcspn(ptr, "\n");
249                 if (linelen == 0) return(0);    /* end of headers */    
250                 
251                 if (!strncasecmp(ptr, "Content-type: text/x-vcard", 26)) {
252                         /* Bingo!  The user is uploading a new vCard, so
253                          * copy it to the Global Address Book room.
254                          */
255
256                         I = atol(msg->cm_fields['I']);
257                         if (I < 0L) return(0);
258
259                         /* Put it in the Global Address Book room... */
260                         CtdlSaveMsgPointerInRoom(ADDRESS_BOOK_ROOM, I,
261                                 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
262
263                         /* ...and also in the directory database. */
264                         vcard_add_to_directory(I, NULL);
265
266                         return(0);
267                 }
268
269                 ptr = strchr((char *)ptr, '\n');
270                 if (ptr != NULL) ++ptr;
271         }
272
273         return(0);
274 }
275
276
277
278 /*
279  * back end function used for callbacks
280  */
281 void vcard_gu_backend(long msgnum, void *userdata) {
282         VC->msgnum = msgnum;
283 }
284
285
286 /*
287  * If this user has a vcard on disk, read it into memory, otherwise allocate
288  * and return an empty vCard.
289  */
290 struct vCard *vcard_get_user(struct usersupp *u) {
291         char hold_rm[ROOMNAMELEN];
292         char config_rm[ROOMNAMELEN];
293         struct CtdlMessage *msg;
294         struct vCard *v;
295
296         strcpy(hold_rm, CC->quickroom.QRname);  /* save current room */
297         MailboxName(config_rm, u, USERCONFIGROOM);
298
299         if (getroom(&CC->quickroom, config_rm) != 0) {
300                 getroom(&CC->quickroom, hold_rm);
301                 return vcard_new();
302         }
303
304         /* We want the last (and probably only) vcard in this room */
305         VC->msgnum = (-1);
306         CtdlForEachMessage(MSGS_LAST, 1, (-127), "text/x-vcard",
307                 NULL, vcard_gu_backend, NULL);
308         getroom(&CC->quickroom, hold_rm);       /* return to saved room */
309
310         if (VC->msgnum < 0L) return vcard_new();
311
312         msg = CtdlFetchMessage(VC->msgnum);
313         if (msg == NULL) return vcard_new();
314
315         v = vcard_load(msg->cm_fields['M']);
316         CtdlFreeMessage(msg);
317         return v;
318 }
319
320
321 /*
322  * Store this user's vCard in the appropriate place
323  */
324 /*
325  * Write our config to disk
326  */
327 void vcard_write_user(struct usersupp *u, struct vCard *v) {
328         char temp[PATH_MAX];
329         FILE *fp;
330         char *ser;
331
332         strcpy(temp, tmpnam(NULL));
333         ser = vcard_serialize(v);
334
335         fp = fopen(temp, "w");
336         if (fp == NULL) return;
337         if (ser == NULL) {
338                 fprintf(fp, "begin:vcard\r\nend:vcard\r\n");
339         } else {
340                 fwrite(ser, strlen(ser), 1, fp);
341                 phree(ser);
342         }
343         fclose(fp);
344
345         /* This handy API function does all the work for us.
346          * NOTE: normally we would want to set that last argument to 1, to
347          * force the system to delete the user's old vCard.  But it doesn't
348          * have to, because the vcard_upload_beforesave() hook above
349          * is going to notice what we're trying to do, and delete the old vCard.
350          */
351         CtdlWriteObject(USERCONFIGROOM, /* which room */
352                         "text/x-vcard", /* MIME type */
353                         temp,           /* temp file */
354                         u,              /* which user */
355                         0,              /* not binary */
356                         0,              /* don't delete others of this type */
357                         0);             /* no flags */
358
359         unlink(temp);
360 }
361
362
363
364 /*
365  * Old style "enter registration info" command.  This function simply honors
366  * the REGI protocol command, translates the entered parameters into a vCard,
367  * and enters the vCard into the user's configuration.
368  */
369 void cmd_regi(char *argbuf) {
370         int a,b,c;
371         char buf[SIZ];
372         struct vCard *my_vcard;
373
374         char tmpaddr[SIZ];
375         char tmpcity[SIZ];
376         char tmpstate[SIZ];
377         char tmpzip[SIZ];
378         char tmpaddress[SIZ];
379         char tmpcountry[SIZ];
380
381         if (!(CC->logged_in)) {
382                 cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
383                 return;
384         }
385
386         my_vcard = vcard_get_user(&CC->usersupp);
387         strcpy(tmpaddr, "");
388         strcpy(tmpcity, "");
389         strcpy(tmpstate, "");
390         strcpy(tmpzip, "");
391         strcpy(tmpcountry, "USA");
392
393         cprintf("%d Send registration...\n", SEND_LISTING);
394         a=0;
395         while (client_gets(buf), strcmp(buf,"000")) {
396                 if (a==0) vcard_set_prop(my_vcard, "n", buf);
397                 if (a==1) strcpy(tmpaddr, buf);
398                 if (a==2) strcpy(tmpcity, buf);
399                 if (a==3) strcpy(tmpstate, buf);
400                 if (a==4) {
401                         for (c=0; c<strlen(buf); ++c) {
402                                 if ((buf[c]>='0') && (buf[c]<='9')) {
403                                         b = strlen(tmpzip);
404                                         tmpzip[b] = buf[c];
405                                         tmpzip[b+1] = 0;
406                                 }
407                         }
408                 }
409                 if (a==5) vcard_set_prop(my_vcard, "tel;home", buf);
410                 if (a==6) vcard_set_prop(my_vcard, "email;internet", buf);
411                 if (a==7) strcpy(tmpcountry, buf);
412                 ++a;
413         }
414
415         sprintf(tmpaddress, ";;%s;%s;%s;%s;%s",
416                 tmpaddr, tmpcity, tmpstate, tmpzip, tmpcountry);
417         vcard_set_prop(my_vcard, "adr", tmpaddress);
418         vcard_write_user(&CC->usersupp, my_vcard);
419         vcard_free(my_vcard);
420
421         lgetuser(&CC->usersupp, CC->curr_user);
422         CC->usersupp.flags=(CC->usersupp.flags|US_REGIS|US_NEEDVALID);
423         lputuser(&CC->usersupp);
424
425         /* set global flag calling for validation */
426         begin_critical_section(S_CONTROL);
427         get_control();
428         CitControl.MMflags = CitControl.MMflags | MM_VALID ;
429         put_control();
430         end_critical_section(S_CONTROL);
431 }
432
433
434
435 /*
436  * Protocol command to fetch registration info for a user
437  */
438 void cmd_greg(char *argbuf)
439 {
440         struct usersupp usbuf;
441         struct vCard *v;
442         char *s;
443         char who[SIZ];
444         char adr[SIZ];
445         char buf[SIZ];
446
447         extract(who, argbuf, 0);
448
449         if (!(CC->logged_in)) {
450                 cprintf("%d Not logged in.\n", ERROR+NOT_LOGGED_IN);
451                 return;
452         }
453
454         if (!strcasecmp(who,"_SELF_")) strcpy(who,CC->curr_user);
455
456         if ((CC->usersupp.axlevel < 6) && (strcasecmp(who,CC->curr_user))) {
457                 cprintf("%d Higher access required.\n",
458                         ERROR+HIGHER_ACCESS_REQUIRED);
459                 return;
460         }
461
462         if (getuser(&usbuf, who) != 0) {
463                 cprintf("%d '%s' not found.\n", ERROR+NO_SUCH_USER, who);
464                 return;
465         }
466
467         v = vcard_get_user(&usbuf);
468
469         cprintf("%d %s\n", LISTING_FOLLOWS, usbuf.fullname);
470         cprintf("%ld\n", usbuf.usernum);
471         cprintf("%s\n", usbuf.password);
472         s = vcard_get_prop(v, "n", 0);
473         cprintf("%s\n", s ? s : " ");   /* name */
474
475         s = vcard_get_prop(v, "adr", 0);
476         sprintf(adr, "%s", s ? s : " ");/* address... */
477
478         extract_token(buf, adr, 2, ';');
479         cprintf("%s\n", buf);                           /* street */
480         extract_token(buf, adr, 3, ';');
481         cprintf("%s\n", buf);                           /* city */
482         extract_token(buf, adr, 4, ';');
483         cprintf("%s\n", buf);                           /* state */
484         extract_token(buf, adr, 5, ';');
485         cprintf("%s\n", buf);                           /* zip */
486
487         s = vcard_get_prop(v, "tel;home", 0);
488         if (s == NULL) s = vcard_get_prop(v, "tel", 1);
489         if (s != NULL) {
490                 cprintf("%s\n", s);
491         }
492         else {
493                 cprintf(" \n");
494         }
495
496         cprintf("%d\n", usbuf.axlevel);
497
498         s = vcard_get_prop(v, "email;internet", 0);
499         cprintf("%s\n", s ? s : " ");
500         s = vcard_get_prop(v, "adr", 0);
501         sprintf(adr, "%s", s ? s : " ");/* address... */
502
503         extract_token(buf, adr, 6, ';');
504         cprintf("%s\n", buf);                           /* country */
505         cprintf("000\n");
506 }
507
508
509 /*
510  * When a user is being deleted, we have to remove his/her vCard.
511  * This is accomplished by issuing a message with 'CANCEL' in the S (special)
512  * field, and the same Extended ID as the existing card.
513  */
514 void vcard_purge(char *username, long usernum) {
515         struct CtdlMessage *msg;
516         char buf[SIZ];
517
518         msg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
519         if (msg == NULL) return;
520         memset(msg, 0, sizeof(struct CtdlMessage));
521
522         msg->cm_magic = CTDLMESSAGE_MAGIC;
523         msg->cm_anon_type = MES_NORMAL;
524         msg->cm_format_type = 0;
525         msg->cm_fields['A'] = strdoop(username);
526         msg->cm_fields['O'] = strdoop(ADDRESS_BOOK_ROOM);
527         msg->cm_fields['N'] = strdoop(NODENAME);
528         msg->cm_fields['M'] = strdoop("Purge this vCard\n");
529
530         sprintf(buf, VCARD_EXT_FORMAT, msg->cm_fields['A'], NODENAME);
531         msg->cm_fields['E'] = strdoop(buf);
532
533         msg->cm_fields['S'] = strdoop("CANCEL");
534
535         CtdlSubmitMsg(msg, NULL, ADDRESS_BOOK_ROOM);
536         CtdlFreeMessage(msg);
537 }
538         
539         
540
541
542 /*
543  * Session startup, allocate some per-session data
544  */
545 void vcard_session_startup_hook(void) {
546         CtdlAllocUserData(SYM_VCARD, sizeof(struct vcard_internal_info));
547 }
548
549
550 char *Dynamic_Module_Init(void)
551 {
552         SYM_VCARD = CtdlGetDynamicSymbol();
553         CtdlRegisterSessionHook(vcard_session_startup_hook, EVT_START);
554         CtdlRegisterMessageHook(vcard_upload_beforesave, EVT_BEFORESAVE);
555         CtdlRegisterMessageHook(vcard_upload_aftersave, EVT_AFTERSAVE);
556         CtdlRegisterProtoHook(cmd_regi, "REGI", "Enter registration info");
557         CtdlRegisterProtoHook(cmd_greg, "GREG", "Get registration info");
558         CtdlRegisterProtoHook(cmd_igab, "IGAB",
559                                         "Initialize Global Address Book");
560         CtdlRegisterUserHook(vcard_purge, EVT_PURGEUSER);
561         create_room(ADDRESS_BOOK_ROOM, 3, "", 0, 1);
562         return "$Id$";
563 }