Compare commits

...

67 Commits

Author SHA1 Message Date
ca7c5ebbf5 doc/README: link to archived releases 2020-05-06 18:57:07 +12:00
1f38eb480d doc: remove TODO.txt (moved to Gitea Issues) 2019-01-07 20:10:50 +13:00
ffdee699da vendor: commit Go vendor directory 2019-01-07 20:01:56 +13:00
e1692b7576 convert build from godist.sh to Go Modules 2019-01-07 20:01:42 +13:00
fff9d4442e doc: top-level README.md 2019-01-07 20:01:33 +13:00
4b5b334a41 hg2git: convert ignores file 2019-01-07 20:01:21 +13:00
97056a0370 hg-git: remove old .hgtags file 2019-01-07 19:57:32 +13:00
b09ae3bcfa Added tag release-1.3.0 for changeset 2b538cb88dfb
--HG--
branch : nmdc-ircfrontend
2018-03-24 16:56:57 +13:00
d88c6248b8 doc: 1.3.0 changelog
--HG--
branch : nmdc-ircfrontend
2018-03-24 16:56:51 +13:00
4a34ccce26 doc: update compatibility list
--HG--
branch : nmdc-ircfrontend
2018-03-24 16:20:55 +13:00
9d9fcef954 change "end of NAMES list" message to more closely match RocketChat's regexp
--HG--
branch : nmdc-ircfrontend
2018-03-24 16:20:48 +13:00
2d19acb846 remove sendNamesOnWho quirk, use nickForGeneralMessages quirk on Yaaic as well as atomic
--HG--
branch : nmdc-ircfrontend
2018-03-24 15:40:20 +13:00
e993e8a32c dev server version is 0.0, not 1.x.x-dev (easier numeric handling)
--HG--
branch : nmdc-ircfrontend
2018-03-24 15:36:56 +13:00
541cbaabd2 client tags: assume atomic/yaaic are the latest abandonware versions
--HG--
branch : nmdc-ircfrontend
2018-03-24 15:36:41 +13:00
86ad1e1633 doc: update README
--HG--
branch : nmdc-ircfrontend
2018-03-24 15:25:09 +13:00
19e0120423 doc: fix bad numeric version for irssi, add clientTag test
--HG--
branch : nmdc-ircfrontend
2018-03-24 15:24:52 +13:00
e9ad8f6962 doc: update help text
--HG--
branch : nmdc-ircfrontend
2018-03-24 15:16:30 +13:00
ef36fc0c24 client tags: better detection for Revolution IRC and HoloIRC
--HG--
branch : nmdc-ircfrontend
2018-03-24 15:16:22 +13:00
458ab0a054 never send blank nicks (FIXES yaaic/atomic, Revolution IRC)
--HG--
branch : nmdc-ircfrontend
2018-03-24 15:04:09 +13:00
0e7e2b629c move chatformatting, quirks to separate package
--HG--
branch : nmdc-ircfrontend
2018-03-24 14:37:40 +13:00
371a7eb9e4 use full syntax for joins/parts (fixes userlist in liteirc)
--HG--
branch : nmdc-ircfrontend
2018-03-24 14:24:44 +13:00
485f68f5d1 move client tag parsing to separate file, add unit tests
--HG--
branch : nmdc-ircfrontend
2018-03-24 14:24:28 +13:00
d465d742c6 update help text to mention ADC support
--HG--
branch : nmdc-ircfrontend
2018-03-24 14:02:36 +13:00
37b62ff817 bump copyright year
--HG--
branch : nmdc-ircfrontend
2018-03-24 14:02:20 +13:00
fa13755311 libnmdc compatibility fixes (now requires 0.17++)
--HG--
branch : nmdc-ircfrontend
2018-03-24 13:22:34 +13:00
e83e2dc111 Added tag release-1.2.3 for changeset 129377245f80
--HG--
branch : nmdc-ircfrontend
2017-05-28 17:44:23 +12:00
a555dbd563 readme
--HG--
branch : nmdc-ircfrontend
2017-05-28 17:43:52 +12:00
f076aeaeda quirks support
--HG--
branch : nmdc-ircfrontend
2017-05-28 17:36:31 +12:00
1056493211 bump copyright year
--HG--
branch : nmdc-ircfrontend
2017-05-28 13:45:05 +12:00
ddcd65fa47 Added tag release-1.2.2 for changeset 111d6e41507d
--HG--
branch : nmdc-ircfrontend
2017-05-28 13:39:48 +12:00
d21b18fc75 fix bad patch for bounds check in previous
--HG--
branch : nmdc-ircfrontend
2017-05-28 13:38:56 +12:00
53b72e0cb0 readme
--HG--
branch : nmdc-ircfrontend
2017-05-27 14:00:25 +12:00
839dea016a chmod +x godist.sh
--HG--
branch : nmdc-ircfrontend
2017-05-27 13:58:12 +12:00
e235ee014e fix wrong bounds check causing panic when server gets scanned by weird clients
--HG--
branch : nmdc-ircfrontend
2017-05-27 13:57:37 +12:00
.
c54a271f17 Added tag release-1.2.1 for changeset 49dcc63e80e9
--HG--
branch : nmdc-ircfrontend
2016-11-29 20:21:29 +13:00
.
264ee8c61c readme
--HG--
branch : nmdc-ircfrontend
2016-11-29 20:21:22 +13:00
.
85e44756e3 libnmdc compatibility updates
--HG--
branch : nmdc-ircfrontend
2016-11-29 19:58:12 +13:00
.
e628bdcf91 update libnmdc import path
--HG--
branch : nmdc-ircfrontend
2016-11-29 19:55:50 +13:00
.
f929379569 fix build script not overwriting app_version
--HG--
branch : nmdc-ircfrontend
2016-08-27 17:53:04 +12:00
.
d5f331ec7c Added tag release-1.2.0 for changeset 3586b48a5abf
--HG--
branch : nmdc-ircfrontend
2016-08-27 17:42:34 +12:00
.
a609996484 readme
--HG--
branch : nmdc-ircfrontend
2016-08-27 17:42:24 +12:00
.
0ec1c20d7e whois: better output format
--HG--
branch : nmdc-ircfrontend
2016-08-27 15:27:50 +12:00
.
3cffacaa45 fixes for 3d8672ff5b9c
--HG--
branch : nmdc-ircfrontend
2016-08-27 14:32:16 +12:00
.
e0e5c742e3 add -version option to display version and quit
--HG--
branch : nmdc-ircfrontend
2016-08-27 14:20:27 +12:00
.
7494823b07 implant version number at build time
--HG--
branch : nmdc-ircfrontend
2016-08-27 14:18:20 +12:00
.
7b2ab6642b WHOIS support (needs testing)
--HG--
branch : nmdc-ircfrontend
2016-08-27 14:07:02 +12:00
.
f1051fbfc2 doc: update todo
--HG--
branch : nmdc-ircfrontend
2016-08-27 14:06:27 +12:00
.
73a4134b18 readme: demote liteirc to with-bugs section
--HG--
branch : nmdc-ircfrontend
2016-08-27 13:21:24 +12:00
.
962cc8dea0 reuse const
--HG--
branch : nmdc-ircfrontend
2016-08-27 12:38:21 +12:00
.
9bde052d9d todo
--HG--
branch : nmdc-ircfrontend
2016-05-10 19:45:28 +12:00
.
eabae2ce9b readme
--HG--
branch : nmdc-ircfrontend
2016-05-10 19:21:28 +12:00
.
10f4db39cc Added tag release-1.1.0 for changeset 34892054c343
--HG--
branch : nmdc-ircfrontend
2016-05-10 19:18:21 +12:00
.
6a7847f71b preparations for 1.1.0 release
--HG--
branch : nmdc-ircfrontend
2016-05-10 19:16:39 +12:00
.
7592bf6002 fix colon in mirc when renaming self
--HG--
branch : nmdc-ircfrontend
2016-05-10 19:08:26 +12:00
.
fb34cefc46 fix crash on renaming self
--HG--
branch : nmdc-ircfrontend
2016-05-10 19:08:19 +12:00
.
0df6237005 strip leading v from mirc version
--HG--
branch : nmdc-ircfrontend
2016-05-10 18:56:32 +12:00
.
c35062a3df support changing nicks (by reconnecting)
--HG--
branch : nmdc-ircfrontend
2016-05-09 18:57:04 +12:00
.
74e4f6cfa3 move mutex calls to outer function
--HG--
branch : nmdc-ircfrontend
2016-05-09 18:49:00 +12:00
.
7bf832322d hubsec nick opt, ctcp before join, delay join until ctcp version (with timeout), mirc/atomic-specific hack
--HG--
branch : nmdc-ircfrontend
2016-05-09 18:48:07 +12:00
.
169debea93 set proper clientversion, better version/tag extraction
--HG--
branch : nmdc-ircfrontend
2016-05-09 18:30:24 +12:00
.
9c2cad7986 doc update
--HG--
branch : nmdc-ircfrontend
2016-05-08 18:39:21 +12:00
.
32296eeffa doc update
--HG--
branch : nmdc-ircfrontend
2016-05-08 15:48:40 +12:00
.
3a00df8ef4 bugreport for mIRC 7
--HG--
branch : nmdc-ircfrontend
2016-05-08 15:26:05 +12:00
.
a948b457bd update readme
--HG--
branch : nmdc-ircfrontend
2016-05-08 14:51:06 +12:00
.
11dbafe387 update readme
--HG--
branch : nmdc-ircfrontend
2016-05-08 14:49:36 +12:00
.
a72f7be558 move readme file
--HG--
branch : nmdc-ircfrontend
2016-05-08 14:43:42 +12:00
.
637bfd6c78 Added tag release-1.0.0 for changeset da295cede46d
--HG--
branch : nmdc-ircfrontend
2016-05-08 14:42:50 +12:00
44 changed files with 3161 additions and 462 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_dist/

View File

@ -1,5 +0,0 @@
mode:regex
\.exe$
^_dist/

142
README.md Normal file
View File

