* 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
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
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};
};
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);
{
$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';
`--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}`
==================
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.
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/;
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/;
}
#############################################################################
# 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
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;
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};
#######################################################################
# 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 {
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!
`--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*]
==================
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.