2afd0f597d6448a7dcce123eca2a9a2e51b28dd0
[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-2002 / released under the GNU General Public License
8  */
9
10 /*
11  * Format of the "Exclusive ID" field of the message containing a user's
12  * vCard.  Doesn't matter what it really looks like as long as it's both
13  * unique and consistent (because we use it for replication checking to
14  * delete the old vCard network-wide when the user enters a new one).
15  */
16 #define VCARD_EXT_FORMAT        "Citadel vCard: personal card for %s at %s"
17
18
19 #include "sysdep.h"
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <stdio.h>
23 #include <fcntl.h>
24 #include <signal.h>
25 #include <pwd.h>
26 #include <errno.h>
27 #include <ctype.h>
28 #include <sys/types.h>
29
30 #if TIME_WITH_SYS_TIME
31 # include <sys/time.h>
32 # include <time.h>
33 #else
34 # if HAVE_SYS_TIME_H
35 #  include <sys/time.h>
36 # else
37 #  include <time.h>
38 # endif
39 #endif
40
41 #include <sys/wait.h>
42 #include <string.h>
43 #include <limits.h>
44 #include "citadel.h"
45 #include "server.h"
46 #include "sysdep_decls.h"
47 #include "citserver.h"
48 #include "support.h"
49 #include "config.h"
50 #include "control.h"
51 #include "serv_extensions.h"
52 #include "room_ops.h"
53 #include "user_ops.h"
54 #include "policy.h"
55 #include "database.h"
56 #include "msgbase.h"
57 #include "internet_addressing.h"
58 #include "tools.h"
59 #include "vcard.h"
60 #include "serv_ldap.h"
61
62 /*
63  * set global flag calling for an aide to validate new users
64  */
65 void set_mm_valid(void) {
66         begin_critical_section(S_CONTROL);
67         get_control();
68         CitControl.MMflags = CitControl.MMflags | MM_VALID ;
69         put_control();
70         end_critical_section(S_CONTROL);
71 }
72
73
74
75 /*
76  * Extract Internet e-mail addresses from a message containing a vCard, and
77  * perform a callback for any found.
78  */
79 void vcard_extract_internet_addresses(struct CtdlMessage *msg,
80                                 void (*callback)(char *, char *) ) {
81         struct vCard *v;
82         char *s;
83         char *addr;
84         char citadel_address[SIZ];
85         int instance = 0;
86         int found_something = 0;
87
88         if (msg->cm_fields['A'] == NULL) return;
89         if (msg->cm_fields['N'] == NULL) return;
90         snprintf(citadel_address, sizeof citadel_address, "%s @ %s",
91                 msg->cm_fields['A'], msg->cm_fields['N']);
92
93         v = vcard_load(msg->cm_fields['M']);
94         if (v == NULL) return;
95
96         /* Go through the vCard searching for *all* instances of
97          * the "email;internet" key
98          */
99         do {
100                 s = vcard_get_prop(v, "email;internet", 0, instance++, 0);
101                 if (s != NULL) {
102                         addr = strdup(s);
103                         striplt(addr);
104                         if (strlen(addr) > 0) {
105                                 if (callback != NULL) {
106                                         callback(addr, citadel_address);
107                                 }
108                         }
109                         free(addr);
110                         found_something = 1;
111                 }
112                 else {
113                         found_something = 0;
114                 }
115         } while(found_something);
116
117         vcard_free(v);
118 }
119
120
121
122 /*
123  * Callback for vcard_add_to_directory()
124  * (Lotsa ugly nested callbacks.  Oh well.)
125  */
126 void vcard_directory_add_user(char *internet_addr, char *citadel_addr) {
127         char buf[SIZ];
128
129         /* We have to validate that we're not stepping on someone else's
130          * email address ... but only if we're logged in.  Otherwise it's
131          * probably just the networker or something.
132          */
133         if (CC->logged_in) {
134                 lprintf(CTDL_DEBUG, "Checking for <%s>...\n", internet_addr);
135                 if (CtdlDirectoryLookup(buf, internet_addr, sizeof buf) == 0) {
136                         if (strcasecmp(buf, citadel_addr)) {
137                                 /* This address belongs to someone else.
138                                  * Bail out silently without saving.
139                                  */
140                                 lprintf(CTDL_DEBUG, "DOOP!\n");
141                                 return;
142                         }
143                 }
144         }
145         lprintf(CTDL_INFO, "Adding %s (%s) to directory\n",
146                         citadel_addr, internet_addr);
147         CtdlDirectoryAddUser(internet_addr, citadel_addr);
148 }
149
150
151 /*
152  * Back end function for cmd_igab()
153  */
154 void vcard_add_to_directory(long msgnum, void *data) {
155         struct CtdlMessage *msg;
156
157         msg = CtdlFetchMessage(msgnum, 1);
158         if (msg != NULL) {
159                 vcard_extract_internet_addresses(msg, vcard_directory_add_user);
160         }
161
162 #ifdef HAVE_LDAP
163         ctdl_vcard_to_ldap(msg, V2L_WRITE);
164 #endif
165
166         CtdlFreeMessage(msg);
167 }
168
169
170 /*
171  * Initialize Global Adress Book
172  */
173 void cmd_igab(char *argbuf) {
174         char hold_rm[ROOMNAMELEN];
175
176         if (CtdlAccessCheck(ac_aide)) return;
177
178         strcpy(hold_rm, CC->room.QRname);       /* save current room */
179
180         if (getroom(&CC->room, ADDRESS_BOOK_ROOM) != 0) {
181                 getroom(&CC->room, hold_rm);
182                 cprintf("%d cannot get address book room\n", ERROR + ROOM_NOT_FOUND);
183                 return;
184         }
185
186         /* Empty the existing database first.
187          */
188         CtdlDirectoryInit();
189
190         /* We want *all* vCards in this room */
191         CtdlForEachMessage(MSGS_ALL, 0, "text/x-vcard",
192                 NULL, vcard_add_to_directory, NULL);
193
194         getroom(&CC->room, hold_rm);    /* return to saved room */
195         cprintf("%d Directory has been rebuilt.\n", CIT_OK);
196 }
197
198
199
200
201 /*
202  * See if there is a valid Internet address in a vCard to use for outbound
203  * Internet messages.  If there is, stick it in the buffer.
204  */
205 void extract_primary_inet_email(char *emailaddrbuf, size_t emailaddrbuf_len, struct vCard *v) {
206         char *s, *addr;
207         int continue_searching = 1;
208         int instance = 0;
209
210         /* Go through the vCard searching for *all* instances of
211          * the "email;internet" key
212          */
213         do {
214                 s = vcard_get_prop(v, "email;internet", 0, instance++, 0);
215                 if (s != NULL) {
216                         continue_searching = 1;
217                         addr = strdup(s);
218                         striplt(addr);
219                         if (strlen(addr) > 0) {
220                                 if (IsDirectory(addr)) {
221                                         continue_searching = 0;
222                                         safestrncpy(emailaddrbuf, addr,
223                                                 emailaddrbuf_len);
224                                 }
225                         }
226                         free(addr);
227                 }
228                 else {
229                         continue_searching = 0;
230                 }
231         } while(continue_searching);
232 }
233
234
235
236 /*
237  * This handler detects whether the user is attempting to save a new
238  * vCard as part of his/her personal configuration, and handles the replace
239  * function accordingly (delete the user's existing vCard in the config room
240  * and in the global address book).
241  */
242 int vcard_upload_beforesave(struct CtdlMessage *msg) {
243         char *ptr;
244         char *s;
245         int linelen;
246         char buf[SIZ];
247         struct ctdluser usbuf;
248         long what_user;
249         struct vCard *v = NULL;
250         char *ser = NULL;
251         int i = 0;
252         int yes_my_citadel_config = 0;
253         int yes_any_vcard_room = 0;
254
255         if (!CC->logged_in) return(0);  /* Only do this if logged in. */
256
257         /* Is this some user's "My Citadel Config" room? */
258         if ( (CC->room.QRflags && QR_MAILBOX)
259            && (!strcasecmp(&CC->room.QRname[11], USERCONFIGROOM)) ) {
260                 /* Yes, we want to do this */
261                 yes_my_citadel_config = 1;
262         }
263
264         /* Is this a room with an address book in it? */
265         if (CC->curr_view == VIEW_ADDRESSBOOK) {
266                 yes_any_vcard_room = 1;
267         }
268
269         /* If neither condition exists, don't run this hook. */
270         if ( (!yes_my_citadel_config) && (!yes_any_vcard_room) ) {
271                 return(0);
272         }
273
274         /* If this isn't a MIME message, don't bother. */
275         if (msg->cm_format_type != 4) return(0);
276
277         /* Ok, if we got this far, look into the situation further... */
278
279         ptr = msg->cm_fields['M'];
280         if (ptr == NULL) return(0);
281         while (ptr != NULL) {
282         
283                 linelen = strcspn(ptr, "\n");
284                 if (linelen == 0) return(0);    /* end of headers */    
285                 
286                 if (!strncasecmp(ptr, "Content-type: text/x-vcard", 26)) {
287
288
289                         if (yes_my_citadel_config) {
290                                 /* Bingo!  The user is uploading a new vCard, so
291                                  * delete the old one.  First, figure out which user
292                                  * is being re-registered...
293                                  */
294                                 what_user = atol(CC->room.QRname);
295         
296                                 if (what_user == CC->user.usernum) {
297                                         /* It's the logged in user.  That was easy. */
298                                         memcpy(&usbuf, &CC->user,
299                                                 sizeof(struct ctdluser) );
300                                 }
301                                 
302                                 else if (getuserbynumber(&usbuf, what_user) == 0) {
303                                         /* We fetched a valid user record */
304                                 }
305                         
306                                 else {
307                                         /* No user with that number! */
308                                         return(0);
309                                 }
310         
311                                 /* Delete the user's old vCard.  This would probably
312                                  * get taken care of by the replication check, but we
313                                  * want to make sure there is absolutely only one
314                                  * vCard in the user's config room at all times.
315                                  */
316                                 CtdlDeleteMessages(CC->room.QRname,
317                                                 0L, "text/x-vcard", 1);
318
319                                 /* Make the author of the message the name of the user.
320                                  */
321                                 if (msg->cm_fields['A'] != NULL) {
322                                         free(msg->cm_fields['A']);
323                                 }
324                                 msg->cm_fields['A'] = strdup(usbuf.fullname);
325                         }
326
327                         /* Manipulate the vCard data structure */
328                         v = vcard_load(msg->cm_fields['M']);
329                         if (v != NULL) {
330
331                                 /* Insert or replace RFC2739-compliant free/busy URL */
332                                 if (yes_my_citadel_config) {
333                                         sprintf(buf, "http://%s/%s.vfb",
334                                                 config.c_fqdn,
335                                                 usbuf.fullname);
336                                         for (i=0; i<strlen(buf); ++i) {
337                                                 if (buf[i] == ' ') buf[i] = '_';
338                                         }
339                                         vcard_set_prop(v, "FBURL;PREF", buf, 0);
340                                 }
341
342                                 /* If this is an address book room, and the vCard has
343                                  * no UID, then give it one.
344                                  */
345                                 if (yes_any_vcard_room) {
346                                         s = vcard_get_prop(v, "UID", 0, 0, 0);
347                                         if (s == NULL) {
348                                                 generate_uuid(buf);
349                                                 vcard_set_prop(v, "UID", buf, 0);
350                                         }
351                                 }
352
353                                 /* Enforce local UID policy if applicable */
354                                 if (yes_my_citadel_config) {
355                                         snprintf(buf, sizeof buf, VCARD_EXT_FORMAT,
356                                                 msg->cm_fields['A'], NODENAME);
357                                         vcard_set_prop(v, "UID", buf, 0);
358                                 }
359
360                                 /* 
361                                  * Set the EUID of the message to the UID of the vCard.
362                                  */
363                                 if (msg->cm_fields['E'] != NULL) free(msg->cm_fields['E']);
364                                 s = vcard_get_prop(v, "UID", 0, 0, 0);
365                                 if (s != NULL) {
366                                         msg->cm_fields['E'] = strdup(s);
367                                         if (msg->cm_fields['U'] == NULL) {
368                                                 msg->cm_fields['U'] = strdup(s);
369                                         }
370                                 }
371
372                                 /*
373                                  * Set the Subject to the name in the vCard.
374                                  */
375                                 s = vcard_get_prop(v, "FN", 0, 0, 0);
376                                 if (s == NULL) {
377                                         s = vcard_get_prop(v, "N", 0, 0, 0);
378                                 }
379                                 if (s != NULL) {
380                                         if (msg->cm_fields['U'] != NULL) {
381                                                 free(msg->cm_fields['U']);
382                                         }
383                                         msg->cm_fields['U'] = strdup(s);
384                                 }
385
386                                 /* Re-serialize it back into the msg body */
387                                 ser = vcard_serialize(v);
388                                 if (ser != NULL) {
389                                         msg->cm_fields['M'] = realloc(
390                                                 msg->cm_fields['M'],
391                                                 strlen(ser) + 1024
392                                         );
393                                         sprintf(msg->cm_fields['M'],
394                                                 "Content-type: text/x-vcard"
395                                                 "\r\n\r\n%s\r\n", ser);
396                                         free(ser);
397                                 }
398                                 vcard_free(v);
399                         }
400
401                         /* Now allow the save to complete. */
402                         return(0);
403                 }
404
405                 ptr = strchr((char *)ptr, '\n');
406                 if (ptr != NULL) ++ptr;
407         }
408
409         return(0);
410 }
411
412
413
414 /*
415  * This handler detects whether the user is attempting to save a new
416  * vCard as part of his/her personal configuration, and handles the replace
417  * function accordingly (copy the vCard from the config room to the global
418  * address book).
419  */
420 int vcard_upload_aftersave(struct CtdlMessage *msg) {
421         char *ptr;
422         int linelen;
423         long I;
424         struct vCard *v;
425
426         if (!CC->logged_in) return(0);  /* Only do this if logged in. */
427
428         /* If this isn't the configuration room, or if this isn't a MIME
429          * message, don't bother.
430          */
431         if (msg->cm_fields['O'] == NULL) return(0);
432         if (strcasecmp(msg->cm_fields['O'], USERCONFIGROOM)) return(0);
433         if (msg->cm_format_type != 4) return(0);
434
435         ptr = msg->cm_fields['M'];
436         if (ptr == NULL) return(0);
437         while (ptr != NULL) {
438         
439                 linelen = strcspn(ptr, "\n");
440                 if (linelen == 0) return(0);    /* end of headers */    
441                 
442                 if (!strncasecmp(ptr, "Content-type: text/x-vcard", 26)) {
443                         /* Bingo!  The user is uploading a new vCard, so
444                          * copy it to the Global Address Book room.
445                          */
446
447                         I = atol(msg->cm_fields['I']);
448                         if (I < 0L) return(0);
449
450                         /* Store our Internet return address in memory */
451                         v = vcard_load(msg->cm_fields['M']);
452                         extract_primary_inet_email(CC->cs_inet_email,
453                                                 sizeof CC->cs_inet_email, v);
454                         vcard_free(v);
455
456                         /* Put it in the Global Address Book room... */
457                         CtdlSaveMsgPointerInRoom(ADDRESS_BOOK_ROOM, I, 1, msg);
458
459                         /* ...and also in the directory database. */
460                         vcard_add_to_directory(I, NULL);
461
462                         /* Some sites want an Aide to be notified when a
463                          * user registers or re-registers...
464                          */
465                         set_mm_valid();
466
467                         /* ...which also means we need to flag the user */
468                         lgetuser(&CC->user, CC->curr_user);
469                         CC->user.flags |= (US_REGIS|US_NEEDVALID);
470                         lputuser(&CC->user);
471
472                         return(0);
473                 }
474
475                 ptr = strchr((char *)ptr, '\n');
476                 if (ptr != NULL) ++ptr;
477         }
478
479         return(0);
480 }
481
482
483
484 /*
485  * back end function used for callbacks
486  */
487 void vcard_gu_backend(long supplied_msgnum, void *userdata) {
488         long *msgnum;
489
490         msgnum = (long *) userdata;
491         *msgnum = supplied_msgnum;
492 }
493
494
495 /*
496  * If this user has a vcard on disk, read it into memory, otherwise allocate
497  * and return an empty vCard.
498  */
499 struct vCard *vcard_get_user(struct ctdluser *u) {
500         char hold_rm[ROOMNAMELEN];
501         char config_rm[ROOMNAMELEN];
502         struct CtdlMessage *msg;
503         struct vCard *v;
504         long VCmsgnum;
505
506         strcpy(hold_rm, CC->room.QRname);       /* save current room */
507         MailboxName(config_rm, sizeof config_rm, u, USERCONFIGROOM);
508
509         if (getroom(&CC->room, config_rm) != 0) {
510                 getroom(&CC->room, hold_rm);
511                 return vcard_new();
512         }
513
514         /* We want the last (and probably only) vcard in this room */
515         VCmsgnum = (-1);
516         CtdlForEachMessage(MSGS_LAST, 1, "text/x-vcard",
517                 NULL, vcard_gu_backend, (void *)&VCmsgnum );
518         getroom(&CC->room, hold_rm);    /* return to saved room */
519
520         if (VCmsgnum < 0L) return vcard_new();
521
522         msg = CtdlFetchMessage(VCmsgnum, 1);
523         if (msg == NULL) return vcard_new();
524
525         v = vcard_load(msg->cm_fields['M']);
526         CtdlFreeMessage(msg);
527         return v;
528 }
529
530
531 /*
532  * Store this user's vCard in the appropriate place
533  */
534 /*
535  * Write our config to disk
536  */
537 void vcard_write_user(struct ctdluser *u, struct vCard *v) {
538         char temp[PATH_MAX];
539         FILE *fp;
540         char *ser;
541
542         CtdlMakeTempFileName(temp, sizeof temp);
543         ser = vcard_serialize(v);
544
545         fp = fopen(temp, "w");
546         if (fp == NULL) return;
547         if (ser == NULL) {
548                 fprintf(fp, "begin:vcard\r\nend:vcard\r\n");
549         } else {
550                 fwrite(ser, strlen(ser), 1, fp);
551                 free(ser);
552         }
553         fclose(fp);
554
555         /* This handy API function does all the work for us.
556          * NOTE: normally we would want to set that last argument to 1, to
557          * force the system to delete the user's old vCard.  But it doesn't
558          * have to, because the vcard_upload_beforesave() hook above
559          * is going to notice what we're trying to do, and delete the old vCard.
560          */
561         CtdlWriteObject(USERCONFIGROOM, /* which room */
562                         "text/x-vcard", /* MIME type */
563                         temp,           /* temp file */
564                         u,              /* which user */
565                         0,              /* not binary */
566                         0,              /* don't delete others of this type */
567                         0);             /* no flags */
568
569         unlink(temp);
570 }
571
572
573
574 /*
575  * Old style "enter registration info" command.  This function simply honors
576  * the REGI protocol command, translates the entered parameters into a vCard,
577  * and enters the vCard into the user's configuration.
578  */
579 void cmd_regi(char *argbuf) {
580         int a,b,c;
581         char buf[SIZ];
582         struct vCard *my_vcard;
583
584         char tmpaddr[SIZ];
585         char tmpcity[SIZ];
586         char tmpstate[SIZ];
587         char tmpzip[SIZ];
588         char tmpaddress[SIZ];
589         char tmpcountry[SIZ];
590
591         unbuffer_output();
592
593         if (!(CC->logged_in)) {
594                 cprintf("%d Not logged in.\n",ERROR + NOT_LOGGED_IN);
595                 return;
596         }
597
598         my_vcard = vcard_get_user(&CC->user);
599         strcpy(tmpaddr, "");
600         strcpy(tmpcity, "");
601         strcpy(tmpstate, "");
602         strcpy(tmpzip, "");
603         strcpy(tmpcountry, "USA");
604
605         cprintf("%d Send registration...\n", SEND_LISTING);
606         a=0;
607         while (client_getln(buf, sizeof buf), strcmp(buf,"000")) {
608                 if (a==0) vcard_set_prop(my_vcard, "n", buf, 0);
609                 if (a==1) strcpy(tmpaddr, buf);
610                 if (a==2) strcpy(tmpcity, buf);
611                 if (a==3) strcpy(tmpstate, buf);
612                 if (a==4) {
613                         for (c=0; c<strlen(buf); ++c) {
614                                 if ((buf[c]>='0') && (buf[c]<='9')) {
615                                         b = strlen(tmpzip);
616                                         tmpzip[b] = buf[c];
617                                         tmpzip[b+1] = 0;
618                                 }
619                         }
620                 }
621                 if (a==5) vcard_set_prop(my_vcard, "tel;home", buf, 0);
622                 if (a==6) vcard_set_prop(my_vcard, "email;internet", buf, 0);
623                 if (a==7) strcpy(tmpcountry, buf);
624                 ++a;
625         }
626
627         snprintf(tmpaddress, sizeof tmpaddress, ";;%s;%s;%s;%s;%s",
628                 tmpaddr, tmpcity, tmpstate, tmpzip, tmpcountry);
629         vcard_set_prop(my_vcard, "adr", tmpaddress, 0);
630         vcard_write_user(&CC->user, my_vcard);
631         vcard_free(my_vcard);
632 }
633
634
635 /*
636  * Protocol command to fetch registration info for a user
637  */
638 void cmd_greg(char *argbuf)
639 {
640         struct ctdluser usbuf;
641         struct vCard *v;
642         char *s;
643         char who[USERNAME_SIZE];
644         char adr[256];
645         char buf[256];
646
647         extract_token(who, argbuf, 0, '|', sizeof who);
648
649         if (!(CC->logged_in)) {
650                 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
651                 return;
652         }
653
654         if (!strcasecmp(who,"_SELF_")) strcpy(who,CC->curr_user);
655
656         if ((CC->user.axlevel < 6) && (strcasecmp(who,CC->curr_user))) {
657                 cprintf("%d Higher access required.\n",
658                         ERROR + HIGHER_ACCESS_REQUIRED);
659                 return;
660         }
661
662         if (getuser(&usbuf, who) != 0) {
663                 cprintf("%d '%s' not found.\n", ERROR + NO_SUCH_USER, who);
664                 return;
665         }
666
667         v = vcard_get_user(&usbuf);
668
669         cprintf("%d %s\n", LISTING_FOLLOWS, usbuf.fullname);
670         cprintf("%ld\n", usbuf.usernum);
671         cprintf("%s\n", usbuf.password);
672         s = vcard_get_prop(v, "n", 0, 0, 0);
673         cprintf("%s\n", s ? s : " ");   /* name */
674
675         s = vcard_get_prop(v, "adr", 0, 0, 0);
676         snprintf(adr, sizeof adr, "%s", s ? s : " ");/* address... */
677
678         extract_token(buf, adr, 2, ';', sizeof buf);
679         cprintf("%s\n", buf);                           /* street */
680         extract_token(buf, adr, 3, ';', sizeof buf);
681         cprintf("%s\n", buf);                           /* city */
682         extract_token(buf, adr, 4, ';', sizeof buf);
683         cprintf("%s\n", buf);                           /* state */
684         extract_token(buf, adr, 5, ';', sizeof buf);
685         cprintf("%s\n", buf);                           /* zip */
686
687         s = vcard_get_prop(v, "tel;home", 0, 0, 0);
688         if (s == NULL) s = vcard_get_prop(v, "tel", 1, 0, 0);
689         if (s != NULL) {
690                 cprintf("%s\n", s);
691         }
692         else {
693                 cprintf(" \n");
694         }
695
696         cprintf("%d\n", usbuf.axlevel);
697
698         s = vcard_get_prop(v, "email;internet", 0, 0, 0);
699         cprintf("%s\n", s ? s : " ");
700         s = vcard_get_prop(v, "adr", 0, 0, 0);
701         snprintf(adr, sizeof adr, "%s", s ? s : " ");/* address... */
702
703         extract_token(buf, adr, 6, ';', sizeof buf);
704         cprintf("%s\n", buf);                           /* country */
705         cprintf("000\n");
706 }
707
708
709
710 /*
711  * When a user is being created, create his/her vCard.
712  */
713 void vcard_newuser(struct ctdluser *usbuf) {
714         char vname[256];
715         char buf[256];
716         int i;
717         struct vCard *v;
718
719         vcard_fn_to_n(vname, usbuf->fullname, sizeof vname);
720         lprintf(CTDL_DEBUG, "Converted <%s> to <%s>\n", usbuf->fullname, vname);
721
722         /* Create and save the vCard */
723         v = vcard_new();
724         if (v == NULL) return;
725         sprintf(buf, "%s@%s", usbuf->fullname, config.c_fqdn);
726         for (i=0; i<strlen(buf); ++i) {
727                 if (buf[i] == ' ') buf[i] = '_';
728         }
729         vcard_add_prop(v, "fn", usbuf->fullname);
730         vcard_add_prop(v, "n", vname);
731         vcard_add_prop(v, "adr", "adr:;;_;_;_;00000;__");
732         vcard_add_prop(v, "email;internet", buf);
733         vcard_write_user(usbuf, v);
734         vcard_free(v);
735 }
736
737
738 /*
739  * When a user is being deleted, we have to remove his/her vCard.
740  * This is accomplished by issuing a message with 'CANCEL' in the S (special)
741  * field, and the same Exclusive ID as the existing card.
742  */
743 void vcard_purge(struct ctdluser *usbuf) {
744         struct CtdlMessage *msg;
745         char buf[SIZ];
746
747         msg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
748         if (msg == NULL) return;
749         memset(msg, 0, sizeof(struct CtdlMessage));
750
751         msg->cm_magic = CTDLMESSAGE_MAGIC;
752         msg->cm_anon_type = MES_NORMAL;
753         msg->cm_format_type = 0;
754         msg->cm_fields['A'] = strdup(usbuf->fullname);
755         msg->cm_fields['O'] = strdup(ADDRESS_BOOK_ROOM);
756         msg->cm_fields['N'] = strdup(NODENAME);
757         msg->cm_fields['M'] = strdup("Purge this vCard\n");
758
759         snprintf(buf, sizeof buf, VCARD_EXT_FORMAT,
760                         msg->cm_fields['A'], NODENAME);
761         msg->cm_fields['E'] = strdup(buf);
762
763         msg->cm_fields['S'] = strdup("CANCEL");
764
765         CtdlSubmitMsg(msg, NULL, ADDRESS_BOOK_ROOM);
766         CtdlFreeMessage(msg);
767 }
768
769
770 /*
771  * Grab vCard directory stuff out of incoming network messages
772  */
773 int vcard_extract_from_network(struct CtdlMessage *msg, char *target_room) {
774         char *ptr;
775         int linelen;
776
777         if (msg == NULL) return(0);
778
779         if (strcasecmp(target_room, ADDRESS_BOOK_ROOM)) {
780                 return(0);
781         }
782
783         if (msg->cm_format_type != 4) return(0);
784
785         ptr = msg->cm_fields['M'];
786         if (ptr == NULL) return(0);
787         while (ptr != NULL) {
788         
789                 linelen = strcspn(ptr, "\n");
790                 if (linelen == 0) return(0);    /* end of headers */    
791                 
792                 if (!strncasecmp(ptr, "Content-type: text/x-vcard", 26)) {
793                          /* It's a vCard.  Add it to the directory. */
794                         vcard_extract_internet_addresses(msg,
795                                                         CtdlDirectoryAddUser);
796                         return(0);
797                 }
798
799                 ptr = strchr((char *)ptr, '\n');
800                 if (ptr != NULL) ++ptr;
801         }
802
803         return(0);
804 }
805
806
807
808 /* 
809  * When a vCard is being removed from the Global Address Book room, remove it
810  * from the directory as well.
811  */
812 void vcard_delete_remove(char *room, long msgnum) {
813         struct CtdlMessage *msg;
814         char *ptr;
815         int linelen;
816
817         if (msgnum <= 0L) return;
818
819         if (strcasecmp(room, ADDRESS_BOOK_ROOM)) {
820                 return;
821         }
822
823         msg = CtdlFetchMessage(msgnum, 1);
824         if (msg == NULL) return;
825
826         ptr = msg->cm_fields['M'];
827         if (ptr == NULL) goto EOH;
828         while (ptr != NULL) {
829                 linelen = strcspn(ptr, "\n");
830                 if (linelen == 0) goto EOH;
831                 
832                 if (!strncasecmp(ptr, "Content-type: text/x-vcard", 26)) {
833                         /* Bingo!  A vCard is being deleted.
834                         */
835                         vcard_extract_internet_addresses(msg,
836                                                         CtdlDirectoryDelUser);
837 #ifdef HAVE_LDAP
838                         ctdl_vcard_to_ldap(msg, V2L_DELETE);
839 #endif
840                 }
841                 ptr = strchr((char *)ptr, '\n');
842                 if (ptr != NULL) ++ptr;
843         }
844
845 EOH:    CtdlFreeMessage(msg);
846 }
847
848
849
850 /*
851  * Query Directory
852  */
853 void cmd_qdir(char *argbuf) {
854         char citadel_addr[256];
855         char internet_addr[256];
856
857         if (CtdlAccessCheck(ac_logged_in)) return;
858
859         extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
860
861         if (CtdlDirectoryLookup(citadel_addr, internet_addr, sizeof citadel_addr) != 0) {
862                 cprintf("%d %s was not found.\n",
863                         ERROR + NO_SUCH_USER, internet_addr);
864                 return;
865         }
866
867         cprintf("%d %s\n", CIT_OK, citadel_addr);
868 }
869
870
871 /*
872  * We don't know if the Contacts room exists so we just create it at login
873  */
874 void vcard_create_room(void)
875 {
876         struct ctdlroom qr;
877         struct visit vbuf;
878
879         /* Create the calendar room if it doesn't already exist */
880         create_room(USERCONTACTSROOM, 4, "", 0, 1, 0, VIEW_ADDRESSBOOK);
881
882         /* Set expiration policy to manual; otherwise objects will be lost! */
883         if (lgetroom(&qr, USERCONTACTSROOM)) {
884                 lprintf(CTDL_ERR, "Couldn't get the user CONTACTS room!\n");
885                 return;
886         }
887         qr.QRep.expire_mode = EXPIRE_MANUAL;
888         qr.QRdefaultview = VIEW_ADDRESSBOOK;    /* 2 = address book view */
889         lputroom(&qr);
890
891         /* Set the view to a calendar view */
892         CtdlGetRelationship(&vbuf, &CC->user, &qr);
893         vbuf.v_view = 2;        /* 2 = address book view */
894         CtdlSetRelationship(&vbuf, &CC->user, &qr);
895
896         return;
897 }
898
899
900
901
902 /*
903  * When a user logs in...
904  */
905 void vcard_session_login_hook(void) {
906         struct vCard *v;
907
908         v = vcard_get_user(&CC->user);
909         extract_primary_inet_email(CC->cs_inet_email,
910                                 sizeof CC->cs_inet_email, v);
911         vcard_free(v);
912
913         vcard_create_room();
914 }
915
916
917 /* 
918  * Turn an arbitrary RFC822 address into a struct vCard for possible
919  * inclusion into an address book.
920  */
921 struct vCard *vcard_new_from_rfc822_addr(char *addr) {
922         struct vCard *v;
923         char user[256], node[256], name[256], email[256], n[256], uid[256];
924         int i;
925
926         v = vcard_new();
927         if (v == NULL) return(NULL);
928
929         process_rfc822_addr(addr, user, node, name);
930         vcard_set_prop(v, "fn", name, 0);
931
932         vcard_fn_to_n(n, name, sizeof n);
933         vcard_set_prop(v, "n", n, 0);
934
935         snprintf(email, sizeof email, "%s@%s", user, node);
936         vcard_set_prop(v, "email;internet", email, 0);
937
938         snprintf(uid, sizeof uid, "collected: %s %s@%s", name, user, node);
939         for (i=0; i<strlen(uid); ++i) {
940                 if (isspace(uid[i])) uid[i] = '_';
941                 uid[i] = tolower(uid[i]);
942         }
943         vcard_set_prop(v, "UID", uid, 0);
944
945         return(v);
946 }
947
948
949
950 /*
951  * This is called by store_harvested_addresses() to remove from the
952  * list any addresses we already have in our address book.
953  */
954 void strip_addresses_already_have(long msgnum, void *userdata) {
955         char *collected_addresses;
956         struct CtdlMessage *msg;
957         struct vCard *v;
958         char *value = NULL;
959         int i, j;
960         char addr[256], user[256], node[256], name[256];
961
962         collected_addresses = (char *)userdata;
963
964         msg = CtdlFetchMessage(msgnum, 1);
965         if (msg == NULL) return;
966         v = vcard_load(msg->cm_fields['M']);
967         CtdlFreeMessage(msg);
968
969         i = 0;
970         while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
971
972                 for (j=0; j<num_tokens(collected_addresses, ','); ++j) {
973                         extract_token(addr, collected_addresses, j, ',', sizeof addr);
974
975                         /* Remove the address if we already have it! */
976                         process_rfc822_addr(addr, user, node, name);
977                         snprintf(addr, sizeof addr, "%s@%s", user, node);
978                         if (!strcasecmp(value, addr)) {
979                                 remove_token(collected_addresses, j, ',');
980                         }
981                 }
982
983         }
984
985         vcard_free(v);
986 }
987
988
989
990 /*
991  * Back end function for store_harvested_addresses()
992  */
993 void store_this_ha(struct addresses_to_be_filed *aptr) {
994         struct CtdlMessage *vmsg = NULL;
995         long vmsgnum = (-1L);
996         char *ser = NULL;
997         struct vCard *v = NULL;
998         char recipient[256];
999         int i;
1000
1001         /* First remove any addresses we already have in the address book */
1002         usergoto(aptr->roomname, 0, 0, NULL, NULL);
1003         CtdlForEachMessage(MSGS_ALL, 0, "text/x-vcard", NULL,
1004                 strip_addresses_already_have, aptr->collected_addresses);
1005
1006         if (strlen(aptr->collected_addresses) > 0)
1007            for (i=0; i<num_tokens(aptr->collected_addresses, ','); ++i) {
1008
1009                 /* Make a vCard out of each address */
1010                 extract_token(recipient, aptr->collected_addresses, i, ',', sizeof recipient);
1011                 striplt(recipient);
1012                 v = vcard_new_from_rfc822_addr(recipient);
1013                 if (v != NULL) {
1014                         vmsg = malloc(sizeof(struct CtdlMessage));
1015                         memset(vmsg, 0, sizeof(struct CtdlMessage));
1016                         vmsg->cm_magic = CTDLMESSAGE_MAGIC;
1017                         vmsg->cm_anon_type = MES_NORMAL;
1018                         vmsg->cm_format_type = FMT_RFC822;
1019                         vmsg->cm_fields['A'] = strdup("Citadel");
1020                         vmsg->cm_fields['E'] =  strdup(vcard_get_prop(v, "UID", 0, 0, 0));
1021                         ser = vcard_serialize(v);
1022                         if (ser != NULL) {
1023                                 vmsg->cm_fields['M'] = malloc(strlen(ser) + 1024);
1024                                 sprintf(vmsg->cm_fields['M'],
1025                                         "Content-type: text/x-vcard"
1026                                         "\r\n\r\n%s\r\n", ser);
1027                                 free(ser);
1028                         }
1029                         vcard_free(v);
1030
1031                         lprintf(CTDL_DEBUG, "Adding contact: %s\n", recipient);
1032                         vmsgnum = CtdlSubmitMsg(vmsg, NULL, aptr->roomname);
1033                         CtdlFreeMessage(vmsg);
1034                 }
1035         }
1036
1037         free(aptr->roomname);
1038         free(aptr->collected_addresses);
1039         free(aptr);
1040 }
1041
1042
1043 /*
1044  * When a user sends a message, we may harvest one or more email addresses
1045  * from the recipient list to be added to the user's address book.  But we
1046  * want to do this asynchronously so it doesn't keep the user waiting.
1047  */
1048 void store_harvested_addresses(void) {
1049
1050         struct addresses_to_be_filed *aptr = NULL;
1051
1052         if (atbf == NULL) return;
1053
1054         begin_critical_section(S_ATBF);
1055         while (atbf != NULL) {
1056                 aptr = atbf;
1057                 atbf = atbf->next;
1058                 end_critical_section(S_ATBF);
1059                 store_this_ha(aptr);
1060                 begin_critical_section(S_ATBF);
1061         }
1062         end_critical_section(S_ATBF);
1063 }
1064
1065
1066 /* 
1067  * Function to output a vCard as plain text.  Nobody uses MSG0 anymore, so
1068  * really this is just so we expose the vCard data to the full text indexer.
1069  */
1070 void vcard_fixed_output(char *ptr, int len) {
1071         char *serialized_vcard;
1072         struct vCard *v;
1073         char *key, *value;
1074         int i = 0;
1075
1076         cprintf("vCard:\n");
1077         serialized_vcard = malloc(len + 1);
1078         safestrncpy(serialized_vcard, ptr, len+1);
1079         v = vcard_load(serialized_vcard);
1080         free(serialized_vcard);
1081
1082         i = 0;
1083         while (key = vcard_get_prop(v, "", 0, i, 1), key != NULL) {
1084                 value = vcard_get_prop(v, "", 0, i++, 0);
1085                 cprintf("%20s : %s\n", key, value);
1086         }
1087
1088         vcard_free(v);
1089 }
1090
1091
1092
1093 char *serv_vcard_init(void)
1094 {
1095         struct ctdlroom qr;
1096
1097         CtdlRegisterSessionHook(vcard_session_login_hook, EVT_LOGIN);
1098         CtdlRegisterMessageHook(vcard_upload_beforesave, EVT_BEFORESAVE);
1099         CtdlRegisterMessageHook(vcard_upload_aftersave, EVT_AFTERSAVE);
1100         CtdlRegisterDeleteHook(vcard_delete_remove);
1101         CtdlRegisterProtoHook(cmd_regi, "REGI", "Enter registration info");
1102         CtdlRegisterProtoHook(cmd_greg, "GREG", "Get registration info");
1103         CtdlRegisterProtoHook(cmd_igab, "IGAB",
1104                                         "Initialize Global Address Book");
1105         CtdlRegisterProtoHook(cmd_qdir, "QDIR", "Query Directory");
1106         CtdlRegisterUserHook(vcard_newuser, EVT_NEWUSER);
1107         CtdlRegisterUserHook(vcard_purge, EVT_PURGEUSER);
1108         CtdlRegisterNetprocHook(vcard_extract_from_network);
1109         CtdlRegisterSessionHook(store_harvested_addresses, EVT_TIMER);
1110         CtdlRegisterFixedOutputHook("text/x-vcard", vcard_fixed_output);
1111
1112         /* Create the Global ADdress Book room if necessary */
1113         create_room(ADDRESS_BOOK_ROOM, 3, "", 0, 1, 0, VIEW_ADDRESSBOOK);
1114
1115         /* Set expiration policy to manual; otherwise objects will be lost! */
1116         if (!lgetroom(&qr, ADDRESS_BOOK_ROOM)) {
1117                 qr.QRep.expire_mode = EXPIRE_MANUAL;
1118                 qr.QRdefaultview = VIEW_ADDRESSBOOK;    /* 2 = address book view */
1119                 lputroom(&qr);
1120         }
1121
1122         return "$Id$";
1123 }