Passed
Branch master (f497d2)
by Mike
03:18
created

index.php (2 issues)

Labels
Severity
1
<?php
2
/*
3
 * SPDX-License-Identifier: AGPL-3.0-only
4
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
5
 * SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH
6
 *
7
 * This is the entry point through which all requests are processed.
8
 */
9
10
ob_start(null, 1048576);
11
12
// ignore user abortions because this can lead to weird errors - see ZP-239
13
ignore_user_abort(true);
14
15
require_once 'vendor/autoload.php';
16
17
if (!defined('ZPUSH_CONFIG')) define('ZPUSH_CONFIG', 'config.php');
18
include_once(ZPUSH_CONFIG);
19
20
    // Attempt to set maximum execution time
21
    ini_set('max_execution_time', SCRIPT_TIMEOUT);
22
    set_time_limit(SCRIPT_TIMEOUT);
23
24
    try {
25
        // check config & initialize the basics
26
        ZPush::CheckConfig();
27
        Request::Initialize();
28
        ZLog::Initialize();
29
30
        ZLog::Write(LOGLEVEL_DEBUG,"-------- Start");
31
        ZLog::Write(LOGLEVEL_DEBUG,
32
                sprintf("cmd='%s' devType='%s' devId='%s' getUser='%s' from='%s' version='%s' method='%s'",
33
                        Request::GetCommand(), Request::GetDeviceType(), Request::GetDeviceID(), Request::GetGETUser(), Request::GetRemoteAddr(), @constant('GROMMUNIOSYNC_VERSION'), Request::GetMethod() ));
34
35
        // always request the authorization header
36
        if (! Request::HasAuthenticationInfo() || !Request::GetGETUser())
37
            throw new AuthenticationRequiredException("Access denied. Please send authorisation information");
38
39
        ZPush::CheckAdvancedConfig();
40
41
        // Process request headers and look for AS headers
42
        Request::ProcessHeaders();
43
44
        // Stop here if this is an OPTIONS request
45
        if (Request::IsMethodOPTIONS()) {
46
            RequestProcessor::Authenticate();
47
            throw new NoPostRequestException("Options request", NoPostRequestException::OPTIONS_REQUEST);
48
        }
49
50
        // Check required GET parameters
51
        if(Request::IsMethodPOST() && (Request::GetCommandCode() === false || !Request::GetDeviceID() || !Request::GetDeviceType()))
52
            throw new FatalException("Requested the grommunio-sync URL without the required GET parameters");
53
54
        // Load the backend
55
        $backend = ZPush::GetBackend();
56
57
        // check the provisioning information
58
        if (PROVISIONING === true && Request::IsMethodPOST() && ZPush::CommandNeedsProvisioning(Request::GetCommandCode()) &&
0 ignored issues
show
It seems like Request::GetCommandCode() can also be of type false; however, parameter $commandCode of ZPush::CommandNeedsProvisioning() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

58
        if (PROVISIONING === true && Request::IsMethodPOST() && ZPush::CommandNeedsProvisioning(/** @scrutinizer ignore-type */ Request::GetCommandCode()) &&
Loading history...
59
            ((Request::WasPolicyKeySent() && Request::GetPolicyKey() == 0) || ZPush::GetProvisioningManager()->ProvisioningRequired(Request::GetPolicyKey())) &&
60
            (LOOSE_PROVISIONING === false ||
61
            (LOOSE_PROVISIONING === true && Request::WasPolicyKeySent())))
62
            //TODO for AS 14 send a wbxml response
63
            throw new ProvisioningRequiredException();
64
65
        // most commands require an authenticated user
66
        if (ZPush::CommandNeedsAuthentication(Request::GetCommandCode()))
0 ignored issues
show
It seems like Request::GetCommandCode() can also be of type false; however, parameter $commandCode of ZPush::CommandNeedsAuthentication() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

66
        if (ZPush::CommandNeedsAuthentication(/** @scrutinizer ignore-type */ Request::GetCommandCode()))
Loading history...
67
            RequestProcessor::Authenticate();
68
69
        // Do the actual processing of the request
70
        if (Request::IsMethodGET())
71
            throw new NoPostRequestException("This is the grommunio-sync location and can only be accessed by Microsoft ActiveSync-capable devices", NoPostRequestException::GET_REQUEST);
72
73
        // Do the actual request
74
        header(ZPush::GetServerHeader());
75
76
        if (RequestProcessor::isUserAuthenticated()) {
77
            header("X-Grommunio-Sync-Version: ". @constant('GROMMUNIOSYNC_VERSION'));
78
79
            // announce the supported AS versions (if not already sent to device)
80
            if (ZPush::GetDeviceManager()->AnnounceASVersion()) {
81
                $versions = ZPush::GetSupportedProtocolVersions(true);
82
                ZLog::Write(LOGLEVEL_INFO, sprintf("Announcing latest AS version to device: %s", $versions));
83
                header("X-MS-RP: ". $versions);
84
            }
85
        }
86
87
        RequestProcessor::Initialize();
88
        RequestProcessor::HandleRequest();
89
90
        // eventually the RequestProcessor wants to send other headers to the mobile
91
        foreach (RequestProcessor::GetSpecialHeaders() as $header) {
92
            ZLog::Write(LOGLEVEL_DEBUG, sprintf("Special header: %s", $header));
93
            header($header);
94
        }
95
96
        // stream the data
97
        $len = ob_get_length();
98
        $data = ob_get_contents();
99
        ob_end_clean();
100
101
        // log amount of data transferred
102
        // TODO check $len when streaming more data (e.g. Attachments), as the data will be send chunked
103
        if (ZPush::GetDeviceManager(false)) {
104
            ZPush::GetDeviceManager()->SentData($len);
105
        }
106
107
        // Unfortunately, even though grommunio-sync can stream the data to the client
108
        // with a chunked encoding, using chunked encoding breaks the progress bar
109
        // on the PDA. So the data is de-chunk here, written a content-length header and
110
        // data send as a 'normal' packet. If the output packet exceeds 1MB (see ob_start)
111
        // then it will be sent as a chunked packet anyway because PHP will have to flush
112
        // the buffer.
113
        if(!headers_sent())
114
            header("Content-Length: $len");
115
116
        // send vnd.ms-sync.wbxml content type header if there is no content
117
        // otherwise text/html content type is added which might break some devices
118
        if (!headers_sent() && $len == 0)
119
            header("Content-Type: application/vnd.ms-sync.wbxml");
120
121
        print $data;
122
123
        // destruct backend after all data is on the stream
124
        $backend->Logoff();
125
    }
126
127
    catch (NoPostRequestException $nopostex) {
128
        if ($nopostex->getCode() == NoPostRequestException::OPTIONS_REQUEST) {
129
            header(ZPush::GetServerHeader());
130
            header(ZPush::GetSupportedProtocolVersions());
131
            header(ZPush::GetSupportedCommands());
132
            ZLog::Write(LOGLEVEL_INFO, $nopostex->getMessage());
133
        }
134
        else if ($nopostex->getCode() == NoPostRequestException::GET_REQUEST) {
135
            if (Request::GetUserAgent())
136
                ZLog::Write(LOGLEVEL_INFO, sprintf("User-agent: '%s'", Request::GetUserAgent()));
137
            if (!headers_sent() && $nopostex->showLegalNotice())
138
                ZPush::PrintGrommunioSyncLegal('GET not supported', $nopostex->getMessage());
139
        }
140
    }
141
142
    catch (Exception $ex) {
143
        // Extract any previous exception message for logging purpose.
144
        $exclass = get_class($ex);
145
        $exception_message = $ex->getMessage();
146
        if($ex->getPrevious()){
147
            do {
148
                $current_exception = $ex->getPrevious();
149
                $exception_message .= ' -> ' . $current_exception->getMessage();
150
            } while($current_exception->getPrevious());
151
        }
152
153
        if (Request::GetUserAgent())
154
            ZLog::Write(LOGLEVEL_INFO, sprintf("User-agent: '%s'", Request::GetUserAgent()));
155
156
        ZLog::Write(LOGLEVEL_FATAL, sprintf('Exception: (%s) - %s', $exclass, $exception_message));
157
158
        if(!headers_sent()) {
159
            if ($ex instanceof ZPushException) {
160
                header('HTTP/1.1 '. $ex->getHTTPCodeString());
161
                foreach ($ex->getHTTPHeaders() as $h)
162
                    header($h);
163
            }
164
            // something really unexpected happened!
165
            else
166
                header('HTTP/1.1 500 Internal Server Error');
167
        }
168
169
        if ($ex instanceof AuthenticationRequiredException) {
170
            // Only print ZPush legal message for GET requests because
171
            // some devices send unauthorized OPTIONS requests
172
            // and don't expect anything in the response body
173
            if (Request::IsMethodGET()) {
174
                ZPush::PrintGrommunioSyncLegal($exclass, sprintf('<pre>%s</pre>',$ex->getMessage()));
175
            }
176
177
            // log the failed login attempt e.g. for fail2ban
178
            if (defined('LOGAUTHFAIL') && LOGAUTHFAIL != false)
179
                ZLog::Write(LOGLEVEL_WARN, sprintf("IP: %s failed to authenticate user '%s'",  Request::GetRemoteAddr(), Request::GetAuthUser()? Request::GetAuthUser(): Request::GetGETUser() ));
180
        }
181
182
        // This could be a WBXML problem.. try to get the complete request
183
        else if ($ex instanceof WBXMLException) {
184
            ZLog::Write(LOGLEVEL_FATAL, "Request could not be processed correctly due to a WBXMLException. Please report this including the 'WBXML debug data' logged. Be aware that the debug data could contain confidential information.");
185
        }
186
187
        // Try to output some kind of error information. This is only possible if
188
        // the output had not started yet. If it has started already, we can't show the user the error, and
189
        // the device will give its own (useless) error message.
190
        else if (!($ex instanceof ZPushException) || $ex->showLegalNotice()) {
191
            $cmdinfo = (Request::GetCommand())? sprintf(" processing command <i>%s</i>", Request::GetCommand()): "";
192
            $extrace = $ex->getTrace();
193
            $trace = (!empty($extrace))? "\n\nTrace:\n". print_r($extrace,1):"";
194
            ZPush::PrintGrommunioSyncLegal($exclass . $cmdinfo, sprintf('<pre>%s</pre>',$ex->getMessage() . $trace));
195
        }
196
197
        // Announce exception to process loop detection
198
        if (ZPush::GetDeviceManager(false))
199
            ZPush::GetDeviceManager()->AnnounceProcessException($ex);
200
201
        // Announce exception if the TopCollector if available
202
        ZPush::GetTopCollector()->AnnounceInformation(get_class($ex), true);
203
    }
204
205
    // save device data if the DeviceManager is available
206
    if (ZPush::GetDeviceManager(false))
207
        ZPush::GetDeviceManager()->Save();
208
209
    // end gracefully
210
    ZLog::Write(LOGLEVEL_INFO,
211
            sprintf("cmd='%s' memory='%s/%s' time='%ss' devType='%s' devId='%s' getUser='%s' from='%s' idle='%ss' version='%s' method='%s' httpcode='%s'",
212
                    Request::GetCommand(), Utils::FormatBytes(memory_get_peak_usage(false)), Utils::FormatBytes(memory_get_peak_usage(true)),
213
                    number_format(microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"], 2),
214
                    Request::GetDeviceType(), Request::GetDeviceID(), Request::GetGETUser(), Request::GetRemoteAddr(),
215
                    RequestProcessor::GetWaitTime(), @constant('GROMMUNIOSYNC_VERSION'), Request::GetMethod(), http_response_code() ));
216
217
    ZLog::Write(LOGLEVEL_DEBUG, "-------- End");
218
219