Internal
Public Access
Allow blank usernames, asking for one at connect time instead
Closes #21. SSH and RDP profiles previously hard-required a username to even save the profile; that validation is dropped, and SessionTab::requestConnectOptions() now prompts for it at connect time when blank, reusing the existing password-prompt bar in unmasked mode -- the same pattern already used for a blank password. VNC's username is trickier: most VNC servers never use one (plain VNC Authentication and no-auth don't), only the two Apple auth schemes (security types 30/33) do, and which auth method gets used isn't known until mid-connection, after the server's security-type list has been negotiated -- too late for the pre-connect prompt SSH/RDP uses. Adds a new async request/response pair to SessionBackend, usernameRequested() signal / provideUsername() slot, mirroring the existing SSH host-key- confirmation pattern. VncSessionBackend pauses its state machine right before computing an Apple-auth response if no username is available -- without consuming the already-buffered prime/host-key bytes, so resuming re-parses them identically -- emits the request, and resumes via provideUsername(). Cancelling (or submitting blank) fails the connection cleanly instead of sending Apple auth an empty username. The username is kept on the tab's in-memory profile copy for its lifetime, not written back to the saved profile, matching how passwords are already handled. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -196,7 +196,8 @@ VncSessionBackend::VncSessionBackend(const Profile& profile, QObject* parent)
|
||||
m_tightCompressionMode(0),
|
||||
m_tightFilterId(0),
|
||||
m_tightLengthByteIndex(0),
|
||||
m_appleAuthKeyLength(0)
|
||||
m_appleAuthKeyLength(0),
|
||||
m_waitingForUsername(false)
|
||||
{
|
||||
std::memset(m_zrleInflateStream, 0, sizeof(z_stream_s));
|
||||
for (z_stream_s* stream : m_tightInflateStreams) {
|
||||
@@ -531,6 +532,8 @@ void VncSessionBackend::resetProtocolState()
|
||||
}
|
||||
m_appleAuthGenerator.clear();
|
||||
m_appleAuthKeyLength = 0;
|
||||
m_waitingForUsername = false;
|
||||
m_promptedUsername.clear();
|
||||
}
|
||||
|
||||
bool VncSessionBackend::haveBytes(int count) const
|
||||
@@ -671,6 +674,47 @@ void VncSessionBackend::sendAppleRsaHostKeyRequest()
|
||||
m_socket->write(msg);
|
||||
}
|
||||
|
||||
QString VncSessionBackend::effectiveUsername() const
|
||||
{
|
||||
const QString profileUsername = profile().username.trimmed();
|
||||
return profileUsername.isEmpty() ? m_promptedUsername : profileUsername;
|
||||
}
|
||||
|
||||
bool VncSessionBackend::ensureUsernameAvailable()
|
||||
{
|
||||
if (!effectiveUsername().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (!m_waitingForUsername) {
|
||||
m_waitingForUsername = true;
|
||||
emit usernameRequested(
|
||||
QStringLiteral("A username is required for %1's authentication method:")
|
||||
.arg(profile().host));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void VncSessionBackend::provideUsername(const QString& username)
|
||||
{
|
||||
if (!m_waitingForUsername) {
|
||||
return;
|
||||
}
|
||||
m_waitingForUsername = false;
|
||||
if (m_state != SessionState::Connecting) {
|
||||
// The connection already failed or was torn down while the
|
||||
// prompt was pending -- nothing left to resume.
|
||||
return;
|
||||
}
|
||||
m_promptedUsername = username.trimmed();
|
||||
if (m_promptedUsername.isEmpty()) {
|
||||
failConnection(
|
||||
QStringLiteral("A username is required for this VNC server's authentication method."),
|
||||
QStringLiteral("Username prompt was cancelled or left empty"));
|
||||
return;
|
||||
}
|
||||
processReceiveBuffer();
|
||||
}
|
||||
|
||||
void VncSessionBackend::finishHandshakeIntoRunningState()
|
||||
{
|
||||
emit remoteDesktopSizeChanged(m_framebuffer.width(), m_framebuffer.height());
|
||||
@@ -963,13 +1007,20 @@ void VncSessionBackend::processReceiveBuffer()
|
||||
if (!haveBytes(static_cast<int>(m_pendingLength))) {
|
||||
return;
|
||||
}
|
||||
// Checked before consuming any bytes: if a username is needed
|
||||
// and not yet available, pause here (leaving m_recvBuffer
|
||||
// untouched) until provideUsername() resumes us and this same
|
||||
// case re-parses identically.
|
||||
if (!ensureUsernameAvailable()) {
|
||||
return;
|
||||
}
|
||||
const int keyLength = static_cast<int>(m_appleAuthKeyLength);
|
||||
const QByteArray prime = m_recvBuffer.left(keyLength);
|
||||
const QByteArray serverPublicKey = m_recvBuffer.mid(keyLength, keyLength);
|
||||
m_recvBuffer.remove(0, static_cast<int>(m_pendingLength));
|
||||
|
||||
const VncAppleDhAuth::Response response = VncAppleDhAuth::computeResponse(
|
||||
m_appleAuthGenerator, prime, serverPublicKey, profile().username,
|
||||
m_appleAuthGenerator, prime, serverPublicKey, effectiveUsername(),
|
||||
m_activeOptions.password);
|
||||
if (response.clientPublicKey.isEmpty()) {
|
||||
failConnection(
|
||||
@@ -1020,11 +1071,15 @@ void VncSessionBackend::processReceiveBuffer()
|
||||
if (!haveBytes(totalBytes)) {
|
||||
return;
|
||||
}
|
||||
// See the matching comment in WaitingAppleAuthPrimeAndServerKey.
|
||||
if (!ensureUsernameAvailable()) {
|
||||
return;
|
||||
}
|
||||
const QByteArray hostKeyDer = m_recvBuffer.left(static_cast<int>(m_pendingLength));
|
||||
m_recvBuffer.remove(0, totalBytes);
|
||||
|
||||
const VncAppleRsaAuth::Response response = VncAppleRsaAuth::computeResponse(
|
||||
hostKeyDer, profile().username, m_activeOptions.password);
|
||||
hostKeyDer, effectiveUsername(), m_activeOptions.password);
|
||||
if (response.encryptedCredentials.isEmpty() || response.encryptedAesKey.isEmpty()) {
|
||||
failConnection(
|
||||
QStringLiteral(
|
||||
|
||||
Reference in New Issue
Block a user