HN2new | past | comments | ask | show | jobs | submitlogin
Turn any application that uses stdin/stdout into a WebSocket server (github.com/joewalnes)
311 points by adito on Dec 10, 2013 | hide | past | favorite | 88 comments


Amazing how the old is new again. Welcome back, CGI! I hope everyone's aware that this is just a toy and should never be used to do any real work, because most command line tools were never written to be exposed to the internet at large.


The first thing that came to my mind was djb's tcpserver which as far as I can see does much the same thing http://cr.yp.to/ucspi-tcp/tcpserver.html


tcpserver does not pipe input. It sets certain environmental variables, closes fd's 0 and 1 and runs another command line program. It has no quivalent to QUERY_STRING. Very different from CGI or this Websocket contraption. Much safer.

But the parent says that "most command line tools were never written to be exposed to the internet at large", and tcpserver is a command line tool that runs other command line tools, so is tcpserver OK to use for "real work" by his standards? Maybe we should use a GUI tool "written to be exposed to the internet at large"?


27 years since inetd came into being, and now we've made it web-scale. ;-)

http://en.wikipedia.org/wiki/Inetd

I actually don't mean to be snarky, this program acknowledges its heritage and is a cool hack!


Whats so toy-like about this? Its no toy but rather a useful method of tying differing software systems together .. I can think of many productive uses for this.


Problem is that it requires that the app handle all the security aspects of talking to the Internet, because STDIN/STDOUT is an unrestricted protocol. Normally when you write a webapp you have a well-tested parser to validate the HTTP request for you, and you have safe APIs to access parameters, and your templating language auto-escapes dangerous characters for you, and the response goes through middleware to prevent XSRF attacks, and the whole thing is written in a safe language where there's no possibility of buffer overruns.

With this, you have to handle all of that in your application binary. You can certainly do this. However, if you are asking why you need to do this, you are almost certainly not ready to do this.

I agree that this is a pretty useful method of tying software systems together. However, one of the the systems you're tying together is the public Internet, where all sorts of danger lurks. Writing secure parsers is hard. I've written an HTML parser [1], one of the most well-tested ones out there (I spent the better part of a year doing nothing but testing & debugging, and ran literally billions of documents through it), and Google's security team still found 2 bugs in it with about a day's work.

[1] https://github.com/google/gumbo-parser


Edited for tone.

> STDIN/STDOUT is an unrestricted protocol.

So are WebSockets.

> Normally when you write a webapp you have a well-tested parser to validate the HTTP request for you

The only HTTP in WebSockets is the handshake, which is what this daemon handles for you.

> and you have safe APIs to access parameters,

There are no "parameters"; WebSockets transport a stream of opaque packets.

> and your templating language auto-escapes dangerous characters for you,

There are no "unsafe characters" in a binary protocol. At best you could claim NUL and LF, the former because it has special meaning to C, and the latter because it's used as a message delimiter by this daemon. The worst either of those will cause is reading less of a message instead of more.

> and the response goes through middleware to prevent XSRF attacks

You have to do this in any language you use. This has nothing to do with communicating via Unix pipes.

> With this, you have to handle all of that in your application binary.

Most HTML parsing engines are available as libraries.


I think you're missing the forest of his argument through the trees of the details you quote.

It's oftentimes really dangerous to expose the raw stdin/stdout to the web. The point is that this by default has none of the usual safeties associated with making a program web-accessible. It's dangerous.

This doesn't make it a bad tool. It just means you need to be aware of the downsides.


If he said, "it's dangerous to connect a non-Web-aware program to the Internet", I'd agree.

Except he didn't, he said, "Problem is that it requires that the app handle all the security aspects of talking to the Internet", which is not unique to applications using this daemon, or even to applications using WebSockets.


Woot woot, downvote party! Any of you want to step up and tell me why you think I'm wrong and Nostrademons is right?