@ -0,0 +1,142 @@
# nmdc-ircfrontend
An IRC protocol frontend for an NMDC server.
This program 'wraps' an NMDC server to make it available to IRC clients. It implements the IRC server protocol, but is not itself an IRC server, deferring to the upstream NMDC server in all salient cases. From a layering perspective it occupies the same position as the web interface.
The intent of this project is to expand client library and application support on platforms where the NMDC community has not been able to spend effort (e.g. Apple iOS client, ruby/rust/erlang libraries, greater choice in web interfaces).
TLS (SSL) support is not integrated. To host the IRC frontend over TLS, please use a reverse proxy in much the same manner as for NMDCS.
## License
This program uses some code from the AGPLv3 project https://github.com/eXeC64/Rosella , from which it inherits the Affero GPLv3 license. Anyone hosting a modified version of this software is required to release their changes under the terms of the AGPLv3.
## Features
- Seamless roundtrip transition between NMDC `/me` and IRC `CTCP ACTION`
- IRC client's "Real Name" exposed as NMDC Description
- IRC client's `CTCP VERSION` exposed as NMDC client tag, with heuristic truncation
- NMDC server's title exposed as IRC channel topic
- Login with passworded nick (classic `PASS`, not `SASL`)
- Full nick list integration including op status
- Support IRC client changing nick via NMDC upstream reconnection
- Automatic join to single-enforced chatroom
- Multithreaded
- Single binary deployment
## Usage
```
Usage of nmdc-ircfrontend:
-autojoin
Automatically join clients to the channel (default true)
-bind string
The address:port to bind to and listen for clients on (default ":6667")
-hubsecurity string
Nick used for administrative events (default "Hub-Security")
-servername string
Server name displayed to clients (default "nmdc-ircfrontend")
-upstream string
Upstream NMDC/ADC server (default "127.0.0.1:411")
-verbose
Display debugging information
```
## Compatibility
*This section was last updated on or around the release of 1.3.0. Current compatibility may differ.*
NMDC's smaller community has standardised around comparatively few protocol implementations by means of necessity. In comparison, there are a lot of IRC client implementations with slightly differing interpretations of the protocol.
For passworded DC usernames, your IRC PASS username must be the same as your IRC nickname.
Tested working:
- AndChat
- AndroIRC
- Atomic
- Hexchat
- HoloIRC
- Irssi
- LiteIRC
- Mango IRC
- mIRC 7
- Mutter
- Revolution IRC
- Weechat
- Yaaic
Unusable:
- Rocket.chat 0.6x IRC plugin (can join room and recieve PMs after some regex tweaking)
## Changelog
2018-03-24 1.3.0
- Feature: Support ADC hubs
- Compatibility: Fix missing userlist in LiteIRC (since 1.2.2)
- Compatibility: Avoid sending empty room list (fixes Yaaic, atomic, Revolution IRC)
- Update libnmdc to 0.17
- [⬇️ nmdc-ircfrontend-1.3.0-win64.7z](https://git.ivysaur.me/attachments/5da75934-863b-4476-86e3-c7da219ee93b) *(818.71 KiB)*
- [⬇️ nmdc-ircfrontend-1.3.0-win32.7z](https://git.ivysaur.me/attachments/7d5b2f9e-6f82-42be-b3ac-5473817cd662) *(766.17 KiB)*
- [⬇️ nmdc-ircfrontend-1.3.0-src.zip](https://git.ivysaur.me/attachments/25b14ccb-82ae-402c-8110-6026b086f4aa) *(23.00 KiB)*
- [⬇️ nmdc-ircfrontend-1.3.0-linux64.tar.xz](https://git.ivysaur.me/attachments/8f4f7592-7070-459f-a2ff-4db910433b60) *(937.04 KiB)*
- [⬇️ nmdc-ircfrontend-1.3.0-linux32.tar.xz](https://git.ivysaur.me/attachments/3ea281d7-2e04-484c-8f0f-70ecfb2918ae) *(876.77 KiB)*
2017-05-28 1.2.3
- Fix a regression with userlist display on HexChat (other IRC client compatibility unknown)
- [⬇️ nmdc-ircfrontend-1.2.3-win64.7z](https://git.ivysaur.me/attachments/19b20903-f173-498a-81bc-e87ab8c22a07) *(751.55 KiB)*
- [⬇️ nmdc-ircfrontend-1.2.3-win32.7z](https://git.ivysaur.me/attachments/30f0a2b7-1f7a-4d15-b70e-bd52f3dde93a) *(687.16 KiB)*
- [⬇️ nmdc-ircfrontend-1.2.3-src.zip](https://git.ivysaur.me/attachments/55a73ae2-96db-4c20-9185-4ce38617053c) *(21.03 KiB)*
- [⬇️ nmdc-ircfrontend-1.2.3-linux64.tar.xz](https://git.ivysaur.me/attachments/ee6992f1-897f-4c8d-bb29-4b9c2a4330df) *(865.07 KiB)*
- [⬇️ nmdc-ircfrontend-1.2.3-linux32.tar.xz](https://git.ivysaur.me/attachments/82f9260c-cfe6-49ea-9a5a-8d6463a399d9) *(799.88 KiB)*
2017-05-27 1.2.2
- Update libnmdc to 0.14
- Fix a crash that could occur if the server is scanned by a non-irc client
- [⬇️ nmdc-ircfrontend-1.2.2-win64.7z](https://git.ivysaur.me/attachments/92ff9cdf-01a7-41bf-82c1-150295509df0) *(751.37 KiB)*
- [⬇️ nmdc-ircfrontend-1.2.2-win32.7z](https://git.ivysaur.me/attachments/450cae51-4d30-46fc-8631-682bbf570128) *(686.93 KiB)*
- [⬇️ nmdc-ircfrontend-1.2.2-src.zip](https://git.ivysaur.me/attachments/a70426b2-d34f-4eef-87a9-3541f28eb04d) *(19.78 KiB)*
- [⬇️ nmdc-ircfrontend-1.2.2-linux64.tar.xz](https://git.ivysaur.me/attachments/d2a2c832-7c1f-4319-bd10-0b7718b56146) *(865.31 KiB)*
- [⬇️ nmdc-ircfrontend-1.2.2-linux32.tar.xz](https://git.ivysaur.me/attachments/1ce22001-dc7f-495b-b384-0e3089392e18) *(799.69 KiB)*
2016-11-29 1.2.1
- Update libnmdc to 0.11
- Fix an issue with -devel version tag in 1.2.0 release binaries
- [⬇️ nmdc-ircfrontend-1.2.1-win64.7z](https://git.ivysaur.me/attachments/a2c89ef5-36c1-41fb-a724-b6665c7ac226) *(750.20 KiB)*
- [⬇️ nmdc-ircfrontend-1.2.1-win32.7z](https://git.ivysaur.me/attachments/3648db7b-12cc-4496-9f0b-ec3dada1a8ff) *(685.56 KiB)*
- [⬇️ nmdc-ircfrontend-1.2.1-src.zip](https://git.ivysaur.me/attachments/97b49b6c-3c36-4eb2-8ca6-b4c9b78ed876) *(20.88 KiB)*
- [⬇️ nmdc-ircfrontend-1.2.1-linux64.tar.xz](https://git.ivysaur.me/attachments/a52a32b1-aca2-4471-99cc-71517e32461f) *(855.09 KiB)*
- [⬇️ nmdc-ircfrontend-1.2.1-linux32.tar.xz](https://git.ivysaur.me/attachments/9b46ee3f-b9d3-4a80-b434-f07952669728) *(798.09 KiB)*
2016-08-27 1.2.0
- Feature: Support WHOIS (display NMDC user's description + client software)
- Feature: `-version` command-line option
- Compatibility: Demote 'Lite IRC' to 'Usable with bugs' section
- Update libnmdc to r9 (fix protocol issues)
- Update golang to 1.7 (smaller binary size)
- [⬇️ nmdc-ircfrontend-1.2.0-win64.7z](https://git.ivysaur.me/attachments/9c35784f-be38-4796-bef1-7fb9a370c2a1) *(747.07 KiB)*
- [⬇️ nmdc-ircfrontend-1.2.0-win32.7z](https://git.ivysaur.me/attachments/f49ff51b-1981-4e26-ad87-80def4a5cd57) *(682.37 KiB)*
- [⬇️ nmdc-ircfrontend-1.2.0-src.zip](https://git.ivysaur.me/attachments/921acc9b-1df9-4445-8192-04effa94cb87) *(20.84 KiB)*
- [⬇️ nmdc-ircfrontend-1.2.0-linux64.tar.xz](https://git.ivysaur.me/attachments/38bdc89f-3519-4217-8950-c13366c1e9c2) *(851.35 KiB)*
- [⬇️ nmdc-ircfrontend-1.2.0-linux32.tar.xz](https://git.ivysaur.me/attachments/1258ef99-e598-4a26-b287-83d0e2cd76cc) *(794.09 KiB)*
2016-05-10 1.1.0
- Feature: Support renaming own client during connection (`/nick`)
- Enhancement: Option to set Hub-Security nick (needed for initial CTCP, upgraded after upstream connection)
- Compatibility: Apply user-agent-specific hacks for mIRC and Atomic clients not nicely handling PRIVMSG with blank sender
- Compatibility: Better heuristic detection for client tag extraction
- Fix an issue causing the default client tag (nmdc-ircfrontend) to briefly appear in the NMDC user list, by (briefly) delaying the upstream join unless CTCP VERSION response comes in
- Fix an issue generating malformed version numbers in NMDC client tag
- [⬇️ nmdc-ircfrontend-1.1.0-win64.7z](https://git.ivysaur.me/attachments/ab9a32da-cff7-4de9-91d5-fcb7244646c2) *(896.08 KiB)*
- [⬇️ nmdc-ircfrontend-1.1.0-win32.7z](https://git.ivysaur.me/attachments/032a195f-585d-4d85-a904-1f5c4556ac75) *(819.77 KiB)*
- [⬇️ nmdc-ircfrontend-1.1.0-src.zip](https://git.ivysaur.me/attachments/61ae2b92-6502-419b-9d73-79733a820a75) *(20.46 KiB)*
- [⬇️ nmdc-ircfrontend-1.1.0-linux64.tar.xz](https://git.ivysaur.me/attachments/dd4a073d-fe01-4c5d-9eac-27d7c580931c) *(989.59 KiB)*
- [⬇️ nmdc-ircfrontend-1.1.0-linux32.tar.xz](https://git.ivysaur.me/attachments/de5e5bdd-2407-4765-917c-6d7c5df40d82) *(924.09 KiB)*
2016-05-08 1.0.0
- Initial public release
- [⬇️ nmdc-ircfrontend-1.0.0-win64.7z](https://git.ivysaur.me/attachments/a6f42ccb-6b62-408b-af25-7dcfa73d669c) *(890.16 KiB)*
- [⬇️ nmdc-ircfrontend-1.0.0-win32.7z](https://git.ivysaur.me/attachments/b1735675-bbad-473f-9141-c890cac71471) *(817.71 KiB)*
- [⬇️ nmdc-ircfrontend-1.0.0-src.zip](https://git.ivysaur.me/attachments/64c2a8a7-5b7e-46f5-8f30-959f665b616f) *(20.98 KiB)*
- [⬇️ nmdc-ircfrontend-1.0.0-linux64.tar.xz](https://git.ivysaur.me/attachments/0f862b24-f0bb-41a6-bdea-2d27b95a1d00) *(987.32 KiB)*
- [⬇️ nmdc-ircfrontend-1.0.0-linux32.tar.xz](https://git.ivysaur.me/attachments/84471f39-bc5a-47ed-a2ab-0eab66c24ee3) *(922.65 KiB)*

View File

@ -1,63 +0,0 @@
An IRC protocol frontend for an NMDC server.
This program 'wraps' an NMDC server to make it available to IRC clients. It implements the IRC server protocol, but is not itself an IRC server, deferring to the upstream NMDC server in all salient cases. From a layering perspective it occupies the same position as the web interface.
The intent of this project is to expand client library and application support on platforms where the NMDC community has not been able to spend effort (e.g. Apple iOS client, ruby/rust/erlang libraries, greater choice in web interfaces).
TLS (SSL) support is not integrated. To host the IRC frontend over TLS, please use a reverse proxy in much the same manner as for NMDCS.
This program uses some code from the AGPLv3 project https://github.com/eXeC64/Rosella , from which it inherits the Affero GPLv3 license. Anyone hosting a modified version of this software is required to release their changes under the terms of the AGPLv3.
Tags: NMDC AGPLv3
=FEATURES=
- Seamless roundtrip transition between NMDC `/me` and IRC `CTCP ACTION`
- IRC client's "Real Name" exposed as NMDC Description
- IRC client's `CTCP VERSION` exposed as NMDC client tag, with heuristic truncation
- NMDC server's title exposed as IRC channel topic
- Login with passworded nick (classic `PASS`, not `SASL`)
- Full nick list integration including op status
- Automatic join to single-enforced chatroom
- Multithreaded
- Single binary deployment
=USAGE=
`Usage of nmdc-ircfrontend:
-autojoin
Automatically join clients to the channel (default true)
-bind string
The address:port to bind to and listen for clients on (default ":6667")
-servername string
Server name displayed to clients (default "nmdc-ircfrontend")
-upstream string
Upstream NMDC server (default "127.0.0.1:411")
-verbose
Display debugging information`
=COMPATIBILITY=
NMDC's smaller community has standardised around comparatively few protocol implementations by means of necessity. In comparison, there are a lot of IRC client implementations with slightly differing interpretations of the protocol.
[b]Everything works:[/b]
- Hexchat
- Mango IRC
- AndroIRC
- Lite IRC
- Mutter
- Weechat
[b]Usable, with bugs:[/b]
- HoloIRC - Can't parse client tag, upstream bug https://github.com/tilal6991/HoloIRC/issues/140
- AndChat - Duplicate usernames appear, upstream bug https://github.com/znc/znc/issues/424
- Irssi - Ignorable warning "critical nicklist_set_host: assertion 'host != NULL' failed"
[b]Unusable:[/b]
- Yaaic - doesn't properly understand/parse the room join
- Atomic - doesn't properly understand/parse the room join, crashes on `PRIVMSG` with blank sender
=CHANGELOG=
2016-05-08 1.0.0
- Initial public release

23
REFERENCE.md Normal file
View File

@ -0,0 +1,23 @@
# References
https://www.alien.net.au/irc/irc2numerics.html
http://faerion.sourceforge.net/doc/irc/whox.var
http://www.anta.net/misc/telnet-troubleshooting/irc.shtml
https://tools.ietf.org/html/rfc2812
http://www.irchelp.org/irchelp/rfc/ctcpspec.html
https://en.wikipedia.org/wiki/List_of_Internet_Relay_Chat_commands
https://en.wikipedia.org/wiki/Client-to-client_protocol
https://wiki.mibbit.com/index.php/Ctcp_(version)
https://github.com/eXeC64/Rosella AGPLv3 license
https://github.com/edmund-huber/ergonomadic MIT license
http://en.wikichip.org/wiki/irc/colors

View File

@ -1,61 +0,0 @@
WISHLIST
========
- verify PING responses (application-layer timeout protocol)
- automatic markdown bold/italic formatting
- support changing nick (via reconnecting)
- support WHOIS
- respond to CTCP VERSION with the clienttag on behalf of other users
- support USERIP/KILL/KICK for ops
- use CTCP chat to support irc-special characters in chat messages (colon, newline)
WONTFIX
=======
There's no additional client compatibility gained by implementing the following
- support USERHOST (used by androirc)
2016/05/08 13:43:55 >>> 'USERHOST' [squirtle90]
2016/05/08 13:43:55 <<< :pokehub.info 421 squirtle90 USERHOST :Unknown command
- support WHOX
- support SASL PLAIN authentication as well as just classic PASS
REF
===
https://www.alien.net.au/irc/irc2numerics.html
http://faerion.sourceforge.net/doc/irc/whox.var
http://www.anta.net/misc/telnet-troubleshooting/irc.shtml
https://tools.ietf.org/html/rfc2812
http://www.irchelp.org/irchelp/rfc/ctcpspec.html
https://en.wikipedia.org/wiki/List_of_Internet_Relay_Chat_commands
https://en.wikipedia.org/wiki/Client-to-client_protocol
https://wiki.mibbit.com/index.php/Ctcp_(version)
https://github.com/eXeC64/Rosella AGPLv3 license
https://github.com/edmund-huber/ergonomadic MIT license
http://en.wikichip.org/wiki/irc/colors

33
chatFormatting.go Normal file
View File

@ -0,0 +1,33 @@
package main
import (
"regexp"
"strings"
)
var (
rx_colorControlCode *regexp.Regexp = regexp.MustCompilePOSIX("\x03[0-9]+(,[0-9]+)?")
rx_formattingControlCode *regexp.Regexp = regexp.MustCompile("(\x02|\x1D|\x1F|\x16|\x0F)")
)
func reformatOutgoingMessageBody(body string) string {
// Strip color codes
noColors := rx_colorControlCode.ReplaceAllString(body, "")
// Strip formatting codes
return rx_formattingControlCode.ReplaceAllString(noColors, "")
}
func reformatIncomingMessageBody(body string) string {
// "Greentext" filter
// TODO markdown filters
if len(body) > 0 && body[0] == '>' {
return "\x033" + strings.Replace(body, "implying", "\x02implying\x02", -1)
} else {
return body
}
}

68
clientTag.go Normal file
View File

@ -0,0 +1,68 @@
package main
import (
"regexp"
"strings"
)
type clientTag struct {
AppName string
Version string
}
var (
rx_bestNumberPart = regexp.MustCompile(`[0-9\.]+`)
)
func parseVersion(ver string) clientTag {
// Try our best to turn the supplied text into a structured version
ret := clientTag{
AppName: APP_NAME,
Version: APP_VERSION,
}
// Special case: Some clients use a structured version AppName:Version:Metadata
// If we check for that, we can support clients with spaces in the name
if cParts := strings.Split(ver, ":"); len(cParts) == 3 {
ret.AppName = cParts[0]
ret.Version = cParts[1]
} else if ver == "Atomic - An IRC client for Android https://indrora.github.io/Atomic" {
ret.AppName = "Atomic"
ret.Version = "2.1" // Abandonware. Last available version
} else if ver == "Yaaic - Yet Another Android IRC Client - http://www.yaaic.org" {
ret.AppName = "Yaaic"
ret.Version = "1.1" // Abandonware. Last available version
} else {
// Special cases all failed, time for heuristics
// Turn colons to spaces; keep the first word, and the the first word containing digits
ver = strings.Trim(strings.Replace(ver, ":", " ", -1), " ")
words := strings.Split(ver, " ")
for _, word := range words[1:] {
if strings.ContainsAny(word, "0123456789") {
ret.AppName = words[0]
ret.Version = strings.Replace(word, "(", "", -1) // AndroIRC
break
}
}
}
// We only support digits, periods, and hyphens in the number part
// This removes the leading v from mIRC and the trailing deb** from irssi
if submatch := rx_bestNumberPart.FindStringSubmatch(ret.Version); len(submatch) == 1 {
ret.Version = submatch[0]
}
// Special case: "Relay" means "HoloIRC"
if ret.AppName == "Relay" && ret.Version == "1.0" && strings.Contains(ver, "Android") {
ret.AppName = "HoloIRC"
ret.Version = "4"
}
return ret
}

73
clientTag_test.go Normal file
View File

@ -0,0 +1,73 @@
package main
import (
"testing"
)
func TestParseVersion(t *testing.T) {
tests := [][3]string{
[3]string{
"HexChat 2.12.1 [x64] / Microsoft Windows 10 Pro (x64) [Intel(R) Core(TM) i5-2500 CPU @ 3.30GHz (3.60GHz)]",
"HexChat", "2.12.1",
},
[3]string{
"AndroIRC - Android IRC Client (5.2 - Build 6830152) - http://www.androirc.com",
"AndroIRC", "5.2",
},
[3]string{
"AndChat 1.4.3.2 http://www.andchat.ne",
"AndChat", "1.4.3.2",
},
[3]string{
"liteIRC for Android 1.1.8",
"liteIRC", "1.1.8",
},
[3]string{
":Relay:1.0:Android", // HoloIRC before 4.1.0
"HoloIRC", "4",
},
[3]string{
"Relay:1.0:Android", // HoloIRC after 4.1.0
"HoloIRC", "4",
},
[3]string{
"mIRC v7.45",
"mIRC", "7.45",
},
[3]string{
"Revolution IRC:0.3.2:Android",
"Revolution IRC", "0.3.2",
},
[3]string{
"irssi v1.0.2-1+deb9u3",
"irssi", "1.0.2",
},
[3]string{
"Atomic - An IRC client for Android https://indrora.github.io/Atomic",
"Atomic", "2.1",
},
[3]string{
"Yaaic - Yet Another Android IRC Client - http://www.yaaic.org",
"Yaaic", "1.1",
},
}
for _, test := range tests {
result := parseVersion(test[0])
if result.AppName != test[1] || result.Version != test[2] {
t.Fatalf("Got <%s,%s> expecting <%s,%s>\n", result.AppName, result.Version, test[1], test[2])
}
}
}

6
go.mod Normal file
View File

@ -0,0 +1,6 @@
module code.ivysaur.me/nmdc-ircfrontend
require (
code.ivysaur.me/libnmdc v0.17.0
github.com/cxmcc/tiger v0.0.0-20170524142333-bde35e2713d7 // indirect
)

4
go.sum Normal file
View File

@ -0,0 +1,4 @@
code.ivysaur.me/libnmdc v0.17.0 h1:51QlGxldeo7DKZGE7koiU8MA2ehwB9FtQLog/LHuOeg=
code.ivysaur.me/libnmdc v0.17.0/go.mod h1:ZHCjIX/zm29hd2H8YtzOBHiSyowZNOHvgoMRjKbBLLg=
github.com/cxmcc/tiger v0.0.0-20170524142333-bde35e2713d7 h1:jBEtq1t2gpn2kEzvRlCUxvvrxl5aSWkXNPwe/hwvSNQ=
github.com/cxmcc/tiger v0.0.0-20170524142333-bde35e2713d7/go.mod h1:ruCYvt9rtYymAr4rNmfYJrl1dz8HSXUFP7cufqKOsDI=

175
godist.sh
View File

@ -1,175 +0,0 @@
#!/bin/bash
# godist.sh is a template build script for golang applications.
#
# Copyright (c) 2016, The godist.sh Author(s)
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
#
set -eu
DIST_DIR=./_dist
sanitise() {
TARGETFILE="$1" php <<'EOD'
<?php
error_reporting(E_ALL);
ini_set('display_errors', 'On');
$targetfile = getenv("TARGETFILE");
$data = file_get_contents($targetfile);
$gopath = getenv("GOPATH");
$newpath = preg_replace('~[a-zA-Z0-9]~', 'x', $gopath);
$data = str_replace([$gopath, str_replace('\\', '/', $gopath)], [$newpath, $newpath], $data);
file_put_contents($targetfile, $data);
EOD
}
get_package_name() {
# Binaries are named after the containing folder.
pwd | tr / $'\n' | tail -n1
}
check_dependencies() {
# `set -e` will take care of killing the application if the dependency
# goes unresolved.
which 7z > /dev/null
which php > /dev/null
}
get_platform_description() {
if [[ $GOOS == windows ]] ; then
echo -n "win"
else
echo -n "${GOOS}"
fi
if [[ $GOARCH == 386 ]] ; then
echo -n 32
elif [[ $GOARCH == amd64 ]] ; then
echo -n 64
else
echo -n "-${GOARCH}"
if [[ -v $GOARM ]] ; then
echo -n "v${GOARM}"
fi
fi
}
pathfix() {
if [[ $(uname -o) == Cygwin ]] ; then
cygpath -w "$1"
else
echo "$1"
fi
}
single_build() {
local version="$1"
# Determine build output name
local bin_name=''
if [[ $GOOS == windows ]] ; then
local_bin_name="$(get_package_name).exe"
else
local_bin_name="$(get_package_name)"
fi
if [[ -f ./$local_bin_name ]] ; then
rm -f "./$local_bin_name"
fi
local tmpdir=$(mktemp -d)
# Platform identifier
local platform="$(get_platform_description)"
echo "[INFO] Building ${version} for ${platform}..."
# Build.
# GOARCH/GOOS supplied in function env
go build -a -ldflags '-s -w' -o "$(pathfix "${tmpdir}/${local_bin_name}")"
# Sanitise.
sanitise "${tmpdir}/${local_bin_name}"
# Archive.
if [[ ! -d $DIST_DIR ]] ; then
mkdir "$DIST_DIR"
fi
local archive_name="${DIST_DIR}/$(get_package_name)-${version}-${platform}"
if [[ $GOOS == windows ]] ; then
archive_name="${archive_name}.7z"
7z a -mx9 "$archive_name" "${tmpdir}/${local_bin_name}" >/dev/null
else
archive_name="${archive_name}.tar.xz"
XZ_OPT='-9' tar caf "$archive_name" -C "${tmpdir}" "${local_bin_name}" --owner=0 --group=0 >/dev/null
fi
# Cleanup
rm -f "${tmpdir}/${local_bin_name}"
rmdir "$tmpdir"
}
datestamp() {
( TZ=UTC date +%Y%m%d%H%M%SZ )
}
usage() {
cat <<EOD
Usage: ./godist.sh [-v VERSION]
EOD
exit 0
}
main() {
check_dependencies
if [[ ! -v GOPATH ]] ; then
echo "Please set GOPATH." >&2
exit 1
fi
if [[ ! -d $GOPATH ]] ; then
echo "Invalid GOPATH set." >&2
exit 1
fi
local version=""
while getopts ':v:' flag ; do
case "$flag" in
v)
version="$OPTARG"
;;
*)
usage
;;
esac
done
if [[ $version == "" ]] ; then
read -p "Enter version string (blank for timestamp)> " version
if [[ $version == "" ]] ; then
version=$(datestamp)
fi
fi
GOARCH=amd64 GOOS=windows single_build "$version"
GOARCH=386 GOOS=windows single_build "$version"
GOARCH=amd64 GOOS=linux single_build "$version"
GOARCH=386 GOOS=linux single_build "$version"
echo "[INFO] Build complete."
}
main "$@"

18
main.go
View File

@ -1,7 +1,7 @@
package main package main
/* /*
Copyright (C) 2016 The `nmdc-ircfrontend' author(s) Copyright (C) 2016-2018 The `nmdc-ircfrontend' author(s)
Copyright (C) 2013 Harry Jeffery Copyright (C) 2013 Harry Jeffery
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -20,20 +20,29 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import ( import (
"flag" "flag"
"libnmdc"
"log" "log"
"net" "net"
"code.ivysaur.me/libnmdc"
) )
func main() { func main() {
ircAddress := flag.String("bind", ":6667", "The address:port to bind to and listen for clients on") ircAddress := flag.String("bind", ":6667", "The address:port to bind to and listen for clients on")
dcAddress := flag.String("upstream", "127.0.0.1:411", "Upstream NMDC server") dcAddress := flag.String("upstream", "127.0.0.1:411", "Upstream NMDC/ADC server")
serverName := flag.String("servername", "nmdc-ircfrontend", "Server name displayed to clients") serverName := flag.String("servername", APP_NAME, "Server name displayed to clients")
hubsec := flag.String("hubsecurity", "Hub-Security", "Nick used for administrative events")
verbose := flag.Bool("verbose", false, "Display debugging information") verbose := flag.Bool("verbose", false, "Display debugging information")
autojoin := flag.Bool("autojoin", true, "Automatically join clients to the channel") autojoin := flag.Bool("autojoin", true, "Automatically join clients to the channel")
version := flag.Bool("version", false, "Display version and exit")
flag.Parse() flag.Parse()
if *version {
log.Printf("%s version %s\n", APP_NAME, APP_VERSION)
return
}
log.Printf("Listening on '%s'...", *ircAddress) log.Printf("Listening on '%s'...", *ircAddress)
if *autojoin { if *autojoin {
log.Printf("Clients will be automatically joined to the channel.") log.Printf("Clients will be automatically joined to the channel.")
@ -56,6 +65,7 @@ func main() {
server := NewServer(*serverName, libnmdc.HubAddress(*dcAddress), conn) server := NewServer(*serverName, libnmdc.HubAddress(*dcAddress), conn)
server.verbose = *verbose server.verbose = *verbose
server.autojoin = *autojoin server.autojoin = *autojoin
server.hubSecNick = *hubsec
go server.RunWorker() go server.RunWorker()
} }
} }

20
quirks.go Normal file
View File

@ -0,0 +1,20 @@
package main
import (
"strings"
)
type Quirks struct {
RequireNickForGeneralMessages bool
}
func GetQuirksForClient(ver string) Quirks {
if strings.Contains(ver, "mIRC") || strings.Contains(ver, "Atomic") || strings.Contains(ver, "Yaaic") {
return Quirks{
RequireNickForGeneralMessages: true,
}
} else {
return Quirks{}
}
}

374
server.go
View File

@ -1,7 +1,7 @@
package main package main
/* /*
Copyright (C) 2016 The `nmdc-ircfrontend' author(s) Copyright (C) 2016-2018 The `nmdc-ircfrontend' author(s)
Copyright (C) 2013 Harry Jeffery Copyright (C) 2013 Harry Jeffery
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -22,12 +22,13 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"libnmdc"
"log" "log"
"net" "net"
"regexp"
"strings" "strings"
"sync"
"time" "time"
"code.ivysaur.me/libnmdc"
) )
type ClientState int type ClientState int
@ -41,23 +42,32 @@ const (
type Server struct { type Server struct {
name string name string
motd string motd string
hubSecNick string
clientConn net.Conn clientConn net.Conn
clientState ClientState clientState ClientState
ClientStateLock sync.Mutex
upstreamLauncher libnmdc.HubConnectionOptions upstreamLauncher libnmdc.HubConnectionOptions
upstreamCloser chan struct{} upstreamCloser chan struct{}
upstreamEvents chan libnmdc.HubEvent
upstream *libnmdc.HubConnection upstream *libnmdc.HubConnection
verbose bool verbose bool
autojoin bool autojoin bool
sentCtcpVersion bool recievedFirstServerMessage bool
recievedCtcpVersion bool
nickChangeAttempt int
sentFakeSelfJoin bool
quirks Quirks
} }
func NewServer(name string, upstream libnmdc.HubAddress, conn net.Conn) *Server { func NewServer(name string, upstream libnmdc.HubAddress, conn net.Conn) *Server {
self := libnmdc.NewUserInfo("") self := libnmdc.NewUserInfo("")
self.ClientTag = APP_DESCRIPTION self.ClientTag = APP_NAME
self.ClientVersion = APP_VERSION
return &Server{ return &Server{
name: name, name: name,
@ -66,10 +76,12 @@ func NewServer(name string, upstream libnmdc.HubAddress, conn net.Conn) *Server
motd: "Connected to " + name + ". You /must/ join " + BLESSED_CHANNEL + " to continue.", motd: "Connected to " + name + ". You /must/ join " + BLESSED_CHANNEL + " to continue.",
upstreamLauncher: libnmdc.HubConnectionOptions{ upstreamLauncher: libnmdc.HubConnectionOptions{
Address: upstream, Address: upstream,
Self: *self, Self: self,
SkipAutoReconnect: true, SkipAutoReconnect: true,
}, },
upstreamEvents: make(chan libnmdc.HubEvent, 0), // unbuffered
upstreamCloser: make(chan struct{}, 1), upstreamCloser: make(chan struct{}, 1),
quirks: Quirks{},
} }
} }
@ -107,7 +119,7 @@ func (s *Server) RunWorker() {
} }
// If this was a /timeout/, send a KA and continue. // If this was a /timeout/, send a KA and continue.
if libnmdc.CheckIsNetTimeout(err) { if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
s.writeClient("PING :" + s.name) s.writeClient("PING :" + s.name)
continue continue
} }
@ -126,12 +138,12 @@ func (s *Server) RunWorker() {
// Client sent a command // Client sent a command
fields := strings.Fields(string(line)) fields := strings.Fields(string(line))
if len(fields) < 1 { if len(fields) == 0 {
return return
} }
if strings.HasPrefix(fields[0], ":") { if strings.HasPrefix(fields[0], ":") {
fields = fields[1:] fields[0] = fields[0][1:]
} }
s.handleCommand(strings.ToUpper(fields[0]), fields[1:]) s.handleCommand(strings.ToUpper(fields[0]), fields[1:])
@ -152,10 +164,14 @@ func (s *Server) RunWorker() {
func (s *Server) postGeneralMessageInRoom(msg string) { func (s *Server) postGeneralMessageInRoom(msg string) {
// FIXME blank names work well in hexchat, but not in yaaic var sendAs = ""
var BLANK_NICK = "" // "!@" + s.name // "PtokaX" // "\xC2\xA0"
// CTCP action conversion // Some clients can't handle blank nicks very well
if s.quirks.RequireNickForGeneralMessages {
sendAs = s.hubSecNick + "!" + s.hubSecNick + "@" + s.hubSecNick
}
// Detect pseudo-system message for potential CTCP ACTION conversion
words := strings.Split(msg, " ") words := strings.Split(msg, " ")
firstWord := words[0] firstWord := words[0]
remainder := strings.Join(words[1:], " ") remainder := strings.Join(words[1:], " ")
@ -176,36 +192,11 @@ func (s *Server) postGeneralMessageInRoom(msg string) {
} else { } else {
// genuine system message // genuine system message
s.reply(rplMsg, BLANK_NICK, BLESSED_CHANNEL, msg) s.reply(rplMsg, sendAs, BLESSED_CHANNEL, msg)
} }
} }
var rx_colorControlCode *regexp.Regexp = regexp.MustCompilePOSIX("\x03[0-9]+(,[0-9]+)?")
var rx_formattingControlCode *regexp.Regexp = regexp.MustCompile("(\x02|\x1D|\x1F|\x16|\x0F)")
func reformatOutgoingMessageBody(body string) string {
// Strip color codes
noColors := rx_colorControlCode.ReplaceAllString(body, "")
// Strip formatting codes
return rx_formattingControlCode.ReplaceAllString(noColors, "")
}
func reformatIncomingMessageBody(body string) string {
// "Greentext" filter
// TODO markdown filters
if len(body) > 0 && body[0] == '>' {
return "\x033" + strings.Replace(body, "implying", "\x02implying\x02", -1)
} else {
return body
}
}
func (s *Server) upstreamWorker() { func (s *Server) upstreamWorker() {
// Read loop // Read loop
@ -217,11 +208,15 @@ func (s *Server) upstreamWorker() {
s.upstream.Disconnect() s.upstream.Disconnect()
return return
case hubEvent := <-s.upstream.OnEvent: case hubEvent := <-s.upstreamEvents:
switch hubEvent.EventType { switch hubEvent.EventType {
case libnmdc.EVENT_USER_JOINED: case libnmdc.EVENT_USER_JOINED:
s.reply(rplJoin, hubEvent.Nick, BLESSED_CHANNEL) if hubEvent.Nick == s.clientNick() && s.sentFakeSelfJoin {
s.sentFakeSelfJoin = false
} else {
// If we want to JOIN with the full power of the supplied nick!user@host, then we'll need to actually remember the active client's USER parameters // If we want to JOIN with the full power of the supplied nick!user@host, then we'll need to actually remember the active client's USER parameters
s.reply(rplJoin, hubEvent.Nick, BLESSED_CHANNEL)
}
case libnmdc.EVENT_USER_PART: case libnmdc.EVENT_USER_PART:
s.reply(rplPart, hubEvent.Nick, BLESSED_CHANNEL, "Disconnected") s.reply(rplPart, hubEvent.Nick, BLESSED_CHANNEL, "Disconnected")
@ -230,14 +225,24 @@ func (s *Server) upstreamWorker() {
// description change - no relevance for IRC users // description change - no relevance for IRC users
case libnmdc.EVENT_CONNECTION_STATE_CHANGED: case libnmdc.EVENT_CONNECTION_STATE_CHANGED:
s.postGeneralMessageInRoom("* Upstream: " + hubEvent.StateChange.Format()) s.postGeneralMessageInRoom("* Upstream: " + hubEvent.StateChange.String())
if hubEvent.StateChange == libnmdc.CONNECTIONSTATE_CONNECTED { if hubEvent.StateChange == libnmdc.CONNECTIONSTATE_CONNECTED {
s.sendNames() // delay doing this until now s.sendNames() // delay doing this until now
} }
if hubEvent.StateChange == libnmdc.CONNECTIONSTATE_DISCONNECTED { if hubEvent.StateChange == libnmdc.CONNECTIONSTATE_DISCONNECTED {
if s.nickChangeAttempt > 0 {
// If this was a nick change, reconnect /immediately/
s.upstream = nil
s.clientState = CSRegistered
s.maybeStartUpstream() // launches new goroutine
} else {
// Abandon thread. Don't try to autoreconnect at our level, the remote client can be responsible for that // Abandon thread. Don't try to autoreconnect at our level, the remote client can be responsible for that
s.DisconnectClient() s.DisconnectClient()
}
return
} }
case libnmdc.EVENT_HUBNAME_CHANGED: case libnmdc.EVENT_HUBNAME_CHANGED:
@ -252,12 +257,10 @@ func (s *Server) upstreamWorker() {
} else { } else {
// nick!username@userhost, but for us all three of those are always identical // nick!username@userhost, but for us all three of those are always identical
s.reply(rplMsg, hubEvent.Nick+"!"+hubEvent.Nick+"@"+hubEvent.Nick, BLESSED_CHANNEL, reformatIncomingMessageBody(hubEvent.Message)) s.reply(rplMsg, hubEvent.Nick+"!"+hubEvent.Nick+"@"+hubEvent.Nick, BLESSED_CHANNEL, reformatIncomingMessageBody(hubEvent.Message))
if !s.sentCtcpVersion {
// Abuse this first username in order to also send a CTCP version request
s.reply(rplMsg, hubEvent.Nick+"!"+hubEvent.Nick+"@"+hubEvent.Nick, BLESSED_CHANNEL, "\x01VERSION\x01")
s.sentCtcpVersion = true
} }
if !s.recievedFirstServerMessage {
s.hubSecNick = hubEvent.Nick // Replace with the hub's real Hub-Security nick, although we shouldn't need it again
} }
case libnmdc.EVENT_SYSTEM_MESSAGE_FROM_CONN, libnmdc.EVENT_SYSTEM_MESSAGE_FROM_HUB: case libnmdc.EVENT_SYSTEM_MESSAGE_FROM_CONN, libnmdc.EVENT_SYSTEM_MESSAGE_FROM_HUB:
@ -280,10 +283,10 @@ func (s *Server) handleCommand(command string, args []string) {
// do nothing // do nothing
case "INFO": case "INFO":
s.reply(rplInfo, APP_DESCRIPTION) s.reply(rplInfo, APP_NAME+" v"+APP_VERSION)
case "VERSION": case "VERSION":
s.reply(rplVersion, VERSION) s.reply(rplVersion, APP_VERSION)
case "MOTD": case "MOTD":
s.sendMOTD(s.motd) s.sendMOTD(s.motd)
@ -317,17 +320,38 @@ func (s *Server) handleCommand(command string, args []string) {
return return
} }
// mIRC puts a colon in first place when changing nick, hexchat doesn't
suppliedNick := args[0]
if len(suppliedNick) >= 2 && suppliedNick[0] == ':' {
suppliedNick = suppliedNick[1:]
}
if s.clientNick() == "" { if s.clientNick() == "" {
// allow set, as part of the login phase // allow set, as part of the login phase
s.upstreamLauncher.Self.Nick = args[0] s.upstreamLauncher.Self.Nick = suppliedNick
} else if args[0] == s.clientNick() { } else if suppliedNick == s.clientNick() {
// Ignore // Ignore
// Required for compatibility with Lite IRC, which sends USER/NICK in the wrong order // Required for compatibility with Lite IRC, which sends USER/NICK in the wrong order
} else { } else {
s.reply(rplKill, "Can't change nicks on this server.")
s.DisconnectClient() s.ClientStateLock.Lock()
defer s.ClientStateLock.Unlock()
if s.upstream == nil {
// Not yet connected, should be safe to change nick
s.upstreamLauncher.Self.Nick = suppliedNick
} else {
// Need to disconnect/reconnect the upstream
s.writeClient(fmt.Sprintf(":%s!%s@%s NICK %s", s.clientNick(), s.clientNick(), s.clientNick(), suppliedNick)) // notify client about what they've done
s.upstreamLauncher.Self.Nick = suppliedNick
s.nickChangeAttempt++
s.upstream.Disconnect()
}
//s.reply(rplKill, "Can't change nicks on this server.")
//s.DisconnectClient()
} }
case "USER": case "USER":
@ -355,7 +379,9 @@ func (s *Server) handleCommand(command string, args []string) {
s.reply(rplWelcome) s.reply(rplWelcome)
s.clientState = CSRegistered s.clientState = CSRegistered
// TODO tell the client that they /must/ join #chat // Send CTCP VERSION request immediately
s.reply(rplMsg, s.hubSecNick+"!"+s.hubSecNick+"@"+s.hubSecNick, BLESSED_CHANNEL, "\x01VERSION\x01")
if s.autojoin { if s.autojoin {
s.handleCommand("JOIN", []string{BLESSED_CHANNEL}) s.handleCommand("JOIN", []string{BLESSED_CHANNEL})
} }
@ -381,98 +407,14 @@ func (s *Server) handleRegisteredCommand(command string, args []string) {
} }
s.reply(rplListEnd) s.reply(rplListEnd)
case "JOIN":
if len(args) < 1 {
s.reply(errMoreArgs)
return
}
switch args[0] {
case BLESSED_CHANNEL:
if s.clientState != CSJoined {
// Join for the first time
s.clientState = CSJoined
// Acknowledge
s.reply(rplJoin, s.clientNick(), BLESSED_CHANNEL)
// Send (initially just us) nicklist for the chat channel
//s.sendNames()
// Spawn upstream connection
s.upstream = s.upstreamLauncher.Connect()
go s.upstreamWorker()
} else {
// They're already here, ignore
// Can happen if autojoin is enabled but the client already requested a login
}
case "0":
// Quitting all channels? Drop client
s.reply(rplKill, "Bye.")
s.DisconnectClient()
default:
s.reply(rplKill, "There is only '"+BLESSED_CHANNEL+"'.")
s.DisconnectClient()
}
default:
s.handleJoinedCommand(command, args)
}
}
func (s *Server) SetClientSoftwareVersion(ver string) {
// "HexChat 2.12.1 [x64] / Microsoft Windows 10 Pro (x64) [Intel(R) Core(TM) i5-2500 CPU @ 3.30GHz (3.60GHz)]"
// "AndroIRC - Android IRC Client (5.2 - Build 6830152) - http://www.androirc.com"
// "AndChat 1.4.3.2 http://www.andchat.net"
// "liteIRC for Android 1.1.8"
// ":Relay:1.0:Android"
// A bit long and unwieldy. Heuristic: stop after the first word containing digits
parts := regexp.MustCompilePOSIX("^([^0-9]*[0-9]+[^ ]*) ").FindStringSubmatch(ver)
if len(parts) > 0 {
ver = strings.TrimRight(parts[0], " ")
}
s.verbosef("Replacing client tag with '%s'...", ver)
s.upstreamLauncher.Self.ClientTag = ver
s.upstream.Hco.Self.ClientTag = ver
s.upstream.SayInfo()
}
func (s *Server) handleJoinedCommand(command string, args []string) {
if s.clientState != CSJoined {
s.reply(errNotReg)
return
}
switch command {
case "PART":
if len(args) < 1 {
s.reply(errMoreArgs)
return
}
if args[0] == BLESSED_CHANNEL {
// You can check out any time you like, but you can never leave
s.reply(rplJoin, s.clientNick(), BLESSED_CHANNEL)
s.sendNames()
}
case "PRIVMSG", "NOTICE": case "PRIVMSG", "NOTICE":
if len(args) < 2 { if len(args) < 2 {
s.reply(errMoreArgs) s.reply(errMoreArgs)
return return
} }
message := strings.Join(args[1:], " ")[1:] // strip leading colon // Strip leading colon (work around a bug in HoloIRC 4.1.0 and older)
message := strings.Join(args[1:], " ")[1:]
if strings.HasPrefix(message, "\x01VERSION ") { if strings.HasPrefix(message, "\x01VERSION ") {
// Not a real message - a reply to our internal request. Change the user's tag to match the actual client software // Not a real message - a reply to our internal request. Change the user's tag to match the actual client software
@ -482,6 +424,7 @@ func (s *Server) handleJoinedCommand(command string, args []string) {
versionString := message[9:] versionString := message[9:]
versionString = versionString[:len(versionString)-1] versionString = versionString[:len(versionString)-1]
s.SetClientSoftwareVersion(versionString) s.SetClientSoftwareVersion(versionString)
s.quirks = GetQuirksForClient(versionString)
return return
} }
@ -509,6 +452,105 @@ func (s *Server) handleJoinedCommand(command string, args []string) {
} }
case "JOIN":
if len(args) < 1 {
s.reply(errMoreArgs)
return
}
switch args[0] {
case BLESSED_CHANNEL:
// Give it a few seconds - it's better to hear the CTCP VERSION
// response first. Once we get that, it'll connect instantly
go func() {
<-time.After(WAIT_FOR_VERSION * time.Second)
s.ClientStateLock.Lock()
defer s.ClientStateLock.Unlock()
s.maybeStartUpstream()
}()
case "0":
// Quitting all channels? Drop client
s.reply(rplKill, "Bye.")
s.DisconnectClient()
default:
s.reply(rplKill, "There is only '"+BLESSED_CHANNEL+"'.")
s.DisconnectClient()
}
default:
s.handleJoinedCommand(command, args)
}
}
func (s *Server) maybeStartUpstream() {
if s.clientState != CSJoined {
// Join for the first time
s.clientState = CSJoined
// Acknowledge
s.reply(rplJoin, s.clientNick(), BLESSED_CHANNEL)
// Spawn upstream connection
s.upstream = libnmdc.ConnectAsync(&s.upstreamLauncher, s.upstreamEvents)
go s.upstreamWorker()
} else {
// They're already here, ignore
// Can happen if autojoin is enabled but the client already requested a login
}
}
func (s *Server) SetClientSoftwareVersion(ver string) {
ct := parseVersion(ver)
s.verbosef("Replacing client tag with '%s' version '%s'", ct.AppName, ct.Version)
s.upstreamLauncher.Self.ClientTag = ct.AppName
s.upstreamLauncher.Self.ClientVersion = ct.Version
s.recievedCtcpVersion = true
s.ClientStateLock.Lock()
defer s.ClientStateLock.Unlock()
if s.upstream != nil {
s.upstream.Hco.Self.ClientTag = ct.AppName
s.upstream.Hco.Self.ClientVersion = ct.Version
s.upstream.SayInfo()
} else {
// Connected for the first time (order was CTCP VERSION --> JOIN)
s.maybeStartUpstream()
}
}
func (s *Server) handleJoinedCommand(command string, args []string) {
if s.clientState != CSJoined {
s.reply(errNotReg)
return
}
switch command {
case "PART":
if len(args) < 1 {
s.reply(errMoreArgs)
return
}
if args[0] == BLESSED_CHANNEL {
// You can check out any time you like, but you can never leave
s.reply(rplJoin, s.clientNick(), BLESSED_CHANNEL)
s.sendNames()
}
case "QUIT": case "QUIT":
s.DisconnectClient() s.DisconnectClient()
@ -552,8 +594,7 @@ func (s *Server) handleJoinedCommand(command string, args []string) {
return return
} }
// s.sendWho(args[0]) // Ignore this command
// s.sendNames() // fixes hexchat, but andchat always sends WHO /immediately/ after NAMES end, causing an infinite loop
case "MODE": case "MODE":
if len(args) < 1 { if len(args) < 1 {
@ -577,6 +618,35 @@ func (s *Server) handleJoinedCommand(command string, args []string) {
return return
case "WHOIS":
if len(args) < 1 {
s.reply(errMoreArgs)
return
}
// WHOIS [target] nick[,nick2[,nick...]]
nicklist := args[0] // Assume WHOIS ${nick} only,
if len(args) >= 2 {
nicklist = args[1] // It was WHOIS ${target} ${nick} instead
}
for _, targetnick := range strings.Split(nicklist, ",") {
// tell the client something about it
// The protocol does ostensibly support wildcard WHOIS, but we don't (yet)
s.upstream.Users(func(u *map[string]libnmdc.UserInfo) error {
for nick, nickinfo := range *u {
if nick == targetnick {
s.reply(rplWhoisUser, nick, nickinfo.Description+" <"+nickinfo.ClientTag+" V:"+nickinfo.ClientVersion+">")
if nickinfo.IsOperator {
s.reply(rplWhoisOperator, nick)
}
}
}
return nil
})
s.reply(rplEndOfWhois)
}
default: default:
s.reply(errUnknownCommand, command) s.reply(errUnknownCommand, command)
} }
@ -609,6 +679,15 @@ func (s *Server) sendNames() {
}) })
} }
if len(nameList) == 0 {
// We don't have a nick list yet. Many clients can't handle a blank list
// We could delay until we do have a nick list
// Or, we could send our nick only, and filter it out of the next join
nameList = append(nameList, s.clientNick())
s.sentFakeSelfJoin = true
}
s.reply(rplNames, BLESSED_CHANNEL, strings.Join(nameList, " ")) s.reply(rplNames, BLESSED_CHANNEL, strings.Join(nameList, " "))
s.reply(rplEndOfNames, BLESSED_CHANNEL) s.reply(rplEndOfNames, BLESSED_CHANNEL)
} }
@ -679,9 +758,9 @@ func (s *Server) reply(code replyCode, args ...string) {
s.writeClient(fmt.Sprintf(":%s 005 %s NAMESX CHANTYPES=# :are supported by this server", s.name, s.clientNick())) s.writeClient(fmt.Sprintf(":%s 005 %s NAMESX CHANTYPES=# :are supported by this server", s.name, s.clientNick()))
case rplJoin: case rplJoin:
s.writeClient(fmt.Sprintf(":%s JOIN %s", args[0], args[1])) s.writeClient(fmt.Sprintf(":%s!%s@%s JOIN %s", args[0], args[0], args[0], args[1]))
case rplPart: case rplPart:
s.writeClient(fmt.Sprintf(":%s PART %s %s", args[0], args[1], args[2])) s.writeClient(fmt.Sprintf(":%s!%s@%s PART %s %s", args[0], args[0], args[0], args[1], args[2]))
case rplTopic: case rplTopic:
s.writeClient(fmt.Sprintf(":%s 332 %s %s :%s", s.name, s.clientNick(), args[0], args[1])) s.writeClient(fmt.Sprintf(":%s 332 %s %s :%s", s.name, s.clientNick(), args[0], args[1]))
case rplNoTopic: case rplNoTopic:
@ -690,7 +769,7 @@ func (s *Server) reply(code replyCode, args ...string) {
case rplNames: case rplNames:
s.writeClient(fmt.Sprintf(":%s 353 %s = %s :%s", s.name, s.clientNick(), args[0], args[1])) s.writeClient(fmt.Sprintf(":%s 353 %s = %s :%s", s.name, s.clientNick(), args[0], args[1]))
case rplEndOfNames: case rplEndOfNames:
s.writeClient(fmt.Sprintf(":%s 366 %s %s :End of NAMES list", s.name, s.clientNick(), args[0])) s.writeClient(fmt.Sprintf(":%s 366 %s %s :End of /NAMES list.", s.name, s.clientNick(), args[0]))
case rplWho: case rplWho:
s.writeClient(fmt.Sprintf(":%s 352 %s %s %s %s %s %s H :0 %s", s.name, s.clientNick(), args[1], args[0], args[0], s.name, args[0], args[0])) s.writeClient(fmt.Sprintf(":%s 352 %s %s %s %s %s %s H :0 %s", s.name, s.clientNick(), args[1], args[0], args[0], s.name, args[0], args[0]))
@ -735,6 +814,13 @@ func (s *Server) reply(code replyCode, args ...string) {
case rplPong: case rplPong:
s.writeClient(fmt.Sprintf(":%s PONG %s %s", s.name, s.clientNick(), args[0])) s.writeClient(fmt.Sprintf(":%s PONG %s %s", s.name, s.clientNick(), args[0]))
case rplWhoisUser:
s.writeClient(fmt.Sprintf(":%s 311 %s %s %s %s * :%s", s.name, args[0], args[0], args[0], s.name, args[1])) // caller should supply nick,description
case rplWhoisOperator:
s.writeClient(fmt.Sprintf(":%s 313 %s :is an IRC operator", s.name, args[0]))
case rplEndOfWhois:
s.writeClient(fmt.Sprintf(":%s 318 :End of WHOIS list", s.name))
case errMoreArgs: case errMoreArgs:
s.writeClient(fmt.Sprintf(":%s 461 %s :Not enough params", s.name, s.clientNick())) s.writeClient(fmt.Sprintf(":%s 461 %s :Not enough params", s.name, s.clientNick()))
case errNoNick: case errNoNick:

View File

@ -1,7 +1,7 @@
package main package main
/* /*
Copyright (C) 2016 The `nmdc-ircfrontend' author(s) Copyright (C) 2016-2018 The `nmdc-ircfrontend' author(s)
Copyright (C) 2013 Harry Jeffery Copyright (C) 2013 Harry Jeffery
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -18,15 +18,19 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var (
APP_VERSION = "0.0" // overridden with build ldflags
)
const ( const (
VERSION = "1.0.0" APP_NAME = "nmdc-ircfrontend"
APP_DESCRIPTION = "nmdc-ircfrontend v" + VERSION
BLESSED_CHANNEL = "#chat" // must be lowercase BLESSED_CHANNEL = "#chat" // must be lowercase
BLESSED_CHANNEL_MODE = "n" // means that you have to be in the channel to chat, but that's it BLESSED_CHANNEL_MODE = "n" // means that you have to be in the channel to chat, but that's it
NICKS_PER_PROTOMSG = 128 // Max number of nicks to send per message NICKS_PER_PROTOMSG = 128 // Max number of nicks to send per message
CLIENT_READ_BUFFSIZE = 512 CLIENT_READ_BUFFSIZE = 512
CLIENT_KEEPALIVE_EVERY = 60 // should be longer than the client's one, hexchat is 30s CLIENT_KEEPALIVE_EVERY = 60 // should be longer than the client's one, hexchat is 30s
WAIT_FOR_VERSION = 3 // Seconds to wait for the CTCP VERSION response before connecting with default MyINFO
) )
type replyCode int type replyCode int
@ -55,6 +59,9 @@ const (
rplMOTD rplMOTD
rplEndOfMOTD rplEndOfMOTD
rplPong rplPong
rplWhoisUser
rplWhoisOperator
rplEndOfWhois
errMoreArgs errMoreArgs
errNoNick errNoNick
errInvalidNick errInvalidNick

14
vendor/code.ivysaur.me/libnmdc/.hgignore generated vendored Normal file
View File

@ -0,0 +1,14 @@
mode:regex
# Compilation output
\.(?:exe|a)$
^pkg/
# Dependencies
^src/(?:github.com|gopkg.in|golang.org)/
# Scratch space
^src/nmdc/
# Binary release artefacts
/?__dist/

21
vendor/code.ivysaur.me/libnmdc/.hgtags generated vendored Normal file
View File

@ -0,0 +1,21 @@
945ab4b16d05aa084f71bf5da9a3f687e0ec8bbd v0.1.0
02a360e95480b97ddad83add5db48b2766339a99 nmdc-log-service-1.0.0
137c1b65039e03c80379826a6efdfd808f6fbc8f v0.2.0
d8b64d5527c2a5e4d76872e5bc3d69f7646135c6 v0.3.0
fca41372e400853775b02e951f9db91d87f41adb nmdc-log-service-1.0.1
050b424a7c5d5a27c9323c8810f3afbead1f5b96 v0.4.0
da9f123633f9c28be6435ed7898139665d4c39d9 nmdc-log-service-1.0.2
75a78f6a78f249a2cd8aa3d29f7e5e6319b4e03b v0.5.0
4116422bb10229d887f9296970a166fa1ef8c5fd nmdc-log-service-1.0.3
cb86f3a40115cc46f450c0c83fd9b9d3b740e820 nmdc-log-service-1.0.4
cb86f3a40115cc46f450c0c83fd9b9d3b740e820 v0.6.0
71343a2c641a438206d30ea7e75dc89a11dbef00 v0.7.0
b0e57a5fcffdf4102d669db51a3648ddf66a0792 v0.8.0
e7c2c71ef24b386add728fad35fff4a996fccbac v0.9.0
3ecc037cf2d7080572fe87c2e39ecd153fb0e947 v0.10.0
5149ffe70ea8475e480b682345b31aa45a3352db v0.11.0
22b156a6fc2f6161765317f4ec9ab3731a26e0e2 v0.12.0
3ee0f4ea5142d66079a9500bdcd48a53bdcf362f v0.13.0
6422ed687cd308c339b6dc188bbe1034ed93f893 v0.14.0
84fb191007017862ffc37af68dcdace5d8c06eee v0.15.0
a48811ff2cfe5246c26801cf27da25bf56fec8bb v0.16.0

715
vendor/code.ivysaur.me/libnmdc/AdcProtocol.go generated vendored Normal file
View File

@ -0,0 +1,715 @@
package libnmdc
import (
"encoding/base32"
"fmt"
"regexp"
"strconv"
"strings"
)
type adcState int
const (
adcStateProtocol adcState = 0
adcStateIdentify adcState = 1
adcStateVerify adcState = 2
adcStateNormal adcState = 3
adcStateData adcState = 4
)
type AdcProtocol struct {
hc *HubConnection
state adcState
sid, pid, cid string // all in base32 encoding
supports map[string]struct{}
}
const (
// extra extensions that aren't flagged in SUPPORTS
adcSeparateApVe string = "SEPARATE_AP_VE" // we invented this string
)
func NewAdcProtocol(hc *HubConnection) Protocol {
proto := AdcProtocol{
hc: hc,
state: adcStateProtocol,
supports: make(map[string]struct{}),
}
rxPid := regexp.MustCompile("^[A-Z2-7]{39}$")
if !rxPid.MatchString(hc.Hco.AdcPID) {
hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Invalid custom PID, regenerating"})
hc.Hco.AdcPID = NewPID()
}
pid_base32 := hc.Hco.AdcPID
cid_base32, err := proto.pid2cid(pid_base32)
if err != nil {
panic(err)
}
proto.cid = cid_base32
proto.pid = pid_base32
// Start logging in
hc.SayRaw("HSUP ADBASE ADTIGR ADUCMD\n")
return &proto
}
func (this *AdcProtocol) pid2cid(pid_base32 string) (string, error) {
pid_raw, err := base32.StdEncoding.DecodeString(pid_base32 + "=")
if err != nil {
return "", err
}
cid_raw := Tiger(string(pid_raw))
cid_base32 := Base32(cid_raw)
return cid_base32, nil
}
func (this *AdcProtocol) SID2Nick(sid string) (string, bool) {
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
nick, ok := this.hc.userSIDs[sid]
return nick, ok
}
func (this *AdcProtocol) Nick2SID(targetNick string) (string, bool) {
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
for sid, nick := range this.hc.userSIDs {
if nick == targetNick {
return sid, true
}
}
return "", false
}
func (this *AdcProtocol) ProcessCommand(msg string) {
if len(msg) == 0 {
return
}
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: msg})
parts := strings.Split(msg, " ")
switch parts[0] {
case "ISUP":
if !(this.state == adcStateProtocol || this.state == adcStateNormal) {
this.malformed(parts)
return
}
for _, supportflag := range parts[1:] {
if len(supportflag) < 2 {
this.malformed(parts)
return
}
if supportflag[0:2] == "AD" {
this.supports[supportflag[2:]] = struct{}{}
} else if supportflag[0:2] == "RM" {
delete(this.supports, supportflag[2:])
} else {
this.malformed(parts)
return
}
}
if this.state == adcStateProtocol {
this.state = adcStateIdentify
}
case "ISID":
if this.state != adcStateIdentify {
this.malformed(parts)
return
}
this.sid = parts[1]
// State transition IDENTIFY --> VERIFY and send our own info
this.SayInfo()
this.state = adcStateVerify
case "IINF":
// Hub telling information about itself
// ADCH++ sends this once we are successfully logged in
flags, err := this.parts2flags(parts[1:])
if err != nil {
this.logError(err)
return
}
if flags["CT"] != "32" {
this.malformed(parts)
return
}
err = this.handleHubInfo(flags)
if err != nil {
this.logError(err)
return
}
if this.state != adcStateNormal {
this.enterNormalState() // successful login
}
case "BINF":
if this.state != adcStateNormal {
this.enterNormalState() // successful login
}
sid := parts[1]
flags, err := this.parts2flags(parts[2:])
if err != nil {
this.logError(err)
return
}
// Log this user in, and associate this SID with this user
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
oldNick, sidExists := this.hc.userSIDs[sid]
uinfo := UserInfo{}
if sidExists {
uinfo_lookup, ok := this.hc.users[oldNick]
if !ok {
// Shouldn't happen
this.hc.processEvent(HubEvent{
EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN,
Message: fmt.Sprintf("Hub connection corrupted (missing info for SID='%s' nick='%s'), disconnecting", sid, oldNick),
})
this.hc.Disconnect()
return
}
uinfo = uinfo_lookup
}
this.updateUserInfo(&uinfo, flags)
newNick := uinfo.Nick
if len(newNick) == 0 {
this.logError(fmt.Errorf("Zero-length nick for user (SID='%s')", sid))
}
shouldHandleNewUser := false
if sidExists && oldNick != newNick {
// Nick change = delete all trace of this user first, treat as new
delete(this.hc.users, oldNick)
delete(this.hc.userSIDs, sid)
this.hc.processEvent(HubEvent{EventType: EVENT_USER_PART, Nick: oldNick})
shouldHandleNewUser = true
} else if sidExists && oldNick == newNick {
// Updating existing user
this.hc.users[newNick] = uinfo
this.hc.processEvent(HubEvent{EventType: EVENT_USER_UPDATED_INFO, Nick: newNick})
} else if !sidExists {
// User joined
shouldHandleNewUser = true
}
//
if shouldHandleNewUser {
// Install this SID as pointing to this nick
this.hc.userSIDs[sid] = uinfo.Nick
// Check if this nick was in use by any other SID already
for otherSid, otherSidNick := range this.hc.userSIDs {
if otherSidNick == newNick && otherSid != sid {
this.hc.processEvent(HubEvent{
EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN,
Message: fmt.Sprintf("Hub connection corrupted (duplicate SIDs '%s' and '%s' for nick '%s'), disconnecting", sid, otherSid, newNick),
})
this.hc.Disconnect()
return
}
}
// Notifications
this.hc.users[newNick] = uinfo
this.hc.processEvent(HubEvent{EventType: EVENT_USER_JOINED, Nick: newNick})
}
case "IMSG":
// General message from the hub
if len(parts) < 2 {
this.malformed(parts)
return
}
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Message: this.unescape(parts[1])})
case "ISTA":
// Error message from the hub
if len(parts) < 3 {
this.malformed(parts)
return
}
code, _ := strconv.Atoi(parts[1])
msg := this.unescape(parts[2])
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Message: this.ErrorMessage(code, msg)})
case "IQUI":
// Error message from the hub
// IQUI V3M6 DI1 MSNick\staken,\splease\spick\sanother\sone TL-1
if len(parts) < 2 {
this.malformed(parts)
return
}
sid := parts[1]
flags, err := this.parts2flags(parts[2:])
if err != nil {
return
}
if sid == this.sid {
if msg, ok := flags["MS"]; ok {
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Message: "The hub is closing our connection because: " + this.unescape(msg)})
} else {
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Message: "The hub is closing our connection"})
}
} else {
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
otherSidNick, ok := this.hc.userSIDs[sid]
if ok {
delete(this.hc.userSIDs, sid)
delete(this.hc.users, otherSidNick)
this.hc.processEvent(HubEvent{EventType: EVENT_USER_PART, Nick: otherSidNick})
} else {
// ??
this.logError(fmt.Errorf("An unknown user quit the hub (SID=%s)", sid))
}
}
case "BMSG":
// Message from a user
// BMSG ZVF4 hi
if len(parts) < 3 {
this.malformed(parts)
return
}
sid := this.unescape(parts[1])
msg := this.unescape(parts[2])
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
nick, ok := this.hc.userSIDs[sid]
if !ok {
this.logError(fmt.Errorf("Recieved message from unknown SID '%s'", sid))
return
}
this.hc.processEvent(HubEvent{EventType: EVENT_PUBLIC, Nick: nick, Message: msg})
case "IGPA":
// Password is needed
// IGPA 7EIAAAECLMAAAPJQAAADQQYAAAWAYAAAKVFQAAF6EAAAAAYFAAAA
// HPAS LZDIJOTZDPWHINHGPT5RHT6WLU7DRME7DQO2O3Q
if len(parts) < 2 {
this.malformed(parts)
return
}
/*
For GPA/PAS, assuming that '12345' is the random data supplied in GPA, then;
PAS = Base32( Hash( password + '12345' ) )
GPA: The data parameter is at least 24 random bytes (base32 encoded).
*/
data_base32 := parts[1]
if len(data_base32)%8 != 0 {
data_base32 += strings.Repeat("=", 8-(len(data_base32)%8))
}
data_raw, err := base32.StdEncoding.DecodeString(data_base32)
if err != nil {
this.logError(err)
return
}
resp := Base32(Tiger(this.hc.Hco.NickPassword + string(data_raw)))
this.hc.SayRaw("HPAS " + resp + "\n")
case "EMSG":
// Private message from other user
// EMSG I5RO FMWH test\spm PMI5RO
// EMSG sender recip==us message [flags...]
if len(parts) < 4 {
this.malformed(parts)
return
}
if parts[2] != this.sid {
this.logError(fmt.Errorf("Recieved a PM intended for someone else (got SID=%s expected SID=%s)", parts[2], this.sid))
return
}
senderSid := parts[1]
senderNick, ok := this.SID2Nick(parts[1])
if !ok {
this.logError(fmt.Errorf("Recieved a PM from an unknown user (SID=%s)", senderSid))
return
}
msg := this.unescape(parts[3])
this.hc.processEvent(HubEvent{EventType: EVENT_PRIVATE, Nick: senderNick, Message: msg})
case "ICMD":
// Usercommand
// ICMD ADCH++/About\sthis\shub TTHMSG\s+about\n CT3
if len(parts) < 2 {
this.malformed(parts)
return
}
uc := UserCommand{
Message: this.unescape(parts[1]),
Type: USERCOMMAND_TYPE_RAW, // default
}
flags, err := this.parts2flags(parts[2:])
if err != nil {
this.malformed(parts)
return
}
if ct, ok := flags["CT"]; ok {
ct64, _ := strconv.ParseUint(ct, 10, 64)
uc.Context = UserCommandContext(ct64)
}
if tt, ok := flags["TT"]; ok {
uc.Command = tt
}
if sp, ok := flags["SP"]; ok && sp == "1" {
uc.Type = USERCOMMAND_TYPE_SEPARATOR
}
if co, ok := flags["CO"]; ok && co == "1" {
uc.Type = USERCOMMAND_TYPE_NICKLIMITED // "Constrained" in ADC parlance
}
if rm, ok := flags["RM"]; ok && rm == "1" {
uc.RemoveThis = true
}
this.hc.processEvent(HubEvent{EventType: EVENT_USERCOMMAND, UserCommand: &uc})
// Ignored messages
// ````````````````
case "DCTM": // Client-client ConnectToMe
case "BSCH": // Search
default:
this.malformed(parts)
}
}
func (this *AdcProtocol) infoFlagsFor(u *UserInfo) map[string]string {
parts := map[string]string{
"NI": u.Nick,
"SS": fmt.Sprintf("%d", u.ShareSize),
"SF": fmt.Sprintf("%d", u.SharedFiles),
"US": fmt.Sprintf("%d", u.UploadSpeedBps),
"DS": fmt.Sprintf("%d", u.DownloadSpeedBps),
"SL": fmt.Sprintf("%d", u.Slots),
"HN": fmt.Sprintf("%d", u.HubsUnregistered),
"HR": fmt.Sprintf("%d", u.HubsRegistered),
"HO": fmt.Sprintf("%d", u.HubsOperator),
}
if _, ok := this.supports[adcSeparateApVe]; ok {
parts["AP"] = u.ClientTag
parts["VE"] = u.ClientVersion
} else {
parts["VE"] = fmt.Sprintf("%s %s", u.ClientTag, u.ClientVersion)
}
// Do not send the hub a CT (it decides what type we are)
return parts
}
func (this *AdcProtocol) ourINFO(includePid bool) string {
parts := this.infoFlagsFor(this.hc.Hco.Self)
parts["ID"] = this.cid
if includePid {
parts["PD"] = this.pid
}
ret := ""
for k, v := range parts {
ret += " " + k + this.escape(v)
}
return ret[1:]
}
func (this *AdcProtocol) SayInfo() {
this.hc.SayRaw("BINF " + this.escape(this.sid) + " " + this.ourINFO(true) + "\n")
}
func (this *AdcProtocol) parts2flags(parts []string) (map[string]string, error) {
flags := make(map[string]string, len(parts))
for _, flag := range parts {
if len(flag) < 2 {
return nil, fmt.Errorf("Malformed flag '%s'", flag)
}
flags[flag[0:2]] = this.unescape(flag[2:])
}
return flags, nil
}
func (this *AdcProtocol) handleHubInfo(flags map[string]string) error {
if flags["CT"] != "32" {
return fmt.Errorf("Expected CT==32")
}
// IINF DEADCH++\sTest\shub VE2.12.1\s(r"[unknown]")\sRelease HI1 NIADCH++ APADCH++ CT32
// AP: extension 3.24 "Application and version separation in INF"
// HI:
// Hub properties updated
// Special SUPPORT that is only indicated in IINF
if _, ok := flags["AP"]; ok {
this.supports[adcSeparateApVe] = struct{}{}
}
// Hub's name is in "NI", hub description in "DE"
hubName, ok := flags["NI"]
if ok {
if hubDesc, ok := flags["DE"]; ok && len(hubDesc) > 0 {
hubName += " - " + hubDesc
}
this.hc.HubName = hubName
this.hc.processEvent(HubEvent{EventType: EVENT_HUBNAME_CHANGED, Nick: this.hc.HubName})
}
return nil
}
func (this *AdcProtocol) updateUserInfo(u *UserInfo, flags map[string]string) {
// User MyINFO
// BINF GUPR IDFEARIFD33NTGC4YBEZ3UFQS5R4ZXXTFL2QN2GRY PDZMIFLG5EKZG3BDRRMIJPG7ARNA6KW3JVIH3DF7Q NIivysaur5 SL3 FS3 SS0 SF0 HN1 HR0 HO0 VEEiskaltDC++\s2.2.9 US2621440 KPSHA256/3UPRORG4BLJ4CG6TO6R3G75A67LXOGD437NALQALRWJF6XBOECTA I40.0.0.0 U418301
// BINF GUPR I4172.17.0.1 U418301 IDFEARIFD33NTGC4YBEZ3UFQS5R4ZXXTFL2QN2GRY VEEiskaltDC++\s2.2.9 SF0 NIivysaur5 SL3 HN1 HO0 KPSHA256/3UPRORG4BLJ4CG6TO6R3G75A67LXOGD437NALQALRWJF6XBOECTA HR0 FS3 SS0 US2621440 SUSEGA,ADC0,TCP4,UDP4
// Or maybe only incremental:
// BINF Z3BA HO1
// TODO
for prop, val := range flags {
switch prop {
case "ID":
u.CID = val
case "PD":
// ignore PID - it will only appear if we're talking about our own user
case "NI":
u.Nick = val
case "SL":
u.Slots, _ = strconv.ParseUint(val, 10, 64)
case "SS":
u.ShareSize, _ = strconv.ParseUint(val, 10, 64)
case "SF":
u.SharedFiles, _ = strconv.ParseUint(val, 10, 64)
case "HN":
u.HubsUnregistered, _ = strconv.ParseUint(val, 10, 64)
case "HR":
u.HubsRegistered, _ = strconv.ParseUint(val, 10, 64)
case "HO":
u.HubsOperator, _ = strconv.ParseUint(val, 10, 64)
case "US":
u.UploadSpeedBps, _ = strconv.ParseUint(val, 10, 64)
case "DS":
u.DownloadSpeedBps, _ = strconv.ParseUint(val, 10, 64)
case "KP":
u.Keyprint = val
case "I4":
u.IPv4Address = val
case "I6":
u.IPv6Address = val
case "U4":
u.IPv4UDPPort, _ = strconv.ParseUint(val, 10, 64)
case "U6":
u.IPv6UDPPort, _ = strconv.ParseUint(val, 10, 64)
case "SU":
u.SupportFlags = make(map[string]struct{})
for _, supportFlag := range strings.Split(val, ",") {
u.SupportFlags[supportFlag] = struct{}{}
}
}
}
// VE / AP
AP, hasAP := flags["AP"]
VE, hasVE := flags["VE"]
if hasAP && hasVE {
u.ClientTag = AP
u.ClientVersion = VE
} else if hasAP && !hasVE {
u.ClientTag, u.ClientVersion = this.getAPVEFromSingle(AP)
} else if !hasAP && hasVE {
u.ClientTag, u.ClientVersion = this.getAPVEFromSingle(VE)
}
}
func (this *AdcProtocol) getAPVEFromSingle(term string) (string, string) {
words := strings.Split(term, " ")
if len(words) > 1 {
return strings.Join(words[0:len(words)-1], " "), words[len(words)-1]
} else {
return term, "0"
}
}
func (this *AdcProtocol) enterNormalState() {
this.state = adcStateNormal
this.hc.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTED})
this.hc.State = CONNECTIONSTATE_CONNECTED
}
func (this *AdcProtocol) malformed(parts []string) {
this.logError(fmt.Errorf("Ignoring malformed, unhandled, or out-of-state protocol command %v", parts))
}
func (this *AdcProtocol) logError(e error) {
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Protocol error: " + e.Error()})
}
func (this *AdcProtocol) escape(plaintext string) string {
// The string "\s" escapes space, "\n" newline and "\\" backslash. This version of the protocol reserves all other escapes for future use; any message containing unknown escapes must be discarded.
v1 := strings.Replace(plaintext, `\`, `\\`, -1)
v2 := strings.Replace(v1, "\n", `\n`, -1)
return strings.Replace(v2, " ", `\s`, -1)
}
func (this *AdcProtocol) unescape(encoded string) string {
v1 := strings.Replace(encoded, `\s`, " ", -1)
v2 := strings.Replace(v1, `\n`, "\n", -1)
return strings.Replace(v2, `\\`, `\`, -1)
}
func (this *AdcProtocol) SayPublic(msg string) {
this.hc.SayRaw("BMSG " + this.sid + " " + this.escape(msg) + "\n")
}
func (this *AdcProtocol) SayPrivate(user, message string) {
if sid, ok := this.Nick2SID(user); ok {
this.hc.SayRaw("DMSG " + this.sid + " " + sid + " " + this.escape(message) + "\n")
} else {
this.logError(fmt.Errorf("Unknown user '%s'", user))
}
}
func (this *AdcProtocol) ProtoMessageSeparator() string {
return "\n"
}
func (this *AdcProtocol) ErrorMessage(code int, msg string) string {
severity := code / 100
category := (code % 100) / 10
cat_sub := (code % 100)
formatSeverity := func(severity int) string {
switch severity {
case 0:
return "OK"
case 1:
return "Warning"
case 2:
return "Error"
default:
return ""
}
}
formatCategory := func(category int) string {
switch category {
case 0:
return ""
case 1:
return "Hub not accepting users"
case 2:
return "Login failed"
case 3:
return "Access denied"
case 4:
return "Protocol error"
case 5:
return "Transfer error"
default:
return ""
}
}
formatCatSub := func(cat_sub int) string {
switch cat_sub {
case 11:
return "Hub is full"
case 12:
return "Hub is disabled"
case 21:
return "Invalid nick"
case 22:
return "Nick is already in use"
case 23:
return "Invalid password"
case 24:
return "CID already connected"
case 25:
return "Access denied"
case 26:
return "Registered users only"
case 27:
return "Invalid PID"
case 31:
return "Permanently banned"
case 32:
return "Temporarily banned"
default:
return ""
}
}
parts := make([]string, 0, 4)
if fs := formatSeverity(severity); len(fs) > 0 {
parts = append(parts, fs)
}
if fc := formatCategory(category); len(fc) > 0 {
parts = append(parts, fc)
}
if fcs := formatCatSub(cat_sub); len(fcs) > 0 {
parts = append(parts, fcs)
}
if len(msg) > 0 {
parts = append(parts, msg)
}
return strings.Join(parts, ": ") + fmt.Sprintf(" (code %d)", code)
}

90
vendor/code.ivysaur.me/libnmdc/AutodetectProtocol.go generated vendored Normal file
View File

@ -0,0 +1,90 @@
package libnmdc
import (
"sync"
"time"
)
type AutodetectProtocol struct {
hc *HubConnection
realProtoMut sync.Mutex
realProto Protocol
}
func NewAutodetectProtocol(hc *HubConnection) Protocol {
proto := AutodetectProtocol{
hc: hc,
realProto: nil,
}
go proto.timeout()
return &proto
}
func (this *AutodetectProtocol) timeout() {
time.Sleep(AUTODETECT_ADC_NMDC_TIMEOUT)
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
this.realProto = NewAdcProtocol(this.hc)
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Detected ADC protocol"})
}
}
func (this *AutodetectProtocol) ProcessCommand(msg string) {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
// We actually got some data using $ as the separator?
// Upgrade to a full NMDC protocol
this.realProto = NewNmdcProtocol(this.hc)
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Detected NMDC protocol"})
}
this.realProto.ProcessCommand(msg)
}
func (this *AutodetectProtocol) SayPublic(msg string) {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
this.realProto = NewNmdcProtocol(this.hc)
}
this.realProto.SayPublic(msg)
}
func (this *AutodetectProtocol) SayPrivate(user, message string) {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
this.realProto = NewNmdcProtocol(this.hc)
}
this.realProto.SayPrivate(user, message)
}
func (this *AutodetectProtocol) SayInfo() {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
this.realProto = NewNmdcProtocol(this.hc)
}
this.realProto.SayInfo()
}
func (this *AutodetectProtocol) ProtoMessageSeparator() string {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
return "|"
}
return this.realProto.ProtoMessageSeparator()
}

29
vendor/code.ivysaur.me/libnmdc/ConnectionMode.go generated vendored Normal file
View File

@ -0,0 +1,29 @@
package libnmdc
import (
"fmt"
)
type ConnectionMode rune
const (
CONNECTIONMODE_ACTIVE ConnectionMode = 'A' // 65
CONNECTIONMODE_PASSIVE ConnectionMode = 'P' // 49
CONNECTIONMODE_SOCKS5 ConnectionMode = '5' // 53
)
func (this ConnectionMode) String() string {
switch this {
case CONNECTIONMODE_ACTIVE:
return "Active"
case CONNECTIONMODE_PASSIVE:
return "Passive"
case CONNECTIONMODE_SOCKS5:
return "SOCKS5"
default:
return fmt.Sprintf("ConnectionMode(\"%s\")", string(this))
}
}

40
vendor/code.ivysaur.me/libnmdc/ConnectionState.go generated vendored Normal file
View File

@ -0,0 +1,40 @@
package libnmdc
import (
"net"
)
type ConnectionState int
const (
CONNECTIONSTATE_DISCONNECTED = 1
CONNECTIONSTATE_CONNECTING = 2 // Handshake in progress
CONNECTIONSTATE_CONNECTED = 3
)
func (cs ConnectionState) String() string {
switch cs {
case CONNECTIONSTATE_DISCONNECTED:
return "Disconnected"
case CONNECTIONSTATE_CONNECTING:
return "Connecting"
case CONNECTIONSTATE_CONNECTED:
return "Connected"
default:
return "?"
}
}
func checkIsNetTimeout(err error) bool {
if err == nil {
return false
}
switch err.(type) {
case net.Error:
return err.(net.Error).Timeout()
default:
return false
}
}

15
vendor/code.ivysaur.me/libnmdc/Gopkg.lock generated vendored Normal file
View File

@ -0,0 +1,15 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/cxmcc/tiger"
packages = ["."]
revision = "bde35e2713d7f674987c2ecb21a6b0fc33749516"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "c88ee670a5600b482019325b6d6633bb6b5fe789596dc29ef809aa7bb013927b"
solver-name = "gps-cdcl"
solver-version = 1

26
vendor/code.ivysaur.me/libnmdc/Gopkg.toml generated vendored Normal file
View File

@ -0,0 +1,26 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
branch = "master"
name = "github.com/cxmcc/tiger"

48
vendor/code.ivysaur.me/libnmdc/HubAddress.go generated vendored Normal file
View File

@ -0,0 +1,48 @@
package libnmdc
import (
"net/url"
"strings"
)
type HubAddress string
func (this *HubAddress) parse() url.URL {
parsed, err := url.Parse(strings.ToLower(string(*this)))
if err != nil || len(parsed.Host) == 0 {
parsed = &url.URL{
Scheme: "",
Host: string(*this),
}
}
// Add default port if not specified
if !strings.ContainsRune(parsed.Host, ':') {
parsed.Host = parsed.Host + ":411"
}
return *parsed
}
func (this *HubAddress) IsSecure() bool {
parsed := this.parse()
return parsed.Scheme == "nmdcs" || parsed.Scheme == "dchubs" || parsed.Scheme == "adcs"
}
func (this *HubAddress) GetHostOnly() string {
return this.parse().Host
}
func (this *HubAddress) GetProtocol() HubProtocol {
parsed := this.parse()
switch parsed.Scheme {
case "nmdc", "dchub", "nmdcs", "dchubs":
return HubProtocolNmdc
case "adc", "adcs":
return HubProtocolAdc
default:
return HubProtocolAutodetect
}
}

233
vendor/code.ivysaur.me/libnmdc/HubConnection.go generated vendored Normal file
View File

@ -0,0 +1,233 @@
package libnmdc
import (
"crypto/tls"
"fmt"
"net"
"regexp"
"sync"
"time"
)
type HubConnection struct {
// Supplied parameters
Hco *HubConnectionOptions
// Current remote status
HubName string
State ConnectionState
usersMut sync.RWMutex
users map[string]UserInfo
userSIDs map[string]string
proto Protocol
// Event callback
processEvent func(HubEvent)
// Private state
conn net.Conn // this is an interface
connValid bool
autoReconnect bool
lastDataRecieved time.Time
}
// Thread-safe user accessor.
func (this *HubConnection) Users(cb func(*map[string]UserInfo) error) error {
this.usersMut.Lock()
defer this.usersMut.Unlock()
return cb(&this.users)
}
func (this *HubConnection) SayPublic(message string) {
this.proto.SayPublic(message)
}
func (this *HubConnection) SayPrivate(recipient string, message string) {
this.proto.SayPrivate(recipient, message)
}
func (this *HubConnection) SayInfo() {
this.proto.SayInfo()
}
func (this *HubConnection) UserExists(nick string) bool {
this.usersMut.RLock()
defer this.usersMut.RUnlock()
_, already_existed := this.users[nick]
return already_existed
}
func (this *HubConnection) UserCount() int {
this.usersMut.RLock()
defer this.usersMut.RUnlock()
return len(this.users)
}
func (this *HubConnection) userJoined_NameOnly(nick string) {
if !this.UserExists(nick) {
this.usersMut.Lock()
this.users[nick] = *NewUserInfo(nick)
this.usersMut.Unlock() // Don't lock over a processEvent boundary
this.processEvent(HubEvent{EventType: EVENT_USER_JOINED, Nick: nick})
}
}
func (this *HubConnection) userJoined_Full(uinf *UserInfo) {
// n.b. also called when we get a replacement MyINFO for someone
this.usersMut.Lock()
_, userExisted := this.users[uinf.Nick] // don't use UserExists as it would deadlock the mutex
this.users[uinf.Nick] = *uinf
this.usersMut.Unlock() // Don't lock over a processEvent boundary
if !userExisted {
this.processEvent(HubEvent{EventType: EVENT_USER_JOINED, Nick: uinf.Nick})
} else {
this.processEvent(HubEvent{EventType: EVENT_USER_UPDATED_INFO, Nick: uinf.Nick})
}
}
// SayRaw sends raw bytes over the TCP socket. Callers should add the protocol
// terminating character themselves (e.g. `|` for NMDC).
// Note that protocol messages are transmitted on the caller thread, not from
// any internal libnmdc thread.
func (this *HubConnection) SayRaw(protocolCommand string) error {
if !this.connValid {
return ErrNotConnected
}
_, err := this.conn.Write([]byte(protocolCommand))
return err
}
func (this *HubConnection) SayKeepalive() error {
if !this.connValid {
return ErrNotConnected
}
return this.SayRaw(this.proto.ProtoMessageSeparator())
}
func (this *HubConnection) Disconnect() {
this.autoReconnect = false
if this.conn != nil {
this.conn.Close()
}
// A CONNECTIONSTATE_DISCONNECTED message will be emitted by the worker.
}
func (this *HubConnection) worker() {
var fullBuffer string
var err error = nil
var nbytes int = 0
for {
// If we're not connected, attempt reconnect
if this.conn == nil {
fullBuffer = "" // clear
if this.Hco.Address.IsSecure() {
this.conn, err = tls.Dial("tcp", this.Hco.Address.GetHostOnly(), &tls.Config{
InsecureSkipVerify: this.Hco.SkipVerifyTLS,
})
} else {
this.conn, err = net.Dial("tcp", this.Hco.Address.GetHostOnly())
}
if err != nil {
this.State = CONNECTIONSTATE_DISCONNECTED
this.connValid = false
this.proto = nil
} else {
this.State = CONNECTIONSTATE_CONNECTING
this.connValid = true
this.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTING})
this.proto = this.Hco.Address.GetProtocol().Create(this)
}
}
// Read from socket into our local buffer (blocking)
if this.connValid {
readBuff := make([]byte, 1024)
this.conn.SetReadDeadline(time.Now().Add(SEND_KEEPALIVE_EVERY))
nbytes, err = this.conn.Read(readBuff)
if checkIsNetTimeout(err) {
// No data before read deadline
err = nil
if this.proto == nil {
// Autodetect: switch to ADC
this.proto = NewAdcProtocol(this)
} else {
// Normal
// Send KA packet
err = this.SayKeepalive()
}
}
if nbytes > 0 {
this.lastDataRecieved = time.Now()
fullBuffer += string(readBuff[0:nbytes])
}
}
if this.proto != nil {
rxSeparator := regexp.QuoteMeta(this.proto.ProtoMessageSeparator())
rxProtocolMessage := regexp.MustCompile(`(?ms)\A[^` + rxSeparator + `]*` + rxSeparator)
// Attempt to parse a message block
for len(fullBuffer) > 0 {
// FIXME nmdc
for len(fullBuffer) > 0 && fullBuffer[0] == '|' {
fullBuffer = fullBuffer[1:]
}
protocolMessage := rxProtocolMessage.FindString(fullBuffer)
if len(protocolMessage) > 0 {
this.proto.ProcessCommand(protocolMessage[:len(protocolMessage)-1])
fullBuffer = fullBuffer[len(protocolMessage):]
} else {
break
}
}
if err == nil && time.Now().Sub(this.lastDataRecieved) > RECONNECT_IF_NO_DATA_RECIEVED_IN {
err = fmt.Errorf("No packets recieved since %s, connection presumed lost", this.lastDataRecieved.Format(time.RFC3339))
}
}
// Maybe we disconnected
// Perform this check *last*, to ensure we've had a final shot at
// clearing out any queued messages
if err != nil {
this.State = CONNECTIONSTATE_DISCONNECTED
this.conn = nil
this.connValid = false
this.proto = nil
this.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_DISCONNECTED, Message: err.Error()})
if this.autoReconnect {
time.Sleep(AUTO_RECONNECT_AFTER) // Wait before reconnect
continue
} else {
return // leave the worker for good
}
}
}
}

79
vendor/code.ivysaur.me/libnmdc/HubConnectionOptions.go generated vendored Normal file
View File

@ -0,0 +1,79 @@
package libnmdc
import (
"crypto/rand"
)
type HubConnectionOptions struct {
Address HubAddress
SkipVerifyTLS bool // using a negative verb, because bools default to false
SkipAutoReconnect bool // as above
Self *UserInfo
NickPassword string
AdcPID string // blank: autogenerate
}
func NewPID() string {
pidBytes := make([]byte, 24)
n, err := rand.Read(pidBytes)
if err != nil {
panic(err) // Insufficient cryptographic randomness
}
if n != 24 {
panic("Insufficient cryptographic randomness")
}
return Base32(pidBytes)
}
func (this *HubConnectionOptions) prepareConnection() *HubConnection {
if this.Self.ClientTag == "" {
this.Self.ClientTag = DEFAULT_CLIENT_TAG
this.Self.ClientVersion = DEFAULT_CLIENT_VERSION
}
// Shouldn't be blank either
if this.Self.ClientVersion == "" {
this.Self.ClientVersion = "0"
}
if this.AdcPID == "" {
this.AdcPID = NewPID()
}
hc := HubConnection{
Hco: this,
HubName: DEFAULT_HUB_NAME,
State: CONNECTIONSTATE_DISCONNECTED,
users: make(map[string]UserInfo),
userSIDs: make(map[string]string),
autoReconnect: !this.SkipAutoReconnect,
}
return &hc
}
// ConnectAsync connects to a hub server, and spawns a background goroutine to handle
// protocol messages. Events will be sent by channel to the supplied onEvent channel,
// the client is responsible for selecting off this.
func ConnectAsync(opts *HubConnectionOptions, onEvent chan HubEvent) *HubConnection {
hc := opts.prepareConnection()
hc.processEvent = func(ev HubEvent) {
onEvent <- ev
}
go hc.worker()
return hc
}
// ConnectSync connects to a hub server, and blocks forever to handle protocol messages.
// Client code should supply an event handling function as hco.OnEventSync.
func ConnectSync(opts *HubConnectionOptions, onEvent func(hub *HubConnection, ev HubEvent)) {
hc := opts.prepareConnection()
hc.processEvent = func(ev HubEvent) {
onEvent(hc, ev)
}
hc.worker()
}

25
vendor/code.ivysaur.me/libnmdc/HubEvent.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
package libnmdc
type HubEventType int
const (
EVENT_PUBLIC HubEventType = 1
EVENT_PRIVATE HubEventType = 2
EVENT_SYSTEM_MESSAGE_FROM_HUB HubEventType = 3
EVENT_SYSTEM_MESSAGE_FROM_CONN HubEventType = 4
EVENT_USER_JOINED HubEventType = 5
EVENT_USER_PART HubEventType = 6
EVENT_USER_UPDATED_INFO HubEventType = 7
EVENT_CONNECTION_STATE_CHANGED HubEventType = 8
EVENT_HUBNAME_CHANGED HubEventType = 9
EVENT_DEBUG_MESSAGE HubEventType = 10
EVENT_USERCOMMAND HubEventType = 11
)
type HubEvent struct {
EventType HubEventType
Nick string
Message string
StateChange ConnectionState
UserCommand *UserCommand
}

407
vendor/code.ivysaur.me/libnmdc/NmdcProtocol.go generated vendored Normal file
View File

@ -0,0 +1,407 @@
package libnmdc
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
)
type NmdcProtocol struct {
hc *HubConnection
sentOurHello bool
supports map[string]struct{}
rxPublicChat *regexp.Regexp
rxIncomingTo *regexp.Regexp
rxUserCommand *regexp.Regexp
rxMyInfo *regexp.Regexp
rxMyInfoNoTag *regexp.Regexp
}
func NewNmdcProtocol(hc *HubConnection) Protocol {
proto := NmdcProtocol{}
proto.hc = hc
// With the `m` flag, use \A instead of ^ to anchor to start
// This fixes accidentally finding a better match in the middle of a multi-line message
proto.rxPublicChat = regexp.MustCompile(`(?ms)\A<([^>]*)> (.*)$`)
proto.rxIncomingTo = regexp.MustCompile(`(?ms)\A([^ ]+) From: ([^ ]+) \$<([^>]*)> (.*)`)
proto.rxUserCommand = regexp.MustCompile(`(?ms)\A(\d+) (\d+)\s?([^\$]*)\$?(.*)`)
// Format: $ALL <nick> <description>$ $<connection><flag>$<e-mail>$<sharesize>$
HEAD := `(?ms)^\$ALL ([^ ]+) `
FOOT := `\$.\$([^$]+)\$([^$]*)\$([0-9]*)\$$`
proto.rxMyInfo = regexp.MustCompile(HEAD + `([^<]*)<(.+?) V:([^,]+),M:(.),H:([0-9]+)/([0-9]+)/([0-9]+),S:([0-9]+)>` + FOOT)
proto.rxMyInfoNoTag = regexp.MustCompile(HEAD + `([^$]*)` + FOOT) // Fallback for no tag
// Done
return &proto
}
func (this *NmdcProtocol) ProcessCommand(message string) {
// Zero-length protocol message
// ````````````````````````````
if len(message) == 0 {
return
}
// Public chat
// ```````````
if this.rxPublicChat.MatchString(message) {
pubchat_parts := this.rxPublicChat.FindStringSubmatch(message)
this.hc.processEvent(HubEvent{EventType: EVENT_PUBLIC, Nick: pubchat_parts[1], Message: this.unescape(pubchat_parts[2])})
return
}
// System messages
// ```````````````
if message[0] != '$' {
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Nick: this.hc.HubName, Message: this.unescape(message)})
return
}
// Protocol messages
// `````````````````
commandParts := strings.SplitN(message, " ", 2)
switch commandParts[0] {
case "$Lock":
this.hc.SayRaw("$Supports NoHello NoGetINFO UserCommand UserIP2 QuickList ChatOnly|" +
"$Key " + this.unlock([]byte(commandParts[1])) + "|")
this.sentOurHello = false
case "$Hello":
if commandParts[1] == this.hc.Hco.Self.Nick && !this.sentOurHello {
this.hc.SayRaw("$Version 1,0091|")
this.hc.SayRaw("$GetNickList|")
this.SayInfo()
this.sentOurHello = true
} else {
this.hc.userJoined_NameOnly(commandParts[1])
}
case "$HubName":
this.hc.HubName = commandParts[1]
this.hc.processEvent(HubEvent{EventType: EVENT_HUBNAME_CHANGED, Nick: commandParts[1]})
case "$ValidateDenide": // sic
if len(this.hc.Hco.NickPassword) > 0 {
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Incorrect password."})
} else {
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Nick already in use."})
}
case "$HubIsFull":
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Hub is full."})
case "$BadPass":
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Incorrect password."})
case "$GetPass":
if len(this.hc.Hco.NickPassword) == 0 {
// We've got a problem. MyPass with no arguments is a syntax error with no message = instant close
// Just drop the connection
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "This account is passworded."})
this.hc.Disconnect()
} else {
this.hc.SayRaw("$MyPass " + this.escape(this.hc.Hco.NickPassword) + "|")
}
case "$Quit":
this.hc.usersMut.Lock()
delete(this.hc.users, commandParts[1])
this.hc.usersMut.Unlock() // Don't lock over a processEvent boundary
this.hc.processEvent(HubEvent{EventType: EVENT_USER_PART, Nick: commandParts[1]})
case "$MyINFO":
u, err := this.parseMyINFO(commandParts[1])
if err != nil {
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: err.Error()})
return
}
this.hc.userJoined_Full(u)
case "$NickList":
nicklist := strings.Split(commandParts[1], "$$")
for _, nick := range nicklist {
if len(nick) > 0 {
this.hc.userJoined_NameOnly(nick)
}
}
case "$OpList":
oplist := strings.Split(commandParts[1], "$$")
opmap := map[string]struct{}{}
// Organise/sort the list, and ensure we're not meeting an operator for
// the first time
for _, nick := range oplist {
if len(nick) > 0 {
opmap[nick] = struct{}{}
this.hc.userJoined_NameOnly(nick) // assert existence; noop otherwise
}
}
// Mark all mentioned nicks as being operators, and all unmentioned nicks
// as being /not/ an operator. (second pass minimises RW mutex use)
func() {
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
for nick, userinfo := range this.hc.users {
_, isop := opmap[nick]
userinfo.IsOperator = isop
this.hc.users[nick] = userinfo
}
}()
case "$To:":
valid := false
if this.rxIncomingTo.MatchString(commandParts[1]) {
txparts := this.rxIncomingTo.FindStringSubmatch(commandParts[1])
if txparts[1] == this.hc.Hco.Self.Nick && txparts[2] == txparts[3] {
this.hc.processEvent(HubEvent{EventType: EVENT_PRIVATE, Nick: txparts[2], Message: this.unescape(txparts[4])})
valid = true
}
}
if !valid {
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Malformed private message '" + commandParts[1] + "'"})
}
case "$UserIP":
this.hc.usersMut.Lock()
pairs := strings.Split(commandParts[1], "$$")
notifyOfUpdate := make([]string, 0, len(pairs))
nextIPPair:
for _, pair := range pairs {
parts := strings.SplitN(pair, " ", 2)
if len(parts) != 2 {
// ????
continue nextIPPair
}
ip2nick := parts[0]
ip2addr := parts[1]
uinfo, ok := this.hc.users[ip2nick]
if !ok {
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Recieved IP '" + ip2addr + "' for unknown user '" + ip2nick + "'"})
continue nextIPPair
}
if uinfo.IPAddress != ip2addr {
uinfo.IPAddress = ip2addr
notifyOfUpdate = append(notifyOfUpdate, ip2nick)
this.hc.users[ip2nick] = uinfo
}
}
this.hc.usersMut.Unlock()
for _, nick := range notifyOfUpdate {
this.hc.processEvent(HubEvent{EventType: EVENT_USER_UPDATED_INFO, Nick: nick})
}
case "$ForceMove":
this.hc.Hco.Address = HubAddress(commandParts[1])
this.hc.conn.Close() // we'll reconnect onto the new address
case "$UserCommand":
// $UserCommand 1 1 Group chat\New group chat$<%[mynick]> !groupchat_new&#124;|
if this.rxUserCommand.MatchString(commandParts[1]) {
usc := this.rxUserCommand.FindStringSubmatch(commandParts[1])
typeInt, _ := strconv.Atoi(usc[1])
contextInt, _ := strconv.Atoi(usc[2])
uscStruct := UserCommand{
Type: UserCommandType(typeInt),
Context: UserCommandContext(contextInt),
Message: usc[3],
Command: this.unescape(usc[4]),
}
this.hc.processEvent(HubEvent{EventType: EVENT_USERCOMMAND, UserCommand: &uscStruct})
} else {
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Malformed usercommand '" + commandParts[1] + "'"})
}
case "$Supports":
this.supports = make(map[string]struct{})
for _, s := range strings.Split(commandParts[1], " ") {
this.supports[s] = struct{}{}
}
if !this.sentOurHello {
// Need to log in.
// If the hub supports QuickList, we can skip one network roundtrip
if _, ok := this.supports["QuickList"]; ok {
this.SayInfo()
this.hc.SayRaw("$GetNickList|")
} else {
this.hc.SayRaw("$ValidateNick " + this.escape(this.hc.Hco.Self.Nick) + "|")
}
// This also counts as the end of the handshake from our POV. Consider
// ourselves logged in
this.sentOurHello = true
if this.hc.State != CONNECTIONSTATE_CONNECTED {
this.hc.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTED})
this.hc.State = CONNECTIONSTATE_CONNECTED
}
}
// IGNORABLE COMMANDS
case "$HubTopic":
case "$Search":
case "$ConnectToMe":
default:
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Unhandled protocol command '" + commandParts[0] + "'"})
}
}
func (this *NmdcProtocol) escape(plaintext string) string {
v1 := strings.Replace(plaintext, "&", "&amp;", -1)
v2 := strings.Replace(v1, "|", "&#124;", -1)
return strings.Replace(v2, "$", "&#36;", -1)
}
func (this *NmdcProtocol) unescape(encoded string) string {
v1 := strings.Replace(encoded, "&#36;", "$", -1)
v2 := strings.Replace(v1, "&#124;", "|", -1)
return strings.Replace(v2, "&amp;", "&", -1)
}
func (this *NmdcProtocol) SayPublic(message string) {
this.hc.SayRaw("<" + this.hc.Hco.Self.Nick + "> " + this.escape(message) + "|")
}
func (this *NmdcProtocol) SayPrivate(recipient, message string) {
this.hc.SayRaw("$To: " + recipient + " From: " + this.hc.Hco.Self.Nick + " $<" + this.hc.Hco.Self.Nick + "> " + this.escape(message) + "|")
}
func (this *NmdcProtocol) SayInfo() {
this.hc.SayRaw(this.getUserMyINFO(this.hc.Hco.Self) + "|")
}
func (this *NmdcProtocol) parseMyINFO(protomsg string) (*UserInfo, error) {
ret := UserInfo{}
// Normal format (with tag in exact V/M/H/S order)
matches := this.rxMyInfo.FindStringSubmatch(protomsg)
if matches != nil {
ret.Nick = matches[1]
ret.Description = this.unescape(matches[2])
ret.ClientTag = this.unescape(matches[3])
ret.ClientVersion = matches[4]
ret.ConnectionMode = ConnectionMode(matches[5][0])
maybeParse(matches[6], &ret.HubsUnregistered, 0)
maybeParse(matches[7], &ret.HubsRegistered, 0)
maybeParse(matches[8], &ret.HubsOperator, 0)
maybeParse(matches[9], &ret.Slots, 0)
if len(matches[10]) > 1 {
ret.Speed = matches[10][:len(matches[10])-2]
} else {
ret.Speed = ""
}
ret.Flag = UserFlag(matches[10][len(matches[10])-1])
ret.Email = this.unescape(matches[11])
maybeParse(matches[12], &ret.ShareSize, 0)
return &ret, nil
}
// No-tag format, used in early connection
matches = this.rxMyInfoNoTag.FindStringSubmatch(protomsg)
if matches != nil {
ret.Nick = matches[1]
ret.Description = this.unescape(matches[2])
ret.ClientTag = ""
ret.ClientVersion = "0"
ret.ConnectionMode = CONNECTIONMODE_PASSIVE
ret.HubsUnregistered = 0
ret.HubsRegistered = 0
ret.HubsOperator = 0
ret.Slots = 0
if len(matches[3]) > 1 {
ret.Speed = matches[3][:len(matches[3])-2]
} else {
ret.Speed = ""
}
ret.Flag = UserFlag(matches[3][len(matches[3])-1])
ret.Email = this.unescape(matches[4])
maybeParse(matches[5], &ret.ShareSize, 0)
return &ret, nil
}
// Couldn't get anything out of it...
return nil, errors.New("Malformed MyINFO")
}
// Returns the MyINFO command, WITH leading $MyINFO, and WITHOUT trailing pipe
func (this *NmdcProtocol) getUserMyINFO(u *UserInfo) string {
return fmt.Sprintf(
"$MyINFO $ALL %s %s<%s V:%s,M:%c,H:%d/%d/%d,S:%d>$ $%s%c$%s$%d$",
u.Nick,
u.Description,
u.ClientTag,
strings.Replace(u.ClientVersion, ",", "-", -1), // just in case
u.ConnectionMode,
u.HubsUnregistered,
u.HubsRegistered,
u.HubsOperator,
u.Slots,
u.Speed,
u.Flag,
u.Email,
u.ShareSize,
)
}
func (this *NmdcProtocol) ProtoMessageSeparator() string {
return "|"
}
func (this *NmdcProtocol) unlock(lock []byte) string {
nibble_swap := func(b byte) byte {
return ((b << 4) & 0xF0) | ((b >> 4) & 0x0F)
}
chr := func(b byte) string {
if b == 0 || b == 5 || b == 36 || b == 96 || b == 124 || b == 126 {
return fmt.Sprintf("/%%DCN%04d%%/", b)
} else {
return string(b)
}
}
key := chr(nibble_swap(lock[0] ^ lock[len(lock)-2] ^ lock[len(lock)-3] ^ 5))
for i := 1; i < len(lock); i += 1 {
key += chr(nibble_swap(lock[i] ^ lock[i-1]))
}
return key
}

33
vendor/code.ivysaur.me/libnmdc/Protocol.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
package libnmdc
type Protocol interface {
ProcessCommand(msg string)
SayPublic(string)
SayPrivate(user, message string)
SayInfo()
ProtoMessageSeparator() string
}
type HubProtocol int
const (
HubProtocolAutodetect HubProtocol = 0
HubProtocolNmdc HubProtocol = 1
HubProtocolAdc HubProtocol = 2
)
func (hp HubProtocol) Create(hc *HubConnection) Protocol {
if hp == HubProtocolNmdc {
return NewNmdcProtocol(hc)
} else if hp == HubProtocolAdc {
return NewAdcProtocol(hc)
} else {
return NewAutodetectProtocol(hc)
}
}

6
vendor/code.ivysaur.me/libnmdc/TODO.txt generated vendored Normal file
View File

@ -0,0 +1,6 @@
NMDC:
- Implement ZPipe ($ZOn)
ADC:
- Usercommands
- ???

27
vendor/code.ivysaur.me/libnmdc/UserCommand.go generated vendored Normal file
View File

@ -0,0 +1,27 @@
package libnmdc
type UserCommandType uint8
const (
USERCOMMAND_TYPE_SEPARATOR UserCommandType = 0
USERCOMMAND_TYPE_RAW UserCommandType = 1
USERCOMMAND_TYPE_NICKLIMITED UserCommandType = 2
USERCOMMAND_TYPE_CLEARALL UserCommandType = 255
)
type UserCommandContext uint8
const (
USERCOMMAND_CONTEXT_HUB UserCommandContext = 1
USERCOMMAND_CONTEXT_USER UserCommandContext = 2
USERCOMMAND_CONTEXT_SEARCH UserCommandContext = 4
USERCOMMAND_CONTEXT_FILELIST UserCommandContext = 8
)
type UserCommand struct {
Type UserCommandType
Context UserCommandContext
Message string
Command string
RemoveThis bool // Currently only set by ADC hubs
}

17
vendor/code.ivysaur.me/libnmdc/UserFlag.go generated vendored Normal file
View File

@ -0,0 +1,17 @@
package libnmdc
type UserFlag byte
const (
FLAG_NORMAL UserFlag = 1
FLAG_AWAY_1 UserFlag = 2
FLAG_AWAY_2 UserFlag = 3
FLAG_SERVER_1 UserFlag = 4
FLAG_SERVER_2 UserFlag = 5
FLAG_SERVER_AWAY_1 UserFlag = 6
FLAG_SERVER_AWAY_2 UserFlag = 7
FLAG_FIREBALL_1 UserFlag = 8
FLAG_FIREBALL_2 UserFlag = 9
FLAG_FIREBALL_AWAY_1 UserFlag = 10
FLAG_FIREBALL_AWAY_2 UserFlag = 11
)

53
vendor/code.ivysaur.me/libnmdc/UserInfo.go generated vendored Normal file
View File

@ -0,0 +1,53 @@
package libnmdc
// This structure represents a user connected to a hub.
type UserInfo struct {
Nick string
Description string
ClientTag string
ClientVersion string
Email string
ShareSize uint64
Flag UserFlag
Slots uint64
HubsUnregistered uint64
HubsRegistered uint64
HubsOperator uint64
IsOperator bool
UserInfo_NMDCOnly
UserInfo_ADCOnly
}
type UserInfo_NMDCOnly struct {
Speed string
IPAddress string
ConnectionMode ConnectionMode
}
type UserInfo_ADCOnly struct {
SharedFiles uint64
UploadSpeedBps uint64
DownloadSpeedBps uint64
IsBot bool
IsRegistered bool
IsSuperUser bool
IsHubOwner bool
IPv4Address string // Passive <==> these fields are not set
IPv6Address string
IPv4UDPPort uint64
IPv6UDPPort uint64
Keyprint string
CID string
SupportFlags map[string]struct{}
}
func NewUserInfo(username string) *UserInfo {
return &UserInfo{
Nick: username,
HubsUnregistered: 1,
UserInfo_NMDCOnly: UserInfo_NMDCOnly{
ConnectionMode: CONNECTIONMODE_PASSIVE,
},
}
}

28
vendor/code.ivysaur.me/libnmdc/libnmdc.go generated vendored Normal file
View File

@ -0,0 +1,28 @@
package libnmdc
import (
"errors"
"strconv"
"time"
)
const (
DEFAULT_CLIENT_TAG string = "libnmdc.go"
DEFAULT_CLIENT_VERSION string = "0.17"
DEFAULT_HUB_NAME string = "(unknown)"
SEND_KEEPALIVE_EVERY time.Duration = 29 * time.Second
AUTO_RECONNECT_AFTER time.Duration = 30 * time.Second
RECONNECT_IF_NO_DATA_RECIEVED_IN time.Duration = 24 * time.Hour // we expect keepalives wayyyy more frequently than this
AUTODETECT_ADC_NMDC_TIMEOUT time.Duration = 3 * time.Second
)
var ErrNotConnected error = errors.New("Not connected")
func maybeParse(str string, dest *uint64, default_val uint64) {
sz, err := strconv.ParseUint(str, 10, 64)
if err == nil {
*dest = sz
} else {
*dest = default_val
}
}

41
vendor/code.ivysaur.me/libnmdc/tth.go generated vendored Normal file
View File

@ -0,0 +1,41 @@
package libnmdc
import (
"encoding/base32"
"errors"
"strings"
"github.com/cxmcc/tiger"
)
// Base32 encodes the input slice in BASE32 string format without any trailing
// padding equals characters.
func Base32(input []byte) string {
return strings.TrimRight(base32.StdEncoding.EncodeToString(input), "=")
}
// TTH returns the TTH hash of a string in raw byte format. Use the Base32()
// function to convert it to normal string format.
// This is a basic implementation that only supports content up to 1024 bytes in length.
func TTH(input string) ([]byte, error) {
// Short segments do not need to be padded.
// Content above 1024 bytes needs tree handling (0x00 prefix for leaf nodes,
// 0x01 prefix for hash nodes) but for content less than 1024 bytes, just
// return the leaf hash
// @ref http://adc.sourceforge.net/draft-jchapweske-thex-02.html
if len(input) > 1024 {
return nil, errors.New("TTH content exceeded 1024 bytes")
}
// Single leaf hash only
leafHash := tiger.New()
leafHash.Write([]byte("\x00" + input))
return leafHash.Sum(nil), nil
}
func Tiger(input string) []byte {
leafHash := tiger.New()
leafHash.Write([]byte(input))
return leafHash.Sum(nil)
}

22
vendor/github.com/cxmcc/tiger/.gitignore generated vendored Normal file
View File

@ -0,0 +1,22 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

7
vendor/github.com/cxmcc/tiger/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,7 @@
language: go
go:
- 1.x
- 1.6
- 1.7.x
- master

20
vendor/github.com/cxmcc/tiger/LICENSE generated vendored Normal file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 Xiuming Chen
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

48
vendor/github.com/cxmcc/tiger/README.md generated vendored Normal file
View File

@ -0,0 +1,48 @@
Tiger cryptographic hash function for Go
-----
[![Build Status](https://travis-ci.org/cxmcc/tiger.svg?branch=master)](https://travis-ci.org/cxmcc/tiger)
[![GoDoc](http://godoc.org/github.com/cxmcc/tiger?status.png)](http://godoc.org/github.com/cxmcc/tiger)
### About Tiger
* Tiger cryptographic hash function is designed by Ross Anderson and Eli Biham in 1995.
* The size of a Tiger hash value is 192 bits. Truncated versions (Tiger/128, Tiger/160) are simply prefixes of Tiger/192.
* Tiger2 is a variant where the message is padded by first appending a byte 0x80, rather than 0x01 as in the case of Tiger.
* Links: [paper](http://www.cs.technion.ac.il/~biham/Reports/Tiger/), [wikipedia](http://en.wikipedia.org/wiki/Tiger_\(cryptography\))
### API Documentation
Implementing [hash.Hash](http://golang.org/pkg/hash/#Hash). Usage is pretty much the same as other stanard hashing libraries.
Documentation currently available at Godoc: [http://godoc.org/github.com/cxmcc/tiger](http://godoc.org/github.com/cxmcc/tiger)
### Installing
~~~
go get github.com/cxmcc/tiger
~~~
### Example
~~~ go
package main
import (
"fmt"
"io"
"github.com/cxmcc/tiger"
)
func main() {
h := tiger.New()
io.WriteString(h, "Example for tiger")
fmt.Printf("Output: %x\n", h.Sum(nil))
// Output: 82bd060e19f945014f0063e8f0e6d7decfa9edfd97e76743
}
~~~
### License
It's MIT License

96
vendor/github.com/cxmcc/tiger/compress.go generated vendored Normal file
View File

@ -0,0 +1,96 @@
package tiger
import (
"encoding/binary"
"unsafe"
)
var littleEndian bool
func init() {
x := uint32(0x04030201)
y := [4]byte{0x1, 0x2, 0x3, 0x4}
littleEndian = *(*[4]byte)(unsafe.Pointer(&x)) == y
}
func pass(a, b, c uint64, x []uint64, mul uint64) (uint64, uint64, uint64) {
a, b, c = round(a, b, c, x[0], mul)
b, c, a = round(b, c, a, x[1], mul)
c, a, b = round(c, a, b, x[2], mul)
a, b, c = round(a, b, c, x[3], mul)
b, c, a = round(b, c, a, x[4], mul)
c, a, b = round(c, a, b, x[5], mul)
a, b, c = round(a, b, c, x[6], mul)
b, c, a = round(b, c, a, x[7], mul)
return a, b, c
}
func round(a, b, c, x, mul uint64) (uint64, uint64, uint64) {
c ^= x
a -= t1[c&0xff] ^ t2[(c>>16)&0xff] ^ t3[(c>>32)&0xff] ^ t4[(c>>48)&0xff]
b += t4[(c>>8)&0xff] ^ t3[(c>>24)&0xff] ^ t2[(c>>40)&0xff] ^ t1[(c>>56)&0xff]
b *= mul
return a, b, c
}
func keySchedule(x []uint64) {
x[0] -= x[7] ^ 0xa5a5a5a5a5a5a5a5
x[1] ^= x[0]
x[2] += x[1]
x[3] -= x[2] ^ ((^x[1]) << 19)
x[4] ^= x[3]
x[5] += x[4]
x[6] -= x[5] ^ ((^x[4]) >> 23)
x[7] ^= x[6]
x[0] += x[7]
x[1] -= x[0] ^ ((^x[7]) << 19)
x[2] ^= x[1]
x[3] += x[2]
x[4] -= x[3] ^ ((^x[2]) >> 23)
x[5] ^= x[4]
x[6] += x[5]
x[7] -= x[6] ^ 0x0123456789abcdef
}
func (d *digest) compress(data []byte) {
// save_abc
a := d.a
b := d.b
c := d.c
var x []uint64
if littleEndian {
x = []uint64{
binary.LittleEndian.Uint64(data[0:8]),
binary.LittleEndian.Uint64(data[8:16]),
binary.LittleEndian.Uint64(data[16:24]),
binary.LittleEndian.Uint64(data[24:32]),
binary.LittleEndian.Uint64(data[32:40]),
binary.LittleEndian.Uint64(data[40:48]),
binary.LittleEndian.Uint64(data[48:56]),
binary.LittleEndian.Uint64(data[56:64]),
}
} else {
x = []uint64{
binary.BigEndian.Uint64(data[0:8]),
binary.BigEndian.Uint64(data[8:16]),
binary.BigEndian.Uint64(data[16:24]),
binary.BigEndian.Uint64(data[24:32]),
binary.BigEndian.Uint64(data[32:40]),
binary.BigEndian.Uint64(data[40:48]),
binary.BigEndian.Uint64(data[48:56]),
binary.BigEndian.Uint64(data[56:64]),
}
}
d.a, d.b, d.c = pass(d.a, d.b, d.c, x, 5)
keySchedule(x)
d.c, d.a, d.b = pass(d.c, d.a, d.b, x, 7)
keySchedule(x)
d.b, d.c, d.a = pass(d.b, d.c, d.a, x, 9)
// feedforward
d.a ^= a
d.b -= b
d.c += c
}

269
vendor/github.com/cxmcc/tiger/sboxes.go generated vendored Normal file
View File

@ -0,0 +1,269 @@
package tiger
var t1 = [...]uint64{
0x02aab17cf7e90c5e, 0xac424b03e243a8ec, 0x72cd5be30dd5fcd3, 0x6d019b93f6f97f3a,
0xcd9978ffd21f9193, 0x7573a1c9708029e2, 0xb164326b922a83c3, 0x46883eee04915870,
0xeaace3057103ece6, 0xc54169b808a3535c, 0x4ce754918ddec47c, 0x0aa2f4dfdc0df40c,
0x10b76f18a74dbefa, 0xc6ccb6235ad1ab6a, 0x13726121572fe2ff, 0x1a488c6f199d921e,
0x4bc9f9f4da0007ca, 0x26f5e6f6e85241c7, 0x859079dbea5947b6, 0x4f1885c5c99e8c92,
0xd78e761ea96f864b, 0x8e36428c52b5c17d, 0x69cf6827373063c1, 0xb607c93d9bb4c56e,
0x7d820e760e76b5ea, 0x645c9cc6f07fdc42, 0xbf38a078243342e0, 0x5f6b343c9d2e7d04,
0xf2c28aeb600b0ec6, 0x6c0ed85f7254bcac, 0x71592281a4db4fe5, 0x1967fa69ce0fed9f,
0xfd5293f8b96545db, 0xc879e9d7f2a7600b, 0x860248920193194e, 0xa4f9533b2d9cc0b3,
0x9053836c15957613, 0xdb6dcf8afc357bf1, 0x18beea7a7a370f57, 0x037117ca50b99066,
0x6ab30a9774424a35, 0xf4e92f02e325249b, 0x7739db07061ccae1, 0xd8f3b49ceca42a05,
0xbd56be3f51382f73, 0x45faed5843b0bb28, 0x1c813d5c11bf1f83, 0x8af0e4b6d75fa169,
0x33ee18a487ad9999, 0x3c26e8eab1c94410, 0xb510102bc0a822f9, 0x141eef310ce6123b,
0xfc65b90059ddb154, 0xe0158640c5e0e607, 0x884e079826c3a3cf, 0x930d0d9523c535fd,
0x35638d754e9a2b00, 0x4085fccf40469dd5, 0xc4b17ad28be23a4c, 0xcab2f0fc6a3e6a2e,
0x2860971a6b943fcd, 0x3dde6ee212e30446, 0x6222f32ae01765ae, 0x5d550bb5478308fe,
0xa9efa98da0eda22a, 0xc351a71686c40da7, 0x1105586d9c867c84, 0xdcffee85fda22853,
0xccfbd0262c5eef76, 0xbaf294cb8990d201, 0xe69464f52afad975, 0x94b013afdf133e14,
0x06a7d1a32823c958, 0x6f95fe5130f61119, 0xd92ab34e462c06c0, 0xed7bde33887c71d2,
0x79746d6e6518393e, 0x5ba419385d713329, 0x7c1ba6b948a97564, 0x31987c197bfdac67,
0xde6c23c44b053d02, 0x581c49fed002d64d, 0xdd474d6338261571, 0xaa4546c3e473d062,
0x928fce349455f860, 0x48161bbacaab94d9, 0x63912430770e6f68, 0x6ec8a5e602c6641c,
0x87282515337ddd2b, 0x2cda6b42034b701b, 0xb03d37c181cb096d, 0xe108438266c71c6f,
0x2b3180c7eb51b255, 0xdf92b82f96c08bbc, 0x5c68c8c0a632f3ba, 0x5504cc861c3d0556,
0xabbfa4e55fb26b8f, 0x41848b0ab3baceb4, 0xb334a273aa445d32, 0xbca696f0a85ad881,
0x24f6ec65b528d56c, 0x0ce1512e90f4524a, 0x4e9dd79d5506d35a, 0x258905fac6ce9779,
0x2019295b3e109b33, 0xf8a9478b73a054cc, 0x2924f2f934417eb0, 0x3993357d536d1bc4,
0x38a81ac21db6ff8b, 0x47c4fbf17d6016bf, 0x1e0faadd7667e3f5, 0x7abcff62938beb96,
0xa78dad948fc179c9, 0x8f1f98b72911e50d, 0x61e48eae27121a91, 0x4d62f7ad31859808,
0xeceba345ef5ceaeb, 0xf5ceb25ebc9684ce, 0xf633e20cb7f76221, 0xa32cdf06ab8293e4,
0x985a202ca5ee2ca4, 0xcf0b8447cc8a8fb1, 0x9f765244979859a3, 0xa8d516b1a1240017,
0x0bd7ba3ebb5dc726, 0xe54bca55b86adb39, 0x1d7a3afd6c478063, 0x519ec608e7669edd,
0x0e5715a2d149aa23, 0x177d4571848ff194, 0xeeb55f3241014c22, 0x0f5e5ca13a6e2ec2,
0x8029927b75f5c361, 0xad139fabc3d6e436, 0x0d5df1a94ccf402f, 0x3e8bd948bea5dfc8,
0xa5a0d357bd3ff77e, 0xa2d12e251f74f645, 0x66fd9e525e81a082, 0x2e0c90ce7f687a49,
0xc2e8bcbeba973bc5, 0x000001bce509745f, 0x423777bbe6dab3d6, 0xd1661c7eaef06eb5,
0xa1781f354daacfd8, 0x2d11284a2b16affc, 0xf1fc4f67fa891d1f, 0x73ecc25dcb920ada,
0xae610c22c2a12651, 0x96e0a810d356b78a, 0x5a9a381f2fe7870f, 0xd5ad62ede94e5530,
0xd225e5e8368d1427, 0x65977b70c7af4631, 0x99f889b2de39d74f, 0x233f30bf54e1d143,
0x9a9675d3d9a63c97, 0x5470554ff334f9a8, 0x166acb744a4f5688, 0x70c74caab2e4aead,
0xf0d091646f294d12, 0x57b82a89684031d1, 0xefd95a5a61be0b6b, 0x2fbd12e969f2f29a,
0x9bd37013feff9fe8, 0x3f9b0404d6085a06, 0x4940c1f3166cfe15, 0x09542c4dcdf3defb,
0xb4c5218385cd5ce3, 0xc935b7dc4462a641, 0x3417f8a68ed3b63f, 0xb80959295b215b40,
0xf99cdaef3b8c8572, 0x018c0614f8fcb95d, 0x1b14accd1a3acdf3, 0x84d471f200bb732d,
0xc1a3110e95e8da16, 0x430a7220bf1a82b8, 0xb77e090d39df210e, 0x5ef4bd9f3cd05e9d,
0x9d4ff6da7e57a444, 0xda1d60e183d4a5f8, 0xb287c38417998e47, 0xfe3edc121bb31886,
0xc7fe3ccc980ccbef, 0xe46fb590189bfd03, 0x3732fd469a4c57dc, 0x7ef700a07cf1ad65,
0x59c64468a31d8859, 0x762fb0b4d45b61f6, 0x155baed099047718, 0x68755e4c3d50baa6,
0xe9214e7f22d8b4df, 0x2addbf532eac95f4, 0x32ae3909b4bd0109, 0x834df537b08e3450,
0xfa209da84220728d, 0x9e691d9b9efe23f7, 0x0446d288c4ae8d7f, 0x7b4cc524e169785b,
0x21d87f0135ca1385, 0xcebb400f137b8aa5, 0x272e2b66580796be, 0x3612264125c2b0de,
0x057702bdad1efbb2, 0xd4babb8eacf84be9, 0x91583139641bc67b, 0x8bdc2de08036e024,
0x603c8156f49f68ed, 0xf7d236f7dbef5111, 0x9727c4598ad21e80, 0xa08a0896670a5fd7,
0xcb4a8f4309eba9cb, 0x81af564b0f7036a1, 0xc0b99aa778199abd, 0x959f1ec83fc8e952,
0x8c505077794a81b9, 0x3acaaf8f056338f0, 0x07b43f50627a6778, 0x4a44ab49f5eccc77,
0x3bc3d6e4b679ee98, 0x9cc0d4d1cf14108c, 0x4406c00b206bc8a0, 0x82a18854c8d72d89,
0x67e366b35c3c432c, 0xb923dd61102b37f2, 0x56ab2779d884271d, 0xbe83e1b0ff1525af,
0xfb7c65d4217e49a9, 0x6bdbe0e76d48e7d4, 0x08df828745d9179e, 0x22ea6a9add53bd34,
0xe36e141c5622200a, 0x7f805d1b8cb750ee, 0xafe5c7a59f58e837, 0xe27f996a4fb1c23c,
0xd3867dfb0775f0d0, 0xd0e673de6e88891a, 0x123aeb9eafb86c25, 0x30f1d5d5c145b895,
0xbb434a2dee7269e7, 0x78cb67ecf931fa38, 0xf33b0372323bbf9c, 0x52d66336fb279c74,
0x505f33ac0afb4eaa, 0xe8a5cd99a2cce187, 0x534974801e2d30bb, 0x8d2d5711d5876d90,
0x1f1a412891bc038e, 0xd6e2e71d82e56648, 0x74036c3a497732b7, 0x89b67ed96361f5ab,
0xffed95d8f1ea02a2, 0xe72b3bd61464d43d, 0xa6300f170bdc4820, 0xebc18760ed78a77a,
}
var t2 = [...]uint64{
0xe6a6be5a05a12138, 0xb5a122a5b4f87c98, 0x563c6089140b6990, 0x4c46cb2e391f5dd5,
0xd932addbc9b79434, 0x08ea70e42015aff5, 0xd765a6673e478cf1, 0xc4fb757eab278d99,
0xdf11c6862d6e0692, 0xddeb84f10d7f3b16, 0x6f2ef604a665ea04, 0x4a8e0f0ff0e0dfb3,
0xa5edeef83dbcba51, 0xfc4f0a2a0ea4371e, 0xe83e1da85cb38429, 0xdc8ff882ba1b1ce2,
0xcd45505e8353e80d, 0x18d19a00d4db0717, 0x34a0cfeda5f38101, 0x0be77e518887caf2,
0x1e341438b3c45136, 0xe05797f49089ccf9, 0xffd23f9df2591d14, 0x543dda228595c5cd,
0x661f81fd99052a33, 0x8736e641db0f7b76, 0x15227725418e5307, 0xe25f7f46162eb2fa,
0x48a8b2126c13d9fe, 0xafdc541792e76eea, 0x03d912bfc6d1898f, 0x31b1aafa1b83f51b,
0xf1ac2796e42ab7d9, 0x40a3a7d7fcd2ebac, 0x1056136d0afbbcc5, 0x7889e1dd9a6d0c85,
0xd33525782a7974aa, 0xa7e25d09078ac09b, 0xbd4138b3eac6edd0, 0x920abfbe71eb9e70,
0xa2a5d0f54fc2625c, 0xc054e36b0b1290a3, 0xf6dd59ff62fe932b, 0x3537354511a8ac7d,
0xca845e9172fadcd4, 0x84f82b60329d20dc, 0x79c62ce1cd672f18, 0x8b09a2add124642c,
0xd0c1e96a19d9e726, 0x5a786a9b4ba9500c, 0x0e020336634c43f3, 0xc17b474aeb66d822,
0x6a731ae3ec9baac2, 0x8226667ae0840258, 0x67d4567691caeca5, 0x1d94155c4875adb5,
0x6d00fd985b813fdf, 0x51286efcb774cd06, 0x5e8834471fa744af, 0xf72ca0aee761ae2e,
0xbe40e4cdaee8e09a, 0xe9970bbb5118f665, 0x726e4beb33df1964, 0x703b000729199762,
0x4631d816f5ef30a7, 0xb880b5b51504a6be, 0x641793c37ed84b6c, 0x7b21ed77f6e97d96,
0x776306312ef96b73, 0xae528948e86ff3f4, 0x53dbd7f286a3f8f8, 0x16cadce74cfc1063,
0x005c19bdfa52c6dd, 0x68868f5d64d46ad3, 0x3a9d512ccf1e186a, 0x367e62c2385660ae,
0xe359e7ea77dcb1d7, 0x526c0773749abe6e, 0x735ae5f9d09f734b, 0x493fc7cc8a558ba8,
0xb0b9c1533041ab45, 0x321958ba470a59bd, 0x852db00b5f46c393, 0x91209b2bd336b0e5,
0x6e604f7d659ef19f, 0xb99a8ae2782ccb24, 0xccf52ab6c814c4c7, 0x4727d9afbe11727b,
0x7e950d0c0121b34d, 0x756f435670ad471f, 0xf5add442615a6849, 0x4e87e09980b9957a,
0x2acfa1df50aee355, 0xd898263afd2fd556, 0xc8f4924dd80c8fd6, 0xcf99ca3d754a173a,
0xfe477bacaf91bf3c, 0xed5371f6d690c12d, 0x831a5c285e687094, 0xc5d3c90a3708a0a4,
0x0f7f903717d06580, 0x19f9bb13b8fdf27f, 0xb1bd6f1b4d502843, 0x1c761ba38fff4012,
0x0d1530c4e2e21f3b, 0x8943ce69a7372c8a, 0xe5184e11feb5ce66, 0x618bdb80bd736621,
0x7d29bad68b574d0b, 0x81bb613e25e6fe5b, 0x071c9c10bc07913f, 0xc7beeb7909ac2d97,
0xc3e58d353bc5d757, 0xeb017892f38f61e8, 0xd4effb9c9b1cc21a, 0x99727d26f494f7ab,
0xa3e063a2956b3e03, 0x9d4a8b9a4aa09c30, 0x3f6ab7d500090fb4, 0x9cc0f2a057268ac0,
0x3dee9d2dedbf42d1, 0x330f49c87960a972, 0xc6b2720287421b41, 0x0ac59ec07c00369c,
0xef4eac49cb353425, 0xf450244eef0129d8, 0x8acc46e5caf4deb6, 0x2ffeab63989263f7,
0x8f7cb9fe5d7a4578, 0x5bd8f7644e634635, 0x427a7315bf2dc900, 0x17d0c4aa2125261c,
0x3992486c93518e50, 0xb4cbfee0a2d7d4c3, 0x7c75d6202c5ddd8d, 0xdbc295d8e35b6c61,
0x60b369d302032b19, 0xce42685fdce44132, 0x06f3ddb9ddf65610, 0x8ea4d21db5e148f0,
0x20b0fce62fcd496f, 0x2c1b912358b0ee31, 0xb28317b818f5a308, 0xa89c1e189ca6d2cf,
0x0c6b18576aaadbc8, 0xb65deaa91299fae3, 0xfb2b794b7f1027e7, 0x04e4317f443b5beb,
0x4b852d325939d0a6, 0xd5ae6beefb207ffc, 0x309682b281c7d374, 0xbae309a194c3b475,
0x8cc3f97b13b49f05, 0x98a9422ff8293967, 0x244b16b01076ff7c, 0xf8bf571c663d67ee,
0x1f0d6758eee30da1, 0xc9b611d97adeb9b7, 0xb7afd5887b6c57a2, 0x6290ae846b984fe1,
0x94df4cdeacc1a5fd, 0x058a5bd1c5483aff, 0x63166cc142ba3c37, 0x8db8526eb2f76f40,
0xe10880036f0d6d4e, 0x9e0523c9971d311d, 0x45ec2824cc7cd691, 0x575b8359e62382c9,
0xfa9e400dc4889995, 0xd1823ecb45721568, 0xdafd983b8206082f, 0xaa7d29082386a8cb,
0x269fcd4403b87588, 0x1b91f5f728bdd1e0, 0xe4669f39040201f6, 0x7a1d7c218cf04ade,
0x65623c29d79ce5ce, 0x2368449096c00bb1, 0xab9bf1879da503ba, 0xbc23ecb1a458058e,
0x9a58df01bb401ecc, 0xa070e868a85f143d, 0x4ff188307df2239e, 0x14d565b41a641183,
0xee13337452701602, 0x950e3dcf3f285e09, 0x59930254b9c80953, 0x3bf299408930da6d,
0xa955943f53691387, 0xa15edecaa9cb8784, 0x29142127352be9a0, 0x76f0371fff4e7afb,
0x0239f450274f2228, 0xbb073af01d5e868b, 0xbfc80571c10e96c1, 0xd267088568222e23,
0x9671a3d48e80b5b0, 0x55b5d38ae193bb81, 0x693ae2d0a18b04b8, 0x5c48b4ecadd5335f,
0xfd743b194916a1ca, 0x2577018134be98c4, 0xe77987e83c54a4ad, 0x28e11014da33e1b9,
0x270cc59e226aa213, 0x71495f756d1a5f60, 0x9be853fb60afef77, 0xadc786a7f7443dbf,
0x0904456173b29a82, 0x58bc7a66c232bd5e, 0xf306558c673ac8b2, 0x41f639c6b6c9772a,
0x216defe99fda35da, 0x11640cc71c7be615, 0x93c43694565c5527, 0xea038e6246777839,
0xf9abf3ce5a3e2469, 0x741e768d0fd312d2, 0x0144b883ced652c6, 0xc20b5a5ba33f8552,
0x1ae69633c3435a9d, 0x97a28ca4088cfdec, 0x8824a43c1e96f420, 0x37612fa66eeea746,
0x6b4cb165f9cf0e5a, 0x43aa1c06a0abfb4a, 0x7f4dc26ff162796b, 0x6cbacc8e54ed9b0f,
0xa6b7ffefd2bb253e, 0x2e25bc95b0a29d4f, 0x86d6a58bdef1388c, 0xded74ac576b6f054,
0x8030bdbc2b45805d, 0x3c81af70e94d9289, 0x3eff6dda9e3100db, 0xb38dc39fdfcc8847,
0x123885528d17b87e, 0xf2da0ed240b1b642, 0x44cefadcd54bf9a9, 0x1312200e433c7ee6,
0x9ffcc84f3a78c748, 0xf0cd1f72248576bb, 0xec6974053638cfe4, 0x2ba7b67c0cec4e4c,
0xac2f4df3e5ce32ed, 0xcb33d14326ea4c11, 0xa4e9044cc77e58bc, 0x5f513293d934fcef,
0x5dc9645506e55444, 0x50de418f317de40a, 0x388cb31a69dde259, 0x2db4a83455820a86,
0x9010a91e84711ae9, 0x4df7f0b7b1498371, 0xd62a2eabc0977179, 0x22fac097aa8d5c0e,
}
var t3 = [...]uint64{
0xf49fcc2ff1daf39b, 0x487fd5c66ff29281, 0xe8a30667fcdca83f, 0x2c9b4be3d2fcce63,
0xda3ff74b93fbbbc2, 0x2fa165d2fe70ba66, 0xa103e279970e93d4, 0xbecdec77b0e45e71,
0xcfb41e723985e497, 0xb70aaa025ef75017, 0xd42309f03840b8e0, 0x8efc1ad035898579,
0x96c6920be2b2abc5, 0x66af4163375a9172, 0x2174abdcca7127fb, 0xb33ccea64a72ff41,
0xf04a4933083066a5, 0x8d970acdd7289af5, 0x8f96e8e031c8c25e, 0xf3fec02276875d47,
0xec7bf310056190dd, 0xf5adb0aebb0f1491, 0x9b50f8850fd58892, 0x4975488358b74de8,
0xa3354ff691531c61, 0x0702bbe481d2c6ee, 0x89fb24057deded98, 0xac3075138596e902,
0x1d2d3580172772ed, 0xeb738fc28e6bc30d, 0x5854ef8f63044326, 0x9e5c52325add3bbe,
0x90aa53cf325c4623, 0xc1d24d51349dd067, 0x2051cfeea69ea624, 0x13220f0a862e7e4f,
0xce39399404e04864, 0xd9c42ca47086fcb7, 0x685ad2238a03e7cc, 0x066484b2ab2ff1db,
0xfe9d5d70efbf79ec, 0x5b13b9dd9c481854, 0x15f0d475ed1509ad, 0x0bebcd060ec79851,
0xd58c6791183ab7f8, 0xd1187c5052f3eee4, 0xc95d1192e54e82ff, 0x86eea14cb9ac6ca2,
0x3485beb153677d5d, 0xdd191d781f8c492a, 0xf60866baa784ebf9, 0x518f643ba2d08c74,
0x8852e956e1087c22, 0xa768cb8dc410ae8d, 0x38047726bfec8e1a, 0xa67738b4cd3b45aa,
0xad16691cec0dde19, 0xc6d4319380462e07, 0xc5a5876d0ba61938, 0x16b9fa1fa58fd840,
0x188ab1173ca74f18, 0xabda2f98c99c021f, 0x3e0580ab134ae816, 0x5f3b05b773645abb,
0x2501a2be5575f2f6, 0x1b2f74004e7e8ba9, 0x1cd7580371e8d953, 0x7f6ed89562764e30,
0xb15926ff596f003d, 0x9f65293da8c5d6b9, 0x6ecef04dd690f84c, 0x4782275fff33af88,
0xe41433083f820801, 0xfd0dfe409a1af9b5, 0x4325a3342cdb396b, 0x8ae77e62b301b252,
0xc36f9e9f6655615a, 0x85455a2d92d32c09, 0xf2c7dea949477485, 0x63cfb4c133a39eba,
0x83b040cc6ebc5462, 0x3b9454c8fdb326b0, 0x56f56a9e87ffd78c, 0x2dc2940d99f42bc6,
0x98f7df096b096e2d, 0x19a6e01e3ad852bf, 0x42a99ccbdbd4b40b, 0xa59998af45e9c559,
0x366295e807d93186, 0x6b48181bfaa1f773, 0x1fec57e2157a0a1d, 0x4667446af6201ad5,
0xe615ebcacfb0f075, 0xb8f31f4f68290778, 0x22713ed6ce22d11e, 0x3057c1a72ec3c93b,
0xcb46acc37c3f1f2f, 0xdbb893fd02aaf50e, 0x331fd92e600b9fcf, 0xa498f96148ea3ad6,
0xa8d8426e8b6a83ea, 0xa089b274b7735cdc, 0x87f6b3731e524a11, 0x118808e5cbc96749,
0x9906e4c7b19bd394, 0xafed7f7e9b24a20c, 0x6509eadeeb3644a7, 0x6c1ef1d3e8ef0ede,
0xb9c97d43e9798fb4, 0xa2f2d784740c28a3, 0x7b8496476197566f, 0x7a5be3e6b65f069d,
0xf96330ed78be6f10, 0xeee60de77a076a15, 0x2b4bee4aa08b9bd0, 0x6a56a63ec7b8894e,
0x02121359ba34fef4, 0x4cbf99f8283703fc, 0x398071350caf30c8, 0xd0a77a89f017687a,
0xf1c1a9eb9e423569, 0x8c7976282dee8199, 0x5d1737a5dd1f7abd, 0x4f53433c09a9fa80,
0xfa8b0c53df7ca1d9, 0x3fd9dcbc886ccb77, 0xc040917ca91b4720, 0x7dd00142f9d1dcdf,
0x8476fc1d4f387b58, 0x23f8e7c5f3316503, 0x032a2244e7e37339, 0x5c87a5d750f5a74b,
0x082b4cc43698992e, 0xdf917becb858f63c, 0x3270b8fc5bf86dda, 0x10ae72bb29b5dd76,
0x576ac94e7700362b, 0x1ad112dac61efb8f, 0x691bc30ec5faa427, 0xff246311cc327143,
0x3142368e30e53206, 0x71380e31e02ca396, 0x958d5c960aad76f1, 0xf8d6f430c16da536,
0xc8ffd13f1be7e1d2, 0x7578ae66004ddbe1, 0x05833f01067be646, 0xbb34b5ad3bfe586d,
0x095f34c9a12b97f0, 0x247ab64525d60ca8, 0xdcdbc6f3017477d1, 0x4a2e14d4decad24d,
0xbdb5e6d9be0a1eeb, 0x2a7e70f7794301ab, 0xdef42d8a270540fd, 0x01078ec0a34c22c1,
0xe5de511af4c16387, 0x7ebb3a52bd9a330a, 0x77697857aa7d6435, 0x004e831603ae4c32,
0xe7a21020ad78e312, 0x9d41a70c6ab420f2, 0x28e06c18ea1141e6, 0xd2b28cbd984f6b28,
0x26b75f6c446e9d83, 0xba47568c4d418d7f, 0xd80badbfe6183d8e, 0x0e206d7f5f166044,
0xe258a43911cbca3e, 0x723a1746b21dc0bc, 0xc7caa854f5d7cdd3, 0x7cac32883d261d9c,
0x7690c26423ba942c, 0x17e55524478042b8, 0xe0be477656a2389f, 0x4d289b5e67ab2da0,
0x44862b9c8fbbfd31, 0xb47cc8049d141365, 0x822c1b362b91c793, 0x4eb14655fb13dfd8,
0x1ecbba0714e2a97b, 0x6143459d5cde5f14, 0x53a8fbf1d5f0ac89, 0x97ea04d81c5e5b00,
0x622181a8d4fdb3f3, 0xe9bcd341572a1208, 0x1411258643cce58a, 0x9144c5fea4c6e0a4,
0x0d33d06565cf620f, 0x54a48d489f219ca1, 0xc43e5eac6d63c821, 0xa9728b3a72770daf,
0xd7934e7b20df87ef, 0xe35503b61a3e86e5, 0xcae321fbc819d504, 0x129a50b3ac60bfa6,
0xcd5e68ea7e9fb6c3, 0xb01c90199483b1c7, 0x3de93cd5c295376c, 0xaed52edf2ab9ad13,
0x2e60f512c0a07884, 0xbc3d86a3e36210c9, 0x35269d9b163951ce, 0x0c7d6e2ad0cdb5fa,
0x59e86297d87f5733, 0x298ef221898db0e7, 0x55000029d1a5aa7e, 0x8bc08ae1b5061b45,
0xc2c31c2b6c92703a, 0x94cc596baf25ef42, 0x0a1d73db22540456, 0x04b6a0f9d9c4179a,
0xeffdafa2ae3d3c60, 0xf7c8075bb49496c4, 0x9cc5c7141d1cd4e3, 0x78bd1638218e5534,
0xb2f11568f850246a, 0xedfabcfa9502bc29, 0x796ce5f2da23051b, 0xaae128b0dc93537c,
0x3a493da0ee4b29ae, 0xb5df6b2c416895d7, 0xfcabbd25122d7f37, 0x70810b58105dc4b1,
0xe10fdd37f7882a90, 0x524dcab5518a3f5c, 0x3c9e85878451255b, 0x4029828119bd34e2,
0x74a05b6f5d3ceccb, 0xb610021542e13eca, 0x0ff979d12f59e2ac, 0x6037da27e4f9cc50,
0x5e92975a0df1847d, 0xd66de190d3e623fe, 0x5032d6b87b568048, 0x9a36b7ce8235216e,
0x80272a7a24f64b4a, 0x93efed8b8c6916f7, 0x37ddbff44cce1555, 0x4b95db5d4b99bd25,
0x92d3fda169812fc0, 0xfb1a4a9a90660bb6, 0x730c196946a4b9b2, 0x81e289aa7f49da68,
0x64669a0f83b1a05f, 0x27b3ff7d9644f48b, 0xcc6b615c8db675b3, 0x674f20b9bcebbe95,
0x6f31238275655982, 0x5ae488713e45cf05, 0xbf619f9954c21157, 0xeabac46040a8eae9,
0x454c6fe9f2c0c1cd, 0x419cf6496412691c, 0xd3dc3bef265b0f70, 0x6d0e60f5c3578a9e,
}
var t4 = [...]uint64{
0x5b0e608526323c55, 0x1a46c1a9fa1b59f5, 0xa9e245a17c4c8ffa, 0x65ca5159db2955d7,
0x05db0a76ce35afc2, 0x81eac77ea9113d45, 0x528ef88ab6ac0a0d, 0xa09ea253597be3ff,
0x430ddfb3ac48cd56, 0xc4b3a67af45ce46f, 0x4ececfd8fbe2d05e, 0x3ef56f10b39935f0,
0x0b22d6829cd619c6, 0x17fd460a74df2069, 0x6cf8cc8e8510ed40, 0xd6c824bf3a6ecaa7,
0x61243d581a817049, 0x048bacb6bbc163a2, 0xd9a38ac27d44cc32, 0x7fddff5baaf410ab,
0xad6d495aa804824b, 0xe1a6a74f2d8c9f94, 0xd4f7851235dee8e3, 0xfd4b7f886540d893,
0x247c20042aa4bfda, 0x096ea1c517d1327c, 0xd56966b4361a6685, 0x277da5c31221057d,
0x94d59893a43acff7, 0x64f0c51ccdc02281, 0x3d33bcc4ff6189db, 0xe005cb184ce66af1,
0xff5ccd1d1db99bea, 0xb0b854a7fe42980f, 0x7bd46a6a718d4b9f, 0xd10fa8cc22a5fd8c,
0xd31484952be4bd31, 0xc7fa975fcb243847, 0x4886ed1e5846c407, 0x28cddb791eb70b04,
0xc2b00be2f573417f, 0x5c9590452180f877, 0x7a6bddfff370eb00, 0xce509e38d6d9d6a4,
0xebeb0f00647fa702, 0x1dcc06cf76606f06, 0xe4d9f28ba286ff0a, 0xd85a305dc918c262,
0x475b1d8732225f54, 0x2d4fb51668ccb5fe, 0xa679b9d9d72bba20, 0x53841c0d912d43a5,
0x3b7eaa48bf12a4e8, 0x781e0e47f22f1ddf, 0xeff20ce60ab50973, 0x20d261d19dffb742,
0x16a12b03062a2e39, 0x1960eb2239650495, 0x251c16fed50eb8b8, 0x9ac0c330f826016e,
0xed152665953e7671, 0x02d63194a6369570, 0x5074f08394b1c987, 0x70ba598c90b25ce1,
0x794a15810b9742f6, 0x0d5925e9fcaf8c6c, 0x3067716cd868744e, 0x910ab077e8d7731b,
0x6a61bbdb5ac42f61, 0x93513efbf0851567, 0xf494724b9e83e9d5, 0xe887e1985c09648d,
0x34b1d3c675370cfd, 0xdc35e433bc0d255d, 0xd0aab84234131be0, 0x08042a50b48b7eaf,
0x9997c4ee44a3ab35, 0x829a7b49201799d0, 0x263b8307b7c54441, 0x752f95f4fd6a6ca6,
0x927217402c08c6e5, 0x2a8ab754a795d9ee, 0xa442f7552f72943d, 0x2c31334e19781208,
0x4fa98d7ceaee6291, 0x55c3862f665db309, 0xbd0610175d53b1f3, 0x46fe6cb840413f27,
0x3fe03792df0cfa59, 0xcfe700372eb85e8f, 0xa7be29e7adbce118, 0xe544ee5cde8431dd,
0x8a781b1b41f1873e, 0xa5c94c78a0d2f0e7, 0x39412e2877b60728, 0xa1265ef3afc9a62c,
0xbcc2770c6a2506c5, 0x3ab66dd5dce1ce12, 0xe65499d04a675b37, 0x7d8f523481bfd216,
0x0f6f64fcec15f389, 0x74efbe618b5b13c8, 0xacdc82b714273e1d, 0xdd40bfe003199d17,
0x37e99257e7e061f8, 0xfa52626904775aaa, 0x8bbbf63a463d56f9, 0xf0013f1543a26e64,
0xa8307e9f879ec898, 0xcc4c27a4150177cc, 0x1b432f2cca1d3348, 0xde1d1f8f9f6fa013,
0x606602a047a7ddd6, 0xd237ab64cc1cb2c7, 0x9b938e7225fcd1d3, 0xec4e03708e0ff476,
0xfeb2fbda3d03c12d, 0xae0bced2ee43889a, 0x22cb8923ebfb4f43, 0x69360d013cf7396d,
0x855e3602d2d4e022, 0x073805bad01f784c, 0x33e17a133852f546, 0xdf4874058ac7b638,
0xba92b29c678aa14a, 0x0ce89fc76cfaadcd, 0x5f9d4e0908339e34, 0xf1afe9291f5923b9,
0x6e3480f60f4a265f, 0xeebf3a2ab29b841c, 0xe21938a88f91b4ad, 0x57dfeff845c6d3c3,
0x2f006b0bf62caaf2, 0x62f479ef6f75ee78, 0x11a55ad41c8916a9, 0xf229d29084fed453,
0x42f1c27b16b000e6, 0x2b1f76749823c074, 0x4b76eca3c2745360, 0x8c98f463b91691bd,
0x14bcc93cf1ade66a, 0x8885213e6d458397, 0x8e177df0274d4711, 0xb49b73b5503f2951,
0x10168168c3f96b6b, 0x0e3d963b63cab0ae, 0x8dfc4b5655a1db14, 0xf789f1356e14de5c,
0x683e68af4e51dac1, 0xc9a84f9d8d4b0fd9, 0x3691e03f52a0f9d1, 0x5ed86e46e1878e80,
0x3c711a0e99d07150, 0x5a0865b20c4e9310, 0x56fbfc1fe4f0682e, 0xea8d5de3105edf9b,
0x71abfdb12379187a, 0x2eb99de1bee77b9c, 0x21ecc0ea33cf4523, 0x59a4d7521805c7a1,
0x3896f5eb56ae7c72, 0xaa638f3db18f75dc, 0x9f39358dabe9808e, 0xb7defa91c00b72ac,
0x6b5541fd62492d92, 0x6dc6dee8f92e4d5b, 0x353f57abc4beea7e, 0x735769d6da5690ce,
0x0a234aa642391484, 0xf6f9508028f80d9d, 0xb8e319a27ab3f215, 0x31ad9c1151341a4d,
0x773c22a57bef5805, 0x45c7561a07968633, 0xf913da9e249dbe36, 0xda652d9b78a64c68,
0x4c27a97f3bc334ef, 0x76621220e66b17f4, 0x967743899acd7d0b, 0xf3ee5bcae0ed6782,
0x409f753600c879fc, 0x06d09a39b5926db6, 0x6f83aeb0317ac588, 0x01e6ca4a86381f21,
0x66ff3462d19f3025, 0x72207c24ddfd3bfb, 0x4af6b6d3e2ece2eb, 0x9c994dbec7ea08de,
0x49ace597b09a8bc4, 0xb38c4766cf0797ba, 0x131b9373c57c2a75, 0xb1822cce61931e58,
0x9d7555b909ba1c0c, 0x127fafdd937d11d2, 0x29da3badc66d92e4, 0xa2c1d57154c2ecbc,
0x58c5134d82f6fe24, 0x1c3ae3515b62274f, 0xe907c82e01cb8126, 0xf8ed091913e37fcb,
0x3249d8f9c80046c9, 0x80cf9bede388fb63, 0x1881539a116cf19e, 0x5103f3f76bd52457,
0x15b7e6f5ae47f7a8, 0xdbd7c6ded47e9ccf, 0x44e55c410228bb1a, 0xb647d4255edb4e99,
0x5d11882bb8aafc30, 0xf5098bbb29d3212a, 0x8fb5ea14e90296b3, 0x677b942157dd025a,
0xfb58e7c0a390acb5, 0x89d3674c83bd4a01, 0x9e2da4df4bf3b93b, 0xfcc41e328cab4829,
0x03f38c96ba582c52, 0xcad1bdbd7fd85db2, 0xbbb442c16082ae83, 0xb95fe86ba5da9ab0,
0xb22e04673771a93f, 0x845358c9493152d8, 0xbe2a488697b4541e, 0x95a2dc2dd38e6966,
0xc02c11ac923c852b, 0x2388b1990df2a87b, 0x7c8008fa1b4f37be, 0x1f70d0c84d54e503,
0x5490adec7ece57d4, 0x002b3c27d9063a3a, 0x7eaea3848030a2bf, 0xc602326ded2003c0,
0x83a7287d69a94086, 0xc57a5fcb30f57a8a, 0xb56844e479ebe779, 0xa373b40f05dcbce9,
0xd71a786e88570ee2, 0x879cbacdbde8f6a0, 0x976ad1bcc164a32f, 0xab21e25e9666d78b,
0x901063aae5e5c33c, 0x9818b34448698d90, 0xe36487ae3e1e8abb, 0xafbdf931893bdcb4,
0x6345a0dc5fbbd519, 0x8628fe269b9465ca, 0x1e5d01603f9c51ec, 0x4de44006a15049b7,
0xbf6c70e5f776cbb1, 0x411218f2ef552bed, 0xcb0c0708705a36a3, 0xe74d14754f986044,
0xcd56d9430ea8280e, 0xc12591d7535f5065, 0xc83223f1720aef96, 0xc3a0396f7363a51f,
}

117
vendor/github.com/cxmcc/tiger/tiger.go generated vendored Normal file
View File

@ -0,0 +1,117 @@
// Package tiger implements the Tiger hash algorithm
// https://github.com/cxmcc/tiger
package tiger
import "hash"
// The size of a Tiger hash value in bytes
const Size = 24
// The blocksize of Tiger hash function in bytes
const BlockSize = 64
const (
chunk = 64
initA = 0x0123456789abcdef
initB = 0xfedcba9876543210
initC = 0xf096a5b4c3b2e187
)
type digest struct {
a uint64
b uint64
c uint64
x [chunk]byte
nx int
length uint64
ver int
}
func (d *digest) Reset() {
d.a = initA
d.b = initB
d.c = initC
d.nx = 0
d.length = 0
}
// New returns a new hash.Hash computing the Tiger hash value
func New() hash.Hash {
d := new(digest)
d.Reset()
d.ver = 1
return d
}
// New returns a new hash.Hash computing the Tiger2 hash value
func New2() hash.Hash {
d := new(digest)
d.Reset()
d.ver = 2
return d
}
func (d *digest) BlockSize() int {
return BlockSize
}
func (d *digest) Size() int {
return Size
}
func (d *digest) Write(p []byte) (length int, err error) {
length = len(p)
d.length += uint64(length)
if d.nx > 0 {
n := len(p)
if n > chunk-d.nx {
n = chunk - d.nx
}
copy(d.x[d.nx:d.nx+n], p[:n])
d.nx += n
if d.nx == chunk {
d.compress(d.x[:chunk])
d.nx = 0
}
p = p[n:]
}
for len(p) >= chunk {
d.compress(p[:chunk])
p = p[chunk:]
}
if len(p) > 0 {
d.nx = copy(d.x[:], p)
}
return
}
func (d digest) Sum(in []byte) []byte {
length := d.length
var tmp [64]byte
if d.ver == 1 {
tmp[0] = 0x01
} else {
tmp[0] = 0x80
}
size := length & 0x3f
if size < 56 {
d.Write(tmp[:56-size])
} else {
d.Write(tmp[:64+56-size])
}
length <<= 3
for i := uint(0); i < 8; i++ {
tmp[i] = byte(length >> (8 * i))
}
d.Write(tmp[:8])
for i := uint(0); i < 8; i++ {
tmp[i] = byte(d.a >> (8 * i))
tmp[i+8] = byte(d.b >> (8 * i))
tmp[i+16] = byte(d.c >> (8 * i))
}
return append(in, tmp[:24]...)
}

4
vendor/modules.txt vendored Normal file
View File

@ -0,0 +1,4 @@
# code.ivysaur.me/libnmdc v0.17.0
code.ivysaur.me/libnmdc
# github.com/cxmcc/tiger v0.0.0-20170524142333-bde35e2713d7
github.com/cxmcc/tiger