usage(0) if $CONFIG{help};
-my $CONFFILE = delete $CONFIG{config} // 'imapsync';
-my $CACHEDIR = './imapsync.cache'; # XXX use a config option
-my $DBFILE = "$CACHEDIR/imap.guilhem.org.db";
-my $LOCKFILE = "$CACHEDIR/.imap.guilhem.org.lck";
+my $CONF = read_config( delete $CONFIG{config} // $NAME
+ , [qw/_ local remote/]
+ , database => qr/\A(\P{Control}+)\z/ );
+my ($DBFILE, $LOCKFILE);
+
+{
+ $DBFILE = $CONF->{_}->{database} if defined $CONF->{_};
+ $DBFILE //= $CONF->{remote}->{host}.'.db' if defined $CONF->{remote};
+ $DBFILE //= $CONF->{local}->{host}. '.db' if defined $CONF->{local};
+ die "Missing option database" unless defined $DBFILE;
+
+ unless ($DBFILE =~ /\A\//) {
+ my $dir = ($ENV{XDG_DATA_HOME} // "$ENV{HOME}/.local/share") .'/'. $NAME;
+ $dir =~ /\A(\/\p{Print}+)\z/ or die "Insecure $dir";
+ $dir = $1;
+ $DBFILE = $dir .'/'. $DBFILE;
+ unless (-d $dir) {
+ mkdir $dir, 0700 or die "Cannot mkdir $dir: $!\n";
+ }
+ }
+
+ $LOCKFILE = $DBFILE =~ s/([^\/]+)\z/.$1.lck/r;
+}
my ($DBH, $IMAP);
#############################################################################
# Lock the database
{
- if (!-d $CACHEDIR) {
- mkdir $CACHEDIR, 0700 or die "Cannot mkdir $CACHEDIR: $!\n";
- }
- elsif (-f $LOCKFILE) {
+ if (-f $LOCKFILE) {
open my $lock, '<', $LOCKFILE or die "Cannot open $LOCKFILE: $!\n";
my $pid = <$lock>;
close $lock;
# Connect to the local and remote IMAP servers
foreach my $name (qw/local remote/) {
- my %config = Net::IMAP::Sync::read_config($CONFFILE, $name);
+ my %config = %{$CONF->{$name}};
$config{$_} = $CONFIG{$_} foreach keys %CONFIG;
$config{enable} = 'QRESYNC';
$config{name} = $name;
#############################################################################
# Utilities
-# read_config($conffile, $section, %opts)
-# Read $conffile's default section, then $section (which takes
-# precedence). %opts extends %OPTIONS and maps each option to a
-# regexp validating its values.
+# read_config($conffile, $sections, %opts)
+# Read $conffile's default section, then each section in the array
+# reference $section (which takes precedence). %opts extends %OPTIONS
+# and maps each option to a regexp validating its values.
sub read_config($$%) {
my $conffile = shift;
- my $section = shift;
+ my $sections = shift;
my %opts = (%OPTIONS, @_);
$conffile = ($ENV{XDG_CONFIG_HOME} // "$ENV{HOME}/.config") .'/'. $conffile
unless defined $conffile and -f $conffile and -r $conffile;
my $h = Config::Tiny::->read($conffile);
- die "No such section $section\n" unless defined $h->{$section};
-
- my $conf = $h->{_}; # default section
- $conf->{$_} = $h->{$section}->{$_} foreach keys %{$h->{$section}};
-
- # default values
- $conf->{type} //= 'imaps';
- $conf->{host} //= 'localhost';
- $conf->{port} //= $conf->{type} eq 'imaps' ? 993 : $conf->{type} eq 'imap' ? 143 : undef;
- $conf->{auth} //= 'PLAIN LOGIN';
- $conf->{STARTTLS} //= 'TRUE';
-
- # untaint and validate the config
- foreach my $k (keys %$conf) {
- die "Invalid option $k\n" unless defined $opts{$k};
- next unless defined $conf->{$k};
- die "Invalid option $k = $conf->{$k}\n" unless $conf->{$k} =~ $opts{$k};
- $conf->{$k} = $1;
+
+ my %configs;
+ foreach my $section (@$sections) {
+ my $conf = { %{$h->{_}} }; # default section
+ $configs{$section} = $conf;
+ next unless defined $section and $section ne '_';
+
+ die "No such section $section\n" unless defined $h->{$section};
+ $conf->{$_} = $h->{$section}->{$_} foreach keys %{$h->{$section}};
+
+ # default values
+ $conf->{type} //= 'imaps';
+ $conf->{host} //= 'localhost';
+ $conf->{port} //= $conf->{type} eq 'imaps' ? 993 : $conf->{type} eq 'imap' ? 143 : undef;
+ $conf->{auth} //= 'PLAIN LOGIN';
+ $conf->{STARTTLS} //= 'TRUE';
+
+ # untaint and validate the config
+ foreach my $k (keys %$conf) {
+ die "Invalid option $k\n" unless defined $opts{$k};
+ next unless defined $conf->{$k};
+ die "Invalid option $k = $conf->{$k}\n" unless $conf->{$k} =~ $opts{$k};
+ $conf->{$k} = $1;
+ }
}
- return %$conf;
+ return \%configs;
}