I'm guess you're getting downvoted not because you are right (I think you're right) but because the tone of your reply is a bit harsh ..


Thanks for the reply, you are probably right. I've cleaned up the snark. Authoritatively stated incorrect information is somewhat of a pet peeve of mine; my day job often requires that I patiently remediate such information, so I have developed a bit of a short fuse for these things off the job.


nostrademons's comment is for me the most important comment here in this discussion. Thanks nostrademons!

Just as an illustration, from the linked page:

"Security. Gumbo was initially designed for a product that worked with trusted input files only. We're working to harden this and make sure that it behaves as expected even on malicious input, but for now, Gumbo should only be run on trusted input or within a sandbox."


That's an issue with this implementation, not the idea of hooking up stdin/stdout to websockets.


Nostrademons doesn't know what he's talking about. See bct's comment, and my heavily-downvoted comment. Security has nothing to do with using STDIO and everything to do with how you code your program.


http://xkcd.com/386/

If you have corrections, post them, which you did up above. My post is being upvoted because it makes people think about considerations that they didn't think about before, which is a good thing. Further details are also a good thing, but I've found - through numerous engineering projects - that if you think you've found the "right" solution and someone is just "wrong", there's usually a trade-off you missed somewhere and a concern they're considering that you're not.

Anyway, I don't think that your conclusion ("Security has nothing to do with using STDIO and everything to do with how you code your program") and my conclusion ("You can do this, but you better consider the attack surface you're opening your program up to first") are all that incompatible. My point is that most programs with an interface on STDIN are not designed out of the box to be run on untrusted, potentially malicious input, and so you will have to spend time hardening yours that far exceeds the time you spend hooking up the network protocol to STDIN.


> My point is that most programs with an interface on STDIN are not designed out of the box to be run on untrusted, potentially malicious input, and so you will have to spend time hardening yours that far exceeds the time you spend hooking up the network protocol to STDIN.

Yes, that is a valid point. However that is not what you said, which was:

> Problem is that it requires that the app handle all the security aspects of talking to the Internet

which is not unique to applications using this daemon, or even to WebSocket applications in general. That's borderline FUD. Combined with the inaccurate statements you made to back it up, your original post is simply incorrect and misleading.

> if you think you've found the "right" solution and someone is just "wrong", there's usually a trade-off you missed somewhere and a concern they're considering that you're not.

No, not if the "right" solution is backed by facts and research, and the "wrong" solution is based on demonstrably incorrect half-remembered mistruths from ten years ago. There's not really a tradeoff there.


There is no reason not to think that anyone who has written an app to use STDIO may be more likely, in fact, to have a safe string-parsing/protocol handler setup. I mean, there is no guarantee that just because an app uses STDIO, they have exposed their strlen() and its ilk to the world ..

This table is not complete:

http://bstring.sourceforge.net/features.html

.. and the thing is, its not the 90's any more. There are a lot of great tools that can be exposed on this interface, if you think about it, quite safely ..


caveat: real work != internet. cgi is still very useful for those super small internal one-shot scripts.


Well, for some folks it works fine on the internet as well:

http://www.mail-archive.com/fossil-users@lists.fossil-scm.or...

tl;dr: sqlite.org and fossil-scm.org run using a simple HTTP server via inetd and some parts of the website are CGI scripts. In 2010 they served over a quarter million requests per day on a tiny VPS with only 3% CPU load.


I run my entire web site on CGI scripts invoked by Apache. I really don't get the hate for it. It's extremely convenient and works great.


Same here. And to get the flame bait machine started, we even do it in Perl!


So I've been curious about this; Nimrod has some SCGI libraries and the like built in, but I didn't know whether that would be good enough for real world usage without building a concurrent application server into my App itself. How does it handle multiple requests at once without impacting performance badly? I know that's a noob question, I struggled to find good information on SCGI/CGI for today's use cases, so I sort of wrote it off as too hard basket...


Shhh! You're on Hackernews man...


CGI (when used with suexec) is still the tool of choice for your usual low-coat shared webhosting provider, and it's still good enough if all your customers want to do is deploy Wordpress, phpBB and maybe some custom PHP and Perl CGI scripts.


Take a look at how the Leap Motion works. You connect the Leap Motion to your usb port, a demon/service interacts with the Leap Motion and opens a websocket server on localhost so that websites can interact with the hardware device without browser plugins.


Thanks lhaussknecht That's also what I thought! I don't understand the negativity here!

"Oh, this is a toy. Oh, this doesn't meet my Enterprise needs. Oh, this isn't EAL7¹ Certified, I can't run my Atombomb defense system with it."

Dear Ladies and Gentlemen, you have to admit that this is ueber useful for so many scenarios, that are out of the box, you're thinking in.

I can now hack a firebase clone, just for fun and my prototypes don't require the setup of dozens or a hundred npm packages. Besides the coolness of NodeJS, there are still some use-cases for hackers with this.

--

1. http://en.wikipedia.org/wiki/Evaluation_Assurance_Level#EAL7...


CGI won't maintain an open connection though


Author here.

If you use this on top of programs like bash, well ermm, you get what you deserve ;).

