| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 | <?phpfunction unset_auth_session(){  unset($_SESSION['keycloak_token']);  unset($_SESSION['keycloak_refresh_token']);  unset($_SESSION['mailcow_cc_username']);  unset($_SESSION['mailcow_cc_role']);  unset($_SESSION['oauth2state']);  unset($_SESSION['pending_mailcow_cc_username']);  unset($_SESSION['pending_mailcow_cc_role']);  unset($_SESSION['pending_tfa_methods']);}function check_login($user, $pass, $app_passwd_data = false) {  global $pdo;  global $redis;  if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {    $_SESSION['return'][] =  array(      'type' => 'danger',      'log' => array(__FUNCTION__, $user, '*'),      'msg' => 'malformed_username'    );    return false;  }  // Validate admin  $result = mailcow_admin_login($user, $pass);  if ($result){    return $result;  }  // Validate domain admin  $result = mailcow_domainadmin_login($user, $pass);  if ($result){    return $result;  }  // Validate mailbox user  // skip log & ldelay if requests comes from dovecot  $is_dovecot = false;  $request_ip =  ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']);  if ($request_ip == getenv('IPV4_NETWORK').'.250'){    $is_dovecot = true;  }  // check authsource  $stmt = $pdo->prepare("SELECT authsource FROM `mailbox`      INNER JOIN domain on mailbox.domain = domain.domain      WHERE `kind` NOT REGEXP 'location|thing|group'        AND `mailbox`.`active`='1'        AND `domain`.`active`='1'        AND `username` = :user");  $stmt->execute(array(':user' => $user));  $row = $stmt->fetch(PDO::FETCH_ASSOC);  if (!$row){    // mbox does not exist, call keycloak login and create mbox if possible    $result = keycloak_mbox_login($user, $pass, $is_dovecot, true);    if ($result){      return $result;    }  } else if ($row['authsource'] == 'keycloak'){    $result = keycloak_mbox_login($user, $pass, $is_dovecot);    if ($result){      return $result;    }  } else {    $result = mailcow_mbox_login($user, $pass, $app_passwd_data, $is_dovecot);    if ($result){      return $result;    }  }  // skip log and only return false  // netfilter uses dovecot error log for banning  if ($is_dovecot){    return false;  }  if (!isset($_SESSION['ldelay'])) {    $_SESSION['ldelay'] = "0";    $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);    error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);  }  elseif (!isset($_SESSION['mailcow_cc_username'])) {    $_SESSION['ldelay'] = $_SESSION['ldelay']+0.5;    $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);    error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);  }  $_SESSION['return'][] =  array(    'type' => 'danger',    'log' => array(__FUNCTION__, $user, '*'),    'msg' => 'login_failed'  );  sleep($_SESSION['ldelay']);  return false;}function mailcow_mbox_login($user, $pass, $app_passwd_data = false, $is_internal = false){  global $pdo;  $stmt = $pdo->prepare("SELECT * FROM `mailbox`      INNER JOIN domain on mailbox.domain = domain.domain      WHERE `kind` NOT REGEXP 'location|thing|group'        AND `mailbox`.`active`='1'        AND `domain`.`active`='1'        AND (`mailbox`.`authsource`='mailcow' OR `mailbox`.`authsource` IS NULL)        AND `username` = :user");  $stmt->execute(array(':user' => $user));  $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);  // check if password is app password  $is_app_passwd = false;  if ($app_passwd_data['eas']){    $is_app_passwd = 'eas';  } else if ($app_passwd_data['dav']){    $is_app_passwd = 'dav';  } else if ($app_passwd_data['smtp']){    $is_app_passwd = 'smtp';  } else if ($app_passwd_data['imap']){    $is_app_passwd = 'imap';  } else if ($app_passwd_data['sieve']){    $is_app_passwd = 'sieve';  } else if ($app_passwd_data['pop3']){    $is_app_passwd = 'pop3';  }  if ($is_app_passwd){    // fetch app password data    $app_passwd_query = "SELECT `app_passwd`.`password` as `password`, `app_passwd`.`id` as `app_passwd_id` FROM `app_passwd`      INNER JOIN `mailbox` ON `mailbox`.`username` = `app_passwd`.`mailbox`      INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain`      WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group'        AND `mailbox`.`active` = '1'        AND `domain`.`active` = '1'        AND `app_passwd`.`active` = '1'        AND `app_passwd`.`mailbox` = :user";    // check if app password has protocol access    // skip if $app_passwd_data['ignore_hasaccess'] is true and the call is not external    if (!$is_internal || ($is_internal && !$app_passwd_data['ignore_hasaccess'])){      $app_passwd_query = $app_passwd_query . " AND `app_passwd`.`" . $is_app_passwd . "_access` = '1'";    }    // fetch password data    $stmt = $pdo->prepare($app_passwd_query);    $stmt->execute(array(':user' => $user));    $rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC));  }  foreach ($rows as $row) {     // verify password    if (verify_hash($row['password'], $pass) !== false) {      if (!array_key_exists("app_passwd_id", $row)){         // password is not a app password        // check for tfa authenticators        $authenticators = get_tfa($user);        if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {          // authenticators found, init TFA flow          $_SESSION['pending_mailcow_cc_username'] = $user;          $_SESSION['pending_mailcow_cc_role'] = "user";          $_SESSION['pending_tfa_methods'] = $authenticators['additional'];          unset($_SESSION['ldelay']);          $_SESSION['return'][] =  array(            'type' => 'success',            'log' => array(__FUNCTION__, $user, '*'),            'msg' => array('logged_in_as', $user)          );          return "pending";        } else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) {          // no authenticators found, login successfull          if (!$is_internal){            unset($_SESSION['ldelay']);            // Reactivate TFA if it was set to "deactivate TFA for next login"            $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");            $stmt->execute(array(':user' => $user));            $_SESSION['return'][] =  array(              'type' => 'success',              'log' => array(__FUNCTION__, $user, '*'),              'msg' => array('logged_in_as', $user)            );          }          return "user";        }      } elseif ($is_app_passwd) {        // password is a app password        if ($is_internal){          // skip log          return "user";        }        $service = strtoupper($is_app_passwd);        $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)");        $stmt->execute(array(          ':service' => $service,          ':app_id' => $row['app_passwd_id'],          ':username' => $user,          ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'])        ));        unset($_SESSION['ldelay']);        return "user";      }    }  }  foreach ($rows as $row) {     // verify password    if (verify_hash($row['password'], $pass) !== false) {      if (!array_key_exists("app_passwd_id", $row)){         // password is not a app password        // check for tfa authenticators        $authenticators = get_tfa($user);        if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 &&            $app_passwd_data['eas'] !== true && $app_passwd_data['dav'] !== true) {          // authenticators found, init TFA flow          $_SESSION['pending_mailcow_cc_username'] = $user;          $_SESSION['pending_mailcow_cc_role'] = "user";          $_SESSION['pending_tfa_methods'] = $authenticators['additional'];          unset($_SESSION['ldelay']);          $_SESSION['return'][] =  array(            'type' => 'success',            'log' => array(__FUNCTION__, $user, '*'),            'msg' => array('logged_in_as', $user)          );          return "pending";        } else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) {          // no authenticators found, login successfull          // Reactivate TFA if it was set to "deactivate TFA for next login"          $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");          $stmt->execute(array(':user' => $user));          unset($_SESSION['ldelay']);          return "user";        }      } elseif ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) {        // password is a app password        $service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV';        $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)");        $stmt->execute(array(          ':service' => $service,          ':app_id' => $row['app_passwd_id'],          ':username' => $user,          ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'])        ));        unset($_SESSION['ldelay']);        return "user";      }    }  }  return false;}function mailcow_domainadmin_login($user, $pass){  global $pdo;  $stmt = $pdo->prepare("SELECT `password` FROM `admin`      WHERE `superadmin` = '0'      AND `active`='1'      AND `username` = :user");  $stmt->execute(array(':user' => $user));  $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);  foreach ($rows as $row) {    // verify password    if (verify_hash($row['password'], $pass) !== false) {      // check for tfa authenticators      $authenticators = get_tfa($user);      if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {        $_SESSION['pending_mailcow_cc_username'] = $user;        $_SESSION['pending_mailcow_cc_role'] = "domainadmin";        $_SESSION['pending_tfa_methods'] = $authenticators['additional'];        unset($_SESSION['ldelay']);        $_SESSION['return'][] =  array(          'type' => 'info',          'log' => array(__FUNCTION__, $user, '*'),          'msg' => 'awaiting_tfa_confirmation'        );        return "pending";      }      else {        unset($_SESSION['ldelay']);        // Reactivate TFA if it was set to "deactivate TFA for next login"        $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");        $stmt->execute(array(':user' => $user));        $_SESSION['return'][] =  array(          'type' => 'success',          'log' => array(__FUNCTION__, $user, '*'),          'msg' => array('logged_in_as', $user)        );        return "domainadmin";      }    }  }  return false;}function mailcow_admin_login($user, $pass){  global $pdo;  $user = strtolower(trim($user));  $stmt = $pdo->prepare("SELECT `password` FROM `admin`      WHERE `superadmin` = '1'      AND `active` = '1'      AND `username` = :user");  $stmt->execute(array(':user' => $user));  $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);  foreach ($rows as $row) {    // verify password    if (verify_hash($row['password'], $pass)) {      // check for tfa authenticators      $authenticators = get_tfa($user);      if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {        // active tfa authenticators found, set pending user login        $_SESSION['pending_mailcow_cc_username'] = $user;        $_SESSION['pending_mailcow_cc_role'] = "admin";        $_SESSION['pending_tfa_methods'] = $authenticators['additional'];        unset($_SESSION['ldelay']);        $_SESSION['return'][] =  array(          'type' => 'info',          'log' => array(__FUNCTION__, $user, '*'),          'msg' => 'awaiting_tfa_confirmation'        );        return "pending";      } else {        unset($_SESSION['ldelay']);        // Reactivate TFA if it was set to "deactivate TFA for next login"        $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");        $stmt->execute(array(':user' => $user));        $_SESSION['return'][] =  array(          'type' => 'success',          'log' => array(__FUNCTION__, $user, '*'),          'msg' => array('logged_in_as', $user)        );        return "admin";      }    }  }  return false;}function keycloak_mbox_login($user, $pass, $is_internal = false, $create = false){  global $pdo;  $identity_provider_settings = identity_provider('get');  $url = "{$identity_provider_settings['server_url']}/realms/{$identity_provider_settings['realm']}/protocol/openid-connect/token";  $req = http_build_query(array(    'grant_type'    => 'password',    'client_id'     => $identity_provider_settings['client_id'],    'client_secret' => $identity_provider_settings['client_secret'],    'username'      => $user,    'password'      => $pass,  ));  $curl = curl_init();  curl_setopt($curl, CURLOPT_URL, $url);  curl_setopt($curl, CURLOPT_POST, 1);  curl_setopt($curl, CURLOPT_POSTFIELDS, $req);  curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));  curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);  $res = json_decode(curl_exec($curl), true);  $code = curl_getinfo($curl, CURLINFO_HTTP_CODE);  curl_close ($curl);  if ($code == 200) {    // decode jwt    $user_data = json_decode(base64_decode(str_replace('_', '/', str_replace('-','+',explode('.', $res['access_token'])[1]))), true);    if ($user != $user_data['email']){      // check if $user is email address, only accept email address as username      return false;    }    if ($create && !empty($identity_provider_settings['roles'])){      // try to create mbox on successfull login      $user_roles = $user_data['realm_access']['roles'];      $mbox_template = null;      // check if matching rolemapping exist      foreach ($user_roles as $index => $role){        if (in_array($role, $identity_provider_settings['roles'])) {          $mbox_template = $identity_provider_settings['templates'][$index];          break;        }      }      if ($mbox_template){        $stmt = $pdo->prepare("SELECT * FROM `templates`         WHERE `template` = :template AND type = 'mailbox'");        $stmt->execute(array(          ":template" => $mbox_template        ));        $mbox_template_data = $stmt->fetch(PDO::FETCH_ASSOC);        if (!empty($mbox_template_data)){          $mbox_template_data = json_decode($mbox_template_data["attributes"], true);          $mbox_template_data['domain'] = explode('@', $user)[1];          $mbox_template_data['local_part'] = explode('@', $user)[0];          $mbox_template_data['authsource'] = 'keycloak';          $_SESSION['iam_create_login'] = true;          $create_res = mailbox('add', 'mailbox', $mbox_template_data);          $_SESSION['iam_create_login'] = false;          if (!$create_res){            return false;          }        }      }    }    $_SESSION['return'][] =  array(      'type' => 'success',      'log' => array(__FUNCTION__, $user, '*'),      'msg' => array('logged_in_as', $user)    );    return 'user';  } else {    return false;  }}function keycloak_verify_token(){  global $keycloak_provider;  global $pdo;  try {    $token = $keycloak_provider->getAccessToken('authorization_code', ['code' => $_GET['code']]);  } catch (Exception $e) {    $_SESSION['return'][] =  array(      'type' => 'danger',      'log' => array(__FUNCTION__),      'msg' => array('login_failed', $e->getMessage())    );    die($e->getMessage() . " - " . $_GET['code'] . " - " . $_SESSION['oauth2state']);    return false;  }    $login = false;  $_SESSION['keycloak_token'] = $token->getToken();  $_SESSION['keycloak_refresh_token'] = $token->getRefreshToken();  // decode jwt data  $info = json_decode(base64_decode(str_replace('_', '/', str_replace('-','+',explode('.', $token->getToken())[1]))), true);  if (in_array("mailbox", $info['realm_access']['roles']) && $info['email']){    // token valid, get mailbox    $stmt = $pdo->prepare("SELECT * FROM `mailbox`      INNER JOIN domain on mailbox.domain = domain.domain      WHERE `kind` NOT REGEXP 'location|thing|group'        AND `mailbox`.`active`='1'        AND `domain`.`active`='1'        AND `username` = :user        AND `authsource`='keycloak'");    $stmt->execute(array(':user' => $info['email']));    $row = $stmt->fetch(PDO::FETCH_ASSOC);    if ($row){      $_SESSION['mailcow_cc_username'] = $info['email'];      $_SESSION['mailcow_cc_role'] = "user";      $login = true;    } else {      $identity_provider_settings = identity_provider('get');      if (!empty($identity_provider_settings['roles'])){        // try to create mbox on successfull login        $user_roles = $info['realm_access']['roles'];        $mbox_template = null;        // check if matching rolemapping exist        foreach ($user_roles as $index => $role){          if (in_array($role, $identity_provider_settings['roles'])) {            $mbox_template = $identity_provider_settings['templates'][$index];            break;          }        }        if ($mbox_template){          $stmt = $pdo->prepare("SELECT * FROM `templates`           WHERE `template` = :template AND type = 'mailbox'");          $stmt->execute(array(            ":template" => $mbox_template          ));          $mbox_template_data = $stmt->fetch(PDO::FETCH_ASSOC);          if (!empty($mbox_template_data)){            $mbox_template_data = json_decode($mbox_template_data["attributes"], true);            $mbox_template_data['domain'] = explode('@', $info['email'])[1];            $mbox_template_data['local_part'] = explode('@', $info['email'])[0];            $mbox_template_data['authsource'] = 'keycloak';            $_SESSION['iam_create_login'] = true;            $create_res = mailbox('add', 'mailbox', $mbox_template_data);            $_SESSION['iam_create_login'] = false;            if ($create_res){              $_SESSION['mailcow_cc_username'] = $info['email'];              $_SESSION['mailcow_cc_role'] = "user";              $login = true;            }          }        }      }    }  }   if ($login){    $_SESSION['return'][] =  array(      'type' => 'success',      'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),      'msg' => array('logged_in_as', $_SESSION['mailcow_cc_username'])    );  } else {    unset_auth_session();      $_SESSION['return'][] =  array(      'type' => 'danger',      'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),      'msg' => 'login_failed'    );  }  return $login;}function keycloak_refresh(){  global $keycloak_provider;  try {    $token = $keycloak_provider->getAccessToken('refresh_token', ['refresh_token' => $_SESSION['keycloak_refresh_token']]);  } catch (Exception $e) {    $_SESSION['return'][] =  array(      'type' => 'danger',      'log' => array(__FUNCTION__),      'msg' => array('login_failed', $e->getMessage())    );    return false;  }  $refresh = false;  $_SESSION['keycloak_token'] = $token->getToken();  $_SESSION['keycloak_refresh_token'] = $token->getRefreshToken();  // decode jwt data  $info = json_decode(base64_decode(str_replace('_', '/', str_replace('-','+',explode('.', $_SESSION['keycloak_token'])[1]))), true);  if (in_array("mailbox",  $info['realm_access']['roles']) && $info['email']){    $_SESSION['mailcow_cc_username'] = $info['email'];    $_SESSION['mailcow_cc_role'] = "user";    $refresh = true;  }   if (!$refresh){    unset_auth_session();      $_SESSION['return'][] =  array(      'type' => 'danger',      'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']),      'msg' => 'refresh_login_failed'    );  }  return $refresh;}function keycloak_get_redirect(){  global $keycloak_provider;  $authUrl = $keycloak_provider->getAuthorizationUrl();  $_SESSION['oauth2state'] = $keycloak_provider->getState();  return $authUrl;}function keycloak_get_logout(){  global $keycloak_provider;  $logoutUrl = $keycloak_provider->getLogoutUrl();  $logoutUrl = $logoutUrl . "&post_logout_redirect_uri=https://" . $_SERVER['SERVER_NAME'];  return $logoutUrl;}
 |