Home

Awesome

bish-bosh

bish-bosh is a MIT-licensed shell script client for MQTT 3.1.1 that runs without installation on any POSIX system on any POSIX shell: Linux, Mac OS X, Cygwin, AIX, FreeBSD, OpenBSD and NetBSD are all known to work, as are the DASH, GNU Bash and BusyBox shells. There are usually no dependencies at all if you're using BusyBox. For everything else, it's a very minimal set of helper programs that even the most basic of POSIX-compatible systems should have. Installation can be cp bish-bosh /path/to. It'll run on your router, high-end server, your smart phone, laptop or even an unlocked BT fibre modem.

A Command Interpreter for Scripting MQTT sessions

You can give bish-bosh any number of scripts to run a MQTT session. And, if it's on your PATH, these can even become MQTT driven programs, eg:-

#!/usr/bin/env bish-bosh
bishbosh_server='test.mosquitto.org'
bishbosh_clientId='my-client-id'

# We've got a message
bishbosh_connection_handler_PUBLISH()
{
	# bish-bosh handles QoS 1 and 2 for us
	
	# bish-bosh wires stdout to be data to send, so we need to redirect to stderr
	printf '%s' "Received a message on topic ${topicName} which was ${messageLength} byte(s) long and is in the file ${messageFilePath}" 1>&2
	
	# clean up
	rm "${messageFilePath}"
}

Making the above snippet executable (chmod +x SCRIPT) creates a fully-fledged MQTT driven program.

What's it good for?

For scripting MQTT!

If there's interest, then I could build bish-bosh into a MQTT broker… That would be quite a win for devops automation, where bootstrapping a set up is quite a chore. If you need to handle XML or JSON messages in your scripts, check out shellfire. bish-bosh is itself a shellfire application.

Download and Quick Start

Download the executable from the latest release, or simply clone from GitHub into your home folder by typing:-

cd ~
git clone 'https://github.com/raphaelcohn/bish-bosh.git'
(cd bish-bosh; git submodule update --init --recursive)
cd -

This will create a folder bish-bosh inside your home folder. bish-bosh can then be used straightaway, eg

cd ~/bish-bosh
./bish-bosh --client-id 12 --verbose 2

where 12 is an example of a client id you'd like to use. bosh-bosh will attempt to find its dependencies on the PATH, choose an optimum configuration and connect to a MQTT server (by default, test.mosquitto.org). This may appear to do very little until you press CTRL-C. That's because we haven't given bish-bosh anything to do apart from CONNECT and DISCONNECT. Why not create this file at /tmp/bish-bosh.example and see what happens:-

cat >/tmp/bish-bosh.example <<EOF
bishbosh_clientId=12

bishbosh_connection_handler_CONNACK()
{
    # Set up some subscriptions... another implementation could read from a standard file
    bishbosh_subscribe \
        '/topic/qos/0' 0 \
        '/topic/qos/1' 1 \
        '/topic/qos/3' 1

    bishbosh_unsubscribe \
        '/topic/not/wanted' \
        '/and/also/topic/not/wanted'

    # Publish a QoS 0 message
    # On topic a/b
    # Unretained
    # With value 'X'
    bishbosh_publishText 0 'a/b' no 'X'
}

bishbosh_connection_handler_PUBLISH()
{
    echo "Message received: retain=$retain, QoS=$QoS, dup=$dup, topicLength=$topicLength, topicName=$topicName, messageLength=$messageLength, messageFilePath=$messageFilePath"
}

bishbosh_connection_handler_noControlPacketsRead()
{
    # This event happens every few milliseconds - use this to publish some messages, change subscriptions or reload our configuration. Perhaps we could monitor a folder path?
    # bishbosh_publishText 0 'nowt' no 'hello world'
	echo 'No Control Packages Read' 1>&2
}
EOF

And run it with ./bish-bosh --verbose 2 -- /tmp/bish-bosh.example.

Of course, this might not work on your setup, and so you might need to install some dependencies or change your backend.

Getting it from Homebrew for Mac OS X

Hopefully in the next few weeks bish-bosh will be available as a Homebrew recipe, so you should be able to do

brew install bish-bosh

Installing into your PATH and Packaging

You might want to install bish-bosh in your PATH, or package it. bish-bosh as checked into GitHub isn't standalone: it needs to be fattened using shellfire. If you want a ready-to-use release, check out releases. Once in your PATH, you can write scripts with #!/usr/bin/env bish-bosh as the first line and have standalone bespoke MQTT clients - that can do anything. See this for an example.

Switches and Configuring

bish-bosh has a lot of switches! Most of them you'll hopefully never use: they're to deal with situations where network access isn't straightforward. Perhaps you've got multiple NICs or IP addresses, or a proxy is blocking you from connecting directly. And all of the switches have sensible defaults. All of bish-bosh's switches can be set using configuration (eg in /etc), or even in the scripts you run; the choice is yours. However, the basic invocation is very simple:-

bish-bosh --server 'SERVER' --client-id 'CLIENT_ID'

# or, if you prefer short options

bish-bosh -s 'SERVER' -c 'CLIENT_ID'

If you don't specify SERVER, it defaults to test.mosquitto.org. CLIENT_ID is a MQTT client id. (We have partial support for random client ids, so eventually you'll not even need to specify this).

If your MQTT server isn't running on port 1883, you can specify it:-

bish-bosh --server 'SERVER' --client-id 'CLIENT_ID' --port 'PORT'

# or, if you prefer short options

bish-bosh -s 'SERVER' -c 'CLIENT_ID' -p 'PORT'

