Tuesday, January 26, 2010

Google Contacts in Mutt

Introduction



For some time now I have managing all my contact information (a.k.a address book) in GMail because this allows me for easy synchronization of contacts with my cell phone that is a Google Phone HTC Hero.

What I was missing was the ability to query the GMail contacts from within my favorite email client: Mutt and the solutions I found in Google were not working for me.

The first solution I found [1] was to export all my GMail contacts to vcards and then import these to abook [2] that is an address book compatible with Mutt. Not really a solution should I say.

The second solution [3] I found is actually pretty good. Is a small python script that can be used within mutt using the "query_command" [4] and actually queries GMail contacts web service API. Also supports caching so you do not need to connect every time to GMail service speeding up the query time.

Unfortunately this solution did not work very well for me. Some contacts would not be auto completed when composing an email and some other would not even appear. For example contacts with non ASCII characters (e.g. Japanese Kanji) would never get autocompleted, even thought they appeared when listing all contacts.

So after some time I implemented my own solution, in Ruby, that works perfectly for me and also has some additional features for groups and mailing lists.

Query GMail Contacts within Mutt



Using the gdata gem [5] and some knowledge of the Google contacts web API [6] it was relatively easy to implement a Ruby script that could be used with Mutt to query my contacts directly from GMail:


#!/usr/bin/env ruby1.8
##
# File:   contactos.rb
# Author: Horacio Sanson (hsanson at gmail)
# Date:   2010/01/22
#
# Descr:
#    Small script to facilitate use of GMail contacts within mutt email client.
#
# Features:
#    - Generates a list of group and subscribe commands generated from your
#      GMail contacts groups that can be sourced from your muttrc file.
#    - Can be used to search for contacts within mutt using the query_command
#      option in the same way abook or ldbd are used.
#    - Results are cached for a period of time (configurable) to speed up
#      queries.
#
# Installation:
#    To use this script you need ruby and rubygems 1.8 and the gdata gem. To
#    install these in Ubuntu/Kubuntu use the following commands.
#
#      sudo aptitude install ruby1.8 rubygems-1.8 libopenssl-ruby1.8
#      sudo gem1.8 install gdata 
#
#    Make sure to read the comments below to learn how to configure this script
#    to work with your GMail account.
#
#    Then in your muttrc file add these lines:
#
#       source '/PATH_TO_SCRIPT/contactos.rb --groups|'
#       source '/PATH_TO_SCRIPT/contactos.rb --subscribes|'
#       set query_command ="/PATH_TO_SCRIPT/contactos.rb --mutt %s"
#       set query_format="%4c %t %-40.40a %-40.40n %?e?(%e)?"
#       bind editor <Tab> complete-query
#       bind editor ^T complete
#
#    with this configuration you will be able to use your GMail groups in all
#    regexp's and create hooks for them. Pressing <tab> when filling a To: or
#    From: header will give you a list of options to choose or auto complete the
#    address if only one matches.
#
# Resources:
#    - http://antonyjepson.wordpress.com/2009/06/27/using-your-gmail-contacts-in-mutt/
#    - http://www.chizang.net/alex/blog/code/mutt-query-gmail/
#    - http://castrojo.wordpress.com/2009/01/28/gmail-contacts-with-mutt/
#    - http://wiki.mutt.org/?QueryCommand
#    - http://code.google.com/apis/contacts/docs/3.0/reference.html#Feeds
#    - http://code.google.com/apis/gdata/articles/gdata_on_rails.html#GettingStarted
#
# TODO:
#   - Mutt only sources the group list once on startup. If we add new groups or
#     add contacts to a group these won't be reflected in mutt until we restart
#     it. Not really a problem because mutt has the fastest start up time.
#   - Create a script to add contacts from within mutt.
#      - Find if create-alias can accept a script as parameter.

require "rubygems"
require "gdata"
require "fileutils"
require "pp"

## Set up here your GMail account username and password
USERNAME="user"
PASSWORD="secret"

# You may create a group in you GMail contacts and add all the mailing lists
# you are subscribed into that group. This script will generate a bunch of
# subscribe commands for all addresses in that group.
MAILISTGROUP="Mailing Lists"

# Make sure this value is larger than the total number of contacts you have.
MAXRESULTS=999

# How much time before the local cache expires in seconds.
UPDATE_INTERVAL=3600

# Where to store the local cache for faster query times.
CACHEFILE="~/.mutt/cache/gcontacts"

###############################################################################
## DON'T CHANGE ANYTHING BELOW THIS POINT
##   unless you know what your are doing
###############################################################################

