]>
Commit | Line | Data |
---|---|---|
14726412 | 1 | package PMG::SMTP; |
73238bb3 DM |
2 | |
3 | use strict; | |
14726412 | 4 | use warnings; |
73238bb3 | 5 | use IO::Socket; |
00eaaf69 | 6 | use Encode; |
73238bb3 | 7 | |
14726412 DM |
8 | use PVE::SafeSyslog; |
9 | ||
10 | use PMG::MailQueue; | |
11 | ||
73238bb3 DM |
12 | sub new { |
13 | my($this, $sock) = @_; | |
14726412 | 14 | |
73238bb3 DM |
15 | my $class = ref($this) || $this; |
16 | ||
9ef3f143 | 17 | die("undefined socket: ERROR") if !defined($sock); |
73238bb3 DM |
18 | |
19 | my $self = {}; | |
20 | $self->{sock} = $sock; | |
21 | $self->{lmtp} = undef; | |
22 | bless($self, $class); | |
23 | ||
24 | $self->reset(); | |
25 | ||
26 | $self->reply ("220 Proxmox SMTP Ready."); | |
27 | return $self; | |
28 | } | |
29 | ||
30 | sub reset { | |
31 | my $self = shift; | |
32 | ||
33 | $self->{from} = undef; | |
34 | $self->{to} = []; | |
35 | $self->{queue} = undef; | |
4a00e01d | 36 | delete $self->{smtputf8}; |
73238bb3 DM |
37 | delete $self->{xforward}; |
38 | delete $self->{status}; | |
39 | } | |
40 | ||
41 | sub abort { | |
42 | shift->{sock}->close(); | |
43 | } | |
44 | ||
45 | sub reply { | |
46 | print {shift->{sock}} @_, "\r\n";; | |
47 | ||
48 | } | |
49 | ||
50 | sub loop { | |
51 | my ($self, $func, $data, $maxcount) = @_; | |
52 | ||
53 | my($cmd, $args); | |
73238bb3 | 54 | |
14726412 | 55 | my $sock = $self->{sock}; |
73238bb3 DM |
56 | |
57 | my $count = 0; | |
58 | ||
59 | while(<$sock>) { | |
60 | chomp; | |
61 | s/^\s+//; | |
62 | s/\s+$//; | |
63 | ||
64 | if (!length ($_)) { | |
65 | $self->reply ("500 5.5.1 Error: bad syntax"); | |
66 | next; | |
67 | } | |
68 | ($cmd, $args) = split(/\s+/, $_, 2); | |
69 | $cmd = lc ($cmd); | |
70 | ||
71 | if ($cmd eq 'helo' || $cmd eq 'ehlo' || $cmd eq 'lhlo') { | |
72 | $self->reset(); | |
73 | ||
73238bb3 DM |
74 | $self->reply ("250-PIPELINING"); |
75 | $self->reply ("250-ENHANCEDSTATUSCODES"); | |
76 | $self->reply ("250-8BITMIME"); | |
00eaaf69 | 77 | $self->reply ("250-SMTPUTF8"); |
73238bb3 DM |
78 | $self->reply ("250-XFORWARD NAME ADDR PROTO HELO"); |
79 | $self->reply ("250 OK."); | |
80 | $self->{lmtp} = 1 if ($cmd eq 'lhlo'); | |
81 | next; | |
82 | } elsif ($cmd eq 'xforward') { | |
83 | my @tmp = split (/\s+/, $args); | |
84 | foreach my $attr (@tmp) { | |
85 | my ($n, $v) = ($attr =~ /^(.*?)=(.*)$/); | |
86 | $self->{xforward}->{lc($n)} = $v; | |
87 | } | |
88 | $self->reply ("250 2.5.0 OK"); | |
89 | next; | |
90 | } elsif ($cmd eq 'noop') { | |
91 | $self->reply ("250 2.5.0 OK"); | |
92 | next; | |
93 | } elsif ($cmd eq 'quit') { | |
94 | $self->reply ("221 2.2.0 OK"); | |
95 | last; | |
96 | } elsif ($cmd eq 'rset') { | |
97 | $self->reset(); | |
98 | $self->reply ("250 2.5.0 OK"); | |
99 | next; | |
100 | } elsif ($cmd eq 'mail') { | |
00eaaf69 | 101 | if ($args =~ m/^from:\s*<([^\s\>]*)>([^>]*)$/i) { |
73238bb3 | 102 | delete $self->{to}; |
00eaaf69 | 103 | my ($from, $opts) = ($1, $2); |
4a00e01d DM |
104 | if ($opts =~ m/\sSMTPUTF8/) { |
105 | $self->{smtputf8} = 1; | |
106 | $from = decode('UTF-8', $from); | |
107 | } | |
00eaaf69 | 108 | $self->{from} = $from; |
73238bb3 DM |
109 | $self->reply ('250 2.5.0 OK'); |
110 | next; | |
111 | } else { | |
112 | $self->reply ("501 5.5.2 Syntax: MAIL FROM: <address>"); | |
113 | next; | |
114 | } | |
115 | } elsif ($cmd eq 'rcpt') { | |
4a00e01d DM |
116 | if ($args =~ m/^to:\s*<([^\s\>]+)>[^>]*$/i) { |
117 | my $to = $self->{smtputf8} ? decode('UTF-8', $1) : $1; | |
118 | push @{$self->{to}} , $to; | |
73238bb3 DM |
119 | $self->reply ('250 2.5.0 OK'); |
120 | next; | |
121 | } else { | |
f6c789ba | 122 | $self->reply ("501 5.5.2 Syntax: RCPT TO: <address>"); |
73238bb3 DM |
123 | next; |
124 | } | |
125 | } elsif ($cmd eq 'data') { | |
126 | if ($self->save_data ()) { | |
127 | eval { &$func ($data, $self); }; | |
9d04575a | 128 | if (my $err = $@) { |
73238bb3 | 129 | $data->{errors} = 1; |
9d04575a | 130 | syslog ('err', $err); |
73238bb3 DM |
131 | } |
132 | ||
133 | if ($self->{lmtp}) { | |
134 | foreach $a (@{$self->{to}}) { | |
135 | if ($self->{queue}->{status}->{$a} eq 'delivered') { | |
136 | $self->reply ("250 2.5.0 OK ($self->{queue}->{logid})"); | |
137 | } elsif ($self->{queue}->{status}->{$a} eq 'blocked') { | |
138 | $self->reply ("250 2.7.0 BLOCKED ($self->{queue}->{logid})"); | |
139 | } elsif ($self->{queue}->{status}->{$a} eq 'error') { | |
140 | my $code = $self->{queue}->{status_code}->{$a}; | |
141 | my $resp = substr($code, 0, 1); | |
142 | my $mess = $self->{queue}->{status_message}->{$a}; | |
143 | $self->reply ("$code $resp.0.0 $mess"); | |
144 | } else { | |
145 | $self->reply ("451 4.4.0 detected undelivered mail to <$a>"); | |
146 | } | |
147 | } | |
148 | } else { | |
149 | my $all_done = 1; | |
150 | ||
151 | foreach $a (@{$self->{to}}) { | |
152 | if (!($self->{queue}->{status}->{$a} eq 'delivered' || | |
153 | $self->{queue}->{status}->{$a} eq 'blocked')) { | |
154 | $all_done = 0; | |
155 | } | |
156 | } | |
157 | if ($all_done) { | |
158 | $self->reply ("250 2.5.0 OK ($self->{queue}->{logid})"); | |
159 | } else { | |
160 | $self->reply ("451 4.4.0 detected undelivered mail"); | |
161 | } | |
162 | } | |
163 | } | |
164 | ||
165 | $self->reset(); | |
166 | ||
167 | $count++; | |
168 | last if $count >= $maxcount; | |
169 | last if $data->{errors}; # abort if we find errors | |
170 | next; | |
171 | } | |
14726412 | 172 | |
73238bb3 DM |
173 | $self->reply ("500 5.5.1 Error: unknown command"); |
174 | } | |
175 | ||
176 | $self->{sock}->close; | |
177 | return $count; | |
178 | } | |
179 | ||
180 | sub save_data { | |
181 | my $self = shift; | |
182 | my $done = undef; | |
14726412 | 183 | |
73238bb3 DM |
184 | if(!defined($self->{from})) { |
185 | $self->reply ("503 5.5.1 Tell me who you are."); | |
186 | return 0; | |
187 | } | |
14726412 | 188 | |
73238bb3 DM |
189 | if(!defined($self->{to})) { |
190 | $self->reply ("503 5.5.1 Tell me who to send it."); | |
191 | return 0; | |
192 | } | |
193 | ||
194 | $self->reply ("354 End data with <CR><LF>.<CR><LF>"); | |
195 | ||
196 | my $sock = $self->{sock}; | |
197 | ||
198 | my $queue; | |
199 | ||
200 | eval { | |
14726412 DM |
201 | $queue = PMG::MailQueue->new ($self->{from}, $self->{to}); |
202 | ||
73238bb3 DM |
203 | while(<$sock>) { |
204 | ||
205 | if(/^\.\015\012$/) { | |
206 | $done = 1; | |
207 | last; | |
208 | } | |
14726412 | 209 | |
73238bb3 DM |
210 | # RFC 2821 compliance. |
211 | s/^\.\./\./; | |
212 | ||
14726412 | 213 | s/\015\012/\n/; |
73238bb3 DM |
214 | |
215 | print {$queue->{fh}} $_; | |
216 | $queue->{bytes} += length ($_); | |
217 | } | |
218 | ||
219 | $queue->{fh}->flush (); | |
220 | ||
14726412 | 221 | $self->{queue} = $queue; |
73238bb3 | 222 | }; |
9d04575a DM |
223 | if (my $err = $@) { |
224 | syslog ('err', $err); | |
225 | $self->reply ("451 4.5.0 Local delivery failed: $err"); | |
73238bb3 DM |
226 | return 0; |
227 | } | |
228 | if(!defined($done)) { | |
229 | $self->reply ("451 4.5.0 Local delivery failed: unfinished data"); | |
230 | return 0; | |
231 | } | |
232 | ||
233 | return 1; | |
234 | } | |
235 | ||
236 | 1; |