Author Archives: morgan

About morgan

Morgan is a freelance IT consultant living in Philadelphia. He lives with his girlfriend in an old house in Fishtown that they may never finish renovating. His focus is enterprise Messaging (think email) and Directory. Many of his customers are education, school districts and Universities. He also gets involved with most aspects of enterprise Linux and UNIX (mostly Solaris) administration, Perl, hopefully Ruby, PHP, some Java and C programming. He holds a romantic attachment to software development though he spends most of his time making software work rather than making software. He rides motorcycles both on and off the track, reads literature with vague thoughts of giving up IT to teach English literature.

pip install results in undefined symbol: XML_SetHashSalt

# python3 -m pip install --upgrade google-api-python-client
Traceback (most recent call last):
File "/usr/lib64/python3.4/", line 170, in _run_module_as_main
"__main__", mod_spec)
File "/usr/lib64/python3.4/", line 85, in _run_code
exec(code, run_globals)
File "/usr/lib/python3.4/site-packages/pip/", line 16, in
from pip._internal import main as _main # noqa
File "/usr/lib/python3.4/site-packages/pip/_internal/", line 42, in
from pip._internal import cmdoptions
File "/usr/lib/python3.4/site-packages/pip/_internal/", line 16, in
from pip._internal.index import (
File "/usr/lib/python3.4/site-packages/pip/_internal/", line 16, in
from pip._vendor.distlib.compat import unescape
File "/usr/lib/python3.4/site-packages/pip/_vendor/distlib/", line 83, in
import xmlrpc.client as xmlrpclib
File "/usr/lib64/python3.4/xmlrpc/", line 137, in
from xml.parsers import expat
File "/usr/lib64/python3.4/xml/parsers/", line 4, in
from pyexpat import *
ImportError: /usr/lib64/python3.4/lib-dynload/ undefined symbol: XML_SetHashSalt
# ldd /usr/lib64/python3.4/lib-dynload/ => (0x00007fffaefc4000) => /ora12cClient/oracle/app/oracle/product/ (0x00007f1939572000) => /lib64/ (0x00007f19390de000) => /lib64/ (0x00007f1938ec2000) => /lib64/ (0x00007f1938aff000) => /lib64/ (0x00007f19388fa000) => /lib64/ (0x00007f19386f7000) => /lib64/ (0x00007f19383f5000)
/lib64/ (0x0000559b24b1f000)
# export LD_LIBRARY_PATH=/usr/lib64
# ldd /usr/lib64/python3.4/lib-dynload/ => (0x00007ffef39b1000) => /usr/lib64/ (0x00007f20c7b7b000) => /usr/lib64/ (0x00007f20c76e7000) => /usr/lib64/ (0x00007f20c74cb000) => /usr/lib64/ (0x00007f20c7108000) => /usr/lib64/ (0x00007f20c6f04000) => /usr/lib64/ (0x00007f20c6d00000) => /usr/lib64/ (0x00007f20c69fe000)
/lib64/ (0x000055a1cf2f4000)
# python3 -m pip install --upgrade google-api-python-client
Collecting google-api-python-client

Run multiple versions of Ansible from source concurrently

If you run Ansible from source you may have noticed that you have to re-source the hacking/env-setup script if you want to switch to a different version. In my case I keep Ansible 2.3 around to support legacy CentOS 5 systems. It’s relatively simple to just run the hacking script when you run ansible:

clone and checkout the legacy version:
$ git clone --recursive
$ mv ansible ansible2.3
$ cd ansible2.3
$ git tag
$ git checkout v2.3.3.0-1
$ cd ..

clone and checkout the latest version:
$ git clone --recursive

$ source ~/.profile
$ vi ~/.profile
alias ansible-playbook2.3="source /path/to/ansible2.3/hacking/env-setup -q && path/to/ansible2.3/bin/ansible-playbook $*"
alias ansible-playbook="source /path/to/ansible/hacking/env-setup -q && /path/to/ansible/bin/ansible-playbook $*"

$ ansible-playbook --version
ansible-playbook 2.6.2 (devel c997811f45) last updated 2018/08/01 12:40:35 (GMT -400)
$ ansible-playbook2.3 --version
ansible-playbook (two-three 0255ab2e27) last updated 2018/12/07 12:05:40 (GMT -400)

Transparently handle first argument to Perl Package subroutines

It’s always baffled me that Perl subroutines behave differently when called from inside the Package vs. outside.

The use case may be obvious but I’ll say it: you write a small utility routine that you want to call as $pack->func(1); and from within the Package as func(1);;

The problem of course is that when called as $pack->func(1); the first arg will the object itself and when called as func(1); from within the package the first arg will be “1.”

There’s a very simple solution:
shift if ((ref $_[0]) eq __PACKAGE__);

Here’s a simple example:

#!/usr/bin/perl -w
package Pack;

sub new {
    my $c = shift;

    my $self = {};
    bless $self, $c;
    return $self;

sub func {
    shift if ((ref $_[0]) eq __PACKAGE__);
    my $a = shift;

    print "passed in: $a\n";

sub call_func {
    print "calling func from inside ", __PACKAGE__, ":\n";

my $p = new Pack;

print "calling func from main:\n";


Try commenting the “shift” line in sub func() and see how it behaves differently.

How to locate perl modules in the same directory as the script

Perl of course allows you to identify the location of your perl modules by modifying @INC but is surprisingly rigid when it comes to placing module(s) in the same directory with the script.

This is fine for permanent installations or for scripts that depend on an installer but if you want to distribute a script plus module(s) for general use or just want flexibility to copy it from system to system without modifying the code the solution is relatively simple:

Setup: you write a perl module that you’d like to include in the same directory as your script and allow the script to be called from anywhere.

in you would:


To contain your script and its supporting files in an arbitrarily located myScript directory you’d have to change into the myScript directory before executing

$ mv /path/to/myScript
$ cd /path/to/myScript && ./

suppose you just want to execute it as

$ /path/to/myScript/

You’d get:

Can't locate in @INC (@INC contains: /usr/lib..

Well the solution is fairly straightforward: parse the content of $0 to identify the location of (ostensibly and put it at the beginning of @INC:

    my $script_dir = $0;
    if ($0 =~ /\/[^\/]+$/) {
        $script_dir =~ s/\/[^\/]+\/*\s*$//;
        unshift @INC, $script_dir;

SSL Certificates in Zimbra 5.0.x

Using these links as a starting place,

Here’s a run-down of installing SSL certificates in Zimbra Collaboration Suite (ZCS) 5.0.x. It’s relatively straightforward once you wrap your head around the steps. You’ll want to do these steps as root.

The customer on which this example is based had existing Comodo wildcard certificates for their *

Browse to
log in
Arrow over to Comodo PreimumSSL Wildcard Certificate for *
click on ‘Download as .zip’

You should have three files that start with ‘STAR.’ rename them:
STAR_domain_edu.crt to commercial.crt
STAR_domain_edu.crt to commercial_ca.crt
STAR_domain_edu.key to commercial.key

Copy the commercial* files to each of the Zimbra hosts.

# mv commercial.key /opt/zimbra/ssl/zimbra/commercial
# mv commercial.crt commercial_ca.crt /var/tmp

Deploy the cert:

# cd /var/tmp
# zmcertmgr deploycrt comm ./commercial.crt commercial_ca.crt

This may not apply to you but we were unable to get openssl and by extension Zimbra to verify the Comodo cert chain. If zmcertmgr deploycrt is failing for you and you’re relatively confident your certs are okay here’s how I fixed it. It’s unconventional but it works. I am open to correction if someone has a more conventional fix for this..

Cd to /opt/zimbra/bin, copy zmshutil and zmcertmgr to /var/tmp and edit zmcertmgr in /var/tmp. Comment out the lines as below and add two lines also as below:

# cd /opt/zimbra/bin
# cp zmshutil /var/tmp/zmshutil
# cp zmcertmgr /var/tmp/zmcertmgr
# vi /var/tmp/zmcertmgr
    #  result=`${openssl} verify -purpose sslserver -CAfile $ca_crt $crt`
    #  if [ x"${result}" = x"${crt}: OK" ]; then
    #   echo "Valid Certificate: $result"
    echo "(artificially) Valid Certificate: $result"
    #  else
    #    echo "${ERROR_PREFIX} Invalid Certificate: $result"
    #    exit 1
    #  fi

    #  result=`${openssl} verify -purpose sslserver -CAfile $cafile $crt`

    #  if [ x"${result}" = x"${crt}: OK" ]; then
    #      echo "Valid Certificate Chain: $result"
    echo "(artificially) Valid Certificate Chain: $result"
    #  else
    #    echo "${ERROR_PREFIX} Invalid Certificate Chain: $result"
    #    exit 1
    #  fi

Once you’ve saved the modified version of zmcertmgr, run it from /var/tmp to deploy the certificates:

# cd /var/tmp
# ./zmcertmgr deploycrt comm ./commercial.crt commercial_ca.crt 
** Verifying ./commercial.crt against /opt/zimbra/ssl/zimbra/commercial/commercial.key
Certificate (./commercial.crt) and private key (/opt/zimbra/ssl/zimbra/commercial/commercial.key) match.
(artificially) Valid Certificate: 
** Copying ./commercial.crt to /opt/zimbra/ssl/zimbra/commercial/commercial.crt
** Appending ca chain commercial_ca.crt to /opt/zimbra/ssl/zimbra/commercial/commercial.crt
** Saving server config key zimbraSSLCertificate...done.
** Saving server config key zimbraSSLPrivateKey...done.
** Installing mta certificate and key...done.
** Installing slapd certificate and key...done.
** Installing proxy certificate and key...done.
** Installing CA to /opt/zimbra/conf/ca...done.

Now your certificates are installed. You need to restart Zimbra for them to take effect:

# su - zimbra -c "zmcontrol stop && zmcontrol start"

You’ll need to repeat the above for each of your servers if you have a multi-server environment: stores, mtas, ldap, etc.

If you have a relatively recent version of openssl you can test that your certificate is working by testing tls on your mta(s):

$ openssl s_client -starttls smtp -connect

RSI update March-August

Let me start with a time line to set up the history:

I was nearly pain free until the end of March.
3/25: Went to a 3 day conference and typed for 3-4 hours with my laptop in my lap. I was not in pain at the time.

4/1-30: I was in pretty constant (4-5/10) pain for most of April. I had only been doing nerve glides 1-2 a day so I increased frequency to 4-5 times a day. By May 1 I was nearly pain free again.

5/1-10: B and I went on vacation near the end of which we spend a few days riding a motorcycle all day. Pain stayed minimal.

5/17: I rode my dirt bike for the first time in quite a while. The pain returned at 4-5/10 at the end of May.

My six month follow-up visit with the hand surgeon was approaching and I was starting to worry so I made an appointment with the physical therapist. She and I put together the time line above, she pointed out that nerve pain is often delayed: something you do Sunday may not cause pain until Wednesday. She also felt strongly that motorcycling was contributing to my increase in pain even though there was not a direct relationship. I left that day discouraged. I continued nerve glides 4-5 times a day.

Pain decreased little by little over a period of weeks. I did not ride motorcycles less, I did continue to type with correct posture but still typed full time.

I finally has my follow-up EMG at the end of July. The results show very close to normal in my left elbow and within normal level in my right. The surgeon released me!

Though the pain is reduced I am still in pain about half the time. The pain varies from 1-3/10 with a periodic 4/10 but it’s minimal. I asked the surgeon about the pain and she described it as the nerve is still irritated but no longer damaged.

The bottom line is that nerve glides and behavior modification works. I can’t thank my therapist at the Hand Center here in Philadelphia for correcting my bad habits and ultimately showing me how to correct this without surgery.

I ride motorcycles as much or as more as ever. I type on a computer 10-15 hours a day 5-6 days a week. I am by all measures healing and able to do my work and my hobby.

It may help others at an earlier stage of recovery from Cubital Tunnel Syndrome to know what I believe was the cause(s) and how I corrected each. I am not a doctor or therapist so this is my experience only.

Sleeping with my arms bent: I have always slept with my arms bent either under my head or under my torso. I have consciously taught myself to sleep with my arms straight. It took a few months until I could consistently sleep without waking up with my arm bent. Here’s what works best for me now: pile two pillows, rest your head in one direction and put the arm behind your head under the pillows straight up. Put the other arm in front of your face over the pillow straight up. You need to slide down so your feet are hanging off the end of the bed. It may sound odd but it works very well and is the one position I can sleep in comfortably every time.

Poor posture at the keyboard: your knees, hips and elbows should all be 90 degrees. Your elbows should be at your side. Put the keyboard at a height that you can type on it without hunching your shoulders: this means either a keyboard tray or just a low table. Arch your lower back and get your head over your torso. Your chair should provide some back support and be the correct height such that your feet rest on the floor with your knees bent 90 degrees.

Raise the monitor/laptop. I use a Rain designs stand on my Mac. I have an iFold for travel. Both are a little low but get the screen up so I’m not looking way down when in the right posture. This reduces your tendency to want to hunch to get your eyes lined up to the screen.

Split keyboard: this makes a surprising difference. It should be essentially two pieces and allow adjustment. Hold your arms in the position described above and put your hands over the keyboards but keep your wrists straight. your keyboards should line up such that you don’t have to angle your wrists. I have 2 Kineses Freestyles and just bought a Goldtouch Go! I can type all day on either the Goldtouch or the Kinesis. Just a few minutes typing on a standard keyboard is painful..

Do your nerve glides! No one does physical therapy generally.. but seriously nerves respond to these exercises. I noticed a huge difference when I first started doing them and when I forget my arms hurt.. I’m still doing them 4-5 times a day. If you use a Mac install this: It reminds me to stop every hour.

Get a rest for your mouse. I use a goldtouch gel pad with a 1/4″ piece of high density foam stuck underneath it. This allows me to wrest the base of my hand while I mouse and thus not bend my wrist. It’s little but it makes a huge difference. I use a regular mouse otherwise: usually cheap corded mouse or inexpensive wireless mouse.

10 days out West

[this was written back in May.. things got crazy and I am just posting it now. -mj]

I somehow managed to convince B to drive 1700 miles to lake Tahoe with me and then ride to the West coast from there. I decided to move a motorcycle that’s been living in the middle of the country to Santa Cruz, CA where close friends H, N and their daughter M have recently relocated. The riding and weather out here are amazing–well worth the storage fees.

The plan was to load the motorcycle into a rental truck, drive 1700 miles to Lake Tahoe and then ride to Santa Cruz. So we rented a Penske, loaded the motorcycle and hit the interstate. We decided on a three day trip: first to Santa Fe, NM, then Vegas and finally stopping in South Lake Tahoe, CA for a rest and to drop off the truck.

Santa Fe is a very quiet town in the off season. We stayed at the La Honda, a historic inn in the center of town. It was not expensive and reasonably well appointed. Santa Fe architecture is unique: mostly earth colored stucco, flat roofs, rounded corners, inset windows and doors. It presumably dates back to the original Native American inhabitants. It makes for a very unique looking town. The town itself is mostly a vacation destination, quiet in the off season. Most of the draw seems to be restaurants and a seemingly endless Native American art market.

Vegas is exactly as I imagined it but a little worse. We stayed at the Luxor, the huge pyramid on the strip. It was not expensive and the staff were genuinely nice. Our first room was clearly used. After a 10 minute wait we were issued a clean room. Our key stopped working. After another 10 minute wait we were issued new keys. The Luxor is huge, full of smoke and just about everyone inside is visibly drunk. We joked with the staff about a clearly drunk patron as he staggered away from the desk: “I believe that man is drunk.” “Most everyone here is” she responded without a hint of sarcasm.

Vegas is only cheap if you’re gambling. We crossed the gaming floor 8 or so times in the course of the night, were were offered comps at least 4 of those times. We had drinks at the bar in the middle of the floor. It was stylish and the waitresses weren’t wearing much but drinks were $12. We decided to hit the strip to have dinner around 9. Just about nothing was open.. Just a few overpriced restaurants in a mall and the mediocre noodle place in the Borgata.

I would characterize Vegas as an overpriced frat party owned and orchestrated by a few huge corporations to amuse a demographic that doesn’t know any better or worse actually finds it entertaining.. That is all assuming you don’t gamble, don’t have ready access to markets with high end restaurants or shopping where you live. On the way out we drove through the old strip and immediately regretted not staying there. It has a more authentic feel: in many ways frozen from a time before corporations owned Vegas. The old strip is frozen aging 50s and 60s glamor.

The Hoover Dam, on the way into Vegas is absolutely worth seeing. It’s really magnificent to just drive through. I’m sure the tour is worthwhile.. it just didn’t fit into our schedule.

In Tahoe we checked into the Inn By the Lake in South Lake Tahoe. We never made it to the top of the lake but I understand the South is more low-end. South Lake Tahoe, CA reminded me of small mountain towns in PA. It was definitely vacation oriented but focused on low cost non-chain motels than high end resorts.

The Nevada-California border cuts through the lake. With a few exceptions there are wedding chapels on the Nevada side right up to the California border. We noticed a few on the California side and asked about them at the hotel. A few are apparently grandfathered from a time when on-the-spot marriage was legal in California.

imapsync between Zimbra environments

I spent entirely too much time in the last few days setting up to imapsync between two Zimbra environments. I will spare you the whole story but suffice to say we moved some users from production to a newly upgraded development environment and then managed to wipe out a significant portion of their mail. They were already receiving mail in the dev environment so re-copying the mail from prod wasn’t an option. Zimbra’s backup and restore isn’t smart enough to restore from one address (> The obvious solution is to imapsync from prod to dev. Simple, right?


It turns out Zimbra adds headers when messages are added to the destination side. This means if you run imapsync more than once, say, to test on a folder and then run it for a whole account it will re-upload messages it just uploaded as the new headers on the destination side made the messages different. The solution is to –skipsize and ‘useheader’ enough headers so you’re sure they’ll make messages unique even if all headers aren’t in all messages (Message-ID is not in all messages for instance) but they won’t be changed on the destination side. I used –useheader Message-ID –useheader ‘From’ –useheader ‘To’ –useheader ‘Date’

imapsync --sep1 '/' --prefix1 ''  --authmech1 PLAIN --ssl1 \
    --host1 --user1 user --password1 pass \
    --authmech2 PLAIN -ssl2 --host2 --user2 user2 \
    --password2 pass2 --useheader Message-ID --useheader 'From' \
    --useheader 'To' --useheader 'Date' --skipsize

We also saw problems with messages presumably larger than 10mb:

++++ From [Sent] Parse 1 ++++
++++ To   [Sent] Parse 1 ++++
++++ Verifying [Sent] -> [Sent] ++++
+ NO msg #286039 [VsZ9lgHjrPxeOAn7S9sU6Q] in Sent
+ Copying msg #286039:12572025 to folder Sent
flags from : [\Seen]["04-May-2009 07:46:14 -0400"]
parse_headers want an ARRAY ref
Couldn't append msg #286039 (Subject:[0]) to folder Sent: Error trying to 
append: 6568 NO [TOOBIG] request too long

A look in the user’s sent folder for a message at 07:46 showed a 12mb message. Search the zimbra forums and:

[zimbra@store01 ~]$ zmprov gacf|grep -i zimbraMtaMaxMessageSize
zimbraMtaMaxMessageSize: 10485760
[zimbra@store01 ~]$ zmprov mcf zimbraMtaMaxMessageSize 50000000
[zimbra@store01 ~]$ 

You can now re-run imapsync as above and the message will be copied.

Also of note is –sep1 ‘/’ and –prefix ”: Apparently the Zimbra 5.0.7 imap server does not have NAMESPACE capability.

edit 090706: changed zimbraFileUploadMaxSize to zimbraMtaMaxMessageSize. According to this was changed in 5.0.6.