| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 1 | <?php |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame] | 2 | require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/vendor/autoload.php'; |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 3 | require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php'; |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 4 | if(file_exists('inc/vars.local.inc.php')) { |
| 5 | include_once 'inc/vars.local.inc.php'; |
| 6 | } |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame] | 7 | require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php'; |
| 8 | require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php'; |
| 9 | require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php'; |
| 10 | require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php'; |
| 11 | require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php'; |
| 12 | $default_autodiscover_config = $autodiscover_config; |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 13 | $autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config); |
| 14 | |
| 15 | // Redis |
| 16 | $redis = new Redis(); |
| 17 | try { |
| 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 Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame] | 24 | $redis->auth(getenv("REDISPASS")); |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 25 | } |
| 26 | catch (Exception $e) { |
| 27 | exit; |
| 28 | } |
| 29 | |
| 30 | error_reporting(0); |
| 31 | |
| 32 | $data = trim(file_get_contents("php://input")); |
| 33 | |
| 34 | if (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 | |
| 46 | if (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 Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 58 | |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame] | 59 | // Init Identity Provider |
| 60 | $iam_provider = identity_provider('init'); |
| 61 | $iam_settings = identity_provider('get'); |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 62 | |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame] | 63 | // Passwordless autodiscover - no authentication required |
| 64 | // Email will be extracted from the request body |
| 65 | $login_user = null; |
| 66 | $login_role = null; |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 67 | |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame] | 68 | header("Content-Type: application/xml"); |
| 69 | echo '<?xml version="1.0" encoding="utf-8" ?>' . PHP_EOL; |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 70 | ?> |
| 71 | <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006"> |
| 72 | <?php |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame] | 73 | if(!$data) { |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 74 | try { |
| 75 | $json = json_encode( |
| 76 | array( |
| 77 | "time" => time(), |
| 78 | "ua" => $_SERVER['HTTP_USER_AGENT'], |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame] | 79 | "user" => "none", |
| Matthias Andreas Benkard | b4b5cc1 | 2021-12-28 18:02:04 +0100 | [diff] [blame] | 80 | "ip" => $_SERVER['REMOTE_ADDR'], |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame] | 81 | "service" => "Error: invalid or missing request data" |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 82 | ) |
| 83 | ); |
| 84 | $redis->lPush('AUTODISCOVER_LOG', $json); |
| 85 | $redis->lTrim('AUTODISCOVER_LOG', 0, 100); |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame] | 86 | $redis->publish("F2B_CHANNEL", "Autodiscover: Invalid request by " . $_SERVER['REMOTE_ADDR']); |
| 87 | error_log("Autodiscover: Invalid request by " . $_SERVER['REMOTE_ADDR']); |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 88 | } |
| 89 | catch (RedisException $e) { |
| 90 | $_SESSION['return'][] = array( |
| 91 | 'type' => 'danger', |
| 92 | 'msg' => 'Redis: '.$e |
| 93 | ); |
| 94 | return false; |
| 95 | } |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame] | 96 | 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 | } |
| 109 | try { |
| 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); |
| 147 | try { |
| 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 | } |
| 156 | catch(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 |
| 173 | if (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 | |
| 206 | if (!empty($MailboxData['name'])) { |
| 207 | $displayname = $MailboxData['name']; |
| 208 | } |
| 209 | else { |
| 210 | $displayname = $email; |
| 211 | } |
| 212 | try { |
| 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 | } |
| 225 | catch (RedisException $e) { |
| 226 | $_SESSION['return'][] = array( |
| 227 | 'type' => 'danger', |
| 228 | 'msg' => 'Redis: '.$e |
| 229 | ); |
| 230 | return false; |
| 231 | } |
| 232 | if ($autodiscover_config['autodiscoverType'] == 'imap') { |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 233 | ?> |
| 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> |