]> git.proxmox.com Git - pmg-api.git/blobdiff - PMG/Fetchmail.pm
improve pmg-email-address regex
[pmg-api.git] / PMG / Fetchmail.pm
index 7482ff4a468ca4a95267d2763f29528b952b6149..7e16c7967767a3a7b45198e0f5226bded569d82e 100644 (file)
@@ -2,12 +2,102 @@ package PMG::Fetchmail;
 
 use strict;
 use warnings;
+use Data::Dumper;
 
 use PVE::Tools;
 use PVE::INotify;
 
+use PMG::Utils;
+use PMG::Config;
+use PMG::ClusterConfig;
+
 my $inotify_file_id = 'fetchmailrc';
 my $config_filename = '/etc/pmg/fetchmailrc';
+my $config_link_filename = '/etc/fetchmailrc';
+
+my $fetchmail_default_id = 'fetchmail_default';
+my $fetchmail_default_filename = '/etc/default/fetchmail';
+
+sub read_fetchmail_default {
+    my ($filename, $fh) = @_;
+
+    if (defined($fh)) {
+       while (defined(my $line = <$fh>)) {
+           if ($line =~ m/^START_DAEMON=yes\s*$/) {
+               return 1;
+           }
+       }
+    }
+
+    return 0;
+}
+
+sub write_fetchmail_default {
+    my ($filename, $fh, $enable) = @_;
+
+    open (my $orgfh, "<", $filename);
+
+    my $wrote_start_daemon = 0;
+
+    my $write_start_daemon_line = sub {
+
+       return if $wrote_start_daemon;  # only once
+       $wrote_start_daemon = 1;
+
+       if ($enable) {
+           print $fh "START_DAEMON=yes\n";
+       } else {
+           print $fh "START_DAEMON=no\n";
+       }
+    };
+
+    if (defined($orgfh)) {
+       while (defined(my $line = <$orgfh>)) {
+           if ($line =~ m/^#?START_DAEMON=.*$/) {
+               $write_start_daemon_line->();
+           } else {
+               print $fh $line;
+           }
+       }
+    } else {
+       $write_start_daemon_line->();
+    }
+}
+
+PVE::INotify::register_file(
+    $fetchmail_default_id, $fetchmail_default_filename,
+    \&read_fetchmail_default,
+    \&write_fetchmail_default,
+    undef,
+    always_call_parser => 1);
+
+my $set_fetchmail_defaults = sub {
+    my ($item) = @_;
+
+    $item->{protocol} //= 'pop3';
+    $item->{interval} //= 1;
+    $item->{enable} //= 0;
+
+    if (!$item->{port}) {
+       if ($item->{protocol} eq 'pop3') {
+           if ($item->{ssl}) {
+               $item->{port} = 995;
+           } else {
+               $item->{port} = 110;
+           }
+       } elsif ($item->{protocol} eq 'imap') {
+           if ($item->{ssl}) {
+               $item->{port} = 993;
+           } else {
+               $item->{port} = 143;
+           }
+       } else {
+           die "unknown fetchmail protocol '$item->{protocol}'\n";
+       }
+    }
+
+    return $item;
+};
 
 sub read_fetchmail_conf {
     my ($filename, $fh) = @_;
@@ -16,25 +106,153 @@ sub read_fetchmail_conf {
 
     if ($fh) {
 
+       # scan for proxmox marker - skip non proxmox lines
        while (defined(my $line = <$fh>)) {
+           last if $line =~ m/^\#\s+proxmox\s+settings.*$/;
+       }
+       # now parse the rest
+
+       my $data = '';
+       my $linenr = 0;
+
+       my $get_next_token = sub {
+
+           do {
+               while ($data =~ /\G("([^"]*)"|\S+|)(?:\s|$)/g) {
+                   my ($token, $string) = ($1, $2);
+                   if ($1 ne '') {
+                       $string =~ s/\\x([0-9A-Fa-f]{2})/chr(hex($1))/eg
+                           if defined($string);
+                       return wantarray ? ($token, $string) : $token;
+                   }
+               }
+               $data = <$fh>;
+               $linenr = $fh->input_line_number();
+           } while (defined($data));
+
+           return undef; # EOF
+       };
+
+       my $get_token_argument = sub {
+           my ($token, $string) = $get_next_token->();
+           die "line $linenr: missing token arguemnt\n" if !$token;
+           return $string // $token;
+       };
+
+       my $finalize_item = sub {
+           my ($item) = @_;
+           $cfg->{$item->{id}} = $item;
+       };
 
+       my $item;
+       while (my ($token, $string) = $get_next_token->()) {
+           last if !defined($token);
+           if ($token eq 'poll' || $token eq 'skip') {
+               $finalize_item->($item) if defined($item);
+               my $id = $get_token_argument->();
+               $item = { id => $id };
+               $item->{enable} = $token eq 'poll' ? 1 : 0;
+               next;
+           }
 
+           die "line $linenr: unexpected token '$token'\n"
+               if !defined($item);
+
+           if ($token eq 'user') {
+               $item->{user} = $get_token_argument->();
+           } elsif ($token eq 'via') {
+               $item->{server} = $get_token_argument->();
+           } elsif ($token eq 'pass') {
+               $item->{pass} = $get_token_argument->();
+           } elsif ($token eq 'to') {
+               $item->{target} = $get_token_argument->();
+           } elsif ($token eq 'protocol') {
+               $item->{protocol} = $get_token_argument->();
+           } elsif ($token eq 'port') {
+               $item->{port} = $get_token_argument->();
+           } elsif ($token eq 'interval') {
+               $item->{interval} = $get_token_argument->();
+           } elsif ($token eq 'ssl' || $token eq 'keep' ||
+                    $token eq 'dropdelivered') {
+               $item->{$token} = 1;
+           } else {
+               die "line $linenr: unexpected token '$token' inside entry '$item->{id}'\n";
+           }
        }
+       $finalize_item->($item) if defined($item);
     }
 
     return $cfg;
 }
 
 sub write_fetchmail_conf {
-    my ($filename, $fh, $cfg) = @_;
+    my ($filename, $fh, $fmcfg) = @_;
+
+    my $data = {};
+
+    # Note: we correctly quote data here to make fetchmailrc.tt simpler
+
+    my $entry_count = 0;
+
+    foreach my $id (keys %$fmcfg) {
+       my $org = $fmcfg->{$id};
+       my $item = { id => $id };
+       $entry_count++;
+       foreach my $k (keys %$org) {
+           my $v = $org->{$k};
+           $v =~ s/([^A-Za-z0-9\:\@\-\._~])/sprintf "\\x%02x",ord($1)/eg;
+           $item->{$k} = $v;
+       }
+       $set_fetchmail_defaults->($item);
+       my $options = [ 'dropdelivered' ];
+       push @$options, 'ssl' if $item->{ssl};
+       push @$options, 'keep' if $item->{keep};
+       $item->{options} = join(' ', @$options);
+       $data->{$id} = $item;
+    }
 
     my $raw = '';
 
-    my $gid = getgrnam('www-data');
-    chown(0, $gid, $fh);
-    chmod(0640, $fh);
+    my $pmgcfg = PMG::Config->new();
+    my $vars = $pmgcfg->get_template_vars();
+    $vars->{fetchmail_users} = $data;
+
+    my $tt = PMG::Config::get_template_toolkit();
+    $tt->process('fetchmailrc.tt', $vars, \$raw) ||
+       die $tt->error() . "\n";
+
+    my (undef, undef, $uid, $gid) = getpwnam('fetchmail');
+    chown($uid, $gid, $fh) if defined($uid) && defined($gid);
+    chmod(0600, $fh);
 
     PVE::Tools::safe_print($filename, $fh, $raw);
+
+    update_fetchmail_default($entry_count);
+}
+
+sub update_fetchmail_default {
+    my ($enable) = @_;
+
+    my $cinfo = PMG::ClusterConfig->new();
+
+    my $is_enabled = PVE::INotify::read_file('fetchmail_default');
+    my $role = $cinfo->{local}->{type} // '-';
+    if (($role eq '-') || ($role eq 'master')) {
+       if (!!$enable != !!$is_enabled) {
+           PVE::INotify::write_file('fetchmail_default', $enable);
+           PMG::Utils::service_cmd('fetchmail', 'restart');
+       }
+       if (! -e $config_link_filename) {
+           symlink ($config_filename, $config_link_filename);
+       }
+    } else {
+       if ($is_enabled) {
+           PVE::INotify::write_file('fetchmail_default', 0);
+       }
+       if (-e $config_link_filename) {
+           unlink $config_link_filename;
+       }
+    }
 }
 
 PVE::INotify::register_file(
@@ -44,6 +262,4 @@ PVE::INotify::register_file(
     undef,
     always_call_parser => 1);
 
-
-
 1;