| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 | <?phprequire_once(__DIR__ . '/../web/inc/vars.inc.php');if (file_exists(__DIR__ . '/../web/inc/vars.local.inc.php')) {  include_once(__DIR__ . '/../web/inc/vars.local.inc.php');}require_once __DIR__ . '/../web/inc/lib/vendor/autoload.php';// Init database//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;$opt = [    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,    PDO::ATTR_EMULATE_PREPARES   => false,];try {  $pdo = new PDO($dsn, $database_user, $database_pass, $opt);}catch (PDOException $e) {  logMsg("err", $e->getMessage());  session_destroy();  exit;}// Init Redis$redis = new Redis();try {  if (!empty(getenv('REDIS_SLAVEOF_IP'))) {    $redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));  }  else {    $redis->connect('redis-mailcow', 6379);  }  $redis->auth(getenv("REDISPASS"));}catch (Exception $e) {  echo "Exiting: " . $e->getMessage();  session_destroy();  exit;}function logMsg($priority, $message, $task = "Keycloak Sync") {  global $redis;  $finalMsg = array(    "time" => time(),    "priority" => $priority,    "task" => $task,    "message" => $message  );  $redis->lPush('CRON_LOG', json_encode($finalMsg));}// Load core functions firstrequire_once __DIR__ . '/../web/inc/functions.inc.php';require_once __DIR__ . '/../web/inc/functions.auth.inc.php';require_once __DIR__ . '/../web/inc/sessions.inc.php';require_once __DIR__ . '/../web/inc/functions.mailbox.inc.php';require_once __DIR__ . '/../web/inc/functions.ratelimit.inc.php';require_once __DIR__ . '/../web/inc/functions.acl.inc.php';$_SESSION['mailcow_cc_username'] = "admin";$_SESSION['mailcow_cc_role'] = "admin";$_SESSION['acl']['tls_policy'] = "1";$_SESSION['acl']['quarantine_notification'] = "1";$_SESSION['acl']['quarantine_category'] = "1";$_SESSION['acl']['ratelimit'] = "1";$_SESSION['acl']['sogo_access'] = "1";$_SESSION['acl']['protocol_access'] = "1";$_SESSION['acl']['mailbox_relayhost'] = "1";$_SESSION['acl']['unlimited_quota'] = "1";$iam_settings = identity_provider('get');if ($iam_settings['authsource'] != "keycloak" || (intval($iam_settings['periodic_sync']) != 1 && intval($iam_settings['import_users']) != 1)) {  session_destroy();  exit;}// Set pagination variables$start = 0;$max = 100;// lock sync if already running$lock_file = '/tmp/iam-sync.lock';if (file_exists($lock_file)) {  $lock_file_parts = explode("\n", file_get_contents($lock_file));  $pid = $lock_file_parts[0];  if (count($lock_file_parts) > 1){    $last_execution = $lock_file_parts[1];    $elapsed_time = (time() - $last_execution) / 60;    if ($elapsed_time < intval($iam_settings['sync_interval'])) {      logMsg("warning", "Sync not ready (".number_format((float)$elapsed_time, 2, '.', '')."min / ".$iam_settings['sync_interval']."min)");      session_destroy();      exit;    }  }  if (posix_kill($pid, 0)) {    logMsg("warning", "Sync is already running");    session_destroy();    exit;  } else {    unlink($lock_file);  }}$lock_file_handle = fopen($lock_file, 'w');fwrite($lock_file_handle, getmypid());fclose($lock_file_handle);// Init Keycloak Provider$iam_provider = identity_provider('init');// Loop until all users have been retrievedwhile (true) {  // Get admin access token  $admin_token = identity_provider("get-keycloak-admin-token");  // Make the API request to retrieve the users  $url = "{$iam_settings['server_url']}/admin/realms/{$iam_settings['realm']}/users?first=$start&max=$max";  $ch = curl_init();  curl_setopt($ch, CURLOPT_URL, $url);  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  curl_setopt($ch, CURLOPT_HTTPHEADER, [    "Content-Type: application/json",    "Authorization: Bearer " . $admin_token  ]);  $response = curl_exec($ch);  $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);  curl_close($ch);  if ($code != 200){    logMsg("err", "Recieved HTTP {$code}");    session_destroy();    exit;  }  try {    $response = json_decode($response, true);  } catch (Exception $e) {    logMsg("err", $e->getMessage());    break;  }  if (!is_array($response)){    logMsg("err", "Recieved malformed response from keycloak api");    break;  }  if (count($response) == 0) {    break;  }  // Process the batch of users  foreach ($response as $user) {    if (empty($user['email'])){      logMsg("warning", "No email address in keycloak found for user " . $user['name']);      continue;    }    if (!isset($user['attributes'])){      logMsg("warning", "No attributes in keycloak found for user " . $user['email']);      continue;    }    if (!isset($user['attributes']['mailcow_template']) ||        !is_array($user['attributes']['mailcow_template']) ||        count($user['attributes']['mailcow_template']) == 0) {      logMsg("warning", "No mailcow_template in keycloak found for user " . $user['email']);      continue;    }    $mailcow_template = $user['attributes']['mailcow_template'];    // try get mailbox user    $stmt = $pdo->prepare("SELECT      mailbox.*,      domain.active AS d_active      FROM `mailbox`      INNER JOIN domain on mailbox.domain = domain.domain      WHERE `kind` NOT REGEXP 'location|thing|group'        AND `username` = :user");    $stmt->execute(array(':user' => $user['email']));    $row = $stmt->fetch(PDO::FETCH_ASSOC);    // check if matching attribute mapping exists    $mbox_template = null;    foreach ($iam_settings['mappers'] as $index => $mapper){      if (in_array($mapper, $user['attributes']['mailcow_template'])) {        $mbox_template = $mapper;        break;      }    }    if (!$mbox_template){      logMsg("warning", "No matching attribute mapping found for user " . $user['email']);      continue;    }    $_SESSION['access_all_exception'] = '1';    if (!$row && intval($iam_settings['import_users']) == 1){      // mailbox user does not exist, create...      logMsg("info", "Creating user " . $user['email']);      $create_res = mailbox('add', 'mailbox_from_template', array(        'domain' => explode('@', $user['email'])[1],        'local_part' => explode('@', $user['email'])[0],        'name' => $user['firstName'] . " " . $user['lastName'],        'authsource' => 'keycloak',        'template' => $mbox_template      ));      if (!$create_res){        logMsg("err", "Could not create user " . $user['email']);        continue;      }    } else if ($row && intval($iam_settings['periodic_sync']) == 1) {      // mailbox user does exist, sync attribtues...      logMsg("info", "Syncing attributes for user " . $user['email']);      mailbox('edit', 'mailbox_from_template', array(        'username' => $user['email'],        'name' => $user['firstName'] . " " . $user['lastName'],        'template' => $mbox_template      ));    } else {      // skip mailbox user      logMsg("info", "Skipping user " . $user['email']);    }    $_SESSION['access_all_exception'] = '0';    sleep(0.025);  }  // Update the pagination variables for the next batch  $start += $max;  sleep(1);}logMsg("info", "DONE!");// add last execution time to lock file$lock_file_handle = fopen($lock_file, 'w');fwrite($lock_file_handle, getmypid() . "\n" . time());fclose($lock_file_handle);session_destroy();
 |