/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ // // mkldap.cpp -- Handles LDAP-related URLs for the core Navigator, without // requiring libmsg. mkldap is responsible for // 1. basic async searching of LDAP servers, including authentication, // capability negotiation with the server, and parsing results // 2. Reading LDAP entries and converting them into HTML for display // in the browsers // 3. Reading an LDAP entry and converting it for use in the local // Address Book // 4. Replicating a large section of an LDAP directory into a local // Address Book using changelog deltas // #include "rosetta.h" #include "mkutils.h" #include "mkgeturl.h" #include "mkldap.h" #include "xpgetstr.h" #include "abcom.h" #include "addrbook.h" #include "msgcom.h" #include "msgnet.h" #if 0 #include "msgpane.h" #include "msgurlq.h" #endif #include "dirprefs.h" #include "net.h" #include "xp_str.h" #include HG87373 #include "secnav.h" #include "libi18n.h" #include "intl_csi.h" #if 0 #include "pw_public.h" #endif #ifdef MOZ_LDAP #ifdef MOZ_LI #include "LILDAPAllOps.h" #endif #define NEEDPROTOS #define LDAP_REFERRALS #include "lber.h" #include "ldap.h" #endif extern "C" { extern int MK_OUT_OF_MEMORY; extern int MK_MSG_SEARCH_FAILED; extern int MK_LDAP_COMMON_NAME; extern int MK_LDAP_FAX_NUMBER; extern int MK_LDAP_GIVEN_NAME; extern int MK_LDAP_LOCALITY; extern int MK_LDAP_PHOTOGRAPH; extern int MK_LDAP_EMAIL_ADDRESS; extern int MK_LDAP_MANAGER; extern int MK_LDAP_ORGANIZATION; extern int MK_LDAP_OBJECT_CLASS; extern int MK_LDAP_ORG_UNIT; extern int MK_LDAP_POSTAL_ADDRESS; extern int MK_LDAP_SECRETARY; extern int MK_LDAP_SURNAME; extern int MK_LDAP_STREET; extern int MK_LDAP_PHONE_NUMBER; extern int MK_LDAP_CAR_LICENSE; extern int MK_LDAP_BUSINESS_CAT; extern int MK_LDAP_DEPT_NUMBER; extern int MK_LDAP_DESCRIPTION; extern int MK_LDAP_EMPLOYEE_TYPE; extern int MK_LDAP_POSTAL_CODE; extern int MK_LDAP_TITLE; extern int MK_LDAP_ADD_SERVER_TO_PREFS; extern int MK_MALFORMED_URL_ERROR; HG73226 extern int MK_LDAP_HTML_TITLE; extern int MK_LDAP_AUTH_PROMPT; extern int XP_LDAP_OPEN_SERVER_FAILED; extern int XP_LDAP_BIND_FAILED; extern int XP_LDAP_SEARCH_FAILED; } #ifdef MOZ_LDAP //***************************************************************************** // // net_LdapConnectionData is the base class which knows how to run // ldap URLs asynchronously and pull out search results. // // If you want to do something special with the results of an LDAP // search, you probably want to derive from this class and use/make // virtual functions. // class net_LdapConnectionData { public: net_LdapConnectionData (ActiveEntry*, XP_Bool bInitLdap = TRUE); virtual ~net_LdapConnectionData (); virtual int Load (ActiveEntry *ce); virtual int Process (); virtual int Interrupt (); protected: typedef enum _LdapConnectionType { kBaseConnection, kHtmlConnection, kReplicatorConnection, kAddressBookConnection, kLIConnection } LdapConnectionType; virtual int Unbind (); virtual int OnBindCompleted () { return 0; } virtual int OnEntryFound (LDAPMessage *) { return 0; } virtual int OnSearchCompleted () { return MK_CONNECTED; } virtual XP_Bool OnError(int err) { return TRUE; } virtual int16 GetLocalCharSet (); virtual LdapConnectionType GetConnectionType() { return kBaseConnection; } void AddLdapServerToPrefs (LDAPURLDesc* pDesc); char *ConvertFromServerCharSet (char *s); char *ConvertToServerCharSet (char *s); // sub classes may have strings prefixed to the url...We need to filter them out here. virtual char * ExtractLDAPEncoding(char * s); XP_Bool ShouldAddUrlDescToPrefs (MWContext *context, XP_List *serverList, LDAPURLDesc *pDesc); void ConvertUrlDescToDirServer (LDAPURLDesc *pDesc, DIR_Server **pServer); const char *GetHostDescription (); char *GetAttributeFromDN (char *dn, const char *name); const char *GetAttributeString (DIR_AttributeId id); void DisplayError (int templateId, const char *contextString, int ldapErrorId); int16 m_csid; LDAP *m_ldap; int m_messageId; ActiveEntry *m_ce; DIR_Server *m_server; }; net_LdapConnectionData::net_LdapConnectionData (ActiveEntry *ce, XP_Bool bInitLdap) { // We init LDAP with no server name and port since we expect all // such information to be spedified by URLs in our case. // if (bInitLdap) m_ldap = ldap_init(NULL,0); else m_ldap = NULL; m_messageId = -1; // Init to an illegal value so we can detect errors m_server = NULL; m_csid = -1; m_ce = ce; m_ce->socket = NULL; // Netlib magic so we get a timeslice without waiting for a read-ready socket. // It would be nice to ditch this, but the LDAP SDK manages the sockets. NET_SetCallNetlibAllTheTime(m_ce->window_id, "mkldap"); } net_LdapConnectionData::~net_LdapConnectionData () { // This is the normal-termination case to let go of the connection Unbind(); // Netlib magic so we won't get called anymore for this URL // It would be nice to ditch this, but the LDAP SDK manages the sockets. NET_ClearCallNetlibAllTheTime(m_ce->window_id, "mkldap"); } int16 net_LdapConnectionData::GetLocalCharSet () { if (m_csid == -1) m_csid = INTL_DefaultDocCharSetID(0); return m_csid; } void net_LdapConnectionData::AddLdapServerToPrefs (LDAPURLDesc* pDesc) { // Takes an ActiveEntry from the current operation, a url descriptor from ldap_url_parse, // determines (by asking the user) if we should add it to our LDAP prefs, and adds it if necessary. // Do we want to add the server specified by the URL to our LDAP prefs? XP_List *serverList = FE_GetDirServers(); if (serverList && ShouldAddUrlDescToPrefs(m_ce->window_id, serverList, pDesc)) { ConvertUrlDescToDirServer (pDesc, &m_server); if (m_server) { XP_ListAddObjectToEnd (serverList, m_server); DIR_SaveServerPreferences (serverList); } } } int net_LdapConnectionData::Load (ActiveEntry *ce) { int ldapErr = 0; LDAPURLDesc *pDesc = NULL; XP_ASSERT(ce); XP_ASSERT(ce->URL_s); XP_ASSERT(ce->URL_s->address); if (m_ldap) { // addbook and html have prefixes in front of them (i.e. addbook-ldap). We now want // to parse that out of the utf8 encoding string that will be sent to the server. // In addition, we want to convert the resulting encoding to utf8 // we do not need to free encoding because it is a part of the url passed in.... char * encoding = ExtractLDAPEncoding(ce->URL_s->address); // now take the ldap encoding and convert to the server char set. char *utf8Encoding = utf8Encoding = ConvertToServerCharSet (encoding); if (utf8Encoding) { ldapErr = ldap_url_parse (utf8Encoding, &pDesc); if (!pDesc || ldapErr != LDAP_SUCCESS) ldapErr = MK_MALFORMED_URL_ERROR; if (ldapErr == 0) { // RFC 1959 allows this syntax, but we don't really have a way // to infer an LDAP server, and it's a pretty strange X.500-ism anyway if (!pDesc->lud_host) ldapErr = MK_MALFORMED_URL_ERROR; AddLdapServerToPrefs (pDesc); HG29237 if (ldapErr == 0) { // Initiate the search on the server int msgId = ldap_url_search (m_ldap, utf8Encoding, 0); if (msgId != -1) m_messageId = msgId; else ldapErr = -1; } } else ldapErr = MK_MALFORMED_URL_ERROR; XP_FREE(utf8Encoding); } else ldapErr = -1; } else XP_ASSERT(pDesc); ldapErr = -1; if (pDesc) ldap_free_urldesc (pDesc); return ldapErr; } // Interpret all data we get from the server as being UTF-8 char *net_LdapConnectionData::ConvertFromServerCharSet (char *s) { char *converted = NULL; XP_ASSERT(s); if (s) { // Use the win_csid for browsers but the GUI csid for the AB int16 destCsid = GetLocalCharSet(); converted = DIR_ConvertFromServerCharSet (m_server, s, destCsid); } return converted; } char * net_LdapConnectionData::ExtractLDAPEncoding(char * url) { // for the base class, we don't know about stripping any prefixes off... return url; } // Send all data to the server in UTF-8 char *net_LdapConnectionData::ConvertToServerCharSet (char *s) { char *converted = NULL; XP_ASSERT(s); if (s) { // Use the win_csid for browsers but the GUI csid for the AB int16 srcCsid = GetLocalCharSet(); converted = DIR_ConvertToServerCharSet (m_server, s, srcCsid); } return converted; } const char *net_LdapConnectionData::GetHostDescription() { // since the description is optional, it might be null, so pick up // the server's DNS name if necessary return m_server->description ? m_server->description : m_server->serverName; } char *net_LdapConnectionData::GetAttributeFromDN (char *dn, const char *name) { // Pull an attribute out of the distinguished name // e.g. the cn attribute from cn=John Smith,o=Netscape Communications Corp.,c=US char *result = NULL; char **explodedDn = ldap_explode_dn (dn, FALSE /* suppress types? */); if (explodedDn) { int i = 0; while (explodedDn[i] && !result) { const char *nameFromDn = XP_STRTOK (explodedDn[i], "="); if (!XP_STRCASECMP(name, nameFromDn)) { char *tmpResult = XP_STRDUP(explodedDn[i] + XP_STRLEN(nameFromDn) + 1); result = ConvertFromServerCharSet (tmpResult); XP_FREE(tmpResult); } i++; } ldap_value_free (explodedDn); } return result; } const char *net_LdapConnectionData::GetAttributeString (DIR_AttributeId id) { // This function is a bottleneck for DIR_GetAttributeName. I think most // of the time we'll have a DIR_Server, but I think it can also be NULL // if the user clicked on an ldap: URL for which there's no server in // the preferences return DIR_GetFirstAttributeString (m_server, id); } void net_LdapConnectionData::DisplayError (int templateId, const char *contextString, int ldapErrorId) { // This builds up error dialogs which look like: // "Failed to open connection to 'ldap.mcom.com' due to LDAP error 'bad DN name' (0x22)" char *templateString = XP_GetString (templateId); char *ldapErrString = NULL; if (ldapErrorId != 0) ldapErrString = ldap_err2string (ldapErrorId); if (templateString && contextString && (ldapErrorId == 0 || ldapErrString)) { int len = XP_STRLEN(templateString) + XP_STRLEN(contextString) + 10; if (ldapErrString) len += XP_STRLEN(ldapErrString); char *theBigString = (char*) XP_ALLOC (len); if (theBigString) { if (ldapErrorId) PR_snprintf (theBigString, len, templateString, contextString, ldapErrString, ldapErrorId); else PR_snprintf (theBigString, len, templateString, contextString); FE_Alert (m_ce->window_id, theBigString); XP_FREE(theBigString); } } } XP_Bool net_LdapConnectionData::ShouldAddUrlDescToPrefs (MWContext *context, XP_List *serverList, LDAPURLDesc *pDesc) { XP_Bool shouldAdd = TRUE; // First look through the prefs to see if the server already exists there for (int i = 1; i <= XP_ListCount(serverList) && shouldAdd; i++) { DIR_Server *server = (DIR_Server*) XP_ListGetObjectNum (serverList, i); if (server && server->serverName && pDesc->lud_host) { if (!XP_STRCASECMP(pDesc->lud_host, server->serverName)) { // The server does exist -- don't ask to add it again shouldAdd = FALSE; if (!m_server) m_server = server; } } } // If the server doesn't exist, ask the user if they want to add it to their prefs if (shouldAdd) { char *prompt = PR_smprintf (XP_GetString(MK_LDAP_ADD_SERVER_TO_PREFS), pDesc->lud_host); if (prompt) { shouldAdd = FE_Confirm (context, prompt); XP_FREE(prompt); } else shouldAdd = FALSE; } return shouldAdd; } void net_LdapConnectionData::ConvertUrlDescToDirServer (LDAPURLDesc *pDesc, DIR_Server **ppServer) { *ppServer = (DIR_Server*) XP_ALLOC (sizeof(DIR_Server)); if (*ppServer) { XP_BZERO(*ppServer, sizeof(DIR_Server)); // These fields just map right over (*ppServer)->serverName = XP_STRDUP (pDesc->lud_host); (*ppServer)->searchBase = XP_STRDUP (pDesc->lud_dn ? pDesc->lud_dn : ""); (*ppServer)->port = pDesc->lud_port; // These could probably be in a generic DIR_Server initializer. Do we need that? (*ppServer)->efficientWildcards = TRUE; (*ppServer)->saveResults = TRUE; (*ppServer)->maxHits = 100; // The address book needs a valid filename. Use the hostname to derive one. char *fileName = XP_STRTOK(pDesc->lud_host, "."); if (fileName) (*ppServer)->fileName = WH_FileName (fileName, xpAddrBook); else (*ppServer)->fileName = WH_FileName (pDesc->lud_host, xpAddrBook); // Try to derive a reasonable name for DIR_Server.description by looking // at the distinguished name and picking out the most specific RDN // (i.e. the last OU, or any OU, or any O, or any C). if (pDesc->lud_dn) { char **explodedDn = ldap_explode_dn (pDesc->lud_dn, TRUE /*suppress types? */); if (explodedDn) { if (explodedDn[0]) (*ppServer)->description = XP_STRDUP (explodedDn[0]); ldap_value_free (explodedDn); } } // If we couldn't find any reasonable-looking DN component, just use the host name if (NULL == (*ppServer)->description) (*ppServer)->description = XP_STRDUP ((*ppServer)->serverName); } } int net_LdapConnectionData::Process () { LDAPMessage *message = NULL; struct timeval timeout; XP_BZERO (&timeout, sizeof(timeout)); int result = ldap_result (m_ldap, m_messageId, 0, &timeout, &message); switch (result) { case LDAP_RES_SEARCH_ENTRY: result = OnEntryFound (message); break; case LDAP_RES_SEARCH_RESULT: result = OnSearchCompleted (); break; case LDAP_RES_BIND: result = OnBindCompleted (); break; case LDAP_TIMEOUT: result = 0; break; case 0: break; default: if (result > 0) { int err = ldap_result2error (m_ldap, message, 0); result = ((err != 0) ? err : result); } if (OnError(result)) { DisplayError (XP_LDAP_OPEN_SERVER_FAILED, GetHostDescription(), result); result = -1; } break; } if (message != NULL) ldap_msgfree (message); return result; } int net_LdapConnectionData::Interrupt () { // The destructor will unbind. int ldapErr = ldap_abandon (m_ldap, m_messageId); return ldapErr; } int net_LdapConnectionData::Unbind () { int ldapErr = 0; if (m_ldap) { ldapErr = ldap_unbind(m_ldap); m_ldap = NULL; } return ldapErr; } //***************************************************************************** // // net_LdapToHtml does the work of converting LDAP entries into HTML // which can be displayed in a browser window // const int kNumAttributeNames = 24; class net_LdapToHtml : public net_LdapConnectionData { public: net_LdapToHtml (ActiveEntry*); virtual int OnEntryFound (LDAPMessage *entry); virtual int OnSearchCompleted(); virtual int16 GetLocalCharSet (); typedef struct { char *attributeName; int resourceId; } AttributeName; protected: virtual LdapConnectionType GetConnectionType() { return kHtmlConnection; } int OpenStream (); int CloseStream (); int WriteLineToStream (const char *line, XP_Bool bold, XP_Bool lineBreak, XP_Bool isMailto = FALSE, XP_Bool isUri = FALSE, XP_Bool aligntop = FALSE, XP_Bool isCell = FALSE); NET_StreamClass *m_stream; const char *LookupAttributeName (const char *attrib); char *GetTableCaption (LDAPMessage*); int WriteEntryToStream (LDAPMessage*); int WriteValue (const char *, XP_Bool isMailto = FALSE, XP_Bool isUri = FALSE); int WriteAttribute (const char *); int WritePlainLine (const char *); XP_Bool IsMailtoAttribute (const char *); XP_Bool IsUriAttribute (const char *); char *ConvertDnToUri (const char *); char *EscapeSpaces (const char *); char *InferUrlBase (); static AttributeName m_names[kNumAttributeNames]; }; // Note that these are sorted. Someday I'll match attribs by binary search // // These are initialized to zero instead of their real resource IDs because // the HP/UX compiler seems not to be capable of doing that net_LdapToHtml::AttributeName net_LdapToHtml::m_names[kNumAttributeNames] = { { "carlicense", 0 }, { "cn", 0 }, { "businesscategory", 0 }, { "departmentnumber", 0 }, { "description", 0 }, { "employeetype", 0 }, { "facsimiletelephonenumber", 0 }, { "givenname", 0 }, { "l", 0 }, { "jpegPhoto", 0 }, { "mail", 0 }, { "manager", 0 }, { "o", 0 }, { "objectclass", 0 }, { "ou", 0 }, { "postaladdress", 0 }, { "postalcode", 0 }, { "secretary", 0 }, { "sn", 0 }, { "street", 0 }, { "telephonenumber", 0 }, { "title", 0 } HG38731 }; net_LdapToHtml::net_LdapToHtml (ActiveEntry *ce) : net_LdapConnectionData (ce) { m_stream = NULL; } int net_LdapToHtml::OnEntryFound (LDAPMessage *message) { int status = 0; if (!m_stream) status = OpenStream(); if (0 == status) status = WriteEntryToStream (message); return status; } int net_LdapToHtml::OnSearchCompleted () { CloseStream(); return net_LdapConnectionData::OnSearchCompleted(); } int16 net_LdapToHtml::GetLocalCharSet () { if (m_csid == -1) { INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(m_ce->window_id); m_csid = INTL_GetCSIWinCSID(c); if (CS_DEFAULT == m_csid) m_csid = INTL_DefaultWinCharSetID (0); } return m_csid; } int net_LdapToHtml::OpenStream () { int err = 0; if (NULL != m_stream) return err; if (!m_ce->URL_s->content_type) StrAllocCopy(m_ce->URL_s->content_type, TEXT_HTML); m_stream = NET_StreamBuilder(m_ce->format_out, m_ce->URL_s, m_ce->window_id); if (!m_stream) return MK_OUT_OF_MEMORY; // Scribble HTML-starting stuff into the stream char *htmlHeaders = PR_smprintf ("%s%s%s", XP_GetString(MK_LDAP_HTML_TITLE), LINEBREAK, LINEBREAK); if (htmlHeaders) err = (*(m_stream)->put_block)(m_stream, htmlHeaders, XP_STRLEN(htmlHeaders)); else err = MK_OUT_OF_MEMORY; if (1 == err) err = 0; //### which is it: 1 for success or 0 for success? return err; } int net_LdapToHtml::CloseStream () { int err = 0; // Never got any hits for this operation if (!m_stream) { if (((err = OpenStream()) == 0) && m_stream) { char *noMatches = PR_smprintf ("