where PORT is a port between 1 and 65535.

Hang on a minute, where do I put the MQTT username / password / other connect stuff?

Well, it's quite straightforward. Rather than use even more switches (and place sensitive data in the command line where any user with ps can see it), you can specify configuration scripts. For example, we could have the script snippet:-

bishbosh_connect_username='raphcohn'
bishbosh_connect_password='whatever you like'

saved as script.bishbosh and use it as

bish-bosh --server 'SERVER' --client-id 'CLIENT_ID' -- 'script.bishbosh'

The -- isn't strictly necessary, but it's good practice - just in case you name something --silly-file-name, it stops bish-bosh getting confused.

Of course, you can have more than one script, eg

bish-bosh --server 'SERVER' --client-id 'CLIENT_ID' -- 'script.bishbosh' 'another-script.bishbosh'

So you could keep sensitive data (eg a password) in one file, and everything else in another - a good approach which would let you check all your scripts into source control bar the one with the password, and so do simple production deployments and devops-stuff.

As an added convenience, you can also store configuration scripts on a per-client-id basis, too. This means that common connection settings for a client can be stored, but different runtime invocations catered for. Very useful for system administration tasks.

There's quite a lot of things than can be configured this way. If a setting is missing, bish-bosh applies a default. For things like QoS, we apply for the lowest; for usernames and passwords and wills, we omit them. So if you've got a MQTT server that doesn't need passwords (a bit odd, but possible), then you can just not set it. Please note that not set isn't the same thing as empty:-

bishbosh_connect_username=''
# is not the same as
unset bishbosh_connect_username

All switches can be set as configuration

Everything you specify as a long-option switch can be specified in configuration. By convention, the naming in configuration matches the switches, eg

--server 'test.mosquitto.org'
--client-path '/var/lib/bish-bosh/client'

is configured as

bishbosh_server='test.mosquitto.org'
bishbosh_clientPath='/var/lib/bish-bosh/client'

ie, prefix with bishbosh_, remove the -- and for every - followed by a letter, remove the - and make the letter capitalized.

But the really interesting scriptable stuff is done with configuration files or scriptlets

For example, this scriptlet shows a skeleton persistent client which does quite a lot (including retransmission) - with very little code:-

#!/usr/bin/env bish-bosh
bishbosh_server=test.mosquitto.org
# load your configuration if you need to, eg bishbosh_clientId="$(</path/to/client-id)" or use '.' (source)
bishbosh_clientId=<some-client-id>

bishbosh_connect_cleanSession=0
bishbosh_connect_willTopic='some/will/topic'
bishbosh_connect_willMessageFilePath=/path/to/will/message
bishbosh_connect_willQoS=1
bishbosh_connect_willRetain=1
# 5 second ping
bishbosh_connect_keepAlive=5
bishbosh_connect_username=<some-user-name>
bishbosh_connect_passwordFilePath=/path/to/password/kept/securely

bishbosh_connection_handler_CONNACK()
{
	# Set up some subscriptions... another implementation could read from a standard file
	bishbosh_subscribe \
		'/topic/qos/0' 0 \
		'/topic/qos/1' 1 \
		'/topic/qos/3' 1
	
	bishbosh_unsubscribe \
		'/topic/not/wanted' \
		'/and/also/topic/not/wanted'
	
	# Publish a QoS 0 message
	# On topic a/b
	# Unretained
	# With value 'X'
	bishbosh_publishText 0 'a/b' no 'X'
	
	# Publish a QoS 1 message
	# bish-bosh handles the QoS for us
	# On topic a/b
	# Unretained
	# Using the contents of file '/path/to/message'
	bishbosh_publishFile 1 'a/b' no '/path/to/message'
	
	# Publish a QoS 2 message
	# bish-bosh handles the QoS for us
	# and will retransmit on re-connect
	# On topic a/b
	# Retained
	# Using the contents of file '/path/to/message/to/remove/after/send'
	# Then remove '/path/to/message/to/remove/after/send' after send (QoS 2 takes a copy)
	bishbosh_publishFileAndRemove 2 'a/b' yes '/path/to/message/to/remove/after/send'
}

bishbosh_connection_handler_PUBLISH()
{
	echo "Message received: retain=$retain, QoS=$QoS, dup=$dup, topicLength=$topicLength, topicName=$topicName, messageLength=$messageLength, messageFilePath=$messageFilePath"
}

bishbosh_connection_handler_noControlPacketsRead()
{
	# Down time - use this to publish some messages, change subscriptions or reload our configuration. Perhaps we could monitor a folder path?
	# Note: bish-bosh silently handles any PING packets on our behalf
	bishbosh_publishText 0 'nowt' no 'hello world'
}

It's easy to see how that could be modified to generate a will message or get a password by a secure means, monitor a folder or add sophisticated subscription tracking to build up a picture of current state.

Being specific about how a is made connection

These settings relate to MQTT's CONNECT packet.

