#!/usr/bin/perl -w ####################################################################### # $Id: snowsync 491 2004-12-08 12:07:50Z kiza $ # # Snownews - A lightweight console RSS newsreader # # Copyright 2003-2004 Oliver Feiler # http://snownews.kcore.de/ # # snowsync # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ####################################################################### use strict; use English; use Fcntl':flock'; use File::Path; use LWP::UserAgent; # Uploading use LWP::Simple; # Downloading use HTTP::Request; # Upload server response checking use HTTP::Response; # Upload server response checking use File::Copy; ####################################################################### my $fileversion = "version1"; my $debug = 0; my $storage_server; my $storage_username; my $storage_password; my $storage_PGP_pass; my $sync_type; my $sync_error = undef; umask 077; $| = 1; my $snowsync_version = 1; my $snownews_version = `snownews -V`; $snownews_version = (split(/ /,$snownews_version))[2]; chomp($snownews_version); my $sysuser = $ENV{'USER'}; my $homedir = $ENV{'HOME'}; my $tmpdir = "/tmp/snowsync-".$ENV{'USER'}.".$PID.".time(); # Default to Snownews my $dot_dir = "$homedir/.snownews"; my $config_loc = "$dot_dir/snowsync.conf"; my $log_loc = "$dot_dir/snowsync.log"; my $main_prog_name = "snownews"; if ($PROGRAM_NAME =~ /lifereasync/) { # Liferea $dot_dir = "$homedir/.liferea"; $config_loc = "$dot_dir/liferea_sync.conf"; $log_loc = "$dot_dir/liferea_sync.log"; $main_prog_name = "liferea"; } if ($ARGV[0]) { if ($ARGV[0] =~ /-h/) { printhelp(); } elsif ($ARGV[0] eq "remote") { $sync_type = "remote"; } elsif ($ARGV[0] eq "local") { $sync_type = "local"; } elsif ($ARGV[0] eq "delete") { $sync_type = "delete"; } else { printhelp(); } } else { printhelp(); } if ($PROGRAM_NAME =~ /snowsync/) { # We don't need this if running as lifereasync checkRunning(); } loadConfig(); if ($sync_type eq "remote") { syncRemote(); } elsif ($sync_type eq "local") { syncLocal(); } elsif ($sync_type eq "delete") { syncDelete(); } logRun(); cleanUp(); exit(0); ####################################################################### # See if snownews is running. Works with snownews >= 1.5.6 sub checkRunning { my($pid_file) = $ENV{'HOME'}."/.snownews/pid"; open (PID, "$pid_file") or return; my($my_pid) = ; close (PID); print "Snownews seems to be running at PID $my_pid.\n". "You should quit snownews before running snowsync. Synced data may be lost\n". "otherwise.\n\nSync anyway (yes/no)? "; if ( ne "yes\n") { print "Aborted.\n"; exit(0); } } ####################################################################### # Configuration. If no config exists create a new one. sub loadConfig { if (!open (CONFIG, "$config_loc")) { createConfig(); } else { while () { if (/^[\[\n#]/) { next; } chomp; if (/^version/) { $fileversion = $_; next; } /(.*?)\s+(.*)$/; if ($1 eq "storage_server") { $storage_server = $2; } elsif ($1 eq "http_username") { $storage_username = $2; } elsif ($1 eq "http_password") { $storage_password = $2; } elsif ($1 eq "PGP_password") { $storage_PGP_pass = $2; } } close (CONFIG); } } sub createConfig { my $input; if ($PROGRAM_NAME =~ /lifereasync/) { # Interactive mode does not work with Liferea print "Config creation error.\n"; exit(1); } print "Snowsync initial configuration.\n". "===============================\n\n"; print "Storage server path (http://kiza.kcore.de/software/snownews/snowsync)? "; $storage_server = ; chomp($storage_server); if ($storage_server eq "") { $storage_server = "http://kiza.kcore.de/software/snownews/snowsync"; } print "-> $storage_server\n\n"; print "Storage server username? "; $storage_username = ; chomp($storage_username); print "-> $storage_username\n\n"; print "Storage server password? "; $storage_password = ; chomp($storage_password); print "-> $storage_password\n\n"; print "PGP encryption password? "; $storage_PGP_pass = ; chomp($storage_PGP_pass); print "-> $storage_PGP_pass\n\n"; print "Storage server: $storage_server\n". "Username: $storage_username\n". "Password: $storage_password\n". "PGP password: $storage_PGP_pass\n". "OK (yes/no)? "; if ( ne "yes\n") { print "Aborted.\n"; exit(0); } print "\n\nWriting file $config_loc...\n"; open (CONFIG, ">$config_loc"); print CONFIG "version1\n\n"; print CONFIG "[Sync server access]\n". "storage_server\t$storage_server\n". "http_username\t$storage_username\n". "http_password\t$storage_password\n". "\n"; print CONFIG "[Encryption]\n". "PGP_password\t$storage_PGP_pass\n". "\n"; close (CONFIG); print "Config created in $config_loc.\n\n"; } # Unused sub getDefaultPGPKey { my $gpg_path = `which gpg`; chomp($gpg_path); open (GPG, "$gpg_path --list-secret-keys|"); while () { if (/^sec/) { /sec\s+\d{4}D\/(.*?) /; return "0x$1"; } } close (GPG); } # Create the tar to upload to the server sub makePkg { mkdir $tmpdir or die "Could not create working directory $tmpdir: $!"; my $tmpfile = "$tmpdir/snowsync.tar"; my $target = "$tmpfile.gpg"; print "Creating $tmpfile...\n"; if ($PROGRAM_NAME =~ /snowsync/) { mkpath ("$tmpdir/.snownews/cache", $debug) or die "Could not create working directories under $tmpdir: $1\n"; copy ("$dot_dir/urls", "$tmpdir/.snownews/") or die "$1"; opendir (CACHE, "$dot_dir/cache"); my @files = readdir CACHE; closedir (CACHE); makeSimplifiedCache(\@files, $tmpdir); # my @files = glob "$dot_dir/cache/*"; # foreach (@files) { # copy ($_, "$tmpdir/.snownews/cache/") or die "$!"; # } # Ugh, wtf can't GnuPG read password from cmd line? # This whole read-from-fd thing is annoying, plus I have no idea # how to do it. Need to split tar and gpg runs. system ("cd /$tmpdir/; tar cf $tmpfile .snownews/ 2>/dev/null"); system ("echo '$storage_PGP_pass' | gpg --passphrase-fd 0 -q -c -o $target $tmpfile 2>/dev/null"); print "OK.\n"; } elsif ($PROGRAM_NAME =~ /lifereasync/) { mkpath ("$tmpdir/.liferea", $debug) or die "Could not create working directories under $tmpdir: $1\n"; copy ("$dot_dir/feedlist.opml", "$tmpdir/.liferea/") or die "$1"; # Add more files here if needed. system ("cd /$tmpdir/; tar cf $tmpfile .liferea/ 2>/dev/null"); system ("echo '$storage_PGP_pass' | gpg --passphrase-fd 0 -q -c -o $target $tmpfile 2>/dev/null"); print "OK.\n"; } else { unknownProgName(); } return $target; } # Create cache without description sub makeSimplifiedCache { my $files = shift; my $tmpdir = shift; use XML::LibXML; use XML::LibXSLT; my $xslt_stylesheet = q{ }; my $parser = XML::LibXML->new(); my $xslt = XML::LibXSLT->new(); $parser->validation(0); # Turn off validation from libxml $parser->recover(1); # Use recovery mode my $style_doc = $parser->parse_string($xslt_stylesheet); my $stylesheet = $xslt->parse_stylesheet($style_doc); foreach (@$files) { if (/^\.+/) { # Filter directory index files "." ".." next; } my $result = $stylesheet->transform_file("$dot_dir/cache/$_"); $stylesheet->output_file($result, "$tmpdir/.snownews/cache/$_"); } } sub syncRemote { my $target = makePkg(); # Workaround. Something like mmap would be cool. open (FILE, "$target"); my $target_contents = join("", ); close (FILE); print "Sending file to storage server... "; my $ua = LWP::UserAgent->new(); my $req = HTTP::Request->new('POST', "$storage_server/nwstorage.cgi?mode=post&resource=$storage_server/data/$storage_username/$main_prog_name.pgp", undef, $target_contents); my $uri = URI->new($storage_server); my $hostname = $uri->host_port; $ua->credentials($hostname, 'Snowsync', $storage_username, $storage_password); my $resp = $ua->request($req); print "OK.\n"; if ($resp->code() == 201) { print "Server reply: 201 Created... OK.\n\n"; # print $resp->content()."\n"; } else { print "Sending failed. Server reply below.\n\n"; print $resp->as_string()."\n\n"; print "Please report this problem to the storage server admin.\n\n"; $sync_error = "Sync failed. Storage server error (HTTP ".$resp->code().")"; } print "Sync done.\n"; } sub syncLocal { mkdir $tmpdir or die "Could not create working directory $tmpdir: $!"; print "Fetching file from storage server... "; my $target = "$tmpdir/snowsync.tar.gpg"; my $status = getstore("$storage_server/data/$storage_username/$main_prog_name.pgp", $target); if (is_success($status)) { print "OK\n"; print "Extracting archive...\n"; system ("cd $homedir; echo '$storage_PGP_pass' | gpg --passphrase-fd 0 -q -d $target 2>/dev/null | tar xf - 2>/dev/null"); print "OK.\n"; } else { print "FAILED!\n"; print "Server status code: $status\n"; print "Please report this problem to the storage server admin.\n\n"; $sync_error = "Sync failed. Storage server error (HTTP $status)"; } } sub cleanUp { print "Cleaning up...\n"; rmtree ($tmpdir, $debug); } sub syncDelete { print "Deleting remote file... "; my $ua = LWP::UserAgent->new(); my $req = HTTP::Request->new('POST', "$storage_server/nwstorage.cgi?mode=delete&resource=$storage_server/data/$storage_username/$main_prog_name.pgp", undef, undef); my $uri = URI->new($storage_server); my $hostname = $uri->host_port; $ua->credentials($hostname, 'Snowsync', $storage_username, $storage_password); my $resp = $ua->request($req); if ($resp->code() == 200) { print "OK.\n"; } else { print "\n\nDeleting failed.\n\n"; print $resp->as_string()."\n\n"; $sync_error = "Deleting of sync data failed (HTTP ".$resp->code().")"; } } ####################################################################### # Logging sub print_timestring { my($sec,$min,$std,$mtag,$mon,$jahr,$wtag,$jtag,$somzeit) = localtime($_[0]); $jahr += 1900; $mon++; $mon = sprintf("%02d", $mon % 100); $mtag = sprintf("%02d", $mtag % 100); $std = sprintf("%02d", $std % 100); $min = sprintf("%02d", $min % 100); $sec = sprintf("%02d", $sec % 100); return "$jahr-$mon-$mtag, $std:$min:$sec"; } sub logRun { open (LOG, ">>$log_loc"); flock (LOG, LOCK_EX); print LOG print_timestring(time); if (defined($sync_error)) { print LOG ": $sync_type sync with $storage_server failed: $sync_error.\n"; } else { print LOG ": $sync_type sync with $storage_server successful.\n"; } flock (LOG, LOCK_UN); close (LOG); } sub printhelp { if ($PROGRAM_NAME =~ /snowsync/) { print "Snowsync options\n\n"; } elsif ($PROGRAM_NAME =~ /lifereasync/) { print "Lifereasync options\n\n"; } else { unknownProgName(); } print "\"snowsync local\" Sync local configuration FROM server.\n". "\"snowsync remote\" Send local configuration TO server.\n". "\"snowsync delete\" Delete remote data.\n\n"; exit(0); } sub unknownProgName { print "The script must be named either snowsync or lifereasync!\n"; exit(1); }