TLS上SMTP(称为SSL/TLS)的通信示例



我正在尝试在SMTP服务器(PHP(中实现SSL/TLS。保护与TLSv1.3的连接是有效的,并且证书(LetsEncrypt(是有效的。我用测试过https://www.checktls.com/TestReceiver它只在我激活"直接TLS"时工作:

seconds        test stage and result
[000.000]       Trying TLS on mrs.dzir.org[212.58.86.63:465] (-1)
[000.100]       Server answered
[000.707]       Connection converted to SSL
SSLVersion in use: TLSv1_3
Cipher in use: TLS_AES_256_GCM_SHA384
Perfect Forward Secrecy: yes
Session Algorithm in use: Curve X25519 DHE(253 bits)
[001.185]       TLS successfully started on this server
[001.185]   <~~ 220 MailRelayServer ESMTP server ready
[001.185]       We are allowed to connect
[001.185]   ~~> EHLO www12-do.checktls.com
[001.284]   <~~ 250-Hello [142.93.73.156]
250-DATA
250-AUTH LOGIN PLAIN CRAM-MD5
250-AUTH=CRAM-MD5
250 OK
[001.285]       We can use this server
[001.285]   ~~> AUTH PLAIN ********
[001.387]   <~~ 235 Authentication successful
[001.387]       AUTH successful
[001.387]   ~~> MAIL FROM:<test@checktls.com>
[001.490]   <~~ 550 test@checktls.com ... Sender not accepted
[001.490]       Cannot proof email address (reason: MAIL FROM rejected)
[001.490]       Note: This does not affect the CheckTLS Confidence Factor
[001.490]   ~~> QUIT
[001.589]   <~~ 221 Bye

当我试图在手机上的GMail应用程序中更新连接详细信息时,它一直在说

Email security not guaranteed  
There was a problem setting up security for this account

我的SMTP服务器日志显示

2022-08-17 15:40:12 New Client Connected (46.114.140.164 [telefonica.de] -> AbuseIPDB Score: 0)
2022-08-17 15:40:12 SSL connection established for 46.114.140.164
2022-08-17 15:40:12 --> 220 MailRelayServer ESMTP server ready
2022-08-17 15:40:12 Client 0 from 46.114.140.164 Disconnecting
2022-08-17 15:40:13 New Client Connected (46.114.140.164 [telefonica.de] -> AbuseIPDB Score: 0)
2022-08-17 15:40:13 SSL connection established for 46.114.140.164
2022-08-17 15:40:13 --> 220 MailRelayServer ESMTP server ready
2022-08-17 15:40:13 Client 0 from 46.114.140.164 Disconnecting

因此,GMail尝试了两次,建立了一个安全连接,然后不再做出反应(甚至不发送"退出"命令(。

现在的问题是:由于通信中显然缺少一些东西,我需要一个通信示例(像我的SMTP服务器协议这样的东西会很好(。有人知道在哪里买吗?我在网上搜索了一下,但只能找到STARTTLS的简单示例,这不是我现在需要的
提前感谢!

openssl工具的输出:

# openssl s_client -connect mrs.dzir.org:465
CONNECTED(00000003)
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = mrs.dzir.org
verify return:1
---
Certificate chain
0 s:CN = mrs.dzir.org
i:C = US, O = Let's Encrypt, CN = R3
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
v:NotBefore: Aug 16 09:27:26 2022 GMT; NotAfter: Nov 14 09:27:25 2022 GMT
1 s:C = US, O = Let's Encrypt, CN = R3
i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
v:NotBefore: Sep  4 00:00:00 2020 GMT; NotAfter: Sep 15 16:00:00 2025 GMT
2 s:C = US, O = Internet Security Research Group, CN = ISRG Root X1
i:O = Digital Signature Trust Co., CN = DST Root CA X3
a:PKEY: rsaEncryption, 4096 (bit); sigalg: RSA-SHA256
v:NotBefore: Jan 20 19:14:03 2021 GMT; NotAfter: Sep 30 18:14:03 2024 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIFHjCCBAagAwIBAgISBDQeLzaBb9+bUfozRluuUoZ9MA0GCSqGSIb3DQEBCwUA
MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
EwJSMzAeFw0yMjA4MTYwOTI3MjZaFw0yMjExMTQwOTI3MjVaMBcxFTATBgNVBAMT
DG1ycy5kemlyLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMCK
AiOqNuaIj6pyt2HeQR/RQtxrXJL8uKppf05YFHhFUfJwOXxSDWioxlqn0igps7cO
NDoti4QC30BhsrAWXzewVtrROHStQYBmSOuGbtrLZ/FKMyXw/fH3ev1ObBgKBD3o
e9D1QC36kiqm34WQtCQ5rizcevKerkeuLAlj81SuyRONarWzG44GWjqtN0g9v6Vz
9QbvAqVLWyfgbJqYNpsbju9Sbc1QFpYL7JZ7nwhf+g6F8zWdSqMNXQCoYWMwf3WE
Qrj5lE8QLEbcszmtfYVvl40hOO9qPDdaz+PCXvWmHkktxt+GYi76HJxWYn7jMd0/
Te+zW7aBZ3s2+AxJScMCAwEAAaOCAkcwggJDMA4GA1UdDwEB/wQEAwIFoDAdBgNV
HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4E
FgQUG2PkYn93TKVeB3bXcQejuT/KBmkwHwYDVR0jBBgwFoAUFC6zF7dYVsuuUAlA
5h+vnYsUwsYwVQYIKwYBBQUHAQEESTBHMCEGCCsGAQUFBzABhhVodHRwOi8vcjMu
by5sZW5jci5vcmcwIgYIKwYBBQUHMAKGFmh0dHA6Ly9yMy5pLmxlbmNyLm9yZy8w
FwYDVR0RBBAwDoIMbXJzLmR6aXIub3JnMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcG
CysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5
cHQub3JnMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYA36Veq2iCTx9sre64X04+
WurNohKkal6OOxLAIERcKnMAAAGCpjGinAAABAMARzBFAiBOeG4tx7GFAeL65wk4
934DXgNrUbkRMalQD27PwPnN3AIhANdIcwJRjJAydZHsin1Wb2QINAcwiay6JOAz
R6HNOnD3AHYARqVV63X6kSAwtaKJafTzfREsQXS+/Um4havy/HD+bUcAAAGCpjGi
xQAABAMARzBFAiBYh3PL5r/CsD+ZUhQ7xUyFuHzV7wqyy/sL1J2vgbn1sQIhAIIV
VF8Th0lP42R0kiUA657yhofIPlBYSHA2umyWQHQYMA0GCSqGSIb3DQEBCwUAA4IB
AQCxdAHo8/vqiKdV1tw9ErjdY1xR0UgDAOPY2w9RgGZBIjQmfpQU0aqHvTBJetrK
Gjli9++Mg9cLKwsOLBe3r0gOLqwitdbcB9hh1sbUqPjhfjoa7uGBciU6XuRPtzRX
X0p7kQ6QrJvs8+QaZKiliQfMfiG8mzBJTOiTrWyfha4FvfC0BgvP5dnZHa8xY5dM
XJSdg0wQzxA8vI+dCyncqDlIo6ngwaoXqELF90fBT2WsvJfBDf5W8iqM/Iujw0DS
tY1d+cq1QM+7bVNrYrQkPRol0hhCzz7eSlvIp6Bx7jc0UcJq/EvkegoCZ7J4qX+/
EkydQTh73ho5D/28Ny0PgMfE
-----END CERTIFICATE-----
subject=CN = mrs.dzir.org
issuer=C = US, O = Let's Encrypt, CN = R3
---
No client certificate CA names sent
Requested Signature Algorithms: ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512:Ed25519:Ed448:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA224:RSA+SHA224
Shared Requested Signature Algorithms: ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512:Ed25519:Ed448:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA+SHA256:RSA+SHA384:RSA+SHA512
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 4633 bytes and written 424 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
Protocol  : TLSv1.3
Cipher    : TLS_AES_256_GCM_SHA384
Session-ID: B8346466CC912BE31A603A30F52C3289464D8964107FC8CCCBAEF6B21E6B5FA2
Session-ID-ctx:
Resumption PSK: 7C325FBD4945DEA9F2E6C0236B94CB968580167BAD18BDA3034A2075BF894E40A1FD35E03D3D82E170BF09C5CFC1BC23
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 7200 (seconds)
TLS session ticket:
0000 - 53 5c 5c 8f 9c 0a ee c8-59 2d 61 ac df c2 61 d1   S\.....Y-a...a.
0010 - fb 91 6c 20 44 df 9f 05-93 86 ce b4 29 eb da 65   ..l D.......)..e
0020 - de f3 97 04 4a 68 72 61-88 a3 7d 7f 13 26 5d 41   ....Jhra..}..&]A
0030 - 89 15 99 4f ab 6c 86 4a-23 b5 52 cc f6 0b 1c 85   ...O.l.J#.R.....
0040 - 63 3d c9 98 36 08 ad 58-fe fb d2 9c 74 f0 ca 52   c=..6..X....t..R
0050 - 73 36 ce d4 41 6d aa 86-53 af 22 ac 42 a7 f6 a9   s6..Am..S.".B...
0060 - 6d 19 3f ca 2a ec 5a c9-fd 26 6d 88 4e 3d 4c 9b   m.?.*.Z..&m.N=L.
0070 - 7f d8 ee a0 ad f3 f2 eb-d0 5a d4 76 25 4f 7f 01   .........Z.v%O..
0080 - ca 2d 50 77 44 fb 62 f3-4d 67 2c dc 00 45 28 74   .-PwD.b.Mg,..E(t
0090 - 88 10 30 c8 b6 7c 8d bb-bc 24 a7 70 3a 00 26 00   ..0..|...$.p:.&.
00a0 - da 85 24 04 c2 2a de b4-59 90 ee d8 b9 e7 81 e7   ..$..*..Y.......
00b0 - ab 9a 06 4c 4b 7b 4e 1d-13 e3 bc a4 13 07 c9 c7   ...LK{N.........
00c0 - 35 26 8a 45 59 e9 fc a0-ff 7d 30 d6 62 8b 51 21   5&.EY....}0.b.Q!
Start Time: 1660805781
Timeout   : 7200 (sec)
Verify return code: 0 (ok)
Extended master secret: no
Max Early Data: 0
---
read R BLOCK
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
Protocol  : TLSv1.3
Cipher    : TLS_AES_256_GCM_SHA384
Session-ID: 2291062CB8DDF654332636FE85D4A9BB9833B1BE052CA12C26CBBA790D542B35
Session-ID-ctx:
Resumption PSK: 5E7ED4B63AF3D4F1703055F06DCEB9F5B730F1FBC1F738C8AAFABDE702578D0C2F9F6D51D822C59B8C5EA6A1A481C0C1
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 7200 (seconds)
TLS session ticket:
0000 - 53 5c 5c 8f 9c 0a ee c8-59 2d 61 ac df c2 61 d1   S\.....Y-a...a.
0010 - 1e 26 ab 4d c7 7f 14 ae-f8 0c 29 c9 2f 2a e4 c5   .&.M......)./*..
0020 - 0b d5 61 8a 80 cd 5c 0a-ef 25 17 52 69 6e c0 0c   ..a.....%.Rin..
0030 - d6 73 16 2d 70 90 d7 9d-bd ac dc 35 62 f3 9a 33   .s.-p......5b..3
0040 - ce 7e 33 e2 f7 56 b7 84-de f6 f8 ff 82 fe 7a 9c   .~3..V........z.
0050 - 4c 68 27 3a 7c 6b 02 44-90 6d 88 d1 97 5d 13 98   Lh':|k.D.m...]..
0060 - a8 41 f5 3c d2 14 84 62-30 94 f2 fd 1c 1b 42 80   .A.<...b0.....B.
0070 - 6c c9 10 ce 60 ff 4b 76-c8 e3 7d 49 d0 fe 0b a3   l...`.Kv..}I....
0080 - 5b 31 c2 77 52 8c 87 17-c3 1b 3d 83 51 2a 12 ed   [1.wR.....=.Q*..
0090 - c6 7c 0e 07 ba b3 bf ec-ee c3 ee b6 41 6d 0b b5   .|..........Am..
00a0 - bf 2c fd 1e 05 e4 c3 76-3b 9d 1d 52 a1 2b f2 5e   .,.....v;..R.+.^
00b0 - 35 f8 a4 56 d1 4c 8f c1-c6 cb 8c 2a 4f a3 fe ad   5..V.L.....*O...
00c0 - 83 f9 9c dd 31 6f 5a e0-fe d4 c0 70 b0 c7 7b 49   ....1oZ....p..{I
Start Time: 1660805781
Timeout   : 7200 (sec)
Verify return code: 0 (ok)
Extended master secret: no
Max Early Data: 0
---
read R BLOCK
220 MailRelayServer ESMTP server ready

我在启用加密之前和之后添加了阻塞和取消阻塞套接字,并添加了对加密是否成功的检查。当连接到openssl工具时,我可以完美地与服务器通信,但当我尝试连接到GMail应用程序时,它仍然显示"电子邮件安全性未得到保证",并且我的服务器的通信日志显示New Client Rejected (79.238.153.195 [telekom.de] -> AbuseIPDB Score: 0, TLS could not be established)(我检查stream_socket_enable_crypto((的返回值:

stream_set_blocking($this->socket, true);
$bOK = stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_TLSv1_3_SERVER);
$aServer['port']);
stream_set_blocking($this->socket, false);
if (!$bOK) {
SocketServer::debug('New Client Rejected (' . $ip . ' [' . $ret['data']['domain'] . '] -> AbuseIPDB Score: ' . $ret['data']['abuseConfidenceScore'] . ', TLS could not be established)', '');
SocketServer::socket_write_smart($this->socket, '554 TLS needed', true);
return false;
}

服务器的连接日志显示:

2022-08-18 08:06:04 New Client Rejected (79.238.153.195 [telekom.de] -> AbuseIPDB Score: 0, TLS could not be established)
2022-08-18 08:06:04 --> 554 TLS needed

以下是SocketServer和SocketServerClient类的类文件,其中包含构建连接的所有内容(尽可能简化,但仍然有效(:

<?php
/**
* class SocketServer
* 
* @author Navarr Barnier
* @abstract A Framework for creating a multi-client server using the PHP language.
*/
class SocketServer {
/**
* @var run
* @abstract Bool - a boolean to signalize to stop
*/
protected $run = true;
/**
* @var config
* @abstract Array - an array of configuration information used by the server.
*/
protected array $config = array();
/**
* @var hooks
* @abstract Array - a dictionary of hooks and the callbacks attached to them.
*/
protected array $hooks = array();
/**
* @var master_socket
* @abstract resource - The master socket used by the server.
*/
protected $master_socket;
/**
* @var max_clients
* @abstract unsigned int - The maximum number of clients allowed to connect.
*/
public int $max_clients = 10;
/**
* @var max_read
* @abstract unsigned int - The maximum number of bytes to read from a socket at a single time.
*/
public int $max_read = 1024;
/**
* @var clients
* @abstract Array - an array of connected clients.
*/
public array $clients;
/**
* function __construct
* 
* @abstract Creates the socket and starts listening to it.
* @param string - IP Address to bind to, NULL for default.
* @param int - Port to bind to
* @return void
*/
public function __construct($bind_ip, $port, $domain = '') {
set_time_limit(0);
$this->config['ip'] = $bind_ip;
$this->config['port'] = $port;
$errno = 0;
$errmsg = '';
$set = ['ssl' => [
'local_cert' => '/etc/letsencrypt/live/' . $domain . '/fullchain.pem',
'local_pk' => '/etc/letsencrypt/live/' . $domain . '/privkey.pem',
'disable_compression' => false,
'ssltransport' => 'tlsv1.3'
]];
$context = stream_context_create($set);
$this->master_socket = stream_socket_server("tcp://{$bind_ip}:{$port}", $errno, $errmsg, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context);
if ($this->master_socket === false) {
SocketServer::debug("Could not start listening for connections on {$bind_ip}:{$port}", '');
die("Issue Bindingrn{$errmsg} ({$errno})rn");
}
stream_socket_enable_crypto($this->master_socket, false);
SocketServer::debug("Listening for connections on {$bind_ip}:{$port} with TLS enabled", '');
echo "Listenting for connections on {$bind_ip}:{$port} with TLS enabledrn";
pcntl_async_signals(true);
pcntl_signal(SIGINT, [$this, 'shutdown']); // Call $this->shutdown() on SIGINT
pcntl_signal(SIGTERM, [$this, 'shutdown']); // Call $this->shutdown() on SIGTERM
}
/**
* function __destruct
*/
public function __destruct() {
fclose($this->master_socket);
foreach ($this->clients as $client) {
fclose($client->socket);
}
}
/**
* function hook
* 
* @abstract Adds a function to be called whenever a certain action happens. Can be extended in your implementation.
* @param string - Command
* @param callback- Function to Call.
* @see unhook
* @see trigger_hooks
* @return void
*/
public function hook($command, $function) {
$command = strtoupper($command);
if (!isset($this->hooks[$command])) {
$this->hooks[$command] = array();
}
$k = array_search($function, $this->hooks[$command]);
if ($k === FALSE) {
$this->hooks[$command][] = $function;
}
}
/**
* function unhook
* 
* @abstract Deletes a function from the call list for a certain action. Can be extended in your implementation.
* @param string - Command
* @param callback- Function to Delete from Call List
* @see hook
* @see trigger_hooks
* @return void
*/
public function unhook($command = NULL, $function) {
$command = strtoupper($command);
if ($command !== NULL) {
$k = array_search($function, $this->hooks[$command]);
if ($k !== FALSE) {
unset($this->hooks[$command][$k]);
}
} else {
$k = array_search($this->user_funcs, $function);
if ($k !== FALSE) {
unset($this->user_funcs[$k]);
}
}
}
/**
* function loop_once
* 
* @abstract Runs the class's actions once.
* @discussion Should only be used if you want to run additional checks during server operation. Otherwise, use infinite_loop()
* @param void
* @see infinite_loop
* @return bool - True
*/
public function loop_once() {
$bOK = true;
// Setup Clients Listen Socket For Reading
$read[0] = $this->master_socket;
for ($i = 0; $i < $this->max_clients; $i ++) {
if (isset($this->clients[$i])) {
$read[$i + 1] = $this->clients[$i]->socket;
}
}
// Set up a blocking call to socket_select
$tv_sec = 5;
$write = $except = null;
if (@stream_select($read, $write, $except, $tv_sec) < 1) {
$bOK = false;
}
if ($bOK) {
// Handle new Connections
if (in_array($this->master_socket, $read)) {
for ($i = 0; $i < $this->max_clients; $i ++) {
if (empty($this->clients[$i])) {
$this->clients[$i] = new SocketServerClient($this->config['ssl']);
if ($this->clients[$i]->connect($this->master_socket, $i)) {
$this->trigger_hooks("CONNECT", $this->clients[$i], "");
}
else {
unset($this->clients[$i]);
}
break;
}
elseif ($i == ($this->max_clients - 1)) {
SocketServer::debug("Too many clients... :( ");
}
}
}
// Handle Input
for($i = 0; $i < $this->max_clients; $i ++) { // for each client
if (isset($this->clients[$i])) {
if (in_array($this->clients[$i]->socket, $read)) {
if ($this->config['ssl']) {
$input = fread($this->clients[$i]->socket, $this->max_read);
}
else {
$input = socket_read($this->clients[$i]->socket, $this->max_read);
}
if ($input == null) {
$this->disconnect($i);
}
else {
$this->trigger_hooks("INPUT", $this->clients[$i], $input);
}
}
}
}
}
return $this->run;
}
/**
* function disconnect
* 
* @abstract Disconnects a client from the server.
* @param int - Index of the client to disconnect.
* @param string - Message to send to the hooks
* @return void
*/
public function disconnect($client_index, $message = "") {
$i = $client_index;
SocketServer::debug("Client {$i} from {$this->clients[$i]->ip} Disconnecting", '');
$this->trigger_hooks("DISCONNECT", $this->clients[$i], $message);
unset($this->clients[$i]);
}
/**
* function trigger_hooks
* 
* @abstract Triggers Hooks for a certain command.
* @param string - Command who's hooks you want to trigger.
* @param object - The client who activated this command.
* @param string - The input from the client, or a message to be sent to the hooks.
* @return void
*/
public function trigger_hooks($command, &$client, $input) {
if (isset($this->hooks[$command])) {
foreach($this->hooks[$command] as $function) {
$continue = call_user_func($function, $this, $client, $input);
if ($continue === FALSE) {
break;
}
}
}
}
/**
* function trigger_hook
* 
* @abstract Triggers Hook for a certain command.
* @param string - Command who's hooks you want to trigger.
* @param string - The input from the client, or a message to be sent to the hooks.
* @return void
*/
public function trigger_hook($command, $input) {
if (isset($this->hooks[$command])) {
foreach($this->hooks[$command] as $function) {
$continue = call_user_func($function, $this, $input);
if ($continue === FALSE) {
break;
}
}
}
}
/**
* function infinite_loop
* 
* @abstract Runs the server code until the server is shut down.
* @see loop_once
* @param void
* @return void
*/
public function infinite_loop() {
$test = true;
do {
$test = $this->loop_once();
if (file_exists(realpath(dirname(__FILE__) . '/../restart.cmd'))) {
unlink(realpath(dirname(__FILE__) . '/../restart.cmd'));
$test = false;
$this->shutdown();
}
} while ($test);
}
/**
* function debug
* 
* @static
* @abstract Outputs Text directly.
* @discussion Yeah, should probably make a way to turn this off.
* @param string - Text to Output
* @return void
*/
public static function debug($text, $dir = '<--', $log = 'communication') {
global $debug;
if ($debug) {
$fp = fopen(dirname(__FILE__) . '/../' . $log . '.log', 'a');
if (is_array($text)) {
foreach ($text as $line) {
fwrite($fp, date('Y-m-d H:i:s') . ' ' . ((strlen($dir)) ? $dir . ' ' : '') . $line . "rn");
}
}
else {
$lines = explode("n", $text);
foreach ($lines as $line) {
fwrite($fp, date('Y-m-d H:i:s') . ' ' . ((strlen($dir)) ? $dir . ' ' : '') . str_replace("r", '', $line) . "rn");
}
}
fclose($fp);
}
}
/**
* function socket_write_smart
* 
* @static
* @abstract Writes data to the socket, including the length of the data, and ends it with a CRLF unless specified.
* @discussion It is perfectly valid for socket_write_smart to return zero which means no bytes have been written. Be sure to use the === operator to check * for FALSE in case of an error.
* @param resource- Socket Instance
* @param string - Data to write to the socket.
* @param string - Data to end the line with. Specify a "" if you don't want a line end sent.
* @return mixed - Returns the number of bytes successfully written to the socket or FALSE on failure. The error code can be retrieved with socket_last_error(). This code may be passed to socket_strerror() to get a textual explanation of the error.
*/
public static function socket_write_smart(&$sock, $string, $crlf = "rn") {
SocketServer::debug($string, '-->');
$ret = fwrite($sock, $string . $crlf);
return $ret;
}
/**
* function __get
* 
* @abstract Magic Method used for allowing the reading of protected variables.
* @discussion You never need to use this method, simply calling $server->variable works because of this method's existence.
* @param string - Variable to retrieve
* @return mixed - Returns the reference to the variable called.
*/
public function &__get($name) {
return $this->{$name};
}
public function shutdown() {
$this->run = false;
SocketServer::debug('Shutting down', '');
echo "nShutting downn";
$this->__destruct();
exit(0);
}
}
/**
* class SocketServerClient
* 
* @author Navarr Barnier
* @abstract A Client Instance for use with SocketServer
*/
class SocketServerClient {
/**
* var socket
* @abstract resource - The client's socket resource, for sending and receiving data with.
*/
protected $socket;
/**
* var ip
* @abstract string - The client's IP address, as seen by the server.
*/
protected $ip;
/**
* var hostname
* @abstract string - The client's hostname, as seen by the server.
* @discussion This variable is only set after calling lookup_hostname, as hostname lookups can take up a decent amount of time.
* @see lookup_hostname
*/
protected $hostname;
/**
* var server_clients_index
* @abstract int - The index of this client in the SocketServer's client array.
*/
protected $server_clients_index;
/**
* function connect
* 
* @param resource- The resource of the socket the client is connecting by, generally the master socket.
* @param int - The Index in the Server's client array.
* @return bool Connection accepted or not
*/
public function connect(&$socket, $i) {
$this->server_clients_index = $i;
$this->socket = stream_socket_accept($socket) or die("Failed to Acceptn");
$ip = '';
$name = stream_socket_get_name($this->socket, true);
list($ip, $port) = explode(':', $name);
$this->ip = $ip;
# Block the connection until the secure connection is established
stream_set_blocking($this->socket, true);
$bOK = stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_TLSv1_3_SERVER);
stream_set_blocking($this->socket, false);
if (!$bOK) {
SocketServer::debug('New Client Rejected (' . $ip . ', TLS could not be established)', '');
SocketServer::socket_write_smart($this->socket, '554 TLS needed', true);
return false;
}
SocketServer::debug('New Client Connected (' . $ip . ')', '');
SocketServer::debug('SSL connection established for ' . $ip, '');
return true;
}
/**
* function lookup_hostname
* 
* @abstract Searches for the user's hostname and stores the result to hostname.
* @see hostname
* @param void
* @return string - The hostname on success or the IP address on failure.
*/
public function lookup_hostname() {
$this->hostname = gethostbyaddr($this->ip);
return $this->hostname;
}
/**
* function __destruct
* 
* @abstract Closes the socket. Thats pretty much it.
* @param void
* @return void
*/
public function __destruct() {
fclose($this->socket);
}
function &__get($name) {
return $this->{$name};
}
function __isset($name) {
return isset($this->{$name});
}
}
?>

这样使用它($bind_ip必须是它运行的机器的本地ip(:

# Create a Server binding to the given ip address and listen to the given port for connections
$sDomain = str_replace(array('http://', 'https://'), '', $sMainUrl);
$p1 = strpos($sDomain, '/');
if ($p1 !== false) {
$sDomain = substr($sDomain, 0, $p1);
}
$server = new SocketServer($aServer['ip'], $aServer['port'], $sDomain);
# Run handleConnect every time someone connects
$server->hook('CONNECT', 'handleConnect');
# Run handleInput whenever text is sent to the server
$server->hook('INPUT', 'handleInput');
# Run Server Code Until Process is terminated
$server->infinite_loop();
# Manage communication classes
$aCommunications = array();
/**
* function handleConnect
* 
* @param $server object Server instance (ignored)
* @param $client object Client instance
* @param $input string Input from the connecting client (ignored)
* @return void
*/
function handleConnect($server, $client, $input) {
global $aCommunications, $aServer;
$aCommunications[$client->server_clients_index] = new Communication($aServer['ssl']);
SocketServer::socket_write_smart($client->socket, $aCommunications[$client->server_clients_index]->welcome(), $aServer['ssl']);
}
/**
* function handleInput
* 
* @param $server object Server instance
* @param $client object Client instance
* @param $input string Input from the connecting client
* @return void
*/
function handleInput($server, $client, $input) {
global $aCommunications;
if (!$aCommunications[$client->server_clients_index]->handleInput($client, $input)) {
$server->disconnect($client->server_clients_index);
unset($aCommunications[$client->server_clients_index]);
}
}

建立连接后的通信在另一个文件中,此问题不需要。

我刚刚用Thunderbird(Windows 10上的便携式(进行了测试,它可以工作,但在Android上的GMail中不可以。这是GMail特有的问题吗?在网上找不到任何有用的东西。

服务器在该端口上不使用TLS进行应答。

使用openssl工具进行连接会引发以下错误,这意味着TLS以外的其他东西已经响应:

# openssl s_client -connect mrs.dzir.org:465
139891305443776:error:1408F10B:SSL routines:ssl3_get_record:wrong version number:../ssl/record/ssl3_record.c:332:

使用nc工具连接得到明文响应:

# nc mrs.dzir.org 465
220 MailRelayServer ESMTP server ready

如果只更改一个设置,上述代码(包括最后的更改(可以正常工作。

在函数connect((中的SocketServerClient类中,更改行

$bOK = stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_TLSv1_3_SERVER);

$bOK = stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_TLS_SERVER);

GMail(Android(和Outlook 2019似乎都无法使用TLS 1.3。
Outlook 2019本机最多可以使用TLS 1.1,使用IISCrypto可以学习TLS 1.2。

最新更新