using System.Net.Http.Headers; using System.Text; using System.Text.Json; namespace AmtScanner.Api.Services; /// /// Guacamole 远程桌面服务 /// public class GuacamoleService { private readonly ILogger _logger; private readonly HttpClient _httpClient; private readonly IConfiguration _config; private string? _authToken; private DateTime _tokenExpiry = DateTime.MinValue; public GuacamoleService(ILogger logger, IHttpClientFactory httpClientFactory, IConfiguration config) { _logger = logger; _httpClient = httpClientFactory.CreateClient("Guacamole"); _config = config; } /// /// 获取 Guacamole 认证 Token /// public async Task GetAuthTokenAsync() { // 如果 token 还有效,直接返回 if (!string.IsNullOrEmpty(_authToken) && DateTime.Now < _tokenExpiry) { return _authToken; } try { var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole"; var username = _config["Guacamole:Username"] ?? "guacadmin"; var password = _config["Guacamole:Password"] ?? "guacadmin"; var content = new FormUrlEncodedContent(new[] { new KeyValuePair("username", username), new KeyValuePair("password", password) }); var response = await _httpClient.PostAsync($"{baseUrl}/api/tokens", content); if (response.IsSuccessStatusCode) { var json = await response.Content.ReadAsStringAsync(); var result = JsonSerializer.Deserialize(json); _authToken = result.GetProperty("authToken").GetString(); _tokenExpiry = DateTime.Now.AddMinutes(30); // Token 有效期约 30 分钟 _logger.LogInformation("Guacamole 认证成功"); return _authToken; } else { _logger.LogError("Guacamole 认证失败: {Status}", response.StatusCode); return null; } } catch (Exception ex) { _logger.LogError(ex, "获取 Guacamole Token 失败"); return null; } } /// /// 创建 RDP 连接 /// public async Task CreateRdpConnectionAsync(string name, string hostname, string username, string password, int port = 3389) { var token = await GetAuthTokenAsync(); if (string.IsNullOrEmpty(token)) return null; try { var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole"; var dataSource = _config["Guacamole:DataSource"] ?? "mysql"; var connection = new { parentIdentifier = "ROOT", name = name, protocol = "rdp", parameters = new { hostname = hostname, port = port.ToString(), username = username, password = password, security = "any", ignoreCert = "true", enableDrive = "true", createDrivePath = "true" }, attributes = new { maxConnections = "", maxConnectionsPerUser = "" } }; var json = JsonSerializer.Serialize(connection); var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync( $"{baseUrl}/api/session/data/{dataSource}/connections?token={token}", content); if (response.IsSuccessStatusCode) { var resultJson = await response.Content.ReadAsStringAsync(); var result = JsonSerializer.Deserialize(resultJson); var connectionId = result.GetProperty("identifier").GetString(); _logger.LogInformation("创建 RDP 连接成功: {Id}", connectionId); return connectionId; } else { var error = await response.Content.ReadAsStringAsync(); _logger.LogError("创建 RDP 连接失败: {Status} - {Error}", response.StatusCode, error); return null; } } catch (Exception ex) { _logger.LogError(ex, "创建 RDP 连接异常"); return null; } } /// /// 创建 VNC 连接 /// public async Task CreateVncConnectionAsync(string name, string hostname, string password, int port = 5900) { var token = await GetAuthTokenAsync(); if (string.IsNullOrEmpty(token)) return null; try { var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole"; var dataSource = _config["Guacamole:DataSource"] ?? "mysql"; var connection = new { parentIdentifier = "ROOT", name = name, protocol = "vnc", parameters = new { hostname = hostname, port = port.ToString(), password = password }, attributes = new { maxConnections = "", maxConnectionsPerUser = "" } }; var json = JsonSerializer.Serialize(connection); var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync( $"{baseUrl}/api/session/data/{dataSource}/connections?token={token}", content); if (response.IsSuccessStatusCode) { var resultJson = await response.Content.ReadAsStringAsync(); var result = JsonSerializer.Deserialize(resultJson); var connectionId = result.GetProperty("identifier").GetString(); _logger.LogInformation("创建 VNC 连接成功: {Id}", connectionId); return connectionId; } else { var error = await response.Content.ReadAsStringAsync(); _logger.LogError("创建 VNC 连接失败: {Status} - {Error}", response.StatusCode, error); return null; } } catch (Exception ex) { _logger.LogError(ex, "创建 VNC 连接异常"); return null; } } /// /// 删除连接 /// public async Task DeleteConnectionAsync(string connectionId) { var token = await GetAuthTokenAsync(); if (string.IsNullOrEmpty(token)) return false; try { var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole"; var dataSource = _config["Guacamole:DataSource"] ?? "mysql"; var response = await _httpClient.DeleteAsync( $"{baseUrl}/api/session/data/{dataSource}/connections/{connectionId}?token={token}"); if (response.IsSuccessStatusCode) { _logger.LogInformation("删除连接成功: {Id}", connectionId); return true; } else { _logger.LogError("删除连接失败: {Status}", response.StatusCode); return false; } } catch (Exception ex) { _logger.LogError(ex, "删除连接异常"); return false; } } /// /// 获取连接 URL(用于 iframe 嵌入) /// public async Task GetConnectionUrlAsync(string connectionId) { var token = await GetAuthTokenAsync(); if (string.IsNullOrEmpty(token)) return null; var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole"; var dataSource = _config["Guacamole:DataSource"] ?? "mysql"; // 构建客户端连接 URL // 格式: BASE_URL/#/client/BASE64(connectionId + '\0' + 'c' + '\0' + dataSource) var clientId = $"{connectionId}\0c\0{dataSource}"; var encodedClientId = Convert.ToBase64String(Encoding.UTF8.GetBytes(clientId)); return $"{baseUrl}/#/client/{encodedClientId}?token={token}"; } /// /// 获取所有连接 /// public async Task> GetConnectionsAsync() { var token = await GetAuthTokenAsync(); if (string.IsNullOrEmpty(token)) return new List(); try { var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole"; var dataSource = _config["Guacamole:DataSource"] ?? "mysql"; var response = await _httpClient.GetAsync( $"{baseUrl}/api/session/data/{dataSource}/connections?token={token}"); if (response.IsSuccessStatusCode) { var json = await response.Content.ReadAsStringAsync(); var connections = JsonSerializer.Deserialize>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); return connections?.Values.ToList() ?? new List(); } } catch (Exception ex) { _logger.LogError(ex, "获取连接列表失败"); } return new List(); } } public class GuacamoleConnection { public string Identifier { get; set; } = ""; public string Name { get; set; } = ""; public string Protocol { get; set; } = ""; public string ParentIdentifier { get; set; } = ""; }