Here's an example of how I used websocketd to create a little dashboard for monitoring Linux CPU/memory/IO stats. It basically uses websocketd to stream the output of vmstat to a web-page that plots the numbers: https://github.com/joewalnes/web-vmstats

Other useful examples: tailing log files, executing long running job and monitoring output, or interactive querying of datasets that require a long running 'cursor'.

This is not for everyone or everything. Remember that like CGI, a process is forked for each connection so it's not the kind of thing if you want to handle a million concurrent connections on a single server.

However for dashboards, admin tools, quick mashups, visualizations, etc - it's a pretty handy tool.


You should probably include a warning anyway. When I was starting out programming I would do stuff like exec-ing shell scripts from PHP, oblivious to the consequences.


I have a project where small time running tasks requires user input, this would be very nice to have. What would be nice to have is attaching the stdouts of procesess via file descriptors. But I think it could be done easily.


Basically the same thing in Node.coffee, just because:

    { Server } = require 'ws'
    { spawn } = require 'child_process'

    command = process.argv[2]
    args = process.argv[3..]

    wss = new Server port: 8080
    wss.on 'connection', (ws) ->
      ps = spawn command, args
      ps.stdout.on 'data', (data) -> ws.send data.toString()
      ws.on 'message', (data) -> ps.stdin.write data.toString()
      ws.on 'close', -> ps.kill()
      ps.on 'close', -> ws.close()
(Needs a bit more error handling)


Author here. Indeed! The very first version of websocketd was actually written in Node. It's great for this kind of stuff.

The reason I switched to Go was to make distribution/installation for end users easier.


Curious as to the approach of pulling a project local Go distribution instead of using the default. Is this to ensure Go version compatibility?


> Is this to ensure Go version compatibility

Huh? There are no version compatibility issues with Go. Just use the most recent version of the compiler to compiler your code.

Anything written since Go 1.0 is compatible with all the 1.x compilers. Anything written before that (which is truly ancient code at this point) can be updated automatically with the 'go fix' command.


The Makefile in the project pulls a copy of Go v1.1.2 into the local tree, builds, and uses that to build websockets:

    # Self contained Go build file that will download and install (locally) the correct
    # version of Go, and build our programs. Go does not need to be installed on the
    # system (and if it already is, it will be ignored).
I wondering why.


Author here. 2 reasons.

1. So developers can do a clean checkout and it will just work. No external dependencies needed (e.g. manually installing Go, setting GOPATH etc).

2. To ensure that anyone who builds and releases it always uses the exact same version of Go.


> 2. To ensure that anyone who builds and releases it always uses the exact same version of Go.

Please don't do this! It's attempting to solve a problem that doesn't exist.

As I mentioned above, Go does not have compatibility issues with different version of the compiler. This is a huge strength of Go, and spreading the idea that using the same version of Go is somehow beneficial creates misunderstanding for new developers.

I challenge anyone to find an example of Go 1.x code in which using the most recent version of the compiler will build an incorrect binary. Seriously, if you find one, please let me (or better yet, the Go mailing list) know, because the Go team has made a dedicated effort to make sure that this doesn't happen. (Such cases exist, but I am fairly certain without even looking that this program doesn't run into any, whereas it's clear (below) that the Makefile for this project doesn't handle cases that the standard build tools do.)

I didn't understand this when I started using Go over a year ago - at the time, I also used Makefiles over Go's built-in build tools, and did a number of other non-idiomatic things surrounding the build process. More experienced Go programmers told me I was wrong, and I didn't listen at first. In the end, I finally realized that they were right: it added complexity for literally zero value. Please don't make the same mistakes that I did.