class Contact
  attr_accessor :emails, :groups
  attr_reader :name, :id
  def initialize(id, name="")
    @id = id
    @name = name
    @emails = []
    @groups = []
  end

  def mutt_match(pattern)
    return mutt_fmt if @name and @name =~ Regexp.new(pattern,Regexp::IGNORECASE)

    str = ""
    @emails.each { |email|
      str << "#{email}\t#{@name||email}\t#{@groups.first}\n" if email =~ Regexp.new(pattern,Regexp::IGNORECASE)
    }
    str
  end

  def mutt_fmt
    str = ""
    @emails.each { |email|
       str << "#{email}\t#{@name||email}\t#{@groups.first}\n"
    }
    str
  end
end

# This method updates the local cache if the cache file is not present or if the
# UPDATE_INTERVAL has expired.
def update_cache
  user_hash = nil
  if ! File.exists?(File.expand_path(CACHEFILE)) or Time.now - File.stat(File.expand_path(CACHEFILE)).mtime > UPDATE_INTERVAL
    #STDERR << "Updating from gmail\n"
    user_hash = {}
    client = GData::Client::Contacts.new

    begin
      client.clientlogin("#{USERNAME}@gmail.com", PASSWORD)
    rescue GData::Client::AuthorizationError
      STDERR << "Failed to authenticate\n"
      return nil
    rescue => e
      STDERR << "Failed to log into Gmail: #{e}\n"
    end

    # Create a hash list of all groups
    group_hash = {}
    groups = client.get("http://www.google.com/m8/feeds/groups/#{USERNAME}%40gmail.com/full?max-results=#{MAXRESULTS}").to_xml
    groups.elements.each('entry') { |entry|
      name = entry.elements['title'].text.gsub("System Group: ","")
      id = entry.elements['id'].text
      group_hash[id] = name
    }

    # Create a hash list of all users
    feeds = client.get("http://www.google.com/m8/feeds/contacts/#{USERNAME}%40gmail.com/full?max-results=#{MAXRESULTS}").to_xml
    feeds.elements.each('entry') { |entry|
      name = entry.elements['title'].text
      id = entry.elements['id'].text
      new_contact = Contact.new(id, name)

      entry.elements.each('gd:email') { |email|
        new_contact.emails << email.attribute('address').value
      }
      entry.elements.each('gContact:groupMembershipInfo') { |group|
        new_contact.groups << group_hash[group.attribute('href').to_s]
      }
      user_hash[id] = new_contact
    }

    File.open(File.expand_path(CACHEFILE),"wb") { |fd|
      fd << Marshal.dump(user_hash)
    }
  end
end

def load_cache
  #STDERR << "Updating from local cache\n"
  user_hash = {}
  if File.exists?(File.expand_path(CACHEFILE))
    File.open(File.expand_path(CACHEFILE),"rb") { |fd|
      user_hash = Marshal.load(fd.read)
    }
  end

  return user_hash
end

def print_help
 puts "usage: "
 puts "  contactos.rb --mutt [pattern]"
 puts "  contactos.rb --groups"
 puts "  contactos.rb --subscribes"
 puts "  contactos.rb --aliases"
end

if ARGV.empty?
  print_help
else
  update_cache
  contacts = load_cache
  case ARGV[0]
    when "--mutt"
      puts ""   # Mutt ignores the first line 
      if ARGV[1]
        contacts.each { |k, contact|
          STDOUT << contact.mutt_match(ARGV[1])
        }
      else
        contacts.each { |k, contact|
          STDOUT << contact.mutt_fmt
        }
      end
    when "--groups"
      contacts.each { |k,contact|
        contact.groups.each { |g|
          puts "group -group \"#{g}\" -addr #{contact.emails.join(', ')}" if ! contact.emails.empty?
        }
      }
    when "--subscribes"
      contacts.each { |k,contact|
        contact.groups.each { |g|
          if g == MAILISTGROUP and contact.emails.size > 0
            puts "subscribe #{contact.emails.join(' ')}"
          end
        }
      }
    when "--aliases"
      contacts.each { |k,contact|
        contact.emails.each { |e|
          if contact.name
            puts "alias \"#{contact.name}\" #{contact.name} <#{e}>"
          else
            puts "alias \"#{e}\" #{e}"
          end
        }
      }
    when "-v"
    when "--version"
      puts "Contactos.rb version 0.2.0, Copyright 2010 Horacio Sanson"
    when "-h"
    when "--help"
      print_help
    else
      print_help
  end
end


Simply copy the above script somewhere in your machine (e.g. ~/.mutt/contactos.rb) and change the USERNAME and PASSWORD variables. Give the script execution property and some secure permissions:

