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
![]() |
|||||
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
$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
![]() |
|||||
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 | } |