]>
Commit | Line | Data |
---|---|---|
26be105b DM |
1 | package PMG::Fetchmail; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
cde18a76 | 5 | use Data::Dumper; |
26be105b DM |
6 | |
7 | use PVE::Tools; | |
8 | use PVE::INotify; | |
9 | ||
81fa07d9 | 10 | use PMG::Utils; |
cde18a76 | 11 | use PMG::Config; |
0757859a | 12 | use PMG::ClusterConfig; |
cde18a76 | 13 | |
26be105b DM |
14 | my $inotify_file_id = 'fetchmailrc'; |
15 | my $config_filename = '/etc/pmg/fetchmailrc'; | |
0757859a DM |
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); | |
26be105b | 73 | |
cde18a76 DM |
74 | my $set_fetchmail_defaults = sub { |
75 | my ($item) = @_; | |
76 | ||
77 | $item->{protocol} //= 'pop3'; | |
78 | $item->{interval} //= 1; | |
004cc4f3 | 79 | $item->{enable} //= 0; |
cde18a76 DM |
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 | ||
26be105b DM |
102 | sub read_fetchmail_conf { |
103 | my ($filename, $fh) = @_; | |
104 | ||
105 | my $cfg = {}; | |
106 | ||
107 | if ($fh) { | |
108 | ||
cde18a76 | 109 | # scan for proxmox marker - skip non proxmox lines |
26be105b | 110 | while (defined(my $line = <$fh>)) { |
cde18a76 | 111 | last if $line =~ m/^\#\s+proxmox\s+settings.*$/; |
26be105b | 112 | } |
cde18a76 DM |
113 | # now parse the rest |
114 | ||
115 | my $data = ''; | |
116 | my $linenr = 0; | |
117 | ||
118 | my $get_next_token = sub { | |
119 | ||
120 | do { | |
96845248 | 121 | while ($data =~ /\G("([^"]*)"|\S+|)(?:\s|$)/g) { |
5072afa7 | 122 | my ($token, $string) = ($1, $2); |
cde18a76 | 123 | if ($1 ne '') { |
719ea622 | 124 | $string =~ s/\\x([0-9A-Fa-f]{2})/chr(hex($1))/eg |
96845248 | 125 | if defined($string); |
5072afa7 | 126 | return wantarray ? ($token, $string) : $token; |
cde18a76 DM |
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 }; | |
2d4e40c3 | 154 | $item->{enable} = $token eq 'poll' ? 1 : 0; |
cde18a76 DM |
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); | |
26be105b DM |
183 | } |
184 | ||
185 | return $cfg; | |
186 | } | |
187 | ||
188 | sub write_fetchmail_conf { | |
cde18a76 DM |
189 | my ($filename, $fh, $fmcfg) = @_; |
190 | ||
5072afa7 DM |
191 | my $data = {}; |
192 | ||
193 | # Note: we correctly quote data here to make fetchmailrc.tt simpler | |
194 | ||
0757859a DM |
195 | my $entry_count = 0; |
196 | ||
cde18a76 | 197 | foreach my $id (keys %$fmcfg) { |
5072afa7 DM |
198 | my $org = $fmcfg->{$id}; |
199 | my $item = { id => $id }; | |
0757859a | 200 | $entry_count++; |
5072afa7 DM |
201 | foreach my $k (keys %$org) { |
202 | my $v = $org->{$k}; | |
96845248 | 203 | $v =~ s/([^A-Za-z0-9\:\@\-\._~])/sprintf "\\x%02x",ord($1)/eg; |
5072afa7 DM |
204 | $item->{$k} = $v; |
205 | } | |
cde18a76 DM |
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); | |
5072afa7 | 211 | $data->{$id} = $item; |
cde18a76 | 212 | } |
26be105b DM |
213 | |
214 | my $raw = ''; | |
215 | ||
cde18a76 DM |
216 | my $pmgcfg = PMG::Config->new(); |
217 | my $vars = $pmgcfg->get_template_vars(); | |
5072afa7 | 218 | $vars->{fetchmail_users} = $data; |
cde18a76 DM |
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); | |
26be105b DM |
227 | |
228 | PVE::Tools::safe_print($filename, $fh, $raw); | |
0757859a DM |
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); | |
81fa07d9 | 243 | PMG::Utils::service_cmd('fetchmail', 'restart'); |
0757859a DM |
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 | } | |
26be105b DM |
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 | ||
26be105b | 265 | 1; |