chmod 700 ~/.mutt/contactos.rb

You may also change the MAXRESULTS, UPDATE_INTERVAL and CACHEFILE variables to fit your needs. Just make sure MAXRESULTS is larger than the total number of contacts you have and that you have write access to the CACHEFILE you set. If you set UPDATE_INTERVAL to zero then the scripts queries GMail directly every time that can be slow depending on you network connection.

Also make sure you have installed Ruby and the gdata gem. In Ubuntu/Kubuntu this can be accomplished with the following commands:


sudo aptitude install ruby1.8 rubygems-1.8 libopenssl-ruby1.8
sudo gem1.8 install gdata

Finally configure Mutt to use this script to query contacts when you are filling the To: and From: headers of emails. To do this add these commands in your muttrc file:

set query_command ="~/mutt/contactos.rb --mutt %s"
set query_format="%4c %t %-40.40a %-40.40n %?e?(%e)?"
bind editor complete-query
bind editor ^T complete

After this you can press to autocomplete contacts using you GMail contact list and all without leaving the comfort of Mutt.

Integrating GMail contact groups within Mutt


After finishing my contacts script I realized that I could make even more things thanks to the rich Google contacts API and the scripting power and simplicity of ruby.

To integrate Mutt with you Google contact groups simply add this to your muttrc file:
source '~/.mutt/contactos.rb --groups|'
This command generates a list of "group" commands that map all GMail contact groups with Mut groups. Once this command is inserted in your muttrc file you may search, tag, delete and create hooks based on your GMail groups and manage the groups within GMail itself.

Managing Mutt mailing lists within GMail


One anoying thing about Mutt mailing lists is that you have to add a subscribe command for each mailing list you read in order to get all the features Mutt offers to handle mailing lists.

With my script you can create a special group for mailing lists (e.g. Mailing Lists) and add all the addresses of all mailing lists you are subscribed into that group. Then you can add this command to your muttrc file:

source './~mutt/contactos.rb --subscribes|'

and magically you get all the subscribe commands automagically generated for you from you GMail contacts. If you prefer to use a different name for you mailing list group you have to change the MAILISTGROUP variable in the ruby script to match the name you have in you GMail contacts.

Resources


Sunday, January 03, 2010

Brother DCP-595CN Printer In Ubuntu/Kubuntu



This little machine is a printer, scanner and copy machine all in one. It has USB and ethernet ports but even better a wireless 802.11a/b/g port. This means I can have the printer anywhere in the house and print from my desktop or lapton freely. In Japan it costs around ¥15,000 (USD 150) that is pretty cheap for all the features packaged in.

And the most important thing for me is that is 100% compatible with linux (Kubuntu). Here I will show the steps to get this little beast working with my Kubuntu machine.

Prerequisites



I use the wireless interface to connect to the printer/scanner so before you follow these instructions make sure the printer has an IP configured. The wireless configuration is done using the printer control panel and is already covered in the printer manual so I will skip it.

You may ping the printer IP address to check that you can reach it from your computer. If you cannot ping it then it is not well configured or you have problems with your LAN network.

Installation



These instructions are based on the Brother solution page instructions.

First we need to download both the LPR driver and the CUPS wrapper driver by going to the following URLs. Inside you will find RPM and DEB packages for the different printer models.

If you have the same printer as me (DCP-595CN) then look for the DEB packages of this printer and click the link. You will be directed to a GPL license page where you must agree to the GPL terms (Left Button) after which the download will start.

Once the DEB packages are downloaded we install them using the following commands:

sudo mkdir /usr/share/cups/model
sudo mkdir /var/spool/lpd
sudo mkdir -p /usr/lib/cups/filter
sudo dpkg -i dcp595cnlpr-1.1.2-1.i386.deb
sudo dpkg -i dcp595cncupswrapper-1.1.2-2.i386.deb
note that there are only packages for i386 architectures but if you see the Q&A link they explain how to install on amd64 architectures. All you have to do is add the --force-arquitecture switch to the dpkg command like:
sudo dpkg --force-arquitecture -i dcp595cnlpr-1.1.2-1.i386.deb
sudo dpkg --force-arquitecture -i dcp595cncupswrapper-1.1.2-2.i386.deb

Now with the drivers installed we can use the CUPS administration page to add the printer. To do this we open our favorite Internet browser and access the following URL: http://localhost:631.

Enter the printer administration tab and add a new printer. If your printer is already configured with an IP address in your LAN it should appear in the list of detected printers (see screenshot below):



Select the printer and in the next window you will be asked to add some details about the printer like the place, description and name. You may add anything you like or leave the defaults and go to the next screen.

