4 * A server-side module for Citadel which supports address book information
5 * using the standard vCard format.
7 * Copyright (c) 1999-2007 / released under the GNU General Public License
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).
16 #define VCARD_EXT_FORMAT "Citadel vCard: personal card for %s at %s"
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*
23 #define VCARD_MIME_TYPE "text/x-vcard"
34 #include <sys/types.h>
36 #if TIME_WITH_SYS_TIME
37 # include <sys/time.h>
41 # include <sys/time.h>
50 #include <libcitadel.h>
53 #include "citserver.h"
62 #include "internet_addressing.h"
63 #include "serv_vcard.h"
65 #include "ctdl_module.h"
70 * set global flag calling for an aide to validate new users
72 void set_mm_valid(void) {
73 begin_critical_section(S_CONTROL
);
75 CitControl
.MMflags
= CitControl
.MMflags
| MM_VALID
;
77 end_critical_section(S_CONTROL
);
83 * Extract Internet e-mail addresses from a message containing a vCard, and
84 * perform a callback for any found.
86 void vcard_extract_internet_addresses(struct CtdlMessage
*msg
,
87 void (*callback
)(char *, char *) ) {
91 char citadel_address
[SIZ
];
93 int found_something
= 0;
95 if (msg
->cm_fields
['A'] == NULL
) return;
96 if (msg
->cm_fields
['N'] == NULL
) return;
97 snprintf(citadel_address
, sizeof citadel_address
, "%s @ %s",
98 msg
->cm_fields
['A'], msg
->cm_fields
['N']);
100 v
= vcard_load(msg
->cm_fields
['M']);
101 if (v
== NULL
) return;
103 /* Go through the vCard searching for *all* instances of
104 * the "email;internet" key
107 s
= vcard_get_prop(v
, "email;internet", 0, instance
++, 0);
111 if (!IsEmptyStr(addr
)) {
112 if (callback
!= NULL
) {
113 callback(addr
, citadel_address
);
122 } while(found_something
);
129 * vCard-to-LDAP conversions.
131 * If 'op' is set to V2L_WRITE, then write
132 * (add, or change if already exists) a directory entry to the
133 * LDAP server, based on the information supplied in a vCard.
135 * If 'op' is set to V2L_DELETE, then delete the entry from LDAP.
139 void ctdl_vcard_to_directory(struct CtdlMessage
*msg
, int op
) {
140 struct vCard
*v
= NULL
;
145 void *objectlist
= NULL
;
157 if (msg
== NULL
) return;
158 if (msg
->cm_fields
['M'] == NULL
) return;
159 if (msg
->cm_fields
['A'] == NULL
) return;
160 if (msg
->cm_fields
['N'] == NULL
) return;
162 /* Initialize variables */
163 strcpy(givenname
, "");
165 strcpy(calFBURL
, "");
167 sprintf(uid
, "%s@%s",
172 sprintf(ldap_dn
, "euid=%s,ou=%s", msg
->cm_fields
['E'], msg
->cm_fields
['N']);
174 /* Are we just deleting? If so, it's simple... */
175 if (op
== V2L_DELETE
) {
176 (void) CtdlDoDirectoryServiceFunc (ldap_dn
, NULL
, NULL
, "ldap", DIRECTORY_USER_DEL
);
181 * If we get to this point then it must be a V2L_WRITE operation.
184 /* First make sure the OU for the user's home Citadel host is created */
185 (void) CtdlDoDirectoryServiceFunc (NULL
, msg
->cm_fields
['N'], NULL
, "ldap", DIRECTORY_CREATE_HOST
);
187 /* Next create the directory service object */
188 (void) CtdlDoDirectoryServiceFunc(NULL
, NULL
, &objectlist
, "ldap", DIRECTORY_CREATE_OBJECT
);
190 /* The first LDAP attribute will be an 'objectclass' list. Citadel
191 * doesn't do anything with this. It's just there for compatibility
194 (void) CtdlDoDirectoryServiceFunc("objectclass", "citadelInetOrgPerson", &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
196 /* Convert the vCard fields to LDAP properties */
197 v
= vcard_load(msg
->cm_fields
['M']);
198 if (v
->numprops
) for (i
=0; i
<(v
->numprops
); ++i
) if (striplt(v
->prop
[i
].value
), strlen(v
->prop
[i
].value
) > 0) {
200 if (!strcasecmp(v
->prop
[i
].name
, "n")) {
201 extract_token(sn
, v
->prop
[i
].value
, 0, ';', sizeof sn
);
202 extract_token(givenname
, v
->prop
[i
].value
, 1, ';', sizeof givenname
);
205 if (!strcasecmp(v
->prop
[i
].name
, "fn")) {
206 (void) CtdlDoDirectoryServiceFunc("cn", v
->prop
[i
].value
, &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
210 if (!strcasecmp(v
->prop
[i
].name
, "title")) {
211 (void) CtdlDoDirectoryServiceFunc("title", v
->prop
[i
].value
, &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
214 if (!strcasecmp(v
->prop
[i
].name
, "org")) {
215 (void) CtdlDoDirectoryServiceFunc("o", v
->prop
[i
].value
, &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
218 if ( (!strcasecmp(v
->prop
[i
].name
, "adr"))
219 ||(!strncasecmp(v
->prop
[i
].name
, "adr;", 4)) ) {
220 /* Unfortunately, we can only do a single address */
224 extract_token(&street
[strlen(street
)],
225 v
->prop
[i
].value
, 0, ';', (sizeof street
- strlen(street
))); /* po box */
227 extract_token(&street
[strlen(street
)],
228 v
->prop
[i
].value
, 1, ';', (sizeof street
- strlen(street
))); /* extend addr */
230 extract_token(&street
[strlen(street
)],
231 v
->prop
[i
].value
, 2, ';', (sizeof street
- strlen(street
))); /* street */
233 extract_token(city
, v
->prop
[i
].value
, 3, ';', sizeof city
);
234 extract_token(state
, v
->prop
[i
].value
, 4, ';', sizeof state
);
235 extract_token(zipcode
, v
->prop
[i
].value
, 5, ';', sizeof zipcode
);
237 // ldap requires these fields to be something
238 if (IsEmptyStr(street
)) strcpy(street
, "_");
239 if (IsEmptyStr(zipcode
)) strcpy(zipcode
, "_");
240 if (IsEmptyStr(city
)) strcpy(city
, "_");
241 if (IsEmptyStr(state
)) strcpy(state
, "_");
243 (void) CtdlDoDirectoryServiceFunc("street", street
, &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
244 (void) CtdlDoDirectoryServiceFunc("l", city
, &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
245 (void) CtdlDoDirectoryServiceFunc("st", state
, &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
246 (void) CtdlDoDirectoryServiceFunc("postalcode", zipcode
, &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
250 if ( (!strcasecmp(v
->prop
[i
].name
, "tel;home"))
251 || (!strcasecmp(v
->prop
[i
].name
, "tel;type=home")) )
252 (void) CtdlDoDirectoryServiceFunc("homePhone", v
->prop
[i
].value
, &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
254 if ( (!strcasecmp(v
->prop
[i
].name
, "tel;fax"))
255 || (!strcasecmp(v
->prop
[i
].name
, "tel;type=fax")) )
256 (void) CtdlDoDirectoryServiceFunc("facsimileTelephoneNumber", v
->prop
[i
].value
, &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
258 if ( (!strcasecmp(v
->prop
[i
].name
, "tel;cell"))
259 || (!strcasecmp(v
->prop
[i
].name
, "tel;type=cell")) )
260 (void) CtdlDoDirectoryServiceFunc("mobile", v
->prop
[i
].value
, &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
262 if ( (!strcasecmp(v
->prop
[i
].name
, "tel"))
263 ||(!strncasecmp(v
->prop
[i
].name
, "tel;", 4)) ) {
264 (void) CtdlDoDirectoryServiceFunc("telephoneNumber", v
->prop
[i
].value
, &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
268 if ( (!strcasecmp(v
->prop
[i
].name
, "email"))
269 ||(!strcasecmp(v
->prop
[i
].name
, "email;internet")) ) {
270 (void) CtdlDoDirectoryServiceFunc("mail", v
->prop
[i
].value
, &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
273 /* Calendar free/busy URL (take the first one we find, but if a subsequent
274 * one contains the "pref" designation then we go with that instead.)
276 if ( (!strcasecmp(v
->prop
[i
].name
, "fburl"))
277 ||(!strncasecmp(v
->prop
[i
].name
, "fburl;", 6)) ) {
278 if ( (IsEmptyStr(calFBURL
))
279 || (!strncasecmp(v
->prop
[i
].name
, "fburl;pref", 10)) ) {
280 safestrncpy(calFBURL
, v
->prop
[i
].value
, sizeof calFBURL
);
285 vcard_free(v
); /* Don't need this anymore. */
287 /* "sn" (surname) based on info in vCard */
288 (void) CtdlDoDirectoryServiceFunc("sn", sn
, &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
290 /* "givenname" (first name) based on info in vCard */
291 if (IsEmptyStr(givenname
)) strcpy(givenname
, "_");
292 if (IsEmptyStr(sn
)) strcpy(sn
, "_");
293 (void) CtdlDoDirectoryServiceFunc("givenname", givenname
, &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
295 /* "uid" is a Kolab compatibility thing. We just do cituser@citnode */
296 (void) CtdlDoDirectoryServiceFunc("uid", uid
, &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
298 /* Add a "cn" (Common Name) attribute based on the user's screen name,
299 * but only there was no 'fn' (full name) property in the vCard
302 (void) CtdlDoDirectoryServiceFunc("cn", msg
->cm_fields
['A'], &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
304 /* Add a "calFBURL" attribute if a calendar free/busy URL exists */
305 if (!IsEmptyStr(calFBURL
)) {
306 (void) CtdlDoDirectoryServiceFunc("calFBURL", calFBURL
, &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
309 // Add this messages EUID as the primary key for this entry.
310 (void) CtdlDoDirectoryServiceFunc("euid", msg
->cm_fields
['E'], &objectlist
, "ldap", DIRECTORY_ATTRIB_ADD
);
313 (void) CtdlDoDirectoryServiceFunc(ldap_dn
, NULL
, &objectlist
, "ldap", DIRECTORY_SAVE_OBJECT
);
315 (void) CtdlDoDirectoryServiceFunc(NULL
, NULL
, &objectlist
, "ldap", DIRECTORY_FREE_OBJECT
);
316 CtdlLogPrintf(CTDL_DEBUG
, "Directory Services write operation complete.\n");
322 * Callback for vcard_add_to_directory()
323 * (Lotsa ugly nested callbacks. Oh well.)
325 void vcard_directory_add_user(char *internet_addr
, char *citadel_addr
) {
328 /* We have to validate that we're not stepping on someone else's
329 * email address ... but only if we're logged in. Otherwise it's
330 * probably just the networker or something.
333 CtdlLogPrintf(CTDL_DEBUG
, "Checking for <%s>...\n", internet_addr
);
334 if (CtdlDirectoryLookup(buf
, internet_addr
, sizeof buf
) == 0) {
335 if (strcasecmp(buf
, citadel_addr
)) {
336 /* This address belongs to someone else.
337 * Bail out silently without saving.
339 CtdlLogPrintf(CTDL_DEBUG
, "DOOP!\n");
344 CtdlLogPrintf(CTDL_INFO
, "Adding %s (%s) to directory\n",
345 citadel_addr
, internet_addr
);
346 CtdlDirectoryAddUser(internet_addr
, citadel_addr
);
351 * Back end function for cmd_igab()
353 void vcard_add_to_directory(long msgnum
, void *data
) {
354 struct CtdlMessage
*msg
;
356 msg
= CtdlFetchMessage(msgnum
, 1);
358 vcard_extract_internet_addresses(msg
, vcard_directory_add_user
);
361 ctdl_vcard_to_directory(msg
, V2L_WRITE
);
363 CtdlFreeMessage(msg
);
368 * Initialize Global Adress Book
370 void cmd_igab(char *argbuf
) {
371 char hold_rm
[ROOMNAMELEN
];
373 if (CtdlAccessCheck(ac_aide
)) return;
375 strcpy(hold_rm
, CC
->room
.QRname
); /* save current room */
377 if (getroom(&CC
->room
, ADDRESS_BOOK_ROOM
) != 0) {
378 getroom(&CC
->room
, hold_rm
);
379 cprintf("%d cannot get address book room\n", ERROR
+ ROOM_NOT_FOUND
);
383 /* Empty the existing database first.
387 /* We want *all* vCards in this room */
388 CtdlForEachMessage(MSGS_ALL
, 0, NULL
, "^[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$",
389 NULL
, vcard_add_to_directory
, NULL
);
391 getroom(&CC
->room
, hold_rm
); /* return to saved room */
392 cprintf("%d Directory has been rebuilt.\n", CIT_OK
);
399 * See if there is a valid Internet address in a vCard to use for outbound
400 * Internet messages. If there is, stick it in the buffer.
402 void extract_inet_email_addrs(char *emailaddrbuf
, size_t emailaddrbuf_len
,
403 char *secemailaddrbuf
, size_t secemailaddrbuf_len
,
404 struct vCard
*v
, int local_addrs_only
) {
407 int saved_instance
= 0;
409 /* Go through the vCard searching for *all* instances of
410 * the "email;internet" key
412 while (s
= vcard_get_prop(v
, "email;internet", 0, instance
++, 0), s
!= NULL
) {
415 if (!IsEmptyStr(addr
)) {
416 if ( (IsDirectory(addr
, 1)) ||
417 (!local_addrs_only
) ) {
419 if ((saved_instance
== 1) && (emailaddrbuf
!= NULL
)) {
420 safestrncpy(emailaddrbuf
, addr
, emailaddrbuf_len
);
422 else if ((saved_instance
== 2) && (secemailaddrbuf
!= NULL
)) {
423 safestrncpy(secemailaddrbuf
, addr
, secemailaddrbuf_len
);
425 else if ((saved_instance
> 2) && (secemailaddrbuf
!= NULL
)) {
426 if ( (strlen(addr
) + strlen(secemailaddrbuf
) + 2)
427 < secemailaddrbuf_len
) {
428 strcat(secemailaddrbuf
, "|");
429 strcat(secemailaddrbuf
, addr
);
441 * See if there is a name / screen name / friendly name in a vCard to use for outbound
442 * Internet messages. If there is, stick it in the buffer.
444 void extract_friendly_name(char *namebuf
, size_t namebuf_len
, struct vCard
*v
)
448 s
= vcard_get_prop(v
, "fn", 0, 0, 0);
450 s
= vcard_get_prop(v
, "n", 0, 0, 0);
454 safestrncpy(namebuf
, s
, namebuf_len
);
460 * Callback function for vcard_upload_beforesave() hunts for the real vcard in the MIME structure
462 void vcard_extract_vcard(char *name
, char *filename
, char *partnum
, char *disp
,
463 void *content
, char *cbtype
, char *cbcharset
, size_t length
,
464 char *encoding
, char *cbid
, void *cbuserdata
)
466 struct vCard
**v
= (struct vCard
**) cbuserdata
;
468 if ( (!strcasecmp(cbtype
, "text/x-vcard"))
469 || (!strcasecmp(cbtype
, "text/vcard")) ) {
471 CtdlLogPrintf(CTDL_DEBUG
, "Part %s contains a vCard! Loading...\n", partnum
);
475 *v
= vcard_load(content
);
481 * This handler detects whether the user is attempting to save a new
482 * vCard as part of his/her personal configuration, and handles the replace
483 * function accordingly (delete the user's existing vCard in the config room
484 * and in the global address book).
486 int vcard_upload_beforesave(struct CtdlMessage
*msg
) {
490 struct ctdluser usbuf
;
492 struct vCard
*v
= NULL
;
495 int yes_my_citadel_config
= 0;
496 int yes_any_vcard_room
= 0;
498 if (!CC
->logged_in
) return(0); /* Only do this if logged in. */
500 /* Is this some user's "My Citadel Config" room? */
501 if ( (CC
->room
.QRflags
&& QR_MAILBOX
)
502 && (!strcasecmp(&CC
->room
.QRname
[11], USERCONFIGROOM
)) ) {
503 /* Yes, we want to do this */
504 yes_my_citadel_config
= 1;
506 #ifdef VCARD_SAVES_BY_AIDES_ONLY
507 /* Prevent non-aides from performing registration changes */
508 if (CC
->user
.axlevel
< 6) {
515 /* Is this a room with an address book in it? */
516 if (CC
->room
.QRdefaultview
== VIEW_ADDRESSBOOK
) {
517 yes_any_vcard_room
= 1;
520 /* If neither condition exists, don't run this hook. */
521 if ( (!yes_my_citadel_config
) && (!yes_any_vcard_room
) ) {
525 /* If this isn't a MIME message, don't bother. */
526 if (msg
->cm_format_type
!= 4) return(0);
528 /* Ok, if we got this far, look into the situation further... */
530 ptr
= msg
->cm_fields
['M'];
531 if (ptr
== NULL
) return(0);
533 mime_parser(msg
->cm_fields
['M'],
535 *vcard_extract_vcard
,
537 &v
, /* user data ptr - put the vcard here */
541 if (v
== NULL
) return(0); /* no vCards were found in this message */
543 /* If users cannot create their own accounts, they cannot re-register either. */
544 if ( (yes_my_citadel_config
) && (config
.c_disable_newu
) && (CC
->user
.axlevel
< 6) ) {
548 s
= vcard_get_prop(v
, "FN", 0, 0, 0);
549 if (s
) CtdlLogPrintf(CTDL_DEBUG
, "vCard beforesave hook running for <%s>\n", s
);
551 if (yes_my_citadel_config
) {
552 /* Bingo! The user is uploading a new vCard, so
553 * delete the old one. First, figure out which user
554 * is being re-registered...
556 what_user
= atol(CC
->room
.QRname
);
558 if (what_user
== CC
->user
.usernum
) {
559 /* It's the logged in user. That was easy. */
560 memcpy(&usbuf
, &CC
->user
, sizeof(struct ctdluser
));
563 else if (getuserbynumber(&usbuf
, what_user
) == 0) {
564 /* We fetched a valid user record */
568 /* somebody set up us the bomb! */
569 yes_my_citadel_config
= 0;
573 if (yes_my_citadel_config
) {
574 /* Delete the user's old vCard. This would probably
575 * get taken care of by the replication check, but we
576 * want to make sure there is absolutely only one
577 * vCard in the user's config room at all times.
580 CtdlDeleteMessages(CC
->room
.QRname
, NULL
, 0, "^[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$");
582 /* Make the author of the message the name of the user. */
583 if (msg
->cm_fields
['A'] != NULL
) {
584 free(msg
->cm_fields
['A']);
586 msg
->cm_fields
['A'] = strdup(usbuf
.fullname
);
589 /* Insert or replace RFC2739-compliant free/busy URL */
590 if (yes_my_citadel_config
) {
591 sprintf(buf
, "http://%s/%s.vfb",
594 for (i
=0; buf
[i
]; ++i
) {
595 if (buf
[i
] == ' ') buf
[i
] = '_';
597 vcard_set_prop(v
, "FBURL;PREF", buf
, 0);
600 /* If the vCard has no UID, then give it one. */
601 s
= vcard_get_prop(v
, "UID", 0, 0, 0);
604 vcard_set_prop(v
, "UID", buf
, 0);
607 /* Enforce local UID policy if applicable */
608 if (yes_my_citadel_config
) {
609 snprintf(buf
, sizeof buf
, VCARD_EXT_FORMAT
, msg
->cm_fields
['A'], NODENAME
);
610 vcard_set_prop(v
, "UID", buf
, 0);
614 * Set the EUID of the message to the UID of the vCard.
616 if (msg
->cm_fields
['E'] != NULL
)
618 free(msg
->cm_fields
['E']);
619 msg
->cm_fields
['E'] = NULL
;
621 s
= vcard_get_prop(v
, "UID", 0, 0, 0);
623 msg
->cm_fields
['E'] = strdup(s
);
624 if (msg
->cm_fields
['U'] == NULL
) {
625 msg
->cm_fields
['U'] = strdup(s
);
630 * Set the Subject to the name in the vCard.
632 s
= vcard_get_prop(v
, "FN", 0, 0, 0);
634 s
= vcard_get_prop(v
, "N", 0, 0, 0);
637 if (msg
->cm_fields
['U'] != NULL
) {
638 free(msg
->cm_fields
['U']);
640 msg
->cm_fields
['U'] = strdup(s
);
643 /* Re-serialize it back into the msg body */
644 ser
= vcard_serialize(v
);
646 msg
->cm_fields
['M'] = realloc(msg
->cm_fields
['M'], strlen(ser
) + 1024);
647 sprintf(msg
->cm_fields
['M'],
648 "Content-type: " VCARD_MIME_TYPE
649 "\r\n\r\n%s\r\n", ser
);
653 /* Now allow the save to complete. */
661 * This handler detects whether the user is attempting to save a new
662 * vCard as part of his/her personal configuration, and handles the replace
663 * function accordingly (copy the vCard from the config room to the global
666 int vcard_upload_aftersave(struct CtdlMessage
*msg
) {
674 if (!CC
->logged_in
) return(0); /* Only do this if logged in. */
676 /* If this isn't the configuration room, or if this isn't a MIME
677 * message, don't bother.
679 if (msg
->cm_fields
['O'] == NULL
) return(0);
680 if (!strcasecmp(msg
->cm_fields
['O'], USERCONFIGROOM
)) is_UserConf
= 1;
681 if (!strcasecmp(msg
->cm_fields
['O'], ADDRESS_BOOK_ROOM
)) is_GAB
= 1;
682 if (!is_UserConf
&& !is_GAB
) return(0);
683 if (msg
->cm_format_type
!= 4) return(0);
685 ptr
= msg
->cm_fields
['M'];
686 if (ptr
== NULL
) return(0);
687 while (ptr
!= NULL
) {
689 linelen
= strcspn(ptr
, "\n");
690 if (linelen
== 0) return(0); /* end of headers */
692 if ( (!strncasecmp(ptr
, "Content-type: text/x-vcard", 26))
693 || (!strncasecmp(ptr
, "Content-type: text/vcard", 24)) ) {
695 * Bingo! The user is uploading a new vCard, so
696 * copy it to the Global Address Book room.
699 I
= atol(msg
->cm_fields
['I']);
700 if (I
< 0L) return(0);
702 /* Store our Internet return address in memory */
703 v
= vcard_load(msg
->cm_fields
['M']);
704 extract_inet_email_addrs(CC
->cs_inet_email
, sizeof CC
->cs_inet_email
,
705 CC
->cs_inet_other_emails
, sizeof CC
->cs_inet_other_emails
,
707 extract_friendly_name(CC
->cs_inet_fn
, sizeof CC
->cs_inet_fn
, v
);
711 { // This is not the GAB
712 /* Put it in the Global Address Book room... */
713 CtdlSaveMsgPointerInRoom(ADDRESS_BOOK_ROOM
, I
, 1, msg
);
716 /* ...and also in the directory database. */
717 vcard_add_to_directory(I
, NULL
);
719 /* Some sites want an Aide to be notified when a
720 * user registers or re-registers
721 * But if the user was an Aide or was edited by an Aide then we can
722 * Assume they don't need validating.
724 if (CC
->user
.axlevel
>= 6) {
725 lgetuser(&CC
->user
, CC
->curr_user
);
726 CC
->user
.flags
|= US_REGIS
;
733 /* ...which also means we need to flag the user */
734 lgetuser(&CC
->user
, CC
->curr_user
);
735 CC
->user
.flags
|= (US_REGIS
|US_NEEDVALID
);
741 ptr
= strchr((char *)ptr
, '\n');
742 if (ptr
!= NULL
) ++ptr
;
751 * back end function used for callbacks
753 void vcard_gu_backend(long supplied_msgnum
, void *userdata
) {
756 msgnum
= (long *) userdata
;
757 *msgnum
= supplied_msgnum
;
762 * If this user has a vcard on disk, read it into memory, otherwise allocate
763 * and return an empty vCard.
765 struct vCard
*vcard_get_user(struct ctdluser
*u
) {
766 char hold_rm
[ROOMNAMELEN
];
767 char config_rm
[ROOMNAMELEN
];
768 struct CtdlMessage
*msg
= NULL
;
772 strcpy(hold_rm
, CC
->room
.QRname
); /* save current room */
773 MailboxName(config_rm
, sizeof config_rm
, u
, USERCONFIGROOM
);
775 if (getroom(&CC
->room
, config_rm
) != 0) {
776 getroom(&CC
->room
, hold_rm
);
780 /* We want the last (and probably only) vcard in this room */
782 CtdlForEachMessage(MSGS_LAST
, 1, NULL
, "^[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$",
783 NULL
, vcard_gu_backend
, (void *)&VCmsgnum
);
784 getroom(&CC
->room
, hold_rm
); /* return to saved room */
786 if (VCmsgnum
< 0L) return vcard_new();
788 msg
= CtdlFetchMessage(VCmsgnum
, 1);
789 if (msg
== NULL
) return vcard_new();
791 v
= vcard_load(msg
->cm_fields
['M']);
792 CtdlFreeMessage(msg
);
798 * Store this user's vCard in the appropriate place
801 * Write our config to disk
803 void vcard_write_user(struct ctdluser
*u
, struct vCard
*v
) {
806 ser
= vcard_serialize(v
);
808 ser
= strdup("begin:vcard\r\nend:vcard\r\n");
812 /* This handy API function does all the work for us.
813 * NOTE: normally we would want to set that last argument to 1, to
814 * force the system to delete the user's old vCard. But it doesn't
815 * have to, because the vcard_upload_beforesave() hook above
816 * is going to notice what we're trying to do, and delete the old vCard.
818 CtdlWriteObject(USERCONFIGROOM
, /* which room */
819 VCARD_MIME_TYPE
, /* MIME type */
821 strlen(ser
)+1, /* length */
824 0, /* don't delete others of this type */
833 * Old style "enter registration info" command. This function simply honors
834 * the REGI protocol command, translates the entered parameters into a vCard,
835 * and enters the vCard into the user's configuration.
837 void cmd_regi(char *argbuf
) {
840 struct vCard
*my_vcard
;
846 char tmpaddress
[SIZ
];
847 char tmpcountry
[SIZ
];
851 if (!(CC
->logged_in
)) {
852 cprintf("%d Not logged in.\n",ERROR
+ NOT_LOGGED_IN
);
856 /* If users cannot create their own accounts, they cannot re-register either. */
857 if ( (config
.c_disable_newu
) && (CC
->user
.axlevel
< 6) ) {
858 cprintf("%d Self-service registration is not allowed here.\n",
859 ERROR
+ HIGHER_ACCESS_REQUIRED
);
862 my_vcard
= vcard_get_user(&CC
->user
);
865 strcpy(tmpstate
, "");
867 strcpy(tmpcountry
, "USA");
869 cprintf("%d Send registration...\n", SEND_LISTING
);
871 while (client_getln(buf
, sizeof buf
), strcmp(buf
,"000")) {
872 if (a
==0) vcard_set_prop(my_vcard
, "n", buf
, 0);
873 if (a
==1) strcpy(tmpaddr
, buf
);
874 if (a
==2) strcpy(tmpcity
, buf
);
875 if (a
==3) strcpy(tmpstate
, buf
);
877 for (c
=0; buf
[c
]; ++c
) {
878 if ((buf
[c
]>='0') && (buf
[c
]<='9')) {
885 if (a
==5) vcard_set_prop(my_vcard
, "tel", buf
, 0);
886 if (a
==6) vcard_set_prop(my_vcard
, "email;internet", buf
, 0);
887 if (a
==7) strcpy(tmpcountry
, buf
);
891 snprintf(tmpaddress
, sizeof tmpaddress
, ";;%s;%s;%s;%s;%s",
892 tmpaddr
, tmpcity
, tmpstate
, tmpzip
, tmpcountry
);
893 vcard_set_prop(my_vcard
, "adr", tmpaddress
, 0);
894 vcard_write_user(&CC
->user
, my_vcard
);
895 vcard_free(my_vcard
);
900 * Protocol command to fetch registration info for a user
902 void cmd_greg(char *argbuf
)
904 struct ctdluser usbuf
;
907 char who
[USERNAME_SIZE
];
911 extract_token(who
, argbuf
, 0, '|', sizeof who
);
913 if (!(CC
->logged_in
)) {
914 cprintf("%d Not logged in.\n", ERROR
+ NOT_LOGGED_IN
);
918 if (!strcasecmp(who
,"_SELF_")) strcpy(who
,CC
->curr_user
);
920 if ((CC
->user
.axlevel
< 6) && (strcasecmp(who
,CC
->curr_user
))) {
921 cprintf("%d Higher access required.\n",
922 ERROR
+ HIGHER_ACCESS_REQUIRED
);
926 if (getuser(&usbuf
, who
) != 0) {
927 cprintf("%d '%s' not found.\n", ERROR
+ NO_SUCH_USER
, who
);
931 v
= vcard_get_user(&usbuf
);
933 cprintf("%d %s\n", LISTING_FOLLOWS
, usbuf
.fullname
);
934 cprintf("%ld\n", usbuf
.usernum
);
935 cprintf("%s\n", usbuf
.password
);
936 s
= vcard_get_prop(v
, "n", 0, 0, 0);
937 cprintf("%s\n", s
? s
: " "); /* name */
939 s
= vcard_get_prop(v
, "adr", 0, 0, 0);
940 snprintf(adr
, sizeof adr
, "%s", s
? s
: " ");/* address... */
942 extract_token(buf
, adr
, 2, ';', sizeof buf
);
943 cprintf("%s\n", buf
); /* street */
944 extract_token(buf
, adr
, 3, ';', sizeof buf
);
945 cprintf("%s\n", buf
); /* city */
946 extract_token(buf
, adr
, 4, ';', sizeof buf
);
947 cprintf("%s\n", buf
); /* state */
948 extract_token(buf
, adr
, 5, ';', sizeof buf
);
949 cprintf("%s\n", buf
); /* zip */
951 s
= vcard_get_prop(v
, "tel", 0, 0, 0);
952 if (s
== NULL
) s
= vcard_get_prop(v
, "tel", 1, 0, 0);
960 cprintf("%d\n", usbuf
.axlevel
);
962 s
= vcard_get_prop(v
, "email;internet", 0, 0, 0);
963 cprintf("%s\n", s
? s
: " ");
964 s
= vcard_get_prop(v
, "adr", 0, 0, 0);
965 snprintf(adr
, sizeof adr
, "%s", s
? s
: " ");/* address... */
967 extract_token(buf
, adr
, 6, ';', sizeof buf
);
968 cprintf("%s\n", buf
); /* country */
976 * When a user is being created, create his/her vCard.
978 void vcard_newuser(struct ctdluser
*usbuf
) {
984 vcard_fn_to_n(vname
, usbuf
->fullname
, sizeof vname
);
985 CtdlLogPrintf(CTDL_DEBUG
, "Converted <%s> to <%s>\n", usbuf
->fullname
, vname
);
987 /* Create and save the vCard */
989 if (v
== NULL
) return;
990 vcard_add_prop(v
, "fn", usbuf
->fullname
);
991 vcard_add_prop(v
, "n", vname
);
992 vcard_add_prop(v
, "adr", "adr:;;_;_;_;00000;__");
994 #ifdef HAVE_GETPWUID_R
995 /* If using host auth mode, we add an email address based on the login */
996 if (config
.c_auth_mode
== AUTHMODE_HOST
) {
998 char pwd_buffer
[SIZ
];
1000 #ifdef SOLARIS_GETPWUID
1001 if (getpwuid_r(usbuf
->uid
, &pwd
, pwd_buffer
, sizeof pwd_buffer
) != NULL
) {
1002 #else // SOLARIS_GETPWUID
1003 struct passwd
*result
= NULL
;
1004 CtdlLogPrintf(CTDL_DEBUG
, "Searching for uid %d\n", usbuf
->uid
);
1005 if (getpwuid_r(usbuf
->uid
, &pwd
, pwd_buffer
, sizeof pwd_buffer
, &result
) == 0) {
1006 #endif // HAVE_GETPWUID_R
1007 snprintf(buf
, sizeof buf
, "%s@%s", pwd
.pw_name
, config
.c_fqdn
);
1008 vcard_add_prop(v
, "email;internet", buf
);
1013 /* Everyone gets an email address based on their display name */
1014 snprintf(buf
, sizeof buf
, "%s@%s", usbuf
->fullname
, config
.c_fqdn
);
1015 for (i
=0; buf
[i
]; ++i
) {
1016 if (buf
[i
] == ' ') buf
[i
] = '_';
1018 vcard_add_prop(v
, "email;internet", buf
);
1021 vcard_write_user(usbuf
, v
);
1027 * When a user is being deleted, we have to remove his/her vCard.
1028 * This is accomplished by issuing a message with 'CANCEL' in the S (special)
1029 * field, and the same Exclusive ID as the existing card.
1031 void vcard_purge(struct ctdluser
*usbuf
) {
1032 struct CtdlMessage
*msg
;
1035 msg
= (struct CtdlMessage
*) malloc(sizeof(struct CtdlMessage
));
1036 if (msg
== NULL
) return;
1037 memset(msg
, 0, sizeof(struct CtdlMessage
));
1039 msg
->cm_magic
= CTDLMESSAGE_MAGIC
;
1040 msg
->cm_anon_type
= MES_NORMAL
;
1041 msg
->cm_format_type
= 0;
1042 msg
->cm_fields
['A'] = strdup(usbuf
->fullname
);
1043 msg
->cm_fields
['O'] = strdup(ADDRESS_BOOK_ROOM
);
1044 msg
->cm_fields
['N'] = strdup(NODENAME
);
1045 msg
->cm_fields
['M'] = strdup("Purge this vCard\n");
1047 snprintf(buf
, sizeof buf
, VCARD_EXT_FORMAT
,
1048 msg
->cm_fields
['A'], NODENAME
);
1049 msg
->cm_fields
['E'] = strdup(buf
);
1051 msg
->cm_fields
['S'] = strdup("CANCEL");
1053 CtdlSubmitMsg(msg
, NULL
, ADDRESS_BOOK_ROOM
, QP_EADDR
);
1054 CtdlFreeMessage(msg
);
1059 * Grab vCard directory stuff out of incoming network messages
1061 int vcard_extract_from_network(struct CtdlMessage
*msg
, char *target_room
) {
1065 if (msg
== NULL
) return(0);
1067 if (strcasecmp(target_room
, ADDRESS_BOOK_ROOM
)) {
1071 if (msg
->cm_format_type
!= 4) return(0);
1073 ptr
= msg
->cm_fields
['M'];
1074 if (ptr
== NULL
) return(0);
1075 while (ptr
!= NULL
) {
1077 linelen
= strcspn(ptr
, "\n");
1078 if (linelen
== 0) return(0); /* end of headers */
1080 if ( (!strncasecmp(ptr
, "Content-type: text/x-vcard", 26))
1081 || (!strncasecmp(ptr
, "Content-type: text/vcard", 24)) ) {
1082 /* It's a vCard. Add it to the directory. */
1083 vcard_extract_internet_addresses(msg
, CtdlDirectoryAddUser
);
1087 ptr
= strchr((char *)ptr
, '\n');
1088 if (ptr
!= NULL
) ++ptr
;
1097 * When a vCard is being removed from the Global Address Book room, remove it
1098 * from the directory as well.
1100 void vcard_delete_remove(char *room
, long msgnum
) {
1101 struct CtdlMessage
*msg
;
1105 if (msgnum
<= 0L) return;
1107 if (room
== NULL
) return;
1109 if (strcasecmp(room
, ADDRESS_BOOK_ROOM
)) {
1113 msg
= CtdlFetchMessage(msgnum
, 1);
1114 if (msg
== NULL
) return;
1116 ptr
= msg
->cm_fields
['M'];
1117 if (ptr
== NULL
) goto EOH
;
1118 while (ptr
!= NULL
) {
1119 linelen
= strcspn(ptr
, "\n");
1120 if (linelen
== 0) goto EOH
;
1122 if ( (!strncasecmp(ptr
, "Content-type: text/x-vcard", 26))
1123 || (!strncasecmp(ptr
, "Content-type: text/vcard", 24)) ) {
1124 /* Bingo! A vCard is being deleted. */
1125 vcard_extract_internet_addresses(msg
, CtdlDirectoryDelUser
);
1126 ctdl_vcard_to_directory(msg
, V2L_DELETE
);
1128 ptr
= strchr((char *)ptr
, '\n');
1129 if (ptr
!= NULL
) ++ptr
;
1132 EOH
: CtdlFreeMessage(msg
);
1138 * Get Valid Screen Names
1140 void cmd_gvsn(char *argbuf
)
1142 if (CtdlAccessCheck(ac_logged_in
)) return;
1144 cprintf("%d valid screen names:\n", LISTING_FOLLOWS
);
1145 cprintf("%s\n", CC
->user
.fullname
);
1146 if ( (!IsEmptyStr(CC
->cs_inet_fn
)) && (strcasecmp(CC
->user
.fullname
, CC
->cs_inet_fn
)) ) {
1147 cprintf("%s\n", CC
->cs_inet_fn
);
1154 * Get Valid Email Addresses
1156 void cmd_gvea(char *argbuf
)
1158 int num_secondary_emails
= 0;
1162 if (CtdlAccessCheck(ac_logged_in
)) return;
1164 cprintf("%d valid email addresses:\n", LISTING_FOLLOWS
);
1165 if (!IsEmptyStr(CC
->cs_inet_email
)) {
1166 cprintf("%s\n", CC
->cs_inet_email
);
1168 if (!IsEmptyStr(CC
->cs_inet_other_emails
)) {
1169 num_secondary_emails
= num_tokens(CC
->cs_inet_other_emails
, '|');
1170 for (i
=0; i
<num_secondary_emails
; ++i
) {
1171 extract_token(buf
, CC
->cs_inet_other_emails
,i
,'|',sizeof CC
->cs_inet_other_emails
);
1172 cprintf("%s\n", buf
);
1182 * Callback function for cmd_dvca() that hunts for vCard content types
1183 * and outputs any email addresses found within.
1185 void dvca_mime_callback(char *name
, char *filename
, char *partnum
, char *disp
,
1186 void *content
, char *cbtype
, char *cbcharset
, size_t length
, char *encoding
,
1187 char *cbid
, void *cbuserdata
) {
1190 char displayname
[256];
1191 int displayname_len
;
1192 char emailaddr
[256];
1196 if ( (strcasecmp(cbtype
, "text/vcard")) && (strcasecmp(cbtype
, "text/x-vcard")) ) {
1200 v
= vcard_load(content
);
1201 if (v
== NULL
) return;
1203 extract_friendly_name(displayname
, sizeof displayname
, v
);
1204 extract_inet_email_addrs(emailaddr
, sizeof emailaddr
, NULL
, 0, v
, 0);
1206 displayname_len
= strlen(displayname
);
1207 for (i
=0; i
<displayname_len
; ++i
) {
1208 if (displayname
[i
] == '\"') displayname
[i
] = ' ';
1209 if (displayname
[i
] == ';') displayname
[i
] = ',';
1210 if (displayname
[i
] == ',') has_commas
= 1;
1212 striplt(displayname
);
1214 cprintf("%s%s%s <%s>\n",
1215 (has_commas
? "\"" : ""),
1217 (has_commas
? "\"" : ""),
1226 * Back end callback function for cmd_dvca()
1228 * It's basically just passed a list of message numbers, which we're going
1229 * to fetch off the disk and then pass along to the MIME parser via another
1230 * layer of callback...
1232 void dvca_callback(long msgnum
, void *userdata
) {
1233 struct CtdlMessage
*msg
= NULL
;
1235 msg
= CtdlFetchMessage(msgnum
, 1);
1236 if (msg
== NULL
) return;
1237 mime_parser(msg
->cm_fields
['M'],
1239 *dvca_mime_callback
, /* callback function */
1241 NULL
, /* user data */
1244 CtdlFreeMessage(msg
);
1249 * Dump VCard Addresses
1251 void cmd_dvca(char *argbuf
)
1253 if (CtdlAccessCheck(ac_logged_in
)) return;
1255 cprintf("%d addresses:\n", LISTING_FOLLOWS
);
1256 CtdlForEachMessage(MSGS_ALL
, 0, NULL
, NULL
, NULL
, dvca_callback
, NULL
);
1264 void cmd_qdir(char *argbuf
) {
1265 char citadel_addr
[256];
1266 char internet_addr
[256];
1268 if (CtdlAccessCheck(ac_logged_in
)) return;
1270 extract_token(internet_addr
, argbuf
, 0, '|', sizeof internet_addr
);
1272 if (CtdlDirectoryLookup(citadel_addr
, internet_addr
, sizeof citadel_addr
) != 0) {
1273 cprintf("%d %s was not found.\n",
1274 ERROR
+ NO_SUCH_USER
, internet_addr
);
1278 cprintf("%d %s\n", CIT_OK
, citadel_addr
);
1282 * Query Directory, in fact an alias to match postfix tcp auth.
1284 void check_get(void) {
1285 char internet_addr
[256];
1290 memset(cmdbuf
, 0, sizeof cmdbuf
); /* Clear it, just in case */
1291 if (client_getln(cmdbuf
, sizeof cmdbuf
) < 1) {
1292 CtdlLogPrintf(CTDL_CRIT
, "Client disconnected: ending session.\n");
1296 CtdlLogPrintf(CTDL_INFO
, ": %s\n", cmdbuf
);
1297 while (strlen(cmdbuf
) < 3) strcat(cmdbuf
, " ");
1299 if (strcasecmp(cmdbuf
, "GET "));
1301 struct recptypes
*rcpt
;
1302 char *argbuf
= &cmdbuf
[4];
1304 extract_token(internet_addr
, argbuf
, 0, '|', sizeof internet_addr
);
1305 rcpt
= validate_recipients(internet_addr
, NULL
, CHECK_EXISTANCE
);
1306 if ((rcpt
!= NULL
)&&
1308 (*rcpt
->recp_local
!= '\0')||
1309 (*rcpt
->recp_room
!= '\0')||
1310 (*rcpt
->recp_ignet
!= '\0')))
1313 cprintf("200 OK %s\n", internet_addr
);
1314 CtdlLogPrintf(CTDL_INFO
, "sending 200 OK for the room %s\n", rcpt
->display_recp
);
1318 cprintf("500 REJECT noone here by that name.\n");
1320 CtdlLogPrintf(CTDL_INFO
, "sending 500 REJECT noone here by that name: %s\n", internet_addr
);
1322 if (rcpt
!= NULL
) free_recipients(rcpt
);
1326 void check_get_greeting(void) {
1327 /* dummy function, we have no greeting in this verry simple protocol. */
1332 * We don't know if the Contacts room exists so we just create it at login
1334 void vcard_create_room(void)
1339 /* Create the calendar room if it doesn't already exist */
1340 create_room(USERCONTACTSROOM
, 4, "", 0, 1, 0, VIEW_ADDRESSBOOK
);
1342 /* Set expiration policy to manual; otherwise objects will be lost! */
1343 if (lgetroom(&qr
, USERCONTACTSROOM
)) {
1344 CtdlLogPrintf(CTDL_ERR
, "Couldn't get the user CONTACTS room!\n");
1347 qr
.QRep
.expire_mode
= EXPIRE_MANUAL
;
1348 qr
.QRdefaultview
= VIEW_ADDRESSBOOK
; /* 2 = address book view */
1351 /* Set the view to a calendar view */
1352 CtdlGetRelationship(&vbuf
, &CC
->user
, &qr
);
1353 vbuf
.v_view
= 2; /* 2 = address book view */
1354 CtdlSetRelationship(&vbuf
, &CC
->user
, &qr
);
1363 * When a user logs in...
1365 void vcard_session_login_hook(void) {
1366 struct vCard
*v
= NULL
;
1368 v
= vcard_get_user(&CC
->user
);
1369 extract_inet_email_addrs(CC
->cs_inet_email
, sizeof CC
->cs_inet_email
,
1370 CC
->cs_inet_other_emails
, sizeof CC
->cs_inet_other_emails
,
1372 extract_friendly_name(CC
->cs_inet_fn
, sizeof CC
->cs_inet_fn
, v
);
1375 vcard_create_room();
1380 * Turn an arbitrary RFC822 address into a struct vCard for possible
1381 * inclusion into an address book.
1383 struct vCard
*vcard_new_from_rfc822_addr(char *addr
) {
1385 char user
[256], node
[256], name
[256], email
[256], n
[256], uid
[256];
1389 if (v
== NULL
) return(NULL
);
1391 process_rfc822_addr(addr
, user
, node
, name
);
1392 vcard_set_prop(v
, "fn", name
, 0);
1394 vcard_fn_to_n(n
, name
, sizeof n
);
1395 vcard_set_prop(v
, "n", n
, 0);
1397 snprintf(email
, sizeof email
, "%s@%s", user
, node
);
1398 vcard_set_prop(v
, "email;internet", email
, 0);
1400 snprintf(uid
, sizeof uid
, "collected: %s %s@%s", name
, user
, node
);
1401 for (i
=0; uid
[i
]; ++i
) {
1402 if (isspace(uid
[i
])) uid
[i
] = '_';
1403 uid
[i
] = tolower(uid
[i
]);
1405 vcard_set_prop(v
, "UID", uid
, 0);
1413 * This is called by store_harvested_addresses() to remove from the
1414 * list any addresses we already have in our address book.
1416 void strip_addresses_already_have(long msgnum
, void *userdata
) {
1417 char *collected_addresses
;
1418 struct CtdlMessage
*msg
= NULL
;
1422 char addr
[256], user
[256], node
[256], name
[256];
1424 collected_addresses
= (char *)userdata
;
1426 msg
= CtdlFetchMessage(msgnum
, 1);
1427 if (msg
== NULL
) return;
1428 v
= vcard_load(msg
->cm_fields
['M']);
1429 CtdlFreeMessage(msg
);
1432 while (value
= vcard_get_prop(v
, "email", 1, i
++, 0), value
!= NULL
) {
1434 for (j
=0; j
<num_tokens(collected_addresses
, ','); ++j
) {
1435 extract_token(addr
, collected_addresses
, j
, ',', sizeof addr
);
1437 /* Remove the address if we already have it! */
1438 process_rfc822_addr(addr
, user
, node
, name
);
1439 snprintf(addr
, sizeof addr
, "%s@%s", user
, node
);
1440 if (!strcasecmp(value
, addr
)) {
1441 remove_token(collected_addresses
, j
, ',');
1453 * Back end function for store_harvested_addresses()
1455 void store_this_ha(struct addresses_to_be_filed
*aptr
) {
1456 struct CtdlMessage
*vmsg
= NULL
;
1457 long vmsgnum
= (-1L);
1459 struct vCard
*v
= NULL
;
1460 char recipient
[256];
1463 /* First remove any addresses we already have in the address book */
1464 usergoto(aptr
->roomname
, 0, 0, NULL
, NULL
);
1465 CtdlForEachMessage(MSGS_ALL
, 0, NULL
, "^[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$", NULL
,
1466 strip_addresses_already_have
, aptr
->collected_addresses
);
1468 if (!IsEmptyStr(aptr
->collected_addresses
))
1469 for (i
=0; i
<num_tokens(aptr
->collected_addresses
, ','); ++i
) {
1471 /* Make a vCard out of each address */
1472 extract_token(recipient
, aptr
->collected_addresses
, i
, ',', sizeof recipient
);
1474 v
= vcard_new_from_rfc822_addr(recipient
);
1476 vmsg
= malloc(sizeof(struct CtdlMessage
));
1477 memset(vmsg
, 0, sizeof(struct CtdlMessage
));
1478 vmsg
->cm_magic
= CTDLMESSAGE_MAGIC
;
1479 vmsg
->cm_anon_type
= MES_NORMAL
;
1480 vmsg
->cm_format_type
= FMT_RFC822
;
1481 vmsg
->cm_fields
['A'] = strdup("Citadel");
1482 vmsg
->cm_fields
['E'] = strdup(vcard_get_prop(v
, "UID", 0, 0, 0));
1483 ser
= vcard_serialize(v
);
1485 vmsg
->cm_fields
['M'] = malloc(strlen(ser
) + 1024);
1486 sprintf(vmsg
->cm_fields
['M'],
1487 "Content-type: " VCARD_MIME_TYPE
1488 "\r\n\r\n%s\r\n", ser
);
1493 CtdlLogPrintf(CTDL_DEBUG
, "Adding contact: %s\n", recipient
);
1494 vmsgnum
= CtdlSubmitMsg(vmsg
, NULL
, aptr
->roomname
, QP_EADDR
);
1495 CtdlFreeMessage(vmsg
);
1499 free(aptr
->roomname
);
1500 free(aptr
->collected_addresses
);
1506 * When a user sends a message, we may harvest one or more email addresses
1507 * from the recipient list to be added to the user's address book. But we
1508 * want to do this asynchronously so it doesn't keep the user waiting.
1510 void store_harvested_addresses(void) {
1512 struct addresses_to_be_filed
*aptr
= NULL
;
1514 if (atbf
== NULL
) return;
1516 begin_critical_section(S_ATBF
);
1517 while (atbf
!= NULL
) {
1520 end_critical_section(S_ATBF
);
1521 store_this_ha(aptr
);
1522 begin_critical_section(S_ATBF
);
1524 end_critical_section(S_ATBF
);
1529 * Function to output vCard data as plain text. Nobody uses MSG0 anymore, so
1530 * really this is just so we expose the vCard data to the full text indexer.
1532 void vcard_fixed_output(char *ptr
, int len
) {
1533 char *serialized_vcard
;
1538 serialized_vcard
= malloc(len
+ 1);
1539 safestrncpy(serialized_vcard
, ptr
, len
+1);
1540 v
= vcard_load(serialized_vcard
);
1541 free(serialized_vcard
);
1544 while (key
= vcard_get_prop(v
, "", 0, i
, 1), key
!= NULL
) {
1545 value
= vcard_get_prop(v
, "", 0, i
++, 0);
1546 cprintf("%s\n", value
);
1553 const char *CitadelServiceDICT_TCP
="DICT_TCP";
1555 CTDL_MODULE_INIT(vcard
)
1563 CtdlRegisterSessionHook(vcard_session_login_hook
, EVT_LOGIN
);
1564 CtdlRegisterMessageHook(vcard_upload_beforesave
, EVT_BEFORESAVE
);
1565 CtdlRegisterMessageHook(vcard_upload_aftersave
, EVT_AFTERSAVE
);
1566 CtdlRegisterDeleteHook(vcard_delete_remove
);
1567 CtdlRegisterProtoHook(cmd_regi
, "REGI", "Enter registration info");
1568 CtdlRegisterProtoHook(cmd_greg
, "GREG", "Get registration info");
1569 CtdlRegisterProtoHook(cmd_igab
, "IGAB",
1570 "Initialize Global Address Book");
1571 CtdlRegisterProtoHook(cmd_qdir
, "QDIR", "Query Directory");
1572 CtdlRegisterProtoHook(cmd_gvsn
, "GVSN", "Get Valid Screen Names");
1573 CtdlRegisterProtoHook(cmd_gvea
, "GVEA", "Get Valid Email Addresses");
1574 CtdlRegisterProtoHook(cmd_dvca
, "DVCA", "Dump VCard Addresses");
1575 CtdlRegisterUserHook(vcard_newuser
, EVT_NEWUSER
);
1576 CtdlRegisterUserHook(vcard_purge
, EVT_PURGEUSER
);
1577 CtdlRegisterNetprocHook(vcard_extract_from_network
);
1578 CtdlRegisterSessionHook(store_harvested_addresses
, EVT_TIMER
);
1579 CtdlRegisterFixedOutputHook("text/x-vcard", vcard_fixed_output
);
1580 CtdlRegisterFixedOutputHook("text/vcard", vcard_fixed_output
);
1582 /* Create the Global ADdress Book room if necessary */
1583 create_room(ADDRESS_BOOK_ROOM
, 3, "", 0, 1, 0, VIEW_ADDRESSBOOK
);
1585 /* Set expiration policy to manual; otherwise objects will be lost! */
1586 if (!lgetroom(&qr
, ADDRESS_BOOK_ROOM
)) {
1587 qr
.QRep
.expire_mode
= EXPIRE_MANUAL
;
1588 qr
.QRdefaultview
= VIEW_ADDRESSBOOK
; /* 2 = address book view */
1592 * Also make sure it has a netconfig file, so the networker runs
1593 * on this room even if we don't share it with any other nodes.
1594 * This allows the CANCEL messages (i.e. "Purge this vCard") to be
1597 assoc_file_name(filename
, sizeof filename
, &qr
, ctdl_netcfg_dir
);
1598 fp
= fopen(filename
, "a");
1599 if (fp
!= NULL
) fclose(fp
);
1600 chown(filename
, CTDLUID
, (-1));
1603 /* for postfix tcpdict */
1604 CtdlRegisterServiceHook(config
.c_pftcpdict_port
, /* Postfix */
1609 CitadelServiceDICT_TCP
);
1612 /* return our Subversion id for the Log */