FWIW removing the Makefile and simply running "go build" in the directory results in a working build on go version go1.2 darwin/amd64.

With the Makefile I had to alter the static Go version to "darwin" to get it to pull the correct source.


It solves the problem of users having an older version of Go installed on their system. For example, if they installed it via apt-get.


Sure, but that's a very complex solution to a very simple problem.

While using older versions of Go is not recommended, it's not going to cause you any problems unless you're relying on (e.g.) 1.2-specific syntax like the three-argument slice syntax introduced in 1.2. If you are doing that, then older versions of the compiler will fail to parse the code, which would hopefully motivate the developer to upgrade their system-wide Go installation[0]. That way, they still only have one version of the compiler installed, and it will work for all programs they need to compile.

The only people who need to have multiple different versions of the gc compiler installed on their system are people who know what they're doing and handle this themselves manually (ie, the core contributors to Go).

One of the explicit design goals of Go is to simplify the build process. A side-effect of this is that the build process and best practices are different for Go than for most other languages.

I appreciate the effort that you've put into trying to make this easy to deploy, but using Makefiles and local versions of the compiler in this way is generally bad practice for experienced Go programmers, and encourages bad habits for inexperienced Go programmers.

[0] Ubuntu's repositories are hilariously behind the times on this. That's a separate matter of discussion, but it's why it's not encouraged to install Go through the main Ubuntu repositories.


I noticed that the Makefile downloads Go over plain http and runs it immediately. It is a security risk to run software without proper verification of its origin.


Of course, Perl has been doing this for over a decade. Nobody thought it was remarkable enough to write an article about it though.


Not just Perl. This is basically inetd re-invented.

[0] http://en.wikipedia.org/wiki/Inetd


huhtenberg: I believe AsymetricCom was referring to being able to compile an executable using perlcc. He wasn't referring to the networking aspects of it (WebSockets have not been around 10 years).

huhtenberg: You're right. It's very similar to inetd - that's why the first sentence in the websocketd description is "Like inetd, but for WebSockets".

The thing it adds on top of inetd is it takes care of the protocol so the underlying app doesn't need to. Namely HTTP WebSocket handshaking and data framing.


Nice! I love Node for these kind of asynchronous handling of data, it fits perfectly. Keep in mind you can go down the stream route. Instead of:

    ps.stdout.on 'data', (data) -> ws.send data.toString()
    ws.on 'message', (data) -> ps.stdin.write data.toString()
Just do:

    ps.stdout.pipe ws
    ws.pipe ps.stdin
I started using Node before Streams and I often forget piping too!

I still have a question for Node.js experts... would the 'close' events had to be handled then? Streams handle and propagate the 'end' event just fine, but should the underlying resource be closed? I guess killing `ps` is mandatory since ending `stdout` won't kill the process (or will it?), but is closing `ws` still mandatory too?


If the socket is terminated mid-command (thus emitting 'close'), one would want the process to be killed. Also, the socket doesn't know that no more transmission is required just because one particular stream is over. That is why close is called manually on the socket after the stream emits a 'close'.

This all provided you use a streaming web socket module like https://github.com/maxogden/websocket-stream


I thought about using pipe but the WebSocket library doesn't implement the Stream API, does it?



A nice example on Node. Only I don't quote get the ',' syntax. Is it a scoping thing?


It's a Coffee syntax thing.

  ws.on 'close', -> ps.kill()
That compiles to:

  ws.on('close', function() {
    return ps.kill();
  });


I have never done any Coffee but it looks like arguments are separated by commas, and, in this case, the second argument is a function of no argument (so the left hand side of the arrow is empty).

Edit: it was meant to be a reply to the parent.


Sorry if the question is too basic (I don't code in Node.js) but where is the websocket handshaking and the envelop management on this?


Creating a new server will start listening for connections, and the connection event will only be emitted after the handshake has finished. http://socket.io/ has more info on how the node api works.

    wss = new Server port: 8080
    wss.on 'connection', (ws) ->


{ Server } = require 'ws'


    websocketd --port=8080 bash
Then in the browser console:

    ws.send("ls")
I just ported bash to the web :)


Ha ha! That's so cool. Hey, off topic... what's your IP? ;)


Don't know his, but I've got the best one 127.123.234.345!


