Made some ldap functions a little more generic, pass the ldap key in as
[citadel.git] / citadel / modules / vcard / 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-2007 / 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  * Citadel will accept either text/vcard or text/x-vcard as the MIME type
20  * for a vCard.  The following definition determines which one it *generates*
21  * when serializing.
22  */
23 #define VCARD_MIME_TYPE         "text/x-vcard"
24
25 #include "sysdep.h"
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <stdio.h>
29 #include <fcntl.h>
30 #include <signal.h>
31 #include <pwd.h>
32 #include <errno.h>
33 #include <ctype.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 "citserver.h"
53 #include "support.h"
54 #include "config.h"
55 #include "control.h"
56 #include "room_ops.h"
57 #include "user_ops.h"
58 #include "policy.h"
59 #include "database.h"
60 #include "msgbase.h"
61 #include "internet_addressing.h"
62 #include "tools.h"
63 #include "mime_parser.h"
64 #include "vcard.h"
65 #include "serv_vcard.h"
66
67 #include "ctdl_module.h"
68
69
70
71 /*
72  * set global flag calling for an aide to validate new users
73  */
74 void set_mm_valid(void) {
75         begin_critical_section(S_CONTROL);
76         get_control();
77         CitControl.MMflags = CitControl.MMflags | MM_VALID ;
78         put_control();
79         end_critical_section(S_CONTROL);
80 }
81
82
83
84 /*
85  * Extract Internet e-mail addresses from a message containing a vCard, and
86  * perform a callback for any found.
87  */
88 void vcard_extract_internet_addresses(struct CtdlMessage *msg,
89                                 void (*callback)(char *, char *) ) {
90         struct vCard *v;
91         char *s;
92         char *addr;
93         char citadel_address[SIZ];
94         int instance = 0;
95         int found_something = 0;
96
97         if (msg->cm_fields['A'] == NULL) return;
98         if (msg->cm_fields['N'] == NULL) return;
99         snprintf(citadel_address, sizeof citadel_address, "%s @ %s",
100                 msg->cm_fields['A'], msg->cm_fields['N']);
101
102         v = vcard_load(msg->cm_fields['M']);
103         if (v == NULL) return;
104
105         /* Go through the vCard searching for *all* instances of
106          * the "email;internet" key
107          */
108         do {
109                 s = vcard_get_prop(v, "email;internet", 0, instance++, 0);
110                 if (s != NULL) {
111                         addr = strdup(s);
112                         striplt(addr);
113                         if (!IsEmptyStr(addr)) {
114                                 if (callback != NULL) {
115                                         callback(addr, citadel_address);
116                                 }
117                         }
118                         free(addr);
119                         found_something = 1;
120                 }
121                 else {
122                         found_something = 0;
123                 }
124         } while(found_something);
125
126         vcard_free(v);
127 }
128
129
130 /* 
131  * vCard-to-LDAP conversions.
132  *
133  * If 'op' is set to V2L_WRITE, then write
134  * (add, or change if already exists) a directory entry to the
135  * LDAP server, based on the information supplied in a vCard.
136  *
137  * If 'op' is set to V2L_DELETE, then delete the entry from LDAP.
138  */
139  
140  
141 void ctdl_vcard_to_directory(struct CtdlMessage *msg, int op) {
142         struct vCard *v = NULL;
143         int i;
144         int have_addr = 0;
145         int have_cn = 0;
146         
147         void *objectlist = NULL;
148
149         char givenname[128];
150         char sn[128];
151         char uid[256];
152         char street[256];
153         char city[128];
154         char state[3];
155         char zipcode[10];
156         char calFBURL[256];
157         char ldap_dn[SIZ];
158
159         if (msg == NULL) return;
160         if (msg->cm_fields['M'] == NULL) return;
161         if (msg->cm_fields['A'] == NULL) return;
162         if (msg->cm_fields['N'] == NULL) return;
163
164         /* Initialize variables */
165         strcpy(givenname, "");
166         strcpy(sn, "");
167         strcpy(calFBURL, "");
168
169         sprintf(uid, "%s@%s",
170                 msg->cm_fields['A'],
171                 msg->cm_fields['N']
172         );
173
174         sprintf(ldap_dn, "euid=%s,ou=%s", msg->cm_fields['E'], msg->cm_fields['N']);
175         
176         /* Are we just deleting?  If so, it's simple... */
177         if (op == V2L_DELETE) {
178                 (void) CtdlDoDirectoryServiceFunc (ldap_dn, NULL, NULL, "ldap", DIRECTORY_USER_DEL);
179                 return;
180         }
181
182         /*
183          * If we get to this point then it must be a V2L_WRITE operation.
184          */
185
186         /* First make sure the OU for the user's home Citadel host is created */
187         (void) CtdlDoDirectoryServiceFunc (NULL, msg->cm_fields['N'], NULL, "ldap", DIRECTORY_CREATE_HOST);
188         
189         /* Next create the directory service object */
190         (void) CtdlDoDirectoryServiceFunc(NULL, NULL, &objectlist, "ldap", DIRECTORY_CREATE_OBJECT);
191
192         /* The first LDAP attribute will be an 'objectclass' list.  Citadel
193          * doesn't do anything with this.  It's just there for compatibility
194          * with Kolab.
195          */
196         (void) CtdlDoDirectoryServiceFunc("objectclass", "citadelInetOrgPerson", &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
197
198         /* Convert the vCard fields to LDAP properties */
199         v = vcard_load(msg->cm_fields['M']);
200         if (v->numprops) for (i=0; i<(v->numprops); ++i) if (striplt(v->prop[i].value), strlen(v->prop[i].value) > 0) {
201
202                 if (!strcasecmp(v->prop[i].name, "n")) {
203                         extract_token(sn,               v->prop[i].value, 0, ';', sizeof sn);
204                         extract_token(givenname,        v->prop[i].value, 1, ';', sizeof givenname);
205                 }
206
207                 if (!strcasecmp(v->prop[i].name, "fn")) {
208                         (void) CtdlDoDirectoryServiceFunc("cn", v->prop[i].value, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
209                         have_cn = 1;
210                 }
211
212                 if (!strcasecmp(v->prop[i].name, "title")) {
213                         (void) CtdlDoDirectoryServiceFunc("title", v->prop[i].value, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
214                 }
215
216                 if (!strcasecmp(v->prop[i].name, "org")) {
217                         (void) CtdlDoDirectoryServiceFunc("o", v->prop[i].value, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
218                 }
219
220                 if ( (!strcasecmp(v->prop[i].name, "adr"))
221                    ||(!strncasecmp(v->prop[i].name, "adr;", 4)) ) {
222                         /* Unfortunately, we can only do a single address */
223                         if (!have_addr) {
224                                 have_addr = 1;
225                                 strcpy(street, "");
226                                 extract_token(&street[strlen(street)],
227                                         v->prop[i].value, 0, ';', (sizeof street - strlen(street))); /* po box */
228                                 strcat(street, " ");
229                                 extract_token(&street[strlen(street)],
230                                         v->prop[i].value, 1, ';', (sizeof street - strlen(street))); /* extend addr */
231                                 strcat(street, " ");
232                                 extract_token(&street[strlen(street)],
233                                         v->prop[i].value, 2, ';', (sizeof street - strlen(street))); /* street */
234                                 striplt(street);
235                                 extract_token(city, v->prop[i].value, 3, ';', sizeof city);
236                                 extract_token(state, v->prop[i].value, 4, ';', sizeof state);
237                                 extract_token(zipcode, v->prop[i].value, 5, ';', sizeof zipcode);
238
239                                 // ldap requires these fields to be something
240                                 if (IsEmptyStr(street)) strcpy(street, "_");
241                                 if (IsEmptyStr(zipcode)) strcpy(zipcode, "_");
242                                 if (IsEmptyStr(city)) strcpy(city, "_");
243                                 if (IsEmptyStr(state)) strcpy(state, "_");
244
245                                 (void) CtdlDoDirectoryServiceFunc("street", street, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
246                                 (void) CtdlDoDirectoryServiceFunc("l", city, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
247                                 (void) CtdlDoDirectoryServiceFunc("st", state, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
248                                 (void) CtdlDoDirectoryServiceFunc("postalcode", zipcode, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
249                         }
250                 }
251
252                 if (!strcasecmp(v->prop[i].name, "tel;home"))
253                         (void) CtdlDoDirectoryServiceFunc("homePhone", v->prop[i].value, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
254                 else
255                 if ( (!strcasecmp(v->prop[i].name, "tel"))
256                    ||(!strncasecmp(v->prop[i].name, "tel;", 4)) ) {
257                         (void) CtdlDoDirectoryServiceFunc("telephoneNumber", v->prop[i].value, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
258                 }
259
260
261                 if ( (!strcasecmp(v->prop[i].name, "email"))
262                    ||(!strcasecmp(v->prop[i].name, "email;internet")) ) {
263                         (void) CtdlDoDirectoryServiceFunc("mail", v->prop[i].value, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
264                 }
265
266                 /* Calendar free/busy URL (take the first one we find, but if a subsequent
267                  * one contains the "pref" designation then we go with that instead.)
268                  */
269                 if ( (!strcasecmp(v->prop[i].name, "fburl"))
270                    ||(!strncasecmp(v->prop[i].name, "fburl;", 6)) ) {
271                         if ( (IsEmptyStr(calFBURL))
272                            || (!strncasecmp(v->prop[i].name, "fburl;pref", 10)) ) {
273                                 safestrncpy(calFBURL, v->prop[i].value, sizeof calFBURL);
274                         }
275                 }
276
277         }
278         vcard_free(v);  /* Don't need this anymore. */
279
280         /* "sn" (surname) based on info in vCard */
281         (void) CtdlDoDirectoryServiceFunc("sn", sn, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
282
283         /* "givenname" (first name) based on info in vCard */
284         if (IsEmptyStr(givenname)) strcpy(givenname, "_");
285         if (IsEmptyStr(sn)) strcpy(sn, "_");
286         (void) CtdlDoDirectoryServiceFunc("givenname", givenname, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
287
288         /* "uid" is a Kolab compatibility thing.  We just do cituser@citnode */
289         (void) CtdlDoDirectoryServiceFunc("uid", uid, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
290
291         /* Add a "cn" (Common Name) attribute based on the user's screen name,
292          * but only there was no 'fn' (full name) property in the vCard 
293          */
294         if (!have_cn)
295                 (void) CtdlDoDirectoryServiceFunc("cn", msg->cm_fields['A'], &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
296
297         /* Add a "calFBURL" attribute if a calendar free/busy URL exists */
298         if (!IsEmptyStr(calFBURL)) {
299                 (void) CtdlDoDirectoryServiceFunc("calFBURL", calFBURL, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
300         }
301         
302         // Add this messages EUID as the primary key for this entry.
303         (void) CtdlDoDirectoryServiceFunc("euid", msg->cm_fields['E'], &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
304         
305         
306         (void) CtdlDoDirectoryServiceFunc(ldap_dn, NULL, &objectlist, "ldap", DIRECTORY_SAVE_OBJECT);
307         
308         (void) CtdlDoDirectoryServiceFunc(NULL, NULL, &objectlist, "ldap", DIRECTORY_FREE_OBJECT);
309         lprintf(CTDL_DEBUG, "Directory Services write operation complete.\n");
310 }
311
312
313
314 /*
315  * Callback for vcard_add_to_directory()
316  * (Lotsa ugly nested callbacks.  Oh well.)
317  */
318 void vcard_directory_add_user(char *internet_addr, char *citadel_addr) {
319         char buf[SIZ];
320
321         /* We have to validate that we're not stepping on someone else's
322          * email address ... but only if we're logged in.  Otherwise it's
323          * probably just the networker or something.
324          */
325         if (CC->logged_in) {
326                 lprintf(CTDL_DEBUG, "Checking for <%s>...\n", internet_addr);
327                 if (CtdlDirectoryLookup(buf, internet_addr, sizeof buf) == 0) {
328                         if (strcasecmp(buf, citadel_addr)) {
329                                 /* This address belongs to someone else.
330                                  * Bail out silently without saving.
331                                  */
332                                 lprintf(CTDL_DEBUG, "DOOP!\n");
333                                 return;
334                         }
335                 }
336         }
337         lprintf(CTDL_INFO, "Adding %s (%s) to directory\n",
338                         citadel_addr, internet_addr);
339         CtdlDirectoryAddUser(internet_addr, citadel_addr);
340 }
341
342
343 /*
344  * Back end function for cmd_igab()
345  */
346 void vcard_add_to_directory(long msgnum, void *data) {
347         struct CtdlMessage *msg;
348
349         msg = CtdlFetchMessage(msgnum, 1);
350         if (msg != NULL) {
351                 vcard_extract_internet_addresses(msg, vcard_directory_add_user);
352         }
353
354         ctdl_vcard_to_directory(msg, V2L_WRITE);
355
356         CtdlFreeMessage(msg);
357 }
358
359
360 /*
361  * Initialize Global Adress Book
362  */
363 void cmd_igab(char *argbuf) {
364         char hold_rm[ROOMNAMELEN];
365
366         if (CtdlAccessCheck(ac_aide)) return;
367
368         strcpy(hold_rm, CC->room.QRname);       /* save current room */
369
370         if (getroom(&CC->room, ADDRESS_BOOK_ROOM) != 0) {
371                 getroom(&CC->room, hold_rm);
372                 cprintf("%d cannot get address book room\n", ERROR + ROOM_NOT_FOUND);
373                 return;
374         }
375
376         /* Empty the existing database first.
377          */
378         CtdlDirectoryInit();
379
380         /* We want *all* vCards in this room */
381         CtdlForEachMessage(MSGS_ALL, 0, NULL, "^[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$",
382                 NULL, vcard_add_to_directory, NULL);
383
384         getroom(&CC->room, hold_rm);    /* return to saved room */
385         cprintf("%d Directory has been rebuilt.\n", CIT_OK);
386 }
387
388
389
390
391 /*
392  * See if there is a valid Internet address in a vCard to use for outbound
393  * Internet messages.  If there is, stick it in the buffer.
394  */
395 void extract_inet_email_addrs(char *emailaddrbuf, size_t emailaddrbuf_len,
396                                 char *secemailaddrbuf, size_t secemailaddrbuf_len,
397                                 struct vCard *v, int local_addrs_only) {
398         char *s, *addr;
399         int instance = 0;
400         int saved_instance = 0;
401
402         /* Go through the vCard searching for *all* instances of
403          * the "email;internet" key
404          */
405         while (s = vcard_get_prop(v, "email;internet", 0, instance++, 0),  s != NULL) {
406                 addr = strdup(s);
407                 striplt(addr);
408                 if (!IsEmptyStr(addr)) {
409                         if ( (IsDirectory(addr, 1)) || 
410                              (!local_addrs_only) ) {
411                                 ++saved_instance;
412                                 if ((saved_instance == 1) && (emailaddrbuf != NULL)) {
413                                         safestrncpy(emailaddrbuf, addr, emailaddrbuf_len);
414                                 }
415                                 else if ((saved_instance == 2) && (secemailaddrbuf != NULL)) {
416                                         safestrncpy(secemailaddrbuf, addr, secemailaddrbuf_len);
417                                 }
418                                 else if ((saved_instance > 2) && (secemailaddrbuf != NULL)) {
419                                         if ( (strlen(addr) + strlen(secemailaddrbuf) + 2) 
420                                            < secemailaddrbuf_len ) {
421                                                 strcat(secemailaddrbuf, "|");
422                                                 strcat(secemailaddrbuf, addr);
423                                         }
424                                 }
425                         }
426                 }
427                 free(addr);
428         }
429 }
430
431
432
433 /*
434  * See if there is a name / screen name / friendly name  in a vCard to use for outbound
435  * Internet messages.  If there is, stick it in the buffer.
436  */
437 void extract_friendly_name(char *namebuf, size_t namebuf_len, struct vCard *v)
438 {
439         char *s;
440
441         s = vcard_get_prop(v, "fn", 0, 0, 0);
442         if (s == NULL) {
443                 s = vcard_get_prop(v, "n", 0, 0, 0);
444         }
445
446         if (s != NULL) {
447                 safestrncpy(namebuf, s, namebuf_len);
448         }
449 }
450
451
452 /*
453  * Callback function for vcard_upload_beforesave() hunts for the real vcard in the MIME structure
454  */
455 void vcard_extract_vcard(char *name, char *filename, char *partnum, char *disp,
456                    void *content, char *cbtype, char *cbcharset, size_t length,
457                    char *encoding, void *cbuserdata)
458 {
459         struct vCard **v = (struct vCard **) cbuserdata;
460
461         if (  (!strcasecmp(cbtype, "text/x-vcard"))
462            || (!strcasecmp(cbtype, "text/vcard")) ) {
463
464                 lprintf(CTDL_DEBUG, "Part %s contains a vCard!  Loading...\n", partnum);
465                 if (*v != NULL) {
466                         vcard_free(*v);
467                 }
468                 *v = vcard_load(content);
469         }
470 }
471
472
473 /*
474  * This handler detects whether the user is attempting to save a new
475  * vCard as part of his/her personal configuration, and handles the replace
476  * function accordingly (delete the user's existing vCard in the config room
477  * and in the global address book).
478  */
479 int vcard_upload_beforesave(struct CtdlMessage *msg) {
480         char *ptr;
481         char *s;
482         char buf[SIZ];
483         struct ctdluser usbuf;
484         long what_user;
485         struct vCard *v = NULL;
486         char *ser = NULL;
487         int i = 0;
488         int yes_my_citadel_config = 0;
489         int yes_any_vcard_room = 0;
490
491         if (!CC->logged_in) return(0);  /* Only do this if logged in. */
492
493         /* Is this some user's "My Citadel Config" room? */
494         if ( (CC->room.QRflags && QR_MAILBOX)
495            && (!strcasecmp(&CC->room.QRname[11], USERCONFIGROOM)) ) {
496                 /* Yes, we want to do this */
497                 yes_my_citadel_config = 1;
498
499 #ifdef VCARD_SAVES_BY_AIDES_ONLY
500                 /* Prevent non-aides from performing registration changes */
501                 if (CC->user.axlevel < 6) {
502                         return(1);
503                 }
504 #endif
505
506         }
507
508         /* Is this a room with an address book in it? */
509         if (CC->room.QRdefaultview == VIEW_ADDRESSBOOK) {
510                 yes_any_vcard_room = 1;
511         }
512
513         /* If neither condition exists, don't run this hook. */
514         if ( (!yes_my_citadel_config) && (!yes_any_vcard_room) ) {
515                 return(0);
516         }
517
518         /* If this isn't a MIME message, don't bother. */
519         if (msg->cm_format_type != 4) return(0);
520
521         /* Ok, if we got this far, look into the situation further... */
522
523         ptr = msg->cm_fields['M'];
524         if (ptr == NULL) return(0);
525
526         mime_parser(msg->cm_fields['M'],
527                 NULL,
528                 *vcard_extract_vcard,
529                 NULL, NULL,
530                 &v,             /* user data ptr - put the vcard here */
531                 0
532         );
533
534         if (v == NULL) return(0);       /* no vCards were found in this message */
535
536         /* If users cannot create their own accounts, they cannot re-register either. */
537         if ( (yes_my_citadel_config) && (config.c_disable_newu) && (CC->user.axlevel < 6) ) {
538                 return(1);
539         }
540
541         s = vcard_get_prop(v, "FN", 0, 0, 0);
542         if (s) lprintf(CTDL_DEBUG, "vCard beforesave hook running for <%s>\n", s);
543
544         if (yes_my_citadel_config) {
545                 /* Bingo!  The user is uploading a new vCard, so
546                  * delete the old one.  First, figure out which user
547                  * is being re-registered...
548                  */
549                 what_user = atol(CC->room.QRname);
550
551                 if (what_user == CC->user.usernum) {
552                         /* It's the logged in user.  That was easy. */
553                         memcpy(&usbuf, &CC->user, sizeof(struct ctdluser));
554                 }
555                 
556                 else if (getuserbynumber(&usbuf, what_user) == 0) {
557                         /* We fetched a valid user record */
558                 }
559
560                 else {
561                         /* somebody set up us the bomb! */
562                         yes_my_citadel_config = 0;
563                 }
564         }
565         
566         if (yes_my_citadel_config) {
567                 /* Delete the user's old vCard.  This would probably
568                  * get taken care of by the replication check, but we
569                  * want to make sure there is absolutely only one
570                  * vCard in the user's config room at all times.
571                  *
572                  */
573                 CtdlDeleteMessages(CC->room.QRname, NULL, 0, "^[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$");
574
575                 /* Make the author of the message the name of the user. */
576                 if (msg->cm_fields['A'] != NULL) {
577                         free(msg->cm_fields['A']);
578                 }
579                 msg->cm_fields['A'] = strdup(usbuf.fullname);
580         }
581
582         /* Insert or replace RFC2739-compliant free/busy URL */
583         if (yes_my_citadel_config) {
584                 sprintf(buf, "http://%s/%s.vfb",
585                         config.c_fqdn,
586                         usbuf.fullname);
587                 for (i=0; buf[i]; ++i) {
588                         if (buf[i] == ' ') buf[i] = '_';
589                 }
590                 vcard_set_prop(v, "FBURL;PREF", buf, 0);
591         }
592
593         /* If the vCard has no UID, then give it one. */
594         s = vcard_get_prop(v, "UID", 0, 0, 0);
595         if (s == NULL) {
596                 generate_uuid(buf);
597                 vcard_set_prop(v, "UID", buf, 0);
598         }
599
600         /* Enforce local UID policy if applicable */
601         if (yes_my_citadel_config) {
602                 snprintf(buf, sizeof buf, VCARD_EXT_FORMAT, msg->cm_fields['A'], NODENAME);
603                 vcard_set_prop(v, "UID", buf, 0);
604         }
605
606         /* 
607          * Set the EUID of the message to the UID of the vCard.
608          */
609         if (msg->cm_fields['E'] != NULL) free(msg->cm_fields['E']);
610         s = vcard_get_prop(v, "UID", 0, 0, 0);
611         if (s != NULL) {
612                 msg->cm_fields['E'] = strdup(s);
613                 if (msg->cm_fields['U'] == NULL) {
614                         msg->cm_fields['U'] = strdup(s);
615                 }
616         }
617
618         /*
619          * Set the Subject to the name in the vCard.
620          */
621         s = vcard_get_prop(v, "FN", 0, 0, 0);
622         if (s == NULL) {
623                 s = vcard_get_prop(v, "N", 0, 0, 0);
624         }
625         if (s != NULL) {
626                 if (msg->cm_fields['U'] != NULL) {
627                         free(msg->cm_fields['U']);
628                 }
629                 msg->cm_fields['U'] = strdup(s);
630         }
631
632         /* Re-serialize it back into the msg body */
633         ser = vcard_serialize(v);
634         if (ser != NULL) {
635                 msg->cm_fields['M'] = realloc(msg->cm_fields['M'], strlen(ser) + 1024);
636                 sprintf(msg->cm_fields['M'],
637                         "Content-type: " VCARD_MIME_TYPE
638                         "\r\n\r\n%s\r\n", ser);
639                 free(ser);
640         }
641
642         /* Now allow the save to complete. */
643         vcard_free(v);
644         return(0);
645 }
646
647
648
649 /*
650  * This handler detects whether the user is attempting to save a new
651  * vCard as part of his/her personal configuration, and handles the replace
652  * function accordingly (copy the vCard from the config room to the global
653  * address book).
654  */
655 int vcard_upload_aftersave(struct CtdlMessage *msg) {
656         char *ptr;
657         int linelen;
658         long I;
659         struct vCard *v;
660         int is_UserConf=0;
661         int is_GAB=0;
662
663         if (!CC->logged_in) return(0);  /* Only do this if logged in. */
664
665         /* If this isn't the configuration room, or if this isn't a MIME
666          * message, don't bother.
667          */
668         if (msg->cm_fields['O'] == NULL) return(0);
669         if (!strcasecmp(msg->cm_fields['O'], USERCONFIGROOM)) is_UserConf = 1;
670         if (!strcasecmp(msg->cm_fields['O'], ADDRESS_BOOK_ROOM)) is_GAB = 1;
671         if (!is_UserConf && !is_GAB) return(0);
672         if (msg->cm_format_type != 4) return(0);
673
674         ptr = msg->cm_fields['M'];
675         if (ptr == NULL) return(0);
676         while (ptr != NULL) {
677         
678                 linelen = strcspn(ptr, "\n");
679                 if (linelen == 0) return(0);    /* end of headers */    
680                 
681                 if (  (!strncasecmp(ptr, "Content-type: text/x-vcard", 26))
682                    || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
683                         /*
684                          * Bingo!  The user is uploading a new vCard, so
685                          * copy it to the Global Address Book room.
686                          */
687
688                         I = atol(msg->cm_fields['I']);
689                         if (I < 0L) return(0);
690
691                         /* Store our Internet return address in memory */
692                         v = vcard_load(msg->cm_fields['M']);
693                         extract_inet_email_addrs(CC->cs_inet_email, sizeof CC->cs_inet_email,
694                                                 CC->cs_inet_other_emails, sizeof CC->cs_inet_other_emails,
695                                                 v, 1);
696                         extract_friendly_name(CC->cs_inet_fn, sizeof CC->cs_inet_fn, v);
697                         vcard_free(v);
698
699                         if (!is_GAB)
700                         {       // This is not the GAB
701                                 /* Put it in the Global Address Book room... */
702                                 CtdlSaveMsgPointerInRoom(ADDRESS_BOOK_ROOM, I, 1, msg);
703                         }
704
705                         /* ...and also in the directory database. */
706                         vcard_add_to_directory(I, NULL);
707
708                         /* Some sites want an Aide to be notified when a
709                          * user registers or re-registers...
710                          */
711                         set_mm_valid();
712
713                         /* ...which also means we need to flag the user */
714                         lgetuser(&CC->user, CC->curr_user);
715                         CC->user.flags |= (US_REGIS|US_NEEDVALID);
716                         lputuser(&CC->user);
717
718                         return(0);
719                 }
720
721                 ptr = strchr((char *)ptr, '\n');
722                 if (ptr != NULL) ++ptr;
723         }
724
725         return(0);
726 }
727
728
729
730 /*
731  * back end function used for callbacks
732  */
733 void vcard_gu_backend(long supplied_msgnum, void *userdata) {
734         long *msgnum;
735
736         msgnum = (long *) userdata;
737         *msgnum = supplied_msgnum;
738 }
739
740
741 /*
742  * If this user has a vcard on disk, read it into memory, otherwise allocate
743  * and return an empty vCard.
744  */
745 struct vCard *vcard_get_user(struct ctdluser *u) {
746         char hold_rm[ROOMNAMELEN];
747         char config_rm[ROOMNAMELEN];
748         struct CtdlMessage *msg = NULL;
749         struct vCard *v;
750         long VCmsgnum;
751
752         strcpy(hold_rm, CC->room.QRname);       /* save current room */
753         MailboxName(config_rm, sizeof config_rm, u, USERCONFIGROOM);
754
755         if (getroom(&CC->room, config_rm) != 0) {
756                 getroom(&CC->room, hold_rm);
757                 return vcard_new();
758         }
759
760         /* We want the last (and probably only) vcard in this room */
761         VCmsgnum = (-1);
762         CtdlForEachMessage(MSGS_LAST, 1, NULL, "^[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$",
763                 NULL, vcard_gu_backend, (void *)&VCmsgnum );
764         getroom(&CC->room, hold_rm);    /* return to saved room */
765
766         if (VCmsgnum < 0L) return vcard_new();
767
768         msg = CtdlFetchMessage(VCmsgnum, 1);
769         if (msg == NULL) return vcard_new();
770
771         v = vcard_load(msg->cm_fields['M']);
772         CtdlFreeMessage(msg);
773         return v;
774 }
775
776
777 /*
778  * Store this user's vCard in the appropriate place
779  */
780 /*
781  * Write our config to disk
782  */
783 void vcard_write_user(struct ctdluser *u, struct vCard *v) {
784         char temp[PATH_MAX];
785         FILE *fp;
786         char *ser;
787
788         CtdlMakeTempFileName(temp, sizeof temp);
789         ser = vcard_serialize(v);
790
791         fp = fopen(temp, "w");
792         if (fp == NULL) return;
793         if (ser == NULL) {
794                 fprintf(fp, "begin:vcard\r\nend:vcard\r\n");
795         } else {
796                 fwrite(ser, strlen(ser), 1, fp);
797                 free(ser);
798         }
799         fclose(fp);
800
801         /* This handy API function does all the work for us.
802          * NOTE: normally we would want to set that last argument to 1, to
803          * force the system to delete the user's old vCard.  But it doesn't
804          * have to, because the vcard_upload_beforesave() hook above
805          * is going to notice what we're trying to do, and delete the old vCard.
806          */
807         CtdlWriteObject(USERCONFIGROOM, /* which room */
808                         VCARD_MIME_TYPE,/* MIME type */
809                         temp,           /* temp file */
810                         u,              /* which user */
811                         0,              /* not binary */
812                         0,              /* don't delete others of this type */
813                         0);             /* no flags */
814
815         unlink(temp);
816 }
817
818
819
820 /*
821  * Old style "enter registration info" command.  This function simply honors
822  * the REGI protocol command, translates the entered parameters into a vCard,
823  * and enters the vCard into the user's configuration.
824  */
825 void cmd_regi(char *argbuf) {
826         int a,b,c;
827         char buf[SIZ];
828         struct vCard *my_vcard;
829
830         char tmpaddr[SIZ];
831         char tmpcity[SIZ];
832         char tmpstate[SIZ];
833         char tmpzip[SIZ];
834         char tmpaddress[SIZ];
835         char tmpcountry[SIZ];
836
837         unbuffer_output();
838
839         if (!(CC->logged_in)) {
840                 cprintf("%d Not logged in.\n",ERROR + NOT_LOGGED_IN);
841                 return;
842         }
843
844         /* If users cannot create their own accounts, they cannot re-register either. */
845         if ( (config.c_disable_newu) && (CC->user.axlevel < 6) ) {
846                 cprintf("%d Self-service registration is not allowed here.\n",
847                         ERROR + HIGHER_ACCESS_REQUIRED);
848         }
849
850         my_vcard = vcard_get_user(&CC->user);
851         strcpy(tmpaddr, "");
852         strcpy(tmpcity, "");
853         strcpy(tmpstate, "");
854         strcpy(tmpzip, "");
855         strcpy(tmpcountry, "USA");
856
857         cprintf("%d Send registration...\n", SEND_LISTING);
858         a=0;
859         while (client_getln(buf, sizeof buf), strcmp(buf,"000")) {
860                 if (a==0) vcard_set_prop(my_vcard, "n", buf, 0);
861                 if (a==1) strcpy(tmpaddr, buf);
862                 if (a==2) strcpy(tmpcity, buf);
863                 if (a==3) strcpy(tmpstate, buf);
864                 if (a==4) {
865                         for (c=0; buf[c]; ++c) {
866                                 if ((buf[c]>='0') && (buf[c]<='9')) {
867                                         b = strlen(tmpzip);
868                                         tmpzip[b] = buf[c];
869                                         tmpzip[b+1] = 0;
870                                 }
871                         }
872                 }
873                 if (a==5) vcard_set_prop(my_vcard, "tel;home", buf, 0);
874                 if (a==6) vcard_set_prop(my_vcard, "email;internet", buf, 0);
875                 if (a==7) strcpy(tmpcountry, buf);
876                 ++a;
877         }
878
879         snprintf(tmpaddress, sizeof tmpaddress, ";;%s;%s;%s;%s;%s",
880                 tmpaddr, tmpcity, tmpstate, tmpzip, tmpcountry);
881         vcard_set_prop(my_vcard, "adr", tmpaddress, 0);
882         vcard_write_user(&CC->user, my_vcard);
883         vcard_free(my_vcard);
884 }
885
886
887 /*
888  * Protocol command to fetch registration info for a user
889  */
890 void cmd_greg(char *argbuf)
891 {
892         struct ctdluser usbuf;
893         struct vCard *v;
894         char *s;
895         char who[USERNAME_SIZE];
896         char adr[256];
897         char buf[256];
898
899         extract_token(who, argbuf, 0, '|', sizeof who);
900
901         if (!(CC->logged_in)) {
902                 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
903                 return;
904         }
905
906         if (!strcasecmp(who,"_SELF_")) strcpy(who,CC->curr_user);
907
908         if ((CC->user.axlevel < 6) && (strcasecmp(who,CC->curr_user))) {
909                 cprintf("%d Higher access required.\n",
910                         ERROR + HIGHER_ACCESS_REQUIRED);
911                 return;
912         }
913
914         if (getuser(&usbuf, who) != 0) {
915                 cprintf("%d '%s' not found.\n", ERROR + NO_SUCH_USER, who);
916                 return;
917         }
918
919         v = vcard_get_user(&usbuf);
920
921         cprintf("%d %s\n", LISTING_FOLLOWS, usbuf.fullname);
922         cprintf("%ld\n", usbuf.usernum);
923         cprintf("%s\n", usbuf.password);
924         s = vcard_get_prop(v, "n", 0, 0, 0);
925         cprintf("%s\n", s ? s : " ");   /* name */
926
927         s = vcard_get_prop(v, "adr", 0, 0, 0);
928         snprintf(adr, sizeof adr, "%s", s ? s : " ");/* address... */
929
930         extract_token(buf, adr, 2, ';', sizeof buf);
931         cprintf("%s\n", buf);                           /* street */
932         extract_token(buf, adr, 3, ';', sizeof buf);
933         cprintf("%s\n", buf);                           /* city */
934         extract_token(buf, adr, 4, ';', sizeof buf);
935         cprintf("%s\n", buf);                           /* state */
936         extract_token(buf, adr, 5, ';', sizeof buf);
937         cprintf("%s\n", buf);                           /* zip */
938
939         s = vcard_get_prop(v, "tel;home", 0, 0, 0);
940         if (s == NULL) s = vcard_get_prop(v, "tel", 1, 0, 0);
941         if (s != NULL) {
942                 cprintf("%s\n", s);
943         }
944         else {
945                 cprintf(" \n");
946         }
947
948         cprintf("%d\n", usbuf.axlevel);
949
950         s = vcard_get_prop(v, "email;internet", 0, 0, 0);
951         cprintf("%s\n", s ? s : " ");
952         s = vcard_get_prop(v, "adr", 0, 0, 0);
953         snprintf(adr, sizeof adr, "%s", s ? s : " ");/* address... */
954
955         extract_token(buf, adr, 6, ';', sizeof buf);
956         cprintf("%s\n", buf);                           /* country */
957         cprintf("000\n");
958         vcard_free(v);
959 }
960
961
962
963 /*
964  * When a user is being created, create his/her vCard.
965  */
966 void vcard_newuser(struct ctdluser *usbuf) {
967         char vname[256];
968         char buf[256];
969         int i;
970         struct vCard *v;
971
972         vcard_fn_to_n(vname, usbuf->fullname, sizeof vname);
973         lprintf(CTDL_DEBUG, "Converted <%s> to <%s>\n", usbuf->fullname, vname);
974
975         /* Create and save the vCard */
976         v = vcard_new();
977         if (v == NULL) return;
978         sprintf(buf, "%s@%s", usbuf->fullname, config.c_fqdn);
979         for (i=0; buf[i]; ++i) {
980                 if (buf[i] == ' ') buf[i] = '_';
981         }
982         vcard_add_prop(v, "fn", usbuf->fullname);
983         vcard_add_prop(v, "n", vname);
984         vcard_add_prop(v, "adr", "adr:;;_;_;_;00000;__");
985         vcard_add_prop(v, "email;internet", buf);
986         vcard_write_user(usbuf, v);
987         vcard_free(v);
988 }
989
990
991 /*
992  * When a user is being deleted, we have to remove his/her vCard.
993  * This is accomplished by issuing a message with 'CANCEL' in the S (special)
994  * field, and the same Exclusive ID as the existing card.
995  */
996 void vcard_purge(struct ctdluser *usbuf) {
997         struct CtdlMessage *msg;
998         char buf[SIZ];
999
1000         msg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1001         if (msg == NULL) return;
1002         memset(msg, 0, sizeof(struct CtdlMessage));
1003
1004         msg->cm_magic = CTDLMESSAGE_MAGIC;
1005         msg->cm_anon_type = MES_NORMAL;
1006         msg->cm_format_type = 0;
1007         msg->cm_fields['A'] = strdup(usbuf->fullname);
1008         msg->cm_fields['O'] = strdup(ADDRESS_BOOK_ROOM);
1009         msg->cm_fields['N'] = strdup(NODENAME);
1010         msg->cm_fields['M'] = strdup("Purge this vCard\n");
1011
1012         snprintf(buf, sizeof buf, VCARD_EXT_FORMAT,
1013                         msg->cm_fields['A'], NODENAME);
1014         msg->cm_fields['E'] = strdup(buf);
1015
1016         msg->cm_fields['S'] = strdup("CANCEL");
1017
1018         CtdlSubmitMsg(msg, NULL, ADDRESS_BOOK_ROOM);
1019         CtdlFreeMessage(msg);
1020 }
1021
1022
1023 /*
1024  * Grab vCard directory stuff out of incoming network messages
1025  */
1026 int vcard_extract_from_network(struct CtdlMessage *msg, char *target_room) {
1027         char *ptr;
1028         int linelen;
1029
1030         if (msg == NULL) return(0);
1031
1032         if (strcasecmp(target_room, ADDRESS_BOOK_ROOM)) {
1033                 return(0);
1034         }
1035
1036         if (msg->cm_format_type != 4) return(0);
1037
1038         ptr = msg->cm_fields['M'];
1039         if (ptr == NULL) return(0);
1040         while (ptr != NULL) {
1041         
1042                 linelen = strcspn(ptr, "\n");
1043                 if (linelen == 0) return(0);    /* end of headers */    
1044                 
1045                 if (  (!strncasecmp(ptr, "Content-type: text/x-vcard", 26))
1046                    || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
1047                         /* It's a vCard.  Add it to the directory. */
1048                         vcard_extract_internet_addresses(msg, CtdlDirectoryAddUser);
1049                         return(0);
1050                 }
1051
1052                 ptr = strchr((char *)ptr, '\n');
1053                 if (ptr != NULL) ++ptr;
1054         }
1055
1056         return(0);
1057 }
1058
1059
1060
1061 /* 
1062  * When a vCard is being removed from the Global Address Book room, remove it
1063  * from the directory as well.
1064  */
1065 void vcard_delete_remove(char *room, long msgnum) {
1066         struct CtdlMessage *msg;
1067         char *ptr;
1068         int linelen;
1069
1070         if (msgnum <= 0L) return;
1071         
1072         if (room == NULL) return;
1073
1074         if (strcasecmp(room, ADDRESS_BOOK_ROOM)) {
1075                 return;
1076         }
1077
1078         msg = CtdlFetchMessage(msgnum, 1);
1079         if (msg == NULL) return;
1080
1081         ptr = msg->cm_fields['M'];
1082         if (ptr == NULL) goto EOH;
1083         while (ptr != NULL) {
1084                 linelen = strcspn(ptr, "\n");
1085                 if (linelen == 0) goto EOH;
1086                 
1087                 if (  (!strncasecmp(ptr, "Content-type: text/x-vcard", 26))
1088                    || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
1089                         /* Bingo!  A vCard is being deleted. */
1090                         vcard_extract_internet_addresses(msg, CtdlDirectoryDelUser);
1091                         ctdl_vcard_to_directory(msg, V2L_DELETE);
1092                 }
1093                 ptr = strchr((char *)ptr, '\n');
1094                 if (ptr != NULL) ++ptr;
1095         }
1096
1097 EOH:    CtdlFreeMessage(msg);
1098 }
1099
1100
1101
1102 /*
1103  * Get Valid Screen Names
1104  */
1105 void cmd_gvsn(char *argbuf)
1106 {
1107         if (CtdlAccessCheck(ac_logged_in)) return;
1108
1109         cprintf("%d valid screen names:\n", LISTING_FOLLOWS);
1110         cprintf("%s\n", CC->user.fullname);
1111         if ( (!IsEmptyStr(CC->cs_inet_fn)) && (strcasecmp(CC->user.fullname, CC->cs_inet_fn)) ) {
1112                 cprintf("%s\n", CC->cs_inet_fn);
1113         }
1114         cprintf("000\n");
1115 }
1116
1117
1118 /*
1119  * Get Valid Email Addresses
1120  */
1121 void cmd_gvea(char *argbuf)
1122 {
1123         int num_secondary_emails = 0;
1124         int i;
1125         char buf[256];
1126
1127         if (CtdlAccessCheck(ac_logged_in)) return;
1128
1129         cprintf("%d valid email addresses:\n", LISTING_FOLLOWS);
1130         if (!IsEmptyStr(CC->cs_inet_email)) {
1131                 cprintf("%s\n", CC->cs_inet_email);
1132         }
1133         if (!IsEmptyStr(CC->cs_inet_other_emails)) {
1134                 num_secondary_emails = num_tokens(CC->cs_inet_other_emails, '|');
1135                 for (i=0; i<num_secondary_emails; ++i) {
1136                         extract_token(buf, CC->cs_inet_other_emails,i,'|',sizeof CC->cs_inet_other_emails);
1137                         cprintf("%s\n", buf);
1138                 }
1139         }
1140         cprintf("000\n");
1141 }
1142
1143
1144
1145
1146 /*
1147  * Callback function for cmd_dvca() that hunts for vCard content types
1148  * and outputs any email addresses found within.
1149  */
1150 void dvca_mime_callback(char *name, char *filename, char *partnum, char *disp,
1151                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1152                 void *cbuserdata) {
1153
1154         struct vCard *v;
1155         char displayname[256];
1156         int displayname_len;
1157         char emailaddr[256];
1158         int i;
1159         int has_commas = 0;
1160
1161         if ( (strcasecmp(cbtype, "text/vcard")) && (strcasecmp(cbtype, "text/x-vcard")) ) {
1162                 return;
1163         }
1164
1165         v = vcard_load(content);
1166         if (v == NULL) return;
1167
1168         extract_friendly_name(displayname, sizeof displayname, v);
1169         extract_inet_email_addrs(emailaddr, sizeof emailaddr, NULL, 0, v, 0);
1170
1171         displayname_len = strlen(displayname);
1172         for (i=0; i<displayname_len; ++i) {
1173                 if (displayname[i] == '\"') displayname[i] = ' ';
1174                 if (displayname[i] == ';') displayname[i] = ',';
1175                 if (displayname[i] == ',') has_commas = 1;
1176         }
1177         striplt(displayname);
1178
1179         cprintf("%s%s%s <%s>\n",
1180                 (has_commas ? "\"" : ""),
1181                 displayname,
1182                 (has_commas ? "\"" : ""),
1183                 emailaddr
1184         );
1185
1186         vcard_free(v);
1187 }
1188
1189
1190 /*
1191  * Back end callback function for cmd_dvca()
1192  *
1193  * It's basically just passed a list of message numbers, which we're going
1194  * to fetch off the disk and then pass along to the MIME parser via another
1195  * layer of callback...
1196  */
1197 void dvca_callback(long msgnum, void *userdata) {
1198         struct CtdlMessage *msg = NULL;
1199
1200         msg = CtdlFetchMessage(msgnum, 1);
1201         if (msg == NULL) return;
1202         mime_parser(msg->cm_fields['M'],
1203                 NULL,
1204                 *dvca_mime_callback,    /* callback function */
1205                 NULL, NULL,
1206                 NULL,                   /* user data */
1207                 0
1208         );
1209         CtdlFreeMessage(msg);
1210 }
1211
1212
1213 /*
1214  * Dump VCard Addresses
1215  */
1216 void cmd_dvca(char *argbuf)
1217 {
1218         if (CtdlAccessCheck(ac_logged_in)) return;
1219
1220         cprintf("%d addresses:\n", LISTING_FOLLOWS);
1221         CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, dvca_callback, NULL);
1222         cprintf("000\n");
1223 }
1224
1225
1226 /*
1227  * Query Directory
1228  */
1229 void cmd_qdir(char *argbuf) {
1230         char citadel_addr[256];
1231         char internet_addr[256];
1232
1233         if (CtdlAccessCheck(ac_logged_in)) return;
1234
1235         extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
1236
1237         if (CtdlDirectoryLookup(citadel_addr, internet_addr, sizeof citadel_addr) != 0) {
1238                 cprintf("%d %s was not found.\n",
1239                         ERROR + NO_SUCH_USER, internet_addr);
1240                 return;
1241         }
1242
1243         cprintf("%d %s\n", CIT_OK, citadel_addr);
1244 }
1245
1246 /*
1247  * Query Directory, in fact an alias to match postfix tcp auth.
1248  */
1249 void check_get(void) {
1250         char internet_addr[256];
1251
1252         char cmdbuf[SIZ];
1253
1254         time(&CC->lastcmd);
1255         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
1256         if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
1257                 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
1258                 CC->kill_me = 1;
1259                 return;
1260         }
1261         lprintf(CTDL_INFO, ": %s\n", cmdbuf);
1262         while (strlen(cmdbuf) < 3) strcat(cmdbuf, " ");
1263
1264         if (strcasecmp(cmdbuf, "GET "));
1265         {
1266                 struct recptypes *rcpt;
1267                 char *argbuf = &cmdbuf[4];
1268                 
1269                 extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
1270                 rcpt = validate_recipients(internet_addr);
1271                 if ((rcpt != NULL)&&
1272                         (
1273                          (*rcpt->recp_local != '\0')||
1274                          (*rcpt->recp_room != '\0')||
1275                          (*rcpt->recp_ignet != '\0')))
1276                 {
1277
1278                         cprintf("200 OK %s\n", internet_addr);
1279                         lprintf(CTDL_INFO, "sending 200 OK for the room %s\n", rcpt->display_recp);
1280                 }
1281                 else 
1282                 {
1283                         cprintf("500 REJECT noone here by that name.\n");
1284                         
1285                         lprintf(CTDL_INFO, "sending 500 REJECT noone here by that name: %s\n", internet_addr);
1286                 }
1287                 if (rcpt != NULL) free_recipients(rcpt);
1288         }
1289 }
1290
1291 void check_get_greeting(void) {
1292 /* dummy function, we have no greeting in this verry simple protocol. */
1293 }
1294
1295
1296 /*
1297  * We don't know if the Contacts room exists so we just create it at login
1298  */
1299 void vcard_create_room(void)
1300 {
1301         struct ctdlroom qr;
1302         struct visit vbuf;
1303
1304         /* Create the calendar room if it doesn't already exist */
1305         create_room(USERCONTACTSROOM, 4, "", 0, 1, 0, VIEW_ADDRESSBOOK);
1306
1307         /* Set expiration policy to manual; otherwise objects will be lost! */
1308         if (lgetroom(&qr, USERCONTACTSROOM)) {
1309                 lprintf(CTDL_ERR, "Couldn't get the user CONTACTS room!\n");
1310                 return;
1311         }
1312         qr.QRep.expire_mode = EXPIRE_MANUAL;
1313         qr.QRdefaultview = VIEW_ADDRESSBOOK;    /* 2 = address book view */
1314         lputroom(&qr);
1315
1316         /* Set the view to a calendar view */
1317         CtdlGetRelationship(&vbuf, &CC->user, &qr);
1318         vbuf.v_view = 2;        /* 2 = address book view */
1319         CtdlSetRelationship(&vbuf, &CC->user, &qr);
1320
1321         return;
1322 }
1323
1324
1325
1326
1327 /*
1328  * When a user logs in...
1329  */
1330 void vcard_session_login_hook(void) {
1331         struct vCard *v = NULL;
1332
1333         v = vcard_get_user(&CC->user);
1334         extract_inet_email_addrs(CC->cs_inet_email, sizeof CC->cs_inet_email,
1335                                 CC->cs_inet_other_emails, sizeof CC->cs_inet_other_emails,
1336                                 v, 1);
1337         extract_friendly_name(CC->cs_inet_fn, sizeof CC->cs_inet_fn, v);
1338         vcard_free(v);
1339
1340         vcard_create_room();
1341 }
1342
1343
1344 /* 
1345  * Turn an arbitrary RFC822 address into a struct vCard for possible
1346  * inclusion into an address book.
1347  */
1348 struct vCard *vcard_new_from_rfc822_addr(char *addr) {
1349         struct vCard *v;
1350         char user[256], node[256], name[256], email[256], n[256], uid[256];
1351         int i;
1352
1353         v = vcard_new();
1354         if (v == NULL) return(NULL);
1355
1356         process_rfc822_addr(addr, user, node, name);
1357         vcard_set_prop(v, "fn", name, 0);
1358
1359         vcard_fn_to_n(n, name, sizeof n);
1360         vcard_set_prop(v, "n", n, 0);
1361
1362         snprintf(email, sizeof email, "%s@%s", user, node);
1363         vcard_set_prop(v, "email;internet", email, 0);
1364
1365         snprintf(uid, sizeof uid, "collected: %s %s@%s", name, user, node);
1366         for (i=0; uid[i]; ++i) {
1367                 if (isspace(uid[i])) uid[i] = '_';
1368                 uid[i] = tolower(uid[i]);
1369         }
1370         vcard_set_prop(v, "UID", uid, 0);
1371
1372         return(v);
1373 }
1374
1375
1376
1377 /*
1378  * This is called by store_harvested_addresses() to remove from the
1379  * list any addresses we already have in our address book.
1380  */
1381 void strip_addresses_already_have(long msgnum, void *userdata) {
1382         char *collected_addresses;
1383         struct CtdlMessage *msg = NULL;
1384         struct vCard *v;
1385         char *value = NULL;
1386         int i, j;
1387         char addr[256], user[256], node[256], name[256];
1388
1389         collected_addresses = (char *)userdata;
1390
1391         msg = CtdlFetchMessage(msgnum, 1);
1392         if (msg == NULL) return;
1393         v = vcard_load(msg->cm_fields['M']);
1394         CtdlFreeMessage(msg);
1395
1396         i = 0;
1397         while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
1398
1399                 for (j=0; j<num_tokens(collected_addresses, ','); ++j) {
1400                         extract_token(addr, collected_addresses, j, ',', sizeof addr);
1401
1402                         /* Remove the address if we already have it! */
1403                         process_rfc822_addr(addr, user, node, name);
1404                         snprintf(addr, sizeof addr, "%s@%s", user, node);
1405                         if (!strcasecmp(value, addr)) {
1406                                 remove_token(collected_addresses, j, ',');
1407                         }
1408                 }
1409
1410         }
1411
1412         vcard_free(v);
1413 }
1414
1415
1416
1417 /*
1418  * Back end function for store_harvested_addresses()
1419  */
1420 void store_this_ha(struct addresses_to_be_filed *aptr) {
1421         struct CtdlMessage *vmsg = NULL;
1422         long vmsgnum = (-1L);
1423         char *ser = NULL;
1424         struct vCard *v = NULL;
1425         char recipient[256];
1426         int i;
1427
1428         /* First remove any addresses we already have in the address book */
1429         usergoto(aptr->roomname, 0, 0, NULL, NULL);
1430         CtdlForEachMessage(MSGS_ALL, 0, NULL, "^[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$", NULL,
1431                 strip_addresses_already_have, aptr->collected_addresses);
1432
1433         if (!IsEmptyStr(aptr->collected_addresses))
1434            for (i=0; i<num_tokens(aptr->collected_addresses, ','); ++i) {
1435
1436                 /* Make a vCard out of each address */
1437                 extract_token(recipient, aptr->collected_addresses, i, ',', sizeof recipient);
1438                 striplt(recipient);
1439                 v = vcard_new_from_rfc822_addr(recipient);
1440                 if (v != NULL) {
1441                         vmsg = malloc(sizeof(struct CtdlMessage));
1442                         memset(vmsg, 0, sizeof(struct CtdlMessage));
1443                         vmsg->cm_magic = CTDLMESSAGE_MAGIC;
1444                         vmsg->cm_anon_type = MES_NORMAL;
1445                         vmsg->cm_format_type = FMT_RFC822;
1446                         vmsg->cm_fields['A'] = strdup("Citadel");
1447                         vmsg->cm_fields['E'] =  strdup(vcard_get_prop(v, "UID", 0, 0, 0));
1448                         ser = vcard_serialize(v);
1449                         if (ser != NULL) {
1450                                 vmsg->cm_fields['M'] = malloc(strlen(ser) + 1024);
1451                                 sprintf(vmsg->cm_fields['M'],
1452                                         "Content-type: " VCARD_MIME_TYPE
1453                                         "\r\n\r\n%s\r\n", ser);
1454                                 free(ser);
1455                         }
1456                         vcard_free(v);
1457
1458                         lprintf(CTDL_DEBUG, "Adding contact: %s\n", recipient);
1459                         vmsgnum = CtdlSubmitMsg(vmsg, NULL, aptr->roomname);
1460                         CtdlFreeMessage(vmsg);
1461                 }
1462         }
1463
1464         free(aptr->roomname);
1465         free(aptr->collected_addresses);
1466         free(aptr);
1467 }
1468
1469
1470 /*
1471  * When a user sends a message, we may harvest one or more email addresses
1472  * from the recipient list to be added to the user's address book.  But we
1473  * want to do this asynchronously so it doesn't keep the user waiting.
1474  */
1475 void store_harvested_addresses(void) {
1476
1477         struct addresses_to_be_filed *aptr = NULL;
1478
1479         if (atbf == NULL) return;
1480
1481         begin_critical_section(S_ATBF);
1482         while (atbf != NULL) {
1483                 aptr = atbf;
1484                 atbf = atbf->next;
1485                 end_critical_section(S_ATBF);
1486                 store_this_ha(aptr);
1487                 begin_critical_section(S_ATBF);
1488         }
1489         end_critical_section(S_ATBF);
1490 }
1491
1492
1493 /* 
1494  * Function to output vCard data as plain text.  Nobody uses MSG0 anymore, so
1495  * really this is just so we expose the vCard data to the full text indexer.
1496  */
1497 void vcard_fixed_output(char *ptr, int len) {
1498         char *serialized_vcard;
1499         struct vCard *v;
1500         char *key, *value;
1501         int i = 0;
1502
1503         serialized_vcard = malloc(len + 1);
1504         safestrncpy(serialized_vcard, ptr, len+1);
1505         v = vcard_load(serialized_vcard);
1506         free(serialized_vcard);
1507
1508         i = 0;
1509         while (key = vcard_get_prop(v, "", 0, i, 1), key != NULL) {
1510                 value = vcard_get_prop(v, "", 0, i++, 0);
1511                 cprintf("%s\n", value);
1512         }
1513
1514         vcard_free(v);
1515 }
1516
1517
1518 const char *CitadelServiceDICT_TCP="DICT_TCP";
1519
1520 CTDL_MODULE_INIT(vcard)
1521 {
1522         struct ctdlroom qr;
1523         char filename[256];
1524         FILE *fp;
1525
1526         CtdlRegisterSessionHook(vcard_session_login_hook, EVT_LOGIN);
1527         CtdlRegisterMessageHook(vcard_upload_beforesave, EVT_BEFORESAVE);
1528         CtdlRegisterMessageHook(vcard_upload_aftersave, EVT_AFTERSAVE);
1529         CtdlRegisterDeleteHook(vcard_delete_remove);
1530         CtdlRegisterProtoHook(cmd_regi, "REGI", "Enter registration info");
1531         CtdlRegisterProtoHook(cmd_greg, "GREG", "Get registration info");
1532         CtdlRegisterProtoHook(cmd_igab, "IGAB",
1533                                         "Initialize Global Address Book");
1534         CtdlRegisterProtoHook(cmd_qdir, "QDIR", "Query Directory");
1535         CtdlRegisterProtoHook(cmd_gvsn, "GVSN", "Get Valid Screen Names");
1536         CtdlRegisterProtoHook(cmd_gvea, "GVEA", "Get Valid Email Addresses");
1537         CtdlRegisterProtoHook(cmd_dvca, "DVCA", "Dump VCard Addresses");
1538         CtdlRegisterUserHook(vcard_newuser, EVT_NEWUSER);
1539         CtdlRegisterUserHook(vcard_purge, EVT_PURGEUSER);
1540         CtdlRegisterNetprocHook(vcard_extract_from_network);
1541         CtdlRegisterSessionHook(store_harvested_addresses, EVT_TIMER);
1542         CtdlRegisterFixedOutputHook("text/x-vcard", vcard_fixed_output);
1543         CtdlRegisterFixedOutputHook("text/vcard", vcard_fixed_output);
1544
1545         /* Create the Global ADdress Book room if necessary */
1546         create_room(ADDRESS_BOOK_ROOM, 3, "", 0, 1, 0, VIEW_ADDRESSBOOK);
1547
1548         /* Set expiration policy to manual; otherwise objects will be lost! */
1549         if (!lgetroom(&qr, ADDRESS_BOOK_ROOM)) {
1550                 qr.QRep.expire_mode = EXPIRE_MANUAL;
1551                 qr.QRdefaultview = VIEW_ADDRESSBOOK;    /* 2 = address book view */
1552                 lputroom(&qr);
1553
1554                 /*
1555                  * Also make sure it has a netconfig file, so the networker runs
1556                  * on this room even if we don't share it with any other nodes.
1557                  * This allows the CANCEL messages (i.e. "Purge this vCard") to be
1558                  * purged.
1559                  */
1560                 assoc_file_name(filename, sizeof filename, &qr, ctdl_netcfg_dir);
1561                 fp = fopen(filename, "a");
1562                 if (fp != NULL) fclose(fp);
1563                 chown(filename, CTDLUID, (-1));
1564         }
1565
1566         /* for postfix tcpdict */
1567         CtdlRegisterServiceHook(config.c_pftcpdict_port,        /* Postfix */
1568                                 NULL,
1569                                 check_get_greeting,
1570                                 check_get,
1571                                 NULL,
1572                                 CitadelServiceDICT_TCP);
1573         
1574         /* return our Subversion id for the Log */
1575         return "$Id$";
1576 }