├── php
├── php.list
├── ext
│ └── xdebug.ini
└── php.ini
├── .env.example
├── .gitignore
├── bin
├── coverage.sh
├── test.sh
└── dev_setup.sh
├── phpcs.xml
├── src
├── Smtpd.php
├── Thread.php
├── Event.php
├── StringParser.php
├── Server.php
└── Client.php
├── .editorconfig
├── tests
├── BasicTest.php
├── ThreadTest.php
├── TestObj.php
├── PhpMailerTest.php
├── StringParserTest.php
├── ServerTest.php
└── ClientTest.php
├── LICENSE
├── phpunit.xml
├── composer.json
├── CHANGELOG-v0.md
├── Vagrantfile
├── example.php
└── README.md
/php/php.list:
--------------------------------------------------------------------------------
1 |
2 | deb https://packages.sury.org/php/ stretch main
3 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | IMAGE_NAME="thefox21/smtpd"
2 | IMAGE_NAME_SHORT="smtpd"
3 | GITHUB_API_TOKEN=""
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | CHANGELOG-*.txt
2 | composer.lock
3 | composer.phar
4 | /vendor/
5 | /tmp/
6 | /.idea/
7 | /.vagrant/
8 | .env
9 | test.php
10 | *.pem
11 |
--------------------------------------------------------------------------------
/php/ext/xdebug.ini:
--------------------------------------------------------------------------------
1 | zend_extension=xdebug.so
2 | xdebug.remote_enable=On
3 | xdebug.remote_host="@HOST_IP@"
4 | xdebug.remote_port=9000
5 | xdebug.remote_handler="dbgp"
6 | xdebug.remote_autostart=1
7 |
--------------------------------------------------------------------------------
/bin/coverage.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | SCRIPT_BASEDIR=$(dirname "$0")
4 |
5 |
6 | set -e
7 | cd "${SCRIPT_BASEDIR}/.."
8 |
9 | mkdir -p tmp
10 | vendor/bin/phpunit --coverage-html tmp/coverage
11 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | src
6 | tests
7 |
8 | vendor/
9 |
10 |
--------------------------------------------------------------------------------
/src/Smtpd.php:
--------------------------------------------------------------------------------
1 | assertTrue(defined('TEST'));
12 | $this->assertFalse(defined('NO_TEST'));
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/bin/test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | SCRIPT_BASEDIR=$(dirname "$0")
4 |
5 |
6 | set -e
7 | cd "${SCRIPT_BASEDIR}/.."
8 |
9 | mkdir -p tmp
10 |
11 | # PHP Code Sniffer
12 | ./vendor/bin/phpcs --config-set ignore_warnings_on_exit 1
13 | ./vendor/bin/phpcs --config-show
14 | ./vendor/bin/phpcs
15 |
16 | # PHPUnit
17 | vendor/bin/phpunit
18 |
19 | # PHPStan
20 | #vendor/bin/phpstan analyse --no-progress --level 5 src tests
21 |
--------------------------------------------------------------------------------
/tests/ThreadTest.php:
--------------------------------------------------------------------------------
1 | setExit();
15 | $this->assertEquals(1, $thread->getExit());
16 |
17 | $thread->setExit(2);
18 | $this->assertEquals(2, $thread->getExit());
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Thread.php:
--------------------------------------------------------------------------------
1 | exit = $exit;
22 | }
23 |
24 | /**
25 | * @return int
26 | */
27 | public function getExit(): int
28 | {
29 | return $this->exit;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/bin/dev_setup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | SCRIPT_BASEDIR=$(dirname "$0")
4 | COMPOSER_OPTS=(
5 | --no-suggest
6 | --no-progress
7 | --no-interaction
8 | )
9 |
10 | set -e
11 | cd "${SCRIPT_BASEDIR}/.."
12 |
13 | which php &> /dev/null || { echo 'ERROR: php not found in PATH'; exit 1; }
14 | which curl &> /dev/null || { echo 'ERROR: curl not found in PATH'; exit 1; }
15 |
16 | if which composer &> /dev/null; then
17 | composer install ${COMPOSER_OPTS[@]}
18 | else
19 | if [[ ! -f composer.phar ]] ; then
20 | curl -sS https://getcomposer.org/installer | php
21 | chmod u=rwx,go=rx composer.phar
22 | fi
23 |
24 | php composer.phar install ${COMPOSER_OPTS[@]}
25 | fi
26 |
--------------------------------------------------------------------------------
/tests/TestObj.php:
--------------------------------------------------------------------------------
1 | getTrigger()."\n");
20 | return 43;
21 | }
22 |
23 | /**
24 | * @param Event $event
25 | * @param \Closure $method
26 | * @param array $credentials
27 | * @return bool
28 | */
29 | public function test2($event, $method, $credentials)
30 | {
31 | #fwrite(STDOUT, 'my function: '.$event->getTrigger()."\n");
32 | return true;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2014 Christian Mayer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | tests/BasicTest.php
28 | tests/ClientTest.php
29 | tests/ServerTest.php
30 | tests/StringParserTest.php
31 | tests/ThreadTest.php
32 |
33 |
34 |
35 |
36 | src
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "thefox/smtpd",
3 | "description": "SMTP server (library) written in pure PHP.",
4 | "license": "MIT",
5 | "type": "library",
6 | "keywords": [
7 | "smtp",
8 | "email",
9 | "mail",
10 | "server",
11 | "daemon"
12 | ],
13 | "homepage": "https://fox21.at",
14 | "authors": [
15 | {
16 | "name": "Christian Mayer",
17 | "email": "christian@fox21.at",
18 | "homepage": "https://fox21.at"
19 | }
20 | ],
21 | "require": {
22 | "php": "^7.0",
23 | "zendframework/zend-mail": "^2.3",
24 | "symfony/options-resolver": "^3.3",
25 | "psr/log": "^1.0",
26 | "thefox/network": "^1.0"
27 | },
28 | "require-dev": {
29 | "phpunit/phpunit": "^6.2",
30 | "phpmailer/phpmailer": "^5.2",
31 | "phpstan/phpstan": "^0.9",
32 | "monolog/monolog": "^1.23",
33 | "squizlabs/php_codesniffer": "^3.3"
34 | },
35 | "autoload": {
36 | "psr-4": {
37 | "TheFox\\Smtp\\": "src"
38 | }
39 | },
40 | "autoload-dev": {
41 | "psr-4": {
42 | "TheFox\\Test\\": "tests"
43 | }
44 | },
45 | "extra": {
46 | "branch-alias": {
47 | "dev-master": "1.x.x-dev"
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/PhpMailerTest.php:
--------------------------------------------------------------------------------
1 | isSMTP();
20 | $mail->Host = '127.0.0.1:20025';
21 | $mail->SMTPAuth = false;
22 |
23 | $mail->From = 'from@example.com';
24 | $mail->FromName = 'Mailer';
25 | $mail->addAddress('to1@example.com', 'Joe User');
26 | $mail->addAddress('to2@example.com');
27 | $mail->addReplyTo('reply@example.com', 'Information');
28 | $mail->addCC('cc@example.com');
29 | $mail->addBCC('bcc@example.com');
30 | $mail->isHTML(false);
31 |
32 | $body = '';
33 | $body .= 'This is the message body.' . Client::MSG_SEPARATOR;
34 | $body .= '.' . Client::MSG_SEPARATOR;
35 | $body .= '..' . Client::MSG_SEPARATOR;
36 | $body .= '.test.' . Client::MSG_SEPARATOR;
37 | $body .= 'END' . Client::MSG_SEPARATOR;
38 |
39 | $mail->Subject = 'Here is the subject';
40 | $mail->Body = $body;
41 |
42 | $this->assertTrue($mail->send());
43 |
44 | fwrite(STDOUT, 'mail info: /' . $mail->ErrorInfo . '/' . "\n");
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/CHANGELOG-v0.md:
--------------------------------------------------------------------------------
1 | # Release Notes for SMTPd v0.x
2 |
3 | ## v0.8.0 [unreleased]
4 |
5 | - MIT License
6 |
7 | ## v0.7.0
8 |
9 | - Add capability to reject RCPT addresses via an event handler. #11
10 | - Use PHP 7.0 inside Docker.
11 | - Use PSR-4.
12 | - Dev Docker setup.
13 |
14 | ## v0.6.1
15 |
16 | - Moved thefox/network to require. Fixes #10.
17 |
18 | ## v0.6.0
19 |
20 | - Use thefox/network 1.0.
21 | - Removed unused thefox/utilities 1.1.
22 |
23 | ## v0.5.0
24 |
25 | - Functions renamed.
26 | - Moved Network namespace to separate Repo.
27 | - Use PSR Logger Interface instead of own logger.
28 |
29 | ## v0.4.0
30 |
31 | - PSR1/PSR2
32 | - DocBlocks added.
33 | - Moved Make targets to Bash scripts.
34 | - PHP7 only.
35 |
36 | ## v0.3.2
37 |
38 | - Suggestions by PHPStan fixed.
39 | - Test against PHP 7.1.
40 |
41 | ## v0.3.1
42 |
43 | - Bugfix: use defined() to check to const. #6
44 |
45 | ## v0.3.0
46 |
47 | - Merge pull request #8 from ashleyhood/feature-starttls
48 | - Stand-alone server removed. #7 #9
49 | - Releases script removed.
50 | - PHP 7 ready.
51 | - Clean up.
52 |
53 | ## v0.2.0
54 |
55 | - [RFC 4954](https://tools.ietf.org/html/rfc4954) ESMTP implemented.
56 |
57 | ## v0.1.4
58 |
59 | - Code style fix.
60 |
61 | ## v0.1.3
62 |
63 | - Tests.
64 | - Release script improvement.
65 |
66 | ## v0.1.2
67 |
68 | - PHP 5.3 support fixes.
69 | - Travis support added.
70 |
71 | ## v0.1.1
72 |
73 | - Version numbers.
74 |
75 | ## v0.1.0
76 |
77 | - Basic implementation.
78 | - User events.
79 |
--------------------------------------------------------------------------------
/Vagrantfile:
--------------------------------------------------------------------------------
1 | # -*- mode: ruby -*-
2 | # vi: set ft=ruby :
3 |
4 | Vagrant.configure('2') do |config|
5 | config.vm.box = 'generic/debian9'
6 | config.vm.box_check_update = false
7 |
8 | config.vm.hostname = 'smtpd'
9 | config.vm.network 'forwarded_port', guest: 20025, host: 20025
10 |
11 | config.vm.synced_folder '.', '/app'
12 |
13 | config.vm.provider 'virtualbox' do |vb|
14 | vb.gui = false
15 | vb.memory = 1024
16 | end
17 |
18 | config.vm.provision 'shell' do |s|
19 | s.env = {
20 | 'DEBIAN_FRONTEND' => 'noninteractive',
21 | 'PHP_VERSION' => '7.0',
22 | 'WORKING_DIR' => '/app',
23 | }
24 | s.inline = <<-SHELL
25 | echo "cd ${WORKING_DIR}" >> /home/vagrant/.bashrc
26 | echo "export PHP_IDE_CONFIG='serverName=vagrant'" >> /home/vagrant/.bashrc
27 |
28 | apt-get install -y apt-transport-https ca-certificates
29 |
30 | cp ${WORKING_DIR}/php/php.list /etc/apt/sources.list.d/php.list
31 | [[ ! -f /etc/apt/trusted.gpg.d/php.gpg ]] && wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg
32 |
33 | apt-get update -yqq
34 | apt-get upgrade -y
35 | apt-get install -y htop vim lsof net-tools rsync zlib1g-dev git php${PHP_VERSION}-dev php${PHP_VERSION}-cli php${PHP_VERSION}-zip php${PHP_VERSION}-xml php${PHP_VERSION}-mbstring composer
36 |
37 | netstat -rn | grep '^0.0.0.0 ' | cut -d ' ' -f10 > /tmp/host_ip.txt
38 | host_ip=$(cat /tmp/host_ip.txt)
39 |
40 | cp ${WORKING_DIR}/php/php.ini /etc/php/${PHP_VERSION}/cli/php.ini
41 |
42 | pecl install xdebug
43 | sed -e "s/@HOST_IP@/$host_ip/g" ${WORKING_DIR}/php/ext/xdebug.ini > /etc/php/${PHP_VERSION}/cli/conf.d/20-xdebug.ini
44 |
45 | echo 'done'
46 | SHELL
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/src/Event.php:
--------------------------------------------------------------------------------
1 | trigger = $trigger;
44 | $this->object = $object;
45 | $this->function = $function;
46 | }
47 |
48 | /**
49 | * @return int|null
50 | */
51 | public function getTrigger()
52 | {
53 | return $this->trigger;
54 | }
55 |
56 | /**
57 | * @return mixed
58 | */
59 | public function getReturnValue()
60 | {
61 | return $this->returnValue;
62 | }
63 |
64 | /**
65 | * @param array $args
66 | * @return mixed
67 | */
68 | public function execute(array $args = [])
69 | {
70 | $object = $this->object;
71 | $function = $this->function;
72 |
73 | array_unshift($args, $this);
74 |
75 | if ($object) {
76 | $this->returnValue = call_user_func_array([$object, $function], $args);
77 | } else {
78 | $this->returnValue = call_user_func_array($function, $args);
79 | }
80 |
81 | return $this->returnValue;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/example.php:
--------------------------------------------------------------------------------
1 | 'UK',
14 | 'stateOrProvinceName' => 'Isle Of Wight',
15 | 'localityName' => 'Cowes',
16 | 'organizationName' => 'Open Sauce Systems',
17 | 'organizationalUnitName' => 'Dev',
18 | 'commonName' => '127.0.0.1',
19 | 'emailAddress' => 'info@opensauce.systems',
20 | ];
21 |
22 | // Generate certificate
23 | $privkey = openssl_pkey_new();
24 | $cert = openssl_csr_new($dn, $privkey);
25 | $cert = openssl_csr_sign($cert, null, $privkey, 365);
26 |
27 | // Generate PEM file
28 | $pem = [];
29 | openssl_x509_export($cert, $pem[0]);
30 | openssl_pkey_export($privkey, $pem[1]);
31 | $pem = implode($pem);
32 |
33 | // Save PEM file
34 | $pemfile = __DIR__ . '/server.pem';
35 | file_put_contents($pemfile, $pem);
36 |
37 | $contextOptions = [
38 | 'ssl' => [
39 | 'verify_peer' => false,
40 | 'local_cert' => $pemfile,
41 | 'allow_self_signed' => true,
42 | ],
43 | ];
44 |
45 | // Create a Logger with Monolog.
46 | $logger = new Logger('smtp_example');
47 | $logger->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG));
48 |
49 | $options = [
50 | 'ip' => '127.0.0.1',
51 | 'port' => 20026,
52 | 'logger' => $logger,
53 | ];
54 | $server = new Server($options);
55 |
56 | if (!$server->listen($contextOptions)) {
57 | print 'Server could not listen.' . "\n";
58 | exit(1);
59 | }
60 |
61 | $sendEvent = new Event(Event::TRIGGER_NEW_MAIL, null, function (Event $event, string $from, array $rcpts, Message $mail) {
62 | // Do stuff: DNS lookup the MX record for the recipient's domain,
63 | // check whether the recipient is on a whitelist,
64 | // handle the email, etc, ...
65 |
66 | // For example, use PHPMailer to reply the mail through mail servers.
67 | $mailer = new PHPMailer();
68 | $mailer->IsSMTP();
69 | $mailer->SMTPAuth = true;
70 | $mailer->SMTPSecure = 'tls';
71 | $mailer->Host = 'smtp.example.com';
72 | $mailer->Port = 587;
73 | $mailer->Username = 'example@example.com';
74 | $mailer->Password = 'your_password';
75 | $mailer->SetFrom('example@example.com', 'John Doe');
76 | $mailer->Subject = $mail->getSubject();
77 | $mailer->AltBody = $mail->getBody();
78 | $mailer->MsgHTML($mail->getBody());
79 |
80 | foreach ($rcpts as $rcptId => $rcpt) {
81 | $mailer->AddAddress($rcpt);
82 | }
83 |
84 | if (!$mailer->Send()) {
85 | throw new Exception($mailer->ErrorInfo);
86 | }
87 | });
88 |
89 | $authEvent = new Event(Event::TRIGGER_AUTH_ATTEMPT, null, function ($event, $type, $credentials): bool {
90 | // Do stuff: Check credentials against database, ...
91 |
92 | return true;
93 | });
94 |
95 | $server->addEvent($sendEvent);
96 | $server->addEvent($authEvent);
97 |
98 | // `$server->loop()` is only a while-loop with `$server->run()` executed.
99 | // If you also need to process other things in your application as well
100 | // it's recommded to execute `$server->run()` from time to time.
101 | // You need to execute `$server->run()` in your own project to keep the SMTP server updated.
102 | // If you use your own loop to keep everything running consider executing `$server->run()` from time to time.
103 | $server->loop();
104 |
--------------------------------------------------------------------------------
/src/StringParser.php:
--------------------------------------------------------------------------------
1 | str = $str;
46 | $this->str = trim($this->str);
47 | $this->len = strlen($this->str);
48 | $this->argsMax = $argsMax;
49 | }
50 |
51 | private function reset()
52 | {
53 | $this->argsId = -1;
54 | $this->args = [];
55 | $this->argsLen = 0;
56 | }
57 |
58 | private function fixPrev()
59 | {
60 | if ($this->argsId >= 0) {
61 | if ($this->args[$this->argsId]
62 | && $this->args[$this->argsId][0] == '"'
63 | && substr($this->args[$this->argsId], -1) == '"'
64 | ) {
65 | $tmp = substr(substr($this->args[$this->argsId], 1), 0, -1);
66 | if (strpos($tmp, '"') === false) {
67 | $this->args[$this->argsId] = $tmp;
68 | $this->argsLen = count($this->args);
69 | }
70 | }
71 | }
72 | }
73 |
74 | /**
75 | * @param string $char
76 | */
77 | private function charNew(string $char = '')
78 | {
79 | if ($this->argsMax === null || $this->argsLen < $this->argsMax) {
80 | $this->fixPrev();
81 | $this->argsId++;
82 | $this->args[$this->argsId] = $char;
83 | $this->argsLen = count($this->args);
84 | }
85 | }
86 |
87 | /**
88 | * @param string $char
89 | */
90 | private function charAppend(string $char)
91 | {
92 | if ($this->argsId != -1) {
93 | $this->args[$this->argsId] .= $char;
94 | }
95 | }
96 |
97 | /**
98 | * @return array
99 | */
100 | public function parse(): array
101 | {
102 | $this->reset();
103 |
104 | $str = $this->str;
105 | $in = false;
106 | //$prevChar = ' ';
107 | $endChar = '';
108 |
109 | for ($pos = 0; $pos < $this->len; $pos++) {
110 | $char = $str[$pos];
111 | $nextChar = ($pos < $this->len - 1) ? $str[$pos + 1] : '';
112 |
113 | if ($in) {
114 | if ($char == $endChar) {
115 | if ($pos == $this->len - 1 || $this->argsMax === null || $this->argsLen < $this->argsMax) {
116 | if ($char == '"') {
117 | $this->charAppend($char);
118 | }
119 | $in = false;
120 | } else {
121 | $this->charAppend($char);
122 | }
123 | } else {
124 | $this->charAppend($char);
125 | }
126 | } else {
127 | if ($this->argsMax === null || $this->argsLen < $this->argsMax) {
128 | if ($char == '"') {
129 | $this->charNew($char);
130 | $endChar = '"';
131 | $in = true;
132 | } elseif ($char == ' ') {
133 | if ($nextChar != ' ' && $nextChar != '"') {
134 | $this->charNew();
135 | $endChar = ' ';
136 | $in = true;
137 | }
138 | } else {
139 | $this->charNew($char);
140 | $endChar = ' ';
141 | $in = true;
142 | }
143 | }
144 | /*else{
145 | fwrite(STDOUT, ' -> char append'."\n");
146 | $this->charAppend($char);
147 | }*/
148 | }
149 |
150 | //$prevChar = $char;
151 | }
152 |
153 | $this->fixPrev();
154 |
155 | return $this->args;
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/tests/StringParserTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('-', $str[0]);
14 | $this->assertEquals('A', $str[1]);
15 | $this->assertEquals('.', $str[4]);
16 | }
17 |
18 | public function testBasic2()
19 | {
20 | $str = 'arg1 arg2 arg3 "arg4" ';
21 | $this->assertEquals(22, strlen($str));
22 | $this->assertEquals(21, strlen(trim($str)));
23 | }
24 |
25 | public function providerParse()
26 | {
27 | $rv = [];
28 |
29 | $expect = ['arg1', 'arg2', 'arg3', 'arg4'];
30 | $rv[] = ['arg1 arg2 arg3 arg4', $expect, null];
31 |
32 | $expect = ['arg1', 'arg2', 'arg3 arg4'];
33 | $rv[] = ['arg1 arg2 arg3 arg4', $expect, 3];
34 | $rv[] = ['arg1 arg2 arg3 arg4', $expect, 3];
35 | $rv[] = ['arg1 arg2 arg3 arg4', $expect, 3];
36 | $rv[] = ['arg1 arg2 arg3 arg4', $expect, 3];
37 |
38 | $expect = ['arg1', 'arg2', 'arg3 arg4'];
39 | $rv[] = ['arg1 arg2 arg3 arg4', $expect, 3];
40 | $rv[] = ['arg1 arg2 arg3 arg4', $expect, 3];
41 |
42 | $expect = ['arg1', 'arg2', 'arg3', 'arg4'];
43 | $rv[] = ['arg1 arg2 arg3 "arg4"', $expect, 4];
44 | $rv[] = ['arg1 arg2 "arg3" arg4', $expect, 4];
45 | $rv[] = ['arg1 arg2 "arg3" arg4', $expect, 4];
46 | $rv[] = ['arg1 arg2 "arg3" arg4', $expect, 4];
47 | $rv[] = ['arg1 arg2 "arg3" arg4', $expect, 4];
48 | $rv[] = ['arg1 arg2 arg3 "arg4"', $expect, 4];
49 | $rv[] = ['arg1 arg2 arg3 "arg4" ', $expect, 4];
50 | $rv[] = ['arg1 arg2 arg3 "arg4" ', $expect, 4];
51 | $rv[] = ['arg1 arg2 "arg3" "arg4"', $expect, 4];
52 |
53 | $expect = ['arg1', 'arg2', 'arg3 arg4', 'arg5'];
54 | $rv[] = ['arg1 arg2 "arg3 arg4" arg5', $expect, 5];
55 |
56 | $expect = ['arg1', 'arg2', 'arg3', 'arg4', 'arg5'];
57 | $rv[] = ['arg1 arg2 arg3 arg4 arg5', $expect, 10];
58 |
59 | $expect = ['arg1', 'arg2', 'arg3 arg4'];
60 | $rv[] = ['arg1 arg2 "arg3 arg4"', $expect, 3];
61 | $rv[] = ['arg1 arg2 "arg3 arg4" ', $expect, 3];
62 | $rv[] = ['arg1 arg2 "arg3 arg4" ', $expect, 3];
63 | $rv[] = ['arg1 arg2 "arg3 arg4"', $expect, 3];
64 |
65 | $expect = ['arg1', 'arg2', '0'];
66 | $rv[] = ['arg1 arg2 0', $expect, 3];
67 |
68 | $expect = ['arg1', 'arg2', 0];
69 | $rv[] = ['arg1 arg2 0', $expect, 3];
70 |
71 | $expect = ['arg1', 'arg2', '000'];
72 | $rv[] = ['arg1 arg2 000', $expect, 3];
73 |
74 | $expect = ['arg1', 'arg2', '123'];
75 | $rv[] = ['arg1 arg2 123', $expect, 3];
76 |
77 | $expect = ['arg1', 'arg2', '0123'];
78 | $rv[] = ['arg1 arg2 0123', $expect, 3];
79 |
80 | $expect = ['arg1', 'arg2', 'arg3 (arg4 "arg5 arg6") arg7'];
81 | $rv[] = ['arg1 arg2 arg3 (arg4 "arg5 arg6") arg7', $expect, 3];
82 |
83 | $expect = ['arg1', 'arg2', 'arg3 ("arg5 arg6" arg4) arg7'];
84 | $rv[] = ['arg1 arg2 arg3 ("arg5 arg6" arg4) arg7', $expect, 3];
85 |
86 | $expect = ['arg1', 'arg2', 'A"arg3"E'];
87 | $rv[] = ['arg1 arg2 A"arg3"E', $expect, 3];
88 |
89 |
90 | $expect = ['arg1', '', 'arg2'];
91 | $rv[] = ['arg1 "" arg2', $expect, 3];
92 |
93 | $expect = ['arg1', 'arg2', '', 'arg4'];
94 | $rv[] = ['arg1 arg2 "" arg4', $expect, null];
95 |
96 | $expect = ['arg1', 'arg2', '"" arg4'];
97 | $rv[] = ['arg1 arg2 "" arg4', $expect, 3];
98 |
99 | $expect = ['arg1', 'arg2', '"" "arg4"'];
100 | $rv[] = ['arg1 arg2 "" "arg4"', $expect, 3];
101 |
102 | $expect = ['', 'arg4'];
103 | $rv[] = ['"" arg4', $expect, null];
104 |
105 | $expect = ['"" arg4'];
106 | $rv[] = ['"" arg4', $expect, 1];
107 |
108 | $expect = ['arg1', 'arg2', 'arg3', 'arg4'];
109 | $rv[] = ['arg1 arg2 "arg3" arg4', $expect, null];
110 |
111 | $expect = ['arg1', 'arg2', '"arg3" arg4'];
112 | $rv[] = ['arg1 arg2 "arg3" arg4', $expect, 3];
113 |
114 | $expect = ['arg1', 'arg2', ' arg3', 'arg4'];
115 | $rv[] = ['arg1 arg2 " arg3" arg4', $expect, null];
116 |
117 | $expect = ['arg1', 'arg2', 'arg3 ', 'arg4'];
118 | $rv[] = ['arg1 arg2 "arg3 " arg4', $expect, null];
119 |
120 | $expect = ['arg1', 'arg2', ' arg3 ', 'arg4'];
121 | $rv[] = ['arg1 arg2 " arg3 " arg4', $expect, null];
122 |
123 | return $rv;
124 | }
125 |
126 | /**
127 | * @dataProvider providerParse
128 | * @group large
129 | * @param string $msgRaw
130 | * @param mixed $expect
131 | * @param null|int $argsMax
132 | */
133 | public function testParse1(string $msgRaw, $expect, $argsMax = null)
134 | {
135 | $str = new StringParser($msgRaw, $argsMax);
136 | $this->assertEquals($expect, $str->parse());
137 | }
138 |
139 | public function testParse2()
140 | {
141 | $str = new StringParser('arg1 arg2 arg3', 10);
142 | $str->parse();
143 | $this->assertTrue(true);
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SMTPd
2 |
3 | SMTP server (library) for receiving emails, written in pure PHP. This library provides an interface to the SMTP server-side protocol with PHP. It creates a `\Zend\Mail\Message` Class object for every incoming email and hands this object to a custom PHP function for further processing. The project is in Beta status, so it's not recommended for production use.
4 |
5 | The `d` in `SMTPd` stands for [Daemon](https://en.wikipedia.org/wiki/Daemon_(computing)). This script can run in background like any other daemon process. It's not meant for running as a webapplication.
6 |
7 | ## Why this project?
8 |
9 | Believe it or not, **email is still the killer feature of the Internet**. There are tons of projects like [PHPMailer](https://github.com/PHPMailer/PHPMailer): to send emails programmatically (with PHP). But there are not so many to receive emails from SMTP.
10 |
11 | With this interface you can do something like this for your app users:
12 |
13 | ```
14 | +------+ +------------------------+ +-------+ +--------------+
15 | | User +---> | MUA (like Thunderbird) +---> | SMTPd +---> | Your PHP App |
16 | +------+ +------------------------+ +-------+ +--------------+
17 | ```
18 |
19 | This is useful when you have a messaging application written in PHP but no graphical user interface for it. So your graphical user interface can be any [email client](http://en.wikipedia.org/wiki/Email_client). [Thunderbird](https://www.mozilla.org/en-US/thunderbird/) for instance.
20 |
21 | ## Project Outlines
22 |
23 | The project outlines as described in my blog post about [Open Source Software Collaboration](https://blog.fox21.at/2019/02/21/open-source-software-collaboration.html).
24 |
25 | - The main purpose of this software is to provide a server-side SMTP API for PHP scripts.
26 | - Although the RFC implementations are not completed yet, they must be strict.
27 | - More features can be possible in the future. In perspective of the protocols the features must be a RFC implementation.
28 | - This list is open. Feel free to request features.
29 |
30 | ## Planned Features
31 |
32 | - Full [RFC 821](https://tools.ietf.org/html/rfc821) implementation.
33 | - Full [RFC 1651](https://tools.ietf.org/html/rfc1651) implementation.
34 | - Full [RFC 1869](https://tools.ietf.org/html/rfc1869) implementation.
35 | - Replace `Zend\Mail` with a better solution.
36 |
37 | ## Installation
38 |
39 | The preferred method of installation is via [Packagist](https://packagist.org/packages/thefox/smtpd) and [Composer](https://getcomposer.org/). Run the following command to install the package and add it as a requirement to composer.json:
40 |
41 | ```bash
42 | composer require thefox/smtpd
43 | ```
44 |
45 | ## Delivery
46 |
47 | At the moment the server accepts all incoming emails. You decide what happens with incoming emails by adding `Event`s to the `Server` object (`$server->eventAdd($event)`). The server can handle certain events. Each event will be executed on a certain trigger. Even if you don't add any Events to the Server it accepts all incoming emails.
48 |
49 | ## Events
50 |
51 | At the moment there are two Event Triggers.
52 |
53 | - `TRIGGER_NEW_MAIL`: will be triggered when a Client has finished transmitting a new email.
54 | - `TRIGGER_AUTH_ATTEMPT`: will be triggered when a Client wants to authenticate. Return a boolean from the callback function whether the authentication was successful or not.
55 |
56 | ## Examples
57 |
58 | See also [`example.php`](example.php) file for full examples.
59 |
60 | ### Trigger New Mail Example
61 |
62 | ```php
63 | $server = new Server(...);
64 |
65 | $event = new Event(Event::TRIGGER_NEW_MAIL, null, function(Event $event, $from, $rcpts, $mail){
66 | // Do stuff: handle email, ...
67 | });
68 | $server->addEvent($event);
69 | $server->loop();
70 | ```
71 |
72 | ### Trigger Auth Example
73 |
74 | ```php
75 | $server = new Server(...);
76 |
77 | $event = new Event(Event::TRIGGER_AUTH_ATTEMPT, null, function(Event $event, $type, $credentials): bool{
78 | // Do stuff: Check credentials against database, ...
79 | return true;
80 | });
81 | $server->addEvent($event);
82 | $server->loop();
83 | ```
84 |
85 | ### Use SMTP Server with own loop
86 |
87 | ```php
88 | $server = new Server(...);
89 |
90 | // Set up server here.
91 | // Add Events, etc, ...
92 |
93 | while(myApplicationRuns()){
94 | // Do stuff your application needs.
95 | // ...
96 |
97 | // Run main SMTPd loop, once.
98 | $server->run();
99 | usleep(10000); // Never run a main thread loop without sleep. Never!
100 | }
101 | ```
102 |
103 | ## RFC 821 Implementation
104 |
105 | ### Complete implementation
106 |
107 | - 3.5 OPENING AND CLOSING
108 |
109 | ### Incomplete implementation
110 |
111 | - 3.1 MAIL
112 | - 4.1.1 COMMAND SEMANTICS
113 | - HELO
114 | - MAIL
115 | - RCPT
116 | - DATA
117 | - NOOP
118 | - QUIT
119 |
120 | ## RFC 1651 Implementation
121 |
122 | ### Complete implementation
123 |
124 | - 4.1.1 First command
125 | - 4.5 Error responses from extended servers
126 |
127 | ## RFC 3207 Implementation
128 |
129 | ## RFC 4954 Implementation
130 |
131 | - 4. The AUTH Command
132 |
133 | ## Related Links
134 |
135 | - [RFC 821](https://tools.ietf.org/html/rfc821)
136 | - [RFC 1425](https://tools.ietf.org/html/rfc1425)
137 | - [RFC 1651](https://tools.ietf.org/html/rfc1651)
138 | - [RFC 1869](https://tools.ietf.org/html/rfc1869)
139 | - [RFC 2821](https://tools.ietf.org/html/rfc2821)
140 | - [RFC 3207](https://tools.ietf.org/html/rfc3207)
141 | - [RFC 4954](https://tools.ietf.org/html/rfc4954)
142 |
143 | ## Related Projects
144 |
145 | - [IMAPd](https://github.com/TheFox/imapd)
146 |
147 | ## Project Links
148 |
149 | - [Packagist Package](https://packagist.org/packages/thefox/smtpd)
150 |
--------------------------------------------------------------------------------
/tests/ServerTest.php:
--------------------------------------------------------------------------------
1 | newClient($socket);
21 | $this->assertTrue($client instanceof Client);
22 | }
23 |
24 | public function testClientGetByHandle()
25 | {
26 | $socket = new Socket();
27 | $socket->bind('127.0.0.1', 22143);
28 | $socket->listen();
29 | $handle1 = $socket->getHandle();
30 |
31 | $server = new Server();
32 |
33 | $client1 = $server->newClient($socket);
34 | $client2 = $server->getClientByHandle($handle1);
35 | $this->assertEquals($client1, $client2);
36 |
37 | $res = fopen('data://text/plain,string', 'r');
38 | $this->assertNull($server->getClientByHandle($res));
39 |
40 | $server->shutdown();
41 | }
42 |
43 | public function testClientRemove()
44 | {
45 | $socket = new Socket();
46 | $socket->bind('127.0.0.1', 22143);
47 | $socket->listen();
48 |
49 | $server = new Server();
50 |
51 | $client = $server->newClient($socket);
52 | $server->removeClient($client);
53 |
54 | $this->assertTrue($client->getStatus('hasShutdown'));
55 |
56 | $server->shutdown();
57 | }
58 |
59 | public function testEvent()
60 | {
61 | $server = new Server();
62 |
63 | $testData = 21;
64 | $phpunit = $this;
65 | $fn = function ($event, $from, $rcpt, $mail) use ($phpunit, &$testData) {
66 | $testData = 24;
67 |
68 | $phpunit->assertEquals('from@example.com', $from);
69 | $phpunit->assertEquals(
70 | ['to1@example.com', 'to2@example.com', 'cc@example.com', 'bcc@example.com'],
71 | $rcpt
72 | );
73 |
74 | $current = [];
75 | foreach ($mail->getTo() as $n => $address) {
76 | $current[] = $address->toString();
77 | }
78 | $phpunit->assertEquals(['Joe User ', ''], $current);
79 |
80 | $phpunit->assertEquals('Here is the subject', $mail->getSubject());
81 |
82 | return 42;
83 | };
84 | $event1 = new Event(Event::TRIGGER_NEW_MAIL, null, $fn);
85 | $server->addEvent($event1);
86 |
87 | $testObj = new TestObj();
88 | $event2 = new Event(Event::TRIGGER_NEW_MAIL, $testObj, 'test1');
89 | $server->addEvent($event2);
90 |
91 | $mail = '';
92 | $mail .= 'Date: Thu, 31 Jul 2014 22:18:51 +0200' . Client::MSG_SEPARATOR;
93 | $mail .= 'To: Joe User , to2@example.com' . Client::MSG_SEPARATOR;
94 | $mail .= 'From: Mailer ' . Client::MSG_SEPARATOR;
95 | $mail .= 'Cc: cc@example.com' . Client::MSG_SEPARATOR;
96 | $mail .= 'Reply-To: Information ' . Client::MSG_SEPARATOR;
97 | $mail .= 'Subject: Here is the subject' . Client::MSG_SEPARATOR;
98 | $mail .= 'MIME-Version: 1.0' . Client::MSG_SEPARATOR;
99 | $mail .= 'Content-Type: text/plain; charset=iso-8859-1' . Client::MSG_SEPARATOR;
100 | $mail .= 'Content-Transfer-Encoding: 8bit' . Client::MSG_SEPARATOR;
101 | $mail .= '' . Client::MSG_SEPARATOR;
102 | $mail .= 'This is the message body.' . Client::MSG_SEPARATOR;
103 | $mail .= 'END' . Client::MSG_SEPARATOR;
104 |
105 | $zmail = Message::fromString($mail);
106 |
107 | $rcpt = ['to1@example.com', 'to2@example.com', 'cc@example.com', 'bcc@example.com'];
108 | $server->newMail('from@example.com', $rcpt, $zmail);
109 |
110 | $this->assertEquals(24, $testData);
111 | $this->assertEquals(42, $event1->getReturnValue());
112 | $this->assertEquals(43, $event2->getReturnValue());
113 | }
114 |
115 | public function rcptProvider()
116 | {
117 | return [
118 | 'valid' => ['valid@example.com', true],
119 | 'invalid' => ['invalid@example.com', false],
120 | ];
121 | }
122 |
123 | /**
124 | * @dataProvider rcptProvider
125 | * @param string $mail
126 | * @param bool $valid
127 | */
128 | public function testEventNewRcpt($mail, $valid)
129 | {
130 | $server = new Server();
131 | $phpunit = $this;
132 | $event1 = new Event(Event::TRIGGER_NEW_RCPT, null, function ($event, $rcpt) use ($phpunit, $mail, $valid) {
133 | $phpunit->assertEquals($mail, $rcpt);
134 | return $valid;
135 | });
136 | $server->addEvent($event1);
137 |
138 | $return = $server->newRcpt($mail);
139 | $this->assertEquals($valid, $return);
140 | }
141 |
142 | public function testEventAuthWithFalse()
143 | {
144 | $server = new Server();
145 |
146 | $username = 'testuser';
147 | $password = 'super_secret_password';
148 |
149 | $phpunit = $this;
150 | $event1 = new Event(
151 | Event::TRIGGER_AUTH_ATTEMPT,
152 | null,
153 | function ($event, $method, $credentials) {
154 | return false;
155 | }
156 | );
157 | $server->addEvent($event1);
158 |
159 | $testObj = new TestObj();
160 | $event2 = new Event(Event::TRIGGER_AUTH_ATTEMPT, $testObj, 'test2');
161 | $server->addEvent($event2);
162 |
163 | $method = 'LOGIN';
164 | $credentials = ['user' => base64_encode($username), 'password' => base64_encode($password)];
165 |
166 | $authenticated = $server->authenticateUser($method, $credentials);
167 |
168 | $this->assertFalse($authenticated);
169 | }
170 |
171 | public function testEventAuthWithAllTrue()
172 | {
173 | $server = new Server();
174 |
175 | $username = 'testuser';
176 | $password = 'super_secret_password';
177 |
178 | $phpunit = $this;
179 | $event1 = new Event(
180 | Event::TRIGGER_AUTH_ATTEMPT,
181 | null,
182 | function ($event, $method, $credentials) {
183 | return true;
184 | }
185 | );
186 | $server->addEvent($event1);
187 |
188 | $testObj = new TestObj();
189 | $event2 = new Event(Event::TRIGGER_AUTH_ATTEMPT, $testObj, 'test2');
190 | $server->addEvent($event2);
191 |
192 | $method = 'LOGIN';
193 | $credentials = ['user' => base64_encode($username), 'password' => base64_encode($password)];
194 |
195 | $authenticated = $server->authenticateUser($method, $credentials);
196 |
197 | $this->assertTrue($authenticated);
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/src/Server.php:
--------------------------------------------------------------------------------
1 | setDefaults([
86 | 'ip' => '127.0.0.1',
87 | 'port' => 20025,
88 | 'hostname' => 'localhost.localdomain',
89 | 'logger' => new NullLogger(),
90 | ]);
91 | $this->options = $resolver->resolve($options);
92 |
93 | $this->logger = $this->options['logger'];
94 |
95 | $this->setIp($this->options['ip']);
96 | $this->setPort($this->options['port']);
97 | $this->setHostname($this->options['hostname']);
98 |
99 | $this->logger->info('start');
100 | $this->logger->info('ip = "' . $this->options['ip'] . '"');
101 | $this->logger->info('port = "' . $this->options['port'] . '"');
102 | $this->logger->info('hostname = "' . $this->options['hostname'] . '"');
103 | }
104 |
105 | /**
106 | * @param string $hostname
107 | */
108 | public function setHostname(string $hostname)
109 | {
110 | $this->hostname = $hostname;
111 | }
112 |
113 | /**
114 | * @return string
115 | */
116 | public function getHostname()
117 | {
118 | return $this->hostname;
119 | }
120 |
121 | /**
122 | * @param string $ip
123 | */
124 | public function setIp(string $ip)
125 | {
126 | $this->ip = $ip;
127 | }
128 |
129 | /**
130 | * @param int $port
131 | */
132 | public function setPort(int $port)
133 | {
134 | $this->port = $port;
135 | }
136 |
137 | /**
138 | * @param array $contextOptions
139 | * @return bool
140 | */
141 | public function listen(array $contextOptions = []): bool
142 | {
143 | if (!$this->ip && !$this->port) {
144 | return false;
145 | }
146 |
147 | $this->socket = new Socket();
148 |
149 | $bind = false;
150 | try {
151 | $bind = $this->socket->bind($this->ip, $this->port);
152 | } catch (Exception $e) {
153 | $this->logger->error($e->getMessage());
154 | }
155 |
156 | if ($bind) {
157 | try {
158 | if ($this->socket->listen($contextOptions)) {
159 | $this->logger->notice('listen ok');
160 | $this->isListening = true;
161 |
162 | return true;
163 | }
164 | } catch (Exception $e) {
165 | $this->logger->error($e->getMessage());
166 | }
167 | }
168 |
169 | return false;
170 | }
171 |
172 | /**
173 | * Main Function
174 | * Handles everything, keeps everything up-to-date.
175 | */
176 | public function run()
177 | {
178 | if (!$this->socket) {
179 | throw new RuntimeException('Socket not initialized. You need to execute listen().', 1);
180 | }
181 |
182 | /** @var \resource[] $readHandles */
183 | $readHandles = [];
184 |
185 | /** @var \resource[] $writeHandles */
186 | $writeHandles = [];
187 |
188 | /** @var \resource[] $exceptHandles */
189 | $exceptHandles = [];
190 |
191 | if ($this->isListening) {
192 | $readHandles[] = $this->socket->getHandle();
193 | }
194 | foreach ($this->clients as $clientId => $client) {
195 | // Collect client handles.
196 | $readHandles[] = $client->getSocket()->getHandle();
197 | }
198 |
199 | $handlesChanged = $this->socket->select($readHandles, $writeHandles, $exceptHandles);
200 |
201 | if ($handlesChanged) {
202 | foreach ($readHandles as $readableHandle) {
203 | if ($this->isListening && $readableHandle == $this->socket->getHandle()) {
204 | // Server
205 | $socket = $this->socket->accept();
206 | if ($socket) {
207 | $client = $this->newClient($socket);
208 | $client->sendReady();
209 | //$this->logger->debug('new client: '.$client->getId().', '.$client->getIpPort());
210 | }
211 | } else {
212 | // Client
213 | $client = $this->getClientByHandle($readableHandle);
214 | if ($client) {
215 | if (feof($client->getSocket()->getHandle())) {
216 | $this->removeClient($client);
217 | } else {
218 | //$this->logger->debug('old client: '.$client->getId().', '.$client->getIpPort());
219 | $client->dataRecv();
220 |
221 | if ($client->getStatus('hasShutdown')) {
222 | $this->removeClient($client);
223 | }
224 | }
225 | }
226 | //$this->logger->debug('old client: '.$client->getId().', '.$client->getIpPort());
227 | }
228 | }
229 | }
230 | }
231 |
232 | /**
233 | * Main Loop
234 | */
235 | public function loop()
236 | {
237 | while (!$this->getExit()) {
238 | $this->run();
239 | usleep(static::LOOP_USLEEP);
240 | }
241 |
242 | $this->shutdown();
243 | }
244 |
245 | /**
246 | * Shutdown the server.
247 | * Should be executed before your application exits.
248 | */
249 | public function shutdown()
250 | {
251 | $this->logger->debug('shutdown');
252 |
253 | // Notify all clients.
254 | foreach ($this->clients as $clientId => $client) {
255 | $client->sendQuit();
256 | $this->removeClient($client);
257 | }
258 |
259 | $this->logger->debug('shutdown done');
260 | }
261 |
262 | /**
263 | * Create a new Client for a new incoming socket connection.
264 | *
265 | * @param AbstractSocket $socket
266 | * @return Client
267 | */
268 | public function newClient(AbstractSocket $socket): Client
269 | {
270 | $this->clientsId++;
271 |
272 | $options = [
273 | 'hostname' => $this->getHostname(),
274 | 'logger' => $this->logger,
275 | ];
276 | $client = new Client($options);
277 | $client->setSocket($socket);
278 | $client->setId($this->clientsId);
279 | $client->setServer($this);
280 |
281 | $this->clients[$this->clientsId] = $client;
282 |
283 | return $client;
284 | }
285 |
286 | /**
287 | * Find a Client by socket handle.
288 | *
289 | * @param \resource $handle
290 | * @return Client|null
291 | */
292 | public function getClientByHandle($handle)
293 | {
294 | foreach ($this->clients as $clientId => $client) {
295 | $socket = $client->getSocket();
296 | if ($socket->getHandle() == $handle) {
297 | return $client;
298 | }
299 | }
300 |
301 | return null;
302 | }
303 |
304 | /**
305 | * @param Client $client
306 | */
307 | public function removeClient(Client $client)
308 | {
309 | $this->logger->debug('client remove: ' . $client->getId());
310 |
311 | $client->shutdown();
312 |
313 | $clientsId = $client->getId();
314 | unset($this->clients[$clientsId]);
315 | }
316 |
317 | /**
318 | * @param Event $event
319 | */
320 | public function addEvent(Event $event)
321 | {
322 | $this->eventsId++;
323 | $this->events[$this->eventsId] = $event;
324 | }
325 |
326 | /**
327 | * @param integer $trigger
328 | * @param array $args
329 | */
330 | private function eventExecute(int $trigger, array $args = [])
331 | {
332 | foreach ($this->events as $eventId => $event) {
333 | if ($event->getTrigger() == $trigger) {
334 | $event->execute($args);
335 | }
336 | }
337 | }
338 |
339 | /**
340 | * @param string $from
341 | * @param array $rcpt
342 | * @param \Zend\Mail\Message $mail
343 | */
344 | public function newMail(string $from, array $rcpt, Message $mail)
345 | {
346 | $this->eventExecute(Event::TRIGGER_NEW_MAIL, [$from, $rcpt, $mail]);
347 | }
348 |
349 | /**
350 | * @param string $rcpt
351 | * @return bool
352 | */
353 | public function newRcpt(string $rcpt)
354 | {
355 | foreach ($this->events as $eventId => $event) {
356 | if ($event->getTrigger() == Event::TRIGGER_NEW_RCPT && !$event->execute([$rcpt])) {
357 | return false;
358 | }
359 | }
360 | return true;
361 | }
362 |
363 | /**
364 | * Execute authentication events.
365 | * All authentication events must return true for authentication to be successful
366 | *
367 | * @param string $method
368 | * @param array $credentials
369 | * @return boolean
370 | */
371 | public function authenticateUser(string $method, array $credentials = []): bool
372 | {
373 | $authenticated = false;
374 | $args = [$method, $credentials];
375 |
376 | foreach ($this->events as $eventId => $event) {
377 | if ($event->getTrigger() == Event::TRIGGER_AUTH_ATTEMPT) {
378 | if (!$event->execute($args)) {
379 | return false;
380 | }
381 |
382 | $authenticated = true;
383 | }
384 | }
385 |
386 | return $authenticated;
387 | }
388 | }
389 |
--------------------------------------------------------------------------------
/tests/ClientTest.php:
--------------------------------------------------------------------------------
1 | setId(1);
20 |
21 | $this->assertEquals(1, $client->getId());
22 | }
23 |
24 | public function testSetIp()
25 | {
26 | $client = new Client();
27 | $client->setIp('192.168.241.21');
28 | $this->assertEquals('192.168.241.21', $client->getIp());
29 | }
30 |
31 | public function testGetIp()
32 | {
33 | $client = new Client();
34 | $this->assertEquals('', $client->getIp());
35 | }
36 |
37 | public function testSetPort()
38 | {
39 | $client = new Client();
40 | $client->setPort(1024);
41 | $this->assertEquals(1024, $client->getPort());
42 | }
43 |
44 | public function testGetPort()
45 | {
46 | $client = new Client();
47 | $this->assertEquals(0, $client->getPort());
48 | }
49 |
50 | public function testGetIpPort1()
51 | {
52 | $client = new Client();
53 | $client->setIp('192.168.241.21');
54 | $client->setPort(1024);
55 | $this->assertEquals('192.168.241.21:1024', $client->getIpPort());
56 | }
57 |
58 | public function testGetIpPort2()
59 | {
60 | $client = new Client();
61 | $client->setIpPort('192.168.241.21', 1024);
62 | $this->assertEquals('192.168.241.21:1024', $client->getIpPort());
63 | }
64 |
65 | public function testGetCredentials()
66 | {
67 | $client = new Client();
68 | $client->setCredentials(['user' => 'testuser', 'password' => 'super_secret_password']);
69 | $credentials = $client->getCredentials();
70 | $this->assertEquals('testuser', $credentials['user']);
71 | $this->assertEquals('super_secret_password', $credentials['password']);
72 | }
73 |
74 | public function testGetHostname()
75 | {
76 | $client = new Client();
77 | $host = $client->getHostname();
78 | $this->assertEquals('localhost.localdomain', $host);
79 | }
80 |
81 | public function testMsgHandleHello()
82 | {
83 | $server = new Server();
84 |
85 | $client = new Client();
86 | $client->setServer($server);
87 | $client->setId(1);
88 |
89 | $msg = $client->handleMessage('HELO localhost.localdomain');
90 | $this->assertEquals('250 localhost.localdomain' . Client::MSG_SEPARATOR, $msg);
91 |
92 | $msg = $client->handleMessage('EHLO localhost.localdomain');
93 | $expect = '250-localhost.localdomain' . Client::MSG_SEPARATOR;
94 | $expect .= '250-AUTH PLAIN LOGIN' . Client::MSG_SEPARATOR;
95 | $expect .= '250-STARTTLS' . Client::MSG_SEPARATOR;
96 | $expect .= '250 HELP' . Client::MSG_SEPARATOR;
97 | $this->assertEquals($expect, $msg);
98 |
99 | $msg = $client->handleMessage('XYZ abc');
100 | $this->assertEquals('500 Syntax error, command unrecognized' . Client::MSG_SEPARATOR, $msg);
101 | }
102 |
103 | public function testMsgHandleMail()
104 | {
105 | $server = new Server();
106 |
107 | $event1 = new Event(Event::TRIGGER_NEW_RCPT, null, function ($event, $rcpt) {
108 | return $rcpt !== 'invalid@example.com';
109 | });
110 | $server->addEvent($event1);
111 |
112 | $client = new Client();
113 | $client->setServer($server);
114 | $client->setId(1);
115 |
116 | $msg = $client->handleMessage('MAIL FROM:');
117 | $this->assertEquals('500 Syntax error, command unrecognized' . Client::MSG_SEPARATOR, $msg);
118 |
119 | $msg = $client->handleMessage('RCPT TO:');
120 | $this->assertEquals('500 Syntax error, command unrecognized' . Client::MSG_SEPARATOR, $msg);
121 |
122 | $msg = $client->handleMessage('DATA');
123 | $this->assertEquals('500 Syntax error, command unrecognized' . Client::MSG_SEPARATOR, $msg);
124 |
125 | $msg = $client->handleMessage('HELO localhost.localdomain');
126 | $this->assertEquals('250 localhost.localdomain' . Client::MSG_SEPARATOR, $msg);
127 |
128 | $msg = $client->handleMessage('MAIL');
129 | $this->assertEquals('501 Syntax error in parameters or arguments' . Client::MSG_SEPARATOR, $msg);
130 |
131 | $msg = $client->handleMessage('MAIL FROM:');
132 | $this->assertEquals('250 OK' . Client::MSG_SEPARATOR, $msg);
133 |
134 | $msg = $client->handleMessage('RCPT');
135 | $this->assertEquals('501 Syntax error in parameters or arguments' . Client::MSG_SEPARATOR, $msg);
136 |
137 | $msg = $client->handleMessage('RCPT TO:');
138 | $this->assertEquals('250 OK' . Client::MSG_SEPARATOR, $msg);
139 |
140 | $msg = $client->handleMessage('RCPT TO:');
141 | $this->assertEquals('250 OK' . Client::MSG_SEPARATOR, $msg);
142 |
143 | $msg = $client->handleMessage('RCPT TO:');
144 | $this->assertEquals('550 User unknown' . Client::MSG_SEPARATOR, $msg);
145 |
146 | $msg = $client->handleMessage('DATA');
147 | $this->assertEquals('354 Start mail input; end with .' . Client::MSG_SEPARATOR, $msg);
148 |
149 | $msg = $client->handleMessage('From: Dev1 ');
150 | $this->assertEquals('', $msg);
151 | $msg = $client->handleMessage('To: Dev1 ');
152 | $this->assertEquals('', $msg);
153 | $msg = $client->handleMessage('Subject: Test');
154 | $this->assertEquals('', $msg);
155 | $msg = $client->handleMessage('');
156 | $this->assertEquals('', $msg);
157 | $msg = $client->handleMessage('Body');
158 | $this->assertEquals('', $msg);
159 |
160 | $msg = $client->handleMessage('.');
161 | $this->assertEquals('250 OK' . Client::MSG_SEPARATOR, $msg);
162 |
163 | $msg = $client->handleMessage('HELP');
164 | $this->assertEquals('250 HELO, EHLO, MAIL FROM, RCPT TO, DATA, NOOP, QUIT' . Client::MSG_SEPARATOR, $msg);
165 |
166 | $msg = $client->handleMessage('NOOP');
167 | $this->assertEquals('250 OK' . Client::MSG_SEPARATOR, $msg);
168 |
169 | $msg = $client->handleMessage('QUIT');
170 | $expected = '221 localhost.localdomain Service closing transmission channel' . Client::MSG_SEPARATOR;
171 | $this->assertEquals($expected, $msg);
172 | }
173 |
174 | public function testMsgHandleAuthPlain()
175 | {
176 | $server = new Server();
177 |
178 | /** @var MockBuilder $mockBuilder */
179 | $mockBuilder = $this->getMockBuilder(Client::class);
180 | $mockBuilder->setMethods(['authenticate']);
181 |
182 | /** @var Client|PHPUnit_Framework_MockObject_MockObject $client */
183 | $client = $mockBuilder->getMock();
184 |
185 | $client->expects($this->at(0))
186 | ->method('authenticate')
187 | ->with('plain')
188 | ->will($this->returnValue(false))
189 | ;
190 | $client->expects($this->at(1))
191 | ->method('authenticate')
192 | ->with('plain')
193 | ->will($this->returnValue(true))
194 | ;
195 | $client->expects($this->at(2))
196 | ->method('authenticate')
197 | ->with('plain')
198 | ->will($this->returnValue(false))
199 | ;
200 | $client->expects($this->at(3))
201 | ->method('authenticate')
202 | ->with('plain')
203 | ->will($this->returnValue(true))
204 | ;
205 |
206 | $client->setServer($server);
207 | $client->setId(1);
208 |
209 | $msg = $client->handleMessage('EHLO localhost.localdomain');
210 | $expect = '250-localhost.localdomain' . Client::MSG_SEPARATOR;
211 | $expect .= '250-AUTH PLAIN LOGIN' . Client::MSG_SEPARATOR;
212 | $expect .= '250-STARTTLS' . Client::MSG_SEPARATOR;
213 | $expect .= '250 HELP' . Client::MSG_SEPARATOR;
214 | $this->assertEquals($expect, $msg);
215 |
216 | $msg = $client->handleMessage('AUTH');
217 | $this->assertEquals('501 Syntax error in parameters or arguments' . Client::MSG_SEPARATOR, $msg);
218 |
219 | $msg = $client->handleMessage('AUTH CRAM-MD5');
220 | $this->assertEquals('502 Command not implemented' . Client::MSG_SEPARATOR, $msg);
221 |
222 | $msg = $client->handleMessage('AUTH UNKOWN');
223 | $this->assertEquals('501 Syntax error in parameters or arguments' . Client::MSG_SEPARATOR, $msg);
224 |
225 | $msg = $client->handleMessage('AUTH PLAIN');
226 | $this->assertEquals('334 ' . Client::MSG_SEPARATOR, $msg);
227 |
228 | // base64 encoded PLAIN username and password
229 | $msg = $client->handleMessage(base64_encode('usertestusersuper_secret_password'));
230 | $this->assertEquals('535 Authentication credentials invalid' . Client::MSG_SEPARATOR, $msg);
231 |
232 | $msg = $client->handleMessage(base64_encode('usertestusersuper_secret_password'));
233 | $this->assertEquals('235 2.7.0 Authentication successful' . Client::MSG_SEPARATOR, $msg);
234 |
235 | $msg = $client->handleMessage('AUTH PLAIN ' . base64_encode('usertestusersuper_secret_password'));
236 | $this->assertEquals('535 Authentication credentials invalid' . Client::MSG_SEPARATOR, $msg);
237 |
238 | $msg = $client->handleMessage('AUTH PLAIN ' . base64_encode('usertestusersuper_secret_password'));
239 | $this->assertEquals('235 2.7.0 Authentication successful' . Client::MSG_SEPARATOR, $msg);
240 | }
241 |
242 | public function testMsgHandleAuthLogin()
243 | {
244 | $server = new Server();
245 |
246 | /** @var MockBuilder $mockBuilder */
247 | $mockBuilder = $this->getMockBuilder(Client::class);
248 | $mockBuilder->setMethods(['authenticate']);
249 |
250 | /** @var Client|PHPUnit_Framework_MockObject_MockObject $client */
251 | $client = $mockBuilder->getMock();
252 |
253 | $client->expects($this->at(0))
254 | ->method('authenticate')
255 | ->with('login')
256 | ->will($this->returnValue(false))
257 | ;
258 | $client->expects($this->at(1))
259 | ->method('authenticate')
260 | ->with('login')
261 | ->will($this->returnValue(true))
262 | ;
263 |
264 | $client->setServer($server);
265 | $client->setId(1);
266 |
267 | $msg = $client->handleMessage('EHLO localhost.localdomain');
268 | $expect = '250-localhost.localdomain' . Client::MSG_SEPARATOR;
269 | $expect .= '250-AUTH PLAIN LOGIN' . Client::MSG_SEPARATOR;
270 | $expect .= '250-STARTTLS' . Client::MSG_SEPARATOR;
271 | $expect .= '250 HELP' . Client::MSG_SEPARATOR;
272 | $this->assertEquals($expect, $msg);
273 |
274 | $msg = $client->handleMessage('AUTH');
275 | $this->assertEquals('501 Syntax error in parameters or arguments' . Client::MSG_SEPARATOR, $msg);
276 |
277 | $msg = $client->handleMessage('AUTH LOGIN');
278 | $this->assertEquals('334 ' . base64_encode('Username:') . Client::MSG_SEPARATOR, $msg);
279 |
280 | // base64 encoded LOGIN username
281 | $msg = $client->handleMessage(base64_encode('testuser'));
282 | $this->assertEquals('334 ' . base64_encode('Password:') . Client::MSG_SEPARATOR, $msg);
283 |
284 | // base64 encoded LOGIN password
285 | $msg = $client->handleMessage(base64_encode('super_secret_password'));
286 | $this->assertEquals('535 Authentication credentials invalid' . Client::MSG_SEPARATOR, $msg);
287 |
288 | // base64 encoded LOGIN password
289 | $msg = $client->handleMessage(base64_encode('super_secret_password'));
290 | $this->assertEquals('235 2.7.0 Authentication successful' . Client::MSG_SEPARATOR, $msg);
291 | }
292 |
293 | public function testMsgHandleStartTls()
294 | {
295 | $server = new Server();
296 |
297 | /** @var MockBuilder $mockBuilder */
298 | $mockBuilder = $this->getMockBuilder(StreamSocket::class);
299 | $mockBuilder->setMethods(['enableEncryption']);
300 |
301 | /** @var StreamSocket|PHPUnit_Framework_MockObject_MockObject $client */
302 | $socket = $mockBuilder->getMock();
303 |
304 | $socket->expects($this->at(0))
305 | ->method('enableEncryption')
306 | ->will($this->throwException(new RuntimeException()))
307 | ;
308 | $socket->expects($this->at(1))
309 | ->method('enableEncryption')
310 | ->will($this->returnValue(true))
311 | ;
312 |
313 | $client = new Client();
314 | $client->setServer($server);
315 | $client->setId(1);
316 | $client->setSocket($socket);
317 |
318 | $msg = $client->handleMessage('EHLO localhost.localdomain');
319 | $expect = '250-localhost.localdomain' . Client::MSG_SEPARATOR;
320 | $expect .= '250-AUTH PLAIN LOGIN' . Client::MSG_SEPARATOR;
321 | $expect .= '250-STARTTLS' . Client::MSG_SEPARATOR;
322 | $expect .= '250 HELP' . Client::MSG_SEPARATOR;
323 | $this->assertEquals($expect, $msg);
324 |
325 | $msg = $client->handleMessage('STARTTLS PARAMETER');
326 | $this->assertEquals('501 Syntax error in parameters or arguments' . Client::MSG_SEPARATOR, $msg);
327 |
328 | $msg = $client->handleMessage('STARTTLS');
329 | $this->assertEquals('454 TLS not available due to temporary reason' . Client::MSG_SEPARATOR, $msg);
330 |
331 | $msg = $client->handleMessage('STARTTLS');
332 | $this->assertEquals('', $msg);
333 | }
334 | }
335 |
--------------------------------------------------------------------------------
/src/Client.php:
--------------------------------------------------------------------------------
1 | setDefaults([
103 | 'hostname' => 'localhost.localdomain',
104 | 'logger' => new NullLogger(),
105 | ]);
106 | $this->options = $resolver->resolve($options);
107 |
108 | $this->logger = $this->options['logger'];
109 | $this->hostname = $this->options['hostname'];
110 |
111 | $this->status = [];
112 | $this->status['hasHello'] = false;
113 | $this->status['hasMail'] = false;
114 | $this->status['hasShutdown'] = false;
115 | }
116 |
117 | /**
118 | * @param int $id
119 | */
120 | public function setId(int $id)
121 | {
122 | $this->id = $id;
123 | }
124 |
125 | /**
126 | * @return int
127 | */
128 | public function getId(): int
129 | {
130 | return $this->id;
131 | }
132 |
133 | /**
134 | * @param string $name
135 | * @return mixed|null
136 | */
137 | public function getStatus(string $name)
138 | {
139 | if (array_key_exists($name, $this->status)) {
140 | return $this->status[$name];
141 | }
142 | return null;
143 | }
144 |
145 | /**
146 | * @param string $name
147 | * @param mixed $value
148 | */
149 | public function setStatus(string $name, $value)
150 | {
151 | $this->status[$name] = $value;
152 | }
153 |
154 | /**
155 | * @param Server $server
156 | */
157 | public function setServer(Server $server)
158 | {
159 | $this->server = $server;
160 | }
161 |
162 | /**
163 | * @return Server|null
164 | */
165 | public function getServer()
166 | {
167 | return $this->server;
168 | }
169 |
170 | /**
171 | * @param AbstractSocket|PHPUnit_Framework_MockObject_MockObject $socket
172 | */
173 | public function setSocket(AbstractSocket $socket)
174 | {
175 | $this->socket = $socket;
176 | }
177 |
178 | /**
179 | * @return AbstractSocket|null
180 | */
181 | public function getSocket()
182 | {
183 | return $this->socket;
184 | }
185 |
186 | /**
187 | * @param string $ip
188 | */
189 | public function setIp(string $ip)
190 | {
191 | $this->ip = $ip;
192 | }
193 |
194 | /**
195 | * @return string
196 | */
197 | public function getIp(): string
198 | {
199 | if (!$this->ip) {
200 | $this->setIpPort();
201 | }
202 | return $this->ip;
203 | }
204 |
205 | /**
206 | * @param int $port
207 | */
208 | public function setPort(int $port)
209 | {
210 | $this->port = $port;
211 | }
212 |
213 | /**
214 | * @return int
215 | */
216 | public function getPort(): int
217 | {
218 | if (!$this->port) {
219 | $this->setIpPort();
220 | }
221 | return $this->port;
222 | }
223 |
224 | /**
225 | * @param string $ip
226 | * @param int $port
227 | */
228 | public function setIpPort(string $ip = '', int $port = 0)
229 | {
230 | // @codeCoverageIgnoreStart
231 | if (!defined('TEST')) {
232 | $this->getSocket()->getPeerName($ip, $port);
233 | }
234 | // @codeCoverageIgnoreEnd
235 |
236 | $this->setIp($ip);
237 | $this->setPort($port);
238 | }
239 |
240 | /**
241 | * @return string
242 | */
243 | public function getIpPort(): string
244 | {
245 | return $this->getIp() . ':' . $this->getPort();
246 | }
247 |
248 | /**
249 | * @param array $credentials
250 | */
251 | public function setCredentials(array $credentials = [])
252 | {
253 | $this->credentials = $credentials;
254 | }
255 |
256 | /**
257 | * @return array
258 | */
259 | public function getCredentials(): array
260 | {
261 | return $this->credentials;
262 | }
263 |
264 | /**
265 | * @return string
266 | */
267 | public function getHostname(): string
268 | {
269 | return $this->options['hostname'];
270 | }
271 |
272 | public function dataRecv()
273 | {
274 | $data = $this->getSocket()->read();
275 |
276 | do {
277 | $separatorPos = strpos($data, static::MSG_SEPARATOR);
278 | $separatorLen = strlen(static::MSG_SEPARATOR);
279 |
280 | // handle separators broken over multiple recvs
281 | if (($separatorPos === false) && $this->recvBufferTmp) {
282 | $tmp = substr($this->recvBufferTmp, -$separatorLen) . substr($data, 0, $separatorLen);
283 | $separatorPos = strpos($tmp, static::MSG_SEPARATOR) - $separatorLen;
284 | }
285 | if ($separatorPos === false) {
286 | $this->recvBufferTmp .= $data;
287 |
288 | $this->logger->debug('client ' . $this->id . ': collect data');
289 | break;
290 | } else {
291 | $msg = substr(
292 | $this->recvBufferTmp . $data,
293 | 0,
294 | strlen($this->recvBufferTmp) + $separatorPos
295 | );
296 | $this->recvBufferTmp = '';
297 |
298 | $this->handleMessage($msg);
299 |
300 | $data = substr($data, $separatorPos + $separatorLen);
301 | }
302 | } while ($data);
303 | }
304 |
305 | /**
306 | * @param string $msgRaw
307 | * @return string
308 | */
309 | public function handleMessage(string $msgRaw): string
310 | {
311 | $str = new StringParser($msgRaw);
312 | $args = $str->parse();
313 |
314 | $command = array_shift($args);
315 | $commandCmp = strtolower($command);
316 |
317 | if ($commandCmp == 'helo') {
318 | $this->setStatus('hasHello', true);
319 |
320 | return $this->sendOk($this->getHostname());
321 | } elseif ($commandCmp == 'ehlo') {
322 | $this->setStatus('hasHello', true);
323 | $response = '250-' . $this->getHostname() . static::MSG_SEPARATOR;
324 | $count = count($this->extendedCommands) - 1;
325 |
326 | for ($i = 0; $i < $count; $i++) {
327 | $response .= '250-' . $this->extendedCommands[$i] . static::MSG_SEPARATOR;
328 | }
329 |
330 | $response .= '250 ' . end($this->extendedCommands);
331 |
332 | return $this->dataSend($response);
333 | } elseif ($commandCmp == 'mail') {
334 | if ($this->getStatus('hasHello')) {
335 | if (isset($args[0]) && $args[0]) {
336 | $this->setStatus('hasMail', true);
337 | $from = $args[0];
338 | if (substr(strtolower($from), 0, 6) == 'from:<') {
339 | $from = substr(substr($from, 6), 0, -1);
340 | }
341 | $this->from = $from;
342 | $this->mail = '';
343 |
344 | return $this->sendOk();
345 | }
346 | return $this->sendSyntaxErrorInParameters();
347 | }
348 | return $this->sendSyntaxErrorCommandUnrecognized();
349 | } elseif ($commandCmp == 'rcpt') {
350 | if ($this->getStatus('hasHello')) {
351 | if (isset($args[0]) && $args[0]) {
352 | $this->setStatus('hasMail', true);
353 | $rcpt = $args[0];
354 | if (substr(strtolower($rcpt), 0, 4) == 'to:<') {
355 | $rcpt = substr(substr($rcpt, 4), 0, -1);
356 |
357 | $server = $this->getServer();
358 | if (!$server->newRcpt($rcpt)) {
359 | return $this->sendUserUnknown();
360 | }
361 | $this->rcpt[] = $rcpt;
362 | }
363 |
364 | return $this->sendOk();
365 | }
366 | return $this->sendSyntaxErrorInParameters();
367 | }
368 | return $this->sendSyntaxErrorCommandUnrecognized();
369 | } elseif ($commandCmp == 'data') {
370 | if ($this->getStatus('hasHello')) {
371 | $this->setStatus('hasData', true);
372 |
373 | return $this->sendDataResponse();
374 | }
375 |
376 | return $this->sendSyntaxErrorCommandUnrecognized();
377 | } elseif ($commandCmp == 'noop') {
378 | return $this->sendOk();
379 | } elseif ($commandCmp == 'quit') {
380 | $response = $this->sendQuit();
381 | $this->shutdown();
382 |
383 | return $response;
384 | } elseif ($commandCmp == 'auth') {
385 | $this->setStatus('hasAuth', true);
386 |
387 | if (empty($args)) {
388 | return $this->sendSyntaxErrorInParameters();
389 | }
390 |
391 | $authentication = strtolower($args[0]);
392 |
393 | if ($authentication == 'plain') {
394 | $this->setStatus('hasAuthPlain', true);
395 |
396 | if (isset($args[1])) {
397 | $this->setStatus('hasAuthPlainUser', true);
398 | $this->setCredentials([$args[1]]);
399 |
400 | if ($this->authenticate('plain')) {
401 | return $this->sendAuthSuccessResponse();
402 | }
403 |
404 | return $this->sendAuthInvalid();
405 | }
406 |
407 | return $this->sendAuthPlainResponse();
408 | } elseif ($authentication == 'login') {
409 | $this->setStatus('hasAuthLogin', true);
410 |
411 | return $this->sendAskForUserResponse();
412 | } elseif ($authentication == 'cram-md5') {
413 | return $this->sendCommandNotImplemented();
414 | }
415 |
416 | return $this->sendSyntaxErrorInParameters();
417 | } elseif ($commandCmp == 'starttls') {
418 | if (!empty($args)) {
419 | return $this->sendSyntaxErrorInParameters();
420 | }
421 |
422 | $this->sendReadyStartTls();
423 |
424 | try {
425 | $socket = $this->getSocket();
426 | $socket->enableEncryption();
427 | } catch (RuntimeException $e) {
428 | return $this->sendTemporaryErrorStartTls();
429 | }
430 | } elseif ($commandCmp == 'help') {
431 | return $this->sendOk('HELO, EHLO, MAIL FROM, RCPT TO, DATA, NOOP, QUIT');
432 | } else {
433 | if ($this->getStatus('hasAuth')) {
434 | if ($this->getStatus('hasAuthPlain')) {
435 | $this->setStatus('hasAuthPlainUser', true);
436 | $this->setCredentials([$command]);
437 |
438 | if ($this->authenticate('plain')) {
439 | return $this->sendAuthSuccessResponse();
440 | }
441 |
442 | return $this->sendAuthInvalid();
443 | } elseif ($this->getStatus('hasAuthLogin')) {
444 | $credentials = $this->getCredentials();
445 |
446 | if ($this->getStatus('hasAuthLoginUser')) {
447 | $credentials['password'] = $command;
448 | $this->setCredentials($credentials);
449 |
450 | if ($this->authenticate('login')) {
451 | return $this->sendAuthSuccessResponse();
452 | }
453 |
454 | return $this->sendAuthInvalid();
455 | }
456 |
457 | $this->setStatus('hasAuthLoginUser', true);
458 | $credentials['user'] = $command;
459 | $this->setCredentials($credentials);
460 |
461 | return $this->sendAskForPasswordResponse();
462 | }
463 |
464 | // @todo
465 | // $this->sendSyntaxErrorCommandUnrecognized();
466 | $this->sendCommandNotImplemented();
467 | throw new \RuntimeException('Unhandled situation.');
468 | } elseif ($this->getStatus('hasData')) {
469 | if ($msgRaw == '.') {
470 | $this->mail = substr($this->mail, 0, -strlen(static::MSG_SEPARATOR));
471 |
472 | try {
473 | $zmail = Message::fromString($this->mail);
474 | } catch (InvalidArgumentException $e) {
475 | return $this->sendSyntaxErrorInParameters();
476 | }
477 |
478 | $server = $this->getServer();
479 | $server->newMail($this->from, $this->rcpt, $zmail);
480 |
481 | $this->from = '';
482 | $this->rcpt = [];
483 | $this->mail = '';
484 |
485 | return $this->sendOk();
486 | } else {
487 | $this->mail .= $msgRaw . static::MSG_SEPARATOR;
488 | }
489 | } else {
490 | $tmp = [$this->id, $command, join('/ /', $args)];
491 | $this->logger->debug(vsprintf('client %d not implemented: /%s/ - /%s/', $tmp));
492 |
493 | return $this->sendSyntaxErrorCommandUnrecognized();
494 | }
495 | }
496 |
497 | return '';
498 | }
499 |
500 | /**
501 | * @param string $msg
502 | * @return string
503 | */
504 | private function dataSend(string $msg): string
505 | {
506 | $output = $msg . static::MSG_SEPARATOR;
507 | if ($this->getSocket()) {
508 | $tmp = $msg;
509 | $tmp = str_replace("\r", '', $tmp);
510 | $tmp = str_replace("\n", '\\n', $tmp);
511 | $this->logger->debug('client ' . $this->id . ' data send: "' . $tmp . '"');
512 | $this->getSocket()->write($output);
513 | }
514 | return $output;
515 | }
516 |
517 | /**
518 | * @param string $method
519 | * @return boolean
520 | */
521 | public function authenticate(string $method): bool
522 | {
523 | $attempt = $this->getServer()->authenticateUser($method, $this->getCredentials());
524 |
525 | $this->setStatus('hasAuth', false);
526 | $this->setStatus('hasAuth' . ucfirst($method), false);
527 | $this->setStatus('hasAuth' . ucfirst($method) . 'User', false);
528 |
529 | if (!$attempt) {
530 | return false;
531 | }
532 |
533 | return true;
534 | }
535 |
536 | /**
537 | * @return string
538 | */
539 | public function sendReady(): string
540 | {
541 | return $this->dataSend('220 ' . $this->getHostname() . ' SMTP Service Ready');
542 | }
543 |
544 | /**
545 | * @return string
546 | */
547 | private function sendReadyStartTls(): string
548 | {
549 | return $this->dataSend('220 Ready to start TLS');
550 | }
551 |
552 | /**
553 | * @return string
554 | */
555 | public function sendQuit(): string
556 | {
557 | return $this->dataSend('221 ' . $this->getHostname() . ' Service closing transmission channel');
558 | }
559 |
560 | /**
561 | * @param string $text
562 | * @return string
563 | */
564 | private function sendOk(string $text = 'OK'): string
565 | {
566 | return $this->dataSend('250 ' . $text);
567 | }
568 |
569 | /**
570 | * @return string
571 | */
572 | private function sendDataResponse(): string
573 | {
574 | return $this->dataSend('354 Start mail input; end with .');
575 | }
576 |
577 | /**
578 | * @return string
579 | */
580 | private function sendAuthPlainResponse(): string
581 | {
582 | return $this->dataSend('334 ');
583 | }
584 |
585 | /**
586 | * @return string
587 | */
588 | private function sendAuthSuccessResponse(): string
589 | {
590 | return $this->dataSend('235 2.7.0 Authentication successful');
591 | }
592 |
593 | /**
594 | * @return string
595 | */
596 | private function sendAskForUserResponse(): string
597 | {
598 | return $this->dataSend('334 VXNlcm5hbWU6');
599 | }
600 |
601 | /**
602 | * @return string
603 | */
604 | private function sendAskForPasswordResponse(): string
605 | {
606 | return $this->dataSend('334 UGFzc3dvcmQ6');
607 | }
608 |
609 | /**
610 | * @return string
611 | */
612 | private function sendTemporaryErrorStartTls(): string
613 | {
614 | return $this->dataSend('454 TLS not available due to temporary reason');
615 | }
616 |
617 | /**
618 | * @return string
619 | */
620 | private function sendSyntaxErrorCommandUnrecognized(): string
621 | {
622 | return $this->dataSend('500 Syntax error, command unrecognized');
623 | }
624 |
625 | /**
626 | * @return string
627 | */
628 | private function sendSyntaxErrorInParameters(): string
629 | {
630 | return $this->dataSend('501 Syntax error in parameters or arguments');
631 | }
632 |
633 | /**
634 | * @return string
635 | */
636 | private function sendCommandNotImplemented(): string
637 | {
638 | return $this->dataSend('502 Command not implemented');
639 | }
640 |
641 | /**
642 | * @return string
643 | */
644 | private function sendBadSequenceOrAuth(): string
645 | {
646 | return $this->dataSend('503 Bad sequence of commands');
647 | }
648 |
649 | /**
650 | * @return string
651 | */
652 | private function sendAuthInvalid(): string
653 | {
654 | return $this->dataSend('535 Authentication credentials invalid');
655 | }
656 |
657 | /**
658 | * @return string
659 | */
660 | private function sendUserUnknown(): string
661 | {
662 | return $this->dataSend('550 User unknown');
663 | }
664 |
665 | public function shutdown()
666 | {
667 | if (!$this->getStatus('hasShutdown')) {
668 | $this->setStatus('hasShutdown', true);
669 |
670 | if ($this->getSocket()) {
671 | $this->getSocket()->shutdown();
672 | $this->getSocket()->close();
673 | }
674 | }
675 | }
676 | }
677 |
--------------------------------------------------------------------------------
/php/php.ini:
--------------------------------------------------------------------------------
1 | [PHP]
2 |
3 | ;;;;;;;;;;;;;;;;;;;
4 | ; About php.ini ;
5 | ;;;;;;;;;;;;;;;;;;;
6 | ; PHP's initialization file, generally called php.ini, is responsible for
7 | ; configuring many of the aspects of PHP's behavior.
8 |
9 | ; PHP attempts to find and load this configuration from a number of locations.
10 | ; The following is a summary of its search order:
11 | ; 1. SAPI module specific location.
12 | ; 2. The PHPRC environment variable. (As of PHP 5.2.0)
13 | ; 3. A number of predefined registry keys on Windows (As of PHP 5.2.0)
14 | ; 4. Current working directory (except CLI)
15 | ; 5. The web server's directory (for SAPI modules), or directory of PHP
16 | ; (otherwise in Windows)
17 | ; 6. The directory from the --with-config-file-path compile time option, or the
18 | ; Windows directory (usually C:\windows)
19 | ; See the PHP docs for more specific information.
20 | ; http://php.net/configuration.file
21 |
22 | ; The syntax of the file is extremely simple. Whitespace and lines
23 | ; beginning with a semicolon are silently ignored (as you probably guessed).
24 | ; Section headers (e.g. [Foo]) are also silently ignored, even though
25 | ; they might mean something in the future.
26 |
27 | ; Directives following the section heading [PATH=/www/mysite] only
28 | ; apply to PHP files in the /www/mysite directory. Directives
29 | ; following the section heading [HOST=www.example.com] only apply to
30 | ; PHP files served from www.example.com. Directives set in these
31 | ; special sections cannot be overridden by user-defined INI files or
32 | ; at runtime. Currently, [PATH=] and [HOST=] sections only work under
33 | ; CGI/FastCGI.
34 | ; http://php.net/ini.sections
35 |
36 | ; Directives are specified using the following syntax:
37 | ; directive = value
38 | ; Directive names are *case sensitive* - foo=bar is different from FOO=bar.
39 | ; Directives are variables used to configure PHP or PHP extensions.
40 | ; There is no name validation. If PHP can't find an expected
41 | ; directive because it is not set or is mistyped, a default value will be used.
42 |
43 | ; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one
44 | ; of the INI constants (On, Off, True, False, Yes, No and None) or an expression
45 | ; (e.g. E_ALL & ~E_NOTICE), a quoted string ("bar"), or a reference to a
46 | ; previously set variable or directive (e.g. ${foo})
47 |
48 | ; Expressions in the INI file are limited to bitwise operators and parentheses:
49 | ; | bitwise OR
50 | ; ^ bitwise XOR
51 | ; & bitwise AND
52 | ; ~ bitwise NOT
53 | ; ! boolean NOT
54 |
55 | ; Boolean flags can be turned on using the values 1, On, True or Yes.
56 | ; They can be turned off using the values 0, Off, False or No.
57 |
58 | ; An empty string can be denoted by simply not writing anything after the equal
59 | ; sign, or by using the None keyword:
60 |
61 | ; foo = ; sets foo to an empty string
62 | ; foo = None ; sets foo to an empty string
63 | ; foo = "None" ; sets foo to the string 'None'
64 |
65 | ; If you use constants in your value, and these constants belong to a
66 | ; dynamically loaded extension (either a PHP extension or a Zend extension),
67 | ; you may only use these constants *after* the line that loads the extension.
68 |
69 | ;;;;;;;;;;;;;;;;;;;
70 | ; About this file ;
71 | ;;;;;;;;;;;;;;;;;;;
72 | ; PHP comes packaged with two INI files. One that is recommended to be used
73 | ; in production environments and one that is recommended to be used in
74 | ; development environments.
75 |
76 | ; php.ini-production contains settings which hold security, performance and
77 | ; best practices at its core. But please be aware, these settings may break
78 | ; compatibility with older or less security conscience applications. We
79 | ; recommending using the production ini in production and testing environments.
80 |
81 | ; php.ini-development is very similar to its production variant, except it is
82 | ; much more verbose when it comes to errors. We recommend using the
83 | ; development version only in development environments, as errors shown to
84 | ; application users can inadvertently leak otherwise secure information.
85 |
86 | ; This is the php.ini-production INI file.
87 |
88 | ;;;;;;;;;;;;;;;;;;;
89 | ; Quick Reference ;
90 | ;;;;;;;;;;;;;;;;;;;
91 | ; The following are all the settings which are different in either the production
92 | ; or development versions of the INIs with respect to PHP's default behavior.
93 | ; Please see the actual settings later in the document for more details as to why
94 | ; we recommend these changes in PHP's behavior.
95 |
96 | ; display_errors
97 | ; Default Value: On
98 | ; Development Value: On
99 | ; Production Value: Off
100 |
101 | ; display_startup_errors
102 | ; Default Value: Off
103 | ; Development Value: On
104 | ; Production Value: Off
105 |
106 | ; error_reporting
107 | ; Default Value: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
108 | ; Development Value: E_ALL
109 | ; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT
110 |
111 | ; html_errors
112 | ; Default Value: On
113 | ; Development Value: On
114 | ; Production value: On
115 |
116 | ; log_errors
117 | ; Default Value: Off
118 | ; Development Value: On
119 | ; Production Value: On
120 |
121 | ; max_input_time
122 | ; Default Value: -1 (Unlimited)
123 | ; Development Value: 60 (60 seconds)
124 | ; Production Value: 60 (60 seconds)
125 |
126 | ; output_buffering
127 | ; Default Value: Off
128 | ; Development Value: 4096
129 | ; Production Value: 4096
130 |
131 | ; register_argc_argv
132 | ; Default Value: On
133 | ; Development Value: Off
134 | ; Production Value: Off
135 |
136 | ; request_order
137 | ; Default Value: None
138 | ; Development Value: "GP"
139 | ; Production Value: "GP"
140 |
141 | ; session.gc_divisor
142 | ; Default Value: 100
143 | ; Development Value: 1000
144 | ; Production Value: 1000
145 |
146 | ; session.sid_bits_per_character
147 | ; Default Value: 4
148 | ; Development Value: 5
149 | ; Production Value: 5
150 |
151 | ; short_open_tag
152 | ; Default Value: On
153 | ; Development Value: Off
154 | ; Production Value: Off
155 |
156 | ; track_errors
157 | ; Default Value: Off
158 | ; Development Value: On
159 | ; Production Value: Off
160 |
161 | ; variables_order
162 | ; Default Value: "EGPCS"
163 | ; Development Value: "GPCS"
164 | ; Production Value: "GPCS"
165 |
166 | ;;;;;;;;;;;;;;;;;;;;
167 | ; php.ini Options ;
168 | ;;;;;;;;;;;;;;;;;;;;
169 | ; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini"
170 | ;user_ini.filename = ".user.ini"
171 |
172 | ; To disable this feature set this option to an empty value
173 | ;user_ini.filename =
174 |
175 | ; TTL for user-defined php.ini files (time-to-live) in seconds. Default is 300 seconds (5 minutes)
176 | ;user_ini.cache_ttl = 300
177 |
178 | ;;;;;;;;;;;;;;;;;;;;
179 | ; Language Options ;
180 | ;;;;;;;;;;;;;;;;;;;;
181 |
182 | ; Enable the PHP scripting language engine under Apache.
183 | ; http://php.net/engine
184 | engine = On
185 |
186 | ; This directive determines whether or not PHP will recognize code between
187 | ; and ?> tags as PHP source which should be processed as such. It is
188 | ; generally recommended that should be used and that this feature
189 | ; should be disabled, as enabling it may result in issues when generating XML
190 | ; documents, however this remains supported for backward compatibility reasons.
191 | ; Note that this directive does not control the = shorthand tag, which can be
192 | ; used regardless of this directive.
193 | ; Default Value: On
194 | ; Development Value: Off
195 | ; Production Value: Off
196 | ; http://php.net/short-open-tag
197 | short_open_tag = Off
198 |
199 | ; The number of significant digits displayed in floating point numbers.
200 | ; http://php.net/precision
201 | precision = 14
202 |
203 | ; Output buffering is a mechanism for controlling how much output data
204 | ; (excluding headers and cookies) PHP should keep internally before pushing that
205 | ; data to the client. If your application's output exceeds this setting, PHP
206 | ; will send that data in chunks of roughly the size you specify.
207 | ; Turning on this setting and managing its maximum buffer size can yield some
208 | ; interesting side-effects depending on your application and web server.
209 | ; You may be able to send headers and cookies after you've already sent output
210 | ; through print or echo. You also may see performance benefits if your server is
211 | ; emitting less packets due to buffered output versus PHP streaming the output
212 | ; as it gets it. On production servers, 4096 bytes is a good setting for performance
213 | ; reasons.
214 | ; Note: Output buffering can also be controlled via Output Buffering Control
215 | ; functions.
216 | ; Possible Values:
217 | ; On = Enabled and buffer is unlimited. (Use with caution)
218 | ; Off = Disabled
219 | ; Integer = Enables the buffer and sets its maximum size in bytes.
220 | ; Note: This directive is hardcoded to Off for the CLI SAPI
221 | ; Default Value: Off
222 | ; Development Value: 4096
223 | ; Production Value: 4096
224 | ; http://php.net/output-buffering
225 | output_buffering = 4096
226 |
227 | ; You can redirect all of the output of your scripts to a function. For
228 | ; example, if you set output_handler to "mb_output_handler", character
229 | ; encoding will be transparently converted to the specified encoding.
230 | ; Setting any output handler automatically turns on output buffering.
231 | ; Note: People who wrote portable scripts should not depend on this ini
232 | ; directive. Instead, explicitly set the output handler using ob_start().
233 | ; Using this ini directive may cause problems unless you know what script
234 | ; is doing.
235 | ; Note: You cannot use both "mb_output_handler" with "ob_iconv_handler"
236 | ; and you cannot use both "ob_gzhandler" and "zlib.output_compression".
237 | ; Note: output_handler must be empty if this is set 'On' !!!!
238 | ; Instead you must use zlib.output_handler.
239 | ; http://php.net/output-handler
240 | ;output_handler =
241 |
242 | ; URL rewriter function rewrites URL on the fly by using
243 | ; output buffer. You can set target tags by this configuration.
244 | ; "form" tag is special tag. It will add hidden input tag to pass values.
245 | ; Refer to session.trans_sid_tags for usage.
246 | ; Default Value: "form="
247 | ; Development Value: "form="
248 | ; Production Value: "form="
249 | ;url_rewriter.tags
250 |
251 | ; URL rewriter will not rewrite absolute URL nor form by default. To enable
252 | ; absolute URL rewrite, allowed hosts must be defined at RUNTIME.
253 | ; Refer to session.trans_sid_hosts for more details.
254 | ; Default Value: ""
255 | ; Development Value: ""
256 | ; Production Value: ""
257 | ;url_rewriter.hosts
258 |
259 | ; Transparent output compression using the zlib library
260 | ; Valid values for this option are 'off', 'on', or a specific buffer size
261 | ; to be used for compression (default is 4KB)
262 | ; Note: Resulting chunk size may vary due to nature of compression. PHP
263 | ; outputs chunks that are few hundreds bytes each as a result of
264 | ; compression. If you prefer a larger chunk size for better
265 | ; performance, enable output_buffering in addition.
266 | ; Note: You need to use zlib.output_handler instead of the standard
267 | ; output_handler, or otherwise the output will be corrupted.
268 | ; http://php.net/zlib.output-compression
269 | zlib.output_compression = Off
270 |
271 | ; http://php.net/zlib.output-compression-level
272 | ;zlib.output_compression_level = -1
273 |
274 | ; You cannot specify additional output handlers if zlib.output_compression
275 | ; is activated here. This setting does the same as output_handler but in
276 | ; a different order.
277 | ; http://php.net/zlib.output-handler
278 | ;zlib.output_handler =
279 |
280 | ; Implicit flush tells PHP to tell the output layer to flush itself
281 | ; automatically after every output block. This is equivalent to calling the
282 | ; PHP function flush() after each and every call to print() or echo() and each
283 | ; and every HTML block. Turning this option on has serious performance
284 | ; implications and is generally recommended for debugging purposes only.
285 | ; http://php.net/implicit-flush
286 | ; Note: This directive is hardcoded to On for the CLI SAPI
287 | implicit_flush = Off
288 |
289 | ; The unserialize callback function will be called (with the undefined class'
290 | ; name as parameter), if the unserializer finds an undefined class
291 | ; which should be instantiated. A warning appears if the specified function is
292 | ; not defined, or if the function doesn't include/implement the missing class.
293 | ; So only set this entry, if you really want to implement such a
294 | ; callback-function.
295 | unserialize_callback_func =
296 |
297 | ; When floats & doubles are serialized, store serialize_precision significant
298 | ; digits after the floating point. The default value ensures that when floats
299 | ; are decoded with unserialize, the data will remain the same.
300 | ; The value is also used for json_encode when encoding double values.
301 | ; If -1 is used, then dtoa mode 0 is used which automatically select the best
302 | ; precision.
303 | serialize_precision = -1
304 |
305 | ; open_basedir, if set, limits all file operations to the defined directory
306 | ; and below. This directive makes most sense if used in a per-directory
307 | ; or per-virtualhost web server configuration file.
308 | ; http://php.net/open-basedir
309 | ;open_basedir =
310 |
311 | ; This directive allows you to disable certain functions for security reasons.
312 | ; It receives a comma-delimited list of function names.
313 | ; http://php.net/disable-functions
314 | disable_functions =
315 |
316 | ; This directive allows you to disable certain classes for security reasons.
317 | ; It receives a comma-delimited list of class names.
318 | ; http://php.net/disable-classes
319 | disable_classes =
320 |
321 | ; Colors for Syntax Highlighting mode. Anything that's acceptable in
322 | ; would work.
323 | ; http://php.net/syntax-highlighting
324 | ;highlight.string = #DD0000
325 | ;highlight.comment = #FF9900
326 | ;highlight.keyword = #007700
327 | ;highlight.default = #0000BB
328 | ;highlight.html = #000000
329 |
330 | ; If enabled, the request will be allowed to complete even if the user aborts
331 | ; the request. Consider enabling it if executing long requests, which may end up
332 | ; being interrupted by the user or a browser timing out. PHP's default behavior
333 | ; is to disable this feature.
334 | ; http://php.net/ignore-user-abort
335 | ;ignore_user_abort = On
336 |
337 | ; Determines the size of the realpath cache to be used by PHP. This value should
338 | ; be increased on systems where PHP opens many files to reflect the quantity of
339 | ; the file operations performed.
340 | ; http://php.net/realpath-cache-size
341 | ;realpath_cache_size = 4096k
342 |
343 | ; Duration of time, in seconds for which to cache realpath information for a given
344 | ; file or directory. For systems with rarely changing files, consider increasing this
345 | ; value.
346 | ; http://php.net/realpath-cache-ttl
347 | ;realpath_cache_ttl = 120
348 |
349 | ; Enables or disables the circular reference collector.
350 | ; http://php.net/zend.enable-gc
351 | zend.enable_gc = On
352 |
353 | ; If enabled, scripts may be written in encodings that are incompatible with
354 | ; the scanner. CP936, Big5, CP949 and Shift_JIS are the examples of such
355 | ; encodings. To use this feature, mbstring extension must be enabled.
356 | ; Default: Off
357 | ;zend.multibyte = Off
358 |
359 | ; Allows to set the default encoding for the scripts. This value will be used
360 | ; unless "declare(encoding=...)" directive appears at the top of the script.
361 | ; Only affects if zend.multibyte is set.
362 | ; Default: ""
363 | ;zend.script_encoding =
364 |
365 | ;;;;;;;;;;;;;;;;;
366 | ; Miscellaneous ;
367 | ;;;;;;;;;;;;;;;;;
368 |
369 | ; Decides whether PHP may expose the fact that it is installed on the server
370 | ; (e.g. by adding its signature to the Web server header). It is no security
371 | ; threat in any way, but it makes it possible to determine whether you use PHP
372 | ; on your server or not.
373 | ; http://php.net/expose-php
374 | expose_php = On
375 |
376 | ;;;;;;;;;;;;;;;;;;;
377 | ; Resource Limits ;
378 | ;;;;;;;;;;;;;;;;;;;
379 |
380 | ; Maximum execution time of each script, in seconds
381 | ; http://php.net/max-execution-time
382 | ; Note: This directive is hardcoded to 0 for the CLI SAPI
383 | max_execution_time = 30
384 |
385 | ; Maximum amount of time each script may spend parsing request data. It's a good
386 | ; idea to limit this time on productions servers in order to eliminate unexpectedly
387 | ; long running scripts.
388 | ; Note: This directive is hardcoded to -1 for the CLI SAPI
389 | ; Default Value: -1 (Unlimited)
390 | ; Development Value: 60 (60 seconds)
391 | ; Production Value: 60 (60 seconds)
392 | ; http://php.net/max-input-time
393 | max_input_time = 60
394 |
395 | ; Maximum input variable nesting level
396 | ; http://php.net/max-input-nesting-level
397 | ;max_input_nesting_level = 64
398 |
399 | ; How many GET/POST/COOKIE input variables may be accepted
400 | ;max_input_vars = 1000
401 |
402 | ; Maximum amount of memory a script may consume (128MB)
403 | ; http://php.net/memory-limit
404 | memory_limit = -1
405 |
406 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
407 | ; Error handling and logging ;
408 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
409 |
410 | ; This directive informs PHP of which errors, warnings and notices you would like
411 | ; it to take action for. The recommended way of setting values for this
412 | ; directive is through the use of the error level constants and bitwise
413 | ; operators. The error level constants are below here for convenience as well as
414 | ; some common settings and their meanings.
415 | ; By default, PHP is set to take action on all errors, notices and warnings EXCEPT
416 | ; those related to E_NOTICE and E_STRICT, which together cover best practices and
417 | ; recommended coding standards in PHP. For performance reasons, this is the
418 | ; recommend error reporting setting. Your production server shouldn't be wasting
419 | ; resources complaining about best practices and coding standards. That's what
420 | ; development servers and development settings are for.
421 | ; Note: The php.ini-development file has this setting as E_ALL. This
422 | ; means it pretty much reports everything which is exactly what you want during
423 | ; development and early testing.
424 | ;
425 | ; Error Level Constants:
426 | ; E_ALL - All errors and warnings (includes E_STRICT as of PHP 5.4.0)
427 | ; E_ERROR - fatal run-time errors
428 | ; E_RECOVERABLE_ERROR - almost fatal run-time errors
429 | ; E_WARNING - run-time warnings (non-fatal errors)
430 | ; E_PARSE - compile-time parse errors
431 | ; E_NOTICE - run-time notices (these are warnings which often result
432 | ; from a bug in your code, but it's possible that it was
433 | ; intentional (e.g., using an uninitialized variable and
434 | ; relying on the fact it is automatically initialized to an
435 | ; empty string)
436 | ; E_STRICT - run-time notices, enable to have PHP suggest changes
437 | ; to your code which will ensure the best interoperability
438 | ; and forward compatibility of your code
439 | ; E_CORE_ERROR - fatal errors that occur during PHP's initial startup
440 | ; E_CORE_WARNING - warnings (non-fatal errors) that occur during PHP's
441 | ; initial startup
442 | ; E_COMPILE_ERROR - fatal compile-time errors
443 | ; E_COMPILE_WARNING - compile-time warnings (non-fatal errors)
444 | ; E_USER_ERROR - user-generated error message
445 | ; E_USER_WARNING - user-generated warning message
446 | ; E_USER_NOTICE - user-generated notice message
447 | ; E_DEPRECATED - warn about code that will not work in future versions
448 | ; of PHP
449 | ; E_USER_DEPRECATED - user-generated deprecation warnings
450 | ;
451 | ; Common Values:
452 | ; E_ALL (Show all errors, warnings and notices including coding standards.)
453 | ; E_ALL & ~E_NOTICE (Show all errors, except for notices)
454 | ; E_ALL & ~E_NOTICE & ~E_STRICT (Show all errors, except for notices and coding standards warnings.)
455 | ; E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR (Show only errors)
456 | ; Default Value: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
457 | ; Development Value: E_ALL
458 | ; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT
459 | ; http://php.net/error-reporting
460 | error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
461 |
462 | ; This directive controls whether or not and where PHP will output errors,
463 | ; notices and warnings too. Error output is very useful during development, but
464 | ; it could be very dangerous in production environments. Depending on the code
465 | ; which is triggering the error, sensitive information could potentially leak
466 | ; out of your application such as database usernames and passwords or worse.
467 | ; For production environments, we recommend logging errors rather than
468 | ; sending them to STDOUT.
469 | ; Possible Values:
470 | ; Off = Do not display any errors
471 | ; stderr = Display errors to STDERR (affects only CGI/CLI binaries!)
472 | ; On or stdout = Display errors to STDOUT
473 | ; Default Value: On
474 | ; Development Value: On
475 | ; Production Value: Off
476 | ; http://php.net/display-errors
477 | display_errors = Off
478 |
479 | ; The display of errors which occur during PHP's startup sequence are handled
480 | ; separately from display_errors. PHP's default behavior is to suppress those
481 | ; errors from clients. Turning the display of startup errors on can be useful in
482 | ; debugging configuration problems. We strongly recommend you
483 | ; set this to 'off' for production servers.
484 | ; Default Value: Off
485 | ; Development Value: On
486 | ; Production Value: Off
487 | ; http://php.net/display-startup-errors
488 | display_startup_errors = Off
489 |
490 | ; Besides displaying errors, PHP can also log errors to locations such as a
491 | ; server-specific log, STDERR, or a location specified by the error_log
492 | ; directive found below. While errors should not be displayed on productions
493 | ; servers they should still be monitored and logging is a great way to do that.
494 | ; Default Value: Off
495 | ; Development Value: On
496 | ; Production Value: On
497 | ; http://php.net/log-errors
498 | log_errors = On
499 |
500 | ; Set maximum length of log_errors. In error_log information about the source is
501 | ; added. The default is 1024 and 0 allows to not apply any maximum length at all.
502 | ; http://php.net/log-errors-max-len
503 | log_errors_max_len = 1024
504 |
505 | ; Do not log repeated messages. Repeated errors must occur in same file on same
506 | ; line unless ignore_repeated_source is set true.
507 | ; http://php.net/ignore-repeated-errors
508 | ignore_repeated_errors = Off
509 |
510 | ; Ignore source of message when ignoring repeated messages. When this setting
511 | ; is On you will not log errors with repeated messages from different files or
512 | ; source lines.
513 | ; http://php.net/ignore-repeated-source
514 | ignore_repeated_source = Off
515 |
516 | ; If this parameter is set to Off, then memory leaks will not be shown (on
517 | ; stdout or in the log). This has only effect in a debug compile, and if
518 | ; error reporting includes E_WARNING in the allowed list
519 | ; http://php.net/report-memleaks
520 | report_memleaks = On
521 |
522 | ; This setting is on by default.
523 | ;report_zend_debug = 0
524 |
525 | ; Store the last error/warning message in $php_errormsg (boolean). Setting this value
526 | ; to On can assist in debugging and is appropriate for development servers. It should
527 | ; however be disabled on production servers.
528 | ; This directive is DEPRECATED.
529 | ; Default Value: Off
530 | ; Development Value: Off
531 | ; Production Value: Off
532 | ; http://php.net/track-errors
533 | ;track_errors = Off
534 |
535 | ; Turn off normal error reporting and emit XML-RPC error XML
536 | ; http://php.net/xmlrpc-errors
537 | ;xmlrpc_errors = 0
538 |
539 | ; An XML-RPC faultCode
540 | ;xmlrpc_error_number = 0
541 |
542 | ; When PHP displays or logs an error, it has the capability of formatting the
543 | ; error message as HTML for easier reading. This directive controls whether
544 | ; the error message is formatted as HTML or not.
545 | ; Note: This directive is hardcoded to Off for the CLI SAPI
546 | ; Default Value: On
547 | ; Development Value: On
548 | ; Production value: On
549 | ; http://php.net/html-errors
550 | html_errors = On
551 |
552 | ; If html_errors is set to On *and* docref_root is not empty, then PHP
553 | ; produces clickable error messages that direct to a page describing the error
554 | ; or function causing the error in detail.
555 | ; You can download a copy of the PHP manual from http://php.net/docs
556 | ; and change docref_root to the base URL of your local copy including the
557 | ; leading '/'. You must also specify the file extension being used including
558 | ; the dot. PHP's default behavior is to leave these settings empty, in which
559 | ; case no links to documentation are generated.
560 | ; Note: Never use this feature for production boxes.
561 | ; http://php.net/docref-root
562 | ; Examples
563 | ;docref_root = "/phpmanual/"
564 |
565 | ; http://php.net/docref-ext
566 | ;docref_ext = .html
567 |
568 | ; String to output before an error message. PHP's default behavior is to leave
569 | ; this setting blank.
570 | ; http://php.net/error-prepend-string
571 | ; Example:
572 | ;error_prepend_string = ""
573 |
574 | ; String to output after an error message. PHP's default behavior is to leave
575 | ; this setting blank.
576 | ; http://php.net/error-append-string
577 | ; Example:
578 | ;error_append_string = ""
579 |
580 | ; Log errors to specified file. PHP's default behavior is to leave this value
581 | ; empty.
582 | ; http://php.net/error-log
583 | ; Example:
584 | ;error_log = php_errors.log
585 | ; Log errors to syslog (Event Log on Windows).
586 | ;error_log = syslog
587 |
588 | ; The syslog ident is a string which is prepended to every message logged
589 | ; to syslog. Only used when error_log is set to syslog.
590 | ;syslog.ident = php
591 |
592 | ; The syslog facility is used to specify what type of program is logging
593 | ; the message. Only used when error_log is set to syslog.
594 | ;syslog.facility = user
595 |
596 | ;windows.show_crt_warning
597 | ; Default value: 0
598 | ; Development value: 0
599 | ; Production value: 0
600 |
601 | ;;;;;;;;;;;;;;;;;
602 | ; Data Handling ;
603 | ;;;;;;;;;;;;;;;;;
604 |
605 | ; The separator used in PHP generated URLs to separate arguments.
606 | ; PHP's default setting is "&".
607 | ; http://php.net/arg-separator.output
608 | ; Example:
609 | ;arg_separator.output = "&"
610 |
611 | ; List of separator(s) used by PHP to parse input URLs into variables.
612 | ; PHP's default setting is "&".
613 | ; NOTE: Every character in this directive is considered as separator!
614 | ; http://php.net/arg-separator.input
615 | ; Example:
616 | ;arg_separator.input = ";&"
617 |
618 | ; This directive determines which super global arrays are registered when PHP
619 | ; starts up. G,P,C,E & S are abbreviations for the following respective super
620 | ; globals: GET, POST, COOKIE, ENV and SERVER. There is a performance penalty
621 | ; paid for the registration of these arrays and because ENV is not as commonly
622 | ; used as the others, ENV is not recommended on productions servers. You
623 | ; can still get access to the environment variables through getenv() should you
624 | ; need to.
625 | ; Default Value: "EGPCS"
626 | ; Development Value: "GPCS"
627 | ; Production Value: "GPCS";
628 | ; http://php.net/variables-order
629 | variables_order = "GPCS"
630 |
631 | ; This directive determines which super global data (G,P & C) should be
632 | ; registered into the super global array REQUEST. If so, it also determines
633 | ; the order in which that data is registered. The values for this directive
634 | ; are specified in the same manner as the variables_order directive,
635 | ; EXCEPT one. Leaving this value empty will cause PHP to use the value set
636 | ; in the variables_order directive. It does not mean it will leave the super
637 | ; globals array REQUEST empty.
638 | ; Default Value: None
639 | ; Development Value: "GP"
640 | ; Production Value: "GP"
641 | ; http://php.net/request-order
642 | request_order = "GP"
643 |
644 | ; This directive determines whether PHP registers $argv & $argc each time it
645 | ; runs. $argv contains an array of all the arguments passed to PHP when a script
646 | ; is invoked. $argc contains an integer representing the number of arguments
647 | ; that were passed when the script was invoked. These arrays are extremely
648 | ; useful when running scripts from the command line. When this directive is
649 | ; enabled, registering these variables consumes CPU cycles and memory each time
650 | ; a script is executed. For performance reasons, this feature should be disabled
651 | ; on production servers.
652 | ; Note: This directive is hardcoded to On for the CLI SAPI
653 | ; Default Value: On
654 | ; Development Value: Off
655 | ; Production Value: Off
656 | ; http://php.net/register-argc-argv
657 | register_argc_argv = Off
658 |
659 | ; When enabled, the ENV, REQUEST and SERVER variables are created when they're
660 | ; first used (Just In Time) instead of when the script starts. If these
661 | ; variables are not used within a script, having this directive on will result
662 | ; in a performance gain. The PHP directive register_argc_argv must be disabled
663 | ; for this directive to have any affect.
664 | ; http://php.net/auto-globals-jit
665 | auto_globals_jit = On
666 |
667 | ; Whether PHP will read the POST data.
668 | ; This option is enabled by default.
669 | ; Most likely, you won't want to disable this option globally. It causes $_POST
670 | ; and $_FILES to always be empty; the only way you will be able to read the
671 | ; POST data will be through the php://input stream wrapper. This can be useful
672 | ; to proxy requests or to process the POST data in a memory efficient fashion.
673 | ; http://php.net/enable-post-data-reading
674 | ;enable_post_data_reading = Off
675 |
676 | ; Maximum size of POST data that PHP will accept.
677 | ; Its value may be 0 to disable the limit. It is ignored if POST data reading
678 | ; is disabled through enable_post_data_reading.
679 | ; http://php.net/post-max-size
680 | post_max_size = 8M
681 |
682 | ; Automatically add files before PHP document.
683 | ; http://php.net/auto-prepend-file
684 | auto_prepend_file =
685 |
686 | ; Automatically add files after PHP document.
687 | ; http://php.net/auto-append-file
688 | auto_append_file =
689 |
690 | ; By default, PHP will output a media type using the Content-Type header. To
691 | ; disable this, simply set it to be empty.
692 | ;
693 | ; PHP's built-in default media type is set to text/html.
694 | ; http://php.net/default-mimetype
695 | default_mimetype = "text/html"
696 |
697 | ; PHP's default character set is set to UTF-8.
698 | ; http://php.net/default-charset
699 | default_charset = "UTF-8"
700 |
701 | ; PHP internal character encoding is set to empty.
702 | ; If empty, default_charset is used.
703 | ; http://php.net/internal-encoding
704 | ;internal_encoding =
705 |
706 | ; PHP input character encoding is set to empty.
707 | ; If empty, default_charset is used.
708 | ; http://php.net/input-encoding
709 | ;input_encoding =
710 |
711 | ; PHP output character encoding is set to empty.
712 | ; If empty, default_charset is used.
713 | ; See also output_buffer.
714 | ; http://php.net/output-encoding
715 | ;output_encoding =
716 |
717 | ;;;;;;;;;;;;;;;;;;;;;;;;;
718 | ; Paths and Directories ;
719 | ;;;;;;;;;;;;;;;;;;;;;;;;;
720 |
721 | ; UNIX: "/path1:/path2"
722 | ;include_path = ".:/usr/share/php"
723 | ;
724 | ; Windows: "\path1;\path2"
725 | ;include_path = ".;c:\php\includes"
726 | ;
727 | ; PHP's default setting for include_path is ".;/path/to/php/pear"
728 | ; http://php.net/include-path
729 |
730 | ; The root of the PHP pages, used only if nonempty.
731 | ; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root
732 | ; if you are running php as a CGI under any web server (other than IIS)
733 | ; see documentation for security issues. The alternate is to use the
734 | ; cgi.force_redirect configuration below
735 | ; http://php.net/doc-root
736 | doc_root =
737 |
738 | ; The directory under which PHP opens the script using /~username used only
739 | ; if nonempty.
740 | ; http://php.net/user-dir
741 | user_dir =
742 |
743 | ; Directory in which the loadable extensions (modules) reside.
744 | ; http://php.net/extension-dir
745 | ;extension_dir = "./"
746 | ; On windows:
747 | ;extension_dir = "ext"
748 |
749 | ; Directory where the temporary files should be placed.
750 | ; Defaults to the system default (see sys_get_temp_dir)
751 | ;sys_temp_dir = "/tmp"
752 |
753 | ; Whether or not to enable the dl() function. The dl() function does NOT work
754 | ; properly in multithreaded servers, such as IIS or Zeus, and is automatically
755 | ; disabled on them.
756 | ; http://php.net/enable-dl
757 | enable_dl = Off
758 |
759 | ; cgi.force_redirect is necessary to provide security running PHP as a CGI under
760 | ; most web servers. Left undefined, PHP turns this on by default. You can
761 | ; turn it off here AT YOUR OWN RISK
762 | ; **You CAN safely turn this off for IIS, in fact, you MUST.**
763 | ; http://php.net/cgi.force-redirect
764 | ;cgi.force_redirect = 1
765 |
766 | ; if cgi.nph is enabled it will force cgi to always sent Status: 200 with
767 | ; every request. PHP's default behavior is to disable this feature.
768 | ;cgi.nph = 1
769 |
770 | ; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape
771 | ; (iPlanet) web servers, you MAY need to set an environment variable name that PHP
772 | ; will look for to know it is OK to continue execution. Setting this variable MAY
773 | ; cause security issues, KNOW WHAT YOU ARE DOING FIRST.
774 | ; http://php.net/cgi.redirect-status-env
775 | ;cgi.redirect_status_env =
776 |
777 | ; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI. PHP's
778 | ; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok
779 | ; what PATH_INFO is. For more information on PATH_INFO, see the cgi specs. Setting
780 | ; this to 1 will cause PHP CGI to fix its paths to conform to the spec. A setting
781 | ; of zero causes PHP to behave as before. Default is 1. You should fix your scripts
782 | ; to use SCRIPT_FILENAME rather than PATH_TRANSLATED.
783 | ; http://php.net/cgi.fix-pathinfo
784 | ;cgi.fix_pathinfo=1
785 |
786 | ; if cgi.discard_path is enabled, the PHP CGI binary can safely be placed outside
787 | ; of the web tree and people will not be able to circumvent .htaccess security.
788 | ;cgi.discard_path=1
789 |
790 | ; FastCGI under IIS supports the ability to impersonate
791 | ; security tokens of the calling client. This allows IIS to define the
792 | ; security context that the request runs under. mod_fastcgi under Apache
793 | ; does not currently support this feature (03/17/2002)
794 | ; Set to 1 if running under IIS. Default is zero.
795 | ; http://php.net/fastcgi.impersonate
796 | ;fastcgi.impersonate = 1
797 |
798 | ; Disable logging through FastCGI connection. PHP's default behavior is to enable
799 | ; this feature.
800 | ;fastcgi.logging = 0
801 |
802 | ; cgi.rfc2616_headers configuration option tells PHP what type of headers to
803 | ; use when sending HTTP response code. If set to 0, PHP sends Status: header that
804 | ; is supported by Apache. When this option is set to 1, PHP will send
805 | ; RFC2616 compliant header.
806 | ; Default is zero.
807 | ; http://php.net/cgi.rfc2616-headers
808 | ;cgi.rfc2616_headers = 0
809 |
810 | ; cgi.check_shebang_line controls whether CGI PHP checks for line starting with #!
811 | ; (shebang) at the top of the running script. This line might be needed if the
812 | ; script support running both as stand-alone script and via PHP CGI<. PHP in CGI
813 | ; mode skips this line and ignores its content if this directive is turned on.
814 | ; http://php.net/cgi.check-shebang-line
815 | ;cgi.check_shebang_line=1
816 |
817 | ;;;;;;;;;;;;;;;;
818 | ; File Uploads ;
819 | ;;;;;;;;;;;;;;;;
820 |
821 | ; Whether to allow HTTP file uploads.
822 | ; http://php.net/file-uploads
823 | file_uploads = On
824 |
825 | ; Temporary directory for HTTP uploaded files (will use system default if not
826 | ; specified).
827 | ; http://php.net/upload-tmp-dir
828 | ;upload_tmp_dir =
829 |
830 | ; Maximum allowed size for uploaded files.
831 | ; http://php.net/upload-max-filesize
832 | upload_max_filesize = 2M
833 |
834 | ; Maximum number of files that can be uploaded via a single request
835 | max_file_uploads = 20
836 |
837 | ;;;;;;;;;;;;;;;;;;
838 | ; Fopen wrappers ;
839 | ;;;;;;;;;;;;;;;;;;
840 |
841 | ; Whether to allow the treatment of URLs (like http:// or ftp://) as files.
842 | ; http://php.net/allow-url-fopen
843 | allow_url_fopen = On
844 |
845 | ; Whether to allow include/require to open URLs (like http:// or ftp://) as files.
846 | ; http://php.net/allow-url-include
847 | allow_url_include = Off
848 |
849 | ; Define the anonymous ftp password (your email address). PHP's default setting
850 | ; for this is empty.
851 | ; http://php.net/from
852 | ;from="john@doe.com"
853 |
854 | ; Define the User-Agent string. PHP's default setting for this is empty.
855 | ; http://php.net/user-agent
856 | ;user_agent="PHP"
857 |
858 | ; Default timeout for socket based streams (seconds)
859 | ; http://php.net/default-socket-timeout
860 | default_socket_timeout = 60
861 |
862 | ; If your scripts have to deal with files from Macintosh systems,
863 | ; or you are running on a Mac and need to deal with files from
864 | ; unix or win32 systems, setting this flag will cause PHP to
865 | ; automatically detect the EOL character in those files so that
866 | ; fgets() and file() will work regardless of the source of the file.
867 | ; http://php.net/auto-detect-line-endings
868 | ;auto_detect_line_endings = Off
869 |
870 | ;;;;;;;;;;;;;;;;;;;;;;
871 | ; Dynamic Extensions ;
872 | ;;;;;;;;;;;;;;;;;;;;;;
873 |
874 | ; If you wish to have an extension loaded automatically, use the following
875 | ; syntax:
876 | ;
877 | ; extension=modulename
878 | ;
879 | ; For example:
880 | ;
881 | ; extension=mysqli
882 | ;
883 | ; When the extension library to load is not located in the default extension
884 | ; directory, You may specify an absolute path to the library file:
885 | ;
886 | ; extension=/path/to/extension/mysqli.so
887 | ;
888 | ; Note : The syntax used in previous PHP versions ('extension=.so' and
889 | ; 'extension='php_.dll') is supported for legacy reasons and may be
890 | ; deprecated in a future PHP major version. So, when it is possible, please
891 | ; move to the new ('extension=) syntax.
892 | ;
893 | ; Notes for Windows environments :
894 | ;
895 | ; - Many DLL files are located in the extensions/ (PHP 4) or ext/ (PHP 5+)
896 | ; extension folders as well as the separate PECL DLL download (PHP 5+).
897 | ; Be sure to appropriately set the extension_dir directive.
898 | ;
899 | ;extension=bz2
900 | ;extension=curl
901 | ;extension=fileinfo
902 | ;extension=gd2
903 | ;extension=gettext
904 | ;extension=gmp
905 | ;extension=intl
906 | ;extension=imap
907 | ;extension=interbase
908 | ;extension=ldap
909 | ;extension=mbstring
910 | ;extension=exif ; Must be after mbstring as it depends on it
911 | ;extension=mysqli
912 | ;extension=oci8_12c ; Use with Oracle Database 12c Instant Client
913 | ;extension=odbc
914 | ;extension=openssl
915 | ;extension=pdo_firebird
916 | ;extension=pdo_mysql
917 | ;extension=pdo_oci
918 | ;extension=pdo_odbc
919 | ;extension=pdo_pgsql
920 | ;extension=pdo_sqlite
921 | ;extension=pgsql
922 | ;extension=shmop
923 |
924 | ; The MIBS data available in the PHP distribution must be installed.
925 | ; See http://www.php.net/manual/en/snmp.installation.php
926 | ;extension=snmp
927 |
928 | ;extension=soap
929 | ;extension=sockets
930 | ;extension=sodium
931 | ;extension=sqlite3
932 | ;extension=tidy
933 | ;extension=xmlrpc
934 | ;extension=xsl
935 |
936 | ;;;;;;;;;;;;;;;;;;;
937 | ; Module Settings ;
938 | ;;;;;;;;;;;;;;;;;;;
939 |
940 | [CLI Server]
941 | ; Whether the CLI web server uses ANSI color coding in its terminal output.
942 | cli_server.color = On
943 |
944 | [Date]
945 | ; Defines the default timezone used by the date functions
946 | ; http://php.net/date.timezone
947 | ;date.timezone =
948 |
949 | ; http://php.net/date.default-latitude
950 | ;date.default_latitude = 31.7667
951 |
952 | ; http://php.net/date.default-longitude
953 | ;date.default_longitude = 35.2333
954 |
955 | ; http://php.net/date.sunrise-zenith
956 | ;date.sunrise_zenith = 90.583333
957 |
958 | ; http://php.net/date.sunset-zenith
959 | ;date.sunset_zenith = 90.583333
960 |
961 | [filter]
962 | ; http://php.net/filter.default
963 | ;filter.default = unsafe_raw
964 |
965 | ; http://php.net/filter.default-flags
966 | ;filter.default_flags =
967 |
968 | [iconv]
969 | ; Use of this INI entry is deprecated, use global input_encoding instead.
970 | ; If empty, default_charset or input_encoding or iconv.input_encoding is used.
971 | ; The precedence is: default_charset < input_encoding < iconv.input_encoding
972 | ;iconv.input_encoding =
973 |
974 | ; Use of this INI entry is deprecated, use global internal_encoding instead.
975 | ; If empty, default_charset or internal_encoding or iconv.internal_encoding is used.
976 | ; The precedence is: default_charset < internal_encoding < iconv.internal_encoding
977 | ;iconv.internal_encoding =
978 |
979 | ; Use of this INI entry is deprecated, use global output_encoding instead.
980 | ; If empty, default_charset or output_encoding or iconv.output_encoding is used.
981 | ; The precedence is: default_charset < output_encoding < iconv.output_encoding
982 | ; To use an output encoding conversion, iconv's output handler must be set
983 | ; otherwise output encoding conversion cannot be performed.
984 | ;iconv.output_encoding =
985 |
986 | [intl]
987 | ;intl.default_locale =
988 | ; This directive allows you to produce PHP errors when some error
989 | ; happens within intl functions. The value is the level of the error produced.
990 | ; Default is 0, which does not produce any errors.
991 | ;intl.error_level = E_WARNING
992 | ;intl.use_exceptions = 0
993 |
994 | [sqlite3]
995 | ;sqlite3.extension_dir =
996 |
997 | [Pcre]
998 | ; PCRE library backtracking limit.
999 | ; http://php.net/pcre.backtrack-limit
1000 | ;pcre.backtrack_limit=100000
1001 |
1002 | ; PCRE library recursion limit.
1003 | ; Please note that if you set this value to a high number you may consume all
1004 | ; the available process stack and eventually crash PHP (due to reaching the
1005 | ; stack size limit imposed by the Operating System).
1006 | ; http://php.net/pcre.recursion-limit
1007 | ;pcre.recursion_limit=100000
1008 |
1009 | ; Enables or disables JIT compilation of patterns. This requires the PCRE
1010 | ; library to be compiled with JIT support.
1011 | ;pcre.jit=1
1012 |
1013 | [Pdo]
1014 | ; Whether to pool ODBC connections. Can be one of "strict", "relaxed" or "off"
1015 | ; http://php.net/pdo-odbc.connection-pooling
1016 | ;pdo_odbc.connection_pooling=strict
1017 |
1018 | ;pdo_odbc.db2_instance_name
1019 |
1020 | [Pdo_mysql]
1021 | ; Default socket name for local MySQL connects. If empty, uses the built-in
1022 | ; MySQL defaults.
1023 | pdo_mysql.default_socket=
1024 |
1025 | [Phar]
1026 | ; http://php.net/phar.readonly
1027 | ;phar.readonly = On
1028 |
1029 | ; http://php.net/phar.require-hash
1030 | ;phar.require_hash = On
1031 |
1032 | ;phar.cache_list =
1033 |
1034 | [mail function]
1035 | ; For Win32 only.
1036 | ; http://php.net/smtp
1037 | SMTP = localhost
1038 | ; http://php.net/smtp-port
1039 | smtp_port = 25
1040 |
1041 | ; For Win32 only.
1042 | ; http://php.net/sendmail-from
1043 | ;sendmail_from = me@example.com
1044 |
1045 | ; For Unix only. You may supply arguments as well (default: "sendmail -t -i").
1046 | ; http://php.net/sendmail-path
1047 | ;sendmail_path =
1048 |
1049 | ; Force the addition of the specified parameters to be passed as extra parameters
1050 | ; to the sendmail binary. These parameters will always replace the value of
1051 | ; the 5th parameter to mail().
1052 | ;mail.force_extra_parameters =
1053 |
1054 | ; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename
1055 | mail.add_x_header = Off
1056 |
1057 | ; The path to a log file that will log all mail() calls. Log entries include
1058 | ; the full path of the script, line number, To address and headers.
1059 | ;mail.log =
1060 | ; Log mail to syslog (Event Log on Windows).
1061 | ;mail.log = syslog
1062 |
1063 | [ODBC]
1064 | ; http://php.net/odbc.default-db
1065 | ;odbc.default_db = Not yet implemented
1066 |
1067 | ; http://php.net/odbc.default-user
1068 | ;odbc.default_user = Not yet implemented
1069 |
1070 | ; http://php.net/odbc.default-pw
1071 | ;odbc.default_pw = Not yet implemented
1072 |
1073 | ; Controls the ODBC cursor model.
1074 | ; Default: SQL_CURSOR_STATIC (default).
1075 | ;odbc.default_cursortype
1076 |
1077 | ; Allow or prevent persistent links.
1078 | ; http://php.net/odbc.allow-persistent
1079 | odbc.allow_persistent = On
1080 |
1081 | ; Check that a connection is still valid before reuse.
1082 | ; http://php.net/odbc.check-persistent
1083 | odbc.check_persistent = On
1084 |
1085 | ; Maximum number of persistent links. -1 means no limit.
1086 | ; http://php.net/odbc.max-persistent
1087 | odbc.max_persistent = -1
1088 |
1089 | ; Maximum number of links (persistent + non-persistent). -1 means no limit.
1090 | ; http://php.net/odbc.max-links
1091 | odbc.max_links = -1
1092 |
1093 | ; Handling of LONG fields. Returns number of bytes to variables. 0 means
1094 | ; passthru.
1095 | ; http://php.net/odbc.defaultlrl
1096 | odbc.defaultlrl = 4096
1097 |
1098 | ; Handling of binary data. 0 means passthru, 1 return as is, 2 convert to char.
1099 | ; See the documentation on odbc_binmode and odbc_longreadlen for an explanation
1100 | ; of odbc.defaultlrl and odbc.defaultbinmode
1101 | ; http://php.net/odbc.defaultbinmode
1102 | odbc.defaultbinmode = 1
1103 |
1104 | [Interbase]
1105 | ; Allow or prevent persistent links.
1106 | ibase.allow_persistent = 1
1107 |
1108 | ; Maximum number of persistent links. -1 means no limit.
1109 | ibase.max_persistent = -1
1110 |
1111 | ; Maximum number of links (persistent + non-persistent). -1 means no limit.
1112 | ibase.max_links = -1
1113 |
1114 | ; Default database name for ibase_connect().
1115 | ;ibase.default_db =
1116 |
1117 | ; Default username for ibase_connect().
1118 | ;ibase.default_user =
1119 |
1120 | ; Default password for ibase_connect().
1121 | ;ibase.default_password =
1122 |
1123 | ; Default charset for ibase_connect().
1124 | ;ibase.default_charset =
1125 |
1126 | ; Default timestamp format.
1127 | ibase.timestampformat = "%Y-%m-%d %H:%M:%S"
1128 |
1129 | ; Default date format.
1130 | ibase.dateformat = "%Y-%m-%d"
1131 |
1132 | ; Default time format.
1133 | ibase.timeformat = "%H:%M:%S"
1134 |
1135 | [MySQLi]
1136 |
1137 | ; Maximum number of persistent links. -1 means no limit.
1138 | ; http://php.net/mysqli.max-persistent
1139 | mysqli.max_persistent = -1
1140 |
1141 | ; Allow accessing, from PHP's perspective, local files with LOAD DATA statements
1142 | ; http://php.net/mysqli.allow_local_infile
1143 | ;mysqli.allow_local_infile = On
1144 |
1145 | ; Allow or prevent persistent links.
1146 | ; http://php.net/mysqli.allow-persistent
1147 | mysqli.allow_persistent = On
1148 |
1149 | ; Maximum number of links. -1 means no limit.
1150 | ; http://php.net/mysqli.max-links
1151 | mysqli.max_links = -1
1152 |
1153 | ; Default port number for mysqli_connect(). If unset, mysqli_connect() will use
1154 | ; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the
1155 | ; compile-time value defined MYSQL_PORT (in that order). Win32 will only look
1156 | ; at MYSQL_PORT.
1157 | ; http://php.net/mysqli.default-port
1158 | mysqli.default_port = 3306
1159 |
1160 | ; Default socket name for local MySQL connects. If empty, uses the built-in
1161 | ; MySQL defaults.
1162 | ; http://php.net/mysqli.default-socket
1163 | mysqli.default_socket =
1164 |
1165 | ; Default host for mysql_connect() (doesn't apply in safe mode).
1166 | ; http://php.net/mysqli.default-host
1167 | mysqli.default_host =
1168 |
1169 | ; Default user for mysql_connect() (doesn't apply in safe mode).
1170 | ; http://php.net/mysqli.default-user
1171 | mysqli.default_user =
1172 |
1173 | ; Default password for mysqli_connect() (doesn't apply in safe mode).
1174 | ; Note that this is generally a *bad* idea to store passwords in this file.
1175 | ; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw")
1176 | ; and reveal this password! And of course, any users with read access to this
1177 | ; file will be able to reveal the password as well.
1178 | ; http://php.net/mysqli.default-pw
1179 | mysqli.default_pw =
1180 |
1181 | ; Allow or prevent reconnect
1182 | mysqli.reconnect = Off
1183 |
1184 | [mysqlnd]
1185 | ; Enable / Disable collection of general statistics by mysqlnd which can be
1186 | ; used to tune and monitor MySQL operations.
1187 | mysqlnd.collect_statistics = On
1188 |
1189 | ; Enable / Disable collection of memory usage statistics by mysqlnd which can be
1190 | ; used to tune and monitor MySQL operations.
1191 | mysqlnd.collect_memory_statistics = Off
1192 |
1193 | ; Records communication from all extensions using mysqlnd to the specified log
1194 | ; file.
1195 | ; http://php.net/mysqlnd.debug
1196 | ;mysqlnd.debug =
1197 |
1198 | ; Defines which queries will be logged.
1199 | ;mysqlnd.log_mask = 0
1200 |
1201 | ; Default size of the mysqlnd memory pool, which is used by result sets.
1202 | ;mysqlnd.mempool_default_size = 16000
1203 |
1204 | ; Size of a pre-allocated buffer used when sending commands to MySQL in bytes.
1205 | ;mysqlnd.net_cmd_buffer_size = 2048
1206 |
1207 | ; Size of a pre-allocated buffer used for reading data sent by the server in
1208 | ; bytes.
1209 | ;mysqlnd.net_read_buffer_size = 32768
1210 |
1211 | ; Timeout for network requests in seconds.
1212 | ;mysqlnd.net_read_timeout = 31536000
1213 |
1214 | ; SHA-256 Authentication Plugin related. File with the MySQL server public RSA
1215 | ; key.
1216 | ;mysqlnd.sha256_server_public_key =
1217 |
1218 | [OCI8]
1219 |
1220 | ; Connection: Enables privileged connections using external
1221 | ; credentials (OCI_SYSOPER, OCI_SYSDBA)
1222 | ; http://php.net/oci8.privileged-connect
1223 | ;oci8.privileged_connect = Off
1224 |
1225 | ; Connection: The maximum number of persistent OCI8 connections per
1226 | ; process. Using -1 means no limit.
1227 | ; http://php.net/oci8.max-persistent
1228 | ;oci8.max_persistent = -1
1229 |
1230 | ; Connection: The maximum number of seconds a process is allowed to
1231 | ; maintain an idle persistent connection. Using -1 means idle
1232 | ; persistent connections will be maintained forever.
1233 | ; http://php.net/oci8.persistent-timeout
1234 | ;oci8.persistent_timeout = -1
1235 |
1236 | ; Connection: The number of seconds that must pass before issuing a
1237 | ; ping during oci_pconnect() to check the connection validity. When
1238 | ; set to 0, each oci_pconnect() will cause a ping. Using -1 disables
1239 | ; pings completely.
1240 | ; http://php.net/oci8.ping-interval
1241 | ;oci8.ping_interval = 60
1242 |
1243 | ; Connection: Set this to a user chosen connection class to be used
1244 | ; for all pooled server requests with Oracle 11g Database Resident
1245 | ; Connection Pooling (DRCP). To use DRCP, this value should be set to
1246 | ; the same string for all web servers running the same application,
1247 | ; the database pool must be configured, and the connection string must
1248 | ; specify to use a pooled server.
1249 | ;oci8.connection_class =
1250 |
1251 | ; High Availability: Using On lets PHP receive Fast Application
1252 | ; Notification (FAN) events generated when a database node fails. The
1253 | ; database must also be configured to post FAN events.
1254 | ;oci8.events = Off
1255 |
1256 | ; Tuning: This option enables statement caching, and specifies how
1257 | ; many statements to cache. Using 0 disables statement caching.
1258 | ; http://php.net/oci8.statement-cache-size
1259 | ;oci8.statement_cache_size = 20
1260 |
1261 | ; Tuning: Enables statement prefetching and sets the default number of
1262 | ; rows that will be fetched automatically after statement execution.
1263 | ; http://php.net/oci8.default-prefetch
1264 | ;oci8.default_prefetch = 100
1265 |
1266 | ; Compatibility. Using On means oci_close() will not close
1267 | ; oci_connect() and oci_new_connect() connections.
1268 | ; http://php.net/oci8.old-oci-close-semantics
1269 | ;oci8.old_oci_close_semantics = Off
1270 |
1271 | [PostgreSQL]
1272 | ; Allow or prevent persistent links.
1273 | ; http://php.net/pgsql.allow-persistent
1274 | pgsql.allow_persistent = On
1275 |
1276 | ; Detect broken persistent links always with pg_pconnect().
1277 | ; Auto reset feature requires a little overheads.
1278 | ; http://php.net/pgsql.auto-reset-persistent
1279 | pgsql.auto_reset_persistent = Off
1280 |
1281 | ; Maximum number of persistent links. -1 means no limit.
1282 | ; http://php.net/pgsql.max-persistent
1283 | pgsql.max_persistent = -1
1284 |
1285 | ; Maximum number of links (persistent+non persistent). -1 means no limit.
1286 | ; http://php.net/pgsql.max-links
1287 | pgsql.max_links = -1
1288 |
1289 | ; Ignore PostgreSQL backends Notice message or not.
1290 | ; Notice message logging require a little overheads.
1291 | ; http://php.net/pgsql.ignore-notice
1292 | pgsql.ignore_notice = 0
1293 |
1294 | ; Log PostgreSQL backends Notice message or not.
1295 | ; Unless pgsql.ignore_notice=0, module cannot log notice message.
1296 | ; http://php.net/pgsql.log-notice
1297 | pgsql.log_notice = 0
1298 |
1299 | [bcmath]
1300 | ; Number of decimal digits for all bcmath functions.
1301 | ; http://php.net/bcmath.scale
1302 | bcmath.scale = 0
1303 |
1304 | [browscap]
1305 | ; http://php.net/browscap
1306 | ;browscap = extra/browscap.ini
1307 |
1308 | [Session]
1309 | ; Handler used to store/retrieve data.
1310 | ; http://php.net/session.save-handler
1311 | session.save_handler = files
1312 |
1313 | ; Argument passed to save_handler. In the case of files, this is the path
1314 | ; where data files are stored. Note: Windows users have to change this
1315 | ; variable in order to use PHP's session functions.
1316 | ;
1317 | ; The path can be defined as:
1318 | ;
1319 | ; session.save_path = "N;/path"
1320 | ;
1321 | ; where N is an integer. Instead of storing all the session files in
1322 | ; /path, what this will do is use subdirectories N-levels deep, and
1323 | ; store the session data in those directories. This is useful if
1324 | ; your OS has problems with many files in one directory, and is
1325 | ; a more efficient layout for servers that handle many sessions.
1326 | ;
1327 | ; NOTE 1: PHP will not create this directory structure automatically.
1328 | ; You can use the script in the ext/session dir for that purpose.
1329 | ; NOTE 2: See the section on garbage collection below if you choose to
1330 | ; use subdirectories for session storage
1331 | ;
1332 | ; The file storage module creates files using mode 600 by default.
1333 | ; You can change that by using
1334 | ;
1335 | ; session.save_path = "N;MODE;/path"
1336 | ;
1337 | ; where MODE is the octal representation of the mode. Note that this
1338 | ; does not overwrite the process's umask.
1339 | ; http://php.net/session.save-path
1340 | ;session.save_path = "/var/lib/php/sessions"
1341 |
1342 | ; Whether to use strict session mode.
1343 | ; Strict session mode does not accept an uninitialized session ID, and
1344 | ; regenerates the session ID if the browser sends an uninitialized session ID.
1345 | ; Strict mode protects applications from session fixation via a session adoption
1346 | ; vulnerability. It is disabled by default for maximum compatibility, but
1347 | ; enabling it is encouraged.
1348 | ; https://wiki.php.net/rfc/strict_sessions
1349 | session.use_strict_mode = 0
1350 |
1351 | ; Whether to use cookies.
1352 | ; http://php.net/session.use-cookies
1353 | session.use_cookies = 1
1354 |
1355 | ; http://php.net/session.cookie-secure
1356 | ;session.cookie_secure =
1357 |
1358 | ; This option forces PHP to fetch and use a cookie for storing and maintaining
1359 | ; the session id. We encourage this operation as it's very helpful in combating
1360 | ; session hijacking when not specifying and managing your own session id. It is
1361 | ; not the be-all and end-all of session hijacking defense, but it's a good start.
1362 | ; http://php.net/session.use-only-cookies
1363 | session.use_only_cookies = 1
1364 |
1365 | ; Name of the session (used as cookie name).
1366 | ; http://php.net/session.name
1367 | session.name = PHPSESSID
1368 |
1369 | ; Initialize session on request startup.
1370 | ; http://php.net/session.auto-start
1371 | session.auto_start = 0
1372 |
1373 | ; Lifetime in seconds of cookie or, if 0, until browser is restarted.
1374 | ; http://php.net/session.cookie-lifetime
1375 | session.cookie_lifetime = 0
1376 |
1377 | ; The path for which the cookie is valid.
1378 | ; http://php.net/session.cookie-path
1379 | session.cookie_path = /
1380 |
1381 | ; The domain for which the cookie is valid.
1382 | ; http://php.net/session.cookie-domain
1383 | session.cookie_domain =
1384 |
1385 | ; Whether or not to add the httpOnly flag to the cookie, which makes it
1386 | ; inaccessible to browser scripting languages such as JavaScript.
1387 | ; http://php.net/session.cookie-httponly
1388 | session.cookie_httponly =
1389 |
1390 | ; Handler used to serialize data. php is the standard serializer of PHP.
1391 | ; http://php.net/session.serialize-handler
1392 | session.serialize_handler = php
1393 |
1394 | ; Defines the probability that the 'garbage collection' process is started
1395 | ; on every session initialization. The probability is calculated by using
1396 | ; gc_probability/gc_divisor. Where session.gc_probability is the numerator
1397 | ; and gc_divisor is the denominator in the equation. Setting this value to 1
1398 | ; when the session.gc_divisor value is 100 will give you approximately a 1% chance
1399 | ; the gc will run on any given request.
1400 | ; Default Value: 1
1401 | ; Development Value: 1
1402 | ; Production Value: 1
1403 | ; http://php.net/session.gc-probability
1404 | session.gc_probability = 0
1405 |
1406 | ; Defines the probability that the 'garbage collection' process is started on every
1407 | ; session initialization. The probability is calculated by using the following equation:
1408 | ; gc_probability/gc_divisor. Where session.gc_probability is the numerator and
1409 | ; session.gc_divisor is the denominator in the equation. Setting this value to 100
1410 | ; when the session.gc_probability value is 1 will give you approximately a 1% chance
1411 | ; the gc will run on any given request. Increasing this value to 1000 will give you
1412 | ; a 0.1% chance the gc will run on any given request. For high volume production servers,
1413 | ; this is a more efficient approach.
1414 | ; Default Value: 100
1415 | ; Development Value: 1000
1416 | ; Production Value: 1000
1417 | ; http://php.net/session.gc-divisor
1418 | session.gc_divisor = 1000
1419 |
1420 | ; After this number of seconds, stored data will be seen as 'garbage' and
1421 | ; cleaned up by the garbage collection process.
1422 | ; http://php.net/session.gc-maxlifetime
1423 | session.gc_maxlifetime = 1440
1424 |
1425 | ; NOTE: If you are using the subdirectory option for storing session files
1426 | ; (see session.save_path above), then garbage collection does *not*
1427 | ; happen automatically. You will need to do your own garbage
1428 | ; collection through a shell script, cron entry, or some other method.
1429 | ; For example, the following script would is the equivalent of
1430 | ; setting session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes):
1431 | ; find /path/to/sessions -cmin +24 -type f | xargs rm
1432 |
1433 | ; Check HTTP Referer to invalidate externally stored URLs containing ids.
1434 | ; HTTP_REFERER has to contain this substring for the session to be
1435 | ; considered as valid.
1436 | ; http://php.net/session.referer-check
1437 | session.referer_check =
1438 |
1439 | ; Set to {nocache,private,public,} to determine HTTP caching aspects
1440 | ; or leave this empty to avoid sending anti-caching headers.
1441 | ; http://php.net/session.cache-limiter
1442 | session.cache_limiter = nocache
1443 |
1444 | ; Document expires after n minutes.
1445 | ; http://php.net/session.cache-expire
1446 | session.cache_expire = 180
1447 |
1448 | ; trans sid support is disabled by default.
1449 | ; Use of trans sid may risk your users' security.
1450 | ; Use this option with caution.
1451 | ; - User may send URL contains active session ID
1452 | ; to other person via. email/irc/etc.
1453 | ; - URL that contains active session ID may be stored
1454 | ; in publicly accessible computer.
1455 | ; - User may access your site with the same session ID
1456 | ; always using URL stored in browser's history or bookmarks.
1457 | ; http://php.net/session.use-trans-sid
1458 | session.use_trans_sid = 0
1459 |
1460 | ; Set session ID character length. This value could be between 22 to 256.
1461 | ; Shorter length than default is supported only for compatibility reason.
1462 | ; Users should use 32 or more chars.
1463 | ; http://php.net/session.sid-length
1464 | ; Default Value: 32
1465 | ; Development Value: 26
1466 | ; Production Value: 26
1467 | session.sid_length = 26
1468 |
1469 | ; The URL rewriter will look for URLs in a defined set of HTML tags.
1470 | ;