Configuration SettingValuesInterpreted as if unsetExplanation
bishbosh_connect_cleanSession0 or 1 *1 (ie non-persistent)Clean Session flag
bishbosh_connect_willTopicAny valid topic nameNo will messagesWill topic
bishbosh_connect_willQoS0 - 2 inclusive0Will QoS, invalid if bishbosh_connect_willTopic is unset
bishbosh_connect_willRetain0 or 1 *0Will Retain flag, invalid if bishbosh_connect_willTopic is unset
bishbosh_connect_willMessageAny valid message, but Unicode U+0000 is not supported.†invalidWill message, invalid if bishbosh_connect_willTopic is unset
bishbosh_connect_willMessageFilePathA path to a valid messageinvalidWill message, invalid if bishbosh_connect_willTopic is unset or bishbosh_connect_willMessage is set. Must be a regular file (reading from a FIFO, etc, is unsupported), as we need to know the size in advance. Useful if a message might contain Unicode U+0000.†
bishbosh_connect_keepAlive0 to 65535 inclusive0Keep Alive for pings in seconds. A value of 0 disables keep alive handling
bishbosh_clientIdAny valid UTF-8 string excluding Unicode U+0000invalidClient id. Empty client ids, and random client ids, are not yet supported. Usually set on the command line with the switch --client-id CLIENT_ID
bishbosh_connect_usernameAny valid UTF-8 string excluding Unicode U+0000. May be emptyNo usernameUsername. May be empty or unset (the latter meaning it is not sent)
bishbosh_connect_passwordAny sequence of bytes excluding Unicode U+0000. May be emptyNo passwordPassword. May be empty or unset (the latter meaning it is not sent)
bishbosh_connect_passwordFilePathA path to a valid file. May be empty.No passwordPassword, invalid if bishbosh_connect_password is set. Must be a regular file (reading from a FIFO, etc, is unsupported), as we need to know the size in advance. Useful if a password might contain Unicode U+0000†, or you want to able to check in configuration to source control or change passwords in production.

* Technically, a boolean, which might also be Y, YES, Yes, yes, T, TRUE, True, true, ON, On, on for 1 and N, NO, No, no, F, FALSE, False, false, OFF, Off and off for 0, but best as a number.

† Apart from zsh, no shell can either have variables with Unicode U+0000 (ACSCII NUL as was) in them, or read them directly.

Publishing

Messages can be published using one of three functions:-

Any unacknowledged PUBLISH packets (and PUBREL packets) are resent by bish-bosh on start-up once CONNACK is received.

bishbosh_publishText
PositionPurposeValid Values
1QoS level0 to 2 inclusive
2Topic nameAny valid topic name, although \n is currently rejected in topics (see Specification Violations)
3Retain Flag as a Booleanyes or no
4Message textAny message text. May be empty or omitted (interpreted as empty)

For example

bishbosh_publishText 0 'a/b' no 'My message'

publishes a message with the text My message at QoS 0, to the topic named a/b with RETAIN off (ie not retained).

bishbosh_publishFile
PositionPurposeValid Values
1QoS level0 to 2 inclusive
2Topic nameAny valid topic name, although \n is currently rejected in topics (see Specification Violations)
3Retain Flag as a Booleanyes or no
4File pathAny valid file path. Must be readable. Can be empty.

For example

bishbosh_publishFile 1 'a/b' no '/path/to/message'

publishes a message with the contents of the file /path/to/message/ at QoS 1, to the topic named a/b with RETAIN off (ie not retained).

bishbosh_publishFileAndRemove
PositionPurposeValid Values
1QoS level0 to 2 inclusive
2Topic nameAny valid topic name, although \n is currently rejected in topics (see Specification Violations)
3Retain Flag as a Booleanyes or no
4File pathAny valid file path. Must be readable. Can be empty. Must be writable so we can delete it.

For example

bishbosh_publishFileAndRemove 2 'a/b' yes '/path/to/message/to/remove/after/send'

publishes a message with the contents of the file /path/to/message/to/remove/after/send at QoS 2, to the topic named a/b with RETAIN on (ie retained). When it is sent, it then removes the file /path/to/message/to/remove/after/send. An internal copy is kept for QoS handling. For interest, internal copies are made using mv, ln or ln -s wherever possible.

Subscribing

Subscriptions are sent using the function bishbosh_subscribe. Any unacknowledged SUBSCRIBE packets are resent by bish-bosh on start-up once CONNACK is received.

Subscriptions are given by specifiying pairs of variable arguments as topicFilter - topicQos. At least one pair must be supplied. For example

bishbosh_subscribe \
'/topic/qos/0' 0 \
'/topic/qos/1' 1 \
'/topic/qos/3' 1

subcribes to:-

Unsubscribing

Unsubscriptions are sent using the function bishbosh_unsubscribe. Any unacknowledged UNSUBSCRIBE packets are resent by bish-bosh on start-up once CONNACK is received.

Unsubscriptions are given by specifiying variable arguments of topicFilter. At least one topicFilter must be supplied. For example

bishbosh_unsubscribe \
'/topic/not/wanted' \
'/and/also/topic/not/wanted'

unsubscribes from:-

Handling read events

bish-bosh supports a number of callbacks, called handlers, whenever something interesting has been read and processed. The default implementations of these just do logging if --verbose 2 is used.

To override a handler, you just write a shell function definition:-

bishbosh_connection_handler_PUBLISH()
{
	printf '%s' "Received a message on topic ${topicName} which was ${messageLength} byte(s) long and is in the file ${messageFilePath}" 1>&2
	
	# Run a parser?
	# Write a reply?
	# Move or Hardlink to another location (perhaps an inotify-based process)?
	# Or something else? You could even embed your entire program logic here, if it's shell script
	
	# Make sure we clean up
	rm "${messageFilePath}"
}

You need to be careful if using printf or echo - by default, all data written to standard out goes to the MQTT server! BTW, bish-bosh handles all the publication, subscription and unscribe acknowledgments. You don't have to do anything apart from have a handler (bishbosh_connection_handler_PUBLISH) to read your messages. But if you do:-

HandlerControl Packet ReceivedLocal Variables in ScopeNotes
bishbosh_connection_handler_CONNACKCONNACKbishbosh_connection_sessionPresentInvalid packets and non-zero CONNACK codes are handled for you
bishbosh_connection_handler_SUBACKSUBACKpacketIdentifier, returnCodeCount, $@ which is a list of return codesInvalid and unexpected packets are handled for you; active sessions are tracked on your behalf
bishbosh_connection_handler_UNSUBACKUNSUBACKpacketIdentifierInvalid and unexpected packets are handled for you; active sessions are tracked on your behalf
bishbosh_connection_handler_PUBLISHPUBLISHpacketIdentifier, retain, dup, QoS, topicLength, topicName, messageLength, messageFilePathInvalid and unexpected packets and duplicates are handled appropriately. Publication acknowledgments (PUBACK, PUBCOMP) likewise are handled. The only thing you need to do is rm "$messageFilePath" if you want
bishbosh_connection_handler_PUBLISH_againPUBLISHpacketIdentifier, retain, dup, QoS, topicLength, topicName, messageLength, messageFilePathCalled when a QoS 2 message is redelivered
bishbosh_connection_handler_PUBACKPUBACKpacketIdentifierInvalid and unexpected packets are handled for you. Acknowledgments likewise.
bishbosh_connection_handler_PUBRECPUBRECpacketIdentifierInvalid and unexpected packets are handled for you. Acknowledgments likewise.
bishbosh_connection_handler_PUBRELPUBRELpacketIdentifierInvalid and unexpected packets are handled for you.
bishbosh_connection_handler_PUBCOMPPUBCOMPpacketIdentifierInvalid and unexpected packets are handled for you.
bishbosh_connection_handler_PINGRESPPINGRESPNothing much to say.
bishbosh_connection_handler_noControlPacketsReadnoneOccurs when a read for a control packet timed out.

Writing control packets

Inside any of bish-bosh's handlers, you can publish a message, make a subscription request, etc. Indeed, you can do it yourself - anything sent to standard out goes to the server - but it's probably better to use our built in writers. For example once connected (you received CONNACK control packet), you might want to subscribe:-

bishbosh_connection_handler_CONNACK()
{
	bishbosh_connection_write_SUBSCRIBE \
		'/topic/qos/0' 0 \
		'/topic/qos/1' 1 \
		'/topic/qos/3' 1
    
	bishbosh_connection_write_UNSUBSCRIBE \
		'/topic/not/wanted' \
		'/and/also/topic/not/wanted'
}

OK, back to switches

Informational Settings

SwitchValueConfiguration SettingDefaultPurpose
-v, --verbose[LEVEL]bishbosh_verbose0Adjusts verbosity of output on standard error (stderr). LEVEL is optional; omitting causes a +1 increase in verbosity. May be specified multiple times, although levels greater than 2 have no effect currently. LEVEL must be an unsigned integer.
-q, --quietSpecify (optionally more than once) to reduce verbosity by a step of 1
--versionVersion and license information in a GNU-like format on standard error.
-h, --helpA very long help message recapping most of this document's information.

MQTT Big Hitters

SwitchValueConfiguration SettingDefaultPurpose
-t, --tunnelTUNNELbishbosh_tunnelnoneThe tunnel TUNNEL controls how a MQTT connection is made. Ordinarily, it's just none: MQTT. Values are none, tls and cryptcat. Changing this setting changes how the backends are chosen. Most backends support only none; some also support tls, and some only tls. In the future, if there's demand, support can also be added for SSH, telnet, WebSockets and WebSocketsSecure. This is not the same thing as proxying. Additional tunnel settings may be required.
-s, --serverHOSTbishbosh_servertest.mosquitto.orgHOST is a DNS-resolved hostname, IPv4 or IPv6 address of an MQTT server to connect to. If using Unix domain sockets (see --transport) it is a file path to a readable Unix domain socket. If using serial devices it a file path to a readable serial device file.
-p, --portPORTbishbosh_portBy TUNNEL: 1883 for none, cryptcat. 8883 for tls.Port your MQTT HOST is running on, between 1 to 65535, inclusive. Ignored if using Unix domain sockets or serial device files (see --transport).
-i, --client-idIDbishbosh_clientIdunsetMQTT ClientId. When specified, it also, in conjunction with HOST and PORT, is used to find a folder containing state and scripts for the client id ID, to the server HOST, on the port PORT. If unset, and bishbosh_connect_cleanSession is 1, then forced to empty (''), which MAY NOT work with some MQTT servers.
-r, --random-client-idbishbosh_randomClientId*0When specified, --client-id isn't and Clean Session is 1, then a random client-id of 16 bytes, base64-encoded, is used, instead of an empty client id. This should work with most MQTT servers. To be compatible with servers that only use a restricted alphanumeric range, the base64 trailing = is discarded. Random client-ids with + and / are discarded and another client id generated. This alogrithm gives similar, but not quite as random, results as using a Type 4 UUID.
-x, --ping-timeoutSECSbishbosh_pingTimeout30When the client's Keep Alive value is not 0, this is the 'reasonable time' in SECS seconds that the client will wait to receive a PINGRESP packet.
-w, --connect-timeoutSECSbishbosh_connectTimeout30This is the time in SECS seconds that the client will wait to try to connect to a MQTT server. Not all [backends] honour this setting. Some older versions of netcat interpret it as an idle connection timeout. 0 is infinity.

_ * This value is a boolean. Use 0 for false, 1 for true ._

Backends

SwitchValueConfiguration SettingDefaultPurpose
-b, --backendsA,B,…bishbosh_backendsopenssl,socat,ncat,nc6,nc,ncDebianOpenBSD,ncFreeBSD,ncOpenBSD,ncMirBSD,ncMacOSX,ncDebianTraditional,ncSolaris,ncGNU,ncToybox,ncBusyBox,devtcp,cryptcatBackends are specified in preference order, comma-separated, with no spaces. To specify just one backend, just give its name, eg ncat. The backend nc represents all the netcat permutations.

A backend is the strategy bish-bosh uses to connect to a MQTT server. It incorporates the encryption capabilities, foibles, and gotchas of the necessary binary that provides a socket connection. Some backends are actually 'meta' backends that use feature detection to work. An example of this is the nc backend. bish-bosh ships with a large number of backends to accommodate the varying state of different operating systems, package managers and Linux distributions. In particular, the situation around 'netcat' is particularly bad, with a large number of variants of a popular program.

By default, bish-bosh has a list of backends in preferred order, and tries to choose the first that looks like it will work. Of course, given the vagaries of your system, it might not get that right, so you might want to override it. Not all backends support all features; in particular, unix domain sockets, proxies and serial devices vary: this list of backends gives more information.

Configuration Tweaks

SwitchValueConfiguration SettingDefaultPurpose
-c, --client-pathPATHbishbosh_clientPathSee help outputPATH to a location to configuration - scriptlets for a client-id on a per-server, per-port, per-client-id basis. See Configuration Locations
-d, --session-pathPATHbishbosh_sessionPathSee help outputPATH to a location to store session data for clients connecting with Clean Session = 0
-l, --lock-pathPATHbishbosh_lockPathSee help outputPATH to a location to screate a Mutex lock so only one instance connects per-server, per-port, per-client-id at a time.
--filesize-algorithmALGObishbosh_filesizeAlgorithmlsSpecify a more efficient filesize algorithm ALGO if you have the stat program and know which one it is. Choices are ls, GNUAndBusyBoxStat, BSDStat and ToyboxStat (not recommended due to lack of a -L switch).
--read-latencyMSECSbishbosh_readLatencySee help outputMSECS is a value in milliseconds between 0 and 1000 inclusive to tweak blocking read timeouts. blocking read timeouts are experimental and may not work properly in your shell. The value 0 may be interpreted differently by different shells and should be used with caution.

Ordinarily, you should not need to change any of these settings.

The --client-path controls where bish-bosh looks for script information for a particular client. When bish-bosh is installed, it typically defaults to /var/lib/bish-bosh/client. The --session-path controls where bish-bosh looks for Clean Session = 0 information for a particular client. When bish-bosh is installed, it typically defaults to /var/spool/bish-bosh/session. The --lock-path controls where bish-bosh tries to create a lock for a particular client. When bish-bosh is installed, it typically defaults to /var/lib/bish-bosh/lock, which is not the Linux FHS default of /var/lock (but is used because that works out of the box on Mac OS X).

Source-Routing Settings

SwitchValueConfiguration SettingDefaultPurpose
--transportTRANSPTbishbosh_transportinetUse a particular socket transport TRANSPT. TRANSPT may be one of inet, inet4, inet6, unix or serial. Using inet allows the backend to select either a IPv4 or IPv6 connection as appropriate after DNS resolution. inet4 forces an IPv4 connection; inet6 likewise forces an IPv6 connection. unix uses a Unix domain socket connection. serial opens a serial character device file.
--source-addressSbishbosh_sourceAddressunsetConnect using the NIC with the source address S. Results in packets being sent from this address. S may be a host name resolved using DNS, or an IPv4 or IPv6 address. If you disable DNS resolution of MQTT server names, it's likely that a backend will do likewise for HOST. If S is set to '' (the empty string), then it is treated as if unset. This is to allow local users to override global configuration. Ignored if TRANSPT is unix or serial.
--source-portPORTbishbosh_sourcePortunsetConnect using the source port PORT. If TRANSPT is unix then this setting is invalid. Results in packets being sent from this port. If unset, then a random source port is chosen. If PORT is set to '' (the empty string), then it is treated as if unset. This is to allow local users to override global configuration. Ignored if TRANSPT is unix or serial.

If you have a box with multiple NICs or IP addresses, broken IPv4 / IPv6 networking (or DNS resolution) or strange firewall policies that block certain source ports, you can control those as follows:-

Proxy Settings*

SwitchValueConfiguration SettingDefaultPurpose
--proxy-kindKINDbishbosh_proxyKindunsetUse a particular KIND of proxy. KIND is one of SOCKS4, SOCKS4a, SOCKS5, HTTP or none. Using none disables the proxy; this is for when a global configuration has been set for a machine but a local user needs to run without it. Most backends do not support SOCKS4a. When using the SOCKS4 protocol, HOST (below) must be a numeric address. SOCKS4 and SOCKS4a do not support IPv6.
-proxy-serverHOSTbishbosh_proxyServerunsetConnect to a proxy server on a given HOST, which may be a name, an IPv4 or IPv6 address (in the case of the latter, you may need to surround it in [], eg [::1]; backends vary and do not document IPv6 proxy address handling). If you disable DNS resolution of MQTT server names, it's likely that a backend will do likewise for HOST.
--proxy-portPORTbishbosh_proxyPort1080 for KIND of SOCKS4, SOCKS4a or SOCKS5. 3128 for HTTP. unset for none.Port the proxy server HOST is running on.
--proxy-usernameUNbishbosh_proxyUsernameunsetUsername UN to use. Please note that passing this as a switch is insecure.
--proxy-passwordPWDbishbosh_proxyPasswordunsetPassword PWD to use. Please note that passing this as a switch is insecure. Rarely supported.

Personally, I find proxies extremely irritating, and of very limited benefit (especially in these days of deep packet inspection abuse). But many organizations still use them, if simply because once they go in, they tend to stay in - they appeal to the control freak in all of us, I suppose. bish-bosh does its best to support SOCKS and HTTP proxies, but we're reliant on the rather limited support of backends.

When using a proxy, you won't be able to use Unix domain sockets (--transport unix) or serial devices (--transport serial). Not every backend supports using a proxy; even those that do don't support every option above (there's a compatibility table).

* Not running proxies myself, I can't test many of these settings combinations.

Alternative

It may be possible to hook proxy support into several of the backends using proxychains-ng. If you have an use case for this, please get in touch.

Tunnel Settings

tls tunnel settings
SwitchValueConfiguration SettingDefaultPurpose
--tunnel-tls-ca-fileFILEbishbosh_tunnelTlsCaPathunsetA PEM-encoded file FILE which contains a Certificate Authority certificate chain. Do not specify this if --tunnel-tls-ca-path is specified. Most backends have a default location for this or --tunnel-tls-ca-file.
--tunnel-tls-ca-pathPATHbishbosh_tunnelTlsCaPathunsetA folder PATH which contains PEM-encoded Certificate Authority certificates with OpenSSL-compatible hashes. Do not specify this if --tunnel-tls-ca-file is specified. Most backends have a default location for this or --tunnel-tls-ca-path.
--tunnel-tls-certificateFILEbishbosh_tunnelTlsCertificateunsetA PEM-encoded* file FILE which a certificate to authenticate the client with. Not normally required. If specified, then --tunnel-tls-key must also be specified.
--tunnel-tls-keyFILEbishbosh_tunnelTlsKeyunsetA PEM-encoded file* FILE which contains a private key to authenticate the client with. Not normally required. If specified, then --tunnel-tls-certificate must also be specified.
--tunnel-tls-use-derBOOLbishbosh_tunnelTlsUseDeroffModifies --tunnel-tls-certificate and --tunnel-tls-key to expect DER-encoded files.
--tunnel-tls-verifyBOOLbishbosh_tunnelTlsCiphersonA boolean BOOL used to enable or disable verification of the MQTT server's X.509 certificate chain. Revocation checks (CRL, OCSP) are not performed by most backends. Some backends (eg openssl) do not fail on verification failure.
--tunnel-tls-ciphersSTRbishbosh_tunnelTlsCiphersunsetA backend specific string STR. Nearly all backends use openssl syntax (man 5 ciphers and openssl ciphers), except for gnutls, which calls this a 'Priority string' (info gnutls, then find section 6.10, and gnutls-cli --list).

_* Can be DER-encoded when --tunnel-tls-use-der is on . The socat and ncat backends only support PEM. _

There are a number of limitations at this time:-

In many ways, this list of exclusions typifies the problems of TLS - too many choices, too many options and too many ways of implementing them.

If you need support for any of these features, please contact me - it may be possible to modify bish-bosh to accommodate specific needs.

stunnel Alternative

As an alternative to using tls tunnel, one can use a none tunnel but connect to, say, stunnel running on localhost with a stunnel.conf such as

;stunnel.conf

[mqtts]
accept = 1883
connect = ${bishbosh_server}:${bishbosh_port}
foreground = no
CApath = ${bishbosh_tunnelTlsCaPath}
;Or
;CAfile = ${bishbosh_tunnelTlsCaFile}
cert = ${bishbosh_tunnelTlsCertificate}
key = ${bishbosh_tunnelTlsKey}
TIMEOUTconnect = ${bishbosh_connectTimeout}
verify = ${bishbosh_verify}

where ${bishbosh_XXX} relates to a bish-bosh configuration setting.

See man 8 stunnel for more details. From experience, it can be a bit troublesome to get configured and made to start reliably. It doesn't like configuration values with spaces in.

cryptcat tunnel settings
SwitchValueConfiguration SettingDefaultPurpose
--tunnel-cryptcat-passwordPWDbishbosh_tunnelCryptcatPasswordunsetShould ideally be set using configuration, as it's insecure to set on the command line. However, cryptcat itself exposes the password on the command-line…

Exit Codes

bish-bosh tries to follow the BSD exit code conventions. A non-zero exit code is indicative of failure. Typical codes are:-

CodeMeaningCommon Causes
78Configuration issueConfiguration omitted, contradictory or incorrectly specified
77Permission DeniedRun with setuid / setgid bits set. CONNACK had a connection return code of 4 or 5
76ProtocolAn invalid control packet code, remaining length or control packet was read or decoded
75Temporary FailureAnother process has locked our client-id. We could not establish a socket connection to the MQTT server
74I/O ErrorWe couldn't unlink (delete) a message file
73Can't createWe couldn't create a temporary file or folder
72Missing FileWe tried very hard, but even a fallback dependency was missing
71Not used
70Internal ErrorSomething went wrong with bish-bosh; an assumption was violated
69UnavailablePing timed out. CONNACK had a connection return code of 1 or 3
68Unknown HostNot used presently
67Unknown UserCONNACK had a connection return code of 2
66Not used
65Data ErrorCorrupt or unexpected data found in stored session state.
64Incorrect command lineCommand line switches omitted, contradictory or incorrectly specified
2A shell builtin misbehaved
1Something went wrong we didn't expect or couldn't intercept
0Successful operation; connection disconnected cleanly

File Locations

Configuration Locations

Anything you can do with a command line switch, you can do as configuration. But configuration can also be used with scripts. Indeed, the configuration syntax is simply shell script. Configuration files should not be executable. This means that if you really want to, you can override just about any feature or behaviour of bish-bosh - although that's not explicitly supported. Configuration can be in any number of locations. Configuration may be a single file, or a folder of files; in the latter case, every file in the folder is parsed in 'shell glob-expansion order' (typically ASCII sort order of file names). Locations are searched in order as follows:-

  1. Global (Per-machine)
  2. The file INSTALL_PREFIX/etc/bish-bosh/rc where INSTALL_PREFIX is where bish-bosh has been installed.
  3. Any files in the folder INSTALL_PREFIX/etc/bish-bosh/rc.d
  4. Per User, where HOME is your home folder path*
  5. The file HOME/.bish-bosh/rc
  6. Any files in the folder HOME/.bish-bosh/rc.d
  7. Per Environment
  8. The file in the environment variable bishbosh_RC (if the environment variable is set and the path is readable)
  9. Any files in the folder in the environment variable bishbosh_RC_D (if the environment variable is set and the path is searchable)
  10. In SCRIPTLETS
  1. Under the configuration setting bishbosh_clientPath or switch --client-path
  2. The file servers/${bishbosh_server}/rc where bishbosh_server is a configuration setting or the switch --server
  3. Any files in the folder servers/${bishbosh_server}/rc.d
  4. The file servers/${bishbosh_server}/ports/${bishbosh_port}/rc where bishbosh_port is a configuration setting or the switch --port
  5. Any files in the folder servers/${bishbosh_server}/port/${bishbosh_port}/rc.d
  6. The file servers/${bishbosh_server}/ports/${bishbosh_port}/client-ids/_${bishbosh_clientId}/rc where bishbosh_clientId is a configuration setting or the switch --client-id§
  7. Any files in the folder servers/${bishbosh_server}/ports/${bishbosh_port}/client-ids/_${bishbosh_clientId}/rc.d§

Nothing stops any of these paths, or files in them, being symlinks. This can be exploited to symlink together, say, port numbers 1883 and 8883, or client ids that share usernames and passwords, etc.

* An installation as a daemon using a service account would normally set HOME to something like /var/lib/bishbosh.

† it is possible for a configuration file here to set bishbosh_port (or even bishbost_clientId), so influencing the search in 3 - 6.

‡ It is possible for a configuration file here to set bishbosh_clientId, so influencing the search in 5 and 6.

§ Note the leading _ before ${bishbosh_clientId}. This is to accommodate Client Ids that are empty or start with . or ...

/var

We use a /var folder underneath where we're installed. If you've just cloned bish-bosh from GitHub, then this is within the clone.

/etc

We use a /etc folder underneath where we're installed. If you've just cloned bish-bosh from GitHub, then this is within the clone.

/tmp: Temporary Files

/dev: Devices

Unix domain sockets

Dependencies

bish-bosh tries to use as few dependencies as possible, but, since this is shell script, that's not always possible. It's compounded by the need to support the difference between major shells, too. It also does its best to work around differences in common binaries, by using feature detection, and where it can't do any better, by attempting to install using your package manager.

Required Dependencies

All of these should be present even on the most minimal system. Usage is restricted to those flags known to work across Mac OS X, GNU, BusyBox and Toybox. Even the most minimal system is likely to have these:-

The following are needed if not builtin to your shell (except for kill, this would be highly unusual):-

If cloning from GitHub, then you'll also need to make sure you have git.

Either Or Dependencies (one is required)

These are listed in preference order. Ordinarily, bish-bosh uses the PATH and feature detection to try to find an optimum dependency. Making some choices, however, influences others (eg hexdump and od preferences change when stdbuf is discovered, to try to use GNU od). Some choices are sub-optimal, and may cause operational irritation (mostly, bishbosh responds far more slowly to signals and socket disconnections).

* It may be possible to also use EGD sockets and other programs and sources (eg a TPM or rng-tools). Please get in touch if this is interesting to you. † It is probably possible to replace base64 + tr with either od or hexdump. Get in touch if that would be useful to you.

Optimal Choices

A word on GNU Bash versions

Unfortunately, there are a lot of GNU Bash versions that are still in common use. Versions 3 and 4 of Bash differ in their support of key features (such as associative arrays). Even then, Bash 4.1 is arguably not particularly useful with associative arrays, though, as its declare syntax lacks the -g global setting. bish-bosh tries to maintain compatibility with bash as at version 3.1/3.2, even though it's obsolescent, because it occurs on two common platforms. A quick guide to common bash version occurrence is below.

A word on [suckless]

bish-bosh hasn't been tested with them, but should work using suckless sbase and suckless ubase for dependencies.

Supported Configurations

The widely varying list of dependencies and preferences can be confusing, so here's a little guidance.

Tested and works 'out-of-the-box'

Tested and work with minor changes

Untested, but should work

Not Tested Yet

Might Work

These configurations can be made to work if there's enough interest, but are unlikely to be optimal.

Can Not Work

These configurations can not work without a lot of re-engineering, and, even then, would be barely functional. That said, if you have an use case to make them work, get in touch. Nothing's impossible. That said, for Windows, why not just use Cygwin?

Optimised

For Debian / Ubuntu

For Mac OS X

For BusyBox Embedded Use (as of version 1.22.1)

For Toybox Embedded Use (as of 0.5.0)

Supported Shells

bish-bosh tries very hard to make sure it works under any POSIX-compliant shell. However, in practice, that's quite hard to do; many features on the periphery of POSIX compliance, are subtly different (eg signal handling during read). That can lead to a matrix of pain. We constrain the list to widely-used shells common in the sorts of places you'd want to use bish-bosh: system administration, one-off scripting, boot-time and embedded devices with no compiler toolchain. Consequently, we try to support in decreasing priority order:-

All of these shells support dynamically-scoped local variables, something we make extensive use of. Some of them also support read timeouts, which is very useful for making bish-bosh responsive. The pdksh-derived shells (including mksh) are challenging to support, as they're not in full POSIX compliance.

Unsupported Shells

zsh

bish-bosh is not actively tested under zsh although it should work once the inevitable few bugs are fixed. zsh is a nice interactive shell, and good for scripting, too. In particular, it is the only shell where it's possible for the read builtin to read data containing Unicode U+0000 (ACSCII NUL as was), and is also trully non-blocking. bish-bosh can not take advantage of these features yet, however.

ksh93

At this time, ksh93 is known not to work and looks like a lot of work to make work. This means UWIN won't work, either.

Others

Status of Supported Backends

BackendFilenameVariantTunnelsStatus--transport inet4--transport inet6--transport unix--transport serialProxy--source-server HOST--source-port PORT
opensslgnutls[OpenSSL] / [LibreSSL]tlsFully functionalNoNoNoNoNoNoNo
ncatncatNmap ncatnone, tlsFully functional‡YesYesYesNoSOCKS4, SOCKS5 and HTTP. Usernames and passwords supported for HTTP, usernames only for SOCKS.YesYes
socatsocatsocatnone, tlsFully functionalYesYesYesYesSOCKS4, SOCKS4a and HTTP. Usernames are supported.YesYes
nc'Meta' backendAny nc* backendnoneFully functional*Yes†Yes†Yes†Yes†Yes†Yes†Yes†
ncFreeBSDncFreeBSDnoneFully functionalYesYesYesNoSOCKS4, SOCKS5 and HTTP. Usernames only for HTTP.YesYes
ncOpenBSDncOpenBSDnoneFully functionalYesYesYesNoSOCKS4, SOCKS5 and HTTP. Usernames only for HTTP.YesYes
ncMirBSDncMac OS XnoneFully functionalYesYesYesNoSOCKS4, SOCKS5 and HTTP. No usernames or passwords.YesYes
ncMacOSXncMac OS XnoneFully functionalYesYesYesNoSOCKS4, SOCKS5 and HTTP. No usernames or passwords.YesYes
ncDebianOpenBSDnc.openbsdDebian OpenBSDnoneFully functional‡YesYesYesNoSOCKS4, SOCKS5 and HTTP. Usernames only for HTTP.YesYes
ncDebianTraditionalnc.traditional or netcat (on DragonFly BSD, sic)Debian Traditional / HobbitnoneFully functionalYesYesNoNoNoYesYes
ncSolarisncSolarisnoneFully functionalYesYesYesNoSOCKS4, SOCKS5 and HTTP. Usernames only for HTTP.YesYes
ncGNUncGNUnoneFully functionalNoNoNoNoNoYesYes
ncToyboxnc / toybox nc / toybox-$(uname) /ToyboxnoneFully functional‡NoNoNoYesNoYesYes
ncBusyBoxnc / busybox ncBusyBoxnoneFully functional‡NoNoNoYesNoNoYes
devtcpbash / kshGNU Bash / ksh93noneFully functionalNoNoNo? maybe ?NoNoNo
cryptcatcryptcat-MQTT Encryting variant of netcat, but, because the password is supplied on the command line, insecure.
gnutlsgnutls[OpenSSL] / [LibreSSL]tlsBrokenNoNoNoNoNoNoNo

* Refers to the meta backend itself. A detected backend may not be.

† Yes, if the detected variant of the backend does.

‡ Does not respond to 'Ctrl-C'.

Please note that all backends do not respond well to 'Ctrl-C' being sent to a process group, or SIGINT (some die early, some never die). It is best to terminate by sending TERM to bish-bosh, eg using kill.

Unimplemented Backends

If you have a particular need to use these approaches to connecting to MQTT servers, raise an issue and I'll consider it. None of them are widely used or offer particularl advantages.

BackendFilenameHome PageNotes
tcpclienttcpclientucspi-tcp and Debian uscpi-tcp-ipv6Executes a program on connection, which does not suit our model. Does not offer any proxy support. Not widely used.
sbd?Homepage Dead, but links still around and hereAlso known as 'sbd for linux' and 'Shadowinteger's Backdoor'. Was here
pnetcatpnetcatHomeBSD-like licence, but web page infers mis-distribution. Implemented in Python, which whilst interesting, mitigates against the point of bish-bosh.
nc.pl??There are also perl implementations of netcat. Just as for pnetcat, it seems a moot choice.
ncSslCapablescncSSL-capable netcatAnother perl implementation. Might be worth adding if only for the SSL support.
ssliosslioipvsdEffectively a wrapper around tcpclient.

Limitations

suid / sgid

bish-bosh explicitly tries to detect if run with suid or sgid set, and will exit as soon as possible with an error. It is madness to run shell scripts with such settings.

Specification Violations

Client Ids

Topic Names and Topic Filters

Broken but Fixable

Useful to do

Ideas