Importing GoogleContacts to OpenLDAP

The task is to export contacts from Google and import them to a OpenLDAP server. Not that simple as it sounds. The goal was also that I wanted to be sure that all relevant data are imported. Using the import in Horde for example imported only some emails and some other stuff but no phone numbers, websites and ims.

Export Google Contacts

First the export. This was the simple part. Just export all your Google contacts in vcf format. You will download something like contacts.vcf.

Import to OpenLDAP

At this point i assume we have all the schmas I talked about in the previous post in our OpenLDAP server. Then we need to have a root for our address book in our OpenLDAP server. In my case it was:
ou=user,ou=adressbook-personal,dc=example,dc=com. So you can see it is a private address book for the user “user”.

Then I wrote a script to transform the VCF file to an LDIF file which should be easily importable to the OpenLDAP server.

#!/usr/bin/php
< ?php
if (!isset($argv[1]) || !isset($argv[2])) {
  die("Usage: {$argv[1]} [*.vcf] [PhoneBookDN]\n");
}

$phonebookdn = $argv[2];
$hash = $phonebookdn;
$content = file($argv[1]);
$contacts = array();
$countryCode = array(
  'Austria' => 'AT',
  'Österreich' => 'AT',
  'Czech Republic' => 'CZ',
  'Česká Republika' => 'CZ',
  'Egypt' => 'EG',
  'Mexico' => 'MX',
  'Poland' => 'PL',
  'USA' => 'US',
);

function checkUnset($contact, $key) {
  if (isset($contact[$key])) {
    die("{$key} is already taken for {$contact['cn']}\n");
  }
}

function parseAddress($contact, $address, $keyPostalAddress, $keyPOBox,
    $keyStreet, $keyLocality, $keyState, $keyPostalCode, $keyCountry) {

  global $countryCode;
  checkUnset($contact, $keyPostalAddress);

  $contact[$keyPOBox]      = $address[0];
  if (isset($address[2]))
    $contact[$keyStreet]     = $address[2];
  if (isset($address[3]))
    $contact[$keyLocality]   = $address[3];
  if (isset($address[4]))
    $contact[$keyState]      = $address[4];
  if (isset($address[5]))
    $contact[$keyPostalCode] = $address[5];
  if (isset($address[6]))
    $contact[$keyCountry]    = $address[6];

  $contact[$keyPostalAddress] = "";
  if (!empty($address[2])) $contact[$keyPostalAddress] = "{$address[2]}";
  if (!empty($address[5])) {

    if (!empty($contact[$keyPostalAddress]))
      $contact[$keyPostalAddress] .= '$';

    $contact[$keyPostalAddress] .= "{$address[5]}";

    if (!empty($address[3]))
      $contact[$keyPostalAddress] .= " ";
  }

  if (!empty($address[3])) {
    if (empty($address[5]) && !empty($contact[$keyPostalAddress]))
      $contact[$keyPostalAddress] .= '$';
    $contact[$keyPostalAddress] .= "{$address[3]}";
  }

  if (!empty($address[4])) {
    if (!empty($contact[$keyPostalAddress]))
      $contact[$keyPostalAddress] .= '$';
    $contact[$keyPostalAddress] .= "{$address[4]}";
  }

  if (!empty($address[6])) {

    if (!empty($contact[$keyPostalAddress]))
      $contact[$keyPostalAddress] .= '$';

    if (!isset($countryCode[$address[6]]))
      die("No country code for '{$address[6]}'\n");

    $contact[$keyPostalAddress] .= $countryCode[$address[6]];
  }
}