In the next screen (see screenshot below) we select the printer driver to use. If you installed the DEB packages correctly then the correct driver should be selected here by default:



Simply press the "add printer" button to finish. Now your printer should be ready for printing. Go ahead the print something.

Brother DCP-595CN Scanner In Ubuntu/Kubuntu



Getting the scanner working in Kubuntu is even easier. First we need to download the SANE driver from Brothers home page. Which driver to download depends on the scanner model you have. In my case (DCP-595CN) I need the brscan3 driver. There are 32 and 64bit versions and RPM and DEB packages. For my configuration I need the brscan3 32bit DEB pacakge. Download the package for your configuration and install it using this command:

sudo aptitude install xsane sane-utils brscan3-0.2.8-1.i386.deb

In the command above I am installing some SANE packages and the brscan3 driver. Next we need to configure the scanner with the following command:

sudo brsaneconfig3 -a name=DCP-595CN model=DCP-595CN ip=192.168.11.4

Depending on the driver you installed the configuration command changes. For brscan3 we use the "brsaneconfig3" command. The name can be anything you want as long as it has no white spaces but the model and the ip must be set to the correct values. The ip of course is the IP address the printer has configured and the model is the model name of the printer. If you are not sure about the model or IP you can use the following command to query the network:

sudo brsaneconfig3 -q

This command gives a list of supported models and at the end a list of detected scanners. In my configuration at the end I get:

Devices on network
0 DCP-595CN           "DCP-595CN"         I:192.168.11.4


If you do not see this list then your machine is not able to reach the scanner on the IP level so make sure it has the IP address configured and that you can ping it.

After you have configured the scanner with the brscanconfig3 tool you are ready to scan. To test it you can run xsane and the scanner should be listed like in the screenshot below:



Select the scanner and scan something. Now you have a fully functional printer, scanner and copy machine in you Kubuntu installation.

Sunday, December 27, 2009

Synchronizing two machines

