Back to index

citadel  8.12
serv_vcard.c
Go to the documentation of this file.
00001 /*
00002  * A server-side module for Citadel which supports address book information
00003  * using the standard vCard format.
00004  * 
00005  * Copyright (c) 1999-2012 by the citadel.org team
00006  *
00007  * This program is open source software; you can redistribute it and/or modify
00008  * it under the terms of the GNU General Public License version 3.
00009  *
00010  * This program is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  * GNU General Public License for more details.
00014  */
00015 
00016 /*
00017  * Format of the "Exclusive ID" field of the message containing a user's
00018  * vCard.  Doesn't matter what it really looks like as long as it's both
00019  * unique and consistent (because we use it for replication checking to
00020  * delete the old vCard network-wide when the user enters a new one).
00021  */
00022 #define VCARD_EXT_FORMAT    "Citadel vCard: personal card for %s at %s"
00023 
00024 /*
00025  * Citadel will accept either text/vcard or text/x-vcard as the MIME type
00026  * for a vCard.  The following definition determines which one it *generates*
00027  * when serializing.
00028  */
00029 #define VCARD_MIME_TYPE            "text/x-vcard"
00030 
00031 #include "sysdep.h"
00032 #include <stdlib.h>
00033 #include <unistd.h>
00034 #include <stdio.h>
00035 #include <fcntl.h>
00036 #include <signal.h>
00037 #include <pwd.h>
00038 #include <errno.h>
00039 #include <ctype.h>
00040 #include <sys/types.h>
00041 
00042 #if TIME_WITH_SYS_TIME
00043 # include <sys/time.h>
00044 # include <time.h>
00045 #else
00046 # if HAVE_SYS_TIME_H
00047 #  include <sys/time.h>
00048 # else
00049 #  include <time.h>
00050 # endif
00051 #endif
00052 
00053 #include <sys/wait.h>
00054 #include <string.h>
00055 #include <limits.h>
00056 #include <libcitadel.h>
00057 #include "citadel.h"
00058 #include "server.h"
00059 #include "citserver.h"
00060 #include "support.h"
00061 #include "config.h"
00062 #include "control.h"
00063 #include "user_ops.h"
00064 #include "database.h"
00065 #include "msgbase.h"
00066 #include "internet_addressing.h"
00067 #include "serv_vcard.h"
00068 #include "citadel_ldap.h"
00069 #include "ctdl_module.h"
00070 
00071 /*
00072  * set global flag calling for an aide to validate new users
00073  */
00074 void set_mm_valid(void) {
00075        begin_critical_section(S_CONTROL);
00076        get_control();
00077        CitControl.MMflags = CitControl.MMflags | MM_VALID ;
00078        put_control();
00079        end_critical_section(S_CONTROL);
00080 }
00081 
00082 
00083 
00084 /*
00085  * Extract Internet e-mail addresses from a message containing a vCard, and
00086  * perform a callback for any found.
00087  */
00088 void vcard_extract_internet_addresses(struct CtdlMessage *msg, int (*callback)(char *, char *) ) {
00089        struct vCard *v;
00090        char *s;
00091        char *k;
00092        char *addr;
00093        char citadel_address[SIZ];
00094        int instance = 0;
00095        int found_something = 0;
00096 
00097        if (msg->cm_fields['A'] == NULL) return;
00098        if (msg->cm_fields['N'] == NULL) return;
00099        snprintf(citadel_address, sizeof citadel_address, "%s @ %s",
00100               msg->cm_fields['A'], msg->cm_fields['N']);
00101 
00102        v = vcard_load(msg->cm_fields['M']);
00103        if (v == NULL) return;
00104 
00105        /* Go through the vCard searching for *all* instances of
00106         * the "email;internet" key
00107         */
00108        do {
00109               s = vcard_get_prop(v, "email", 1, instance, 0);         /* get any 'email' field */
00110               k = vcard_get_prop(v, "email", 1, instance++, 1);       /* but also learn it with attrs */
00111               if ( (s != NULL) && (k != NULL) && (bmstrcasestr(k, "internet")) ) {
00112                      addr = strdup(s);
00113                      striplt(addr);
00114                      if (!IsEmptyStr(addr)) {
00115                             if (callback != NULL) {
00116                                    callback(addr, citadel_address);
00117                             }
00118                      }
00119                      free(addr);
00120                      found_something = 1;
00121               }
00122               else {
00123                      found_something = 0;
00124               }
00125        } while(found_something);
00126 
00127        vcard_free(v);
00128 }
00130 #define _(a) a
00131 /*
00132  * Callback for vcard_add_to_directory()
00133  * (Lotsa ugly nested callbacks.  Oh well.)
00134  */
00135 int vcard_directory_add_user(char *internet_addr, char *citadel_addr) {
00136        struct CitContext *CCC = CC;
00137        char buf[SIZ];
00138 
00139        /* We have to validate that we're not stepping on someone else's
00140         * email address ... but only if we're logged in.  Otherwise it's
00141         * probably just the networker or something.
00142         */
00143        if (CCC->logged_in) {
00144               syslog(LOG_DEBUG, "Checking for <%s>...", internet_addr);
00145               if (CtdlDirectoryLookup(buf, internet_addr, sizeof buf) == 0) {
00146                      if (strcasecmp(buf, citadel_addr)) {
00147                             /* This address belongs to someone else.
00148                              * Bail out silently without saving.
00149                              */
00150                             syslog(LOG_DEBUG, "DOOP!");
00151                             
00152                             StrBufAppendPrintf(CCC->StatusMessage, "\n%d|", ERROR+ALREADY_EXISTS);
00153                             StrBufAppendBufPlain(CCC->StatusMessage, internet_addr, -1, 0);
00154                             StrBufAppendBufPlain(CCC->StatusMessage, HKEY("|"), 0);
00155                             StrBufAppendBufPlain(CCC->StatusMessage, _("Unable to add this email address again."), -1, 0);
00156                             StrBufAppendBufPlain(CCC->StatusMessage, HKEY("\n"), 0);
00157                             return 0;
00158                      }
00159               }
00160        }
00161        syslog(LOG_INFO, "Adding %s (%s) to directory", citadel_addr, internet_addr);
00162        if (CtdlDirectoryAddUser(internet_addr, citadel_addr))
00163        {
00164               StrBufAppendPrintf(CCC->StatusMessage, "\n%d|", CIT_OK);
00165               StrBufAppendBufPlain(CCC->StatusMessage, internet_addr, -1, 0);
00166               StrBufAppendBufPlain(CCC->StatusMessage, HKEY("|"), 0);
00167               StrBufAppendBufPlain(CCC->StatusMessage, _("Successfully added email address."), -1, 0);
00168               return 1;
00169        }
00170        else
00171        {
00172               StrBufAppendPrintf(CCC->StatusMessage, "\n%d|", ERROR+ ILLEGAL_VALUE);
00173               StrBufAppendBufPlain(CCC->StatusMessage, internet_addr, -1, 0);
00174               StrBufAppendBufPlain(CCC->StatusMessage, HKEY("|"), 0);
00175               StrBufAppendBufPlain(CCC->StatusMessage, _("Unable to add this email address. It does not match any local domain."), -1, 0);
00176               return 0;
00177        }
00178 }
00179 
00180 
00181 /*
00182  * Back end function for cmd_igab()
00183  */
00184 void vcard_add_to_directory(long msgnum, void *data) {
00185        struct CtdlMessage *msg;
00186 
00187        msg = CtdlFetchMessage(msgnum, 1);
00188        if (msg != NULL) {
00189               vcard_extract_internet_addresses(msg, vcard_directory_add_user);
00190        }
00191 
00192        CtdlFreeMessage(msg);
00193 }
00194 
00195 
00196 /*
00197  * Initialize Global Adress Book
00198  */
00199 void cmd_igab(char *argbuf) {
00200        char hold_rm[ROOMNAMELEN];
00201 
00202        if (CtdlAccessCheck(ac_aide)) return;
00203 
00204        strcpy(hold_rm, CC->room.QRname);  /* save current room */
00205 
00206        if (CtdlGetRoom(&CC->room, ADDRESS_BOOK_ROOM) != 0) {
00207               CtdlGetRoom(&CC->room, hold_rm);
00208               cprintf("%d cannot get address book room\n", ERROR + ROOM_NOT_FOUND);
00209               return;
00210        }
00211 
00212        /* Empty the existing database first.
00213         */
00214        CtdlDirectoryInit();
00215 
00216        /* We want *all* vCards in this room */
00217        NewStrBufDupAppendFlush(&CC->StatusMessage, NULL, NULL, 0);
00218        CtdlForEachMessage(MSGS_ALL, 0, NULL, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$",
00219               NULL, vcard_add_to_directory, NULL);
00220 
00221        CtdlGetRoom(&CC->room, hold_rm);   /* return to saved room */
00222        cprintf("%d Directory has been rebuilt.\n", CIT_OK);
00223 }
00224 
00225 
00226 
00227 
00228 /*
00229  * See if there is a valid Internet address in a vCard to use for outbound
00230  * Internet messages.  If there is, stick it in the buffer.
00231  */
00232 void extract_inet_email_addrs(char *emailaddrbuf, size_t emailaddrbuf_len,
00233                            char *secemailaddrbuf, size_t secemailaddrbuf_len,
00234                            struct vCard *v,
00235                            int local_addrs_only)
00236 {
00237        struct CitContext *CCC = CC;              /* put this on the stack, just for speed */
00238        char *s, *k, *addr;
00239        int instance = 0;
00240        int IsDirectoryAddress;
00241        int saved_instance = 0;
00242 
00243        /* Go through the vCard searching for *all* Internet email addresses
00244         */
00245        while (s = vcard_get_prop(v, "email", 1, instance, 0),  s != NULL) {
00246               k = vcard_get_prop(v, "email", 1, instance, 1);
00247               if ( (s != NULL) && (k != NULL) && (bmstrcasestr(k, "internet")) ) {
00248                      addr = strdup(s);
00249                      striplt(addr);
00250                      if (!IsEmptyStr(addr)) {
00251                             IsDirectoryAddress = IsDirectory(addr, 1);
00252                             if ( IsDirectoryAddress || !local_addrs_only)
00253                             {
00254                                    ++saved_instance;
00255                                    if ((saved_instance == 1) && (emailaddrbuf != NULL)) {
00256                                           safestrncpy(emailaddrbuf, addr, emailaddrbuf_len);
00257                                    }
00258                                    else if ((saved_instance == 2) && (secemailaddrbuf != NULL)) {
00259                                           safestrncpy(secemailaddrbuf, addr, secemailaddrbuf_len);
00260                                    }
00261                                    else if ((saved_instance > 2) && (secemailaddrbuf != NULL)) {
00262                                           if ( (strlen(addr) + strlen(secemailaddrbuf) + 2) 
00263                                           < secemailaddrbuf_len ) {
00264                                                  strcat(secemailaddrbuf, "|");
00265                                                  strcat(secemailaddrbuf, addr);
00266                                           }
00267                                    }
00268                             }
00269                             if (!IsDirectoryAddress && local_addrs_only)
00270                             {
00271                                    StrBufAppendPrintf(CCC->StatusMessage, "\n%d|", ERROR+ ILLEGAL_VALUE);
00272                                    StrBufAppendBufPlain(CCC->StatusMessage, addr, -1, 0);
00273                                    StrBufAppendBufPlain(CCC->StatusMessage, HKEY("|"), 0);
00274                                    StrBufAppendBufPlain(CCC->StatusMessage, _("unable to add this emailaddress; its not matching our domain."), -1, 0);
00275                             }
00276                      }
00277                      free(addr);
00278               }
00279               ++instance;
00280        }
00281 }
00282 
00283 
00284 
00285 /*
00286  * See if there is a name / screen name / friendly name  in a vCard to use for outbound
00287  * Internet messages.  If there is, stick it in the buffer.
00288  */
00289 void extract_friendly_name(char *namebuf, size_t namebuf_len, struct vCard *v)
00290 {
00291        char *s;
00292 
00293        s = vcard_get_prop(v, "fn", 1, 0, 0);
00294        if (s == NULL) {
00295               s = vcard_get_prop(v, "n", 1, 0, 0);
00296        }
00297 
00298        if (s != NULL) {
00299               safestrncpy(namebuf, s, namebuf_len);
00300        }
00301 }
00302 
00303 
00304 /*
00305  * Callback function for vcard_upload_beforesave() hunts for the real vcard in the MIME structure
00306  */
00307 void vcard_extract_vcard(char *name, char *filename, char *partnum, char *disp,
00308                  void *content, char *cbtype, char *cbcharset, size_t length,
00309                  char *encoding, char *cbid, void *cbuserdata)
00310 {
00311        struct vCard **v = (struct vCard **) cbuserdata;
00312 
00313        if (  (!strcasecmp(cbtype, "text/x-vcard"))
00314           || (!strcasecmp(cbtype, "text/vcard")) ) {
00315 
00316               syslog(LOG_DEBUG, "Part %s contains a vCard!  Loading...", partnum);
00317               if (*v != NULL) {
00318                      vcard_free(*v);
00319               }
00320               *v = vcard_load(content);
00321        }
00322 }
00323 
00324 
00325 /*
00326  * This handler detects whether the user is attempting to save a new
00327  * vCard as part of his/her personal configuration, and handles the replace
00328  * function accordingly (delete the user's existing vCard in the config room
00329  * and in the global address book).
00330  */
00331 int vcard_upload_beforesave(struct CtdlMessage *msg) {
00332        char *ptr;
00333        char *s;
00334        char buf[SIZ];
00335        struct ctdluser usbuf;
00336        long what_user;
00337        struct vCard *v = NULL;
00338        char *ser = NULL;
00339        int i = 0;
00340        int yes_my_citadel_config = 0;
00341        int yes_any_vcard_room = 0;
00342 
00343        if (!CC->logged_in) return(0);     /* Only do this if logged in. */
00344 
00345        /* Is this some user's "My Citadel Config" room? */
00346        if ( (CC->room.QRflags && QR_MAILBOX)
00347           && (!strcasecmp(&CC->room.QRname[11], USERCONFIGROOM)) ) {
00348               /* Yes, we want to do this */
00349               yes_my_citadel_config = 1;
00350 
00351 #ifdef VCARD_SAVES_BY_AIDES_ONLY
00352               /* Prevent non-aides from performing registration changes */
00353               if (CC->user.axlevel < AxAideU) {
00354                      return(1);
00355               }
00356 #endif
00357 
00358        }
00359 
00360        /* Is this a room with an address book in it? */
00361        if (CC->room.QRdefaultview == VIEW_ADDRESSBOOK) {
00362               yes_any_vcard_room = 1;
00363        }
00364 
00365        /* If neither condition exists, don't run this hook. */
00366        if ( (!yes_my_citadel_config) && (!yes_any_vcard_room) ) {
00367               return(0);
00368        }
00369 
00370        /* If this isn't a MIME message, don't bother. */
00371        if (msg->cm_format_type != 4) return(0);
00372 
00373        /* Ok, if we got this far, look into the situation further... */
00374 
00375        ptr = msg->cm_fields['M'];
00376        if (ptr == NULL) return(0);
00377 
00378        mime_parser(msg->cm_fields['M'],
00379               NULL,
00380               *vcard_extract_vcard,
00381               NULL, NULL,
00382               &v,           /* user data ptr - put the vcard here */
00383               0
00384        );
00385 
00386        if (v == NULL) return(0);   /* no vCards were found in this message */
00387 
00388        /* If users cannot create their own accounts, they cannot re-register either. */
00389        if ( (yes_my_citadel_config) && (config.c_disable_newu) && (CC->user.axlevel < AxAideU) ) {
00390               return(1);
00391        }
00392 
00393        vcard_get_prop(v, "fn", 1, 0, 0);
00394 
00395        if (yes_my_citadel_config) {
00396               /* Bingo!  The user is uploading a new vCard, so
00397                * delete the old one.  First, figure out which user
00398                * is being re-registered...
00399                */
00400               what_user = atol(CC->room.QRname);
00401 
00402               if (what_user == CC->user.usernum) {
00403                      /* It's the logged in user.  That was easy. */
00404                      memcpy(&usbuf, &CC->user, sizeof(struct ctdluser));
00405               }
00406               
00407               else if (CtdlGetUserByNumber(&usbuf, what_user) == 0) {
00408                      /* We fetched a valid user record */
00409               }
00410 
00411               else {
00412                      /* somebody set up us the bomb! */
00413                      yes_my_citadel_config = 0;
00414               }
00415        }
00416        
00417        if (yes_my_citadel_config) {
00418               /* Delete the user's old vCard.  This would probably
00419                * get taken care of by the replication check, but we
00420                * want to make sure there is absolutely only one
00421                * vCard in the user's config room at all times.
00422                *
00423                */
00424               CtdlDeleteMessages(CC->room.QRname, NULL, 0, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$");
00425 
00426               /* Make the author of the message the name of the user. */
00427               if (msg->cm_fields['A'] != NULL) {
00428                      free(msg->cm_fields['A']);
00429               }
00430               msg->cm_fields['A'] = strdup(usbuf.fullname);
00431        }
00432 
00433        /* Insert or replace RFC2739-compliant free/busy URL */
00434        if (yes_my_citadel_config) {
00435               sprintf(buf, "http://%s/%s.vfb",
00436                      config.c_fqdn,
00437                      usbuf.fullname);
00438               for (i=0; buf[i]; ++i) {
00439                      if (buf[i] == ' ') buf[i] = '_';
00440               }
00441               vcard_set_prop(v, "FBURL;PREF", buf, 0);
00442        }
00443 
00444        /* If the vCard has no UID, then give it one. */
00445        s = vcard_get_prop(v, "UID", 1, 0, 0);
00446        if (s == NULL) {
00447               generate_uuid(buf);
00448               vcard_set_prop(v, "UID", buf, 0);
00449        }
00450 
00451        /* Enforce local UID policy if applicable */
00452        if (yes_my_citadel_config) {
00453               snprintf(buf, sizeof buf, VCARD_EXT_FORMAT, msg->cm_fields['A'], NODENAME);
00454               vcard_set_prop(v, "UID", buf, 0);
00455        }
00456 
00457        /* 
00458         * Set the EUID of the message to the UID of the vCard.
00459         */
00460        if (msg->cm_fields['E'] != NULL)
00461        {
00462               free(msg->cm_fields['E']);
00463               msg->cm_fields['E'] = NULL;
00464        }
00465        s = vcard_get_prop(v, "UID", 1, 0, 0);
00466        if (s != NULL) {
00467               msg->cm_fields['E'] = strdup(s);
00468               if (msg->cm_fields['U'] == NULL) {
00469                      msg->cm_fields['U'] = strdup(s);
00470               }
00471        }
00472 
00473        /*
00474         * Set the Subject to the name in the vCard.
00475         */
00476        s = vcard_get_prop(v, "FN", 1, 0, 0);
00477        if (s == NULL) {
00478               s = vcard_get_prop(v, "N", 1, 0, 0);
00479        }
00480        if (s != NULL) {
00481               if (msg->cm_fields['U'] != NULL) {
00482                      free(msg->cm_fields['U']);
00483               }
00484               msg->cm_fields['U'] = strdup(s);
00485        }
00486 
00487        /* Re-serialize it back into the msg body */
00488        ser = vcard_serialize(v);
00489        if (ser != NULL) {
00490               msg->cm_fields['M'] = realloc(msg->cm_fields['M'], strlen(ser) + 1024);
00491               sprintf(msg->cm_fields['M'],
00492                      "Content-type: " VCARD_MIME_TYPE
00493                      "\r\n\r\n%s\r\n", ser);
00494               free(ser);
00495        }
00496 
00497        /* Now allow the save to complete. */
00498        vcard_free(v);
00499        return(0);
00500 }
00501 
00502 
00503 
00504 /*
00505  * This handler detects whether the user is attempting to save a new
00506  * vCard as part of his/her personal configuration, and handles the replace
00507  * function accordingly (copy the vCard from the config room to the global
00508  * address book).
00509  */
00510 int vcard_upload_aftersave(struct CtdlMessage *msg) {
00511        char *ptr;
00512        int linelen;
00513        long I;
00514        struct vCard *v;
00515        int is_UserConf=0;
00516        int is_MY_UserConf=0;
00517        int is_GAB=0;
00518        char roomname[ROOMNAMELEN];
00519 
00520        if (msg->cm_format_type != 4) return(0);
00521        if (!CC->logged_in) return(0);     /* Only do this if logged in. */
00522 
00523        /* We're interested in user config rooms only. */
00524 
00525        if ( (strlen(CC->room.QRname) >= 12) && (!strcasecmp(&CC->room.QRname[11], USERCONFIGROOM)) ) {
00526               is_UserConf = 1;     /* It's someone's config room */
00527        }
00528        CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCONFIGROOM);
00529        if (!strcasecmp(CC->room.QRname, roomname)) {
00530               is_UserConf = 1;
00531               is_MY_UserConf = 1;  /* It's MY config room */
00532        }
00533        if (!strcasecmp(CC->room.QRname, ADDRESS_BOOK_ROOM)) {
00534               is_GAB = 1;          /* It's the Global Address Book */
00535        }
00536 
00537        if (!is_UserConf && !is_GAB) return(0);
00538 
00539        ptr = msg->cm_fields['M'];
00540        if (ptr == NULL) return(0);
00541 
00542        NewStrBufDupAppendFlush(&CC->StatusMessage, NULL, NULL, 0);
00543 
00544        StrBufPrintf(CC->StatusMessage, "%d\n", LISTING_FOLLOWS);
00545 
00546        while (ptr != NULL) {
00547        
00548               linelen = strcspn(ptr, "\n");
00549               if (linelen == 0) return(0);       /* end of headers */ 
00550               
00551               if (  (!strncasecmp(ptr, "Content-type: text/x-vcard", 26))
00552                  || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
00553                      /*
00554                       * Bingo!  The user is uploading a new vCard, so
00555                       * copy it to the Global Address Book room.
00556                       */
00557 
00558                      I = atol(msg->cm_fields['3']);
00559                      if (I <= 0L) return(0);
00560 
00561                      /* Store our Internet return address in memory */
00562                      if (is_MY_UserConf) {
00563                             v = vcard_load(msg->cm_fields['M']);
00564                             extract_inet_email_addrs(CC->cs_inet_email, sizeof CC->cs_inet_email,
00565                                           CC->cs_inet_other_emails, sizeof CC->cs_inet_other_emails,
00566                                           v, 1);
00567                             extract_friendly_name(CC->cs_inet_fn, sizeof CC->cs_inet_fn, v);
00568                             vcard_free(v);
00569                      }
00570 
00571                      if (!is_GAB)
00572                      {      // This is not the GAB
00573                             /* Put it in the Global Address Book room... */
00574                             CtdlSaveMsgPointerInRoom(ADDRESS_BOOK_ROOM, I, 1, msg);
00575                      }
00576 
00577                      /* ...and also in the directory database. */
00578                      vcard_add_to_directory(I, NULL);
00579 
00580                      /* Some sites want an Aide to be notified when a
00581                       * user registers or re-registers
00582                       * But if the user was an Aide or was edited by an Aide then we can
00583                       * Assume they don't need validating.
00584                       */
00585                      if (CC->user.axlevel >= AxAideU) {
00586                             CtdlGetUserLock(&CC->user, CC->curr_user);
00587                             CC->user.flags |= US_REGIS;
00588                             CtdlPutUserLock(&CC->user);
00589                             return (0);
00590                      }
00591                      
00592                      set_mm_valid();
00593 
00594                      /* ...which also means we need to flag the user */
00595                      CtdlGetUserLock(&CC->user, CC->curr_user);
00596                      CC->user.flags |= (US_REGIS|US_NEEDVALID);
00597                      CtdlPutUserLock(&CC->user);
00598 
00599                      return(0);
00600               }
00601 
00602               ptr = strchr((char *)ptr, '\n');
00603               if (ptr != NULL) ++ptr;
00604        }
00605 
00606        return(0);
00607 }
00608 
00609 
00610 
00611 /*
00612  * back end function used for callbacks
00613  */
00614 void vcard_gu_backend(long supplied_msgnum, void *userdata) {
00615        long *msgnum;
00616 
00617        msgnum = (long *) userdata;
00618        *msgnum = supplied_msgnum;
00619 }
00620 
00621 
00622 /*
00623  * If this user has a vcard on disk, read it into memory, otherwise allocate
00624  * and return an empty vCard.
00625  */
00626 struct vCard *vcard_get_user(struct ctdluser *u) {
00627        char hold_rm[ROOMNAMELEN];
00628        char config_rm[ROOMNAMELEN];
00629        struct CtdlMessage *msg = NULL;
00630        struct vCard *v;
00631        long VCmsgnum;
00632 
00633        strcpy(hold_rm, CC->room.QRname);  /* save current room */
00634        CtdlMailboxName(config_rm, sizeof config_rm, u, USERCONFIGROOM);
00635 
00636        if (CtdlGetRoom(&CC->room, config_rm) != 0) {
00637               CtdlGetRoom(&CC->room, hold_rm);
00638               return vcard_new();
00639        }
00640 
00641        /* We want the last (and probably only) vcard in this room */
00642        VCmsgnum = (-1);
00643        CtdlForEachMessage(MSGS_LAST, 1, NULL, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$",
00644               NULL, vcard_gu_backend, (void *)&VCmsgnum );
00645        CtdlGetRoom(&CC->room, hold_rm);   /* return to saved room */
00646 
00647        if (VCmsgnum < 0L) return vcard_new();
00648 
00649        msg = CtdlFetchMessage(VCmsgnum, 1);
00650        if (msg == NULL) return vcard_new();
00651 
00652        v = vcard_load(msg->cm_fields['M']);
00653        CtdlFreeMessage(msg);
00654        return v;
00655 }
00656 
00657 
00658 /*
00659  * Store this user's vCard in the appropriate place
00660  */
00661 /*
00662  * Write our config to disk
00663  */
00664 void vcard_write_user(struct ctdluser *u, struct vCard *v) {
00665        char *ser;
00666 
00667        ser = vcard_serialize(v);
00668        if (ser == NULL) {
00669               ser = strdup("begin:vcard\r\nend:vcard\r\n");
00670        }
00671        if (!ser) return;
00672 
00673        /* This handy API function does all the work for us.
00674         * NOTE: normally we would want to set that last argument to 1, to
00675         * force the system to delete the user's old vCard.  But it doesn't
00676         * have to, because the vcard_upload_beforesave() hook above
00677         * is going to notice what we're trying to do, and delete the old vCard.
00678         */
00679        CtdlWriteObject(USERCONFIGROOM,           /* which room */
00680                      VCARD_MIME_TYPE,     /* MIME type */
00681                      ser,                 /* data */
00682                      strlen(ser)+1,              /* length */
00683                      u,                   /* which user */
00684                      0,                   /* not binary */
00685                      0,                   /* don't delete others of this type */
00686                      0);                  /* no flags */
00687 
00688        free(ser);
00689 }
00690 
00691 
00692 
00693 /*
00694  * Old style "enter registration info" command.  This function simply honors
00695  * the REGI protocol command, translates the entered parameters into a vCard,
00696  * and enters the vCard into the user's configuration.
00697  */
00698 void cmd_regi(char *argbuf) {
00699        int a,b,c;
00700        char buf[SIZ];
00701        struct vCard *my_vcard;
00702 
00703        char tmpaddr[SIZ];
00704        char tmpcity[SIZ];
00705        char tmpstate[SIZ];
00706        char tmpzip[SIZ];
00707        char tmpaddress[SIZ];
00708        char tmpcountry[SIZ];
00709 
00710        unbuffer_output();
00711 
00712        if (!(CC->logged_in)) {
00713               cprintf("%d Not logged in.\n",ERROR + NOT_LOGGED_IN);
00714               return;
00715        }
00716 
00717        /* If users cannot create their own accounts, they cannot re-register either. */
00718        if ( (config.c_disable_newu) && (CC->user.axlevel < AxAideU) ) {
00719               cprintf("%d Self-service registration is not allowed here.\n",
00720                      ERROR + HIGHER_ACCESS_REQUIRED);
00721        }
00722 
00723        my_vcard = vcard_get_user(&CC->user);
00724        strcpy(tmpaddr, "");
00725        strcpy(tmpcity, "");
00726        strcpy(tmpstate, "");
00727        strcpy(tmpzip, "");
00728        strcpy(tmpcountry, "USA");
00729 
00730        cprintf("%d Send registration...\n", SEND_LISTING);
00731        a=0;
00732        while (client_getln(buf, sizeof buf), strcmp(buf,"000")) {
00733               if (a==0) vcard_set_prop(my_vcard, "n", buf, 0);
00734               if (a==1) strcpy(tmpaddr, buf);
00735               if (a==2) strcpy(tmpcity, buf);
00736               if (a==3) strcpy(tmpstate, buf);
00737               if (a==4) {
00738                      for (c=0; buf[c]; ++c) {
00739                             if ((buf[c]>='0') && (buf[c]<='9')) {
00740                                    b = strlen(tmpzip);
00741                                    tmpzip[b] = buf[c];
00742                                    tmpzip[b+1] = 0;
00743                             }
00744                      }
00745               }
00746               if (a==5) vcard_set_prop(my_vcard, "tel", buf, 0);
00747               if (a==6) vcard_set_prop(my_vcard, "email;internet", buf, 0);
00748               if (a==7) strcpy(tmpcountry, buf);
00749               ++a;
00750        }
00751 
00752        snprintf(tmpaddress, sizeof tmpaddress, ";;%s;%s;%s;%s;%s",
00753               tmpaddr, tmpcity, tmpstate, tmpzip, tmpcountry);
00754        vcard_set_prop(my_vcard, "adr", tmpaddress, 0);
00755        vcard_write_user(&CC->user, my_vcard);
00756        vcard_free(my_vcard);
00757 }
00758 
00759 
00760 /*
00761  * Protocol command to fetch registration info for a user
00762  */
00763 void cmd_greg(char *argbuf)
00764 {
00765        struct ctdluser usbuf;
00766        struct vCard *v;
00767        char *s;
00768        char who[USERNAME_SIZE];
00769        char adr[256];
00770        char buf[256];
00771 
00772        extract_token(who, argbuf, 0, '|', sizeof who);
00773 
00774        if (!(CC->logged_in)) {
00775               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
00776               return;
00777        }
00778 
00779        if (!strcasecmp(who,"_SELF_")) strcpy(who,CC->curr_user);
00780 
00781        if ((CC->user.axlevel < AxAideU) && (strcasecmp(who,CC->curr_user))) {
00782               cprintf("%d Higher access required.\n",
00783                      ERROR + HIGHER_ACCESS_REQUIRED);
00784               return;
00785        }
00786 
00787        if (CtdlGetUser(&usbuf, who) != 0) {
00788               cprintf("%d '%s' not found.\n", ERROR + NO_SUCH_USER, who);
00789               return;
00790        }
00791 
00792        v = vcard_get_user(&usbuf);
00793 
00794        cprintf("%d %s\n", LISTING_FOLLOWS, usbuf.fullname);
00795        cprintf("%ld\n", usbuf.usernum);
00796        cprintf("%s\n", usbuf.password);
00797        s = vcard_get_prop(v, "n", 1, 0, 0);
00798        cprintf("%s\n", s ? s : " ");      /* name */
00799 
00800        s = vcard_get_prop(v, "adr", 1, 0, 0);
00801        snprintf(adr, sizeof adr, "%s", s ? s : " ");/* address... */
00802 
00803        extract_token(buf, adr, 2, ';', sizeof buf);
00804        cprintf("%s\n", buf);                            /* street */
00805        extract_token(buf, adr, 3, ';', sizeof buf);
00806        cprintf("%s\n", buf);                            /* city */
00807        extract_token(buf, adr, 4, ';', sizeof buf);
00808        cprintf("%s\n", buf);                            /* state */
00809        extract_token(buf, adr, 5, ';', sizeof buf);
00810        cprintf("%s\n", buf);                            /* zip */
00811 
00812        s = vcard_get_prop(v, "tel", 1, 0, 0);
00813        if (s == NULL) s = vcard_get_prop(v, "tel", 1, 0, 0);
00814        if (s != NULL) {
00815               cprintf("%s\n", s);
00816        }
00817        else {
00818               cprintf(" \n");
00819        }
00820 
00821        cprintf("%d\n", usbuf.axlevel);
00822 
00823        s = vcard_get_prop(v, "email;internet", 0, 0, 0);
00824        cprintf("%s\n", s ? s : " ");
00825        s = vcard_get_prop(v, "adr", 0, 0, 0);
00826        snprintf(adr, sizeof adr, "%s", s ? s : " ");/* address... */
00827 
00828        extract_token(buf, adr, 6, ';', sizeof buf);
00829        cprintf("%s\n", buf);                            /* country */
00830        cprintf("000\n");
00831        vcard_free(v);
00832 }
00833 
00834 
00835 
00836 /*
00837  * When a user is being created, create his/her vCard.
00838  */
00839 void vcard_newuser(struct ctdluser *usbuf) {
00840        char vname[256];
00841        char buf[256];
00842        int i;
00843        struct vCard *v;
00844 
00845        vcard_fn_to_n(vname, usbuf->fullname, sizeof vname);
00846        syslog(LOG_DEBUG, "Converted <%s> to <%s>", usbuf->fullname, vname);
00847 
00848        /* Create and save the vCard */
00849        v = vcard_new();
00850        if (v == NULL) return;
00851        vcard_add_prop(v, "fn", usbuf->fullname);
00852        vcard_add_prop(v, "n", vname);
00853        vcard_add_prop(v, "adr", "adr:;;_;_;_;00000;__");
00854 
00855 #ifdef HAVE_GETPWUID_R
00856        /* If using host auth mode, we add an email address based on the login */
00857        if (config.c_auth_mode == AUTHMODE_HOST) {
00858               struct passwd pwd;
00859               char pwd_buffer[SIZ];
00860               
00861 #ifdef SOLARIS_GETPWUID
00862               if (getpwuid_r(usbuf->uid, &pwd, pwd_buffer, sizeof pwd_buffer) != NULL) {
00863 #else // SOLARIS_GETPWUID
00864               struct passwd *result = NULL;
00865               syslog(LOG_DEBUG, "Searching for uid %d", usbuf->uid);
00866               if (getpwuid_r(usbuf->uid, &pwd, pwd_buffer, sizeof pwd_buffer, &result) == 0) {
00867 #endif // HAVE_GETPWUID_R
00868                      snprintf(buf, sizeof buf, "%s@%s", pwd.pw_name, config.c_fqdn);
00869                      vcard_add_prop(v, "email;internet", buf);
00870               }
00871        }
00872 #endif
00873 
00874        /* Everyone gets an email address based on their display name */
00875        snprintf(buf, sizeof buf, "%s@%s", usbuf->fullname, config.c_fqdn);
00876        for (i=0; buf[i]; ++i) {
00877               if (buf[i] == ' ') buf[i] = '_';
00878        }
00879        vcard_add_prop(v, "email;internet", buf);
00880 
00881 
00882        vcard_write_user(usbuf, v);
00883        vcard_free(v);
00884 }
00885 
00886 
00887 /*
00888  * When a user is being deleted, we have to remove his/her vCard.
00889  * This is accomplished by issuing a message with 'CANCEL' in the S (special)
00890  * field, and the same Exclusive ID as the existing card.
00891  */
00892 void vcard_purge(struct ctdluser *usbuf) {
00893        struct CtdlMessage *msg;
00894        char buf[SIZ];
00895 
00896        msg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
00897        if (msg == NULL) return;
00898        memset(msg, 0, sizeof(struct CtdlMessage));
00899 
00900        msg->cm_magic = CTDLMESSAGE_MAGIC;
00901        msg->cm_anon_type = MES_NORMAL;
00902        msg->cm_format_type = 0;
00903        msg->cm_fields['A'] = strdup(usbuf->fullname);
00904        msg->cm_fields['O'] = strdup(ADDRESS_BOOK_ROOM);
00905        msg->cm_fields['N'] = strdup(NODENAME);
00906        msg->cm_fields['M'] = strdup("Purge this vCard\n");
00907 
00908        snprintf(buf, sizeof buf, VCARD_EXT_FORMAT,
00909                      msg->cm_fields['A'], NODENAME);
00910        msg->cm_fields['E'] = strdup(buf);
00911 
00912        msg->cm_fields['S'] = strdup("CANCEL");
00913 
00914        CtdlSubmitMsg(msg, NULL, ADDRESS_BOOK_ROOM, QP_EADDR);
00915        CtdlFreeMessage(msg);
00916 }
00917 
00918 
00919 /*
00920  * Grab vCard directory stuff out of incoming network messages
00921  */
00922 int vcard_extract_from_network(struct CtdlMessage *msg, char *target_room) {
00923        char *ptr;
00924        int linelen;
00925 
00926        if (msg == NULL) return(0);
00927 
00928        if (strcasecmp(target_room, ADDRESS_BOOK_ROOM)) {
00929               return(0);
00930        }
00931 
00932        if (msg->cm_format_type != 4) return(0);
00933 
00934        ptr = msg->cm_fields['M'];
00935        if (ptr == NULL) return(0);
00936        while (ptr != NULL) {
00937        
00938               linelen = strcspn(ptr, "\n");
00939               if (linelen == 0) return(0);       /* end of headers */ 
00940               
00941               if (  (!strncasecmp(ptr, "Content-type: text/x-vcard", 26))
00942                  || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
00943                      /* It's a vCard.  Add it to the directory. */
00944                      vcard_extract_internet_addresses(msg, CtdlDirectoryAddUser);
00945                      return(0);
00946               }
00947 
00948               ptr = strchr((char *)ptr, '\n');
00949               if (ptr != NULL) ++ptr;
00950        }
00951 
00952        return(0);
00953 }
00954 
00955 
00956 
00957 /* 
00958  * When a vCard is being removed from the Global Address Book room, remove it
00959  * from the directory as well.
00960  */
00961 void vcard_delete_remove(char *room, long msgnum) {
00962        struct CtdlMessage *msg;
00963        char *ptr;
00964        int linelen;
00965 
00966        if (msgnum <= 0L) return;
00967        
00968        if (room == NULL) return;
00969 
00970        if (strcasecmp(room, ADDRESS_BOOK_ROOM)) {
00971               return;
00972        }
00973 
00974        msg = CtdlFetchMessage(msgnum, 1);
00975        if (msg == NULL) return;
00976 
00977        ptr = msg->cm_fields['M'];
00978        if (ptr == NULL) goto EOH;
00979        while (ptr != NULL) {
00980               linelen = strcspn(ptr, "\n");
00981               if (linelen == 0) goto EOH;
00982               
00983               if (  (!strncasecmp(ptr, "Content-type: text/x-vcard", 26))
00984                  || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
00985                      /* Bingo!  A vCard is being deleted. */
00986                      vcard_extract_internet_addresses(msg, CtdlDirectoryDelUser);
00987               }
00988               ptr = strchr((char *)ptr, '\n');
00989               if (ptr != NULL) ++ptr;
00990        }
00991 
00992 EOH:   CtdlFreeMessage(msg);
00993 }
00994 
00995 
00996 
00997 /*
00998  * Get Valid Screen Names
00999  */
01000 void cmd_gvsn(char *argbuf)
01001 {
01002        if (CtdlAccessCheck(ac_logged_in)) return;
01003 
01004        cprintf("%d valid screen names:\n", LISTING_FOLLOWS);
01005        cprintf("%s\n", CC->user.fullname);
01006        if ( (!IsEmptyStr(CC->cs_inet_fn)) && (strcasecmp(CC->user.fullname, CC->cs_inet_fn)) ) {
01007               cprintf("%s\n", CC->cs_inet_fn);
01008        }
01009        cprintf("000\n");
01010 }
01011 
01012 
01013 /*
01014  * Get Valid Email Addresses
01015  */
01016 void cmd_gvea(char *argbuf)
01017 {
01018        int num_secondary_emails = 0;
01019        int i;
01020        char buf[256];
01021 
01022        if (CtdlAccessCheck(ac_logged_in)) return;
01023 
01024        cprintf("%d valid email addresses:\n", LISTING_FOLLOWS);
01025        if (!IsEmptyStr(CC->cs_inet_email)) {
01026               cprintf("%s\n", CC->cs_inet_email);
01027        }
01028        if (!IsEmptyStr(CC->cs_inet_other_emails)) {
01029               num_secondary_emails = num_tokens(CC->cs_inet_other_emails, '|');
01030               for (i=0; i<num_secondary_emails; ++i) {
01031                      extract_token(buf, CC->cs_inet_other_emails,i,'|',sizeof CC->cs_inet_other_emails);
01032                      cprintf("%s\n", buf);
01033               }
01034        }
01035        cprintf("000\n");
01036 }
01037 
01038 
01039 
01040 
01041 /*
01042  * Callback function for cmd_dvca() that hunts for vCard content types
01043  * and outputs any email addresses found within.
01044  */
01045 void dvca_mime_callback(char *name, char *filename, char *partnum, char *disp,
01046               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
01047               char *cbid, void *cbuserdata) {
01048 
01049        struct vCard *v;
01050        char displayname[256] = "";
01051        int displayname_len;
01052        char emailaddr[256] = "";
01053        int i;
01054        int has_commas = 0;
01055 
01056        if ( (strcasecmp(cbtype, "text/vcard")) && (strcasecmp(cbtype, "text/x-vcard")) ) {
01057               return;
01058        }
01059 
01060        v = vcard_load(content);
01061        if (v == NULL) return;
01062 
01063        extract_friendly_name(displayname, sizeof displayname, v);
01064        extract_inet_email_addrs(emailaddr, sizeof emailaddr, NULL, 0, v, 0);
01065 
01066        displayname_len = strlen(displayname);
01067        for (i=0; i<displayname_len; ++i) {
01068               if (displayname[i] == '\"') displayname[i] = ' ';
01069               if (displayname[i] == ';') displayname[i] = ',';
01070               if (displayname[i] == ',') has_commas = 1;
01071        }
01072        striplt(displayname);
01073 
01074        cprintf("%s%s%s <%s>\n",
01075               (has_commas ? "\"" : ""),
01076               displayname,
01077               (has_commas ? "\"" : ""),
01078               emailaddr
01079        );
01080 
01081        vcard_free(v);
01082 }
01083 
01084 
01085 /*
01086  * Back end callback function for cmd_dvca()
01087  *
01088  * It's basically just passed a list of message numbers, which we're going
01089  * to fetch off the disk and then pass along to the MIME parser via another
01090  * layer of callback...
01091  */
01092 void dvca_callback(long msgnum, void *userdata) {
01093        struct CtdlMessage *msg = NULL;
01094 
01095        msg = CtdlFetchMessage(msgnum, 1);
01096        if (msg == NULL) return;
01097        mime_parser(msg->cm_fields['M'],
01098               NULL,
01099               *dvca_mime_callback, /* callback function */
01100               NULL, NULL,
01101               NULL,                /* user data */
01102               0
01103        );
01104        CtdlFreeMessage(msg);
01105 }
01106 
01107 
01108 /*
01109  * Dump VCard Addresses
01110  */
01111 void cmd_dvca(char *argbuf)
01112 {
01113        if (CtdlAccessCheck(ac_logged_in)) return;
01114 
01115        cprintf("%d addresses:\n", LISTING_FOLLOWS);
01116        CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, dvca_callback, NULL);
01117        cprintf("000\n");
01118 }
01119 
01120 
01121 /*
01122  * Query Directory
01123  */
01124 void cmd_qdir(char *argbuf) {
01125        char citadel_addr[256];
01126        char internet_addr[256];
01127 
01128        if (CtdlAccessCheck(ac_logged_in)) return;
01129 
01130        extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
01131 
01132        if (CtdlDirectoryLookup(citadel_addr, internet_addr, sizeof citadel_addr) != 0) {
01133               cprintf("%d %s was not found.\n",
01134                      ERROR + NO_SUCH_USER, internet_addr);
01135               return;
01136        }
01137 
01138        cprintf("%d %s\n", CIT_OK, citadel_addr);
01139 }
01140 
01141 /*
01142  * Query Directory, in fact an alias to match postfix tcp auth.
01143  */
01144 void check_get(void) {
01145        char internet_addr[256];
01146 
01147        char cmdbuf[SIZ];
01148 
01149        time(&CC->lastcmd);
01150        memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
01151        if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
01152               syslog(LOG_CRIT, "vcard client disconnected: ending session.");
01153               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
01154               return;
01155        }
01156        syslog(LOG_INFO, ": %s", cmdbuf);
01157        while (strlen(cmdbuf) < 3) strcat(cmdbuf, " ");
01158        syslog(LOG_INFO, "[ %s]", cmdbuf);
01159        
01160        if (strncasecmp(cmdbuf, "GET ", 4)==0)
01161        {
01162               struct recptypes *rcpt;
01163               char *argbuf = &cmdbuf[4];
01164               
01165               extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
01166               rcpt = validate_recipients(internet_addr, NULL, CHECK_EXISTANCE);
01167               if ((rcpt != NULL)&&
01168                      (
01169                       (*rcpt->recp_local != '\0')||
01170                       (*rcpt->recp_room != '\0')||
01171                       (*rcpt->recp_ignet != '\0')))
01172               {
01173 
01174                      cprintf("200 OK %s\n", internet_addr);
01175                      syslog(LOG_INFO, "sending 200 OK for the room %s", rcpt->display_recp);
01176               }
01177               else 
01178               {
01179                      cprintf("500 REJECT noone here by that name.\n");
01180                      
01181                      syslog(LOG_INFO, "sending 500 REJECT no one here by that name: %s", internet_addr);
01182               }
01183               if (rcpt != NULL) 
01184                      free_recipients(rcpt);
01185        }
01186        else {
01187               cprintf("500 REJECT invalid Query.\n");
01188               syslog(LOG_INFO, "sending 500 REJECT invalid query: %s", internet_addr);
01189        }
01190 }
01191 
01192 void check_get_greeting(void) {
01193 /* dummy function, we have no greeting in this verry simple protocol. */
01194 }
01195 
01196 
01197 /*
01198  * We don't know if the Contacts room exists so we just create it at login
01199  */
01200 void vcard_CtdlCreateRoom(void)
01201 {
01202        struct ctdlroom qr;
01203        visit vbuf;
01204 
01205        /* Create the calendar room if it doesn't already exist */
01206        CtdlCreateRoom(USERCONTACTSROOM, 4, "", 0, 1, 0, VIEW_ADDRESSBOOK);
01207 
01208        /* Set expiration policy to manual; otherwise objects will be lost! */
01209        if (CtdlGetRoomLock(&qr, USERCONTACTSROOM)) {
01210               syslog(LOG_ERR, "Couldn't get the user CONTACTS room!");
01211               return;
01212        }
01213        qr.QRep.expire_mode = EXPIRE_MANUAL;
01214        qr.QRdefaultview = VIEW_ADDRESSBOOK;      /* 2 = address book view */
01215        CtdlPutRoomLock(&qr);
01216 
01217        /* Set the view to a calendar view */
01218        CtdlGetRelationship(&vbuf, &CC->user, &qr);
01219        vbuf.v_view = 2;     /* 2 = address book view */
01220        CtdlSetRelationship(&vbuf, &CC->user, &qr);
01221 
01222        return;
01223 }
01224 
01225 
01226 
01227 
01228 /*
01229  * When a user logs in...
01230  */
01231 void vcard_session_login_hook(void) {
01232        struct vCard *v = NULL;
01233        struct CitContext *CCC = CC;              /* put this on the stack, just for speed */
01234 
01235 #ifdef HAVE_LDAP
01236        /*
01237         * Is this an LDAP session?  If so, copy various LDAP attributes from the directory entry
01238         * into the user's vCard.
01239         */
01240        if ((config.c_auth_mode == AUTHMODE_LDAP) || (config.c_auth_mode == AUTHMODE_LDAP_AD)) {
01241               v = vcard_get_user(&CCC->user);
01242               if (v) {
01243                      if (Ctdl_LDAP_to_vCard(CCC->ldap_dn, v)) {
01244                             vcard_write_user(&CCC->user, v);
01245                      }
01246               }
01247        }
01248 #endif
01249 
01250        /*
01251         * Extract from the user's vCard, any Internet email addresses and the user's real name.
01252         * These are inserted into the session data for various message entry commands to use.
01253         */
01254        v = vcard_get_user(&CCC->user);
01255        if (v) {
01256               extract_inet_email_addrs(CCC->cs_inet_email, sizeof CCC->cs_inet_email,
01257                                    CCC->cs_inet_other_emails, sizeof CCC->cs_inet_other_emails,
01258                                    v, 1
01259               );
01260               extract_friendly_name(CCC->cs_inet_fn, sizeof CCC->cs_inet_fn, v);
01261               vcard_free(v);
01262        }
01263 
01264        /*
01265         * Create the user's 'Contacts' room (personal address book) if it doesn't already exist.
01266         */
01267        vcard_CtdlCreateRoom();
01268 }
01269 
01270 
01271 /* 
01272  * Turn an arbitrary RFC822 address into a struct vCard for possible
01273  * inclusion into an address book.
01274  */
01275 struct vCard *vcard_new_from_rfc822_addr(char *addr) {
01276        struct vCard *v;
01277        char user[256], node[256], name[256], email[256], n[256], uid[256];
01278        int i;
01279 
01280        v = vcard_new();
01281        if (v == NULL) return(NULL);
01282 
01283        process_rfc822_addr(addr, user, node, name);
01284        vcard_set_prop(v, "fn", name, 0);
01285 
01286        vcard_fn_to_n(n, name, sizeof n);
01287        vcard_set_prop(v, "n", n, 0);
01288 
01289        snprintf(email, sizeof email, "%s@%s", user, node);
01290        vcard_set_prop(v, "email;internet", email, 0);
01291 
01292        snprintf(uid, sizeof uid, "collected: %s %s@%s", name, user, node);
01293        for (i=0; uid[i]; ++i) {
01294               if (isspace(uid[i])) uid[i] = '_';
01295               uid[i] = tolower(uid[i]);
01296        }
01297        vcard_set_prop(v, "UID", uid, 0);
01298 
01299        return(v);
01300 }
01301 
01302 
01303 
01304 /*
01305  * This is called by store_harvested_addresses() to remove from the
01306  * list any addresses we already have in our address book.
01307  */
01308 void strip_addresses_already_have(long msgnum, void *userdata) {
01309        char *collected_addresses;
01310        struct CtdlMessage *msg = NULL;
01311        struct vCard *v;
01312        char *value = NULL;
01313        int i, j;
01314        char addr[256], user[256], node[256], name[256];
01315 
01316        collected_addresses = (char *)userdata;
01317 
01318        msg = CtdlFetchMessage(msgnum, 1);
01319        if (msg == NULL) return;
01320        v = vcard_load(msg->cm_fields['M']);
01321        CtdlFreeMessage(msg);
01322 
01323        i = 0;
01324        while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
01325 
01326               for (j=0; j<num_tokens(collected_addresses, ','); ++j) {
01327                      extract_token(addr, collected_addresses, j, ',', sizeof addr);
01328 
01329                      /* Remove the address if we already have it! */
01330                      process_rfc822_addr(addr, user, node, name);
01331                      snprintf(addr, sizeof addr, "%s@%s", user, node);
01332                      if (!strcasecmp(value, addr)) {
01333                             remove_token(collected_addresses, j, ',');
01334                      }
01335               }
01336 
01337        }
01338 
01339        vcard_free(v);
01340 }
01341 
01342 
01343 
01344 /*
01345  * Back end function for store_harvested_addresses()
01346  */
01347 void store_this_ha(struct addresses_to_be_filed *aptr) {
01348        struct CtdlMessage *vmsg = NULL;
01349        char *ser = NULL;
01350        struct vCard *v = NULL;
01351        char recipient[256];
01352        int i;
01353 
01354        /* First remove any addresses we already have in the address book */
01355        CtdlUserGoto(aptr->roomname, 0, 0, NULL, NULL);
01356        CtdlForEachMessage(MSGS_ALL, 0, NULL, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$", NULL,
01357               strip_addresses_already_have, aptr->collected_addresses);
01358 
01359        if (!IsEmptyStr(aptr->collected_addresses))
01360           for (i=0; i<num_tokens(aptr->collected_addresses, ','); ++i) {
01361 
01362               /* Make a vCard out of each address */
01363               extract_token(recipient, aptr->collected_addresses, i, ',', sizeof recipient);
01364               striplt(recipient);
01365               v = vcard_new_from_rfc822_addr(recipient);
01366               if (v != NULL) {
01367                      vmsg = malloc(sizeof(struct CtdlMessage));
01368                      memset(vmsg, 0, sizeof(struct CtdlMessage));
01369                      vmsg->cm_magic = CTDLMESSAGE_MAGIC;
01370                      vmsg->cm_anon_type = MES_NORMAL;
01371                      vmsg->cm_format_type = FMT_RFC822;
01372                      vmsg->cm_fields['A'] = strdup("Citadel");
01373                      vmsg->cm_fields['E'] =  strdup(vcard_get_prop(v, "UID", 1, 0, 0));
01374                      ser = vcard_serialize(v);
01375                      if (ser != NULL) {
01376                             vmsg->cm_fields['M'] = malloc(strlen(ser) + 1024);
01377                             sprintf(vmsg->cm_fields['M'],
01378                                    "Content-type: " VCARD_MIME_TYPE
01379                                    "\r\n\r\n%s\r\n", ser);
01380                             free(ser);
01381                      }
01382                      vcard_free(v);
01383 
01384                      syslog(LOG_DEBUG, "Adding contact: %s", recipient);
01385                      CtdlSubmitMsg(vmsg, NULL, aptr->roomname, QP_EADDR);
01386                      CtdlFreeMessage(vmsg);
01387               }
01388        }
01389 
01390        free(aptr->roomname);
01391        free(aptr->collected_addresses);
01392        free(aptr);
01393 }
01394 
01395 
01396 /*
01397  * When a user sends a message, we may harvest one or more email addresses
01398  * from the recipient list to be added to the user's address book.  But we
01399  * want to do this asynchronously so it doesn't keep the user waiting.
01400  */
01401 void store_harvested_addresses(void) {
01402 
01403        struct addresses_to_be_filed *aptr = NULL;
01404 
01405        if (atbf == NULL) return;
01406 
01407        begin_critical_section(S_ATBF);
01408        while (atbf != NULL) {
01409               aptr = atbf;
01410               atbf = atbf->next;
01411               end_critical_section(S_ATBF);
01412               store_this_ha(aptr);
01413               begin_critical_section(S_ATBF);
01414        }
01415        end_critical_section(S_ATBF);
01416 }
01417 
01418 
01419 /* 
01420  * Function to output vCard data as plain text.  Nobody uses MSG0 anymore, so
01421  * really this is just so we expose the vCard data to the full text indexer.
01422  */
01423 void vcard_fixed_output(char *ptr, int len) {
01424        char *serialized_vcard;
01425        struct vCard *v;
01426        char *key, *value;
01427        int i = 0;
01428 
01429        serialized_vcard = malloc(len + 1);
01430        safestrncpy(serialized_vcard, ptr, len+1);
01431        v = vcard_load(serialized_vcard);
01432        free(serialized_vcard);
01433 
01434        i = 0;
01435        while (key = vcard_get_prop(v, "", 0, i, 1), key != NULL) {
01436               value = vcard_get_prop(v, "", 0, i++, 0);
01437               cprintf("%s\n", value);
01438        }
01439 
01440        vcard_free(v);
01441 }
01442 
01443 
01444 const char *CitadelServiceDICT_TCP="DICT_TCP";
01445 
01446 CTDL_MODULE_INIT(vcard)
01447 {
01448        struct ctdlroom qr;
01449        char filename[256];
01450        FILE *fp;
01451        int rv = 0;
01452 
01453        if (!threading)
01454        {
01455               CtdlRegisterSessionHook(vcard_session_login_hook, EVT_LOGIN, PRIO_LOGIN + 70);
01456               CtdlRegisterMessageHook(vcard_upload_beforesave, EVT_BEFORESAVE);
01457               CtdlRegisterMessageHook(vcard_upload_aftersave, EVT_AFTERSAVE);
01458               CtdlRegisterDeleteHook(vcard_delete_remove);
01459               CtdlRegisterProtoHook(cmd_regi, "REGI", "Enter registration info");
01460               CtdlRegisterProtoHook(cmd_greg, "GREG", "Get registration info");
01461               CtdlRegisterProtoHook(cmd_igab, "IGAB", "Initialize Global Address Book");
01462               CtdlRegisterProtoHook(cmd_qdir, "QDIR", "Query Directory");
01463               CtdlRegisterProtoHook(cmd_gvsn, "GVSN", "Get Valid Screen Names");
01464               CtdlRegisterProtoHook(cmd_gvea, "GVEA", "Get Valid Email Addresses");
01465               CtdlRegisterProtoHook(cmd_dvca, "DVCA", "Dump VCard Addresses");
01466               CtdlRegisterUserHook(vcard_newuser, EVT_NEWUSER);
01467               CtdlRegisterUserHook(vcard_purge, EVT_PURGEUSER);
01468               CtdlRegisterNetprocHook(vcard_extract_from_network);
01469               CtdlRegisterSessionHook(store_harvested_addresses, EVT_TIMER, PRIO_CLEANUP + 470);
01470               CtdlRegisterFixedOutputHook("text/x-vcard", vcard_fixed_output);
01471               CtdlRegisterFixedOutputHook("text/vcard", vcard_fixed_output);
01472 
01473               /* Create the Global ADdress Book room if necessary */
01474               CtdlCreateRoom(ADDRESS_BOOK_ROOM, 3, "", 0, 1, 0, VIEW_ADDRESSBOOK);
01475 
01476               /* Set expiration policy to manual; otherwise objects will be lost! */
01477               if (!CtdlGetRoomLock(&qr, ADDRESS_BOOK_ROOM)) {
01478                      qr.QRep.expire_mode = EXPIRE_MANUAL;
01479                      qr.QRdefaultview = VIEW_ADDRESSBOOK;      /* 2 = address book view */
01480                      CtdlPutRoomLock(&qr);
01481 
01482                      /*
01483                       * Also make sure it has a netconfig file, so the networker runs
01484                       * on this room even if we don't share it with any other nodes.
01485                       * This allows the CANCEL messages (i.e. "Purge this vCard") to be
01486                       * purged.
01487                       */
01488                      assoc_file_name(filename, sizeof filename, &qr, ctdl_netcfg_dir);
01489                      fp = fopen(filename, "a");
01490                      if (fp != NULL) fclose(fp);
01491                      rv = chown(filename, CTDLUID, (-1));
01492                      if (rv == -1)
01493                             syslog(LOG_EMERG, "Failed to adjust ownership of: %s [%s]", 
01494                                    filename, strerror(errno));
01495               }
01496 
01497               /* for postfix tcpdict */
01498               CtdlRegisterServiceHook(config.c_pftcpdict_port, /* Postfix */
01499                                    NULL,
01500                                    check_get_greeting,
01501                                    check_get,
01502                                    NULL,
01503                                    CitadelServiceDICT_TCP);
01504        }
01505        
01506        /* return our module name for the log */
01507        return "vcard";
01508 }