blob: 3e31dc3dd53512d36a3c1f709c164fa6abe6464c [file] [log] [blame]
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +01001<?php
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +02002require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/vendor/autoload.php';
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +01003require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php';
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +01004if(file_exists('inc/vars.local.inc.php')) {
5 include_once 'inc/vars.local.inc.php';
6}
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +02007require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
8require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php';
9require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
10require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';
11require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php';
12$default_autodiscover_config = $autodiscover_config;
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010013$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
14
15// Redis
16$redis = new Redis();
17try {
18 if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
19 $redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
20 }
21 else {
22 $redis->connect('redis-mailcow', 6379);
23 }
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020024 $redis->auth(getenv("REDISPASS"));
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010025}
26catch (Exception $e) {
27 exit;
28}
29
30error_reporting(0);
31
32$data = trim(file_get_contents("php://input"));
33
34if (strpos($data, 'autodiscover/outlook/responseschema') !== false) {
35 $autodiscover_config['autodiscoverType'] = 'imap';
36 if ($autodiscover_config['useEASforOutlook'] == 'yes' &&
37 // Office for macOS does not support EAS
38 strpos($_SERVER['HTTP_USER_AGENT'], 'Mac') === false &&
39 // Outlook 2013 (version 15) or higher
40 preg_match('/(Outlook|Office).+1[5-9]\./', $_SERVER['HTTP_USER_AGENT'])
41 ) {
42 $autodiscover_config['autodiscoverType'] = 'activesync';
43 }
44}
45
46if (getenv('SKIP_SOGO') == "y") {
47 $autodiscover_config['autodiscoverType'] = 'imap';
48}
49
50//$dsn = $database_type . ":host=" . $database_host . ";dbname=" . $database_name;
51$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
52$opt = [
53 PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
54 PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
55 PDO::ATTR_EMULATE_PREPARES => false,
56];
57$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010058
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020059// Init Identity Provider
60$iam_provider = identity_provider('init');
61$iam_settings = identity_provider('get');
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010062
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020063// Passwordless autodiscover - no authentication required
64// Email will be extracted from the request body
65$login_user = null;
66$login_role = null;
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010067
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020068header("Content-Type: application/xml");
69echo '<?xml version="1.0" encoding="utf-8" ?>' . PHP_EOL;
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010070?>
71<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
72<?php
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020073if(!$data) {
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010074 try {
75 $json = json_encode(
76 array(
77 "time" => time(),
78 "ua" => $_SERVER['HTTP_USER_AGENT'],
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020079 "user" => "none",
Matthias Andreas Benkardb4b5cc12021-12-28 18:02:04 +010080 "ip" => $_SERVER['REMOTE_ADDR'],
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020081 "service" => "Error: invalid or missing request data"
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010082 )
83 );
84 $redis->lPush('AUTODISCOVER_LOG', $json);
85 $redis->lTrim('AUTODISCOVER_LOG', 0, 100);
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020086 $redis->publish("F2B_CHANNEL", "Autodiscover: Invalid request by " . $_SERVER['REMOTE_ADDR']);
87 error_log("Autodiscover: Invalid request by " . $_SERVER['REMOTE_ADDR']);
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010088 }
89 catch (RedisException $e) {
90 $_SESSION['return'][] = array(
91 'type' => 'danger',
92 'msg' => 'Redis: '.$e
93 );
94 return false;
95 }
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020096 list($usec, $sec) = explode(' ', microtime());
97?>
98 <Response>
99 <Error Time="<?=date('H:i:s', $sec) . substr($usec, 0, strlen($usec) - 2);?>" Id="<?=rand(1000000000, 9999999999);?>">
100 <ErrorCode>600</ErrorCode>
101 <Message>Invalid Request</Message>
102 <DebugData />
103 </Error>
104 </Response>
105</Autodiscover>
106<?php
107 exit(0);
108}
109try {
110 $discover = new SimpleXMLElement($data);
111 $email = $discover->Request->EMailAddress;
112} catch (Exception $e) {
113 // If parsing fails, return error
114 try {
115 $json = json_encode(
116 array(
117 "time" => time(),
118 "ua" => $_SERVER['HTTP_USER_AGENT'],
119 "user" => "none",
120 "ip" => $_SERVER['REMOTE_ADDR'],
121 "service" => "Error: could not parse email from request"
122 )
123 );
124 $redis->lPush('AUTODISCOVER_LOG', $json);
125 $redis->lTrim('AUTODISCOVER_LOG', 0, 100);
126 $redis->publish("F2B_CHANNEL", "Autodiscover: Malformed XML by " . $_SERVER['REMOTE_ADDR']);
127 error_log("Autodiscover: Malformed XML by " . $_SERVER['REMOTE_ADDR']);
128 }
129 catch (RedisException $e) {
130 // Silently fail
131 }
132 list($usec, $sec) = explode(' ', microtime());
133?>
134 <Response>
135 <Error Time="<?=date('H:i:s', $sec) . substr($usec, 0, strlen($usec) - 2);?>" Id="<?=rand(1000000000, 9999999999);?>">
136 <ErrorCode>600</ErrorCode>
137 <Message>Invalid Request</Message>
138 <DebugData />
139 </Error>
140 </Response>
141</Autodiscover>
142<?php
143 exit(0);
144}
145
146$username = trim((string)$email);
147try {
148 $stmt = $pdo->prepare("SELECT `mailbox`.`name`, `mailbox`.`active` FROM `mailbox`
149 INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain`
150 WHERE `mailbox`.`username` = :username
151 AND `mailbox`.`active` = '1'
152 AND `domain`.`active` = '1'");
153 $stmt->execute(array(':username' => $username));
154 $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC);
155}
156catch(PDOException $e) {
157 // Database error - return error response with complete XML
158 list($usec, $sec) = explode(' ', microtime());
159?>
160 <Response>
161 <Error Time="<?=date('H:i:s', $sec) . substr($usec, 0, strlen($usec) - 2);?>" Id="<?=rand(1000000000, 9999999999);?>">
162 <ErrorCode>500</ErrorCode>
163 <Message>Database Error</Message>
164 <DebugData />
165 </Error>
166 </Response>
167</Autodiscover>
168<?php
169 exit(0);
170}
171
172// Mailbox not found or not active - return generic error to prevent user enumeration
173if (empty($MailboxData)) {
174 try {
175 $json = json_encode(
176 array(
177 "time" => time(),
178 "ua" => $_SERVER['HTTP_USER_AGENT'],
179 "user" => $email,
180 "ip" => $_SERVER['REMOTE_ADDR'],
181 "service" => "Error: mailbox not found or inactive"
182 )
183 );
184 $redis->lPush('AUTODISCOVER_LOG', $json);
185 $redis->lTrim('AUTODISCOVER_LOG', 0, 100);
186 $redis->publish("F2B_CHANNEL", "Autodiscover: Invalid mailbox attempt by " . $_SERVER['REMOTE_ADDR']);
187 error_log("Autodiscover: Invalid mailbox attempt by " . $_SERVER['REMOTE_ADDR']);
188 }
189 catch (RedisException $e) {
190 // Silently fail
191 }
192 list($usec, $sec) = explode(' ', microtime());
193?>
194 <Response>
195 <Error Time="<?=date('H:i:s', $sec) . substr($usec, 0, strlen($usec) - 2);?>" Id="<?=rand(1000000000, 9999999999);?>">
196 <ErrorCode>600</ErrorCode>
197 <Message>Invalid Request</Message>
198 <DebugData />
199 </Error>
200 </Response>
201</Autodiscover>
202<?php
203 exit(0);
204}
205
206if (!empty($MailboxData['name'])) {
207 $displayname = $MailboxData['name'];
208}
209else {
210 $displayname = $email;
211}
212try {
213 $json = json_encode(
214 array(
215 "time" => time(),
216 "ua" => $_SERVER['HTTP_USER_AGENT'],
217 "user" => $email,
218 "ip" => $_SERVER['REMOTE_ADDR'],
219 "service" => $autodiscover_config['autodiscoverType']
220 )
221 );
222 $redis->lPush('AUTODISCOVER_LOG', $json);
223 $redis->lTrim('AUTODISCOVER_LOG', 0, 100);
224}
225catch (RedisException $e) {
226 $_SESSION['return'][] = array(
227 'type' => 'danger',
228 'msg' => 'Redis: '.$e
229 );
230 return false;
231}
232if ($autodiscover_config['autodiscoverType'] == 'imap') {
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +0100233?>
234 <Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
235 <User>
236 <DisplayName><?=htmlspecialchars($displayname, ENT_XML1 | ENT_QUOTES, 'UTF-8');?></DisplayName>
237 </User>
238 <Account>
239 <AccountType>email</AccountType>
240 <Action>settings</Action>
241 <Protocol>
242 <Type>IMAP</Type>
243 <Server><?=$autodiscover_config['imap']['server'];?></Server>
244 <Port><?=$autodiscover_config['imap']['port'];?></Port>
245 <DomainRequired>off</DomainRequired>
246 <LoginName><?=$email;?></LoginName>
247 <SPA>off</SPA>
248 <SSL>on</SSL>
249 <AuthRequired>on</AuthRequired>
250 </Protocol>
251 <Protocol>
252 <Type>SMTP</Type>
253 <Server><?=$autodiscover_config['smtp']['server'];?></Server>
254 <Port><?=$autodiscover_config['smtp']['port'];?></Port>
255 <DomainRequired>off</DomainRequired>
256 <LoginName><?=$email;?></LoginName>
257 <SPA>off</SPA>
258 <SSL>on</SSL>
259 <AuthRequired>on</AuthRequired>
260 <UsePOPAuth>on</UsePOPAuth>
261 <SMTPLast>off</SMTPLast>
262 </Protocol>
263 <?php
264 if (getenv('SKIP_SOGO') != "y") {
265 ?>
266 <Protocol>
267 <Type>CalDAV</Type>
268 <Server>https://<?=$autodiscover_config['caldav']['server'];?><?php if ($autodiscover_config['caldav']['port'] != 443) echo ':'.$autodiscover_config['caldav']['port']; ?>/SOGo/dav/<?=$email;?>/</Server>
269 <DomainRequired>off</DomainRequired>
270 <LoginName><?=$email;?></LoginName>
271 </Protocol>
272 <Protocol>
273 <Type>CardDAV</Type>
274 <Server>https://<?=$autodiscover_config['carddav']['server'];?><?php if ($autodiscover_config['caldav']['port'] != 443) echo ':'.$autodiscover_config['carddav']['port']; ?>/SOGo/dav/<?=$email;?>/</Server>
275 <DomainRequired>off</DomainRequired>
276 <LoginName><?=$email;?></LoginName>
277 </Protocol>
278 <?php
279 }
280 ?>
281 </Account>
282 </Response>
283<?php
284 }
285 else if ($autodiscover_config['autodiscoverType'] == 'activesync') {
286?>
287 <Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006">
288 <Culture>en:en</Culture>
289 <User>
290 <DisplayName><?=htmlspecialchars($displayname, ENT_XML1 | ENT_QUOTES, 'UTF-8');?></DisplayName>
291 <EMailAddress><?=$email;?></EMailAddress>
292 </User>
293 <Action>
294 <Settings>
295 <Server>
296 <Type>MobileSync</Type>
297 <Url><?=$autodiscover_config['activesync']['url'];?></Url>
298 <Name><?=$autodiscover_config['activesync']['url'];?></Name>
299 </Server>
300 </Settings>
301 </Action>
302 </Response>
303<?php
304 }
305?>
306</Autodiscover>