prepare("SELECT username FROM users WHERE user_id = ? LIMIT 1"); $stmt->bind_param('i', $userId); $stmt->execute(); $result = $stmt->get_result(); $row = $result->fetch_assoc(); $stmt->close(); if (!$row || empty($row['username'])) { http_response_code(404); exit; } $username = $row['username']; } catch (Exception $e) { error_log("user_avatar: DB error for user_id=$userId: " . $e->getMessage()); http_response_code(500); exit; } // Query lldap via LDAP $ldapHost = $cfg['LDAP_HOST']; $ldapPort = $cfg['LDAP_PORT']; $bindDn = $cfg['LDAP_BIND_DN']; $bindPw = $cfg['LDAP_BIND_PW']; $userBase = $cfg['LDAP_USER_BASE']; // Escape username for LDAP filter (RFC 4515) $safeUsername = ldap_escape($username, '', LDAP_ESCAPE_FILTER); $filter = "(uid=$safeUsername)"; $avatarData = null; try { $ldap = @ldap_connect("ldap://$ldapHost:$ldapPort"); if (!$ldap) { throw new RuntimeException("ldap_connect failed"); } ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3); ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0); ldap_set_option($ldap, LDAP_OPT_NETWORK_TIMEOUT, 3); ldap_set_option($ldap, LDAP_OPT_TIMELIMIT, 3); if (!@ldap_bind($ldap, $bindDn, $bindPw)) { throw new RuntimeException("LDAP bind failed: " . ldap_error($ldap)); } $search = @ldap_search($ldap, $userBase, $filter, ['avatar'], 0, 1, 3); if (!$search) { throw new RuntimeException("LDAP search failed: " . ldap_error($ldap)); } $entries = ldap_get_entries($ldap, $search); if ($entries['count'] > 0 && !empty($entries[0]['avatar'][0])) { // ldap_get_entries() returns the attribute value as raw binary. $avatarData = $entries[0]['avatar'][0]; } ldap_unbind($ldap); } catch (Exception $e) { error_log("user_avatar: LDAP error for username=$username: " . $e->getMessage()); // Fall through to 404 } if ($avatarData === null || strlen($avatarData) < 100) { // Write sentinel so we don't hammer LDAP for users without avatars file_put_contents($noAvatarSentinel, ''); http_response_code(404); exit; } // Validate it's actually a JPEG (magic bytes FF D8 FF) if (substr($avatarData, 0, 3) !== "\xFF\xD8\xFF") { error_log("user_avatar: non-JPEG data for username=$username"); file_put_contents($noAvatarSentinel, ''); http_response_code(404); exit; } // Cache to disk file_put_contents($cacheFile, $avatarData); // Remove stale sentinel if present if (file_exists($noAvatarSentinel)) { unlink($noAvatarSentinel); } header('Content-Type: image/jpeg'); header('Cache-Control: private, max-age=' . $cacheTtl); header('X-Avatar-Source: ldap'); echo $avatarData;