Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assistance with handling and tracking down the cause of: Error: .getCursorLocation() timed out #65

Closed
MichaelLeeHobbs opened this issue May 5, 2018 · 2 comments

Comments

@MichaelLeeHobbs
Copy link

I've built an SSH server to wrap some unsecured connections. Using terminal-kit for the interactive auth and menus. During interactive auth I've been having an issue where sometimes on slow vpn connections term.inputField will return an error of "Error: .getCursorLocation() timed out". I'm managed to solve this with: term.timeout = 3000 but that is not a very robust solution unless I pick some arbitrarily large value. but that could have its own issues.

To give a little background client's connect via varied Linux/Windows ssh clients. I'm using SSH2 on the server side. Once a shell is ready I create a terminal using the shells stream. From there on out its term(prompt) and term.inputField(...) to perform interactive two-factor auth as well as menu display/selection. Mainly, during login term.inputField will sometimes return Error: .getCursorLocation() timed out. Once it has done this it seems to kill stdin half of the stream. Thus it does not seem like it will be easy to recover from this error without creating the term.

_handleConnection(client, info) {
   ...
    client
        ...
        .on('session', (accept, reject)=> {
            let session = accept()
            this.logger(connectionData.id, connectionData.username, "session start")

            session
                .on('shell', (accept, reject)=> {
                    this.logger(connectionData.id, connectionData.username, "shell opened")
                    connectionData.stream = accept()
                    connectionData.term = term.createTerminal({stdin: connectionData.stream, stdout: connectionData.stream})
                    connectionData.term.timeout = 3000
                    connectionData.state = (connectionData.authMethod === 'interactive') ? 'auth' : 'mainMenu'
                    connectionData.interval = setInterval(()=>this._connectionLoop(connectionData), 100)
                })
    ...........

// called repeatedly from the auth code to get username/password/2FA token
_getInput({term, prompt, minLength = 1, maxLength = 128, maxTries = 3, tries = 0, isPassword = false, resolve, reject, connectionData = {id: '-', username: '-'}}) {
    let internalFunc = ({term, minLength = 0, maxLength = 128, maxTries, tries, isPassword, resolve, reject})=> {
        this.logger(connectionData.id, connectionData.username, `_getInput.internalFunc: ${prompt}`)
        term(prompt)
        term.inputField({echo: !isPassword, maxLength, minLength}, (error, input) => {
            this.logger(connectionData.id, connectionData.username, `_getInput.internalFunc.term.inputField received: input = ${input} error = ${error}`)
            term('\r\n\r\n')
            if (error || !input || input === '') {
                if (tries < maxTries) {
                    tries++
                    this._getInput({term, prompt, minLength, maxLength, maxTries, tries, isPassword, resolve, reject, connectionData})
                } else {
                    reject(error)
                }
            } else {
                resolve(input)
            }
        })
    }

    if (resolve === undefined || reject === undefined) {
        this.logger(connectionData.id, connectionData.username, `_getInput: ${prompt}`)
        return new Promise((resolve, reject)=> {
            this._getInput({term, prompt, minLength, maxLength, maxTries, tries, isPassword, resolve, reject, connectionData})
        })
    } else {
        internalFunc({term, prompt, minLength, maxLength, maxTries, tries, isPassword, resolve, reject})
    }
}

I'd be grateful for some feedback on how to better handle the "Error: .getCursorLocation() timed out"

@cronvel
Copy link
Owner

cronvel commented May 7, 2018

Hi @MichaelLeeHobbs ,
What you did is probably already the best solution.

To give you some insight, the terminal "protocol" is not really reliable, and in fact, a terminal that does not handle a cursor location request will just silently ignore it, thus the only way to guess if it does not support that feature is to set up a timeout.

So increasing that timeout value is what you need to do.

However, it sounds weird that you need to set it to a value as big as 3000 ms.

Do you try to use Terminal-kit before the end of the SSH negotiation phase? (in your sample code, it seems that the terminal is created once the SSH connection is fully functional, but I'm not sure how the underlying lib works).
If so, I would suggest you to simply wait for a fully functional connection before using Terminal-kit high-level features like .inputField(), and set the the timeout to something like 500 ms.

Also it's worth noting that there is almost no side-effect with a big timeout value, except that if something is not working, it would take more time to fail.

Also that error does nothing with to SDTIN, you should still be able to use Terminal-kit even after that error.

@MichaelLeeHobbs
Copy link
Author

@cronvel Thanks for the info. The high value is likely due to our older SSL VPN we are phasing out. Saying it's terrible is unfair to the word terrible. I'll set the value back down to a normal value and do some testing/debugging on why the terminal seems to break after an error. It's very likely it could be something we are doing as we are doing man-in-the-middle operations to manage the remote passwords. We had a lot of issues early on where we caused the streams to break or close.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants