From dd7edb8eac0c11fb8168f5028c8b6d8706cc8fdb Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Sun, 20 Jan 2019 19:55:33 +0100 Subject: pullimap, interimap: Use $XDG_CONFIG_HOME/$NAME/config as config file. --- Changelog | 4 ++++ README | 2 +- interimap | 35 +++++++++++++++-------------------- interimap.md | 10 +++++----- lib/Net/IMAP/InterIMAP.pm | 36 +++++++++++++++++++++++++++++++----- pullimap | 35 +++++++++++++++-------------------- pullimap.md | 10 +++++----- 7 files changed, 76 insertions(+), 56 deletions(-) diff --git a/Changelog b/Changelog index 7939fa2..7f033c8 100644 --- a/Changelog +++ b/Changelog @@ -3,6 +3,10 @@ interimap (0.4) UNRELEASED * pullimap: replace non RFC 5321-compliant envelope sender addresses (received by the IMAP FETCH ENVELOPE command) with the null address <>. + * pullimap, interimap: take configuration filename (default: "config") + relative to $XDG_CONFIG_HOME/$NAME (or ~/.config/$NAME), to comply + with the XDG specification. Thus the previous default config file + $XDG_CONFIG_HOME/$NAME should become $XDG_CONFIG_HOME/$NAME/config. + Library: new API idle_start() and idle_stop(). + Add support for untagged ESEARCH responses from RFC 4731. + pullimap: Use extended SEARCH commands (RFC 4731) if supported by diff --git a/README b/README index 6c3ae34..70bf1df 100644 --- a/README +++ b/README @@ -52,7 +52,7 @@ the AUTHENTICATE command. For instance the following configuration snippet saves bandwidth and brings a significant speed gain compared to type=imaps. - local: $XDG_CONFIG_HOME/interimap: + local: $XDG_CONFIG_HOME/interimap/config: [remote] type = tunnel command = /usr/bin/ssh user@imap.example.net diff --git a/interimap b/interimap index 049b564..0f34bf9 100755 --- a/interimap +++ b/interimap @@ -30,7 +30,7 @@ use Fcntl qw/F_GETFD F_SETFD FD_CLOEXEC/; use List::Util 'first'; use lib 'lib'; -use Net::IMAP::InterIMAP qw/read_config compact_set/; +use Net::IMAP::InterIMAP qw/xdg_basedir read_config compact_set/; # Clean up PATH $ENV{PATH} = join ':', qw{/usr/bin /bin}; @@ -63,20 +63,24 @@ my $COMMAND = do { }; usage(1) if defined $COMMAND and (($COMMAND eq 'delete' and !@ARGV) or ($COMMAND eq 'rename' and $#ARGV != 1)); usage(1) if defined $COMMAND and (defined $CONFIG{watch} or defined $CONFIG{notify}); -usage(1) if $CONFIG{target} and !(defined $COMMAND and ($COMMAND eq 'delete'or $COMMAND eq 'rename')); +usage(1) if $CONFIG{target} and !(defined $COMMAND and ($COMMAND eq 'delete' or $COMMAND eq 'rename')); $CONFIG{watch} = $CONFIG{notify} ? 900 : 60 if (defined $CONFIG{watch} or $CONFIG{notify}) and !$CONFIG{watch}; @ARGV = map {uc $_ eq 'INBOX' ? 'INBOX' : $_ } @ARGV; # INBOX is case-insensitive die "Invalid mailbox name $_" foreach grep !/\A([\x01-\x7F]+)\z/, @ARGV; -my $CONF = read_config( delete $CONFIG{config} // $NAME - , [qw/_ local remote/] - , database => qr/\A(\P{Control}+)\z/ - , logfile => qr/\A(\/\P{Control}+)\z/ - , 'list-mailbox' => qr/\A([\x01-\x09\x0B\x0C\x0E-\x7F]+)\z/ - , 'list-select-opts' => qr/\A([\x21\x23\x24\x26\x27\x2B-\x5B\x5E-\x7A\x7C-\x7E]+)\z/ - , 'ignore-mailbox' => qr/\A([\x01-\x09\x0B\x0C\x0E-\x7F]+)\z/ - ); +my $CONF = do { + my $conffile = delete($CONFIG{config}) // "config"; + $conffile = xdg_basedir( XDG_CONFIG_HOME => ".config", $NAME, $conffile ); + read_config( $conffile + , [qw/_ local remote/] + , database => qr/\A(\P{Control}+)\z/ + , logfile => qr/\A(\/\P{Control}+)\z/ + , 'list-mailbox' => qr/\A([\x01-\x09\x0B\x0C\x0E-\x7F]+)\z/ + , 'list-select-opts' => qr/\A([\x21\x23\x24\x26\x27\x2B-\x5B\x5E-\x7A\x7C-\x7E]+)\z/ + , 'ignore-mailbox' => qr/\A([\x01-\x09\x0B\x0C\x0E-\x7F]+)\z/ + ); +}; my ($DBFILE, $LOGGER_FD); { @@ -84,16 +88,7 @@ my ($DBFILE, $LOGGER_FD); $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 "Can't mkdir $dir: $!\n"; - } - } + $DBFILE = xdg_basedir( XDG_DATA_HOME => ".local/share", $NAME, $DBFILE ); if (defined $CONF->{_} and defined $CONF->{_}->{logfile}) { require 'POSIX.pm'; diff --git a/interimap.md b/interimap.md index 4a321f1..ca83dba 100644 --- a/interimap.md +++ b/interimap.md @@ -140,8 +140,8 @@ Options `--config=`*FILE* : Specify an alternate [configuration file](#configuration-file). - Relative paths start from *$XDG_CONFIG_HOME*, or *~/.config* if the - `XDG_CONFIG_HOME` environment variable is unset. + Relative paths start from *$XDG_CONFIG_HOME/interimap*, or *~/.config/interimap* + if the `XDG_CONFIG_HOME` environment variable is unset. `--target={local,remote,database}` @@ -192,9 +192,9 @@ Configuration file ================== Unless told otherwise by the `--config=FILE` command-line option, -`interimap` reads its configuration from *$XDG_CONFIG_HOME/interimap* -(or *~/.config/interimap* if the `XDG_CONFIG_HOME` environment variable -is unset) as an [INI file]. +`interimap` reads its configuration from *$XDG_CONFIG_HOME/interimap/config* +(or *~/.config/interimap/config* if the `XDG_CONFIG_HOME` environment +variable is unset) as an [INI file]. The syntax of the configuration file is a series of `OPTION=VALUE` lines organized under some `[SECTION]`; lines starting with a ‘#’ or ‘;’ character are ignored as comments. diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm index 67b3ce5..7b0a2be 100644 --- a/lib/Net/IMAP/InterIMAP.pm +++ b/lib/Net/IMAP/InterIMAP.pm @@ -22,7 +22,7 @@ use strict; use Compress::Raw::Zlib qw/Z_OK Z_FULL_FLUSH Z_SYNC_FLUSH MAX_WBITS/; use Config::Tiny (); -use Errno 'EINTR'; +use Errno qw/EEXIST EINTR/; use Fcntl qw/F_GETFD F_SETFD FD_CLOEXEC/; use Net::SSLeay (); use List::Util qw/all first/; @@ -35,7 +35,7 @@ BEGIN { Net::SSLeay::SSLeay_add_ssl_algorithms(); Net::SSLeay::randomize(); - our @EXPORT_OK = qw/read_config compact_set $IMAP_text $IMAP_cond + our @EXPORT_OK = qw/xdg_basedir read_config compact_set $IMAP_text $IMAP_cond slurp is_dirty has_new_mails/; } @@ -76,6 +76,35 @@ my $CRLF = "\x0D\x0A"; ############################################################################# # Utilities +# xdg_basedir($xdg_variable, $default, $subdir, $path) +# Return $path if $path is absolute. Otherwise, return +# "$ENV{$xdg_variable}/$subdir/$path" (resp. "~/$default/$subdir/path" +# if the "$xdg_variable" environment variable is not set). +# An error is raised if "$ENV{$xdg_variable}" (resp. "~/$default") is +# not an existing absolute directory. +# If "$ENV{$xdg_variable}/$subdir" doesn't exist, it is created with +# mode 0700. +sub xdg_basedir($$$$) { + my ($xdg_variable, $default, $subdir, $path) = @_; + $path =~ /\A(\p{Print}+)\z/ or die "Insecure $path"; + $path = $1; + return $path if $path =~ /\A\//; + + my $basedir = $ENV{$xdg_variable}; + unless (defined $basedir) { + my @getent = getpwuid($>); + $basedir = $getent[7] ."/". $default; + } + die "No such directory: ", $basedir unless -d $basedir; + $basedir .= "/".$subdir; + $basedir =~ /\A(\/\p{Print}+)\z/ or die "Insecure $basedir"; + $basedir = $1; + unless (mkdir ($basedir, 0700)) { + die "Couldn't create $basedir: $!\n" unless $! == EEXIST; + } + return $basedir ."/". $path; +} + # read_config($conffile, $sections, %opts) # Read $conffile's default section, then each section in the array # reference $section (which takes precedence). %opts extends %OPTIONS @@ -85,9 +114,6 @@ sub read_config($$%) { my $sections = shift; my %opts = (%OPTIONS, @_); - $conffile = ($ENV{XDG_CONFIG_HOME} // "$ENV{HOME}/.config") .'/'. $conffile - unless $conffile =~ /\A\//; # relative path - die "No such config file $conffile\n" unless defined $conffile and -f $conffile and -r $conffile; diff --git a/pullimap b/pullimap index 736bbff..e666114 100755 --- a/pullimap +++ b/pullimap @@ -31,7 +31,7 @@ use List::Util 'first'; use Socket qw/PF_INET PF_INET6 SOCK_STREAM/; use lib 'lib'; -use Net::IMAP::InterIMAP qw/read_config compact_set/; +use Net::IMAP::InterIMAP qw/xdg_basedir read_config compact_set/; # Clean up PATH $ENV{PATH} = join ':', qw{/usr/bin /bin}; @@ -59,15 +59,19 @@ usage(1) unless $#ARGV == 0 and $ARGV[0] ne '_'; ####################################################################### # Read and validate configuration # -my $CONF = read_config( delete $CONFIG{config} // $NAME, - , [$ARGV[0]] - , statefile => qr/\A(\P{Control}+)\z/ - , mailbox => qr/\A([\x01-\x7F]+)\z/ - , 'deliver-method' => qr/\A([ls]mtp:\[.*\]:\d+)\z/ - , 'deliver-ehlo' => qr/\A(\P{Control}+)\z/ - , 'deliver-rcpt' => qr/\A(\P{Control}+)\z/ - , 'purge-after' => qr/\A(\d*)\z/ - )->{$ARGV[0]}; +my $CONF = do { + my $conffile = delete($CONFIG{config}) // "config"; + $conffile = xdg_basedir( XDG_CONFIG_HOME => ".config", $NAME, $conffile ); + read_config( $conffile + , [$ARGV[0]] + , statefile => qr/\A(\P{Control}+)\z/ + , mailbox => qr/\A([\x01-\x7F]+)\z/ + , 'deliver-method' => qr/\A([ls]mtp:\[.*\]:\d+)\z/ + , 'deliver-ehlo' => qr/\A(\P{Control}+)\z/ + , 'deliver-rcpt' => qr/\A(\P{Control}+)\z/ + , 'purge-after' => qr/\A(\d*)\z/ + )->{$ARGV[0]}; +}; my ($MAILBOX, $STATE); do { @@ -75,16 +79,7 @@ do { my $statefile = $CONF->{statefile} // $ARGV[0]; die "Missing option statefile" unless defined $statefile; - $statefile = $statefile =~ /\A(\p{Print}+)\z/ ? $1 : die "Insecure $statefile"; - - unless ($statefile =~ /\A\//) { - my $dir = ($ENV{XDG_DATA_HOME} // "$ENV{HOME}/.local/share") .'/'. $NAME; - $dir = $dir =~ /\A(\/\p{Print}+)\z/ ? $1 : die "Insecure $dir"; - $statefile = $dir .'/'. $statefile; - unless (-d $dir) { - mkdir $dir, 0700 or die "Can't mkdir $dir: $!\n"; - } - } + $statefile = xdg_basedir( XDG_DATA_HOME => ".local/share", $NAME, $statefile ); sysopen($STATE, $statefile, O_CREAT|O_RDWR|O_DSYNC, 0600) or die "Can't open $statefile: $!"; # XXX we need to pack the struct flock manually: not portable! diff --git a/pullimap.md b/pullimap.md index 06035ac..eac8efa 100644 --- a/pullimap.md +++ b/pullimap.md @@ -32,8 +32,8 @@ Options `--config=`*FILE* : Specify an alternate [configuration file](#configuration-file). - Relative paths start from *$XDG_CONFIG_HOME*, or *~/.config* if the - `XDG_CONFIG_HOME` environment variable is unset. + Relative paths start from *$XDG_CONFIG_HOME/pullimap*, or *~/.config/pullimap* + if the `XDG_CONFIG_HOME` environment variable is unset. `--idle`[`=`*seconds*] @@ -74,9 +74,9 @@ Configuration file ================== Unless told otherwise by the `--config=FILE` command-line option, -`pullimap` reads its configuration from *$XDG_CONFIG_HOME/pullimap* (or -*~/.config/pullimap* if the `XDG_CONFIG_HOME` environment variable is -unset) as an [INI file]. +`pullimap` reads its configuration from *$XDG_CONFIG_HOME/pullimap/config* +(or *~/.config/pullimap/config* if the `XDG_CONFIG_HOME` environment variable +is unset) as an [INI file]. The syntax of the configuration file is a series of `OPTION=VALUE` lines organized under some `[SECTION]`; lines starting with a ‘#’ or ‘;’ character are ignored as comments. -- cgit v1.2.3