Passed
Push — master ( 8dcc68...34e8da )
by
unknown
23:44 queued 20:40
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('GSYNC_CONFIG')) {
18
    define('GSYNC_CONFIG', 'config.php');
19
}
20
21
include_once GSYNC_CONFIG;
22
23
    // Attempt to set maximum execution time
24
    ini_set('max_execution_time', SCRIPT_TIMEOUT);
25
    set_time_limit(SCRIPT_TIMEOUT);
26
27
    try {
28
        // check config & initialize the basics
29
        GSync::CheckConfig();
30
        Request::Initialize();
31
        SLog::Initialize();
32
33
        SLog::Write(LOGLEVEL_DEBUG, '-------- Start');
34
        SLog::Write(
35
            LOGLEVEL_DEBUG,
36
            sprintf(
37
                "cmd='%s' devType='%s' devId='%s' getUser='%s' from='%s' version='%s' method='%s'",
38
                Request::GetCommand(),
39
                Request::GetDeviceType(),
40
                Request::GetDeviceID(),
41
                Request::GetGETUser(),
42
                Request::GetRemoteAddr(),
43
                @constant('GROMMUNIOSYNC_VERSION'),
44
                Request::GetMethod()
45
            )
46
        );
47
48
        // always request the authorization header
49
        if (!Request::HasAuthenticationInfo() || !Request::GetGETUser()) {
50
            throw new AuthenticationRequiredException('Access denied. Please send authorisation information');
51
        }
52
53
        GSync::CheckAdvancedConfig();
54
55
        // Process request headers and look for AS headers
56
        Request::ProcessHeaders();
57
58
        // Stop here if this is an OPTIONS request
59
        if (Request::IsMethodOPTIONS()) {
60
            RequestProcessor::Authenticate();
61
62
            throw new NoPostRequestException('Options request', NoPostRequestException::OPTIONS_REQUEST);
63
        }
64
65
        // Check required GET parameters
66
        if (Request::IsMethodPOST() && (false === Request::GetCommandCode() || !Request::GetDeviceID() || !Request::GetDeviceType())) {
67
            throw new FatalException('Requested the grommunio-sync URL without the required GET parameters');
68
        }
69
70
        // Load the backend
71
        $backend = GSync::GetBackend();
72
73
        // check the provisioning information
74
        if (PROVISIONING === true && Request::IsMethodPOST() && GSync::CommandNeedsProvisioning(Request::GetCommandCode())
0 ignored issues
show
It seems like Request::GetCommandCode() can also be of type false; however, parameter $commandCode of GSync::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

74
        if (PROVISIONING === true && Request::IsMethodPOST() && GSync::CommandNeedsProvisioning(/** @scrutinizer ignore-type */ Request::GetCommandCode())
Loading history...
75
            && ((Request::WasPolicyKeySent() && 0 == Request::GetPolicyKey()) || GSync::GetProvisioningManager()->ProvisioningRequired(Request::GetPolicyKey()))
76
            && (LOOSE_PROVISIONING === false
77
            || (LOOSE_PROVISIONING === true && Request::WasPolicyKeySent()))) {
78
            // TODO for AS 14 send a wbxml response
79
            throw new ProvisioningRequiredException();
80
        }
81
82
        // most commands require an authenticated user
83
        if (GSync::CommandNeedsAuthentication(Request::GetCommandCode())) {
0 ignored issues
show
It seems like Request::GetCommandCode() can also be of type false; however, parameter $commandCode of GSync::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

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