Move vcard.c into libcitadel.
[citadel.git] / citadel / modules / vcard / serv_vcard.c
index 99ed9026a6db0f55a5228aca77cdcbc93e95aaa6..7cb8d6d531f65b9506cf9ad96db26cbcc0c660ec 100644 (file)
@@ -47,6 +47,7 @@
 #include <sys/wait.h>
 #include <string.h>
 #include <limits.h>
+#include <libcitadel.h>
 #include "citadel.h"
 #include "server.h"
 #include "citserver.h"
 #include "database.h"
 #include "msgbase.h"
 #include "internet_addressing.h"
-#include "tools.h"
-#include "mime_parser.h"
-#include "vcard.h"
 #include "serv_vcard.h"
-#include "serv_ldap.h"
 
 #include "ctdl_module.h"
 
@@ -128,6 +125,195 @@ void vcard_extract_internet_addresses(struct CtdlMessage *msg,
 }
 
 
+/* 
+ * vCard-to-LDAP conversions.
+ *
+ * If 'op' is set to V2L_WRITE, then write
+ * (add, or change if already exists) a directory entry to the
+ * LDAP server, based on the information supplied in a vCard.
+ *
+ * If 'op' is set to V2L_DELETE, then delete the entry from LDAP.
+ */
+void ctdl_vcard_to_directory(struct CtdlMessage *msg, int op) {
+       struct vCard *v = NULL;
+       int i;
+       int have_addr = 0;
+       int have_cn = 0;
+       
+       void *objectlist = NULL;
+
+       char givenname[128];
+       char sn[128];
+       char uid[256];
+       char street[256];
+       char city[128];
+       char state[3];
+       char zipcode[10];
+       char calFBURL[256];
+       char ldap_dn[SIZ];
+
+       if (msg == NULL) return;
+       if (msg->cm_fields['M'] == NULL) return;
+       if (msg->cm_fields['A'] == NULL) return;
+       if (msg->cm_fields['N'] == NULL) return;
+
+       /* Initialize variables */
+       strcpy(givenname, "");
+       strcpy(sn, "");
+       strcpy(calFBURL, "");
+
+       sprintf(uid, "%s@%s",
+               msg->cm_fields['A'],
+               msg->cm_fields['N']
+       );
+
+       sprintf(ldap_dn, "euid=%s,ou=%s", msg->cm_fields['E'], msg->cm_fields['N']);
+       
+       /* Are we just deleting?  If so, it's simple... */
+       if (op == V2L_DELETE) {
+               (void) CtdlDoDirectoryServiceFunc (ldap_dn, NULL, NULL, "ldap", DIRECTORY_USER_DEL);
+               return;
+       }
+
+       /*
+        * If we get to this point then it must be a V2L_WRITE operation.
+        */
+
+       /* First make sure the OU for the user's home Citadel host is created */
+       (void) CtdlDoDirectoryServiceFunc (NULL, msg->cm_fields['N'], NULL, "ldap", DIRECTORY_CREATE_HOST);
+       
+       /* Next create the directory service object */
+       (void) CtdlDoDirectoryServiceFunc(NULL, NULL, &objectlist, "ldap", DIRECTORY_CREATE_OBJECT);
+
+       /* The first LDAP attribute will be an 'objectclass' list.  Citadel
+        * doesn't do anything with this.  It's just there for compatibility
+        * with Kolab.
+        */
+       (void) CtdlDoDirectoryServiceFunc("objectclass", "citadelInetOrgPerson", &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+
+       /* Convert the vCard fields to LDAP properties */
+       v = vcard_load(msg->cm_fields['M']);
+       if (v->numprops) for (i=0; i<(v->numprops); ++i) if (striplt(v->prop[i].value), strlen(v->prop[i].value) > 0) {
+
+               if (!strcasecmp(v->prop[i].name, "n")) {
+                       extract_token(sn,               v->prop[i].value, 0, ';', sizeof sn);
+                       extract_token(givenname,        v->prop[i].value, 1, ';', sizeof givenname);
+               }
+
+               if (!strcasecmp(v->prop[i].name, "fn")) {
+                       (void) CtdlDoDirectoryServiceFunc("cn", v->prop[i].value, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+                       have_cn = 1;
+               }
+
+               if (!strcasecmp(v->prop[i].name, "title")) {
+                       (void) CtdlDoDirectoryServiceFunc("title", v->prop[i].value, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+               }
+
+               if (!strcasecmp(v->prop[i].name, "org")) {
+                       (void) CtdlDoDirectoryServiceFunc("o", v->prop[i].value, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+               }
+
+               if ( (!strcasecmp(v->prop[i].name, "adr"))
+                  ||(!strncasecmp(v->prop[i].name, "adr;", 4)) ) {
+                       /* Unfortunately, we can only do a single address */
+                       if (!have_addr) {
+                               have_addr = 1;
+                               strcpy(street, "");
+                               extract_token(&street[strlen(street)],
+                                       v->prop[i].value, 0, ';', (sizeof street - strlen(street))); /* po box */
+                               strcat(street, " ");
+                               extract_token(&street[strlen(street)],
+                                       v->prop[i].value, 1, ';', (sizeof street - strlen(street))); /* extend addr */
+                               strcat(street, " ");
+                               extract_token(&street[strlen(street)],
+                                       v->prop[i].value, 2, ';', (sizeof street - strlen(street))); /* street */
+                               striplt(street);
+                               extract_token(city, v->prop[i].value, 3, ';', sizeof city);
+                               extract_token(state, v->prop[i].value, 4, ';', sizeof state);
+                               extract_token(zipcode, v->prop[i].value, 5, ';', sizeof zipcode);
+
+                               // ldap requires these fields to be something
+                               if (IsEmptyStr(street)) strcpy(street, "_");
+                               if (IsEmptyStr(zipcode)) strcpy(zipcode, "_");
+                               if (IsEmptyStr(city)) strcpy(city, "_");
+                               if (IsEmptyStr(state)) strcpy(state, "_");
+
+                               (void) CtdlDoDirectoryServiceFunc("street", street, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+                               (void) CtdlDoDirectoryServiceFunc("l", city, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+                               (void) CtdlDoDirectoryServiceFunc("st", state, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+                               (void) CtdlDoDirectoryServiceFunc("postalcode", zipcode, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+                       }
+               }
+
+               if (!strcasecmp(v->prop[i].name, "tel;home"))
+                       (void) CtdlDoDirectoryServiceFunc("homePhone", v->prop[i].value, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+               else
+               if (!strcasecmp(v->prop[i].name, "tel;fax"))
+                       (void) CtdlDoDirectoryServiceFunc("facsimileTelephoneNumber", v->prop[i].value, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+               else
+               if (!strcasecmp(v->prop[i].name, "tel;cell"))
+                       (void) CtdlDoDirectoryServiceFunc("mobile", v->prop[i].value, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+               else
+               if ( (!strcasecmp(v->prop[i].name, "tel"))
+                  ||(!strncasecmp(v->prop[i].name, "tel;", 4)) ) {
+                       (void) CtdlDoDirectoryServiceFunc("telephoneNumber", v->prop[i].value, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+               }
+
+
+               if ( (!strcasecmp(v->prop[i].name, "email"))
+                  ||(!strcasecmp(v->prop[i].name, "email;internet")) ) {
+                       (void) CtdlDoDirectoryServiceFunc("mail", v->prop[i].value, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+               }
+
+               /* Calendar free/busy URL (take the first one we find, but if a subsequent
+                * one contains the "pref" designation then we go with that instead.)
+                */
+               if ( (!strcasecmp(v->prop[i].name, "fburl"))
+                  ||(!strncasecmp(v->prop[i].name, "fburl;", 6)) ) {
+                       if ( (IsEmptyStr(calFBURL))
+                          || (!strncasecmp(v->prop[i].name, "fburl;pref", 10)) ) {
+                               safestrncpy(calFBURL, v->prop[i].value, sizeof calFBURL);
+                       }
+               }
+
+       }
+       vcard_free(v);  /* Don't need this anymore. */
+
+       /* "sn" (surname) based on info in vCard */
+       (void) CtdlDoDirectoryServiceFunc("sn", sn, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+
+       /* "givenname" (first name) based on info in vCard */
+       if (IsEmptyStr(givenname)) strcpy(givenname, "_");
+       if (IsEmptyStr(sn)) strcpy(sn, "_");
+       (void) CtdlDoDirectoryServiceFunc("givenname", givenname, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+
+       /* "uid" is a Kolab compatibility thing.  We just do cituser@citnode */
+       (void) CtdlDoDirectoryServiceFunc("uid", uid, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+
+       /* Add a "cn" (Common Name) attribute based on the user's screen name,
+        * but only there was no 'fn' (full name) property in the vCard 
+        */
+       if (!have_cn)
+               (void) CtdlDoDirectoryServiceFunc("cn", msg->cm_fields['A'], &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+
+       /* Add a "calFBURL" attribute if a calendar free/busy URL exists */
+       if (!IsEmptyStr(calFBURL)) {
+               (void) CtdlDoDirectoryServiceFunc("calFBURL", calFBURL, &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+       }
+       
+       // Add this messages EUID as the primary key for this entry.
+       (void) CtdlDoDirectoryServiceFunc("euid", msg->cm_fields['E'], &objectlist, "ldap", DIRECTORY_ATTRIB_ADD);
+       
+       
+       (void) CtdlDoDirectoryServiceFunc(ldap_dn, NULL, &objectlist, "ldap", DIRECTORY_SAVE_OBJECT);
+       
+       (void) CtdlDoDirectoryServiceFunc(NULL, NULL, &objectlist, "ldap", DIRECTORY_FREE_OBJECT);
+       lprintf(CTDL_DEBUG, "Directory Services write operation complete.\n");
+}
+
+
 
 /*
  * Callback for vcard_add_to_directory()
@@ -169,9 +355,7 @@ void vcard_add_to_directory(long msgnum, void *data) {
                vcard_extract_internet_addresses(msg, vcard_directory_add_user);
        }
 
-#ifdef HAVE_LDAP
-       ctdl_vcard_to_ldap(msg, V2L_WRITE);
-#endif
+       ctdl_vcard_to_directory(msg, V2L_WRITE);
 
        CtdlFreeMessage(msg);
 }
@@ -404,7 +588,7 @@ int vcard_upload_beforesave(struct CtdlMessage *msg) {
                sprintf(buf, "http://%s/%s.vfb",
                        config.c_fqdn,
                        usbuf.fullname);
-               for (i=0; !IsEmptyStr(&buf[i]); ++i) {
+               for (i=0; buf[i]; ++i) {
                        if (buf[i] == ' ') buf[i] = '_';
                }
                vcard_set_prop(v, "FBURL;PREF", buf, 0);
@@ -477,6 +661,8 @@ int vcard_upload_aftersave(struct CtdlMessage *msg) {
        int linelen;
        long I;
        struct vCard *v;
+       int is_UserConf=0;
+       int is_GAB=0;
 
        if (!CC->logged_in) return(0);  /* Only do this if logged in. */
 
@@ -484,7 +670,9 @@ int vcard_upload_aftersave(struct CtdlMessage *msg) {
         * message, don't bother.
         */
        if (msg->cm_fields['O'] == NULL) return(0);
-       if (strcasecmp(msg->cm_fields['O'], USERCONFIGROOM)) return(0);
+       if (!strcasecmp(msg->cm_fields['O'], USERCONFIGROOM)) is_UserConf = 1;
+       if (!strcasecmp(msg->cm_fields['O'], ADDRESS_BOOK_ROOM)) is_GAB = 1;
+       if (!is_UserConf && !is_GAB) return(0);
        if (msg->cm_format_type != 4) return(0);
 
        ptr = msg->cm_fields['M'];
@@ -512,8 +700,11 @@ int vcard_upload_aftersave(struct CtdlMessage *msg) {
                        extract_friendly_name(CC->cs_inet_fn, sizeof CC->cs_inet_fn, v);
                        vcard_free(v);
 
-                       /* Put it in the Global Address Book room... */
-                       CtdlSaveMsgPointerInRoom(ADDRESS_BOOK_ROOM, I, 1, msg);
+                       if (!is_GAB)
+                       {       // This is not the GAB
+                               /* Put it in the Global Address Book room... */
+                               CtdlSaveMsgPointerInRoom(ADDRESS_BOOK_ROOM, I, 1, msg);
+                       }
 
                        /* ...and also in the directory database. */
                        vcard_add_to_directory(I, NULL);
@@ -675,7 +866,7 @@ void cmd_regi(char *argbuf) {
                if (a==2) strcpy(tmpcity, buf);
                if (a==3) strcpy(tmpstate, buf);
                if (a==4) {
-                       for (c=0; !IsEmptyStr(&buf[c]); ++c) {
+                       for (c=0; buf[c]; ++c) {
                                if ((buf[c]>='0') && (buf[c]<='9')) {
                                        b = strlen(tmpzip);
                                        tmpzip[b] = buf[c];
@@ -789,7 +980,7 @@ void vcard_newuser(struct ctdluser *usbuf) {
        v = vcard_new();
        if (v == NULL) return;
        sprintf(buf, "%s@%s", usbuf->fullname, config.c_fqdn);
-       for (i=0; !IsEmptyStr(&buf[i]); ++i) {
+       for (i=0; buf[i]; ++i) {
                if (buf[i] == ' ') buf[i] = '_';
        }
        vcard_add_prop(v, "fn", usbuf->fullname);
@@ -901,9 +1092,7 @@ void vcard_delete_remove(char *room, long msgnum) {
                   || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
                        /* Bingo!  A vCard is being deleted. */
                        vcard_extract_internet_addresses(msg, CtdlDirectoryDelUser);
-#ifdef HAVE_LDAP
-                       ctdl_vcard_to_ldap(msg, V2L_DELETE);
-#endif
+                       ctdl_vcard_to_directory(msg, V2L_DELETE);
                }
                ptr = strchr((char *)ptr, '\n');
                if (ptr != NULL) ++ptr;
@@ -1178,7 +1367,7 @@ struct vCard *vcard_new_from_rfc822_addr(char *addr) {
        vcard_set_prop(v, "email;internet", email, 0);
 
        snprintf(uid, sizeof uid, "collected: %s %s@%s", name, user, node);
-       for (i=0; !IsEmptyStr(&uid[i]); ++i) {
+       for (i=0; uid[i]; ++i) {
                if (isspace(uid[i])) uid[i] = '_';
                uid[i] = tolower(uid[i]);
        }
@@ -1330,7 +1519,7 @@ void vcard_fixed_output(char *ptr, int len) {
 }
 
 
-
+const char *CitadelServiceDICT_TCP="DICT_TCP";
 
 CTDL_MODULE_INIT(vcard)
 {
@@ -1383,7 +1572,8 @@ CTDL_MODULE_INIT(vcard)
                                NULL,
                                check_get_greeting,
                                check_get,
-                               NULL);
+                               NULL,
+                               CitadelServiceDICT_TCP);
        
        /* return our Subversion id for the Log */
        return "$Id$";