Synchronizing two machines                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                                               
  Introduction                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                                               
    The method I document here shows how I use unison in combination with                                                                                                                                                                        
    incrontab to keep some folders shared between my work and home machines in                                                                                                                                                                   
    sync almost in real time.                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                               
  Background                                                                                                                                                                                                                                   
                                                                                                                                                                                                                                                                               
    This is only a rant so you can skip this if you are in a hurry.                                                                                                                                                                              
                                                                                                                                                                                                                                                                               
    Before this setup I had a cron job with a rsync task to sync my shared                                                                                                                                                                       
    folders. The problem is that rsync is one-way sync tool and works well                                                                                                                                                                       
    when you have a master machine where you add/modify/delete files and then                                                                                                                                                                    
    rsync one or more slave machines to the master. In my case I have two                                                                                                                                                                        
    machines that are both master and changes on any of them have to be                                                                                                                                                                          
    propagated to the other.  Of course this master-master configuration can                                                                                                                                                                     
    be done with rsync but you really MUST be very careful on what order you                                                                                                                                                                     
    sync (e.g.  from A to B or from B to A) when you use the --delete switch                                                                                                                                                                     
    (see [1] and [2] fpr details). Not using the --delete switch means that no                                                                                                                                                                   
    file in those machines will ever be deleted unless you manually delete the                                                                                                                                                                   
    file from both sides.  I have been using rsync with a crob job for years                                                                                                                                                                     
    but a few months ago I was victim of the --delete switch. Looking for                                                                                                                                                                        
    alternatives I found unison that does the master-master configuration                                                                                                                                                                        
    amazingly well and at the same time I found about incrontab. Both unison                                                                                                                                                                     
    and incrontab together resulted in a very good setup that I will now                                                                                                                                                                         
    document in the rest of this post.                                                                                                                                                                                                           
                                                                                                                                                                                                                                                                               
  Two way sync of two machines (master-master backup)                                                                                                                                                                                          
                                                                                                                                                                                                                                                                               
    Unison will keep two machines in sync taking care of the --delete flag for                                                                                                                                                                   
    us and even has an option to keep several days of backups of any file it                                                                                                                                                                     
    deletes so you can check once a week if it deleted a file you did not want                                                                                                                                                                   
    deleted.                                                                                                                                                                                                                                     
                                                                                                                                                                                                                                                                               
    To install unison in (K)Ubuntu we simply do:                                                                                                                                                                                                 
                                                                                                                                                                                                                                                                               
    ; sudo aptitude install unison                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                               
    Note that unison MUST be installed in the two machines you want to keep in                                                                                                                                                                   
    sync. As additional bonus this tool works in all major operating systems                                                                                                                                                                     
    so you can sync your work and home machines even if you use Windows at                                                                                                                                                                       
    work and Linux at home.                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                                               
    Now to sync some folders between machines you issue a simple command:                                                                                                                                                                        
                                                                                                                                                                                                                                                                               
    ; unison /home/user ssh://<ipaddress>//home/user      \                                                                                                                                                                                 
    ; -path .vim -path .vimrc -path .muttrc -path .mutt   \                                                                                                                                                                                       
    ; -path Music -path Photos -path Docs                                                                                                                                                                                                         
                                                                                                                                                                                                                                                                               
    In the above example I sync my vim, mutt configuration files and my Music,                                                                                                                                                                   
    Photos and Docs folders in my home directory. Replace the ipaddress with                                                                                                                                                                     
    the address of the remote machine and note that the double slash (//)                                                                                                                                                                        
    after the ipaddress is not a typo, you MUST have two slashes there when                                                                                                                                                                      
    using absolute paths in the remote side.                                                                                                                                                                                                     
                                                                                                                                                                                                                                                                               
    This command can be executed in any of the two machines and the result                                                                                                                                                                       
    will be the same. Both of them will have the directories selected (-path)                                                                                                                                                                    
    syncrhonized to the last detail.                                                                                                                                                                                                             
                                                                                                                                                                                                                                                                               
    Since I use ssh as transport I also set up my machines to authenticate                                                                                                                                                                       
    using ssh keys and configure Kwallet [3] as my ssh passphrase manager.                                                                                                                                                                       
    This is very important if we want to automate the task via cron or incron                                                                                                                                                                    
    jobs as I show later in this post.                                                                                                                                                                                                           
                                                                                                                                                                                                                                                                               
    To simplify the synchronization task I created a small bash script that                                                                                                                                                                      
    loads my ssh key manager (keychain), executes unison to synchronize my                                                                                                                                                                       
    machines while logging everything to /tmp/unison.log. 
    <SCRIPT                                                                                                                                                                                                                                 
    < #!/usr/bin/env bash                                                                                                                                                                                                                   
    < LOGGER=/usr/bin/logger                                                                                                                                                                                                                
    < UNISON=/usr/bin/unison                                                                                                                                                                                                                
    < KEYCHAIN=/usr/bin/keychain                                                                                                                                                                                                            
    < $LOGGER -t unison -p cron.info "Unison started for user $HOME"                                                                                                                                                              
    < if [ -e /tmp/unison.lock ]                                                                                                                                                                                                            
    < then                                                                                                                                                                                                                                  
    <   $LOGGER -t unison -p cron.info "Unison already running... abort"                                                                                                                                                          
    < else                                                                                                                                                                                                                                  
    <   echo "running" > /tmp/unison.lock                                                                                                                                                                                      
    <   $KEYCHAIN id_rsa                                                                                                                                                                                                                    
    <   source $HOME/.keychain/$HOSTNAME-sh                                                                                                                                                                                                 
    <   $UNISON $HOME ssh://192.168.1.10/$HOME -batch -log -logfile /tmp/unison.log \                                                                                                                                                       
    <                           -path .vim -path .vimrc -path .muttrc -path .mutt   \                                                                                                                                                       
    <                           -path Music -path Photos -path Docs                                                                                                                                                                         
    <   [ $? -eq 0 ] && $LOGGER -t unison -p cron.info "Unison finished" || $LOGGER -t unison -p cron.info "Unison failed with error $?"                                                                        
    <   rm -f /tmp/unison.lock                                                                                                                                                                                                              
    < fi                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                               
  Using incrontab for almost real time two way file sync                                                                                                                                                                                       
                                                                                                                                                                                                                                                                               
    Now that I can sync my two machines with a simple script I wanted this to be                                                                                                                                                                 
    automated. The normal way would be using a cron job but a problem I always                                                                                                                                                                   
    had with cron was how to set the time period. Once an hour or a day or a                                                                                                                                                                     
    minute? While researching I found incrontab that is like cron but it reacts                                                                                                                                                                  
    to file system events. With it I could invoke my unison script every time a                                                                                                                                                                  
    file is created or edited that would result in almost real time sync between                                                                                                                                                                 
    my machines.                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                                               
    To install incrontab in (K)Ubuntu simply do:                                                                                                                                                                                                 
                                                                                                                                                                                                                                                                               
    ; sudo aptitude install incron                                                                                                                                                                                                                
    ; echo "username" >> /etc/incron.allow                                                                                                                                                                                        
                                                                                                                                                                                                                                                                               
    The first command installs incron and the second one adds your user name                                                                                                                                                                     
    to the list of allowed incron users. If your username is not in the                                                                                                                                                                          
    "incron.allow" file then you wont be able to use incrontab.                                                                                                                                                                        
                                                                                                                                                                                                                                                                               
    A common problem that happens when using rsync or unison from within cron                                                                                                                                                                    
    and incron is how to handle the authentication of the ssh session.                                                                                                                                                                           
    Remember that the cron and incron processes are running in a different                                                                                                                                                                       
    environment as your user so setting a key manager like ssh-agent or                                                                                                                                                                          
    keychain in the current user termial wont work. If you have ever had rsync                                                                                                                                                                   
    work perfectly from a terminal but fail with an error like: unexplained                                                                                                                                                                      
    error (code 255) then you know what I am talking about.                                                                                                                                                                                      
                                                                                                                                                                                                                                                                               
    To solve this problem you first need to set your machines to use                                                                                                                                                                             
    password-less ssh key authentication. This way you can execute remote                                                                                                                                                                        
    commands via ssh without need to input a password.                                                                                                                                                                                           
                                                                                                                                                                                                                                                                               
    Second you need to setup a keymanager like ssh-agent or keychain. I found                                                                                                                                                                    
    that keychain does a better job in keeping a single instance of itself to                                                                                                                                                                    
    handle the keys on multiple processes. So I setup keychain and configure                                                                                                                                                                     
    Kwallet [3] as the interface that asks and stores the ssh pashphrases.                                                                                                                                                                       
    With this setup Kwallet asks me to unlock it at KDE startup and from that                                                                                                                                                                    
    point on it takes care of providing authentication to all processes that                                                                                                                                                                     
    require ssh loging (incrontab included).                                                                                                                                                                                                     
                                                                                                                                                                                                                                                                               
    Once you have setup the key manager you can create an incrontab rule to                                                                                                                                                                      
    invoke the unison script (from the previous section) every time a file is                                                                                                                                                                    
    changed:                                                                                                                                                                                                                                     
                                                                                                                                                                                                                                                                               
    ; incrontab -e                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                               
    This command will open you text editor (vim) with your user's incrontab                                                                                                                                                                      
    configuration file. All you have to do is to input the following line and                                                                                                                                                                    
    save/close the editor:                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                               
    ; /home/user IN_ATTRIB,IN_DONT_FOLLOW,IN_NO_LOOP /home/user/unison.sh                                                                                                                                                                         
                                                                                                                                                                                                                                                                               
    This incrontab rule invokes "unison.sh" every time an attribute changes in                                                                                                                                                         
    a file or folder inside my home directory. The IN_ATTRIB switch may be                                                                                                                                                                       
    overkill and you may prefer to use IN_CREATE or IN_CLOSE_WRITE. For                                                                                                                                                                          
    details on incrontab switches you may read the man page:                                                                                                                                                                                     
                                                                                                                                                                                                                                                                               
    ; man 5 incrontab 

    This command will open you text editor (vim) with your user's incrontab                                                                                                                                                                      
    configuration file. All you have to do is to input the following line and                                                                                                                                                                    
    save/close the editor:                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                               
    ; /home/user IN_ATTRIB,IN_DONT_FOLLOW,IN_NO_LOOP /home/user/unison.sh                                                                                                                                                                         
                                                                                                                                                                                                                                                                               
    This incrontab rule invokes "unison.sh" every time an attribute changes in                                                                                                                                                         
    a file or folder inside my home directory. The IN_ATTRIB switch may be                                                                                                                                                                       
    overkill and you may prefer to use IN_CREATE or IN_CLOSE_WRITE. For                                                                                                                                                                          
    details on incrontab switches you may read the man page:                                                                                                                                                                                     
                                                                                                                                                                                                                                                                               
    ; man 5 incrontab                                                                                                                                                                                                                             
                                                                                                                                                                                                                                                                               
    The IN_NO_LOOP avoids the script from being invoked several times when                                                                                                                                                                       
    more than one file changes at a time. Also the script I provided uses a                                                                                                                                                                      
    simple lock mechanism to avoid it from running several times in parallel.                                                                                                                                                                    
                                                                                                                                                                                                                                                                               
    As a final note you can open /var/log/syslog and search for all entries                                                                                                                                                                      
    with the unison tag to see if the script is running correctly or failing.                                                                                                                                                                    
    If it is failing you can further chech the /tmp/unison.log file to see why                                                                                                                                                                   
    it is failing. Note that an abort is not an error, it means that then                                                                                                                                                                        
    unison script was invoked several times in succession and we only allow                                                                                                                                                                      
    one instance at any given time.                                                                                                                                                                                                              
                                                                                                                                                                                                                                                                               
  Resources                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                               
    [1] http://www.darcynorman.net/2006/10/03/be-careful-with-rsync-delete/                                                                                    
    [2] http://www.davidgrant.ca/rsync_delete_dangerous                                                                                                                            
    [3] http://piao-tech.blogspot.com/2009/12/manage-ssh-and-gpg-keys-efficiently-in.html                                                        

Thursday, December 17, 2009

Manage SSH and GPG keys efficiently in KDE

Manage SSH and GPG keys efficiently in KDE                                                                                                                                                                                                      
                                                                                                                                                                                                                                                                               
  Introduction                                                                                                                                                                                                                                 
    Here I present a simple way to handle SSH and GPG keys easily in KDE4.                                                                                                                                                                       
    These method presents several advantages:                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                               
      * Works great with KDE4 that ships with Kubuntu Maverick Meerkat and may                                                                                                                                                                    
        work in other KDE distributions without too much trouble.                                                                                                                                                                                 
      * Use Kwallet as the passphrase manager so you unlock Kwallet once on                                                                                                                                                                       
        login and from there it will handle all passphrase requests.                                                                                                                                                                              
      * Works great with automated tasks (via cron or incron) that use SSH key                                                                                                                                                                    
        authentication (e.g. famous rsync unexplained error 255).                                                                                                                                                                                 
                                                                                                                                                                                                                                                                               
  Pre-requisities                                                                                                                                                                                                                              
                                                                                                                                                                                                                                                                               
    Considering that you are reading this post because you need a better way                                                                                                                                                                     
    to manage you ssh and gpg keys then it is safe to assume you have already                                                                                                                                                                    
    generated your ssh/gpg keys and that you know how to use them.                                                                                                                                                                               
                                                                                                                                                                                                                                                                               
  Disable KDE4 from starting ssh-agent and gpp-agent                                                                                                                                                                                           
                                                                                                                                                                                                                                                                               
    Kubuntu 10.10 by default starts the ssh-agent and gpg-agent causing some                                                                                                                                                                     
    conflicts with this setup based on keychain. Using the default                                                                                                                                                                               
    configuration does not seem to use kwallet and certainly does not work                                                                                                                                                                       
    with kmail/mutt so I prefer to disable these and enable keychain instead.                                                                                                                                                                    
                                                                                                                                                                                                                                                                               
    To disable the default ssh-agent edit the "/etc/X11/Xsession.options" file                                                                                                                                                         
    and comment out the line that says use-ssh-agent.                                                                                                                                                                                            
                                                                                                                                                                                                                                                 
    To disable the default gpg-agent edit the "~/.gnupg/gpg.conf" file and                                                                                                                                                             
    comment out the line that says use-agent.                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                               
    With these default agent's disabled now we can configure KDE to use                                                                                                                                                                          
    keychain that I consider a superior tool to handle ssh/gpg keys.                                                                                                                                                                             
                                                                                                                                                                                                                                                                               
  SSH/GPG Key management with KWallet                                                                                                                                                                                                          
                                                                                                                                                                                                                                                                               
    First we install the needed software packages:                                                                                                                                                                                               
                                                                                                                                                                                                                                                                               
    ; sudo aptitude install keychain ksshaskpass kwalletcli                                                                                                                                                                                       
                                                                                                                                                                                                                                                                               
    configure the "~/.gnupg/gpg-agent.conf" file so it uses the kwallet                                                                                                                                                                
    pinentry program to manage gpg keys. Simply add the pinentry-program line                                                                                                                                                                    
    or replace it if it already exists with:                                                                                                                                                                                                     
                                                                                                                                                                                                                                                                               
    ; pinentry-program /usr/bin/pinentry-kwallet                                                                                                                                                                                                  
                                                                                                                                                                                                                                                                               
    Now we need to load keychain and all the environment variables it sets                                                                                                                                                                       
    when KDE starts. To do this we simply create a small script, say                                                                                                                                                                             
    "keychain.sh" and put it inside out ".kde/env" directory. The script                                                                                                                                                     
    contains these lines:                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                               
    <SCRIPT                                                                                                                                                                                                                                 
    < #!/bin/sh                                                                                                                                                                                                                             
    < ################################################################################                                                                                                                                                      
    < # Load keychain to handle ssh and gpg keys                                                                                                                                                                                            
    < ################################################################################                                                                                                                                                      
    < if [ -f /usr/bin/keychain ]; then                                                                                                                                                                                                     
    <   if [ -f /usr/bin/ksshaskpass ]; then                                                                                                                                                                                                
    <     export SSH_ASKPASS=/usr/bin/ksshaskpass                                                                                                                                                                                           
    <   else                                                                                                                                                                                                                                
    <     export SSH_ASKPASS=/usr/bin/askpass                                                                                                                                                                                               
    <   fi                                                                                                                                                                                                                                  
    <   /usr/bin/keychain                                                                                                                                                                                                                   
    <   $HOME/.keychain/`hostname`-sh                                                                                                                                                                                                       
    <   $HOME/.keychain/`hostname`-sh-gpg                                                                                                                                                                                                   
    < fi                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                               
    what this does is to setup the SSH_ASKPASS environment variable to use the                                                                                                                                                                   
    ksshaskpass program that handles ssh keys inside KWallet. Then invokes                                                                                                                                                                       
    keychain which starts the ssh-agent and gpg-agent daemons and sets some                                                                                                                                                                      
    environment variables so all KDE applications can see them.                                                                                                                                                                                  
                                                                                                                                                                                                                                                                               
    Finally we must load our ssh/gpg keys into keychain. The best place to do                                                                                                                                                                    
    this is with the KDE Autostart scripts. Simply create a script, say                                                                                                                                                                          
    add_keys.sh, into you ".kde/Autostart" folder that contains something                                                                                                                                                              
    like: 
                                                                                                                                                                                                                                                                               
    <SCRIPT                                                                                                                                                                                                                                 
    < #!/bin/sh                                                                                                                                                                                                                             
    < ################################################################################                                                                                                                                                      
    < # Load keychain to handle ssh and gpg keys                                                                                                                                                                                            
    < ################################################################################                                                                                                                                                      
    < if [ -f /usr/bin/keychain ]; then                                                                                                                                                                                                     
    <   if [ -f /usr/bin/ksshaskpass ]; then                                                                                                                                                                                                
    <     export SSH_ASKPASS=/usr/bin/ksshaskpass                                                                                                                                                                                           
    <   else                                                                                                                                                                                                                                
    <     export SSH_ASKPASS=/usr/bin/askpass                                                                                                                                                                                               
    <   fi                                                                                                                                                                                                                                  
    <   /usr/bin/keychain                                                                                                                                                                                                                   
    <   $HOME/.keychain/`hostname`-sh                                                                                                                                                                                                       
    <   $HOME/.keychain/`hostname`-sh-gpg                                                                                                                                                                                                   
    < fi                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                               
    what this does is to setup the SSH_ASKPASS environment variable to use the                                                                                                                                                                   
    ksshaskpass program that handles ssh keys inside KWallet. Then invokes                                                                                                                                                                       
    keychain which starts the ssh-agent and gpg-agent daemons and sets some                                                                                                                                                                      
    environment variables so all KDE applications can see them.                                                                                                                                                                                  
                                                                                                                                                                                                                                                                               
    Finally we must load our ssh/gpg keys into keychain. The best place to do                                                                                                                                                                    
    this is with the KDE Autostart scripts. Simply create a script, say                                                                                                                                                                          
    add_keys.sh, into you ".kde/Autostart" folder that contains something                                                                                                                                                              
    like:                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                               
    <SCRIPT                                                                                                                                                                                                                                 
    < #!/bin/sh                                                                                                                                                                                                                             
    < ################################################################################                                                                                                                                                      
    < # Load keychain to handle ssh and gpg keys                                                                                                                                                                                            
    < ################################################################################                                                                                                                                                      
    < if [ -f /usr/bin/keychain ]; then                                                                                                                                                                                                     
    <  /usr/bin/keychain id_rsa 0x12345 0x23456                                                                                                                                                                                             
    <  $HOME/.keychain/`hostname`-sh                                                                                                                                                                                                        
    <  $HOME/.keychain/`hostname`-sh-gpg                                                                                                                                                                                                    
    < fi                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                               
    what this Autostart script does is to load your ssh key (id_rsa) and gpg                                                                                                                                                                     
    keys (0x12345, 0x23456) into keychain. The next time you log into a KDE                                                                                                                                                                      
    session this script will ask you if you want to give keychain access to                                                                                                                                                                      
    Kwallet and then ask all the registered key passphrases. Once registered                                                                                                                                                                     
    with kwallet all your applications will be able to use these keys without                                                                                                                                                                    
    asking you for the passphrase each time.                                                                                                                                                                                                     
                                                                                                                                                                                                                                                                               
    Make sure the env and Autostart scripts have exec privileges:                                                                                                                                                                                
                                                                                                                                                                                                                                                                               
    ; chmod +x ~/.kde/env/keychain.sh                                                                                                                                                                                                             
    ; chmod +x ~/.kde/Autostart/add_key.sh