This project provides a Node.js implementation of the aMule External Connections (EC) Protocol based on the aMule EC Protocol HOWTO.
Tested with aMule 3.0.1, 3.2.0 and current master, which is where SRP landed. Older cores, back to 2.3.3, keep working: a command the daemon may not know is gated on the capability it advertises at connect time and is never sent otherwise, and the rest degrade to the older reply.
- Communicate with aMule via EC protocol
- Send commands and receive status or transfer info
git clone https://github.com/got3nks/amule-ec-node.git
cd amule-ec-node
npm installnpm startSee examples/test.js for usage.
const client = new AmuleClient(host, port, password, {
requestTimeout: 30000, // per-request timeout in ms, 0 disables it
autoReconnect: true, // reconnect when the connection drops
encryption: 'require', // 'require', 'prefer' or 'off'
allowLegacyAuth: false // permit the pre-SRP password challenge
});With autoReconnect: false, a dropped connection rejects pending requests and
stops. Use it when your code rebuilds the client itself.
Otherwise a drop is retried eight times, the delay doubling from one second to
a minute. Set client.session.onReconnectFailed to hear about a core that never
came back; the client does not throw on its own, because the retries run from a
socket event handler where there is nothing to catch them.
The client speaks three handshakes and picks one from the core's reply. One AUTH_REQ serves every core, so no reconnect or retry is involved.
| Core | Handshake |
|---|---|
| master (3.3.0, unreleased) | SRP-6a, over a mandatory sealed channel |
| 3.1.0 to 3.2.0 | password challenge, over a sealed channel |
| 2.3.3 to 3.0.1 | password challenge, in the clear |
aMule 3.3.0 is not released yet. SRP is in master, and the client was built and verified against a build of it, so nothing here changes when 3.3.0 ships.
client.session.srpAuthenticated says which credential scheme was used, and
client.session.encryptionState what was negotiated.
The defaults assume the newest core, so a released one needs one setting
relaxed. <host> is the address you passed to the constructor.
| Your core | Settings |
|---|---|
| master, with SRP | the defaults |
| 3.1.0 to 3.2.0, on loopback | the defaults |
| 3.1.0 to 3.2.0, remote | allowLegacyAuth: ['<host>'] |
| 2.3.3 to 3.0.1, on loopback | encryption: 'prefer' |
| 2.3.3 to 3.0.1, remote | encryption: 'prefer', allowLegacyAuth: ['<host>'] |
If you do not know the core's version, treat it as the oldest one you might meet. The settings for an old core still work against a new one: SRP and the sealed channel are used whenever the core offers them.
encryption covers the channel: X25519 key agreement, then AES-128-GCM or
ChaCha20-Poly1305 per packet. require is the default and refuses a core that
cannot seal, loopback included. prefer uses encryption where the core offers
it, which is what a pre-3.1 core needs. off never offers it, and so only
reaches a pre-3.1 core: SRP without a sealed channel is refused on both ends.
allowLegacyAuth covers the credential. A core that does not offer SRP is
refused by default, because a missing EC_TAG_SRP_B cannot be told from an
attacker removing it, and the pre-SRP proof is offline-crackable by whoever
chose the salt. Loopback is always permitted: impersonating a core there already
needs local code execution, which can read the password out of amule.conf
anyway. For any other pre-SRP core, list it:
new AmuleClient('192.168.1.50', 4712, pass, { allowLegacyAuth: ['192.168.1.50'] })Private LAN ranges are deliberately not exempt. An address in 10/8 says
nothing about the transit, and one compromised device on the segment is enough.
close() is final. It also stops a reconnect that is already running. To
connect again, create a new client.
MIT