So, I really want to see a copy of my Zimbra calendar over in Google Calendar. I want this for two reasons: 1) My family uses Google Calendar (with our own domain in Google Apps) and I would like my wife to be able to see my work calendar. 2) I am using a T-Mobile G1 Android phone, and having my calendar in Google Calendar is ideal for phone viewing and reminders.
All I really want is an iCal feed going from Zimbra to Google Calendar. Sounds easy, right? Not so fast… The problem is that Google Calendar doesn’t seem to handle HTTPS feeds, which we require for all access to Zimbra. Google Calendar also doesn't seem to do Basic Authentication, and I don't want to expose my entire Calendar at an easily guessable and unauthenticated URL.
I had previously set up a horribly tortured process that involved copying my calendar from Zimbra to a local file and then syncing it up to Google using GCALDaemon, but this frequently had errors in it and generally awful. Plus, GCALDaemon appears to be a dead project, so I don't think any of the bugs I am seeing are going to get fixed anytime soon.
So, I finally put together a "relay" process that make it possible to access a Zimbra iCal feed via a URL that Google Calendar likes. This process is meant to be usable by everyone in the company, and builds on the existing capabilities of Zimbra to manage authenticated external calendar shares. There is no persistent data to manage, so it's really just a matter of deploying a couple of scripts into a web server and exposing them properly. The only things these scripts require are PHP and the mcrypt module for PHP.
Disclaimer: I'm not much of a PHP developer, so these are hacks. Feel free to suggest improvements.
The first script ("calendarurl.php") helps the user construct a private URL for a Zimbra calendar share. This one should be deployed such that it requires HTTPS and requires user authentication. It is only used once by the user to create the URL and is not used after that. The script does need to be updated with the appropriate base URL for the second script.
<?php } ?>Private Calendar Share URL Private Calendar Share URL
<?php // replace with your own random 32 and 8 character keys $key = '12345678123456781234567812345678'; $iv = '12345678'; // replace with the URL to the second script $calurl = 'http://localhost/calendar.php'; if (isset($_POST['submit'])) { $login = $_POST['login']; $password = $_POST['password']; $user = $_POST['user']; $calendar = $_POST['calendar']; $daysback = $_POST['daysback']; $daysforward = $_POST['daysforward']; $input = implode('|', array($login, $password, $user, $calendar, $daysback, $daysforward)); $input = crc32($input)."|".$input; $cipher = mcrypt_module_open(MCRYPT_BLOWFISH,'',MCRYPT_MODE_CBC,''); mcrypt_generic_init($cipher, $key, $iv); $id = strtr(base64_encode(mcrypt_generic($cipher, $input)), '+/=', '-_,'); mcrypt_generic_deinit($cipher); mcrypt_module_close($cipher); $url = $calurl.'?id='.$id; echo ''.$url.''; } else { ?>
The second script ("calendar.php") is the one that then provides the iCal feed. It should not require SSL and should not require any authentication. It should be wide open to the public, but that's fine because it is useless without the giant ID the first script creates. This script needs to be updated with the URLs to properly reach your Zimbra server.
<?php // replace with your own random 32 and 8 character keys $key = '12345678123456781234567812345678'; $iv = '12345678'; $id = $_GET['id']; $result = 'HTTP/1.1 400 Bad Request'; if (isset($id)) { $cipher = mcrypt_module_open(MCRYPT_BLOWFISH,'',MCRYPT_MODE_CBC,''); mcrypt_generic_init($cipher, $key, $iv); $id = rtrim(mdecrypt_generic($cipher,base64_decode(strtr($id, '-_,', '+/='))),"\0"); mcrypt_generic_deinit($cipher); mcrypt_module_close($cipher); list($crc, $id) = explode("|",$id,2); if (is_numeric($crc) && (!empty($id)) && ($crc == crc32($id))) { list($login, $password, $user, $calendar, $daysback, $daysforward) = explode("|",$id); // replace the host/domain names with your won $url = "https://".urlencode($login).":".urlencode($password). "@zimbra.example.org/home/".urlencode($user)."@example.org/". urlencode($calendar)."?fmt=ics&start=-".$daysback."d&end=".$daysforward."d"; $calendar = @file_get_contents($url); $result = $http_response_header[0]; if ($result == 'HTTP/1.1 200 OK') { header('Content-Type: text/calendar; charset=utf-8'); header('Content-Disposition: attachment; filename="calendar.ics"'); print $calendar; exit(); } } } header($result); ?> <?php echo $result; ?>
The two scripts have a shared set on encryption keys (the $key and $iv variables at the top of each script). These have to be the same for the scripts to work together. If these get changed, it will break any URLs that have previously been created, so be sure to set these the way you want before you let users start using the scripts.
Once set up, you use the scripts by first setting up an "External Guest" share from within Zimbra. You then go to the URL for the first script and fill out the corresponding information from the share you created, as follows:
- Login is the external guest email address you used in creating the share -- your personal email address, for example.
- Password is the password you set up in the external guest share.
- User is your Zimbra account username.
- Calendar is the name of the calendar for which you created the share. Your default calendar is simply named "Calendar", which is already pre-populated in the form for you. Only change this if you are in fact wanting to share a different calendar.
- Days Back is the number of days into the past that the calendar feed should contain. Keeping this number reasonably small keeps the size of the feed down since old calendar data is generally not important to see. 30-90 days seems reasonable.
- Days Forward is the number of days into the future that the calendar feed should contain. 90-180 days seems reasonable.
Once you submit the form, it will display a simple URL with a single opaque calendar ID. That ID is an encrypted representation of all the data you just submitted in the form. If at any point you want invalidate that URL, just go to Zimbra and revoke the corresponding share. Be careful about sharing this URL -- the large key makes this reasonably secure (as secure as Google Calendar's "Private URLs"), but the URL itself is all it takes to see all your calendar details.
You can now click on that URL to test the iCal feed. If you get some kind of HTTP error, then something has gone wrong with the feed -- probably a mismatch in the email/password for the share. If a file named "calendar.ics" starts to download, then the share is working. You can now take that URL and paste it into an external calendaring application, like Google Calendar.
We now have these scripts up and running at Unicon and they seem to be functioning smoothly. If you are trying to do something similar, I hope you find these useful. Happy calendar sharing!