%s

", XP_GetString(MK_MSG_SEARCH_FAILED)); if (noMatches) { err = (*(m_stream)->put_block)(m_stream, noMatches, XP_STRLEN(noMatches)); XP_FREE(noMatches); } } } // Scribble HTML-ending stuff into the stream if (m_stream) { char htmlFooters[32]; PR_snprintf (htmlFooters, sizeof(htmlFooters), "%s%s", LINEBREAK, LINEBREAK); err = (*(m_stream)->put_block)(m_stream, htmlFooters, XP_STRLEN(htmlFooters)); if (1 == err) err = 0; //### which is it: 1 for success or 0 for success? // complete the stream (*m_stream->complete) (m_stream); XP_FREE(m_stream); } return err; } const char *net_LdapToHtml::LookupAttributeName (const char *attrib) { // If we know about this attribute (it has a DIR_AttributeId and we // have a DIR_Server to ask, get the name from the DIR_Server DIR_AttributeId id; if (0 == DIR_AttributeNameToId (attrib, &id) && m_server) return DIR_GetAttributeName (m_server, id); // If we didn't get the name from LDAP prefs, get it using our // own hacky internal table. ### This should really move into the LDAP SDK. // Fill these resource IDs in here since the HP/UX compiler can't do it // in the initializer for the static array. if (m_names[0].resourceId == 0) { m_names[0].resourceId = MK_LDAP_CAR_LICENSE; m_names[1].resourceId = MK_LDAP_COMMON_NAME; m_names[2].resourceId = MK_LDAP_BUSINESS_CAT; m_names[3].resourceId = MK_LDAP_DEPT_NUMBER; m_names[4].resourceId = MK_LDAP_DESCRIPTION; m_names[5].resourceId = MK_LDAP_EMPLOYEE_TYPE; m_names[6].resourceId = MK_LDAP_FAX_NUMBER; m_names[7].resourceId = MK_LDAP_GIVEN_NAME; m_names[8].resourceId = MK_LDAP_LOCALITY; m_names[9].resourceId = MK_LDAP_PHOTOGRAPH; m_names[10].resourceId = MK_LDAP_EMAIL_ADDRESS; m_names[11].resourceId = MK_LDAP_MANAGER; m_names[12].resourceId = MK_LDAP_ORGANIZATION; m_names[13].resourceId = MK_LDAP_OBJECT_CLASS; m_names[14].resourceId = MK_LDAP_ORG_UNIT; m_names[15].resourceId = MK_LDAP_POSTAL_ADDRESS; m_names[16].resourceId = MK_LDAP_POSTAL_CODE; m_names[17].resourceId = MK_LDAP_SECRETARY; m_names[18].resourceId = MK_LDAP_SURNAME; m_names[19].resourceId = MK_LDAP_STREET; m_names[20].resourceId = MK_LDAP_PHONE_NUMBER; m_names[21].resourceId = MK_LDAP_TITLE; m_names[22].resourceId = HG87272; m_names[23].resourceId = HG73272; } for (int i = 0; i < kNumAttributeNames; i++) { if (!XP_STRCASECMP(attrib, m_names[i].attributeName)) return XP_GetString (m_names[i].resourceId); } // ### Boy, is this hacky. Maybe we can push this into default prefs now that we have excluded attributes if (XP_STRCASECMP(attrib, "userpassword") && XP_STRCMP(attrib, "objectclass") && XP_STRCASECMP(attrib, "subtreeaci")) return attrib; return NULL; } int net_LdapToHtml::WriteLineToStream (const char *line, XP_Bool bold, XP_Bool lineBreak, XP_Bool isMailto, XP_Bool isUri, XP_Bool aligntop, XP_Bool isCell) { #define kMailtoTemplate "%s" #define kUriTemplate "%s" int err = 0; int htmlLen = XP_STRLEN(line) + XP_STRLEN(""); if (bold) htmlLen += XP_STRLEN (""); if (lineBreak) htmlLen += XP_STRLEN(LINEBREAK); if (isMailto) htmlLen += XP_STRLEN(kMailtoTemplate) + XP_STRLEN(line); if (isUri) htmlLen += XP_STRLEN(kUriTemplate) + XP_STRLEN(line); char *htmlLine = (char *) XP_ALLOC(htmlLen + 1); if (htmlLine) { htmlLine[0] = '\0'; if (isCell) { if (aligntop) XP_STRCAT (htmlLine, ""); else XP_STRCAT (htmlLine, ""); } if (bold) XP_STRCAT (htmlLine, ""); if (isMailto) { char *mailtoHtml = PR_smprintf (kMailtoTemplate, line, line); if (mailtoHtml) { XP_STRCAT(htmlLine, mailtoHtml); XP_FREE(mailtoHtml); } } else if (isUri) { // RFC 2079 sez the URL comes first, then zero or more spaces, then an optional label char *label = NULL; char *url = XP_STRDUP(line); if (url) { int i = 0; while (url[i] && url[i] != ' ') i++; if (url[i] == ' ') { url[i] = '\0'; int j = i + 1; while (url[j] == ' ') j++; label = &url[j]; } } char *uriHtml = PR_smprintf (kUriTemplate, url ? url : line, label ? label : line); if (uriHtml) { XP_STRCAT(htmlLine, uriHtml); XP_FREE(uriHtml); } XP_FREEIF(url); } else XP_STRCAT (htmlLine, line); if (bold) XP_STRCAT (htmlLine, " "); if (isCell) XP_STRCAT (htmlLine, ""); if (lineBreak) XP_STRCAT (htmlLine, LINEBREAK); err = (*(m_stream)->put_block)(m_stream, htmlLine, XP_STRLEN(htmlLine)); XP_FREE (htmlLine); } else err = MK_OUT_OF_MEMORY; return err; } int net_LdapToHtml::WriteAttribute (const char *attrib) { return WriteLineToStream (attrib, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE); } int net_LdapToHtml::WriteValue (const char *value, XP_Bool isMailto, XP_Bool isUri) { return WriteLineToStream (value, TRUE, TRUE, isMailto, isUri, FALSE, TRUE); } int net_LdapToHtml::WritePlainLine (const char *line) { return WriteLineToStream (line, FALSE, TRUE); } XP_Bool net_LdapToHtml::IsMailtoAttribute (const char *attrib) { if (m_server) { const char **strings = DIR_GetAttributeStrings (m_server, mail); if (strings) { int i = 0; while (strings[i]) { if (!XP_STRCASECMP(attrib, strings[i])) return TRUE; i++; } } } else if (!XP_STRCASECMP(attrib, "mail")) return TRUE; return FALSE; } XP_Bool net_LdapToHtml::IsUriAttribute (const char *attrib) { switch (XP_TO_LOWER(attrib[0])) { case 'l': if (!XP_STRCASECMP(attrib, "labeleduri") || // preferred !XP_STRCASECMP(attrib, "labeledurl")) // old-style return TRUE; break; case 'u': if (!XP_STRCASECMP(attrib, "url")) // apparently some servers do this. be lenient in what we accept return TRUE; break; } return FALSE; } char *net_LdapToHtml::InferUrlBase () { // Look in Netlib's data structures to find the URL we're running right now // and assume that new URLs have the same host, port, etc // I'm guessing we can just look for everything up to the third slash. If this // turns out to be not true, we can do the more expensive thing of running it // through ldap_url_parse and then reforming it from the ldap_url_desc char *url = m_ce->URL_s->address; // Small memory wastage here, but this buf is so short-lived, it's probably // not worth figuring out the real right size char *urlBase = (char*) XP_ALLOC (XP_STRLEN(url)); if (urlBase) { char *tmpSrc = url; char *tmpDst = urlBase; int slashCount = 0; while (*tmpSrc) { if (*tmpSrc == '/') slashCount++; if (slashCount == 3) { *tmpDst = '\0'; break; } else *tmpDst++ = *tmpSrc; tmpSrc++; } } return urlBase; } char *net_LdapToHtml::EscapeSpaces (const char *string) { int length = XP_STRLEN (string) + 1; // +1 for trailing null const char *tmp = string; while (*tmp) { if (*tmp == 0x20) length += 2; // From " " to "%20" tmp++; } char *result = (char*) XP_ALLOC (length); if (result) { tmp = string; char *tmp2 = result; while (*tmp) { if (*tmp == 0x20) { *tmp2++ = '%'; *tmp2++ = '2'; *tmp2++ = '0'; } else *tmp2++ = *tmp; *tmp++; } *tmp2 = '\0'; } return result; } char *net_LdapToHtml::ConvertDnToUri (const char *dn) { char *uri = NULL; char **explodedDn = ldap_explode_dn (dn, TRUE /* suppress types? */); if (explodedDn) { char *urlBase = InferUrlBase(); if (urlBase) { char *unescapedUrl = PR_smprintf ("%s/%s", urlBase, dn); if (unescapedUrl) { char *escapedUrl = EscapeSpaces (unescapedUrl); if (escapedUrl) { uri = PR_smprintf ("%s %s", escapedUrl, explodedDn[0]); XP_FREE(escapedUrl); } XP_FREE(unescapedUrl); } XP_FREE(urlBase); } ldap_value_free (explodedDn); } return uri; } char *net_LdapToHtml::GetTableCaption (LDAPMessage *message) { // Here's a somewhat arbitrary way to decide what the table caption for an entry should be char *caption = NULL; // Look first for a "cn" attribute. const char *cnAttrName = GetAttributeString (cn); if (cnAttrName) { char **cnValues = ldap_get_values (m_ldap, message, cnAttrName); if (cnValues) { if (cnValues[0]) caption = ConvertFromServerCharSet (cnValues[0]); ldap_value_free (cnValues); } } if (!caption) { // Use the left-most (most specific) component of the DN char *dn = ldap_get_dn (m_ldap, message); if (dn) { char **explodedDn = ldap_explode_dn (dn, TRUE /* suppress types? */); if (explodedDn && explodedDn[0]) caption = ConvertFromServerCharSet (explodedDn[0]); ldap_value_free (explodedDn); ldap_memfree(dn); } } return caption; } int net_LdapToHtml::WriteEntryToStream (LDAPMessage *message) { // Write an LDAP directory entry to the HTML stream XP_ASSERT(message && m_stream); if (!message || !m_stream) return -1; WritePlainLine (""); char *caption = GetTableCaption (message); if (caption) { char *captionHtml = PR_smprintf ("", caption); if (captionHtml) { WritePlainLine (captionHtml); XP_FREE(captionHtml); } XP_FREE(caption); } BerElement *ber; char *attrib = ldap_first_attribute (m_ldap, message, &ber); while (attrib) { const char *attribName = LookupAttributeName (attrib); if (attribName && !DIR_IsAttributeExcludedFromHtml (m_server, attrib)) { int valueIndex = 0; HG73211 if (XP_STRCASECMP(attrib, "jpegPhoto") == 0 || XP_STRCASECMP(attrib, "audio") == 0) { struct berval **berImages = ldap_get_values_len (m_ldap, message, attrib); while (berImages[valueIndex]) { WritePlainLine (""); if (valueIndex == 0) WriteAttribute (attribName); else WritePlainLine(""); // ### There are Win16 problems with this strategy. We could use libmime // to stream the base64 bits one line at a time, but we'd still need to get the // LDAP SDK to do something similar, or to use huge pointers. char *base64Encoding = NET_Base64Encode ( berImages[valueIndex]->bv_val, berImages[valueIndex]->bv_len); if (base64Encoding) { char *htmlLine = NULL; if (XP_STRCASECMP(attrib, "jpegPhoto") == 0) htmlLine = PR_smprintf ("", base64Encoding); else htmlLine = PR_smprintf ("", base64Encoding); if (htmlLine) { WritePlainLine (htmlLine); XP_FREE (htmlLine); } XP_FREE(base64Encoding); } WritePlainLine (""); valueIndex++; } ldap_value_free_len (berImages); } else { char **values = ldap_get_values (m_ldap, message, attrib); if (values) { XP_Bool isMailtoAttribute = IsMailtoAttribute (attrib); XP_Bool isUriAttribute = IsUriAttribute (attrib); XP_Bool isDnAttribute = DIR_IsDnAttribute (m_server, attrib); XP_Bool isEscapedAttribute = DIR_IsEscapedAttribute (m_server, attrib); while (values[valueIndex]) { char *decodedUtf8 = ConvertFromServerCharSet (values[valueIndex]); if (decodedUtf8) { WritePlainLine (""); if (valueIndex == 0) WriteAttribute (attribName); else WritePlainLine(""); // This unescaping code is mandated by. DNs are one class of thing, and // attributes which are "productions" of strings are another class of thing // and plain old string attributes get left alone. if (isDnAttribute) { // Here's some special code to make values which look like DNs into // links to LDAP URLs. Cool, huh? char *tmp = ConvertDnToUri (decodedUtf8); if (tmp) { XP_FREE(decodedUtf8); decodedUtf8 = tmp; isUriAttribute = TRUE; } } else if (isEscapedAttribute) { // Some attributes which are "productions" of a couple of strings have // special encoding rules. Decode 'em into something useful here. char *tmp = DIR_Unescape (decodedUtf8, TRUE /*makeHtml?*/); if (tmp) { XP_FREE(decodedUtf8); decodedUtf8 = tmp; } } WriteValue (decodedUtf8, isMailtoAttribute, isUriAttribute); XP_FREE(decodedUtf8); WritePlainLine (""); } valueIndex++; } ldap_value_free (values); } } } ldap_memfree (attrib); attrib = ldap_next_attribute (m_ldap, message, ber); } WritePlainLine ("
%s
"); WritePlainLine ("
"); return 0; } //***************************************************************************** // // net_LdapReplicator is the class which knows how to read replication entries // from the LDAP server to the local Address Book using the "change log" format // specified in: // http://ds.internic.net/internet-drafts/draft-good-ldap-changelog-00.txt // #include "garray.h" extern "C" { extern int MK_OUT_OF_MEMORY; extern int MK_LDAP_REPL_CANT_SYNC_REPLICA; extern int MK_LDAP_REPL_DSE_BOGUS; extern int MK_LDAP_REPL_CHANGELOG_BOGUS; extern int MK_LDAP_REPL_PROGRESS_TITLE; extern int MK_LDAP_REPL_CONNECTING; extern int MK_LDAP_REPL_CHANGE_ENTRY; extern int MK_LDAP_REPL_NEW_ENTRY; extern int MK_LDAP_AUTHDN_LOOKUP_FAILED; } #define LDAP_REPL_CHUNK_SIZE 50 static void CancelReplication(void *closure) { MWContext *progressContext = (MWContext *)closure; if (progressContext) XP_InterruptContext(progressContext); } class net_LdapReplicator : public net_LdapConnectionData { public: net_LdapReplicator(ActiveEntry *ce); virtual ~net_LdapReplicator(); virtual int Interrupt (); virtual int OnBindCompleted (); virtual int OnEntryFound (LDAPMessage *message); virtual int OnSearchCompleted (); virtual XP_Bool OnError(int err); protected: typedef enum _LdapReplicationState { kInitializing, kAuthorizing, kBinding, kParsingRootDSE, kReplicatingChanges, kReplicatingAdditions, kReplicatingEntries } LdapReplicationState; virtual LdapConnectionType GetConnectionType() { return kReplicatorConnection; } XP_Bool CollectUserCredentials (); void SaveCredentialsToPrefs (); void UpdateProgress (); int Load (ActiveEntry *); int Process (); int Initialize (); int Authorize (); int Bind (); int SearchRootDSE (); int Replicate (XP_Bool initial = FALSE); int ProcessAdditions (); int ParseAuthorizationEntry (LDAPMessage *); int ParseRootDSE (LDAPMessage *); int ParseChangelogEntry (LDAPMessage *); int ParseInitialEntry (LDAPMessage *); void ParseReplicationUrl(const char *); XP_Bool m_initialReplicationOkay; XP_Bool m_changesReturned; char *m_authDn; char *m_authPwd; char *m_valueUsedToFindDn; char *m_dnOfChangelog; // Root DSE's setting for the DN suffix of changelog entries char *m_dataVersion; // Root DSE's setting to scope change numbers. The data version int32 m_firstChangeNumber; // Root DSE's setting for oldest change it knows int32 m_lastChangeNumber; // Root DSE's setting for newest change it knows // gets reset whenever the server's DB gets reloaded from LDIF int32 m_progressEntry; int32 m_progressTotal; MWContext *m_progressContext; pw_ptr m_progressWindow; LdapReplicationState m_replState; CXP_GrowableArray m_dnsToAdd; AB_ContainerInfo *m_container; static char *m_rootReplicationAttribs[5]; static char *m_entryReplicationAttribs[3]; }; char *net_LdapReplicator::m_rootReplicationAttribs[5] = { "changelog", "firstChangeNumber", "lastChangeNumber", "dataVersion", NULL }; char *net_LdapReplicator::m_entryReplicationAttribs[3] = { "targetdn", "changetype", NULL }; net_LdapReplicator::net_LdapReplicator (ActiveEntry *ce) : net_LdapConnectionData (ce, FALSE) { m_replState = kInitializing; m_initialReplicationOkay = FALSE; m_changesReturned = FALSE; m_authDn = NULL; m_authPwd = NULL; m_valueUsedToFindDn = NULL; m_dnOfChangelog = NULL; m_dataVersion = NULL; m_firstChangeNumber = 0; m_lastChangeNumber = 0; m_progressEntry = 0; m_progressTotal = 0; m_progressContext = NULL; m_progressWindow = NULL; m_container = NULL; } net_LdapReplicator::~net_LdapReplicator () { int size; XP_FREEIF(m_authDn); XP_FREEIF(m_authPwd); XP_FREEIF(m_dnOfChangelog); XP_FREEIF(m_dataVersion); size = m_dnsToAdd.Size(); if (size > 0) { while (--size >= 0) XP_FREE(m_dnsToAdd[size]); } if (m_container) { AB_EndReplication(m_container); m_container = NULL; } if (m_progressWindow) { PW_Hide(m_progressWindow); PW_Destroy(m_progressWindow); if (m_progressContext) PW_DestroyProgressContext(m_progressContext); } } int net_LdapReplicator::OnBindCompleted () { return SearchRootDSE(); } int net_LdapReplicator::OnEntryFound (LDAPMessage *message) { int status = 0; switch (m_replState) { case kAuthorizing: status = ParseAuthorizationEntry (message); break; case kParsingRootDSE: status = ParseRootDSE (message); break; case kReplicatingChanges: status = ParseChangelogEntry (message); break; case kReplicatingAdditions: case kReplicatingEntries: status = ParseInitialEntry (message); break; default: XP_ASSERT(FALSE); } return status; } int net_LdapReplicator::OnSearchCompleted () { int status = 0; switch (m_replState) { case kAuthorizing: status = Bind(); break; case kParsingRootDSE: status = Replicate (TRUE); break; case kReplicatingAdditions: m_server->replInfo->lastChangeNumber++; /* fall-through */ case kReplicatingChanges: status = ProcessAdditions (); break; case kReplicatingEntries: if (m_initialReplicationOkay) m_server->replInfo->lastChangeNumber = m_lastChangeNumber; if (m_server->replInfo->lastChangeNumber >= 0) { SaveCredentialsToPrefs (); DIR_SaveServerPreferences (FE_GetDirServers()); } status = MK_CONNECTED; break; default: XP_ASSERT(FALSE); } return status; } XP_Bool net_LdapReplicator::OnError(int err) { /* If we were performing the initial replication, destroy the subset of * entries that we had replicated so far. */ if (m_server->replInfo->lastChangeNumber < 0) AB_RemoveReplicaEntries(m_container); /* Because replication can be very expensive, we must try to save the data * we have already replicated even in the event of an error. */ DIR_SaveServerPreferences (FE_GetDirServers()); return TRUE; } XP_Bool net_LdapReplicator::CollectUserCredentials () { /* First look in the DIR_Server we read out of the prefs. If it already * knows the user's credentials, there's no need to ask again here. */ if (m_server->authDn && XP_STRLEN(m_server->authDn)) m_authDn = XP_STRDUP (m_server->authDn); if (m_server->savePassword && m_server->password && XP_STRLEN(m_server->password)) m_authPwd = XP_STRDUP (m_server->password); if (m_authDn && m_authPwd) return TRUE; XP_Bool rc = FALSE; char *prompt = PR_smprintf (XP_GetString(MK_LDAP_AUTH_PROMPT), DIR_GetAttributeName (m_server, auth), m_server->description); if (prompt) { /* TBD: Only prompt for password if we already have the DN */ char *username = NULL, *password = NULL; if (m_authDn) { password = FE_PromptPassword (m_ce->window_id, prompt); rc = (password != NULL); } else rc = FE_PromptUsernameAndPassword (m_ce->window_id, prompt, &username, &password); if (rc) { if (username) m_valueUsedToFindDn = username; if (password) { m_authPwd = SECNAV_MungeString (password); XP_FREE(password); } } XP_FREE(prompt); } return rc; } void net_LdapReplicator::SaveCredentialsToPrefs () { if (m_authDn) DIR_SetAuthDN (m_server, m_authDn); if (m_server->savePassword && m_authPwd) DIR_SetPassword (m_server, m_authPwd); } void net_LdapReplicator::UpdateProgress () { ++m_progressEntry; if (!m_progressContext) return; if (m_replState == kReplicatingAdditions) { int percent = (m_progressEntry * 100) / m_progressTotal; FE_SetProgressBarPercent (m_progressContext, percent); } if ((m_progressEntry % MAX(1, m_progressEntry / 10)) == 0) { char *status; if (m_replState == kReplicatingAdditions) status = PR_smprintf (XP_GetString(MK_LDAP_REPL_CHANGE_ENTRY), m_progressEntry); else status = PR_smprintf (XP_GetString(MK_LDAP_REPL_NEW_ENTRY), m_progressEntry); if (status) { FE_Progress (m_progressContext, status); XP_FREE(status); } } } int net_LdapReplicator::Load (ActiveEntry *ce) { XP_ASSERT(ce); XP_ASSERT(ce->URL_s); XP_ASSERT(ce->URL_s->address); /* If this URL was not started via NET_ReplicateDirectory, we need to * create a progress window for it. */ if (!ce->URL_s->internal_url) { m_progressContext = PW_CreateProgressContext(); if (m_progressContext) { m_progressWindow = PW_Create(NULL, pwStandard); if (m_progressWindow) { PW_AssociateWindowWithContext(m_progressContext, m_progressWindow); PW_SetWindowTitle(m_progressWindow, XP_GetString(MK_LDAP_REPL_PROGRESS_TITLE)); PW_SetLine1(m_progressWindow, NULL); PW_SetLine2(m_progressWindow, NULL); PW_SetProgressRange(m_progressWindow, 0, 0); PW_SetCancelCallback(m_progressWindow, CancelReplication, m_progressContext); PW_Show(m_progressWindow); } else { PW_DestroyProgressContext(m_progressContext); m_progressContext = NULL; } } } else m_progressContext = ce->window_id; if (m_progressContext) { FE_Progress (m_progressContext, XP_GetString(MK_LDAP_REPL_CONNECTING)); FE_SetProgressBarPercent (m_progressContext, 0); } /* Get the DIR_Server that matches the hostname in the URL. */ ParseReplicationUrl (ce->URL_s->address); if (m_server == NULL) return -1; /* Initialize the LDAP structure * * TBD: need to account for the case where one host name corresponds to * more than one machine (e.g. directory.netscape.com). */ m_ldap = ldap_init(m_server->serverName, m_server->port); if (!m_ldap) return MK_OUT_OF_MEMORY; return 0; } int net_LdapReplicator::Process () { int status = 0; if (m_replState == kInitializing) status = Initialize (); else status = net_LdapConnectionData::Process (); return status; } int net_LdapReplicator::Initialize () { int status = 0; /* If we are supposed to authenticate with the server, attempt to bind to * the server using authentication info from the prefs or the user. */ if (m_server->enableAuth) { if (CollectUserCredentials()) { /* If we don't have an authorization DN but we have an email id, * then try to look up the authorization DN. */ if (!m_authDn && m_valueUsedToFindDn) return Authorize(); else return Bind(); } } return SearchRootDSE(); } int net_LdapReplicator::Authorize () { int status = 0; char *attribs[2]; attribs[0] = XP_STRDUP(DIR_GetFirstAttributeString (m_server, cn)); attribs[1] = NULL; char *filter = PR_smprintf ("(%s=%s)", DIR_GetFirstAttributeString (m_server, auth), m_valueUsedToFindDn); int msgId = -1; if (attribs[0] && filter) msgId = ldap_search (m_ldap, m_server->searchBase, LDAP_SCOPE_SUBTREE, filter, &attribs[0], 0); else status = MK_OUT_OF_MEMORY; XP_FREEIF(attribs[0]); XP_FREEIF(filter); if (msgId != -1) { m_replState = kAuthorizing; m_messageId = msgId; } else if (status == 0) { DisplayError (XP_LDAP_SEARCH_FAILED, GetHostDescription(), ldap_get_lderrno(m_ldap,NULL,NULL)); status = -1; } return status; } int net_LdapReplicator::Bind () { char *upwd = SECNAV_UnMungeString (m_authPwd); if (upwd) { int msgId = ldap_simple_bind(m_ldap, m_authDn, upwd); XP_FREE(upwd); if (msgId != -1) { m_replState = kBinding; m_messageId = msgId; } else { DisplayError (XP_LDAP_BIND_FAILED, GetHostDescription(), ldap_get_lderrno(m_ldap,NULL,NULL)); return -1; } } else return MK_OUT_OF_MEMORY; return 0; } int net_LdapReplicator::SearchRootDSE () { /* Kick things off with a search of the Root DSE for the replication data. */ int msgId = ldap_search(m_ldap, "", LDAP_SCOPE_BASE, "objectclass=*", m_rootReplicationAttribs, 0); if (msgId != -1) { m_replState = kParsingRootDSE; m_messageId = msgId; } else { DisplayError (XP_LDAP_SEARCH_FAILED, GetHostDescription(), ldap_get_lderrno(m_ldap,NULL,NULL)); return -1; } return 0; } int net_LdapReplicator::Replicate (XP_Bool initial) { int msgId = -1; /* If the last completed change is -1, we need to perform an initial * replication. */ if (m_server->replInfo->lastChangeNumber == -1) { /* We're going to replicate every entry. */ m_replState = kReplicatingEntries; /* Delete the old replica database and start a fresh one. */ AB_RemoveReplicaEntries(m_container); /* Try to do a search which will return every entry (limited by the * filter preference) on the server. * * TBD: This fails even on thehole unless Directory Manager is used. * We must have a special replication dn or find some other way * to initialize the database. */ msgId = ldap_search(m_ldap, m_server->searchBase, LDAP_SCOPE_SUBTREE, DIR_GetReplicationFilter(m_server), AB_GetReplicaAttributeNames(m_container), 0); } else if (m_lastChangeNumber > m_server->replInfo->lastChangeNumber) { /* We're going to replicate only the changes. */ m_replState = kReplicatingChanges; if (initial) m_progressTotal = m_lastChangeNumber - m_server->replInfo->lastChangeNumber; char *filter = PR_smprintf ("(&(changenumber>=%d)(changenumber<=%d))", m_server->replInfo->lastChangeNumber + 1, MIN(m_lastChangeNumber, m_server->replInfo->lastChangeNumber + LDAP_REPL_CHUNK_SIZE)); if (filter) { msgId = ldap_search(m_ldap, m_dnOfChangelog, LDAP_SCOPE_ONELEVEL, filter, m_entryReplicationAttribs, 0); XP_FREE(filter); } } else { if (!initial) { SaveCredentialsToPrefs (); DIR_SaveServerPreferences (FE_GetDirServers()); } return MK_CONNECTED; } if (msgId != -1) m_messageId = msgId; else { DisplayError (XP_LDAP_SEARCH_FAILED, GetHostDescription(), ldap_get_lderrno(m_ldap,NULL,NULL)); return -1; } return 0; } int net_LdapReplicator::ProcessAdditions() { int size = m_dnsToAdd.Size(); if (size > 0) { int msgId = -1; char *targetDn = (char *)(m_dnsToAdd[size - 1]); msgId = ldap_search(m_ldap, targetDn, LDAP_SCOPE_SUBTREE, DIR_GetReplicationFilter(m_server), AB_GetReplicaAttributeNames(m_container), 0); XP_FREE(targetDn); m_dnsToAdd.SetSize(size - 1); if (msgId != -1) { m_messageId = msgId; m_replState = kReplicatingAdditions; } else { DisplayError (XP_LDAP_SEARCH_FAILED, GetHostDescription(), ldap_get_lderrno(m_ldap,NULL,NULL)); return -1; } } else { /* We must verify that the changelog search returned at least one * entry. It may return none if the user has insufficient access. * If so, we must abort the replication; otherwise, we will get stuck * in an infinite loop. */ if (m_changesReturned) { return Replicate(); } else { DisplayError (XP_LDAP_SEARCH_FAILED, GetHostDescription(), LDAP_INSUFFICIENT_ACCESS); return -1; } } return 0; } int net_LdapReplicator::Interrupt () { /* Put the last processed change number into prefs */ if (m_server->replInfo && m_server->replInfo->lastChangeNumber >= 0) DIR_SaveServerPreferences (FE_GetDirServers()); /* If we were performing the initial replication, destroy the subset of * entries that we had replicated so far. */ else AB_RemoveReplicaEntries(m_container); if (m_ldap) ldap_abandon (m_ldap, m_messageId); return net_LdapConnectionData::Interrupt(); // base class will unbind } void net_LdapReplicator::ParseReplicationUrl(const char *ldap_url) { char *url = XP_STRDUP(ldap_url); LDAPURLDesc *desc = (LDAPURLDesc *)XP_CALLOC(1, sizeof(LDAPURLDesc)); if (desc && url) { char *next; char *fullurl = url; /* Pull out the connection type (and fill in the default port numbers). */ if (XP_STRCASECMP(url, "repl-ldaps://") == 0) { url += 13; desc->lud_options |= LDAP_URL_OPT_SECURE; desc->lud_port = LDAPS_PORT; } else { url += 12; desc->lud_port = LDAP_PORT; } /* Pull out the authorized DN and password if they are present and * requested. */ char *authDn=NULL, *authPwd=NULL; next = XP_STRCHR(url, '@'); if (next) { next[0] = '\0'; char *pwd = XP_STRCHR(url, ':'); if (pwd) { pwd[0] = '\0'; authPwd = pwd + 1; } authDn = url; url = next + 1; } /* Pull out the host name, the port number and the base dn. */ next = XP_STRCHR(url, ':'); if (!next) next = XP_STRCHR(url, '/'); if (!next) desc->lud_host = url; else { if (next[0] == ':') desc->lud_port = 0; next[0] = '\0'; desc->lud_host = url; url = next + 1; if (desc->lud_port == 0) { next = XP_STRCHR(url, '/'); if (next) next[0] = '\0'; desc->lud_port = XP_ATOI(url); } if (next) desc->lud_dn = next + 1; } /* The host name, port number and base dn must be present and valid. */ if (desc->lud_host && desc->lud_port != 0 && desc->lud_dn) { AddLdapServerToPrefs(desc); if (m_server) { if (!m_server->authDn && authDn) m_server->authDn = XP_STRDUP(authDn); if (!m_server->password && authPwd) m_server->password = XP_STRDUP(authPwd); } } url = fullurl; } XP_FREEIF(desc); XP_FREEIF(url); } int net_LdapReplicator::ParseAuthorizationEntry (LDAPMessage *message) { if (!m_authDn) { char *authDn = ldap_get_dn (m_ldap, message); m_authDn = XP_STRDUP(authDn); ldap_memfree(authDn); } else { /* Better not have more than one hit for this search. * We don't have a way for the user to choose which "phil" they are. */ DisplayError (MK_LDAP_AUTHDN_LOOKUP_FAILED, GetHostDescription(), 0); return -1; } return 0; } int net_LdapReplicator::ParseRootDSE (LDAPMessage *message) { int i; char **values = NULL; for (i = 0; i < 4; i++) { values = ldap_get_values (m_ldap, message, m_rootReplicationAttribs[i]); if (values && values[0]) { switch (i) { case 0: m_dnOfChangelog = XP_STRDUP (values[0]); break; case 1: m_firstChangeNumber = XP_ATOI (values[0]); break; case 2: m_lastChangeNumber = XP_ATOI (values[0]); break; case 3: m_dataVersion = XP_STRDUP (values[0]); break; } ldap_value_free (values); } else { DisplayError (MK_LDAP_REPL_DSE_BOGUS, GetHostDescription(), 0); return -1; } } /* Parse the root DSE. DIR_ValidateRootDSE can return three different * types of values; * <0 - There is no replication information available for this server; * we should abort replication. * 0 - Either our replication data matches what the server returned or * we have never replicated. In the former case, perform an * incremental update. In the later case, generate the replica * from scratch. * >0 - The server's replication data does not match ours; reinitialize * our replica from scratch. */ int rc = DIR_ValidateRootDSE (m_server, m_dataVersion, m_firstChangeNumber, m_lastChangeNumber); if (rc < 0) { DisplayError (MK_LDAP_REPL_DSE_BOGUS, GetHostDescription(), 0); return -1; } /* Get access to the container info that will manage the interaction * between ldap server connection and the replica database. * * NOTE: This must be called after DIR_ValidateRootDSE, because the later * is what sets up the replication information in the DIR_Server * which in turn is required by the container to replicate. */ m_container = AB_BeginReplication(m_progressContext, m_server); if (m_container == NULL) { DisplayError (MK_LDAP_REPL_CANT_SYNC_REPLICA, GetHostDescription(), 0); return -1; } return 0; } // Here we get an entry from the changelog and try to figure out what kind of // change happened to the entry given by the targetdn. int net_LdapReplicator::ParseChangelogEntry (LDAPMessage *message) { char **values = NULL; char *targetDn = NULL; values = ldap_get_values (m_ldap, message, "targetdn"); if (values && values[0]) { targetDn = XP_STRDUP(values[0]); ldap_value_free (values); } else { DisplayError (MK_LDAP_REPL_CHANGELOG_BOGUS, GetHostDescription(), 0); return -1; } values = ldap_get_values (m_ldap, message, "changetype"); if (values && values[0]) { if (!XP_STRCASECMP(values[0], "add")) { m_dnsToAdd.Add (targetDn); } else if (!XP_STRCASECMP(values[0], "mod")) { AB_DeleteReplicaEntry (m_container,targetDn); m_dnsToAdd.Add (targetDn); } else if (!XP_STRCASECMP(values[0], "del")) { AB_DeleteReplicaEntry (m_container,targetDn); UpdateProgress(); } ldap_value_free (values); } else { DisplayError (MK_LDAP_REPL_CHANGELOG_BOGUS, GetHostDescription(), 0); return -1; } m_changesReturned = TRUE; return 0; } /* ParseInitialEntry * * Method for adding an entry to the replication database. */ int net_LdapReplicator::ParseInitialEntry (LDAPMessage *message) { message = ldap_first_entry (m_ldap, message); if (message) { int mapSize = AB_GetNumReplicaAttributes(m_container); char **mapNames = AB_GetReplicaAttributeNames(m_container); char **valueList = (char **)XP_ALLOC(sizeof(char *) * mapSize); if (!valueList) { m_initialReplicationOkay = FALSE; return MK_OUT_OF_MEMORY; } /* Copy each of the attribute values from the LDAP result into the index * in the value array that corresponds to the same index in the attribute * mapping structure. */ int i, indexCn = -1, indexOrg = -1; char **scratchArray = NULL; for (i = 0; i < mapSize; i++) { scratchArray = ldap_get_values (m_ldap, message, mapNames[i]); if (scratchArray && scratchArray[0]) { valueList[i] = DIR_ConvertFromServerCharSet (m_server, scratchArray[0], GetLocalCharSet()); ldap_value_free (scratchArray); } else { if (AB_ReplicaAttributeMatchesId(m_container, i, cn)) indexCn = i; else if (AB_ReplicaAttributeMatchesId(m_container, i, o)) indexOrg = i; valueList[i] = NULL; } } /* If we didn't get some attributes we wanted, try to pick them up from the DN */ if (indexOrg != -1 || indexCn != -1) { char *scratch; if (scratch = ldap_get_dn (m_ldap, message)) { if ((scratchArray = ldap_explode_dn (scratch, FALSE)) != NULL) { char *dnComponent = NULL; for (i = 0; (dnComponent = scratchArray[i]) != NULL; i++) { if (indexOrg != -1 && !XP_STRNCASECMP (dnComponent, "o=", 2)) { valueList[indexOrg] = DIR_ConvertFromServerCharSet (m_server, dnComponent+2, GetLocalCharSet()); indexOrg = -1; } if (indexCn != -1 && !XP_STRNCASECMP (dnComponent, "cn=", 3)) { valueList[indexCn] = DIR_ConvertFromServerCharSet (m_server, dnComponent+3, GetLocalCharSet()); indexCn = -1; } } ldap_value_free (scratchArray); } ldap_memfree (scratch); } } /* If the entry is valid, add it to the replica database. Also set the * initial replication okay flag to true in case we are performing an * initial replication. Finally, update the progress window. */ if (indexCn == -1 && AB_AddReplicaEntry (m_container, valueList) == AB_SUCCESS) { m_initialReplicationOkay = TRUE; UpdateProgress(); } for (i = 0; i < mapSize; i++) XP_FREEIF(valueList[i]); XP_FREE(valueList); } return 0; } //***************************************************************************** // // net_OfflineLdapReplicator is the class which knows which directory replicas // should be updated and how to replicate them in succession. // class net_OfflineLdapReplicator { public: net_OfflineLdapReplicator(MSG_Pane *, DIR_Server *server = NULL); ~net_OfflineLdapReplicator(); XP_Bool ReplicateDirectory(); static void ReplicateNextDirectory(URL_Struct *, int, MWContext *); protected: MSG_Pane *m_pane; XP_List *m_serverList; MWContext *m_progressContext; pw_ptr m_progressWindow; }; net_OfflineLdapReplicator::net_OfflineLdapReplicator(MSG_Pane *pane, DIR_Server *server) { m_pane = pane; m_progressContext = NULL; m_progressWindow = NULL; m_serverList = XP_ListNew(); if (m_serverList) { if (server) { if (DIR_TestFlag(server, DIR_REPLICATION_ENABLED)) { /* Create a progress window. */ m_progressContext = PW_CreateProgressContext(); if (m_progressContext) { m_progressWindow = PW_Create(NULL, pwStandard); if (m_progressWindow) { PW_AssociateWindowWithContext(m_progressContext, m_progressWindow); PW_SetWindowTitle(m_progressWindow, XP_GetString(MK_LDAP_REPL_PROGRESS_TITLE)); PW_SetLine1(m_progressWindow, NULL); PW_SetLine2(m_progressWindow, NULL); PW_SetProgressRange(m_progressWindow, 0, 0); PW_SetCancelCallback(m_progressWindow, CancelReplication, m_progressContext); PW_Show(m_progressWindow); } else { PW_DestroyProgressContext(m_progressContext); } } XP_ListAddObjectToEnd(m_serverList, server); } } else { XP_ASSERT(m_pane); m_progressContext = m_pane->GetContext(); XP_List *serverList = FE_GetDirServers(); for (int i = 1; i <= XP_ListCount(serverList); i++) { server = (DIR_Server *)XP_ListGetObjectNum(serverList, i); if (DIR_TestFlag(server, DIR_REPLICATION_ENABLED)) XP_ListAddObjectToEnd(m_serverList, server); } } if (XP_ListCount(m_serverList) == 0) { XP_ListDestroy(m_serverList); m_serverList = NULL; } } } net_OfflineLdapReplicator::~net_OfflineLdapReplicator() { if (m_serverList) XP_ListDestroy(m_serverList); /* We destroy the progress window, but the pane destroys the context? * This seems kind of weird, but it's the way things work. */ if (m_progressWindow) { PW_Hide(m_progressWindow); PW_Destroy(m_progressWindow); } } XP_Bool net_OfflineLdapReplicator::ReplicateDirectory() { /* Return FALSE iff there are no more servers to replicate. */ DIR_Server *server = (DIR_Server *)XP_ListRemoveTopObject(m_serverList); if (!server) return FALSE; char *auth = NULL; if (server->enableAuth && server->authDn) { if (server->password) auth = PR_smprintf("%s:%s@", server->authDn, server->password); else auth = PR_smprintf("%s@", server->authDn); } char *url = PR_smprintf("repl-ldap%s://%s%s:%d/%s", server->isSecure ? "s" : "", auth ? auth : "", server->serverName, server->port, server->searchBase); if (url) { URL_Struct *URL_s = NET_CreateURLStruct(url, NET_DONT_RELOAD); if(URL_s) { URL_s->owner_data = this; URL_s->internal_url = TRUE; URL_s->msg_pane = m_pane; if (m_pane) MSG_UrlQueue::AddUrlToPane(URL_s, net_OfflineLdapReplicator::ReplicateNextDirectory, m_pane, TRUE, FO_PRESENT); else NET_GetURL(URL_s, FO_PRESENT, m_progressContext, net_OfflineLdapReplicator::ReplicateNextDirectory); } XP_FREE(url); } XP_FREEIF(auth); return TRUE; } void net_OfflineLdapReplicator::ReplicateNextDirectory(URL_Struct *URL_s, int, MWContext *) { net_OfflineLdapReplicator *replicator = (net_OfflineLdapReplicator *)(URL_s->owner_data); if (replicator) { /* ReplicateDirectory returns FALSE if there are no more directories * left to replicate. It is our job to clean up the replicator object * when we're done. */ if (!replicator->ReplicateDirectory()) delete replicator; } } //***************************************************************************** // // net_LdapToAddressBook does the work of converting LDAP entries into a format // that can be stored in the local Address Book database. // class net_LdapToAddressBook : public net_LdapConnectionData { public: net_LdapToAddressBook (ActiveEntry*); virtual ~net_LdapToAddressBook(); // Base class overrides virtual int OnEntryFound (LDAPMessage*); // Address Book specific stuff void ConvertEntryToPerson (LDAPMessage *, PersonEntry &); void ConvertEntryToPerson (LDAPMessage *message, AB_AttributeValue ** valuesArray, uint16 * numItems); // new version for new AB int WriteEntryToAddressBook (LDAPMessage*); virtual char * ExtractLDAPEncoding(char * url); protected: virtual LdapConnectionType GetConnectionType() { return kAddressBookConnection; } // used to take an ldap attribute, value pair and conver it to one or more address book attribute // values, adding those values to the abAttributes list. int AddLDAPAttribute(const char * dirAttrib, const char * dirValue, XP_List * abAttributes); AB_AddressBookCopyInfo * m_abCopyInfo; // used for new address book... }; // prototypes fo net_LdapToAddressBook helper functions... AB_AttributeValue * findAttributeInList(AB_AttribID attrib, XP_List * abAttributes); net_LdapToAddressBook::net_LdapToAddressBook (ActiveEntry *ce) : net_LdapConnectionData(ce) { if (ce && ce->URL_s) m_abCopyInfo = (AB_AddressBookCopyInfo *) ce->URL_s->owner_data; else m_abCopyInfo = NULL; } net_LdapToAddressBook::~net_LdapToAddressBook() { // copy info freed by url exit function... // if (m_abCopyInfo) // AB_FreeAddressBookCopyInfo(m_abCopyInfo); // also destroys allocated memory.. m_abCopyInfo = NULL; } char * net_LdapToAddressBook::ExtractLDAPEncoding(char * url) { // we want to strip off the addbook- part for the LDAP encoding... // caller should not be freeing this return string because it is part of the url.. return url+8; // 8 for "addbook-" } // helper function used to find an attribute in the list AB_AttributeValue * findAttributeInList(AB_AttribID attrib, XP_List * abAttributes) { if (abAttributes) { for (int i = 1; i <= XP_ListCount(abAttributes); i++) { AB_AttributeValue *v = (AB_AttributeValue*) XP_ListGetObjectNum (abAttributes, i); if (v && v->attrib == attrib) return v; } } return NULL; } int net_LdapToAddressBook::AddLDAPAttribute(const char * attrib, const char * dirValue, XP_List * abAttributes) { int status = 0; AB_AttributeValue * value = (AB_AttributeValue *) XP_ALLOC(sizeof(AB_AttributeValue)); if (value) { status = -1; // we can make a minor hack to optmize this...since all of // them are string attributes, we only need to set the // value->attrib field in the if statements...then if // we found a match, set the value->u.string outside the // if statements... if (!XP_STRCASECMP(attrib, GetAttributeString(cn))) { value->attrib = AB_attribDisplayName; status = 0; } if (!XP_STRCASECMP(attrib, GetAttributeString (givenname))) { value->attrib = AB_attribGivenName; status = 0; } else if (!XP_STRCASECMP(attrib, GetAttributeString(sn))) { value->attrib = AB_attribFamilyName; status = 0; } else if (!XP_STRCASECMP(attrib, GetAttributeString(o))) { value->attrib = AB_attribCompanyName; status = 0; } else if (!XP_STRCASECMP(attrib, GetAttributeString(l))) { value->attrib = AB_attribLocality; status = 0; } else if (!XP_STRCASECMP(attrib, GetAttributeString(mail))) { value->attrib = AB_attribEmailAddress; status = 0; } else if (!XP_STRCASECMP(attrib, "title")) { value->attrib = AB_attribTitle; status = 0; } else if (!XP_STRCASECMP(attrib, "postaladdress")) { value->attrib = AB_attribPOAddress; status = 0; } else if (!XP_STRCASECMP(attrib, "st")) { value->attrib = AB_attribRegion; status = 0; } else if (!XP_STRCASECMP(attrib, "postalcode")) { value->attrib = AB_attribZipCode; status = 0; } else if (!XP_STRCASECMP(attrib, GetAttributeString(telephonenumber))) { value->attrib = AB_attribWorkPhone; status = 0; } else if (!XP_STRCASECMP(attrib, "facsimiletelephonenumber")) { value->attrib = AB_attribFaxPhone; status = 0; } else if (!XP_STRCASECMP(attrib, "dlshost")) { value->attrib = AB_attribCoolAddress; // we also need to set the use server... AB_AttributeValue * nextValue = (AB_AttributeValue *) XP_ALLOC(sizeof(AB_AttributeValue)); if (nextValue) { nextValue->attrib = AB_attribUseServer; nextValue->u.shortValue = kSpecificDLS; XP_ListAddObjectToEnd(abAttributes, (void *) nextValue); } } else if (!XP_STRCASECMP(attrib, "host")) { value->attrib = AB_attribCoolAddress; // we also need to set the use server... AB_AttributeValue * nextValue = (AB_AttributeValue *) XP_ALLOC(sizeof(AB_AttributeValue)); if (nextValue) { nextValue->attrib = AB_attribUseServer; nextValue->u.shortValue = kHostOrIPAddress; XP_ListAddObjectToEnd(abAttributes, (void *) nextValue); } } if (status == 0) // we found a match { value->u.string = XP_STRDUP(dirValue); XP_ListAddObjectToEnd(abAttributes, (void *) value); } else // we didn't find a match.... XP_FREEIF(value); } else status = MK_OUT_OF_MEMORY; return status; } void net_LdapToAddressBook::ConvertEntryToPerson (LDAPMessage *message, AB_AttributeValue ** valuesArray, uint16 * numItems) { XP_List * abAttributes = XP_ListNew(); AB_AttributeValue * values = NULL; uint16 numAttributes = 0; if (abAttributes) { // loop over each attribute in the entry, and pick out the ones the address book // is interested in. We are ignoring the case where the same attribute has multiple // values. The AB doesn't have a way to deal with that. char *tmpCn = NULL; BerElement *ber; char *attrib = ldap_first_attribute (m_ldap, message, &ber); while (attrib) { char ** values = ldap_get_values(m_ldap, message, attrib); if (m_server && values && values[0]) { char *decodedUtf8 = ConvertFromServerCharSet (values[0]); if (decodedUtf8) { AddLDAPAttribute(attrib, decodedUtf8, abAttributes); if (!XP_STRCASECMP(attrib, GetAttributeString (cn))) tmpCn = XP_STRDUP(decodedUtf8); // hang on to cn in case we need it later } XP_FREEIF (decodedUtf8); } if (values) ldap_value_free (values); ldap_memfree (attrib); attrib = ldap_next_attribute (m_ldap, message, ber); } // add the next attribute.... // now we have some fixing up to do...adding CSID, AB_AttributeValue * abValue = (AB_AttributeValue *) XP_ALLOC(sizeof(AB_AttributeValue)); if (abValue) { abValue->attrib = AB_attribWinCSID; abValue->u.shortValue = INTL_GetCSIWinCSID(LO_GetDocumentCharacterSetInfo(m_ce->window_id)); XP_ListAddObjectToEnd(abAttributes, (void *) abValue); } // If the entry doesn't have an 'o', get it from the DN char *dn = NULL; AB_AttributeValue * company = findAttributeInList(AB_attribCompanyName, abAttributes); if (!company) { if (!dn) dn = ldap_get_dn (m_ldap, message); if (dn) { const char *oAttribName = GetAttributeString (o); AB_AttributeValue * abValue = (AB_AttributeValue *) XP_ALLOC(sizeof(AB_AttributeValue)); if (abValue) { abValue->attrib = AB_attribCompanyName; abValue->u.string = GetAttributeFromDN (dn, oAttribName); XP_ListAddObjectToEnd(abAttributes, (void *) abValue); } } } // If the entry doesn't have 'givenname' or 'sn' try to use 'cn' AB_AttributeValue * givenName = findAttributeInList(AB_attribGivenName, abAttributes); AB_AttributeValue * familyName = findAttributeInList(AB_attribFamilyName, abAttributes); if (!givenName /* || !familyName */) { char * useThisForGivenName = NULL; // prefer CN as attribute to CN from dist name if (tmpCn) { useThisForGivenName = tmpCn; tmpCn = NULL; } else { if (!dn) dn = ldap_get_dn (m_ldap, message); if (dn) { const char *cnAttribName = GetAttributeString (cn); useThisForGivenName = GetAttributeFromDN (dn, cnAttribName); } } if (useThisForGivenName) { AB_AttributeValue * abValue = (AB_AttributeValue *) XP_ALLOC(sizeof(AB_AttributeValue)); if (abValue) { abValue->attrib = AB_attribGivenName; abValue->u.string = useThisForGivenName; XP_ListAddObjectToEnd(abAttributes, (void *) abValue); } } } // !givenName if (dn) ldap_memfree(dn); if (tmpCn) XP_FREE(tmpCn); // our last step is to turn the XP_List into an array of attribute values... numAttributes = XP_ListCount(abAttributes); if (numAttributes) { values = (AB_AttributeValue *) XP_ALLOC(sizeof(AB_AttributeValue) * numAttributes); if (valuesArray) { // iterate through the list adding each attribute to our static array for (uint16 index = 1; index <= numAttributes; index++) { AB_AttributeValue * value = (AB_AttributeValue *) XP_ListGetObjectNum(abAttributes, index); AB_CopyEntryAttributeValue(value, &values[index-1]); } } } // free the list attributes... for (int i = 1; i <= XP_ListCount(abAttributes); i++) { AB_AttributeValue * value = (AB_AttributeValue *) XP_ListGetObjectNum(abAttributes, i); AB_FreeEntryAttributeValue(value); } XP_ListDestroy (abAttributes); } // if abAttributes // set up the return values... if (numItems) *numItems = numAttributes; if (valuesArray) *valuesArray = values; else AB_FreeEntryAttributeValues(values, numAttributes); } #ifdef MOZ_NEWADDR int net_LdapToAddressBook::WriteEntryToAddressBook (LDAPMessage *message) { int status = 0; if (m_abCopyInfo && m_abCopyInfo->destContainer) { AB_AttributeValue * valuesArray = NULL; uint16 numItems = 0; ConvertEntryToPerson(message, &valuesArray, &numItems); if (valuesArray) { // If there is only one URL in the queue, we bring up the UI, but // if there is more than one, we add them silently. There could be // a ton of URLs, and we don't have a Yes/No/All dialog status = AB_AddUserAB2(m_abCopyInfo->destContainer, valuesArray, numItems, NULL /* we don't care about the entry id...*/); AB_FreeEntryAttributeValues(valuesArray, numItems); } } else status = ABError_NullPointer; return status; } #else int net_LdapToAddressBook::WriteEntryToAddressBook (LDAPMessage *message) { // Add an entry we found on an LDAP directory into the local AB DB int err = 0; PersonEntry person; person.Initialize (); ConvertEntryToPerson (message, person); XP_List *dirServers = FE_GetDirServers (); XP_ASSERT(dirServers); if (dirServers) { DIR_Server *pab = NULL; DIR_GetPersonalAddressBook (dirServers, &pab); XP_ASSERT(pab); if (pab) { ABook *ab = FE_GetAddressBook (NULL /* don't have a pane? */); AB_BreakApartFirstName (ab, &person); // If there is only one URL in the queue, we bring up the UI, but // if there is more than one, we add them silently. There could be // a ton of URLs, and we don't have a Yes/No/All dialog if (MSG_GetUrlQueueSize (m_ce->URL_s->address, m_ce->window_id) <= 1) err = AB_AddUserWithUI (m_ce->window_id, &person, pab, TRUE); else { // Don't allow any external URLs to add batches of people to the AB if (m_ce->URL_s->internal_url) { ABID unusedId; err = AB_AddUser (pab, ab, &person, &unusedId); } } } } person.CleanUp(); return err; } #endif int net_LdapToAddressBook::OnEntryFound (LDAPMessage *message) { return WriteEntryToAddressBook (message); } void net_LdapToAddressBook::ConvertEntryToPerson (LDAPMessage *message, PersonEntry &person) { // Loop over each attribute in the entry, and pick out the ones the // Address Book is interested in. Note that we're ignoring cases where // the same attribute has multiple values. The AB doesn't have a way to // deal with that. char *tmpCn = NULL; BerElement *ber; char *attrib = ldap_first_attribute (m_ldap, message, &ber); while (attrib) { char **values = ldap_get_values (m_ldap, message, attrib); // We better have a DIR_Server for this, otherwise GetAttributeString // will crash. Since the only way to get to this code is through the Search // Directory dialog, I think this is an ok restriction. We could certainly // add default names in dirprefs.c if necessary. XP_ASSERT(m_server && values && values[0]); if (!m_server || !values || !values[0]) return; char *decodedUtf8 = ConvertFromServerCharSet (values[0]); if (!decodedUtf8) return; // ### maybe it would be nice to have a few more of these // expressed as DIR_Prefs attributeIds??? if (!XP_STRCASECMP(attrib, GetAttributeString (cn))) tmpCn = XP_STRDUP(decodedUtf8); // hang on to cn in case we need it later if (!XP_STRCASECMP(attrib, GetAttributeString (givenname))) person.pGivenName = XP_STRDUP(decodedUtf8); else if (!XP_STRCASECMP(attrib, GetAttributeString(sn))) person.pFamilyName = XP_STRDUP(decodedUtf8); else if (!XP_STRCASECMP(attrib, GetAttributeString(o))) person.pCompanyName = XP_STRDUP(decodedUtf8); else if (!XP_STRCASECMP(attrib, GetAttributeString(l))) person.pLocality = XP_STRDUP(decodedUtf8); else if (!XP_STRCASECMP(attrib, GetAttributeString(mail))) person.pEmailAddress = XP_STRDUP(decodedUtf8); else if (!XP_STRCASECMP(attrib, "title")) person.pTitle = XP_STRDUP(decodedUtf8); else if (!XP_STRCASECMP(attrib, "postaladdress")) person.pPOAddress = XP_STRDUP(decodedUtf8); else if (!XP_STRCASECMP(attrib, "st")) person.pRegion = XP_STRDUP(decodedUtf8); else if (!XP_STRCASECMP(attrib, "postalcode")) person.pZipCode = XP_STRDUP(decodedUtf8); else if (!XP_STRCASECMP(attrib, GetAttributeString(telephonenumber))) person.pWorkPhone = XP_STRDUP(decodedUtf8); else if (!XP_STRCASECMP(attrib, "facsimiletelephonenumber")) person.pFaxPhone = XP_STRDUP(decodedUtf8); else if (!XP_STRCASECMP(attrib, "dlshost")) { person.UseServer = kSpecificDLS; person.pCoolAddress = XP_STRDUP(decodedUtf8); } else if (!XP_STRCASECMP(attrib, "host")) { person.UseServer = kHostOrIPAddress; person.pCoolAddress = XP_STRDUP(decodedUtf8); } ldap_value_free (values); XP_FREE (decodedUtf8); ldap_memfree (attrib); attrib = ldap_next_attribute (m_ldap, message, ber); } // If the entry doesn't have an 'o', get it from the DN char *dn = NULL; if (!person.pCompanyName) { if (!dn) dn = ldap_get_dn (m_ldap, message); if (dn) { const char *oAttribName = GetAttributeString (o); person.pCompanyName = GetAttributeFromDN (dn, oAttribName); } } // If the entry doesn't have 'givenname' or 'sn' try to use 'cn' if (!person.pGivenName || !person.pFamilyName) { // prefer CN as attribute to CN from dist name if (tmpCn) { person.pGivenName = tmpCn; tmpCn = NULL; // PersonEntry now owns that memory, so don't free it } else { if (!dn) dn = ldap_get_dn (m_ldap, message); if (dn) { const char *cnAttribName = GetAttributeString (cn); person.pGivenName = GetAttributeFromDN (dn, cnAttribName); } } } if (dn) ldap_memfree(dn); if (tmpCn) XP_FREE(tmpCn); person.WinCSID = INTL_GetCSIWinCSID(LO_GetDocumentCharacterSetInfo(m_ce->window_id)); } #ifdef MOZ_LI /* * Class net_LdapLIConnectionData * ============================== * This class is used for LI LDAP operations. It expects a LDAPOperation* to be * passed in via the URL_s->owner_data. The operation to be performed is determined * by URL_s->method. The results of the operations are returned in the LDAPOperation; * the caller is responsible for creating and deleting the operation object. */ class net_LdapLIConnectionData : public net_LdapConnectionData { public: net_LdapLIConnectionData(ActiveEntry *ce); virtual ~net_LdapLIConnectionData (); // The base class handles it correctly. virtual int Load (ActiveEntry *ce); virtual int Process (); virtual int Interrupt (); virtual int Unbind (); protected: virtual LdapConnectionType GetConnectionType() { return kLIConnection; } private: XP_Bool m_bCallerProvidedConnection; // Did the caller provide the m_ldap connection? LDAPOperation* m_pParams; // Parameters. }; /* net_LdapLIConnectionData Constructor * ==================================== * Create a connection data object class for an LI operation; set up a connection if * the caller didn't provide one. We tell the base class not to initialize m_ldap since * a LDAP* may be passed in to us. */ net_LdapLIConnectionData::net_LdapLIConnectionData(ActiveEntry *ce) : net_LdapConnectionData(ce,FALSE) { m_pParams = (LDAPOperation*)ce->URL_s->owner_data; if(m_pParams && (m_pParams->getConnection() || (m_pParams->getURLMethod() == LDAP_LI_BIND_METHOD))) { // Set the connection in the base class. // Free the LDAP* struct if created by the base class. m_ldap = m_pParams->getConnection(); m_bCallerProvidedConnection = TRUE; } else { m_ldap = ldap_init(m_pParams->getURL()->getHost(),m_pParams->getURL()->getPort()); m_bCallerProvidedConnection = FALSE; } } /* net_LdapLIConnectionData Destructor * ==================================== * Remember not to unbind if the caller provided the connection. */ net_LdapLIConnectionData::~net_LdapLIConnectionData() { // Base class destructor will try to unbind, so we have to prevent that if // the caller provided the connection. if(m_bCallerProvidedConnection) m_ldap = NULL; } /* Load() * ====== * Called by netlib when starting an LI operation. This function is responsible to starting * the LDAP operations. */ int net_LdapLIConnectionData::Load(ActiveEntry *ce) { int ldapErr = 0; int msgId = -1; char *attrs[2] = { "modifyTimeStamp" , NULL }; const LDAPURL* url = m_pParams->getURL(); char *utf8Encoding = NULL; if(url->getParseError() != LDAP_SUCCESS) { ldapErr = MK_MALFORMED_URL_ERROR; goto net_LdapLIConnectionDataLoadExit; } if(m_pParams->started()) { ldapErr = LI_LDAP_ALREADY_STARTED; goto net_LdapLIConnectionDataLoadExit; } XP_ASSERT(m_ce); XP_ASSERT(m_ce->URL_s); XP_ASSERT(m_ce->URL_s->address); XP_ASSERT(m_ldap); /* Convert the URL to UTF8 */ utf8Encoding = ConvertToServerCharSet (ce->URL_s->address); if(!utf8Encoding) return LI_LDAP_MEMORY_ERROR; /* The URL is assumed to be valid (checked by * LDAPOperation before calling NET_GetURL) */ /* This is where the real work begins */ // let the world know what we are doing. FE_GraphProgressInit(ce->window_id, NULL, 1); // Initiate the operation on the server switch(m_pParams->getURLMethod()) { case LDAP_LI_BIND_METHOD: // Bind operation is not really async yet, the initial IP stuff is still synchronous in the LDAP API. msgId = ldap_simple_bind(m_ldap,m_pParams->getURL()->getDN(),((LDAPBindOperation*)m_pParams)->getPassword()); break; case LDAP_LI_SEARCH_METHOD: msgId = ldap_search(m_ldap, url->getDN(), url->getScope(), url->getFilter(), url->getAttrs(), ((LDAPSearchOperation*)m_pParams)->getAttrsOnly()); break; case LDAP_LI_ADD_METHOD: case LDAP_LI_ADDGLM_METHOD: // Note that since we take apart the URL, we end up ignoring everything but the DN. msgId = ldap_add(m_ldap, url->getDN(), ((LDAPAddOperation*)m_pParams)->getLDAPModArray()->getLDAPModStarStar()); break; case LDAP_LI_MOD_METHOD: case LDAP_LI_MODGLM_METHOD: // Note that since we take apart the URL, we end up ignoring everything but the DN. msgId = ldap_modify(m_ldap, url->getDN(), ((LDAPModOperation*)m_pParams)->getLDAPModArray()->getLDAPModStarStar()); break; case LDAP_LI_DEL_METHOD: msgId = ldap_delete(m_ldap, url->getDN()); break; case LDAP_LI_GETLASTMOD_METHOD: { msgId = ldap_search(m_ldap, url->getDN(), url->getScope(), url->getFilter(), attrs, ((LDAPSearchOperation*)m_pParams)->getAttrsOnly()); } break; case LDAP_SUCCESS: break; } if (msgId != -1) { m_messageId = msgId; } else { m_pParams->setErrNo(ldap_get_lderrno(m_ldap,NULL,NULL)); ldapErr = LI_LDAP_ERROR; } net_LdapLIConnectionDataLoadExit: XP_FREEIF(utf8Encoding); return ldapErr; } /* Process() * ========= * Process LI Gets/Puts, etc. Only one entry can be gotten/put at a time. * The result of the process is a LDAPMessage* put into the LDAPOperation* * in the URL_s->owner_data in the ActiveEntry. * The caller of NET_GetURL is required to free this data via ldap_msgfree() * if it is non-NULL in the LDAPOperation object. */ int net_LdapLIConnectionData::Process() { LDAPMessage *message = NULL; struct timeval timeout; // REMIND: put in the timeout stuff. XP_BZERO (&timeout, sizeof(timeout)); int ldapErr = ldap_result (m_ldap, m_messageId, 1, &timeout, &message); XP_ASSERT(m_pParams); /* Check for network error */ // We don't set result to NULL here because it may have already been set to // something, e.g. by the LDAP_LI_ADDGLM or LDAP_LI_MODGLM methods. if(ldapErr == -1) { m_pParams->setErrNo(ldap_get_lderrno(m_ldap,NULL,NULL)); return LI_LDAP_ERROR; } switch (ldapErr) { /* * We can only get one entry at a time in the current implementation, so we * terminate when we have at least one. */ case LDAP_RES_SEARCH_ENTRY: case LDAP_RES_SEARCH_RESULT: case LDAP_RES_MODIFY: case LDAP_RES_DELETE: case LDAP_RES_ADD: FE_GraphProgress(m_ce->window_id, NULL, 0, 1, -2); // -2 specifies that we want items not bytes case LDAP_RES_BIND: m_pParams->setResults(message); ldapErr = LI_LDAP_DONE; /* Special value to indicate to netlib that we're done. */ /* We don't use -1 here so we can differentiate between */ /* a valid result and an ldap error */ m_pParams->setErrNo(ldap_result2error(m_ldap,message,0)); break; case LDAP_SUCCESS: break; default: if(message) m_pParams->setResults(message); m_pParams->setErrNo(ldap_result2error(m_ldap,message,0)); ldapErr = LI_LDAP_ERROR; break; } /* If the ldapErr is -2, it means we're done and we've gotten something */ if(ldapErr == LI_LDAP_DONE) { char *utf8Encoding = NULL; // the last modified time is given by the modifyTimeStamp attribute. char *attrs[2] = { "modifyTimeStamp" , NULL }; int msgID; const LDAPURL* url = m_pParams->getURL(); // For LDAP_LI_MODGLM_METHOD and LDAP_LI_ADDGLM_METHOD, we have to // do another search to get the last modified time if the first // search completes successfully. To this, we simply start the search // and don't return -1 so netlib will keep calling process(). We also // change m_method to LDAP_LI_SEARCH_METHOD so that we won't end up in this // if statement again. Note that we don't change it in m_ce->URL_s->method // This is mainly to avoid netlib weirdness. switch(m_pParams->getURLMethod()) { case LDAP_LI_ADDGLM_METHOD: case LDAP_LI_MODGLM_METHOD: // Determine if the ADD part succeeded. We don't free the result because // it's the policy of the URL handler to always return the result when // it's available. We'll have to free if it the ADD/MOD succeed so that // the GLM result can be returned. if(m_pParams-> getErrNo() != LDAP_SUCCESS) { // m_pParams->result and param->status already set above. ((LDAPModGLMOperation*)m_pParams)->setModSucceeded(FALSE); break; // exit the ADDGLM and MODGLM stuff. } // setResults will free message, since setResults(message) was called earlier. m_pParams->setResults(NULL); message = NULL; ((LDAPModGLMOperation*)m_pParams)->setModSucceeded(TRUE); msgID = ldap_search(m_ldap,url->getDN(),url->getScope(),"(objectclass=*)",attrs,0); // each time we initiate a new ldap operation we need to increase the count FE_GraphProgressInit(m_ce->window_id, NULL, 1); // If msgID == -1, then there was a network error; so we set the error condition and // leave ldapErr = -1 so netlib will exit the url handler. if(msgID == -1) { m_pParams->setErrNo(ldap_get_lderrno(m_ldap,NULL,NULL)); ldapErr = LI_LDAP_ERROR; } else { ldapErr = 1; // >0 indicates to netlib to continue processing m_messageId = msgID; ((LDAPModGLMOperation*)m_pParams)->changeModToGLM(); } break; } // switch FE_GraphProgressDestroy(m_ce->window_id, NULL, 1, 1); } // if return ldapErr; } /* Unbind() * ======== * Unbind from the server if the caller didn't provide a connection. */ int net_LdapLIConnectionData::Unbind() { // If the caller did not provide the connection, we do the standard unbind. // If the caller did provide the connection, we have a sticky situation // because net_LdapConnectionData::Unbind() is called in the base class' // destructor, and that destructor is guaranteed to be called. To avoid // unbinding a caller provided connection, we just set m_ldap to NULL so that // the base class' Unbind() won't do anything. if(!m_bCallerProvidedConnection) return net_LdapConnectionData::Unbind(); else m_ldap = NULL; return 0; // Don't know what else we can return. } /* Interrupt() * =========== * netlib wants to interrupt this connect; we rely on the base class to do most of * the work and just set the appropriate error. */ int net_LdapLIConnectionData::Interrupt() { return net_LdapConnectionData::Interrupt(); } #endif // MOZ_LI #endif // MOZ_LDAP //**************************************************************************** // // Callbacks from NET_GetURL // extern "C" int32 net_LoadLdap (ActiveEntry *ce) { #ifdef MOZ_LDAP if (NET_IsOffline()) return MK_OFFLINE; net_LdapConnectionData *cd = NULL; switch (NET_URL_Type (ce->URL_s->address)) { case LDAP_TYPE_URL: case SECURE_LDAP_TYPE_URL: #ifdef MOZ_LI // If we're doing an LDAP operation, we // have to figure out if it's an LI operation // and instantiate the right connection data object. if((LDAP_SEARCH_METHOD < ce->URL_s->method) && (LDAP_LI_BIND_METHOD >= ce->URL_s->method)) cd = new net_LdapLIConnectionData(ce); else #endif cd = new net_LdapToHtml (ce); break; case LDAP_QUERY_DSE_TYPE_URL: // I think we need to query the DSE for any operation, so this is in the base class cd = new net_LdapToHtml (ce); break; case ADDRESS_BOOK_LDAP_TYPE_URL: cd = new net_LdapToAddressBook (ce); break; case LDAP_REPLICATION_TYPE_URL: cd = new net_LdapReplicator (ce); break; default: XP_ASSERT(FALSE); return MK_MALFORMED_URL_ERROR; } if (!cd) return MK_OUT_OF_MEMORY; ce->con_data = cd; int err = cd->Load (ce); if (err) { ce->status = err; delete cd; ce->con_data = NULL; } return err; #else return MK_MALFORMED_URL_ERROR; #endif } extern "C" int32 net_ProcessLdap (ActiveEntry *ce) { #ifdef MOZ_LDAP net_LdapConnectionData *cd = (net_LdapConnectionData*) ce->con_data; int err = cd->Process(); if (err < 0 || err == MK_CONNECTED) { delete cd; ce->con_data = NULL; } if(err == MK_CONNECTED) err = -1; return err; #else return MK_MALFORMED_URL_ERROR; #endif } extern "C" int32 net_InterruptLdap (ActiveEntry * ce) { #ifdef MOZ_LDAP net_LdapConnectionData *cd = (net_LdapConnectionData*) ce->con_data; int err = cd->Interrupt (); delete cd; ce->con_data = NULL; ce->status = MK_INTERRUPTED; return err; #else return -1; #endif } extern "C" void net_CleanupLdap(void) { } extern "C" void NET_InitLDAPProtocol(void) { static NET_ProtoImpl ldap_proto_impl; ldap_proto_impl.init = net_LoadLdap; ldap_proto_impl.process = net_ProcessLdap; ldap_proto_impl.interrupt = net_InterruptLdap; ldap_proto_impl.resume = NULL; ldap_proto_impl.cleanup = net_CleanupLdap; NET_RegisterProtocolImplementation(&ldap_proto_impl, LDAP_TYPE_URL); NET_RegisterProtocolImplementation(&ldap_proto_impl, SECURE_LDAP_TYPE_URL); NET_RegisterProtocolImplementation(&ldap_proto_impl, ADDRESS_BOOK_LDAP_TYPE_URL); } //**************************************************************************** // // Helper APIs // extern "C" XP_Bool NET_ReplicateDirectory (MSG_Pane *pane, DIR_Server *server) { #ifdef MOZ_LDAP net_OfflineLdapReplicator *replicator = new net_OfflineLdapReplicator(pane, server); if (replicator->ReplicateDirectory()) return TRUE; delete replicator; #endif return FALSE; }