This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | namespace SamIT\React\Smtp; |
||
3 | |||
4 | |||
5 | use React\Dns\Query\TimeoutException; |
||
6 | use React\EventLoop\LoopInterface; |
||
7 | use React\EventLoop\Timer\TimerInterface; |
||
8 | use React\Socket\ConnectionInterface; |
||
9 | use React\Stream\WritableStream; |
||
10 | |||
11 | class Connection extends \React\Socket\Connection{ |
||
12 | const STATUS_NEW = 0; |
||
13 | const STATUS_INIT = 1; |
||
14 | const STATUS_FROM = 2; |
||
15 | const STATUS_TO = 3; |
||
16 | const STATUS_HEADERS = 4; |
||
17 | const STATUS_UNFOLDING = 5; |
||
18 | const STATUS_BODY = 6; |
||
19 | |||
20 | |||
21 | /** |
||
22 | * This status is used when all mail data has been received and the system is deciding whether to accept or reject. |
||
23 | */ |
||
24 | const STATUS_PROCESSING = 7; |
||
25 | |||
26 | |||
27 | const REGEXES = [ |
||
28 | 'Quit' => '/^QUIT$/', |
||
29 | 'Helo' => '/^HELO (.*)$/', |
||
30 | 'Ehlo' => '/^EHLO (.*)$/', |
||
31 | 'MailFrom' => '/^MAIL FROM:\s*(.*)$/', |
||
32 | 'Reset' => '/^RSET$/', |
||
33 | 'RcptTo' => '/^RCPT TO:\s*(.*)$/', |
||
34 | 'StartData' => '/^DATA$/', |
||
35 | 'StartHeader' => '/^(\w+):\s*(.*)$/', |
||
36 | 'StartBody' => '/^$/', |
||
37 | 'Unfold' => '/^ (.*)$/', |
||
38 | 'EndData' => '/^\.$/', |
||
39 | 'BodyLine' => '/^(.*)$/', |
||
40 | 'EndBody' => '/^\.$/' |
||
41 | ]; |
||
42 | |||
43 | protected $states = [ |
||
44 | self::STATUS_NEW => [ |
||
45 | 'Quit', 'Helo', 'Ehlo' |
||
46 | ], |
||
47 | self::STATUS_INIT => [ |
||
48 | 'MailFrom', |
||
49 | 'Quit' |
||
50 | |||
51 | ], |
||
52 | self::STATUS_FROM => [ |
||
53 | 'RcptTo', |
||
54 | 'Quit', |
||
55 | 'Reset', |
||
56 | ], |
||
57 | self::STATUS_TO => [ |
||
58 | 'Quit', |
||
59 | 'StartData', |
||
60 | 'Reset', |
||
61 | 'RcptTo', |
||
62 | |||
63 | ], |
||
64 | self::STATUS_HEADERS => [ |
||
65 | 'EndBody', |
||
66 | 'StartHeader', |
||
67 | 'StartBody', |
||
68 | ], |
||
69 | self::STATUS_UNFOLDING => [ |
||
70 | 'StartBody', |
||
71 | 'EndBody', |
||
72 | 'Unfold', |
||
73 | 'StartHeader', |
||
74 | ], |
||
75 | self::STATUS_BODY => [ |
||
76 | 'EndBody', |
||
77 | 'BodyLine' |
||
78 | ], |
||
79 | self::STATUS_PROCESSING => [ |
||
80 | |||
81 | ] |
||
82 | |||
83 | |||
84 | |||
85 | ]; |
||
86 | |||
87 | protected $state = self::STATUS_NEW; |
||
88 | |||
89 | protected $banner = 'Welcome to ReactPHP SMTP Server'; |
||
90 | /** |
||
91 | * @var bool Accept messages by default |
||
92 | */ |
||
93 | protected $acceptByDefault = true; |
||
94 | /** |
||
95 | * If there are event listeners, how long will they get to accept or reject a message? |
||
96 | * @var int |
||
97 | */ |
||
98 | protected $defaultActionTimeout = 5; |
||
99 | /** |
||
100 | * The timer for the default action, canceled in [accept] and [reject] |
||
101 | * @var TimerInterface |
||
102 | */ |
||
103 | protected $defaultActionTimer; |
||
104 | /** |
||
105 | * The current line buffer used by handleData. |
||
106 | * @var string |
||
107 | */ |
||
108 | protected $lineBuffer = ''; |
||
109 | |||
110 | /** |
||
111 | * @var string Name of the header in the foldBuffer. |
||
112 | */ |
||
113 | protected $foldHeader = ''; |
||
114 | /** |
||
115 | * Buffer used for unfolding multiline headers.. |
||
116 | * @var string |
||
117 | */ |
||
118 | protected $foldBuffer = ''; |
||
119 | protected $from; |
||
120 | protected $recipients = []; |
||
121 | /** |
||
122 | * @var Message |
||
123 | */ |
||
124 | protected $message; |
||
125 | |||
126 | public $bannerDelay = 0; |
||
127 | |||
128 | |||
129 | public $recipientLimit = 100; |
||
130 | |||
131 | 1 | public function __construct($stream, LoopInterface $loop) |
|
132 | { |
||
133 | 1 | parent::__construct($stream, $loop); |
|
134 | 1 | stream_get_meta_data($stream); |
|
135 | // We sleep for 3 seconds, if client does not wait for our banner we disconnect. |
||
136 | $disconnect = function($data, ConnectionInterface $conn) { |
||
137 | $conn->end("I can break rules too, bye.\n"); |
||
138 | 1 | }; |
|
139 | 1 | $this->on('data', $disconnect); |
|
140 | 1 | $this->reset(self::STATUS_NEW); |
|
141 | 1 | $this->on('line', [$this, 'handleCommand']); |
|
142 | 1 | if ($this->bannerDelay > 0) { |
|
143 | $loop->addTimer($this->bannerDelay, function () use ($disconnect) { |
||
144 | $this->sendReply(220, $this->banner); |
||
145 | $this->removeListener('data', $disconnect); |
||
146 | }); |
||
147 | } else { |
||
148 | 1 | $this->sendReply(220, $this->banner); |
|
149 | } |
||
150 | 1 | } |
|
151 | |||
152 | /** |
||
153 | * We read until we find an and of line sequence for SMTP. |
||
154 | * http://www.jebriggs.com/blog/2010/07/smtp-maximum-line-lengths/ |
||
155 | * @param $stream |
||
156 | */ |
||
157 | public function handleData($stream) |
||
158 | { |
||
159 | // Socket is raw, not using fread as it's interceptable by filters |
||
160 | // See issues #192, #209, and #240 |
||
161 | $data = stream_socket_recvfrom($stream, $this->bufferSize);; |
||
162 | |||
163 | $limit = $this->state == self::STATUS_BODY ? 1000 : 512; |
||
164 | if ('' !== $data && false !== $data) { |
||
165 | $this->lineBuffer .= $data; |
||
166 | if (strlen($this->lineBuffer) > $limit) { |
||
167 | $this->sendReply(500, "Line length limit exceeded."); |
||
168 | $this->lineBuffer = ''; |
||
169 | } |
||
170 | |||
171 | $delimiter = "\r\n"; |
||
172 | while(false !== $pos = strpos($this->lineBuffer, $delimiter)) { |
||
173 | $line = substr($this->lineBuffer, 0, $pos); |
||
174 | $this->lineBuffer = substr($this->lineBuffer, $pos + strlen($delimiter)); |
||
175 | $this->emit('line', [$line, $this]); |
||
176 | } |
||
177 | } |
||
178 | |||
179 | if ('' === $data || false === $data || !is_resource($stream) || feof($stream)) { |
||
180 | $this->end(); |
||
181 | } |
||
182 | } |
||
183 | |||
184 | /** |
||
185 | * Parses the command from the beginning of the line. |
||
186 | * |
||
187 | * @param string $line |
||
188 | * @return string[] An array containing the command and all arguments. |
||
189 | */ |
||
190 | protected function parseCommand($line) |
||
191 | { |
||
192 | |||
193 | foreach ($this->states[$this->state] as $key) { |
||
194 | if (preg_match(self::REGEXES[$key], $line, $matches) === 1) { |
||
195 | $matches[0] = $key; |
||
196 | $this->emit('debug', ["$line match for $key (" . self::REGEXES[$key] . ")"]); |
||
197 | return $matches; |
||
198 | } else { |
||
199 | $this->emit('debug', ["$line does not match for $key (" . self::REGEXES[$key] . ")"]); |
||
200 | } |
||
201 | } |
||
202 | return [null]; |
||
203 | } |
||
204 | |||
205 | protected function handleCommand($line) |
||
206 | { |
||
207 | $arguments = $this->parseCommand($line); |
||
208 | $command = array_shift($arguments); |
||
209 | if ($command === null) { |
||
210 | $this->sendReply(500, array_merge( |
||
211 | $this->states[$this->state], |
||
212 | ["Unexpected or unknown command."] |
||
213 | )); |
||
214 | } else { |
||
215 | call_user_func_array([$this, "handle{$command}Command"], $arguments); |
||
216 | } |
||
217 | } |
||
218 | |||
219 | 1 | protected function sendReply($code, $message, $close = false) |
|
220 | { |
||
221 | 1 | $out = ''; |
|
222 | 1 | if (is_array($message)) { |
|
223 | $last = array_pop($message); |
||
224 | foreach($message as $line) { |
||
225 | $out .= "$code-$line\r\n"; |
||
226 | } |
||
227 | $this->write($out); |
||
228 | $message = $last; |
||
229 | } |
||
230 | 1 | if ($close) { |
|
231 | $this->end("$code $message\r\n"); |
||
232 | } else { |
||
233 | 1 | $this->write("$code $message\r\n"); |
|
234 | } |
||
235 | |||
236 | 1 | } |
|
237 | |||
238 | protected function handleResetCommand() |
||
239 | { |
||
240 | $this->reset(); |
||
241 | $this->sendReply(250, "Reset OK"); |
||
242 | } |
||
243 | protected function handleHeloCommand($domain) |
||
244 | { |
||
245 | $this->state = self::STATUS_INIT; |
||
246 | $this->sendReply(250, "Hello {$domain} @ {$this->getRemoteAddress()}"); |
||
247 | } |
||
248 | |||
249 | protected function handleEhloCommand($domain) |
||
250 | { |
||
251 | $this->state = self::STATUS_INIT; |
||
252 | $this->sendReply(250, "Hello {$domain} @ {$this->getRemoteAddress()}"); |
||
253 | } |
||
254 | |||
255 | View Code Duplication | protected function handleMailFromCommand($arguments) |
|
0 ignored issues
–
show
|
|||
256 | { |
||
257 | |||
258 | // Parse the email. |
||
259 | if (preg_match('/\<(?<email>.*)\>( .*)?/', $arguments, $matches) == 1) { |
||
260 | $this->state = self::STATUS_FROM; |
||
261 | $this->from = $matches['email']; |
||
262 | $this->sendReply(250, "MAIL OK"); |
||
263 | } else { |
||
264 | $this->sendReply(500, "Invalid mail argument"); |
||
265 | } |
||
266 | |||
267 | } |
||
268 | |||
269 | protected function handleQuitCommand() |
||
270 | { |
||
271 | $this->sendReply(221, "Goodbye.", true); |
||
272 | |||
273 | } |
||
274 | |||
275 | View Code Duplication | protected function handleRcptToCommand($arguments) { |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
276 | // Parse the recipient. |
||
277 | if (preg_match('/^(?<name>.*?)\s*?\<(?<email>.*)\>\s*$/', $arguments, $matches) == 1) { |
||
278 | // Always set to 3, since this command might occur multiple times. |
||
279 | $this->state = self::STATUS_TO; |
||
280 | $this->recipients[$matches['email']] = $matches['name']; |
||
281 | $this->sendReply(250, "Accepted"); |
||
282 | } else { |
||
283 | $this->sendReply(500, "Invalid RCPT TO argument."); |
||
284 | } |
||
285 | } |
||
286 | |||
287 | protected function handleStartDataCommand() |
||
288 | { |
||
289 | $this->state = self::STATUS_HEADERS; |
||
290 | $this->sendReply(354, "Enter message, end with CRLF . CRLF"); |
||
291 | } |
||
292 | |||
293 | protected function handleUnfoldCommand($content) |
||
294 | { |
||
295 | $this->foldBuffer .= $content; |
||
296 | } |
||
297 | |||
298 | protected function handleStartHeaderCommand($name, $content) |
||
299 | { |
||
300 | // Check if status is unfolding. |
||
301 | View Code Duplication | if ($this->state === self::STATUS_UNFOLDING) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
302 | $this->message = $this->message->withAddedHeader($this->foldHeader, $this->foldBuffer); |
||
303 | } |
||
304 | |||
305 | $this->foldBuffer = $content; |
||
306 | $this->foldHeader = $name; |
||
307 | $this->state = self::STATUS_UNFOLDING; |
||
308 | } |
||
309 | |||
310 | protected function handleStartBodyCommand() |
||
311 | { |
||
312 | // Check if status is unfolding. |
||
313 | View Code Duplication | if ($this->state === self::STATUS_UNFOLDING) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
314 | $this->message = $this->message->withAddedHeader($this->foldHeader, $this->foldBuffer); |
||
315 | } |
||
316 | $this->state = self::STATUS_BODY; |
||
317 | |||
318 | } |
||
319 | |||
320 | protected function handleEndBodyCommand() |
||
321 | { |
||
322 | // Check if status is unfolding. |
||
323 | View Code Duplication | if ($this->state === self::STATUS_UNFOLDING) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
324 | $this->message = $this->message->withAddedHeader($this->foldHeader, $this->foldBuffer); |
||
325 | } |
||
326 | |||
327 | $this->state = self::STATUS_PROCESSING; |
||
328 | /** |
||
329 | * Default action, using timer so that callbacks above can be called asynchronously. |
||
330 | */ |
||
331 | $this->defaultActionTimer = $this->loop->addTimer($this->defaultActionTimeout, function() { |
||
332 | if ($this->acceptByDefault) { |
||
333 | $this->accept(); |
||
334 | } else { |
||
335 | $this->reject(); |
||
336 | } |
||
337 | }); |
||
338 | |||
339 | |||
340 | |||
341 | $this->emit('message', [ |
||
342 | 'from' => $this->from, |
||
343 | 'recipients' => $this->recipients, |
||
344 | 'message' => $this->message, |
||
345 | 'connection' => $this, |
||
346 | ]); |
||
347 | } |
||
348 | protected function handleBodyLineCommand($line) |
||
349 | { |
||
350 | $this->message->getBody()->write($line); |
||
351 | } |
||
352 | |||
353 | /** |
||
354 | * Reset the SMTP session. |
||
355 | * By default goes to the initialized state (ie no new EHLO or HELO is required / possible.) |
||
356 | * |
||
357 | * @param int $state The state to go to. |
||
358 | */ |
||
359 | 1 | protected function reset($state = self::STATUS_INIT) { |
|
360 | 1 | $this->state = $state; |
|
361 | 1 | $this->from = null; |
|
362 | 1 | $this->recipients = []; |
|
363 | 1 | $this->message = new Message(); |
|
364 | 1 | } |
|
365 | |||
366 | View Code Duplication | public function accept($message = "OK") { |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
367 | if ($this->state != self::STATUS_PROCESSING) { |
||
368 | throw new \DomainException("SMTP Connection not in a valid state to accept a message."); |
||
369 | } |
||
370 | $this->loop->cancelTimer($this->defaultActionTimer); |
||
371 | unset($this->defaultActionTimer); |
||
372 | $this->sendReply(250, $message); |
||
373 | $this->reset(); |
||
374 | } |
||
375 | |||
376 | View Code Duplication | public function reject($code = 550, $message = "Message not accepted") { |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
377 | if ($this->state != self::STATUS_PROCESSING) { |
||
378 | throw new \DomainException("SMTP Connection not in a valid state to reject message."); |
||
379 | } |
||
380 | $this->defaultActionTimer->cancel(); |
||
381 | unset($this->defaultActionTimer); |
||
382 | $this->sendReply($code, $message); |
||
383 | $this->reset(); |
||
384 | } |
||
385 | |||
386 | /** |
||
387 | * Delay the default action by $seconds. |
||
388 | * @param int $seconds |
||
389 | */ |
||
390 | public function delay($seconds) { |
||
391 | if (isset($this->defaultActionTimer)) { |
||
392 | $this->defaultActionTimer->cancel(); |
||
393 | $this->defaultActionTimer = $this->loop->addTimer($seconds, $this->defaultActionTimer->getCallback()); |
||
394 | } |
||
395 | } |
||
396 | |||
397 | } |
||
398 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.