foreach($content as $line) {
  $line = str_replace("\n", "", $line);
  $line = str_replace("\r", "", $line);

  if ($line == "BEGIN:VCARD") {
    $contact = array(
      'dn' => $phonebookdn,
      'objectClass' => array(
	'top', 'person', 'mozillaOrgPerson',
	'organizationalPerson', 'inetOrgPerson',
	'evolutionPerson',
      ),
    );

  } elseif ($line == "END:VCARD") {
    if (isset($contacts[$contact['dn']])) {
      die("Contact {$contact['cn']} is duplicate.\n");
    }
    $contacts[$contact['dn']] = $contact;

  } else {
    list($key, $value) = explode(":", $line, 2);
    $key = explode(";", $key);

    if ($key[0] == 'FN') {
      $contact['cn'] = $value;
      $contact['dn'] = "uid=" . sha1($value . "-" . $hash) . ",{$contact['dn']}";

    } elseif ($key[0] == 'N') {
      $name = explode(";", $value);
      $contact['sn'] = $name[0];
      $contact['givenName'] = $name[1];
      $contact['mozillaAdditionalNames'] = $name[2];
      $contact['mozillaNamePrefix'] = $name[3];
      $contact['mozillaNameSuffix'] = $name[4];

    } elseif ($key[0] == 'NICKNAME') {
      $contact['mozillaNickName'] = $value;

    } elseif ($key[0] == 'EMAIL'
	|| preg_match("/item[0-9]+\.EMAIL/", $key[0])) {

      if (!isset($contact['mail']))
	$contact['mail'] = array();

      $contact['mail'][] = $value;
    } elseif ($key[0] == 'X-GTALK'
	|| $key[0] == 'X-YAHOO'
	|| $key[0] == 'X-SKYPE'
	|| $key[0] == 'X-ICQ'
	|| $key[0] == 'X-JABBER'
	|| $key[0] == 'X-MSN') {

      $im = explode('-', $key[0]);
      if (!isset($contact['mozillaInstantMessenger']))
	$contact['mozillaInstantMessenger'] = array();
      $contact['mozillaInstantMessenger'][] = "{$value} {$im[1]}";
    } elseif ($key[0] == 'TEL') {
      $type = array();
      for ($i=1; $i < count($key); $i++) {
	$t = explode("=", $key[$i], 2);
	if ($t[0] == 'TYPE') $type[] = $t[1];
      }
      sort($type);
      $type = implode("", $type);

      if ($type == 'CELL') {
	checkUnset($contact, 'mobile');
	$contact['mobile'] = $value;
      } elseif ($type == 'FAXWORK') {
	checkUnset($contact, 'facsimileTelephoneNumber');
	$contact['facsimileTelephoneNumber'] = $value;
      } elseif ($type == 'FAXHOME') {
	checkUnset($contact, 'homeFacsimileTelephoneNumber');
	$contact['homeFacsimileTelephoneNumber'] = $value;
      } elseif ($type == 'HOME') {
	checkUnset($contact, 'homePhone');
	$contact['homePhone'] = $value;
      } elseif ($type == 'OTHER') {
	checkUnset($contact, 'otherPhone');
	$contact['otherPhone'] = $value;
      } elseif ($type == 'WORK' || $type == '') {
	checkUnset($contact, 'telephoneNumber');
	$contact['telephoneNumber'] = $value;
      } else {
	die("Uknown TEL '{$type}'\n");
      }
    } elseif ($key[0] == 'ADR') {
      $address = explode(";", $value);
      //POBox;ExtendedAddress;Street;Locality;Region;ZIPCode;Country
      if (!isset($key[1])) $key[1] = 'TYPE=HOME';
      if ($key[1] == 'TYPE=WORK') {
	parseAddress($contact, $address, 'postalAddress', 'postOfficeBox',
	    'street', 'l', 'st', 'postalCode', 'countryName');

      } elseif ($key[1] == 'TYPE=HOME') {
	parseAddress($contact, $address, 'homePostalAddress',
	    'mozillaHomePostOfficeBox', 'mozillaHomeStreet',
	    'mozillaHomeLocalityName', 'mozillaHomeState',
	    'mozillaHomePostalCode', 'mozillaHomeCountryName');
      } else {

	die("Uknown ADR {$key[1]}\n");
      }
    } elseif ($key[0] == 'ORG') {
      checkUnset($contact, 'o');
      $contact['o'] = $value;

    } elseif ($key[0] == 'TITLE') {
      checkUnset($contact, 'title');
      $contact['title'] = $value;

    } elseif ($key[0] == 'BDAY') {
      checkUnset($contact, 'birthDate');
      $contact['birthDate'] = $value;

    } elseif (preg_match("/item([0-9]+)\.X\-ABDATE/", $key[0])) {
      checkUnset($contact, 'anniversary');
      $contact['anniversary'] = $value;

    } elseif ($key[0] == 'URL'
	|| preg_match ("/item[0-9]+\.URL/", $key[0])) {

      $k = null;
      if (isset($key[1])) {
	$type = explode("=", $key[1], 2);
      } elseif (preg_match("/item([0-9]+)\.URL/", $key[0], $matches)) {
	$type = "";
	$k = "item{$matches[1]}.X-ABLabel";
      } else {
	$type = "";
      }

      if (!isset($contact['labeledURI']))
	$contact['labeledURI'] = array();

      if ($k == null)
	$contact['labeledURI'][] = "{$value} {$type}";
      else
	$contact['labeledURI'][$k] = $value;

    } elseif (preg_match ("/item[0-9]+\.X\-ABLabel/", $key[0], $matches)
	&& isset($contact['labeledURI'][$matches[0]])) {

      $contact['labeledURI'][$matches[0]] .= " {$value}";

    } elseif ($key[0] == 'NOTE') {
      checkUnset($contact, 'description');
      $contact['description'] = $value;

    //} elseif ($key[0] == '') {

    } elseif ($key[0] == 'VERSION'
	|| $key[0] == 'X-PHONETIC-LAST-NAME'
	|| preg_match("/item[0-9]+\.X\-ABLabel/", $key[0])) {
      // ignore

    } else {
      die("Unknown KEY {$key[0]}\n");
    }
  }
}

foreach($contacts as $contact) {
  foreach($contact as $key => $value) {
    if (empty($value)) {
      // skip
    } elseif (is_array($value)) {
      foreach($value as $v) {
	echo "{$key}: {$v}\n";
      }
    } else {
      echo "{$key}: {$value}\n";
    }
  }
  echo "\n";
}

The script takes 2 parameters: the VCF file and the root DN of the address book in the OpenLDAP server. If the script ends with an error you will need to tweak the VCF file. If it prints out the LDIF content you can save it:

$ ./vcf2ldif.php contacts.vcf ou=user,ou=adressbook-personal,dc=example,dc=com > contacts.ldif

And import them into the OpenLDAP server:

$ ldapadd -v -x -D cn=admin -w ******** -f contacts.ldif

If something goes wrong and only a subset of the contacts gets imported you can delete them:

$ for i in `cat contacts.ldif | grep "dn:" | cut -d" " -f2`; do ldapdelete -v -x -D cn=admin -w ******** $i; done

Thas all.

Leave a Reply

Your email address will not be published. Required fields are marked *