]> git.proxmox.com Git - pmg-api.git/blob - PMG/Fetchmail.pm
UserConfig: virify: check username vs userid
[pmg-api.git] / PMG / Fetchmail.pm
1 package PMG::Fetchmail;
2
3 use strict;
4 use warnings;
5 use Data::Dumper;
6
7 use PVE::Tools;
8 use PVE::INotify;
9
10 use PMG::Utils;
11 use PMG::Config;
12 use PMG::ClusterConfig;
13
14 my $inotify_file_id = 'fetchmailrc';
15 my $config_filename = '/etc/pmg/fetchmailrc';
16 my $config_link_filename = '/etc/fetchmailrc';
17
18 my $fetchmail_default_id = 'fetchmail_default';
19 my $fetchmail_default_filename = '/etc/default/fetchmail';
20
21 sub read_fetchmail_default {
22 my ($filename, $fh) = @_;
23
24 if (defined($fh)) {
25 while (defined(my $line = <$fh>)) {
26 if ($line =~ m/^START_DAEMON=yes\s*$/) {
27 return 1;
28 }
29 }
30 }
31
32 return 0;
33 }
34
35 sub write_fetchmail_default {
36 my ($filename, $fh, $enable) = @_;
37
38 open (my $orgfh, "<", $filename);
39
40 my $wrote_start_daemon = 0;
41
42 my $write_start_daemon_line = sub {
43
44 return if $wrote_start_daemon; # only once
45 $wrote_start_daemon = 1;
46
47 if ($enable) {
48 print $fh "START_DAEMON=yes\n";
49 } else {
50 print $fh "START_DAEMON=no\n";
51 }
52 };
53
54 if (defined($orgfh)) {
55 while (defined(my $line = <$orgfh>)) {
56 if ($line =~ m/^#?START_DAEMON=.*$/) {
57 $write_start_daemon_line->();
58 } else {
59 print $fh $line;
60 }
61 }
62 } else {
63 $write_start_daemon_line->();
64 }
65 }
66
67 PVE::INotify::register_file(
68 $fetchmail_default_id, $fetchmail_default_filename,
69 \&read_fetchmail_default,
70 \&write_fetchmail_default,
71 undef,
72 always_call_parser => 1);
73
74 my $set_fetchmail_defaults = sub {
75 my ($item) = @_;
76
77 $item->{protocol} //= 'pop3';
78 $item->{interval} //= 1;
79 $item->{enable} //= 0;
80
81 if (!$item->{port}) {
82 if ($item->{protocol} eq 'pop3') {
83 if ($item->{ssl}) {
84 $item->{port} = 995;
85 } else {
86 $item->{port} = 110;
87 }
88 } elsif ($item->{protocol} eq 'imap') {
89 if ($item->{ssl}) {
90 $item->{port} = 993;
91 } else {
92 $item->{port} = 143;
93 }
94 } else {
95 die "unknown fetchmail protocol '$item->{protocol}'\n";
96 }
97 }
98
99 return $item;
100 };
101
102 sub read_fetchmail_conf {
103 my ($filename, $fh) = @_;
104
105 my $cfg = {};
106
107 if ($fh) {
108
109 # scan for proxmox marker - skip non proxmox lines
110 while (defined(my $line = <$fh>)) {
111 last if $line =~ m/^\#\s+proxmox\s+settings.*$/;
112 }
113 # now parse the rest
114
115 my $data = '';
116 my $linenr = 0;
117
118 my $get_next_token = sub {
119
120 do {
121 while ($data =~ /\G("([^"]*)"|\S+|)(?:\s|$)/g) {
122 my ($token, $string) = ($1, $2);
123 if ($1 ne '') {
124 $string =~ s/\\x([0-9A-Fa-f]{2})/chr(hex($1))/eg
125 if defined($string);
126 return wantarray ? ($token, $string) : $token;
127 }
128 }
129 $data = <$fh>;
130 $linenr = $fh->input_line_number();
131 } while (defined($data));
132
133 return undef; # EOF
134 };
135
136 my $get_token_argument = sub {
137 my ($token, $string) = $get_next_token->();
138 die "line $linenr: missing token arguemnt\n" if !$token;
139 return $string // $token;
140 };
141
142 my $finalize_item = sub {
143 my ($item) = @_;
144 $cfg->{$item->{id}} = $item;
145 };
146
147 my $item;
148 while (my ($token, $string) = $get_next_token->()) {
149 last if !defined($token);
150 if ($token eq 'poll' || $token eq 'skip') {
151 $finalize_item->($item) if defined($item);
152 my $id = $get_token_argument->();
153 $item = { id => $id };
154 $item->{enable} = $token eq 'poll' ? 1 : 0;
155 next;
156 }
157
158 die "line $linenr: unexpected token '$token'\n"
159 if !defined($item);
160
161 if ($token eq 'user') {
162 $item->{user} = $get_token_argument->();
163 } elsif ($token eq 'via') {
164 $item->{server} = $get_token_argument->();
165 } elsif ($token eq 'pass') {
166 $item->{pass} = $get_token_argument->();
167 } elsif ($token eq 'to') {
168 $item->{target} = $get_token_argument->();
169 } elsif ($token eq 'protocol') {
170 $item->{protocol} = $get_token_argument->();
171 } elsif ($token eq 'port') {
172 $item->{port} = $get_token_argument->();
173 } elsif ($token eq 'interval') {
174 $item->{interval} = $get_token_argument->();
175 } elsif ($token eq 'ssl' || $token eq 'keep' ||
176 $token eq 'dropdelivered') {
177 $item->{$token} = 1;
178 } else {
179 die "line $linenr: unexpected token '$token' inside entry '$item->{id}'\n";
180 }
181 }
182 $finalize_item->($item) if defined($item);
183 }
184
185 return $cfg;
186 }
187
188 sub write_fetchmail_conf {
189 my ($filename, $fh, $fmcfg) = @_;
190
191 my $data = {};
192
193 # Note: we correctly quote data here to make fetchmailrc.tt simpler
194
195 my $entry_count = 0;
196
197 foreach my $id (keys %$fmcfg) {
198 my $org = $fmcfg->{$id};
199 my $item = { id => $id };
200 $entry_count++;
201 foreach my $k (keys %$org) {
202 my $v = $org->{$k};
203 $v =~ s/([^A-Za-z0-9\:\@\-\._~])/sprintf "\\x%02x",ord($1)/eg;
204 $item->{$k} = $v;
205 }
206 $set_fetchmail_defaults->($item);
207 my $options = [ 'dropdelivered' ];
208 push @$options, 'ssl' if $item->{ssl};
209 push @$options, 'keep' if $item->{keep};
210 $item->{options} = join(' ', @$options);
211 $data->{$id} = $item;
212 }
213
214 my $raw = '';
215
216 my $pmgcfg = PMG::Config->new();
217 my $vars = $pmgcfg->get_template_vars();
218 $vars->{fetchmail_users} = $data;
219
220 my $tt = PMG::Config::get_template_toolkit();
221 $tt->process('fetchmailrc.tt', $vars, \$raw) ||
222 die $tt->error() . "\n";
223
224 my (undef, undef, $uid, $gid) = getpwnam('fetchmail');
225 chown($uid, $gid, $fh) if defined($uid) && defined($gid);
226 chmod(0600, $fh);
227
228 PVE::Tools::safe_print($filename, $fh, $raw);
229
230 update_fetchmail_default($entry_count);
231 }
232
233 sub update_fetchmail_default {
234 my ($enable) = @_;
235
236 my $cinfo = PMG::ClusterConfig->new();
237
238 my $is_enabled = PVE::INotify::read_file('fetchmail_default');
239 my $role = $cinfo->{local}->{type} // '-';
240 if (($role eq '-') || ($role eq 'master')) {
241 if (!!$enable != !!$is_enabled) {
242 PVE::INotify::write_file('fetchmail_default', $enable);
243 PMG::Utils::service_cmd('fetchmail', 'restart');
244 }
245 if (! -e $config_link_filename) {
246 symlink ($config_filename, $config_link_filename);
247 }
248 } else {
249 if ($is_enabled) {
250 PVE::INotify::write_file('fetchmail_default', 0);
251 }
252 if (-e $config_link_filename) {
253 unlink $config_link_filename;
254 }
255 }
256 }
257
258 PVE::INotify::register_file(
259 $inotify_file_id, $config_filename,
260 \&read_fetchmail_conf,
261 \&write_fetchmail_conf,
262 undef,
263 always_call_parser => 1);
264
265 1;