| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 | <?phpfunction check_login($user, $pass, $app_passwd_data = false, $extra = null) {  global $pdo;  global $redis;  $is_internal = $extra['is_internal'];  $role = $extra['role'];  // Try validate admin  if (!isset($role) || $role == "admin") {    $result = admin_login($user, $pass);    if ($result !== false) return $result;  }  // Try validate domain admin  if (!isset($role) || $role == "domain_admin") {    $result = domainadmin_login($user, $pass);    if ($result !== false) return $result;  }  // Try validate user  if (!isset($role) || $role == "user") {    $result = user_login($user, $pass);    if ($result !== false) return $result;  }  // Try validate app password  if (!isset($role) || $role == "app") {    $result = apppass_login($user, $pass, $app_passwd_data);    if ($result !== false) return $result;  }  // skip log and only return false if it's an internal request  if ($is_internal == true) 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 admin_login($user, $pass){  global $pdo;  if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {    if (!$is_internal){      $_SESSION['return'][] =  array(        'type' => 'danger',        'log' => array(__FUNCTION__, $user, '*'),        'msg' => 'malformed_username'      );    }    return false;  }  $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));  $row = $stmt->fetch(PDO::FETCH_ASSOC);  // 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 domainadmin_login($user, $pass){  global $pdo;  if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {    if (!$is_internal){      $_SESSION['return'][] =  array(        'type' => 'danger',        'log' => array(__FUNCTION__, $user, '*'),        'msg' => 'malformed_username'      );    }    return false;  }  $stmt = $pdo->prepare("SELECT `password` FROM `admin`      WHERE `superadmin` = '0'      AND `active`='1'      AND `username` = :user");  $stmt->execute(array(':user' => $user));  $row = $stmt->fetch(PDO::FETCH_ASSOC);  // 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 user_login($user, $pass, $extra = null){  global $pdo;  global $iam_provider;  global $iam_settings;  $is_internal = $extra['is_internal'];  if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {    if (!$is_internal){      $_SESSION['return'][] =  array(        'type' => 'danger',        'log' => array(__FUNCTION__, $user, '*'),        'msg' => 'malformed_username'      );    }    return false;  }  $stmt = $pdo->prepare("SELECT * FROM `mailbox`      INNER JOIN domain on mailbox.domain = domain.domain      WHERE `kind` NOT REGEXP 'location|thing|group'        AND `domain`.`active`='1'        AND `username` = :user");  $stmt->execute(array(':user' => $user));  $row = $stmt->fetch(PDO::FETCH_ASSOC);  // user does not exist, try call idp login and create user if possible via rest flow  if (!$row){    if ($iam_settings['authsource'] == 'keycloak' && intval($iam_settings['mailpassword_flow']) == 1){      $result = keycloak_mbox_login_rest($user, $pass, array('is_internal' => $is_internal, 'create' => true));      if ($result !== false) return $result;    } else if ($iam_settings['authsource'] == 'ldap') {      $result = ldap_mbox_login($user, $pass, array('is_internal' => $is_internal, 'create' => true));      if ($result !== false) return $result;    }  }  if ($row['active'] != 1) {    return false;  }  switch ($row['authsource']) {    case 'keycloak':      // user authsource is keycloak, try using via rest flow      if (intval($iam_settings['mailpassword_flow']) == 1){        $result = keycloak_mbox_login_rest($user, $pass, array('is_internal' => $is_internal));        if ($result !== false) {          // 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";          }        }        return $result;      } else {        return false;      }    break;    case 'ldap':      // user authsource is ldap      $result = ldap_mbox_login($user, $pass, array('is_internal' => $is_internal));      if ($result !== false) {        // 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";        }      }      return $result;    break;    default:      // 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 && !$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";        }      }    break;  }  return false;}function apppass_login($user, $pass, $app_passwd_data, $extra = null){  global $pdo;  $is_internal = $extra['is_internal'];  if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {    if (!$is_internal){      $_SESSION['return'][] =  array(        'type' => 'danger',        'log' => array(__FUNCTION__, $user, '*'),        'msg' => 'malformed_username'      );    }    return false;  }  $protocol = false;  if ($app_passwd_data['eas']){    $protocol = 'eas';  } else if ($app_passwd_data['dav']){    $protocol = 'dav';  } else if ($app_passwd_data['smtp']){    $protocol = 'smtp';  } else if ($app_passwd_data['imap']){    $protocol = 'imap';  } else if ($app_passwd_data['sieve']){    $protocol = 'sieve';  } else if ($app_passwd_data['pop3']){    $protocol = 'pop3';  } else if (!$is_internal) {    return false;  }  // fetch app password data  $stmt = $pdo->prepare("SELECT `app_passwd`.*, `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"  );  // fetch password data  $stmt->execute(array(    ':user' => $user,  ));  $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);  foreach ($rows as $row) {    if ($protocol && $row[$protocol . '_access'] != '1'){      continue;    }    // verify password    if (verify_hash($row['password'], $pass) !== false) {      if ($is_internal){        $remote_addr = $extra['remote_addr'];      } else {        $remote_addr = ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']);      }      $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' => $remote_addr      ));      unset($_SESSION['ldelay']);      return "user";    }  }  return false;}// Keycloak REST Api Flow - auth user by mailcow_password attribute// This password will be used for direct UI, IMAP and SMTP Auth// To use direct user credentials, only Authorization Code Flow is validfunction keycloak_mbox_login_rest($user, $pass, $extra = null){  global $pdo;  global $iam_provider;  global $iam_settings;  $is_internal = $extra['is_internal'];  $create = $extra['create'];  if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {    if (!$is_internal){      $_SESSION['return'][] =  array(        'type' => 'danger',        'log' => array(__FUNCTION__, $user, '*'),        'msg' => 'malformed_username'      );    }    return false;  }  // get access_token for service account of mailcow client  $admin_token = identity_provider("get-keycloak-admin-token");  // get the mailcow_password attribute from keycloak user  $url = "{$iam_settings['server_url']}/admin/realms/{$iam_settings['realm']}/users";  $queryParams = array('email' => $user, 'exact' => true);  $queryString = http_build_query($queryParams);  $curl = curl_init();  curl_setopt($curl, CURLOPT_TIMEOUT, 7);  curl_setopt($curl, CURLOPT_URL, $url . '?' . $queryString);  curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);  curl_setopt($curl, CURLOPT_HTTPHEADER, array(      'Authorization: Bearer ' . $admin_token,      'Content-Type: application/json'  ));  $user_res = json_decode(curl_exec($curl), true)[0];  $code = curl_getinfo($curl, CURLINFO_HTTP_CODE);  curl_close($curl);  if ($code != 200) {    return false;  }  if (!isset($user_res['attributes']['mailcow_password']) || !is_array($user_res['attributes']['mailcow_password'])){    return false;  }  if (empty($user_res['attributes']['mailcow_password'][0])){    return false;  }  // validate mailcow_password  $mailcow_password = $user_res['attributes']['mailcow_password'][0];  if (!verify_hash($mailcow_password, $pass)) {    return false;  }  // get mapped template, if not set return false  // also return false if no mappers were defined  $user_template = $user_res['attributes']['mailcow_template'][0];  if ($create && (empty($iam_settings['mappers']) || !$user_template)){    return false;  } else if (!$create) {    // login success - dont create mailbox    return 'user';  }  // check if matching attribute exist  $mapper_key = array_search($user_template, $iam_settings['mappers']);  if ($mapper_key === false) return false;  // create mailbox  $create_res = mailbox('add', 'mailbox_from_template', array(    'domain' => explode('@', $user)[1],    'local_part' => explode('@', $user)[0],    'name' => $user_res['name'],    'authsource' => 'keycloak',    'template' => $iam_settings['templates'][$mapper_key]  ));  if (!$create_res) return false;  return 'user';}function ldap_mbox_login($user, $pass, $extra = null){  global $pdo;  global $iam_provider;  global $iam_settings;  $is_internal = $extra['is_internal'];  $create = $extra['create'];  if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {    if (!$is_internal){      $_SESSION['return'][] =  array(        'type' => 'danger',        'log' => array(__FUNCTION__, $user, '*'),        'msg' => 'malformed_username'      );    }    return false;  }  if (!$iam_provider) {    return false;  }  try {    $ldap_query = $iam_provider->query();    if (!empty($iam_settings['filter'])) {      $ldap_query = $ldap_query->rawFilter($iam_settings['filter']);    }    $ldap_query = $ldap_query->where($iam_settings['username_field'], '=', $user)      ->select([$iam_settings['username_field'], $iam_settings['attribute_field'], 'displayname', 'distinguishedname', 'dn']);    $user_res = $ldap_query->firstOrFail();  } catch (Exception $e) {    // clear $_SESSION['return'] to not leak data    $_SESSION['return'] = array();    $_SESSION['return'][] =  array(      'type' => 'danger',      'log' => array(__FUNCTION__, $user, '*', $e->getMessage()),      'msg' => 'ldap_error'    );    return false;  }  try {    if (!$iam_provider->auth()->attempt($user_res['dn'], $pass)) {      $_SESSION['return'][] =  array(        'type' => 'danger',        'log' => array(__FUNCTION__, $user, '*', $user_res),        'msg' => 'ldap_auth_failed'      );      return false;    }  } catch (Exception $e) {    // clear $_SESSION['return'] to not leak data    $_SESSION['return'] = array();    $_SESSION['return'][] =  array(      'type' => 'danger',      'log' => array(__FUNCTION__, $user, '*', $e->getMessage()),      'msg' => 'ldap_error'    );    return false;  }  // get mapped template, if not set return false  // also return false if no mappers were defined  $user_template = $user_res[$iam_settings['attribute_field']][0];  if ($create && (empty($iam_settings['mappers']) || !$user_template)){    return false;  } else if (!$create) {    // login success - dont create mailbox    return 'user';  }  // check if matching attribute exist  $mapper_key = array_search($user_template, $iam_settings['mappers']);  if ($mapper_key === false) return false;  // create mailbox  $create_res = mailbox('add', 'mailbox_from_template', array(    'domain' => explode('@', $user)[1],    'local_part' => explode('@', $user)[0],    'name' => $user_res['displayname'][0],    'authsource' => 'ldap',    'template' => $iam_settings['templates'][$mapper_key]  ));  if (!$create_res) return false;  return 'user';}
 |