Combine that with Term.js[1] and you could have a fully functioning terminal in the browser.

[1] https://github.com/chjj/term.js



The possibilities are endless, just add a websocket proxy as a front-server and you can allow dynamic and even secure control of an entire infrastructure (given that you add auth layer). Here's something you could use for that: https://hackernews.hn/item?id=6880487 Or just setup haproxy with it.


So basically replicate SSH in a half assed way?


Well, no. More like telnet (optionally over tls).


ws.send('cat $HOME/.ssh/{id_rsa,id_rsa.pub,known_hosts}');

ws.send('history');

ws.send('echo "' + pubkey + '" >> $HOME/.ssh/authorized_hosts');


For some stupid reason I hadn't considered that logging stdin/out/err to a web interface for my node.js web apps via websockets. To think, I made a browser-side web IRC interface, and didn't consider this. It would be so useful for my clients, who don't know how, or find it too archaic to ssh in.

To just open up the admin area and see what's going on, provided they're indeed full-permission admins. Then, to actually send input from said interface, that could make ssh'ing into the server something one need not do often beyond initially setting up the app.

Thanks for the idea. Assuming we're not running as root, and the admin side of things is secure, am I not considering any critical pitfalls of this approach? Also, any frameworky cmsy thingers that already do this? Is this new, am I a unique snowflake?


yup new, but use a restricted jailed and chrooted shell. Ask your #sysadmin of choice =) You can even build a REST like API wrapper for this. That's what I was going to do.


Yeah, I thought the same thing concerning the REST api, after mulling it over a bit. Being tied directly to an app could be a bit awkward in practice (Bit of a problem when either can take either's process down).


This is beautiful. I can think of a dozen use cases for this right now for one of my pet projects.


Call me unimaginative. What uses are you thinking?


I run a popular Minecraft server, we have tons of scripts between the mc server, forum, admin interfaces, and all the other supporting stuff we run. This makes it so easy to create new functionality that interfaces with our various scripts. I'm hacking away as we speak.


isn't this reimplementing netcat -e command?

http://linux.die.net/man/1/ncat


Seems more like an inetd to me. Give a command and run it again and again.


It's similar to netcat -e and inetd, except it also handles the WebSocket protocol (negotiating the handshake and message framing), so your apps don't need to.


If you're on Ubuntu (and maybe other distros as well), be sure to install the nc-traditional package. Otherwise you don't get the nifty features such as -e


Basically, but it also handles some details specific to the websockets protocol.


Awesome.. I use the same kind of thing to monitor adb output sometimes:

https://github.com/minikomi/pipesock

also in go.

Edit: Doesn't do any receiving, only pipes to a socket what it gets.


That image made me chuckle. Had to confirm that it was just sourced from google images; but still. :)



If the shells script takes input parameters can we pass that and eventually turn it into some kind of web service ?


I don't recommend any kind of input parsing in a shell script. You're better of having websocketd fork something like Python, Ruby, Perl, PHP, etc.

Like CGI, websocketd will pass additional URL path and query parameters as environment variables.

So you could access something like ws://somehost/?a=b and you'd access the QUERY_STRING environment variable in your script (which contains "a=b"). Virtually every scripting language already has a library for parsing CGI environment variables so you can do that.

Basically, websocketd tries to emulate CGI as much as possible, but provide WebSocket streams instead of HTTP responses.


By integrating anything shell-based with anything web-based you encounter a plethora of potential exploits and security holes. That said, if you need to run a script that requires command line parameters, and all you have is standard in, then write a wrapper script that reads N lines of text from stdin first, then parses those lines into the parameters you require.

You have to be very conscious of input escaping and validation, though; otherwise your box will be owned in no time.


anyone getting an HTTPS warning? It's telling me that github.com cert was signed by an untrusted issuer...


Chrome tells me it's valid (signed by DigiCert) with a sha1 thumbprint of ‎d7 12 e9 69 65 dc f2 36 c8 74 c7 03 7d c0 b2 24 a9 3b d2 33.

Check your system time?


Check your date/time


Ok for me. Latest Chrome 31.0.1650.63 on Windows.


Not on Chrome.


Is this just xinetd for the ignorant?


no. you can still chain xinetd.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: