Smtp::smtpOk()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 8
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 11
rs 10
1
<?php
2
/**
3
 * Class Smtp
4
 * @author Tinymeng <[email protected]>
5
 * @date: 2019/9/25 18:51
6
 */
7
namespace tinymeng\mailer\Gateways;
8
use tinymeng\mailer\Connector\Gateway;
9
10
class Smtp extends Gateway{
11
12
    /**
13
     * @var
14
     * @author Tinymeng <[email protected]>
15
     * @date: 2019/9/26 16:21
16
     */
17
    private $sock;
18
    private $boundary;
19
20
    /**
21
     * Function Name: 发送email
22
     * @param string $subject 邮件主题
23
     * @param string $body 邮件内容
24
     * @param string $mailType 邮件格式(HTML/TXT),TXT为文本邮件
25
     * @return bool
26
     * @author Tinymeng <[email protected]>
27
     * @date: 2019/9/26 15:33
28
     */
29
    public function sendmail($subject, $body, $mailType = 'HTML')
30
    {
31
        $mail_from = $this->getAddress($this->stripComment($this->from_address));
32
        $body = preg_replace("/(^|(\r\n))(\.)/", "\1.\3", $body);
33
        $this->boundary = "====" . md5(uniqid()) . "====";
34
        $body = $this->prepareBody($body,$mailType);
35
        $header = $this->buildHeaders($subject, $mailType, $mail_from);
36
37
        // Prepare recipient list
38
        $TO = $this->getRecipientList();
39
40
        $sent = true;
41
        foreach ($TO as $rcpt_to) {
42
            $rcpt_to = $this->getAddress($rcpt_to);
43
            if (!$this->smtpSockopen($rcpt_to)) {
44
                $this->logWrite("Error: Cannot send email to " . $rcpt_to . "\n");
45
                $sent = false;
46
                continue;
47
            }
48
            if ($this->smtpSend($this->host_name, $mail_from, $rcpt_to, $header, $body)) {
49
                $this->logWrite("E-mail has been sent to <" . $rcpt_to . ">\n");
50
            } else {
51
                $this->logWrite("Error: Cannot send email to <" . $rcpt_to . ">\n");
52
                $sent = false;
53
            }
54
            fclose($this->sock);
55
            $this->logWrite("Disconnected from remote host\n");
56
        }
57
        return $sent;
58
    }
59
60
    /**
61
     * @param $subject
62
     * @param $mailType
63
     * @param $mail_from
64
     * @return string
65
     * @author Tinymeng <[email protected]>
66
     */
67
    private function buildHeaders($subject, $mailType, $mail_from){
68
        // Set headers
69
        $header = "MIME-Version: 1.0\r\n";
70
        if (strtoupper($mailType) == "HTML") {
71
            $header .= "Content-Type: multipart/mixed; boundary=\"$this->boundary\"\r\n";
72
        } else {
73
            $header .= "Content-Type: text/plain; charset=UTF-8\r\n";
74
        }
75
        $header .= "To: " . $this->to_address . "\r\n";
76
        if (!empty($this->cc_address)) {
77
            $header .= "Cc: " . $this->cc_address . "\r\n";
78
        }
79
        $header .= "From: $this->from_address <" . $this->from_address . ">\r\n";
80
        $header .= "Subject: " . $subject . "\r\n";
81
        if(!empty($this->headers)){
82
            foreach ($this->headers as $head){
83
                $header .= $head;
84
            }
85
        }
86
        $header .= "Date: " . date("r") . "\r\n";
87
        $header .= "X-Mailer: By Redhat (PHP/" . phpversion() . ")\r\n";
88
        $header .= "Message-ID: <" . date("YmdHis") . "." . uniqid() . "@" . $mail_from . ">\r\n";
89
        return $header;
90
    }
91
92
93
    /**
94
     * @param $body
95
     * @param $mailType
96
     * @return string
97
     * @author Tinymeng <[email protected]>
98
     */
99
    private function prepareBody($body, $mailType){
100
        if (strtoupper($mailType) == "HTML") {
101
            // Body with text and attachments
102
            $body = "--$this->boundary\r\n" .
103
                "Content-Type: text/html; charset=UTF-8\r\n" .
104
                "Content-Transfer-Encoding: 7bit\r\n\r\n" .
105
                $body . "\r\n";
106
107
            if(!empty($this->attachments)){
108
                foreach ($this->attachments as $fileName=>$attachment) {
109
                    $fileContent = chunk_split(base64_encode(file_get_contents($attachment)));
110
                    $body .= "--$this->boundary\r\n" .
111
                        "Content-Type: application/octet-stream; name=\"$fileName\"\r\n" .
112
                        "Content-Transfer-Encoding: base64\r\n" .
113
                        "Content-Disposition: attachment; filename=\"$fileName\"\r\n\r\n" .
114
                        $fileContent . "\r\n";
115
                }
116
                $this->attachments = array();
117
            }
118
119
            $body .= "--$this->boundary--";
120
        } else {
121
            $body .= "\r\n";
122
        }
123
        return $body;
124
    }
125
126
    /**
127
     * @return false|string[]
128
     * @author Tinymeng <[email protected]>
129
     */
130
    private function getRecipientList()
131
    {
132
        $TO = explode(",", $this->stripComment($this->to_address));
0 ignored issues
show
Bug introduced by
It seems like $this->stripComment($this->to_address) can also be of type null and string[]; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

132
        $TO = explode(",", /** @scrutinizer ignore-type */ $this->stripComment($this->to_address));
Loading history...
133
        if (!empty($this->cc_address)) {
134
            $TO = array_merge($TO, explode(",", $this->stripComment($this->cc_address)));
135
        }
136
        if (!empty($this->bcc_address)) {
137
            $TO = array_merge($TO, explode(",", $this->stripComment($this->bcc_address)));
138
        }
139
        return $TO;
140
    }
141
142
    /**
143
     * Function Name: smtpSend
144
     * @param $helo
145
     * @param $from
146
     * @param $to
147
     * @param $header
148
     * @param string $body
149
     * @return bool
150
     * @author Tinymeng <[email protected]>
151
     * @date: 2019/9/26 15:30
152
     */
153
    private function smtpSend($helo, $from, $to, $header, $body = "")
154
    {
155
        if (!$this->smtpPutCmd("HELO", $helo)) {
156
            return $this->smtpError("sending HELO command");
157
        }
158
        //auth
159
        if($this->auth){
160
            if (!$this->smtpPutCmd("AUTH LOGIN", base64_encode($this->username))) {
161
                return $this->smtpError("sending HELO command");
162
            }
163
            if (!$this->smtpPutCmd("", base64_encode($this->password))) {
164
                return $this->smtpError("sending HELO command");
165
            }
166
        }
167
        if (!$this->smtpPutCmd("MAIL", "FROM:<".$from.">")) {
168
            return $this->smtpError("sending MAIL FROM command");
169
        }
170
        if (!$this->smtpPutCmd("RCPT", "TO:<".$to.">")) {
171
            return $this->smtpError("sending RCPT TO command");
172
        }
173
        if (!$this->smtpPutCmd("DATA")) {
174
            return $this->smtpError("sending DATA command");
175
        }
176
        if (!$this->smtpMessage($header, $body)) {
177
            return $this->smtpError("sending message");
178
        }
179
        if (!$this->smtpEom()) {
180
            return $this->smtpError("sending <CR><LF>.<CR><LF> [EOM]");
181
        }
182
        if (!$this->smtpPutCmd("QUIT")) {
183
            return $this->smtpError("sending QUIT command");
184
        }
185
        return TRUE;
186
    }
187
188
    /**
189
     * Function Name: smtpSockopen
190
     * @param $address
191
     * @return bool
192
     * @author Tinymeng <[email protected]>
193
     * @date: 2019/9/26 15:30
194
     */
195
    private function smtpSockopen($address)
196
    {
197
        if ($this->host == "") {
198
            return $this->smtpSockopenMx($address);
199
        } else {
200
            return $this->smtpSockopenRelay();
201
        }
202
    }
203
204
    /**
205
     * Function Name: smtpSockopenRelay
206
     * @return bool
207
     * @author Tinymeng <[email protected]>
208
     * @date: 2019/9/26 15:31
209
     */
210
    private function smtpSockopenRelay()
211
    {
212
        $this->logWrite("Trying to ".$this->host.":".$this->port."\n");
213
        $this->sock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->time_out);
214
        if (!($this->sock && $this->smtpOk())) {
215
            $this->logWrite("Error: Cannot connenct to relay host ".$this->host."\n");
216
            $this->logWrite("Error: ".$errstr." (".$errno.")\n");
217
            return FALSE;
218
        }
219
        $this->logWrite("Connected to relay host ".$this->host."\n");
220
        return TRUE;
221
    }
222
223
    /**
224
     * Function Name: smtpSockopenMx
225
     * @param $address
226
     * @return bool
227
     * @author Tinymeng <[email protected]>
228
     * @date: 2019/9/26 15:31
229
     */
230
    private function smtpSockopenMx($address)
231
    {
232
        $domain = preg_replace("/^.+@([^@]+)$/", "\1", $address);
233
        if (!@getmxrr($domain, $MXHOSTS)) {
234
            $this->logWrite("Error: Cannot resolve MX \"".$domain."\"\n");
235
            return FALSE;
236
        }
237
        foreach ($MXHOSTS as $host) {
238
            $this->logWrite("Trying to ".$host.":".$this->port."\n");
239
            $this->sock = @fsockopen($host, $this->port, $errno, $errstr, $this->time_out);
240
            if (!($this->sock && $this->smtpOk())) {
241
                $this->logWrite("Warning: Cannot connect to mx host ".$host."\n");
242
                $this->logWrite("Error: ".$errstr." (".$errno.")\n");
243
                continue;
244
            }
245
            $this->logWrite("Connected to mx host ".$host."\n");
246
            return TRUE;
247
        }
248
        $this->logWrite("Error: Cannot connect to any mx hosts (".implode(", ", $MXHOSTS).")\n");
249
        return FALSE;
250
    }
251
252
    /**
253
     * Function Name: smtpMessage
254
     * @param $header
255
     * @param $body
256
     * @return bool
257
     * @author Tinymeng <[email protected]>
258
     * @date: 2019/9/26 15:31
259
     */
260
    private function smtpMessage($header, $body)
261
    {
262
        fputs($this->sock, $header."\r\n".$body);
263
        $this->debug("> ".str_replace("\r\n", "\n"."> ", $header."\n> ".$body."\n> "));
264
        return TRUE;
265
    }
266
267
    /**
268
     * Function Name: smtpEom
269
     * @return bool
270
     * @author Tinymeng <[email protected]>
271
     * @date: 2019/9/26 15:31
272
     */
273
    private function smtpEom()
274
    {
275
        fputs($this->sock, "\r\n.\r\n");
276
        $this->debug(". [EOM]\n");
277
        return $this->smtpOk();
278
    }
279
280
    /**
281
     * Function Name: smtpOk
282
     * @return bool
283
     * @author Tinymeng <[email protected]>
284
     * @date: 2019/9/26 15:31
285
     */
286
    private function smtpOk()
287
    {
288
        $response = str_replace("\r\n", "", fgets($this->sock, 512));
289
        $this->debug($response."\n");
290
        if (!preg_match("/^[23]/", $response)) {
291
            fputs($this->sock, "QUIT\r\n");
292
            fgets($this->sock, 512);
293
            $this->logWrite("Error: Remote host returned \"".$response."\"\n");
294
            return FALSE;
295
        }
296
        return TRUE;
297
    }
298
299
    /**
300
     * Function Name: smtpPutCmd
301
     * @param $cmd
302
     * @param string $arg
303
     * @return bool
304
     * @author Tinymeng <[email protected]>
305
     * @date: 2019/9/26 15:31
306
     */
307
    private function smtpPutCmd($cmd, $arg = "")
308
    {
309
        if ($arg != "") {
310
            if($cmd=="") $cmd = $arg;
311
            else $cmd = $cmd." ".$arg;
312
        }
313
        fputs($this->sock, $cmd."\r\n");
314
        $this->debug("> ".$cmd."\n");
315
        return $this->smtpOk();
316
    }
317
318
    /**
319
     * Function Name: smtpError
320
     * @param $string
321
     * @return bool
322
     * @author Tinymeng <[email protected]>
323
     * @date: 2019/9/26 15:31
324
     */
325
    private function smtpError($string)
326
    {
327
        $this->logWrite("Error: Error occurred while ".$string.".\n");
328
        return FALSE;
329
    }
330
331
    /**
332
     * Function Name: logWrite
333
     * @param $message
334
     * @return bool
335
     * @author Tinymeng <[email protected]>
336
     * @date: 2019/9/26 15:26
337
     */
338
    private function logWrite($message)
339
    {
340
        $this->debug($message);
341
        if ($this->log_file == "") {
342
            return TRUE;
343
        }
344
        $message = date("Y-m-d H:i:s ").get_current_user()."[".getmypid()."]: ".$message;
345
        if (($handle = @fopen($this->log_file, "a")) === false) {
0 ignored issues
show
Bug introduced by
$this->log_file of type boolean is incompatible with the type string expected by parameter $filename of fopen(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

345
        if (($handle = @fopen(/** @scrutinizer ignore-type */ $this->log_file, "a")) === false) {
Loading history...
346
            $this->debug("Warning: Cannot open log file \"".$this->log_file."\"\n");
347
            return FALSE;
348
        }
349
        flock($handle, LOCK_EX);
350
        fputs($handle, $message);
351
        fclose($handle);
352
        return TRUE;
353
    }
354
355
    /**
356
     * Function Name: stripComment
357
     * @param $address
358
     * @return string|string[]|null
359
     * @author Tinymeng <[email protected]>
360
     * @date: 2019/9/26 15:26
361
     */
362
    private function stripComment($address)
363
    {
364
        $comment = "/\([^()]*\)/";
365
        while (preg_match($comment, $address)) {
366
            $address = preg_replace($comment, "", $address);
367
        }
368
        return $address;
369
    }
370
371
    /**
372
     * Function Name: getAddress
373
     * @param $address
374
     * @return string|string[]|null
375
     * @author Tinymeng <[email protected]>
376
     * @date: 2019/9/26 15:26
377
     */
378
    private function getAddress($address)
379
    {
380
        $address = preg_replace("/([ \t\r\n])+/", "", $address);
381
        $address = preg_replace("/^.*<(.+)>.*$/", "\1", $address);
382
        return $address;
383
    }
384
385
    /**
386
     * Function Name: debug
387
     * @param $message
388
     * @author Tinymeng <[email protected]>
389
     * @date: 2019/9/26 15:27
390
     */
391
    private function debug($message)
392
    {
393
        if ($this->debug) {
394
            echo $message."<hr/>";
395
        }
396
    }
397
}