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