Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
205 lines (160 sloc) 5.33 KB
#!/usr/bin/perl -w
# Copyright (C) 2009 Bruce Pennypacker
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
use Mac::Growl;
use IO::Socket;
use Digest::MD5 qw(md5_base64);
use Getopt::Long;
use File::Basename;
use Crypt::CBCeasy;
use MIME::Base64;
my $options_file = ".growl-server";
my %option;
my %registered;
my $progname = basename ($0);
my $override_config;
my $imagefile;
sub Usage {
print <<EOT;
Usage: $progname [-f config_file] [-h host] [-o port] [-p passphrase]
Start the Growl server daemon which listens for messages from either
the command line utility or the Irssi script.
Default parameters are read from ~/$options_file. Any other command line
parameters override values read from the configuraiton file.
-f : Override ~/$options_file with a different configuration file
-h : Specify the hostname/IP to bind to. Defaults to localhost.
-o : Specify the port to listen on. Defaults to 7106.
-p : Specify the passphrase used for validating incoming messages.
The same passphrase must be used by both the client and server.
sub read_config {
my $config = shift;
open(OPTS, $config) || die "Cannot open $config\n";
while (<OPTS>) {
next if ( m/^#/);
my ($key, $value) = split('=');
next unless (defined($value));
$option{$key} = $value;
if (@ARGV) {
Usage if ($ARGV[0] eq "-?");
Usage if ($ARGV[0] eq "--help");
# See if the user wants to override the default config file
GetOptions("config|f=s", \$override_config);
# Read the appropriate config file
if (defined($override_config)) {
unless (-f $override_config) {
print "Error: configuration file $override_config not found\n";
$options_file = $override_config;
} else {
$options_file = $ENV{'HOME'} . "/" . $options_file;
# Now read the command line options to override the config file defaults
GetOptions ( \%option,
unless (defined($option{port})) {
print "Error: port not specified\n";
unless (defined($option{passphrase})) {
print "Error: passphrase not specified\n";
if (defined($option{debug})) {
print "default settings read from $options_file\n";
print "listen on " . $option{host} . "\n";
print "listening on port " . $option{port} . "\n";
my $sock = new IO::Socket::INET (
LocalHost => $option{host},
LocalPort => $option{port},
Proto => 'tcp',
Listen => 10,
Reuse => 1
die "Error creating socket: $!\n" unless $sock;
while (my $accept = $sock->accept()) {
while (my $rawdata = <$accept>) {
chomp ($rawdata);
if (defined($option{blowfish_key})) {
$rawdata = decode_base64($rawdata);
$rawdata =~ s/\s*$//;
$rawdata = Blowfish::decipher($option{blowfish_key}, scalar($rawdata));
my ($cksum, $data) = split('\|', $rawdata, 2);
unless ($cksum eq md5_base64($data, $option{passphrase})) {
print "checksum failure [$cksum] [$data]\n";
my ($appname, $types, $type, $header, $msg, $cmd, $image) =
split('\|', $data);
if (defined($option{debug})) {
print "\n";
print "App name: \"$appname\"\n";
print "Types : \"$types\"\n";
print "Type : \"$type\"\n";
print "Header : \"$header\"\n";
print "Message : \"$msg\"\n";
print "Command : \"$cmd\"\n";
print "Image : \"$image\"\n";
my $sticky = 0;
if ($cmd eq "sticky") {
$sticky = 1;
} elsif ($cmd ne "notify") {
print "Unrecognized command [$cmd]\n";
if (length($image)) {
$imagefile = $option{"image.$image"};
if (defined($imagefile)) {
if (defined($option{debug})) {
print "Image file : \"$imagefile\"\n";
unless (-f $imagefile) {
print "warning: $imagefile not found\n";
$imagefile = undef;
} else { # defined ($imagefile)
print "warning: image.$image not defined\n";
} # length($image)
unless ($registered{$appname}) {
my @typelist = split(',', $types);
Mac::Growl::RegisterNotifications($appname, \@typelist, \@typelist);
$registered{$appname} = 1;
Mac::Growl::PostNotification($appname, $type, $header, $msg, $sticky, "0", $imagefile);
} # <accept>
} # sock->accept
close ($sock);
Something went wrong with that request. Please try again.