From 8603250d73f587456e47db8df6eadaaa93c8ebb1 Mon Sep 17 00:00:00 2001 From: jrandom <jrandom> Date: Wed, 21 Jul 2004 06:25:44 +0000 Subject: [PATCH] updated the readme to reference the current specs and implementations removed the old out of date jython and python code --- apps/sam/code.leo | 3957 --------------------------- apps/sam/doc/README | 35 +- apps/sam/jython/README | 54 - apps/sam/jython/build.xml | 36 - apps/sam/jython/src/i2psam.py | 2579 ----------------- apps/sam/python/src/i2psamclient.py | 1334 --------- 6 files changed, 8 insertions(+), 7987 deletions(-) delete mode 100644 apps/sam/code.leo delete mode 100644 apps/sam/jython/README delete mode 100644 apps/sam/jython/build.xml delete mode 100644 apps/sam/jython/src/i2psam.py delete mode 100644 apps/sam/python/src/i2psamclient.py diff --git a/apps/sam/code.leo b/apps/sam/code.leo deleted file mode 100644 index 8869595259..0000000000 --- a/apps/sam/code.leo +++ /dev/null @@ -1,3957 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<leo_file> -<leo_header file_format="2" tnodes="0" max_tnode_index="217" clone_windows="0"/> -<globals body_outline_ratio="0.35262008733624456"> - <global_window_position top="70" left="219" height="649" width="978"/> - <global_log_window_position top="0" left="0" height="0" width="0"/> -</globals> -<preferences> -</preferences> -<find_panel_settings> - <find_string></find_string> - <change_string></change_string> -</find_panel_settings> -<vnodes> -<v t="davidmcnab.041004143447" a="E"><vh>I2P SAM Server and Client</vh> -<v t="davidmcnab.041004144338" a="E" tnodeList="davidmcnab.041004144338,davidmcnab.041004144338.1,davidmcnab.041004144338.2,davidmcnab.041004144338.4,davidmcnab.041004144338.5,davidmcnab.041004144338.6,davidmcnab.041004144338.8,davidmcnab.041004144338.9,davidmcnab.041004144338.10,davidmcnab.041004144338.11,davidmcnab.041004144338.12,davidmcnab.041004144338.13,davidmcnab.041004144338.14,davidmcnab.041004144338.15,davidmcnab.041004144338.17,davidmcnab.041004144338.18,davidmcnab.041004144338.19,davidmcnab.041004144338.20,davidmcnab.041004144338.21,davidmcnab.041004144338.22,davidmcnab.041004144338.23,davidmcnab.041004144338.24,davidmcnab.041004144338.26,davidmcnab.041004144338.27,davidmcnab.041004144338.29,davidmcnab.041004144338.30,davidmcnab.041004144338.31,davidmcnab.041004144338.32,davidmcnab.041004144338.33,davidmcnab.041004144338.34,davidmcnab.041004144338.35,davidmcnab.041004144338.36,davidmcnab.041004144338.37,davidmcnab.041004144338.38,davidmcnab.041004144338.39,davidmcnab.041004144338.40,davidmcnab.041004144338.41,davidmcnab.041004144338.42,davidmcnab.041004144338.43,davidmcnab.041004144338.44,davidmcnab.041004144338.45,davidmcnab.041004144338.46,davidmcnab.041004144338.47,davidmcnab.041004144338.49,davidmcnab.041004144338.50,davidmcnab.041004144338.51,davidmcnab.041004144338.52,davidmcnab.041004144338.53,davidmcnab.041004144338.54,davidmcnab.041004144338.55,davidmcnab.041004144338.56,davidmcnab.041004144338.57,davidmcnab.041004144338.58,davidmcnab.041004144338.59,davidmcnab.041004144338.60,davidmcnab.041004144338.62,davidmcnab.041004144338.63,davidmcnab.041004144338.64,davidmcnab.041004144338.65,davidmcnab.041004144338.66,davidmcnab.041004144338.67,davidmcnab.041004144338.68,davidmcnab.041004144338.69,davidmcnab.041004144338.70,davidmcnab.041004144338.71,davidmcnab.041004144338.72,davidmcnab.041004144338.73,davidmcnab.041004144338.74,davidmcnab.041004144338.75,davidmcnab.041004144338.76,davidmcnab.041004144338.77,davidmcnab.041004144338.78,davidmcnab.041004144338.79,davidmcnab.041004144338.80,davidmcnab.041004144338.81,davidmcnab.041004144338.82,davidmcnab.041004144338.83,davidmcnab.041004144338.84,davidmcnab.041304205426,davidmcnab.041004144338.85,davidmcnab.041004144338.86,davidmcnab.041004144338.87,davidmcnab.041004144338.88,davidmcnab.041004144338.89,davidmcnab.041004144338.90,davidmcnab.041004144338.92,davidmcnab.041004144338.93,davidmcnab.041004144338.94,davidmcnab.041004144338.95,davidmcnab.041004144338.96,davidmcnab.041004144338.97,davidmcnab.041004144338.98,davidmcnab.041004144338.99,davidmcnab.041004144338.100,davidmcnab.041004144338.101,davidmcnab.041004144338.105,davidmcnab.041004144338.106,davidmcnab.041004144338.107,davidmcnab.041004144338.108,davidmcnab.041004144338.102,davidmcnab.041004144338.103,davidmcnab.041004144338.109"><vh>@file jython/src/i2psam.py</vh> -<v t="davidmcnab.041004144338.1" a="M"><vh>imports</vh></v> -<v t="davidmcnab.041004144338.2" a="V"><vh>globals</vh></v> -<v t="davidmcnab.041004144338.3" a="E"><vh>I2CP Interface Classes</vh> -<v t="davidmcnab.041004144338.4"><vh>class JavaWrapper</vh></v> -<v t="davidmcnab.041004144338.5" a="E"><vh>class I2PDestination</vh> -<v t="davidmcnab.041004144338.6"><vh>__init__</vh></v> -<v t="davidmcnab.041004144338.7" a="E"><vh>Exporting Methods</vh> -<v t="davidmcnab.041004144338.8"><vh>toBin</vh></v> -<v t="davidmcnab.041004144338.9"><vh>toBinFile</vh></v> -<v t="davidmcnab.041004144338.10"><vh>toBinPrivate</vh></v> -<v t="davidmcnab.041004144338.11"><vh>toBinFilePrivate</vh></v> -<v t="davidmcnab.041004144338.12"><vh>toBase64</vh></v> -<v t="davidmcnab.041004144338.13"><vh>toBase64Private</vh></v> -<v t="davidmcnab.041004144338.14"><vh>toBase64File</vh></v> -<v t="davidmcnab.041004144338.15"><vh>toBase64FilePrivate</vh></v> -</v> -<v t="davidmcnab.041004144338.16" a="E"><vh>Importing Methods</vh> -<v t="davidmcnab.041004144338.17"><vh>fromBin</vh></v> -<v t="davidmcnab.041004144338.18"><vh>fromBinFile</vh></v> -<v t="davidmcnab.041004144338.19"><vh>fromBinPrivate</vh></v> -<v t="davidmcnab.041004144338.20"><vh>fromBinFilePrivate</vh></v> -<v t="davidmcnab.041004144338.21"><vh>fromBase64</vh></v> -<v t="davidmcnab.041004144338.22"><vh>fromBase64File</vh></v> -<v t="davidmcnab.041004144338.23"><vh>fromBase64Private</vh></v> -<v t="davidmcnab.041004144338.24"><vh>fromBase64PrivateFile</vh></v> -</v> -<v t="davidmcnab.041004144338.25" a="E"><vh>Signature Methods</vh> -<v t="davidmcnab.041004144338.26"><vh>sign</vh></v> -<v t="davidmcnab.041004144338.27"><vh>verify</vh></v> -</v> -<v t="davidmcnab.041004144338.28" a="E"><vh>Sanity Methods</vh> -<v t="davidmcnab.041004144338.29"><vh>hasPrivate</vh></v> -</v> -</v> -<v t="davidmcnab.041004144338.30" a="E"><vh>class I2PClient</vh> -<v t="davidmcnab.041004144338.31" a="E"><vh>__init__</vh></v> -<v t="davidmcnab.041004144338.32"><vh>createDestination</vh></v> -<v t="davidmcnab.041004144338.33"><vh>createSession</vh></v> -</v> -<v t="davidmcnab.041004144338.34" a="E"><vh>class I2PSession</vh> -<v t="davidmcnab.041004144338.35"><vh>attributes</vh></v> -<v t="davidmcnab.041004144338.36"><vh>__init__</vh></v> -<v t="davidmcnab.041004144338.37"><vh>sendMessage</vh></v> -<v t="davidmcnab.041004144338.38"><vh>numMessages</vh></v> -<v t="davidmcnab.041004144338.39"><vh>getMessage</vh></v> -<v t="davidmcnab.041004144338.40"><vh>setSessionListener</vh></v> -<v t="davidmcnab.041004144338.41"><vh>destroySession</vh></v> -<v t="davidmcnab.041004144338.42" a="E"><vh>CALLBACKS</vh> -<v t="davidmcnab.041004144338.43"><vh>on_message</vh></v> -<v t="davidmcnab.041004144338.44"><vh>on_abuse</vh></v> -<v t="davidmcnab.041004144338.45"><vh>on_disconnected</vh></v> -<v t="davidmcnab.041004144338.46"><vh>on_error</vh></v> -</v> -</v> -<v t="davidmcnab.041004144338.47"><vh>class I2PSessionListener</vh></v> -</v> -<v t="davidmcnab.041004144338.48" a="E"><vh>Streaming Interface Classes</vh> -<v t="davidmcnab.041004144338.49" a="E"><vh>class I2PSocket</vh> -<v t="davidmcnab.041004144338.50"><vh>attributes</vh></v> -<v t="davidmcnab.041004144338.51"><vh>__init__</vh></v> -<v t="davidmcnab.041004144338.52"><vh>bind</vh></v> -<v t="davidmcnab.041004144338.53"><vh>listen</vh></v> -<v t="davidmcnab.041004144338.54"><vh>accept</vh></v> -<v t="davidmcnab.041004144338.55"><vh>connect</vh></v> -<v t="davidmcnab.041004144338.56"><vh>recv</vh></v> -<v t="davidmcnab.041004144338.57"><vh>send</vh></v> -<v t="davidmcnab.041004144338.58"><vh>available</vh></v> -<v t="davidmcnab.041004144338.59"><vh>close</vh></v> -<v t="davidmcnab.041004144338.60"><vh>_createSockmgr</vh></v> -</v> -</v> -<v t="davidmcnab.041004144338.61" a="E"><vh>I2P SAM Server</vh> -<v t="davidmcnab.041004144338.62" a="E"><vh>class I2PSamServer</vh> -<v t="davidmcnab.041004144338.63"><vh>attributes</vh></v> -<v t="davidmcnab.041004144338.64"><vh>__init__</vh></v> -<v t="davidmcnab.041004144338.65"><vh>run</vh></v> -<v t="davidmcnab.041004144338.66"><vh>finish_request</vh></v> -<v t="davidmcnab.041004144338.67"><vh>samAllocId</vh></v> -</v> -<v t="davidmcnab.041004144338.68" a="E"><vh>class I2PSamClientHandler</vh> -<v t="davidmcnab.041004144338.69"><vh>handle</vh></v> -<v t="davidmcnab.041004144338.70"><vh>on_genkeys</vh></v> -<v t="davidmcnab.041004144338.71"><vh>on_createsession</vh></v> -<v t="davidmcnab.041004144338.72"><vh>on_destroysession</vh></v> -<v t="davidmcnab.041004144338.73"><vh>on_send</vh></v> -<v t="davidmcnab.041004144338.74"><vh>on_receive</vh></v> -<v t="davidmcnab.041004144338.75"><vh>on_HELLO</vh></v> -<v t="davidmcnab.041004144338.76"><vh>on_SESSION</vh></v> -<v t="davidmcnab.041004144338.77"><vh>on_SESSION_CREATE</vh></v> -<v t="davidmcnab.041004144338.78"><vh>on_STREAM</vh></v> -<v t="davidmcnab.041004144338.79"><vh>on_DATAGRAM</vh></v> -<v t="davidmcnab.041004144338.80"><vh>on_RAW</vh></v> -<v t="davidmcnab.041004144338.81"><vh>on_NAMING</vh></v> -<v t="davidmcnab.041004144338.82"><vh>on_DEST</vh></v> -<v t="davidmcnab.041004144338.83"><vh>on_message</vh></v> -<v t="davidmcnab.041004144338.84"><vh>threadSocketListener</vh></v> -<v t="davidmcnab.041304205426"><vh>threadSocketReceiver</vh></v> -<v t="davidmcnab.041004144338.85"><vh>samParse</vh></v> -<v t="davidmcnab.041004144338.86"><vh>samSend</vh></v> -<v t="davidmcnab.041004144338.87"><vh>samCreateArgsList</vh></v> -<v t="davidmcnab.041004144338.88"><vh>_sendbytes</vh></v> -<v t="davidmcnab.041004144338.89"><vh>_recvbytes</vh></v> -</v> -</v> -<v t="davidmcnab.041004144338.90"><vh>Exceptions</vh></v> -<v t="davidmcnab.041004144338.91" a="E"><vh>Functions</vh> -<v t="davidmcnab.041004144338.92"><vh>shahash</vh></v> -<v t="davidmcnab.041004144338.93"><vh>base64enc</vh></v> -<v t="davidmcnab.041004144338.94"><vh>base64dec</vh></v> -<v t="davidmcnab.041004144338.95"><vh>str2bytearray</vh></v> -<v t="davidmcnab.041004144338.96"><vh>bytearray2str</vh></v> -<v t="davidmcnab.041004144338.97"><vh>byteoutstream2str</vh></v> -<v t="davidmcnab.041004144338.98"><vh>dict2props</vh></v> -<v t="davidmcnab.041004144338.99"><vh>takeKey</vh></v> -<v t="davidmcnab.041004144338.100"><vh>log</vh></v> -<v t="davidmcnab.041004144338.101"><vh>logException</vh></v> -<v t="davidmcnab.041004144338.104" a="E"><vh>Tests</vh> -<v t="davidmcnab.041004144338.105" tnodeList="davidmcnab.041004144338.105"><vh>testdests</vh></v> -<v t="davidmcnab.041004144338.106"><vh>testsigs</vh></v> -<v t="davidmcnab.041004144338.107"><vh>testsession</vh></v> -<v t="davidmcnab.041004144338.108"><vh>testsocket</vh></v> -</v> -<v t="davidmcnab.041004144338.102"><vh>usage</vh></v> -<v t="davidmcnab.041004144338.103"><vh>main</vh></v> -</v> -<v t="davidmcnab.041004144338.109"><vh>MAINLINE</vh></v> -</v> -<v t="davidmcnab.041004144551" a="E" tnodeList="davidmcnab.041004144551,davidmcnab.041004144551.1,davidmcnab.041004144551.2,davidmcnab.041004144551.3,davidmcnab.041004144551.4,davidmcnab.041004144551.5,davidmcnab.041004144551.6,davidmcnab.041004144551.7,davidmcnab.041004144551.8,davidmcnab.041004144551.9,davidmcnab.041004144551.10,davidmcnab.041004144551.12,davidmcnab.041004144551.13,davidmcnab.041004144551.14,davidmcnab.041004144551.15,davidmcnab.041004144551.16,davidmcnab.041004144551.17,davidmcnab.041004144551.18,davidmcnab.041004144551.19,davidmcnab.041004144551.20,davidmcnab.041204020513,davidmcnab.041204204235,davidmcnab.041204044735,davidmcnab.041204050339,davidmcnab.041004144551.21,davidmcnab.041004144551.22,davidmcnab.041004144551.23,davidmcnab.041004144551.24,davidmcnab.041004144551.26,davidmcnab.041004144551.27,davidmcnab.041004144551.28,davidmcnab.041004144551.29,davidmcnab.041004144551.30,davidmcnab.041004144551.31,davidmcnab.041004144551.32,davidmcnab.041004144551.33,davidmcnab.041204042212,davidmcnab.041004144551.35,davidmcnab.041004144551.36,davidmcnab.041004144551.37,davidmcnab.041004144551.38,davidmcnab.041204042212.1,davidmcnab.041204042212.2,davidmcnab.041204044735.1,davidmcnab.041204050339.1,davidmcnab.041304235615,davidmcnab.041204050339.2,davidmcnab.041204050511,davidmcnab.041204044135,davidmcnab.041004144551.39,davidmcnab.041004144551.40,davidmcnab.041004144551.41,davidmcnab.041004144551.42,davidmcnab.041004144551.43,davidmcnab.041004144551.45,davidmcnab.041004144551.46,davidmcnab.041004144551.47,davidmcnab.041004144551.48,davidmcnab.041004144551.49,davidmcnab.041004144551.50,davidmcnab.041204203651,davidmcnab.041004144551.51,davidmcnab.041004144551.52"><vh>@file python/src/i2psamclient.py</vh> -<v t="davidmcnab.041004144551.1"><vh>imports</vh></v> -<v t="davidmcnab.041004144551.2"><vh>globals</vh></v> -<v t="davidmcnab.041004144551.3"><vh>exceptions</vh></v> -<v t="davidmcnab.041004144551.4" a="E"><vh>class I2PSamClient</vh> -<v t="davidmcnab.041004144551.5"><vh>attributes</vh></v> -<v t="davidmcnab.041004144551.6"><vh>__init__</vh></v> -<v t="davidmcnab.041004144551.7"><vh>createSession</vh></v> -<v t="davidmcnab.041004144551.8"><vh>destroySession</vh></v> -<v t="davidmcnab.041004144551.9"><vh>send</vh></v> -<v t="davidmcnab.041004144551.10"><vh>receive</vh></v> -<v t="davidmcnab.041004144551.11" a="E"><vh>SAM methods</vh> -<v t="davidmcnab.041004144551.12"><vh>samHello</vh></v> -<v t="davidmcnab.041004144551.13"><vh>samSessionCreate</vh></v> -<v t="davidmcnab.041004144551.14"><vh>samDestGenerate</vh></v> -<v t="davidmcnab.041004144551.15"><vh>samRawSend</vh></v> -<v t="davidmcnab.041004144551.16"><vh>samRawCheck</vh></v> -<v t="davidmcnab.041004144551.17"><vh>samRawReceive</vh></v> -<v t="davidmcnab.041004144551.18"><vh>samDatagramSend</vh></v> -<v t="davidmcnab.041004144551.19"><vh>samDatagramCheck</vh></v> -<v t="davidmcnab.041004144551.20"><vh>samDatagramReceive</vh></v> -<v t="davidmcnab.041204020513"><vh>samStreamConnect</vh></v> -<v t="davidmcnab.041204204235"><vh>samStreamAccept</vh></v> -<v t="davidmcnab.041204044735"><vh>samStreamSend</vh></v> -<v t="davidmcnab.041204050339"><vh>samStreamClose</vh></v> -<v t="davidmcnab.041004144551.21"><vh>samNamingLookup</vh></v> -<v t="davidmcnab.041004144551.22"><vh>samParse</vh></v> -<v t="davidmcnab.041004144551.23"><vh>samSend</vh></v> -<v t="davidmcnab.041004144551.24"><vh>samCreateArgsList</vh></v> -</v> -<v t="davidmcnab.041004144551.25" a="E"><vh>Receiver Side</vh> -<v t="davidmcnab.041004144551.26"><vh>threadRx</vh></v> -<v t="davidmcnab.041004144551.27"><vh>on_HELLO</vh></v> -<v t="davidmcnab.041004144551.28"><vh>on_SESSION</vh></v> -<v t="davidmcnab.041004144551.29"><vh>on_STREAM</vh></v> -<v t="davidmcnab.041004144551.30"><vh>on_DATAGRAM</vh></v> -<v t="davidmcnab.041004144551.31"><vh>on_RAW</vh></v> -<v t="davidmcnab.041004144551.32"><vh>on_NAMING</vh></v> -<v t="davidmcnab.041004144551.33"><vh>on_DEST</vh></v> -</v> -<v t="davidmcnab.041004144551.34" a="E"><vh>Utility Methods</vh> -<v t="davidmcnab.041204042212"><vh>samAllocId</vh></v> -<v t="davidmcnab.041004144551.35"><vh>_recvline</vh></v> -<v t="davidmcnab.041004144551.36"><vh>_recvbytes</vh></v> -<v t="davidmcnab.041004144551.37"><vh>_sendbytes</vh></v> -<v t="davidmcnab.041004144551.38"><vh>_sendline</vh></v> -</v> -</v> -<v t="davidmcnab.041204042212.1" a="E"><vh>class I2PSAMStream</vh> -<v t="davidmcnab.041204042212.2"><vh>__init__</vh></v> -<v t="davidmcnab.041204044735.1"><vh>send</vh></v> -<v t="davidmcnab.041204050339.1"><vh>recv</vh></v> -<v t="davidmcnab.041304235615"><vh>readline</vh></v> -<v t="davidmcnab.041204050339.2"><vh>close</vh></v> -<v t="davidmcnab.041204050511"><vh>__del__</vh></v> -<v t="davidmcnab.041204044135"><vh>_notifyIncomingData</vh></v> -</v> -<v t="davidmcnab.041004144551.39" a="E"><vh>class I2PRemoteSession</vh> -<v t="davidmcnab.041004144551.40"><vh>__init__</vh></v> -<v t="davidmcnab.041004144551.41"><vh>send</vh></v> -<v t="davidmcnab.041004144551.42"><vh>recv</vh></v> -<v t="davidmcnab.041004144551.43"><vh>destroy</vh></v> -</v> -<v t="davidmcnab.041004144551.44" a="E"><vh>Functions</vh> -<v t="davidmcnab.041004144551.45"><vh>log</vh></v> -<v t="davidmcnab.041004144551.46"><vh>logException</vh></v> -<v t="davidmcnab.041004144551.47"><vh>demoNAMING</vh></v> -<v t="davidmcnab.041004144551.48"><vh>demoRAW</vh></v> -<v t="davidmcnab.041004144551.49"><vh>demoDATAGRAM</vh></v> -<v t="davidmcnab.041004144551.50"><vh>demoSTREAM</vh></v> -<v t="davidmcnab.041204203651"><vh>demoSTREAM_thread</vh></v> -<v t="davidmcnab.041004144551.51"><vh>demo</vh></v> -</v> -<v t="davidmcnab.041004144551.52"><vh>MAINLINE</vh></v> -</v> -</v> -</vnodes> -<tnodes> -<t tx="davidmcnab.041004143447"></t> -<t tx="davidmcnab.041004144338">@first #!/usr/bin/env jython -r""" -Implements I2P SAM Server. (refer U{http://drupal.i2p.net/node/view/144}) - -Also contains useful classes for jython programs, -which wrap the I2P java classes into more python-compatible -paradigms. - -If you run this module (or the i2psam.jar file created from it) -without arguments, it'll run an I2P SAM server bridge, listening -on port 7656. - -The file i2psamclient.py contains python client classes and a -demo program. - -Latest vers of this file is available from U{http://www.freenet.org.nz/i2p/i2psam.py} -Latest epydoc-generated doco at U{http://www.freenet.org.nz/i2p/i2pjyDoc} - -The i2psam.jar file is built from this module with the following -command (requires jython and java 1.4.x+ to be installed):: - - CLASSPATH=/path/to/i2p.jar:/path/to/mstreaming.jar \ - jythonc -jar i2psam.jar --all -A net.invisiblenet i2psam.py - -""" - -@others - - -</t> -<t tx="davidmcnab.041004144338.1"># python imports -import sys, os, time, Queue, thread, threading, StringIO, traceback, getopt -from SocketServer import ThreadingTCPServer, StreamRequestHandler - -# java imports -import java - -# i2p-specific imports -import net.i2p -import net.i2p.client # to shut up epydoc -#import net.i2p.client.I2PClient -#import net.i2p.client.I2PClientFactory -#import net.i2p.client.I2PSessionListener -import net.i2p.client.naming -import net.i2p.client.streaming -import net.i2p.crypto -import net.i2p.data - -# handy shorthand refs -i2p = net.i2p -jI2PClient = i2p.client.I2PClient - -# import my own helper hack module -#import I2PHelper - -</t> -<t tx="davidmcnab.041004144338.2">clientFactory = i2p.client.I2PClientFactory - -#i2phelper = I2PHelper() - -PROP_RELIABILITY_BEST_EFFORT = i2p.client.I2PClient.PROP_RELIABILITY_BEST_EFFORT -PROP_RELIABILITY_GUARANTEED = i2p.client.I2PClient.PROP_RELIABILITY_GUARANTEED - -version = "0.1.0" - -# host/port that our socketserver listens on -i2psamhost = "127.0.0.1" -i2psamport = 7656 - -# host/port that I2P's I2CP listens on -i2cpHost = "127.0.0.1" -i2cpPort = 7654 - -#print "i2cpPort=%s" % repr(i2cpPort) - -# ------------------------------------------ -# logging settings - -# 1=v.quiet, 2=normal, 3=verbose, 4=debug, 5=painful -verbosity = 2 - -# change to a filename to log there instead -logfile = sys.stdout - -# when set to 1, and when logfile != sys.stdout, log msgs are written -# both to logfile and console stdout -log2console = 1 - -# don't touch this! -loglock = threading.Lock() - - -</t> -<t tx="davidmcnab.041004144338.3"></t> -<t tx="davidmcnab.041004144338.4">class JavaWrapper: - """ - Wraps a java object as attribute '_item', and forwards - __getattr__ to it. - - All the classes here derive from this - """ - def __init__(self, item): - self._item = item - - def __getattr__(self, attr): - return getattr(self._item, attr) - - -</t> -<t tx="davidmcnab.041004144338.5">class I2PDestination(JavaWrapper): - """ - Wraps java I2P destination objects, with a big difference - these - objects store the private parts. - """ - @others - -</t> -<t tx="davidmcnab.041004144338.6">def __init__(self, **kw): - """ - Versatile constructor - - Keywords (choose only one option): - - (none) - create a whole new dest - - dest, private - wrap an existing I2P java dest with private stream - (private is a byte array) - - bin - reconstitute a public-only dest from a binary string - - binfile - reconstitute public-only from a binary file - - binprivate - reconsistitute private dest from binary string - - binfileprivate - reconsistitute private dest from binary file pathname - - base64 - reconstitute public-only from base64 string - - base64file - reconstitute public-only from file containing base64 - - base64private - reconstitute private from string containing base64 - - base64fileprivate - reconstitute private from file containing base64 - - also: - - client - a java net.i2p.client.I2PClient object - (avoids need for temporary client object when creating new dests) - """ - dest = i2p.data.Destination() - JavaWrapper.__init__(self, dest) - self._private = None - - if kw.has_key('dest'): - self._item = kw['dest'] - if kw.has_key('private'): - self._private = kw['private'] - - elif kw.has_key('bin'): - self.fromBin(kw['bin']) - - elif kw.has_key('binfile'): - self.fromBinFilePrivate(kw['binfile']) - - elif kw.has_key('binprivate'): - self.fromBinPrivate(kw['binprivate']) - - elif kw.has_key('binfileprivate'): - self.fromBinFilePrivate(kw['binfileprivate']) - - elif kw.has_key('base64'): - self.fromBase64(kw['base64']) - - elif kw.has_key('base64file'): - self.fromBase64File(kw['base64file']) - - elif kw.has_key('base64private'): - self.fromBase64Private(kw['base64private']) - - elif kw.has_key('base64fileprivate'): - self.fromBase64FilePrivate(kw['base64fileprivate']) - - else: - # create a whole new one, with a temporary client object (if needed) - if kw.has_key('client'): - client = kw['client'] - else: - client = clientFactory.createClient() - bytestream = java.io.ByteArrayOutputStream() - self._item = client.createDestination(bytestream) - self._private = bytestream.toByteArray() - -</t> -<t tx="davidmcnab.041004144338.7"></t> -<t tx="davidmcnab.041004144338.8">def toBin(self): - """ - Returns a binary string of dest - """ - return bytearray2str(self.toByteArray()) - -</t> -<t tx="davidmcnab.041004144338.9">def toBinFile(self, path): - """ - Writes out public binary to a file - """ - f = open(path, "wb") - f.write(self.toBin()) - f.flush() - f.close() - -</t> -<t tx="davidmcnab.041004144338.10">def toBinPrivate(self): - """ - Returns the private key string as binary - """ - if self._private == None: - raise NoPrivateKey - return bytearray2str(self._private) - -</t> -<t tx="davidmcnab.041004144338.11">def toBinFilePrivate(self, path): - """ - Writes out a binary file with the dest info - """ - f = open(path, "wb") - f.write(self.toBinPrivate()) - f.flush() - f.close() - -</t> -<t tx="davidmcnab.041004144338.12">def toBase64(self): - """ - Returns base64 string of public part - """ - return self._item.toBase64() - -</t> -<t tx="davidmcnab.041004144338.13">def toBase64Private(self): - """ - Exports dest as base64, including private stuff - """ - if self._private == None: - raise NoPrivateKey - return i2p.data.Base64.encode(self._private) - -</t> -<t tx="davidmcnab.041004144338.14">def toBase64File(self, path): - """ - Exports dest to file as base64 - """ - f = open(path, "wb") - f.write(self.toBase64()) - f.flush() - f.close() - -</t> -<t tx="davidmcnab.041004144338.15">def toBase64FilePrivate(self, path): - """ - Writes out a base64 file with the private dest info - """ - f = open(path, "wb") - f.write(self.toBase64Private()) - f.flush() - f.close() - -</t> -<t tx="davidmcnab.041004144338.16"></t> -<t tx="davidmcnab.041004144338.17">def fromBin(self, bin): - """ - Loads this dest from a binary string - """ - self._item.fromByteArray(str2bytearray(bin)) - self._private = None - -</t> -<t tx="davidmcnab.041004144338.18">def fromBinFile(self, path): - """ - Loads public part from file containing binary - """ - f = open(path, "rb") - self.fromBin(f.read()) - f.close() - -</t> -<t tx="davidmcnab.041004144338.19">def fromBinPrivate(self, s): - """ - Loads this dest object from a base64 private key string - """ - bytes = str2bytearray(s) - self._private = bytes - stream = java.io.ByteArrayInputStream(bytes) - self._item.readBytes(stream) - -</t> -<t tx="davidmcnab.041004144338.20">def fromBinFilePrivate(self, path): - """ - Loads this dest object, given the pathname of a file containing - a binary destkey - """ - self.fromBinPrivate(open(path, "rb").read()) - -</t> -<t tx="davidmcnab.041004144338.21">def fromBase64(self, b64): - """ - Loads this dest from a base64 string - """ - self._item.fromBase64(b64) - self._private = None - -</t> -<t tx="davidmcnab.041004144338.22">def fromBase64File(self, path): - """ - Loads public part from file containing base64 - """ - f = open(path, "rb") - self.fromBase64(f.read()) - f.close() - -</t> -<t tx="davidmcnab.041004144338.23">def fromBase64Private(self, s): - """ - Loads this dest object from a base64 private key string - """ - bytes = i2p.data.Base64.decode(s) - self._private = bytes - stream = java.io.ByteArrayInputStream(bytes) - self._item.readBytes(stream) - -</t> -<t tx="davidmcnab.041004144338.24">def fromBase64FilePrivate(self, path): - """ - Loads this dest from a base64 file containing private key - """ - self.fromBase64Private(open(path, "rb").read()) - -</t> -<t tx="davidmcnab.041004144338.25"></t> -<t tx="davidmcnab.041004144338.26">def sign(self, s): - """ - Signs a string using this dest's priv key - """ - # get byte stream - bytes = str2bytearray(s) - - # stream up our private bytes - stream = java.io.ByteArrayInputStream(self._private) - - # temporary dest object - d = i2p.data.Destination() - - # suck the public part off the stream - d.readBytes(stream) - - # temporary private key object - privkey = i2p.data.PrivateKey() - privkey.readBytes(stream) - - # now we should just have the signing key portion left in the stream - signingkey = i2p.data.SigningPrivateKey() - signingkey.readBytes(stream) - - # create DSA engine - dsa = i2p.crypto.DSAEngine() - - sig = dsa.sign(bytes, signingkey) - - rawsig = bytearray2str(sig.getData()) - - return rawsig - -</t> -<t tx="davidmcnab.041004144338.27">def verify(self, s, sig): - """ - Verifies a string against this dest, to test if it was actually - signed by whoever has the dest privkey - """ - # get byte stream from data - databytes = str2bytearray(s) - - # get signature stream from sig - sigstream = java.io.ByteArrayInputStream(str2bytearray(sig)) - - # make a signature object - signature = i2p.data.Signature() - signature.readBytes(sigstream) - - # get signature verify key - pubkey = self.getSigningPublicKey() - - #log(4, "databytes=%s, pubkey=%s" % (repr(databytes), repr(pubkey))) - - # now get a verification - dsa = i2p.crypto.DSAEngine() - result = dsa.verifySignature(signature, databytes, pubkey) - - return result - - - -</t> -<t tx="davidmcnab.041004144338.28"></t> -<t tx="davidmcnab.041004144338.29">def hasPrivate(self): - """ - Returns True if this dest has private parts, False if not - """ - - if self._private: - return 1 - else: - return 0 -</t> -<t tx="davidmcnab.041004144338.30">class I2PClient(JavaWrapper): - """ - jython-comfortable wrapper for java I2P client class - """ - @others - -</t> -<t tx="davidmcnab.041004144338.31">def __init__(self, **kw): - """ - I2PClient constructor - - No args or keywords as yet - """ - client = clientFactory.createClient() - JavaWrapper.__init__(self, client) - -</t> -<t tx="davidmcnab.041004144338.32">def createDestination(self, **kw): - """ - Creates a destination, either a new one, or from a bin or base64 file - - Keywords: - - see L{I2PDestination} constructor - """ - return I2PDestination(**kw) - -</t> -<t tx="davidmcnab.041004144338.33">def createSession(self, dest, sessionClass=None, **kw): - """ - Create a session - - Arguments: - - dest - an L{I2PDestination} object which MUST contain a private portion - - sessionClass - if given, this should be a subclass - of I2PSession. This allows you to implement your own handlers. - - Keywords: - - session options (refer javadocs) - """ - if sessionClass is None: - sessionClass = I2PSession - - if not dest.hasPrivate(): - raise NoPrivateKey("Dest object has no private key") - - #print kw - #session = self._item.createSession(destStream, dict2props(kw)) - session = sessionClass(client=self, dest=dest, **kw) - return session - #return sessionClass(session=session) - -</t> -<t tx="davidmcnab.041004144338.34">class I2PSession(JavaWrapper): - """ - Wraps an I2P client session - - You can subclass this, overriding the on_* handler callbacks, - and pass it as an argument to I2PClient.createSession - - In the default 'on_message' callback, message retrieval is - synchronous - inbound messages get written to an internal queue, - which you can checked with numMessages() and retrieved from via - getMessage(). You may override on_message() if you - want to handle incoming messages asynchronously yourself. - - Note - as far as I can tell, this class should be thread-safe. - """ - @others -</t> -<t tx="davidmcnab.041004144338.35">host = i2cpHost -port = i2cpPort -</t> -<t tx="davidmcnab.041004144338.36">def __init__(self, **kw): - """ - I2PSession constructor - - Keywords: - - either: - - session - a java i2p session object - - or: - - client - an L{I2PClient} object - - dest - an L{I2PDestination} object - Also: - - listener - an L{I2PSessionListener} object. - - Router-level options: - - reliability - one of 'guaranteed' and 'besteffort' (default 'besteffort') - - host - host on which router is running - - port - port on which router is listening - """ - # - # grab options destined for java class - # - options = {} - - reliability = takeKey(kw, 'reliability', 'besteffort') - if reliability == 'guaranteed': - reliability = jI2PClient.PROP_RELIABILITY_GUARANTEED - else: - reliability = jI2PClient.PROP_RELIABILITY_BEST_EFFORT - options[jI2PClient.PROP_RELIABILITY] = reliability - - host = takeKey(kw, 'host', self.host) - options[jI2PClient.PROP_TCP_HOST] = host - - port = takeKey(kw, 'port', self.port) - options[jI2PClient.PROP_TCP_PORT] = str(port) - - if kw.has_key('reliability'): - reliability = kw['reliability'] - - if kw.has_key('listener'): - listener = kw['listener'] - del kw['listener'] - else: - listener = I2PSessionListener() - - #print options - - # - # other keywords handled locally - # - if kw.has_key('session'): - session = kw['session'] - del kw['session'] - JavaWrapper.__init__(self, session) - elif kw.has_key('client') and kw.has_key('dest'): - client = kw['client'] - dest = kw['dest'] - del kw['client'] - del kw['dest'] - destStream = java.io.ByteArrayInputStream(dest._private) - session = self._item = client._item.createSession(destStream, dict2props(options)) - #client.createSession(dest, dict2props(options)) - else: - raise Exception("implementation incomplete") - - # set up a listener - self.setSessionListener(listener) - - # set up a queue for inbound msgs - self.qInbound = Queue.Queue() - self.lockInbound = threading.Lock() - self.nInboundMessages = 0 - - self.lockOutbound = threading.Lock() - - - -</t> -<t tx="davidmcnab.041004144338.37">def sendMessage(self, dest, payload): - """ - Sends a message to another dest - - Arguments: - - dest - an L{I2PDestination} object - - payload - a string to send - """ - dest = dest._item - payload = str2bytearray(payload) - self.lockOutbound.acquire() - try: - res = self._item.sendMessage(dest, payload) - except: - self.lockOutbound.release() - raise - self.lockOutbound.release() - return res -</t> -<t tx="davidmcnab.041004144338.38">def numMessages(self): - """ - Returns the number of unretrieved inbound messages - """ - self.lockInbound.acquire() - n = self.nInboundMessages - self.lockInbound.release() - return n -</t> -<t tx="davidmcnab.041004144338.39">def getMessage(self, blocking=1): - """ - Returns the next available inbound message. - - If blocking is set to 1 (default), blocks - till another message comes in. - - If blocking is set to 0, returns None if there - are no available messages. - """ - if blocking: - msg = self.qInbound.get() - #print "getMessage: acquiring lock" - self.lockInbound.acquire() - #print "getMessage: got lock" - self.nInboundMessages -= 1 - else: - #print "getMessage: acquiring lock" - self.lockInbound.acquire() - #print "getMessage: got lock" - if self.nInboundMessages > 0: - msg = self.qInbound.get() - self.nInboundMessages -= 1 - else: - msg = None - self.lockInbound.release() - #print "getMessage: released lock" - return msg - -</t> -<t tx="davidmcnab.041004144338.40">def setSessionListener(self, listener): - """ - Designates an L{I2PSessionListener} object to listen to this session - """ - self.listener = listener - listener.addSession(self) - self._item.setSessionListener(listener) - - -</t> -<t tx="davidmcnab.041004144338.41">def destroySession(self): - """ - Destroys an existing session - - Note that due to a jython quirk, calls to destroySession might - trigger a TypeError relating to arg mismatch - we ignore such - errors here because by the time the exception happens, the - session has already been successfully closed - """ - try: - self._item.destroySession() - except TypeError: - pass - -</t> -<t tx="davidmcnab.041004144338.42"># -# handler methods which you should override -# - -@others -</t> -<t tx="davidmcnab.041004144338.43">def on_message(self, msg): - """ - Callback for when a message arrives. - - Appends the message to the inbound queue, which you can check - with the numMessages() method, and read with getMessage() - - You should override this if you want to handle inbound messages - asynchronously. - - Arguments: - - msg - a string that was sent by peer - """ - #print "on_message: msg=%s" % msg - self.lockInbound.acquire() - #print "on_message: got lock" - self.qInbound.put(msg) - self.nInboundMessages += 1 - self.lockInbound.release() - #print "on_message: released lock" - -</t> -<t tx="davidmcnab.041004144338.44">def on_abuse(self, severity): - """ - Callback indicating abuse is happening - - Arguments: - - severity - an int of abuse level, 1-100 - """ - print "on_abuse: severity=%s" % severity - -</t> -<t tx="davidmcnab.041004144338.45">def on_disconnected(self): - """ - Callback indicating remote peer disconnected - """ - print "on_disconnected" - -</t> -<t tx="davidmcnab.041004144338.46">def on_error(self, message, error): - """ - Callback indicating an error occurred - """ - print "on_error: message=%s error=%s" % (message, error) - -</t> -<t tx="davidmcnab.041004144338.47">class I2PSessionListener(i2p.client.I2PSessionListener): - """ - Wraps a java i2p.client.I2PSessionListener object - """ - def __init__(self, *sessions): - self.sessions = list(sessions) - - def addSession(self, session): - """ - Adds an L{I2PSession} object to the list of sessions to listen on - - Note - you must also invoke the session's setSessionListener() method - (see I2PSession.setSessionListener) - """ - if session not in self.sessions: - self.sessions.append(session) - - def delSession(self, session): - """ - Stop listening to a given session - """ - if session in self.sessions: - del self.sessions.index[session] - - def messageAvailable(self, session, msgId, size): - """ - Callback from java:: - public void messageAvailable( - I2PSession session, - int msgId, - long size) - """ - #print "listener - messageAvailable" - - # try to find session in our sessions table - sessions = filter(lambda s, session=session: s._item == session, self.sessions) - if sessions: - #print "compare to self.session->%s" % (session == self.session._item) - - # found a matching session - retrieve it - session = sessions[0] - - # retrieve message and pass to callback - msg = session.receiveMessage(msgId) - msgStr = bytearray2str(msg) - session.on_message(msgStr) - else: - print "messageAvailable: unknown session=%s msgId=%s size=%s" % (session, msgId, size) - - def reportAbuse(self, session, severity): - """ - Callback from java:: - public void reportAbuse( - I2PSession session, - int severity) - """ - if self.session: - self.session.on_abuse(severity) - else: - print "reportAbuse: unknown session=%s severity=%s" % (session, severity) - - def disconnected(self, session): - """ - Callback from java:: - public void disconnected(I2PSession session) - """ - if self.session: - self.session.on_disconnected() - else: - print "disconnected: unknown session=%s" % session - - def errorOccurred(session, message, error): - """ - Callback from java:: - public void errorOccurred( - I2PSession session, - java.lang.String message, - java.lang.Throwable error) - """ - if self.session: - self.session.on_error(message, error) - else: - print "errorOccurred: message=%s error=%s" % (message, error) - -</t> -<t tx="davidmcnab.041004144338.48"></t> -<t tx="davidmcnab.041004144338.49">class I2PSocket: - """ - Wraps I2P streaming API into a form resembling python sockets - """ - @others -</t> -<t tx="davidmcnab.041004144338.50">host = i2cpHost -port = i2cpPort - -</t> -<t tx="davidmcnab.041004144338.51">def __init__(self, dest=None, **kw): - """ - Create an I2P streaming socket - - Arguments: - - dest - a private destination to associate with this socket - - Keywords: - - host - hostname on which i2cp is listening (default self.host) - - port - port on which i2cp listens (default self.port) - - Internally used keywords (used for wrapping an accept()ed connection): - - dest - - remdest - - sock - - instream - - outstream - """ - # set up null attribs - self.sockmgr = None - self.instream = None - self.outstream = None - self.sock = None - self._connected = 0 - self._blocking = 1 - - # save dest (or lack thereof) - self.dest = dest - - if kw.has_key('sock') \ - and kw.has_key('remdest') \ - and kw.has_key('instream') \ - and kw.has_key('outstream'): - - # wrapping an accept()'ed connection - log(4, "accept()'ed a connection, wrapping...") - - self.sock = kw['sock'] - self.dest = dest - self.remdest = kw['remdest'] - self.instream = kw['instream'] - self.outstream = kw['outstream'] - else: - log(4, "creating new I2PSocket %s" % dest) - - # process keywords - self.host = kw.get('host', self.host) - self.port = int(kw.get('port', self.port)) - - # we need a factory, don't we? - self.sockmgrFact = i2p.client.streaming.I2PSocketManagerFactory() - -</t> -<t tx="davidmcnab.041004144338.52">def bind(self, dest=None): - """ - 'binds' the socket to a dest - - dest is an I2PDestination object, which you may specify in the constructor - instead of here. However, we give you the option of specifying here for - some semantic compatibility with python sockets. - """ - if dest is not None: - self.dest = dest - elif not self.dest: - # create new dest, client should interrogate it at some time - log(4, "bind: socket has no dest, creating one") - self.dest = I2PDestination() -</t> -<t tx="davidmcnab.041004144338.53">def listen(self, *args, **kw): - """ - Sets up the object to receive connections - """ - # sanity checks - if self.sockmgr: - raise I2PSocketError(".sockmgr already present - have you already called listen?") - if not self.dest: - raise I2PSocketError("socket is not bound to a destination") - - log(4, "listening on socket") - - # create the socket manager - self._createSockmgr() - </t> -<t tx="davidmcnab.041004144338.54">def accept(self): - """ - Waits for incoming connections, and returns a new I2PSocket object - with the connection - """ - # sanity check - if not self.sockmgr: - raise I2PSocketError(".listen() has not been called on this socket") - - # accept a conn and get its streams - sock = self.sockmgr.getServerSocket().accept() - instream = sock.getInputStream() - outstream = sock.getOutputStream() - remdest = I2PDestination(dest=sock.getPeerDestination()) - - # wrap it and return it - sockobj = I2PSocket(dest=self.dest, - remdest=remdest, - sock=sock, - instream=instream, - outstream=outstream) - self._connected = 1 - return sockobj - -</t> -<t tx="davidmcnab.041004144338.55">def connect(self, remdest): - """ - Connects to a remote destination - - This has one totally major difference from the normal socket - paradigm, and that is that you can have n outbound connections - to different dests. - """ - # sanity check - if self.sockmgr: - raise I2PSocketError(".sockmgr already present - have you already called listen/connect?") - - # create whole new dest if none was provided to constructor - if self.dest is None: - log(4, "connect: creating whole new dest") - self.dest = I2PDestination() - - # create the socket manager - self._createSockmgr() - - # do the connect - #print "remdest._item = %s" % repr(remdest._item) - - opts = net.i2p.client.streaming.I2PSocketOptions() - try: - log(4, "trying to connect to %s" % remdest.toBase64()) - sock = self.sock = self.sockmgr.connect(remdest._item, opts) - self.remdest = remdest - except: - logException(2, "apparent exception, continuing...") - - self.instream = sock.getInputStream() - self.outstream = sock.getOutputStream() - - sockobj = I2PSocket(dest=self.dest, - remdest=remdest, - sock=sock, - instream=self.instream, - outstream=self.outstream) - self._connected = 1 - return sockobj -</t> -<t tx="davidmcnab.041004144338.56">def recv(self, nbytes): - """ - Reads nbytes of data from socket - """ - # sanity check - if not self.instream: - raise I2PSocketError("Socket is not connected") - - # for want of better methods, read bytewise - chars = [] - while nbytes > 0: - byte = self.instream.read() - if byte < 0: - break # got all we're gonna get - char = chr(byte) - chars.append(char) - #print "read: got a byte %s (%s)" % (byte, repr(char)) - nbytes -= 1 - - # got it all - buf = "".join(chars) - #print "recv: buf=%s" % repr(buf) - return buf - - -</t> -<t tx="davidmcnab.041004144338.57">def send(self, buf): - """ - Sends buf thru socket - """ - # sanity check - if not self.outstream: - raise I2PSocketError("Socket is not connected") - - # and write it out - log(4, "send: writing '%s' to outstream..." % repr(buf)) - outstream = self.outstream - for c in buf: - outstream.write(ord(c)) - - # flush just in case - log(4, "send: flushing...") - self.outstream.flush() - - log(4, "send: done") - -</t> -<t tx="davidmcnab.041004144338.58">def available(self): - """ - Returns the number of bytes available for recv() - """ - #print "available: sock is %s" % repr(self.sock) - - return self.instream.available() - - -</t> -<t tx="davidmcnab.041004144338.59">def close(self): - """ - Closes the socket - """ - # sanity check - #if not self._connected: - # raise I2PSocketError("Socket is not connected") - - # shut up everything - try: - self.instream.close() - except: - pass - try: - self.outstream.close() - except: - pass - try: - self.sock.close() - except: - pass -</t> -<t tx="davidmcnab.041004144338.60">def _createSockmgr(self): - - if getattr(self, 'sockmgr', None): - return - - #options = {jI2PClient.PROP_TCP_HOST: self.host, - # jI2PClient.PROP_TCP_PORT: self.port} - options = {} - props = dict2props(options) - - # get a java stream thing from dest - stream = java.io.ByteArrayInputStream(self.dest._private) - - # create socket manager thing - self.sockmgr = self.sockmgrFact.createManager(stream, self.host, self.port, props) -</t> -<t tx="davidmcnab.041004144338.61"></t> -<t tx="davidmcnab.041004144338.62">class I2PSamServer(ThreadingTCPServer): - """ - A server which makes I2CP available via a socket - """ - @others -</t> -<t tx="davidmcnab.041004144338.63">host = i2psamhost -port = i2psamport - -i2cphost = i2cpHost -i2cpport = i2cpPort - -version = version - - -</t> -<t tx="davidmcnab.041004144338.64">def __init__(self, i2pclient=None, **kw): - """ - Create the client listener object - - Arguments: - - i2pclient - an I2PClient object - optional - if not - given, one will be created - - Keywords: - - host - host to listen on for client conns (default self.host ('127.0.0.1') - - port - port to listen on for client conns (default self.port (7656) - - i2cphost - host to talk to i2cp on (default self.i2cphost ('127.0.0.1')) - - i2cpport - port to talk to i2cp on (default self.i2cphost ('127.0.0.1')) - """ - - # create an I2PClient object if none given - if i2pclient is None: - i2pclient = I2PClient() - self.i2pclient = i2pclient - - # get optional host/port for client and i2cp - self.host = kw.get('host', self.host) - self.port = int(kw.get('port', self.port)) - self.i2cphost = kw.get('i2cphost', self.i2cphost) - self.i2cpport = int(kw.get('i2cpport', self.i2cpport)) - - # create record of current sessions, and a lock for it - self.sessions = {} - self.sessionsLock = threading.Lock() - self.streams = {} - self.streamsLock = threading.Lock() - self.samNextId = 1 - self.samNextIdLock = threading.Lock() - - # and create the server - try: - ThreadingTCPServer.__init__( - self, - (self.host, self.port), - I2PSamClientHandler) - except: - log(4, "crashed with host=%s, port=%s" % (self.host, self.port)) - raise - -</t> -<t tx="davidmcnab.041004144338.65">def run(self): - """ - Run the SAM server. - - when connections come in, they are automatically - accepted, and an L{I2PClientHandler} object created, - and its L{handle} method invoked. - """ - log(4, "Listening for client requests on %s:%s" % (self.host, self.port)) - self.serve_forever() - - -</t> -<t tx="davidmcnab.041004144338.66">def finish_request(self, request, client_address): - """Finish one request by instantiating RequestHandlerClass.""" - try: - self.RequestHandlerClass(request, client_address, self) - except: - pass - log(3, "Client session terminated") -</t> -<t tx="davidmcnab.041004144338.67">def samAllocId(self): - """ - Allocates a new unique id as required by SAM protocol - """ - self.samNextIdLock.acquire() - id = self.samNextId - self.samNextId += 1 - self.samNextIdLock.release() - return id -</t> -<t tx="davidmcnab.041004144338.68">class I2PSamClientHandler(StreamRequestHandler): - r""" - Manages a single socket connection from a client. - - When a client connects to the SAM server, the I2PSamServer - object creates an instance of this class, and invokes its - handle method. See L{handle}. - - Note that if a client terminates its connection to the server, the server - will destroy all current connections initiated by that client - - Size values are decimal - Connection is persistent - """ - @others</t> -<t tx="davidmcnab.041004144338.69">def handle(self): - """ - Reads command/data messages from SAM Client, executes these, - and sends back responses. - - Plants callback hooks into I2PSession objects, so that when - data arrives via I2P, it can be immediately sent to the client. - """ - self.localsessions = {} - self.globalsessions = self.server.sessions - - self.localstreams = {} # keyed by sam stream id - self.globalstreams = self.server.streams - - self.samSessionIsOpen = 0 - self.samSessionStyle = '' - - # localise the id allocator - self.samAllocId = self.server.samAllocId - - # need a local sending lock - self.sendLock = threading.Lock() - - log(5, "Got req from %s" % repr(self.client_address)) - - try: - self.namingService = i2p.client.naming.HostsTxtNamingService() - except: - logException(2, "Failed to create naming service object") - - try: - while 1: - # get req - req = self.rfile.readline().strip() - flds = [s.strip() for s in req.split(" ")] - cmd = flds[0] - if cmd in ['HELLO', 'SESSION', 'STREAM', 'DATAGRAM', 'RAW', 'NAMING', 'DEST']: - topic, subtopic, args = self.samParse(flds) - method = getattr(self, "on_"+cmd, None) - method(topic, subtopic, args) - else: - method = getattr(self, "on_"+cmd, None) - if method: - method(flds) - else: - # bad shit - self.wfile.write("error unknown command '%s'\n" % cmd) - - except IOError: - log(3, "Client connection terminated") - except ValueError: - pass - except: - logException(4, "Client req handler crashed") - self.wfile.write("error\n") - - # clean up sessions - for dest in self.localsessions.keys(): - if dest in self.globalsessions.keys(): - log(4, "forgetting global dest %s" % dest[:30]) - del self.globalsessions[dest] - - self.finish() - #thread.exit() - -</t> -<t tx="davidmcnab.041004144338.70">def on_genkeys(self, flds): - - log(4, "entered") - - server = self.server - client = server.i2pclient - globalsessions = server.sessions - sessionsLock = server.sessionsLock - - read = self.rfile.read - readline = self.rfile.readline - write = self.wfile.write - flush = self.wfile.flush - - # genkeys - try: - dest = I2PDestination() - priv = dest.toBase64Private() - pub = dest.toBase64() - write("ok %s %s\n" % (pub, priv)) - except: - write("error exception\n") -</t> -<t tx="davidmcnab.041004144338.71">def on_createsession(self, flds): - - log(4, "entered") - - server = self.server - client = server.i2pclient - globalsessions = server.sessions - sessionsLock = server.sessionsLock - - read = self.rfile.read - readline = self.rfile.readline - write = self.wfile.write - flush = self.wfile.flush - - sessionsLock.acquire() - - try: - b64priv = flds[1] - - # spit if someone else already has this dest - if b64priv in globalsessions.keys(): - write("error dest in use\n") - elif b64priv in self.localsessions.keys(): - # duh, already open locally, treat as ok - write("ok\n") - else: - # whole new session - set it up - dest = I2PDestination(base64private=b64priv) - log(4, "Creating session on dest '%s'" % b64priv[:40]) - session = client.createSession(dest) - log(4, "Connecting session on dest '%s'" % b64priv[:40]) - session.connect() - log(4, "Session on dest '%s' now live" % b64priv[:40]) - - # and remember it - self.localsessions[b64priv] = session - globalsessions[b64priv] = session - - # and tell the client the good news - write("ok\n") - except: - logException(4, "createsession fail") - write("error exception\n") - - sessionsLock.release() -</t> -<t tx="davidmcnab.041004144338.72">def on_destroysession(self, flds): - - log(4, "entered") - - server = self.server - client = server.i2pclient - globalsessions = server.sessions - sessionsLock = server.sessionsLock - - read = self.rfile.read - readline = self.rfile.readline - write = self.wfile.write - flush = self.wfile.flush - - sessionsLock.acquire() - - try: - b64priv = flds[1] - - # spit if session not known - if not globalsessions.has_key(b64priv): - # no such session presently exists anywhere - write("error nosuchsession\n") - elif not self.localsessions.has_key(b64priv): - # session exists, but another client owns it - write("error notyoursession\n") - else: - # session exists and we own it - session = self.localsessions[b64priv] - del self.localsessions[b64priv] - del globalsessions[b64priv] - try: - session.destroySession() - write("ok\n") - except: - raise - except: - logException(4, "destroy session failed") - write("error exception\n") - - sessionsLock.release() - - log(4, "done") - -</t> -<t tx="davidmcnab.041004144338.73">def on_send(self, flds): - - #log(4, "entered: %s" % repr(flds)) - log(4, "entered") - - server = self.server - client = server.i2pclient - globalsessions = server.sessions - sessionsLock = server.sessionsLock - - read = self.rfile.read - readline = self.rfile.readline - write = self.wfile.write - flush = self.wfile.flush - - sessionsLock.acquire() - - session = None - try: - size = int(flds[1]) - b64priv = flds[2] - b64peer = flds[3] - msg = self._recvbytes(size) - - # spit if session not known - if not globalsessions.has_key(b64priv): - # no such session presently exists anywhere - log(4, "no such session") - write("error nosuchsession\n") - elif not self.localsessions.has_key(b64priv): - # session exists, but another client owns it - write("error notyoursession\n") - else: - session = self.localsessions[b64priv] - except: - logException(2, "Send exception") - write("error exception on send command\n") - - sessionsLock.release() - - if not session: - return - - # now get/instantiate the remote dest - try: - peerDest = I2PDestination(base64=b64peer) - except: - peerDest = None - logException(2, "Send: bad remote dest") - write("error bad remote dest\n") - if not peerDest: - return - - # and do the send - try: - res = session.sendMessage(peerDest, msg) - except: - logException(2, "Send: failed") - write("error exception on send\n") - res = None - - if res is None: - return - - # report result - if res: - write("ok\n") - else: - write("error send failed\n") - - log(4, "done") - -</t> -<t tx="davidmcnab.041004144338.74">def on_receive(self, flds): - - log(4, "entered") - - server = self.server - client = server.i2pclient - globalsessions = server.sessions - sessionsLock = server.sessionsLock - - read = self.rfile.read - readline = self.rfile.readline - write = self.wfile.write - flush = self.wfile.flush - - sessionsLock.acquire() - - session = None - try: - b64priv = flds[1] - - # spit if session not known - if not globalsessions.has_key(b64priv): - # no such session presently exists anywhere - write("error nosuchsession\n") - elif not self.localsessions.has_key(b64priv): - # session exists, but another client owns it - write("error notyoursession\n") - else: - session = self.localsessions[b64priv] - except: - logException(4, "receive command error") - write("error exception on receive command\n") - sessionsLock.release() - - if not session: - log(4, "no session matching privdest %s" % b64priv[:30]) - return - - # does this session have any received data? - if session.numMessages() > 0: - msg = session.getMessage() - write("ok %s\n%s" % (len(msg), msg)) - else: - write("ok 0\n") - - log(4, "done") - - return - -</t> -<t tx="davidmcnab.041004144338.75">def on_HELLO(self, topic, subtopic, args): - """ - Responds to client PING - """ - log(4, "entered") - self.samSend("HELLO", "PONG") - log(4, "responded to HELLO") - -</t> -<t tx="davidmcnab.041004144338.76">def on_SESSION(self, topic, subtopic, args): - - log(4, "entered") - - server = self.server - client = server.i2pclient - globalsessions = server.sessions - localsessions = self.localsessions - sessionsLock = server.sessionsLock - - read = self.rfile.read - readline = self.rfile.readline - write = self.wfile.write - flush = self.wfile.flush - - if subtopic == 'CREATE': - - if self.samSessionIsOpen: - self.samSend("SESSION", "STATUS", - RESULT="I2P_ERROR", - MESSAGE="Session_already_created", - ) - return - - # get/validate STYLE arg - style = self.samSessionStyle = args.get('STYLE', None) - if style is None: - self.samSend("SESSION", "STATUS", - RESULT="I2P_ERROR", - MESSAGE="Missing_STYLE_argument", - ) - return - elif style not in ['STREAM', 'DATAGRAM', 'RAW']: - self.samSend("SESSION", "STATUS", - RESULT="I2P_ERROR", - MESSAGE="Invalid_STYLE_argument_'%s'" % style, - ) - return - - # get/validate DESTINATION arg - dest = args.get('DESTINATION', None) - if dest == 'TRANSIENT': - # create new temporary dest - dest = self.samDest = I2PDestination() - destb64 = dest.toBase64Private() - else: - # make sure dest isn't globally or locally known - if dest in globalsessions.keys() or dest in localsessions.keys(): - self.samSend("SESSION", "STATUS", - RESULT="DUPLICATED_DEST", - MESSAGE="Destination_'%s...'_already_in_use" % dest[:20], - ) - return - - # try to reconstitute dest from given base64 - try: - destb64 = dest - dest = I2PDestination(base64private=dest) - except: - self.samSend("SESSION", "STATUS", - RESULT="INVALID_KEY", - MESSAGE="Bad_destination_base64_string_'%s...'" % destb64[:20], - ) - return - - # got valid dest now - self.dest = dest - self.samDestPub = dest.toBase64() - - if style in ['RAW', 'DATAGRAM']: - - if style == 'DATAGRAM': - # we need to know how big binary pub dests and sigs - self.samDestPubBin = dest.toBin() - self.samDestPubBinLen = len(self.samDestPubBin) - self.samSigLen = len(self.dest.sign("nothing")) - - log(4, "binary pub dests are %s bytes, sigs are %s bytes" % ( - self.samDestPubBinLen, self.samSigLen)) - - i2cpHost = args.get('I2CP.HOST', server.i2cphost) - i2cpPort = int(args.get('I2CP.PORT', server.i2cpport)) - - # both these styles require an I2PSession object - session = client.createSession(dest, host=i2cpHost, port=i2cpPort) - - # plug in our inbound message handler - session.on_message = self.on_message - - log(4, "Connecting session on dest '%s'" % destb64[:40]) - try: - session.connect() - except net.i2p.client.I2PSessionException: - self.samSend("SESSION", "STATUS", - RESULT="I2P_ERROR", - MESSAGE="Failed_to_connect_to_i2cp_port", - ) - logException(3, "Failed to connect I2PSession") - return - - log(4, "Session on dest '%s' now live" % destb64[:40]) - - # and remember it - localsessions[destb64] = session - globalsessions[destb64] = session - self.samSession = session - - else: # STREAM - # no need to create session object, because we're using streaming api - log(4, "Creating STREAM session") - - # what kind of stream? - direction = args.get('DIRECTION', 'BOTH') - if direction not in ['BOTH', 'RECEIVE', 'CREATE']: - self.samSend("SESSION", "STATUS", - RESULT="I2P_ERROR", - MESSAGE="Illegal_direction_keyword_%s" % direction.replace(" ","_"), - ) - return - - if direction == 'BOTH': - self.canConnect = 1 - self.canAccept = 1 - elif direction == 'RECEIVE': - self.canConnect = 0 - self.canAccept = 1 - elif direction == 'CREATE': - self.canConnect = 1 - self.canAccept = 0 - - # but we do need to mark it as being in use - localsessions[destb64] = globalsessions[destb64] = None - - # make a local socket - sock = self.samSock = I2PSocket(dest) - - # and we also need to fire up a socket listener, if not CREATE-only - if self.canAccept: - thread.start_new_thread(self.threadSocketListener, (sock, dest)) - - # finally, we can reply with the good news - self.samSend("SESSION", "STATUS", - RESULT="OK", - ) - - else: # subtopic != CREATE - self.samSend("SESSION", "STATUS", - RESULT="I2P_ERROR", - MESSAGE="Invalid_command_'SESSION_%s'" % subtopic, - ) - return - -</t> -<t tx="davidmcnab.041004144338.77">def on_SESSION_CREATE(self, topic, subtopic, args): - - log(4, "entered") - - server = self.server - client = server.i2pclient - globalsessions = server.sessions - localsessions = self.localsessions - sessionsLock = server.sessionsLock - - read = self.rfile.read - readline = self.rfile.readline - write = self.wfile.write - flush = self.wfile.flush - -</t> -<t tx="davidmcnab.041004144338.78">def on_STREAM(self, topic, subtopic, args): - - log(4, "entered") - - server = self.server - client = server.i2pclient - globalsessions = server.sessions - sessionsLock = server.sessionsLock - - read = self.rfile.read - readline = self.rfile.readline - write = self.wfile.write - flush = self.wfile.flush - - if subtopic == 'CONNECT': - # who are we connecting to again? - remdest = I2PDestination(base64=args['DESTINATION']) - id = int(args['ID']) - - try: - log(4, "Trying to connect to remote peer %s..." % args['DESTINATION']) - sock = self.samSock.connect(remdest) - log(4, "Connected to remote peer %s..." % args['DESTINATION']) - self.localstreams[id] = sock - self.samSend("STREAM", "STATUS", - RESULT='OK', - ID=id, - ) - thread.start_new_thread(self.threadSocketReceiver, (sock, id)) - - except: - log(4, "Failed to connect to remote peer %s..." % args['DESTINATION']) - self.samSend("STREAM", "STATUS", - RESULT='I2P_ERROR', - MESSAGE='exception_on_connect', - ID=id, - ) - - elif subtopic == 'SEND': - # send to someone - id = int(args['ID']) - try: - sock = self.localstreams[id] - sock.send(args['DATA']) - except: - logException(4, "send failed") - - - - -</t> -<t tx="davidmcnab.041004144338.79">def on_DATAGRAM(self, topic, subtopic, args): - r""" - DATAGRAM SEND - DESTINATION=$base64key - SIZE=$numBytes\n[$numBytes of data] - - All datagram messages have a signature/hash header, formatted as: - - sender's binary public dest - - S(H(sender_bin_pubdest + recipient_bin_pubdest + msg)) - """ - log(4, "entered") - - # at this stage of things, we don't know how to handle anything except SEND - if subtopic != 'SEND': - log(3, "Got illegal subtopic '%s' in DATAGRAM command" % subtopic) - return - - # get the details - peerdestb64 = args['DESTINATION'] - peerdest = I2PDestination(base64=peerdestb64) - peerdestBin = base64dec(peerdestb64) - data = args['DATA'] - - # make up the header - log(4, "samDestPubBin (%s) %s" % (type(self.samDestPubBin), repr(self.samDestPubBin))) - log(4, "peerdestBin (%s) %s" % (type(peerdestBin), repr(peerdestBin))) - log(4, "data (%s) %s" % (type(data), repr(data))) - - hashed = shahash(self.samDestPubBin + peerdestBin + data) - log(4, "hashed=%s" % repr(hashed)) - - sig = self.dest.sign(hashed) - log(4, "sig=%s" % repr(sig)) - hdr = self.samDestPubBin + sig - - # send the thing - self.samSession.sendMessage(peerdest, hdr + data) - -</t> -<t tx="davidmcnab.041004144338.80">def on_RAW(self, topic, subtopic, args): - r""" - RAW SEND - DESTINATION=$base64key - SIZE=$numBytes\n[$numBytes of data] - """ - log(4, "entered") - - # at this stage of things, we don't know how to handle anything except SEND - if subtopic != 'SEND': - return - - # get the details - peerdest = I2PDestination(base64=args['DESTINATION']) - msg = args['DATA'] - - # send the thing - self.samSession.sendMessage(peerdest, msg) -</t> -<t tx="davidmcnab.041004144338.81">def on_NAMING(self, topic, subtopic, args): - - log(4, "entered: %s %s %s" % (repr(topic), repr(subtopic), repr(args))) - - # at this stage of things, we don't know how to handle anything except LOOKUP - if subtopic != 'LOOKUP': - return - - # get the details - host = args['NAME'] - - log(4, "looking up host %s" % host) - - # try to lookup - jdest = self.namingService.lookup(host) - - if not jdest: - log(4, "host %s not found" % host) - self.samSend("NAMING", "REPLY", - RESULT="KEY_NOT_FOUND", - NAME=host, - ) - return - - try: - b64 = I2PDestination(dest=jdest).toBase64() - self.samSend("NAMING", "REPLY", - RESULT="OK", - NAME=host, - VALUE=b64, - ) - log(4, "host %s found and valid key returned" % host) - return - except: - log(4, "host %s found but key invalid" % host) - self.samSend("NAMING", "REPLY", - RESULT="INVALID_KEY", - NAME=host, - ) - -</t> -<t tx="davidmcnab.041004144338.82">def on_DEST(self, topic, subtopic, args): - - log(4, "Generating dest") - - dest = I2PDestination() - priv = dest.toBase64Private() - pub = dest.toBase64() - - log(4, "Sending dest to client") - - self.samSend("DEST", "REPLY", PUB=pub, PRIV=priv) - - log(4, "done") -</t> -<t tx="davidmcnab.041004144338.83">def on_message(self, msg): - """ - This callback gets plugged into the I2PSession object, - so we can asychronously notify our client when stuff arrives - """ - if self.samSessionStyle == 'RAW': - self.samSend("RAW", "RECEIVE", msg) - - elif self.samSessionStyle == 'DATAGRAM': - # ain't so simple, we gotta rip and validate the header - remdestBin = msg[:self.samDestPubBinLen] - log(4, "remdestBin=%s" % repr(remdestBin)) - - sig = msg[self.samDestPubBinLen:self.samDestPubBinLen+self.samSigLen] - log(4, "sig=%s" % repr(sig)) - - data = msg[self.samDestPubBinLen+self.samSigLen:] - log(4, "data=%s" % repr(data)) - - # now try to verify - hashed = shahash(remdestBin + self.samDestPubBin + data) - log(4, "hashed=%s" % repr(hashed)) - - remdest = I2PDestination(bin=remdestBin) - if remdest.verify(hashed, sig): - # fine - very good, pass it on - log(4, "sig from peer is valid") - self.samSend("DATAGRAM", "RECEIVE", data, - DESTINATION=remdest.toBase64(), - ) - else: - log(4, "DATAGRAM sig from peer is invalid") -</t> -<t tx="davidmcnab.041004144338.84">def threadSocketListener(self, sock, dest): - """ - Listens for incoming socket connections, and - notifies the client accordingly - """ - destb64 = dest.toBase64() - - log(4, "Listening for connections to %s..." % destb64) - - sock.bind() - sock.listen() - - while 1: - log(4, "Awaiting next connection to %s..." % destb64) - newsock = sock.accept() - log(4, "Got connection to %s..." % destb64) - - # need an id, negative - id = - self.server.samAllocId() - - # register it in local and global streams - self.localstreams[id] = self.globalstreams[id] = newsock - - # fire up the receiver thread - thread.start_new_thread(self.threadSocketReceiver, (newsock, id)) - - # who is connected to us? - remdest = newsock.remdest - remdest_b64 = remdest.toBase64() - - # and notify the client - self.samSend("STREAM", "CONNECTED", - DESTINATION=remdest_b64, - ID=id) - -</t> -<t tx="davidmcnab.041004144338.85">def samParse(self, flds): - """ - carves up a SAM command, returns it as a 3-tuple: - - cmd - command string - - subcmd - subcommand string - - dargs - dict of args - """ - cmd = flds[0] - subcmd = flds[1] - args = flds[2:] - - dargs = {} - for arg in args: - try: - name, val = arg.split("=", 1) - except: - logException(3, "failed to process %s in %s" % (repr(arg), repr(flds))) - raise - dargs[name] = val - - # read and add data if any - if dargs.has_key('SIZE'): - size = dargs['SIZE'] = int(dargs['SIZE']) - dargs['DATA'] = self._recvbytes(size) - - #log(4, "\n".join([cmd+" "+subcmd] + [("%s=%s (...)" % (k,v[:40])) for k,v in dargs.items()])) - log(4, "\n".join([cmd+" "+subcmd] + [("%s=%s (...)" % (k,v)) for k,v in dargs.items()])) - - return cmd, subcmd, dargs - - - -</t> -<t tx="davidmcnab.041004144338.86">def samSend(self, topic, subtopic, data=None, **kw): - """ - Sends a SAM message (reply?) back to client - - Arguments: - - topic - the first word in the reply, eg 'STREAM' - - subtopic - the second word of the reply, eg 'CONNECTED' - - data - a string of raw data to send back (optional) - Keywords: - - extra 'name=value' items to pass back. - - Notes: - 1. SIZE is not required. If sending back data, it will - be sized and a SIZE arg inserted automatically. - 2. a dict of values can be passed to the 'args' keyword, in lieu - of direct keywords. This allows for cases where arg names would - cause python syntax clashes, eg 'tunnels.depthInbound' - """ - items = [topic, subtopic] - - # stick in SIZE if needed - if data is not None: - kw['SIZE'] = str(len(data)) - else: - data = '' # for later - - self.samCreateArgsList(kw, items) - - # and whack it together - buf = " ".join(items) + '\n' + data - - # and ship it - self.sendLock.acquire() - try: - self._sendbytes(buf) - except: - self.sendLock.release() - raise - self.sendLock.release() - -</t> -<t tx="davidmcnab.041004144338.87">def samCreateArgsList(self, kw1, lst): - for k,v in kw1.items(): - if k == 'args': - self.samCreateArgsList(v, lst) - else: - lst.append("=".join([str(k), str(v)])) -</t> -<t tx="davidmcnab.041004144338.88">def _sendbytes(self, raw): - - self.wfile.write(raw) - self.wfile.flush() -</t> -<t tx="davidmcnab.041004144338.89">def _recvbytes(self, count): - """ - Does a guaranteed read of n bytes - """ - read = self.rfile.read - - chunks = [] - needed = count - while needed > 0: - chunk = read(needed) - chunklen = len(chunk) - needed -= chunklen - chunks.append(chunk) - raw = "".join(chunks) - - # done - return raw - -</t> -<t tx="davidmcnab.041004144338.90">class NoPrivateKey(Exception): - """Destination object has no private key""" - -class I2PSocketError(Exception): - """Error working with I2PSocket objects""" -</t> -<t tx="davidmcnab.041004144338.91"></t> -<t tx="davidmcnab.041004144338.92">def shahash(s): - """ - Calculates SHA Hash of a string, as a string, using - I2P hashing facility - """ - h = net.i2p.crypto.SHA256Generator().calculateHash(s) - h = bytearray2str(h.getData()) - return h -</t> -<t tx="davidmcnab.041004144338.93">def base64enc(s): - return net.i2p.data.Base64.encode(s) -</t> -<t tx="davidmcnab.041004144338.94">def base64dec(s): - return bytearray2str(net.i2p.data.Base64.decode(s)) - -</t> -<t tx="davidmcnab.041004144338.95">def str2bytearray(s): - """ - Convenience - converts python string to java-friendly byte array - """ - a = [] - for c in s: - n = ord(c) - if n >= 128: - n = n - 256 - a.append(n) - return a - -</t> -<t tx="davidmcnab.041004144338.96">def bytearray2str(a): - """ - Convenience - converts java-friendly byte array to python string - """ - chars = [] - for n in a: - if n < 0: - n += 256 - chars.append(chr(n)) - return "".join(chars) - -</t> -<t tx="davidmcnab.041004144338.97">def byteoutstream2str(bs): - """ - Convenience - converts java-friendly byteoutputstream to python string - """ - chars = [] - while 1: - c = bs.read() - if c >= 0: - chars.append(chr(c)) - else: - break - return "".join(chars) - -</t> -<t tx="davidmcnab.041004144338.98">def dict2props(d): - """ - Converts a python dict d into a java.util.Properties object - """ - props = java.util.Properties() - for k,v in d.items(): - props[k] = str(v) - return props - - -</t> -<t tx="davidmcnab.041004144338.99">def takeKey(somedict, keyname, default=None): - """ - Utility function to destructively read a key from a given dict. - Same as the dict's 'takeKey' method, except that the key (if found) - sill be deleted from the dictionary. - """ - if somedict.has_key(keyname): - val = somedict[keyname] - del somedict[keyname] - else: - val = default - return val -</t> -<t tx="davidmcnab.041004144338.100">def log(level, msg, nPrev=0): - - # ignore messages that are too trivial for chosen verbosity - if level > verbosity: - return - - loglock.acquire() - try: - # rip the stack - caller = traceback.extract_stack()[-(2+nPrev)] - path, line, func = caller[:3] - path = os.path.split(path)[1] - full = "%s:%s:%s():\n* %s" % ( - path, - line, - func, - msg.replace("\n", "\n + ")) - now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) - msg = "%s %s\n" % (now, full) - - if logfile == sys.stdout: - print msg - else: - file(logfile, "a").write(msg+"\n") - except: - s = StringIO.StringIO() - traceback.print_exc(file=s) - print s.getvalue() - print "Logger crashed" - loglock.release()</t> -<t tx="davidmcnab.041004144338.101">def logException(level, msg=''): - s = StringIO.StringIO() - traceback.print_exc(file=s) - log(level, "%s\n%s" % (s.getvalue(), msg), 1) -</t> -<t tx="davidmcnab.041004144338.102">def usage(detailed=0): - - print "Usage: %s <options> [<command>]" % sys.argv[0] - if not detailed: - print "Run with '-h' to get detailed help" - sys.exit(0) - - print "I2PSAM is a bridge that allows I2P client programs to access the" - print "I2P network by talking over a plaintext socket connection." - print "References:" - print " - http://www.freenet.org.nz/i2p - source, doco, downloadables" - print " - http://drupal.i2p.net/node/view/144 - I2P SAM specification" - print - print "Options:" - print " -h, -?, --help - display this help" - print " -v, --version - print program version" - print " -V, --verbosity=n - set verbosity to n, default 2, 1==quiet, 4==noisy" - print " -H, --listenhost=host - specify host to listen on for client connections" - print " -P, --listenport=port - port to listen on for client connections" - print " --i2cphost=host - hostname of I2P router's I2CP interface" - print " --i2cpport=port - port of I2P router's I2CP interface" - print - print "Commands:" - print " (run with no commands to launch SAM server)" - print " samserver - runs as a SAM server" - print " test - run a suite of self-tests" - print " testsocket - run only the socket test" - print " testbidirsocket - run socket test in bidirectional mode" - print - - sys.exit(0) -</t> -<t tx="davidmcnab.041004144338.103">def main(): - - argv = sys.argv - argc = len(argv) - - # ------------------------------------------------- - # do the getopt command line parsing - - try: - opts, args = getopt.getopt(sys.argv[1:], - "h?vV:H:P:", - ['help', 'version', 'verbosity=', - 'listenhost=', 'listenport=', - 'i2cphost=', 'i2cpport=', - ]) - except: - traceback.print_exc(file=sys.stdout) - usage("You entered an invalid option") - - #print "args=%s" % args - - serveropts = {} - for opt, val in opts: - if opt in ['-h', '-?', '--help']: - usage(1) - elif opt in ['-v', '--version']: - print "I2P SAM version %s" % version - sys.exit(0) - elif opt in ['-V', '--verbosity']: - serveropts['verbosity'] = int(val) - elif opt in ['-H', '--listenhost']: - serveropts['host'] = val - elif opt in ['-P', '--listenport']: - serveropts['port'] = int(val) - elif opt in ['--i2cphost']: - serveropts['i2cphost'] = val - elif opt in ['--i2cpport']: - serveropts['i2cpport'] = int(val) - else: - usage(0) - - # -------------------------------------------------- - # now run in required mode, default is 'samserver' - - if len(args) == 0: - cmd = 'samserver' - else: - cmd = args[0] - - if cmd == 'samserver': - - log(2, "Running I2P SAM Server...") - server = I2PSamServer(**serveropts) - server.run() - - elif cmd == 'test': - - print "RUNNING full I2PSAM Jython TEST SUITE" - testsigs() - testdests() - testsession() - testsocket() - - elif cmd == 'testsocket': - - print "RUNNING SOCKET TEST" - testsocket(0) - - elif cmd == 'testbidirsocket': - print "RUNNING BIDIRECTIONAL SOCKET TEST" - testsocket(1) - - else: - # spit at unrecognised option - usage(0) - -</t> -<t tx="davidmcnab.041004144338.104"></t> -<t tx="davidmcnab.041004144338.105">def testdests(): - """ - Demo function which tests out dest generation and import/export - """ - print - print "********************************************" - print "Testing I2P destination create/export/import" - print "********************************************" - print - - print "Generating a destination" - d1 = I2PDestination() - - print "Exporting and importing dest1 in several forms" - - print "public binary string..." - d1_bin = d1.toBin() - d2_bin = I2PDestination(bin=d1_bin) - - print "public binary file..." - d1.toBinFile("temp-d1-bin") - d2_binfile = I2PDestination(binfile="temp-d1-bin") - - print "private binary string..." - d1_binprivate = d1.toBinPrivate() - d2_binprivate = I2PDestination(binprivate=d1_binprivate) - - print "private binary file..." - d1.toBinFilePrivate("temp-d1-bin-private") - d2_binfileprivate = I2PDestination(binfileprivate="temp-d1-bin-private") - - print "public base64 string..." - d1_b64 = d1.toBase64() - d2_b64 = I2PDestination(base64=d1_b64) - - print "public base64 file..." - d1.toBase64File("temp-d1-b64") - d2_b64file = I2PDestination(base64file="temp-d1-b64") - - print "private base64 string..." - d1_base64private = d1.toBase64Private() - d2_b64private = I2PDestination(base64private=d1_base64private) - - print "private base64 file..." - d1.toBase64FilePrivate("temp-d1-b64-private") - d2_b64fileprivate = I2PDestination(base64fileprivate="temp-d1-b64-private") - - print "All destination creation/import/export tests passed!" - - -</t> -<t tx="davidmcnab.041004144338.106">def testsigs(): - global d1, d1pub, d1sig, d1res - - print - print "********************************************" - print "Testing I2P dest-based signatures" - print "********************************************" - print - - print "Creating dest..." - d1 = I2PDestination() - - s_good = "original stuff that we're signing" - s_bad = "non-original stuff we're trying to forge" - - print "Signing some shit against d1..." - d1sig = d1.sign(s_good) - - print "Creating public dest d1pub" - d1pub = I2PDestination(bin=d1.toBin()) - - print "Verifying original data with d1pub" - res = d1pub.verify(s_good, d1sig) - print "Result: %s (should be 1)" % repr(res) - - print "Trying to verify on a different string" - res1 = d1pub.verify(s_bad, d1sig) - print "Result: %s (should be 0)" % repr(res1) - - if res and not res1: - print "signing/verifying test passed" - else: - print "SIGNING/VERIFYING TEST FAILED" - -</t> -<t tx="davidmcnab.041004144338.107">def testsession(): - - global c, d1, d2, s1, s2 - - print - print "********************************************" - print "Testing I2P dest->dest messaging" - print "********************************************" - print - - print "Creating I2P client..." - c = I2PClient() - - print "Creating destination d1..." - d1 = c.createDestination() - - print "Creating destination d2..." - d2 = c.createDestination() - - print "Creating destination d3..." - d3 = c.createDestination() - - print "Creating session s1 on dest d1..." - s1 = c.createSession(d1, host='localhost', port=7654) - - print "Creating session s2 on dest d2..." - s2 = c.createSession(d2) - - print "Connecting session s1..." - s1.connect() - - print "Connecting session s2..." - s2.connect() - - print "Sending message from s1 to d2..." - s1.sendMessage(d2, "Hi there, s2!!") - - print "Retrieving message from s2..." - print "got: %s" % repr(s2.getMessage()) - - print "Sending second message from s1 to d2..." - s1.sendMessage(d2, "Hi there again, s2!!") - - print "Retrieving message from s2..." - print "got: %s" % repr(s2.getMessage()) - - print "Sending message from s1 to d3 (should take ages then fail)..." - res = s1.sendMessage(d3, "This is futile!!") - print "result of that send was %s (should have been 0)" % res - - print "Destroying session s1..." - s1.destroySession() - - print "Destroying session s2..." - s2.destroySession() - - print "session tests passed!" -</t> -<t tx="davidmcnab.041004144338.108">def testsocket(bidirectional=0): - - global d1, d2, s1, s2 - - print - print "********************************************" - print "Testing I2P streaming interface" - print "********************************************" - print - - print "Creating destinations..." - dServer = I2PDestination() - dClient = I2PDestination() - - print "Creating sockets..." - sServer = I2PSocket(dServer) - sClient = I2PSocket(dClient) - - # server thread which simply reads a line at a time, then echoes - # that line back to the client - def servThread(s): - print "server: binding socket" - s.bind() - print "server: setting socket to listen" - s.listen() - print "server: awaiting connection" - sock = s.accept() - print "server: got connection" - - sock.send("Hello, echoing...\n") - buf = '' - while 1: - c = sock.recv(1) - if c == '': - sock.close() - print "server: socket closed" - break - - buf += c - if c == '\n': - sock.send("SERVER: "+buf) - buf = '' - - # client thread which reads lines and prints them to stdout - def clientThread(s): - buf = '' - while 1: - c = s.recv(1) - if c == '': - s.close() - print "client: socket closed" - break - buf += c - if c == '\n': - print "client: got %s" % repr(buf) - buf = '' - - print "launching server thread..." - thread.start_new_thread(servThread, (sServer,)) - - if bidirectional: - # dummy thread which accepts connections TO client socket - def threadDummy(s): - print "dummy: listening" - s.listen() - print "dummy: accepting" - - sock = s.accept() - print "dummy: got connection" - - print "test - launching dummy client accept thread" - thread.start_new_thread(threadDummy, (sClient,)) - - print "client: trying to connect" - sClient.connect(dServer) - - print "client: connected, launching rx thread" - thread.start_new_thread(clientThread, (sClient,)) - - while 1: - line = raw_input("Enter something (q to quit)> ") - if line == 'q': - print "closing client socket" - sClient.close() - break - sClient.send(line+"\n") - - print "I2PSocket test apparently succeeded" - - -</t> -<t tx="davidmcnab.041004144338.109">if __name__ == '__main__': - main() - -</t> -<t tx="davidmcnab.041004144551">@first #!/usr/bin/env python -""" -Implements a client API for I2CP messaging via SAM - -Very simple I2P messaging interface, which should prove easy -to reimplement in your language of choice - -This module can be used from cpython or jython - -Run this module without arguments to see a demo in action -(requires SAM server to be already running) -""" -@others - -</t> -<t tx="davidmcnab.041004144551.1">import sys, os, socket, thread, threading, Queue, traceback, StringIO, time - -from pdb import set_trace - -</t> -<t tx="davidmcnab.041004144551.2"># ----------------------------------------- -# server access settings - -i2psamhost = '127.0.0.1' -i2psamport = 7656 - -# ------------------------------------------ -# logging settings - -# 1=v.quiet, 2=normal, 3=verbose, 4=debug, 5=painful -verbosity = 5 - -# change to a filename to log there instead -logfile = sys.stdout - -# when set to 1, and when logfile != sys.stdout, log msgs are written -# both to logfile and console stdout -log2console = 1 - -# don't touch this! -loglock = threading.Lock() - -</t> -<t tx="davidmcnab.041004144551.3">class I2PServerFail(Exception): - """ - A failure in connecting to the I2CP server - """ - -class I2PCommandFail(Exception): - """ - A failure in an I2CP command - """ - pass - -class I2PStreamClosed(Exception): - """ - Stream is not open - """ -</t> -<t tx="davidmcnab.041004144551.4">class I2PSamClient: - """ - Implements a reference client for accessing I2CP via i2psam - - Connects to i2psam's I2PSamServer, sends commands - and receives results - - The primitives should be reasonably self-explanatory - - Usage summary: - 1. create one or more I2PSamClient instances per process (1 should be fine) - 2. invoke the L{genkeys} method to create destination keypairs - 3. create sessions objects via the L{createSession} method - 4. use these session objects to send and receive data - 5. destroy the session objects when you're done - - Refer to the function L{demo} for a simple example - """ - @others -</t> -<t tx="davidmcnab.041004144551.5"># server host/port settings exist here in case you might -# have a reason for overriding in a subclass - -host = i2psamhost -port = i2psamport - -i2cpHost = None -i2cpPort = None - -</t> -<t tx="davidmcnab.041004144551.6">def __init__(self, **kw): - """ - Creates a client connection to i2psam listener - - Keywords: - - host - host to connect to (default 127.0.0.1) - - port - port to connect to (default 7656) - """ - # get optional host/port - log(4, "entered") - - self.host = kw.get('host', self.host) - self.port = int(kw.get('port', self.port)) - - self.cmdLock = threading.Lock() - - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.lockHello = threading.Lock() - self.sendLock = threading.Lock() - self.qNewDests = Queue.Queue() - self.qSession = Queue.Queue() - self.qDatagrams = Queue.Queue() - self.qRawMessages = Queue.Queue() - - self.namingReplies = {} - self.namingCache = {} - - self.streams = {} # currently open streams, keyed by id - self.streamConnectReplies = {} # holds queues awaiting connect resp, keyed by id - self.qNewStreams = Queue.Queue() # incoming connections - - self.samNextIdLock = threading.Lock() - self.samNextId = 1 - - self.isRunning = 1 - - - log(4, "trying connection to SAM server...") - try: - self.sock.connect((self.host, self.port)) - except: - raise I2PServerFail( - "Connection to i2psam server failed\n" - "(are you sure your I2P router is running, and\n" - "listening for I2CP connections on %s:%s?)" % (self.host, self.port) - ) - - # fire up receiver thread - thread.start_new_thread(self.threadRx, ()) - - # ping the server - try: - log(4, "trying to ping SAM server...") - self.samHello() - except: - logException(4, "Exception on handshaking") - raise I2PServerFail("Failed to handshake with i2psam server") - - # connected fine - log(2, "I2CP Client successfully connected") -</t> -<t tx="davidmcnab.041004144551.7">def createSession(self, privdest): - """ - DEPRECATED - use sam* methods instead! - - Creates a session using private destkey - """ - #3. createsession: - # - client->server: - # - createsession <base64private>\n - # - server->client: - # - ok\n OR - # - error[ <reason>]\n - - self.cmdLock.acquire() - try: - self._sendline("createsession %s" % privdest) - respitems = self._recvline().split(" ", 1) - if respitems[0] == 'ok': - res = None - else: - res = respitems[1] - except: - logException(2, "createsession fail") - self.cmdLock.release() - raise - - self.cmdLock.release() - - if res: - raise I2PCommandFail("createsession fail: "+res) - - return I2PRemoteSession(self, privdest) - -</t> -<t tx="davidmcnab.041004144551.8">def destroySession(self, privdest): - """ - DEPRECATED - use sam* methods instead! - - Destrlys a session using private destkey - """ - #4. destroysession: - # - client->server: - # - destroysession <base64private>\n - # - server->client: - # - ok\n OR - # - error[ <reason>]\n - - self.cmdLock.acquire() - try: - self._sendline("destroysession %s" % privdest) - respitems = self._recvline().split(" ", 1) - if respitems[0] == 'ok': - res = None - else: - res = respitems[1] - except: - logException(2, "destroysession fail") - self.cmdLock.release() - raise - - self.cmdLock.release() - - if res: - raise I2PCommandFail("destroysession fail: " + res) - - return res - -</t> -<t tx="davidmcnab.041004144551.9">def send(self, privdest, peerdest, msg): - """ - DEPRECATED - use sam* methods instead! - - Sends a block of data from local dest to remote dest - """ - #5. send: - # - client->server: - # - send <size> <localbase64private> <remotebase64dest>\ndata - # - server->client: - # - ok\n OR - # - error[ <reason>]\n - - self.cmdLock.acquire() - try: - self._sendline("send %s %s %s" % (len(msg), privdest, peerdest)) - self._sendbytes(msg) - line = self._recvline() - #print "** %s" % line - respitems = line.split(" ", 1) - if respitems[0] == 'ok': - res = None - else: - res = " ".join(respitems[1:]) - except: - logException(2, "send fail") - self.cmdLock.release() - raise - - self.cmdLock.release() - - if res: - raise I2PCommandFail("send fail: " + res) - - return res - -</t> -<t tx="davidmcnab.041004144551.10">def receive(self, privdest): - """ - DEPRECATED - use sam* methods instead! - - receives a block of data, returning string, or None if no data available - """ - #6. receive: - # - client->server: - # - receive <localbase64private>\n - # - server->client: - # - ok <size>\ndata OR - # - error[ <reason>]\n - - self.cmdLock.acquire() - try: - self._sendline("receive %s" % privdest) - respitems = self._recvline().split(" ", 1) - if respitems[0] == 'ok': - res = None - size = int(respitems[1]) - msg = self._recvbytes(size) - res = None - else: - res = respitems[1] - except: - logException(2, "receive fail") - self.cmdLock.release() - raise - - self.cmdLock.release() - - if res: - raise I2PCommandFail("destroysession fail: " + res) - - return msg -</t> -<t tx="davidmcnab.041004144551.11"></t> -<t tx="davidmcnab.041004144551.12">def samHello(self): - """ - Sends a quick HELLO PING to SAM server and awaits response - Arguments: - - none - - Keywords: - - none - - Returns: - - nothing (None) if ping sent and pong received, or raises an exception if - failed - """ - self.lockHello.acquire() - self.samSend("HELLO", "PING") - self.lockHello.acquire() - self.lockHello.release() -</t> -<t tx="davidmcnab.041004144551.13">def samSessionCreate(self, style, dest, **kw): - """ - Creates a SAM session - - Arguments: - - style - one of 'STREAM', 'DATAGRAM' or 'RAW' - - dest - base64 private destination - - Keywords: - - direction - only used for STREAM sessions, can be RECEIVE, - CREATE or BOTH (default BOTH) - - i2cphost - hostname for the SAM bridge to contact i2p router on - - i2cpport - port for the SAM bridge to contact i2p router on - - Returns: - - 'OK' if session was created successfully, or a tuple - (keyword, message) if not - """ - kw1 = dict(kw) - kw1['STYLE'] = self.samStyle = style - kw1['DESTINATION'] = dest - if style == 'STREAM': - direction = kw.get('direction', 'BOTH') - kw1['DIRECTION'] = direction - if direction == 'BOTH': - self.canAccept = 1 - self.canConnect = 1 - elif direction == 'RECEIVE': - self.canAccept = 1 - self.canConnect = 0 - elif direction == 'CREATE': - self.canAccept = 0 - self.canConnect = 1 - else: - raise I2PCommandFail("direction keyword must be one of RECEIVE, CREATE or BOTH") - - # stick in i2cp host/port if specified - if kw.has_key('i2cphost'): - kw1['I2CP.HOST'] = kw['i2cphost'] - if kw.has_key('i2cpport'): - kw1['I2CP.PORT'] = kw['i2cpport'] - - self.samSend("SESSION", "CREATE", - **kw1) - subtopic, args = self.qSession.get() - - if args['RESULT'] == 'OK': - return 'OK' - else: - return (args['RESULT'], args['MESSAGE']) -</t> -<t tx="davidmcnab.041004144551.14">def samDestGenerate(self): - """ - Creates a whole new dest and returns an tuple pub, priv as - base64 public and private destination keys - """ - self.samSend("DEST", "GENERATE") - pub, priv = self.qNewDests.get() - return pub, priv -</t> -<t tx="davidmcnab.041004144551.15">def samRawSend(self, peerdest, msg): - """ - Sends a raw anon message to another peer - - peerdest is the public base64 destination key of the peer - """ - self.samSend("RAW", "SEND", msg, - DESTINATION=peerdest, - ) -</t> -<t tx="davidmcnab.041004144551.16">def samRawCheck(self): - """ - Returns 1 if there are received raw messages available, 0 if not - """ - return not self.qRawMessages.empty() -</t> -<t tx="davidmcnab.041004144551.17">def samRawReceive(self, blocking=1): - """ - Returns the next raw message available, - blocking if none is available and the blocking arg is set to 0 - - If blocking is 0, and no messages are available, returns None. - - Remember that you can check for availability with - the .samRawCheck() method - """ - if not blocking: - if self.qRawMessages.empty(): - return None - return self.qRawMessages.get() - </t> -<t tx="davidmcnab.041004144551.18">def samDatagramSend(self, peerdest, msg): - """ - Sends a repliable datagram message to another peer - - peerdest is the public base64 destination key of the peer - """ - self.samSend("DATAGRAM", "SEND", msg, - DESTINATION=peerdest, - ) -</t> -<t tx="davidmcnab.041004144551.19">def samDatagramCheck(self): - """ - Returns 1 if there are datagram messages received messages available, 0 if not - """ - return not self.qDatagrams.empty() -</t> -<t tx="davidmcnab.041004144551.20">def samDatagramReceive(self, blocking=1): - """ - Returns the next datagram message available, - blocking if none is available. - - If blocking is set to 0, and no messages are available, - returns None. - - Remember that you can check for availability with - the .samRawCheck() method - - Returns 2-tuple: dest, msg - where dest is the base64 destination of the peer from - whom the message was received - """ - if not blocking: - if self.qDatagrams.empty(): - return None - return self.qDatagrams.get() -</t> -<t tx="davidmcnab.041004144551.21">def samNamingLookup(self, host): - """ - Looks up a host in hosts.txt - """ - # try the cache first - if self.namingCache.has_key(host): - log(4, "found host %s in cache" % host) - return self.namingCache[host] - - # make a queue for reply - q = self.namingReplies[host] = Queue.Queue() - - # send off req - self.samSend("NAMING", "LOOKUP", - NAME=host, - ) - - # get resp - resp = q.get() - - result = resp.get('RESULT', 'none') - if result == 'OK': - log(4, "adding host %s to cache" % host) - val = resp['VALUE'] - self.namingCache[host] = val - return val - else: - raise I2PCommandFail("Error looking up '%s': %s %s" % ( - host, result, resp.get('MESSAGE', ''))) - -</t> -<t tx="davidmcnab.041004144551.22">def samParse(self, flds): - """ - carves up a SAM command, returns it as a 3-tuple: - - cmd - command string - - subcmd - subcommand string - - dargs - dict of args - """ - cmd = flds[0] - subcmd = flds[1] - args = flds[2:] - - dargs = {} - for arg in args: - try: - name, val = arg.split("=", 1) - except: - logException(3, "failed to process %s in %s" % (repr(arg), repr(flds))) - raise - dargs[name] = val - - # read and add data if any - if dargs.has_key('SIZE'): - size = dargs['SIZE'] = int(dargs['SIZE']) - dargs['DATA'] = self._recvbytes(size) - - #log(4, "\n".join([cmd+" "+subcmd] + [("%s=%s (...)" % (k,v[:40])) for k,v in dargs.items()])) - log(4, "\n".join([cmd+" "+subcmd] + [("%s=%s (...)" % (k,v)) for k,v in dargs.items()])) - - return cmd, subcmd, dargs - - - - - -</t> -<t tx="davidmcnab.041004144551.23">def samSend(self, topic, subtopic, data=None, **kw): - """ - Sends a SAM message (reply?) back to client - - Arguments: - - topic - the first word in the reply, eg 'STREAM' - - subtopic - the second word of the reply, eg 'CONNECTED' - - data - a string of raw data to send back (optional) - Keywords: - - extra 'name=value' items to pass back. - - Notes: - 1. SIZE is not required. If sending back data, it will - be sized and a SIZE arg inserted automatically. - 2. a dict of values can be passed to the 'args' keyword, in lieu - of direct keywords. This allows for cases where arg names would - cause python syntax clashes, eg 'tunnels.depthInbound' - """ - items = [topic, subtopic] - - # stick in SIZE if needed - if data is not None: - kw['SIZE'] = str(len(data)) - else: - data = '' # for later - - self.samCreateArgsList(kw, items) - - # and whack it together - buf = " ".join(items) + '\n' + data - - # and ship it - self.sendLock.acquire() - try: - self._sendbytes(buf) - except: - self.sendLock.release() - raise - self.sendLock.release() - -</t> -<t tx="davidmcnab.041004144551.24">def samCreateArgsList(self, kw1, lst): - for k,v in kw1.items(): - if k == 'args': - self.samCreateArgsList(v, lst) - else: - lst.append("=".join([str(k), str(v)])) -</t> -<t tx="davidmcnab.041004144551.25"></t> -<t tx="davidmcnab.041004144551.26">def threadRx(self): - """ - Handles all incoming stuff from SAM, storing in - local queues as appropriate - """ - while self.isRunning: - try: - log(4, "Awaiting next message from server") - line = self._recvline() - if line == '': - log(3, "I2P server socket closed") - return - flds = line.split(" ") - topic, subtopic, args = self.samParse(flds) - log(4, "Got %s %s %s" % (topic, subtopic, args)) - handleMsg = getattr(self, "on_"+topic, None) - if handleMsg: - handleMsg(topic, subtopic, args) - else: - log(2, "No handler for '%s' message" % topic) - except: - #logException(3, "Exception handling %s %s\n%s" % (topic, subtopic, args)) - logException(3, "Exception handling %s" % repr(line)) -</t> -<t tx="davidmcnab.041004144551.27">def on_HELLO(self, topic, subtopic, args): - """ - Handles HELLO PONG messages from server - """ - # just wake up the caller - log(4, "got HELLO") - self.lockHello.release() -</t> -<t tx="davidmcnab.041004144551.28">def on_SESSION(self, topic, subtopic, args): - """ - Handles SESSION messages from server - """ - # just stick whatever on the queue and wake up the caller - res = subtopic, args - self.qSession.put(res) -</t> -<t tx="davidmcnab.041004144551.29">def on_STREAM(self, topic, subtopic, args): - """ - Handles STREAM messages from server - - STREAM STATUS - RESULT=$result - ID=$id - [MESSAGE=...] - - STREAM CONNECTED - DESTINATION=$base64key - ID=$id - - STREAM RECEIVED - ID=$id - SIZE=$numBytes\n[$numBytes of data] - - STREAM CLOSED - RESULT=$result - ID=$id - [MESSAGE=...] - """ - log(4, "got %s %s %s" % (topic, subtopic, args)) - - # which stream? - id = int(args['ID']) - - # result of prior connection attempt - if subtopic == 'STATUS': - # stick it on the queue that the caller is waiting on and let the - # caller interpret the result - self.streamConnectReplies[id].put(args) - return - - # notice of incoming connection - if subtopic == 'CONNECTED': - - # grab details - dest = args['DESTINATION'] - - # wrap it in a stream obj - conn = I2PSAMStream(self, id, dest) - self.streams[id] = conn - - # and put it there for anyone calling samStreamAccept() - self.qNewStreams.put(conn) - - # done - return - - # notice of received data - elif subtopic == 'RECEIVED': - # grab details - data = args['DATA'] - - # lookup the connection - conn = self.streams.get(id, None) - if not conn: - # conn not known, just ditch - log(2, "got data, but don't recall any conn with id %s" % id) - return - - # and post the received data - conn._notifyIncomingData(data) - - log(4, "wrote data to conn's inbound queue") - - # done - return - - elif subtopic == 'CLOSED': - # lookup the connection - conn = self.streams.get(id, None) - if not conn: - # conn not known, just ditch - return - - # mark conn as closed and forget it - conn._notifyIncomingData("") # special signal to close - conn.isOpen = 0 - del self.streams[id] - - # done - return - - - -</t> -<t tx="davidmcnab.041004144551.30">def on_DATAGRAM(self, topic, subtopic, args): - """ - Handles DATAGRAM messages from server - """ - remdest = args['DESTINATION'] - data = args['DATA'] - - self.qDatagrams.put((remdest, data)) -</t> -<t tx="davidmcnab.041004144551.31">def on_RAW(self, topic, subtopic, args): - """ - Handles RAW messages from server - """ - data = args['DATA'] - - log(3, "Got anonymous datagram %s" % repr(data)) - self.qRawMessages.put(data) -</t> -<t tx="davidmcnab.041004144551.32">def on_NAMING(self, topic, subtopic, args): - """ - Handles NAMING messages from server - """ - # just find out hostname, and stick it on resp q - host = args['NAME'] - self.namingReplies[host].put(args) -</t> -<t tx="davidmcnab.041004144551.33">def on_DEST(self, topic, subtopic, args): - """ - Handles DEST messages from server - """ - pubkey = args['PUB'] - privkey = args['PRIV'] - res = pubkey, privkey - self.qNewDests.put(res) -</t> -<t tx="davidmcnab.041004144551.34"></t> -<t tx="davidmcnab.041004144551.35">def _recvline(self): - """ - Guaranteed read of a full line - """ - chars = [] - while 1: - c = self.sock.recv(1) - if c in ['', '\n']: - break - chars.append(c) - return "".join(chars) -</t> -<t tx="davidmcnab.041004144551.36">def _recvbytes(self, num): - """ - Guaranteed read of num bytes - """ - if num <= 0: - return "" - - reqd = num - chunks = [] - while reqd > 0: - chunk = self.sock.recv(reqd) - if not chunk: - raise I2PServerFail("Buffer read fail") - chunks.append(chunk) - reqd -= len(chunk) - return "".join(chunks) -</t> -<t tx="davidmcnab.041004144551.37">def _sendbytes(self, buf): - """ - Guaranteed complete send of a buffer - """ - reqd = len(buf) - while reqd > 0: - nsent = self.sock.send(buf) - if nsent == 0: - raise I2PServerFail("Send to server failed") - buf = buf[nsent:] - reqd -= nsent -</t> -<t tx="davidmcnab.041004144551.38">def _sendline(self, line): - """ - just tacks on a newline and sends - """ - self._sendbytes(line+"\n") -</t> -<t tx="davidmcnab.041004144551.39">class I2PRemoteSession: - """ - DEPRECATED - - Wrapper for I2CP connections - - Do not instantiate this directly - it gets created by - I2PSamClient.createSession() - """ - @others -</t> -<t tx="davidmcnab.041004144551.40">def __init__(self, client, dest): - """ - Do not instantiate this directly - """ - self.client = client - self.dest = dest -</t> -<t tx="davidmcnab.041004144551.41">def send(self, peerdest, msg): - """ - """ - return self.client.send(self.dest, peerdest, msg) -</t> -<t tx="davidmcnab.041004144551.42">def receive(self): - - return self.client.receive(self.dest) -</t> -<t tx="davidmcnab.041004144551.43">def destroy(self): - - return self.client.destroySession(self.dest) - -</t> -<t tx="davidmcnab.041004144551.44"></t> -<t tx="davidmcnab.041004144551.45">def log(level, msg, nPrev=0): - - # ignore messages that are too trivial for chosen verbosity - if level > verbosity: - return - - loglock.acquire() - try: - # rip the stack - caller = traceback.extract_stack()[-(2+nPrev)] - path, line, func = caller[:3] - path = os.path.split(path)[1] - full = "%s:%s:%s():\n* %s" % ( - path, - line, - func, - msg.replace("\n", "\n + ")) - now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) - msg = "%s %s\n" % (now, full) - - if logfile == sys.stdout: - print msg - else: - file(logfile, "a").write(msg+"\n") - except: - s = StringIO.StringIO() - traceback.print_exc(file=s) - print s.getvalue() - print "Logger crashed" - loglock.release()</t> -<t tx="davidmcnab.041004144551.46">def logException(level, msg=''): - s = StringIO.StringIO() - traceback.print_exc(file=s) - log(level, "%s\n%s" % (s.getvalue(), msg), 1) -</t> -<t tx="davidmcnab.041004144551.47">def demoNAMING(): - """ - Demonstrates the NAMING service - """ - print "Starting SAM NAMING demo..." - print - - print "Instantiating client connection..." - c0 = I2PSamClient() - print "Client connection created" - - for host in ['duck.i2p', 'nonexistent.i2p']: - print "Sending query for host '%s'..." % host - try: - res = c0.samNamingLookup(host) - print "query for %s returned:" % host - print repr(res) - except I2PCommandFail, e: - print "got exception: %s" % repr(e.args) - - print - print "---------------------------------" - print "NAMING service tests succeeded" - print "---------------------------------" - print - - -</t> -<t tx="davidmcnab.041004144551.48">def demoRAW(): - """ - Runs a demo of SAM RAW messaging - """ - print "Starting SAM RAW demo..." - print - - print "Instantiating client connections..." - c1 = I2PSamClient() - c2 = I2PSamClient() - - print "Creating dests via SAM" - pub1, priv1 = c1.samDestGenerate() - pub2, priv2 = c2.samDestGenerate() - print "SAM Dests generated ok" - - print "Creating SAM RAW SESSION on connection c1..." - res = c1.samSessionCreate("RAW", priv1) - if res != 'OK': - print "Failed to create session on connection c1: %s" % repr(res) - return - print "Session on connection c1 created successfully" - - print "Creating SAM SESSION on connection c2..." - res = c2.samSessionCreate("RAW", priv2) - if res != 'OK': - print "Failed to create session on connection c2: %s" % repr(res) - return - print "Session on connection c2 created successfully" - - msg = "Hi there!" - print "sending from c1 to c2: %s" % repr(msg) - c1.samRawSend(pub2, msg) - - print "now try to receive from c2 (will block)..." - msg1 = c2.samRawReceive() - print "Connection c2 got %s" % repr(msg1) - - print - print "---------------------------------" - print "RAW data transfer tests succeeded" - print "---------------------------------" - print - -</t> -<t tx="davidmcnab.041004144551.49">def demoDATAGRAM(): - """ - Runs a demo of SAM DATAGRAM messaging - """ - print "Starting SAM DATAGRAM demo..." - print - - print "Instantiating 2 more client connections..." - c3 = I2PSamClient() - c4 = I2PSamClient() - - print "Creating more dests via SAM" - pub3, priv3 = c3.samDestGenerate() - pub4, priv4 = c4.samDestGenerate() - - print "Creating SAM DATAGRAM SESSION on connection c3..." - res = c3.samSessionCreate("DATAGRAM", priv3) - if res != 'OK': - print "Failed to create DATAGRAM session on connection c3: %s" % repr(res) - return - print "DATAGRAM Session on connection c3 created successfully" - - print "Creating SAM DATAGRAM SESSION on connection c4..." - res = c4.samSessionCreate("DATAGRAM", priv4) - if res != 'OK': - print "Failed to create DATAGRAM session on connection c4: %s" % repr(res) - return - print "Session on connection c4 created successfully" - - msg = "Hi there, this is a datagram!" - print "sending from c3 to c4: %s" % repr(msg) - c3.samDatagramSend(pub4, msg) - - print "now try to receive from c4 (will block)..." - remdest, msg1 = c4.samDatagramReceive() - print "Connection c4 got %s from %s..." % (repr(msg1), repr(remdest)) - - - print - print "--------------------------------------" - print "DATAGRAM data transfer tests succeeded" - print "--------------------------------------" - print - -</t> -<t tx="davidmcnab.041004144551.50">def demoSTREAM(): - """ - Runs a demo of SAM STREAM messaging - """ - print "Starting SAM STREAM demo..." - print - - print "Instantiating client c6..." - c6 = I2PSamClient() - - print "Creating dest for c6" - pub6, priv6 = c6.samDestGenerate() - - print "Creating SAM STREAM SESSION on connection c6..." - res = c6.samSessionCreate("STREAM", priv6, direction="RECEIVE") - if res != 'OK': - print "Failed to create STREAM session on connection c6: %s" % repr(res) - return - print "STREAM Session on connection c6 created successfully" - - print "Launching acceptor thread..." - thread.start_new_thread(demoSTREAM_thread, (c6,)) - - #print "sleep a while and give the server a chance..." - #time.sleep(10) - - print "----------------------------------------" - - print "Instantiating client c5..." - c5 = I2PSamClient() - - print "Creating dest for c5" - pub5, priv5 = c5.samDestGenerate() - - print "Creating SAM STREAM SESSION on connection c5..." - res = c5.samSessionCreate("STREAM", priv5, direction="CREATE") - if res != 'OK': - print "Failed to create STREAM session on connection c5: %s" % repr(res) - return - print "STREAM Session on connection c5 created successfully" - - print "----------------------------------------" - - print "Making connection from c5 to c6..." - - #set_trace() - - try: - conn_c5 = c5.samStreamConnect(pub6) - except: - print "Stream Connection failed" - return - print "Stream connect succeeded" - - print "Receiving from c5..." - buf = conn_c5.readline() - print "Got %s" % repr(buf) - - #print "Try to accept connection on c6..." - #conn_c6 = c6.sam - - print - print "--------------------------------------" - print "DATAGRAM data transfer tests succeeded" - print "--------------------------------------" - print - - - - - -</t> -<t tx="davidmcnab.041004144551.51">def demo(): - """ - This is a simple and straightforward demo of talking to - the i2psam server socket via the I2PSamClient class. - - Read the source, Luke, it's never been so easy... - """ - print - print "-----------------------------------------" - print "Running i2psamclient demo..." - print "-----------------------------------------" - print - - #demoNAMING() - #demoRAW() - #demoDATAGRAM() - demoSTREAM() - - print - print "-----------------------------------------" - print "Demo Finished" - print "-----------------------------------------" - - return -</t> -<t tx="davidmcnab.041004144551.52">if __name__ == '__main__': - - demo() -</t> -<t tx="davidmcnab.041204020513">def samStreamConnect(self, dest): - """ - Makes a STREAM connection to a remote dest - - STREAM STATUS - RESULT=$result - ID=$id - [MESSAGE=...] - """ - # need an ID - id = self.samAllocId() - - # create queue for connect reply - q = self.streamConnectReplies[id] = Queue.Queue() - - # send req - self.samSend("STREAM", "CONNECT", - ID=id, - DESTINATION=dest, - ) - - # await reply - comes back as a dict - resp = q.get() - - # ditch queue - del self.streamConnectReplies[id] - del q - - # check out response - result = resp['RESULT'] - if result == 'OK': - conn = I2PSAMStream(self, id, dest) - self.streams[id] = conn - return conn - else: - msg = resp.get('MESSAGE', '') - raise I2PCommandFail(result, msg, "STREAM CONNECT") - -</t> -<t tx="davidmcnab.041204042212">def samAllocId(self): - """ - Allocates a new unique id as required by SAM protocol - """ - self.samNextIdLock.acquire() - id = self.samNextId - self.samNextId += 1 - self.samNextIdLock.release() - return id -</t> -<t tx="davidmcnab.041204042212.1">class I2PSAMStream: - """ - Wrapper for a stream object - """ - @others -</t> -<t tx="davidmcnab.041204042212.2">def __init__(self, client, id, dest): - """ - """ - self.client = client - self.id = id - self.dest = dest - - self.qIncomingData = Queue.Queue() - - self.inbuf = '' - self.isOpen = 1 -</t> -<t tx="davidmcnab.041204044135">def _notifyIncomingData(self, data): - """ - Called by client receiver to notify incoming data - """ - log(4, "got %s" % repr(data)) - self.qIncomingData.put(data) -</t> -<t tx="davidmcnab.041204044735">def samStreamSend(self, conn, data): - """ - DO NOT CALL THIS DIRECTLY - - Invoked by an I2PSAMStream object to transfer data - Use the object's .send() method instead. - - conn is the I2PSAMStream - - STREAM SEND - ID=$id - SIZE=$numBytes\n[$numBytes of data] - """ - # dispatch - self.samSend("STREAM", "SEND", data, ID=conn.id) - - # useless, but mimics socket paradigm - return len(data) - -</t> -<t tx="davidmcnab.041204044735.1">def send(self, data): - """ - Sends data to a stream connection - """ - # barf if stream not open - if not self.isOpen: - raise I2PStreamClosed - - # can send - return self.client.samStreamSend(self, data) -</t> -<t tx="davidmcnab.041204050339">def samStreamClose(self, conn): - """ - DO NOT CALL DIRECTLY - - Invoked by I2PSAMStream to close stream - Use the object's .send() method instead. - - STREAM CLOSE - ID=$id - """ - self.samSend("STREAM", "CLOSE", ID=conn.id) - del self.streams[conn.id] - -</t> -<t tx="davidmcnab.041204050339.1">def recv(self, size): - """ - Retrieves n bytes from peer - """ - chunks = [] - - while self.isOpen and size > 0: - # try internal buffer first - if self.inbuf: - chunk = self.inbuf[:size] - chunklen = len(chunk) - self.inbuf = self.inbuf[chunklen:] - chunks.append(chunk) - size -= chunklen - else: - # replenish input buffer - log(4, "I2PSAMStream.recv: replenishing input buffer") - buf = self.qIncomingData.get() - if buf == '': - # connection closed by peer - self.isOpen = 0 - break - else: - # got more data - log(4, "I2PSAMStream: queue returned %s" % repr(buf)) - self.inbuf += buf - - # return whatever we've got, hopefully all - return "".join(chunks) - - -</t> -<t tx="davidmcnab.041204050339.2">def close(self): - """ - close this stream connection - """ - log(4, "closing stream") - self.client.samStreamClose(self) - log(4, "stream closed") - self.isOpen = 0 - - # and just to make sure... - self.qIncomingData.put("") # busts out of recv() loops - -</t> -<t tx="davidmcnab.041204050511">def __del__(self): - """ - Dropping last ref to this object closes stream - """ - self.close() -</t> -<t tx="davidmcnab.041204203651">def demoSTREAM_thread(sess): - - while 1: - sock = sess.samStreamAccept() - log(4, "got incoming connection") - - print "**ACCEPTOR SLEEPING 10 secs BEFORE SENDING" - - time.sleep(10) - - sock.send("Hi there, what do you want?\n") - - print "**ACCEPTOR SLEEPING 5 MINS BEFORE CLOSING" - time.sleep(300) - print "**ACCEPTOR CLOSING STREAM" - - sock.close() - -</t> -<t tx="davidmcnab.041204204235">def samStreamAccept(self): - """ - Waits for an incoming connection, returning a wrapped conn obj - """ - log(4, "waiting for connection") - conn = self.qNewStreams.get() - log(4, "got connection") - return conn -</t> -<t tx="davidmcnab.041304205426">def threadSocketReceiver(self, sock, id): - """ - One of these gets launched each time a new stream connection - is created. Due to the lack of callback mechanism within the - ministreaming API, we have to actively poll for and send back - received data - """ - while 1: - #avail = sock.available() - #if avail <= 0: - # print "threadSocketReceiver: waiting for data on %s (%s avail)..." % (id, avail) - # time.sleep(5) - # continue - #log(4, "reading a byte") - - try: - buf = sock.recv(1) - except: - logException(4, "Exception reading first byte") - - if buf == '': - log(4, "stream closed") - - # notify a close - self.samSend("STREAM", "CLOSED", - ID=id) - return - - # grab more if there's any available - navail = sock.available() - if navail > 0: - #log(4, "%d more bytes available, reading..." % navail) - rest = sock.recv(navail) - buf += rest - - # send if off - log(4, "got from peer: %s" % repr(buf)) - - self.samSend("STREAM", "RECEIVED", buf, - ID=id, - ) - - - - -</t> -<t tx="davidmcnab.041304235615">def readline(self): - """ - Read a line of text from stream, return the line without trailing newline - - This method really shouldn't exist in a class that's trying to look a bit - like a socket object, but what the hell! - """ - chars = [] - while 1: - char = self.recv(1) - if char in ['', '\n']: - break - chars.append(char) - return "".join(chars) -</t> -</tnodes> -</leo_file> diff --git a/apps/sam/doc/README b/apps/sam/doc/README index 7c2ff3dd31..8e8736de54 100644 --- a/apps/sam/doc/README +++ b/apps/sam/doc/README @@ -1,27 +1,8 @@ -SAM - Simple Anonymous Messaging - is a protocol which allows -I2P applications to access the I2P network via an unencrypted -TCP socket connection. - -Interim SAM protocol specification can be found at: -http://drupal.i2p.net/node/view/144 - -At time of first writing this README, an implementation of the SAM -server has been implemented in Jython (www.jython.org). - -You can find the server code, and build files, in the ../jython -directory. - -A python client implementation, containing demo functions, -can be found in the ../python directory. - -I2P developers are strongly encouraged to create SAM -client implementations in other languages, most importantly, -popular portable languages like C/C++, Perl and Ruby. - -The 'code.leo' file in this directory is used by the Leo -code editor (http://leo.sf.net), to manage the source in a -flexible tree format. While I can't insist on it, I'd -massively appreciate it if you could use this editor when -making additions and changes to the files herein, because -it will save me a lot of maintenance effort. - +The Simple Anonymous Messaging protocol provides a way for +client applications to communicate anonymously over I2P without +having to deal with the complexities of I2CP. More information can +be found at http://www.i2p.net/sam and a comparison of the various +client access techniques is up at http://www.i2p.net/applications + +There are a few SAM libraries available in this package, as well as +a Java implementation of the SAM bridge. \ No newline at end of file diff --git a/apps/sam/jython/README b/apps/sam/jython/README deleted file mode 100644 index 0d61fae466..0000000000 --- a/apps/sam/jython/README +++ /dev/null @@ -1,54 +0,0 @@ ------------------------------------ -Instructions for building i2psam.jar ------------------------------------- - -1) Requirements - -You will need: - - - jython - www.jython.org - -Note that you don't need python to build the SAM server - -IMPORTANT - when you're installing jython, and running - 'java on jython_n.n.class', -make sure you run the java.exe that's in your SDK, not the one in your -JRE. - ---------------------------------------------------------- - -2) Preparation - - - add the main jython directory to your PATH. Test this by typing - 'jythonc' from a shell prompt. - ----------------------------------------------------------- - -3) Building - - - type 'ant build' - ----------------------------------------------------------- - -4) Installing - -Copy i2psam.jar to wherever the jar files live on your i2p installation, -usually <i2pbasedir>/lib - -Find jython.jar, and copy it there too - ----------------------------------------------------------- - -5) Running - -(assuming that you're putting the start script into your main i2p -runtime directory, where the I2P jars live in a 'lib' subdirectory) - -You will need to launch i2psam.jar with a command like: - - java -cp lib/jython.jar:lib/i2p.jar:lib/mstreaming.jar:lib/i2psam.jar i2psam - -or on windows, - - java -cp lib\jython.jar;lib\i2p.jar;lib\mstreaming.jar;lib\i2psam.jar i2psam - diff --git a/apps/sam/jython/build.xml b/apps/sam/jython/build.xml deleted file mode 100644 index 4a7656152f..0000000000 --- a/apps/sam/jython/build.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project basedir="." default="all" name="sam"> - - <target name="all" depends="build" /> - - <target name="build" depends="builddep, jar" /> - - <target name="builddep"> - <ant dir="../../../core/java/" target="build" /> - <ant dir="../../ministreaming/java/" target="build" /> - </target> - - <target name="jar"> - - <condition property="jythonext" value=".bat"> - <os family="windows" /> - </condition> - <condition property="jythonext" value=""> - <not> - <os family="windows" /> - </not> - </condition> - - <exec executable="jythonc${jythonext}" dir="."> - <env key="CLASSPATH" path="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar"/> - <arg value="--jar"/><arg path="./i2psam.jar"/> - <arg path="./src/i2psam.py"/> - </exec> - </target> - - <target name="clean"> - <delete file="i2psam.jar" /> - <delete dir="./jpywork" /> - </target> - -</project> diff --git a/apps/sam/jython/src/i2psam.py b/apps/sam/jython/src/i2psam.py deleted file mode 100644 index fef0b482ee..0000000000 --- a/apps/sam/jython/src/i2psam.py +++ /dev/null @@ -1,2579 +0,0 @@ -#!/usr/bin/env jython -#@+leo-ver=4 -#@+node:@file jython/src/i2psam.py -#@@first -r""" -Implements I2P SAM Server. (refer U{http://drupal.i2p.net/node/view/144}) - -Also contains useful classes for jython programs, -which wrap the I2P java classes into more python-compatible -paradigms. - -If you run this module (or the i2psam.jar file created from it) -without arguments, it'll run an I2P SAM server bridge, listening -on port 7656. - -The file i2psamclient.py contains python client classes and a -demo program. - -Latest vers of this file is available from U{http://www.freenet.org.nz/i2p/i2psam.py} -Latest epydoc-generated doco at U{http://www.freenet.org.nz/i2p/i2pjyDoc} - -The i2psam.jar file is built from this module with the following -command (requires jython and java 1.4.x+ to be installed):: - - CLASSPATH=/path/to/i2p.jar:/path/to/mstreaming.jar \ - jythonc -jar i2psam.jar --all -A net.invisiblenet i2psam.py - -""" - -#@+others -#@+node:imports -# python imports -import sys, os, time, Queue, thread, threading, StringIO, traceback, getopt -from SocketServer import ThreadingTCPServer, StreamRequestHandler - -# java imports -import java - -# i2p-specific imports -import net.i2p -import net.i2p.client # to shut up epydoc -#import net.i2p.client.I2PClient -#import net.i2p.client.I2PClientFactory -#import net.i2p.client.I2PSessionListener -import net.i2p.client.naming -import net.i2p.client.streaming -import net.i2p.crypto -import net.i2p.data - -# handy shorthand refs -i2p = net.i2p -jI2PClient = i2p.client.I2PClient - -# import my own helper hack module -#import I2PHelper - -#@-node:imports -#@+node:globals -clientFactory = i2p.client.I2PClientFactory - -#i2phelper = I2PHelper() - -PROP_RELIABILITY_BEST_EFFORT = i2p.client.I2PClient.PROP_RELIABILITY_BEST_EFFORT -PROP_RELIABILITY_GUARANTEED = i2p.client.I2PClient.PROP_RELIABILITY_GUARANTEED - -version = "0.1.0" - -# host/port that our socketserver listens on -i2psamhost = "127.0.0.1" -i2psamport = 7656 - -# host/port that I2P's I2CP listens on -i2cpHost = "127.0.0.1" -i2cpPort = 7654 - -#print "i2cpPort=%s" % repr(i2cpPort) - -# ------------------------------------------ -# logging settings - -# 1=v.quiet, 2=normal, 3=verbose, 4=debug, 5=painful -verbosity = 2 - -# change to a filename to log there instead -logfile = sys.stdout - -# when set to 1, and when logfile != sys.stdout, log msgs are written -# both to logfile and console stdout -log2console = 1 - -# don't touch this! -loglock = threading.Lock() - - -#@-node:globals -#@+node:class JavaWrapper -class JavaWrapper: - """ - Wraps a java object as attribute '_item', and forwards - __getattr__ to it. - - All the classes here derive from this - """ - def __init__(self, item): - self._item = item - - def __getattr__(self, attr): - return getattr(self._item, attr) - - -#@-node:class JavaWrapper -#@+node:class I2PDestination -class I2PDestination(JavaWrapper): - """ - Wraps java I2P destination objects, with a big difference - these - objects store the private parts. - """ - #@ @+others - #@+node:__init__ - def __init__(self, **kw): - """ - Versatile constructor - - Keywords (choose only one option): - - (none) - create a whole new dest - - dest, private - wrap an existing I2P java dest with private stream - (private is a byte array) - - bin - reconstitute a public-only dest from a binary string - - binfile - reconstitute public-only from a binary file - - binprivate - reconsistitute private dest from binary string - - binfileprivate - reconsistitute private dest from binary file pathname - - base64 - reconstitute public-only from base64 string - - base64file - reconstitute public-only from file containing base64 - - base64private - reconstitute private from string containing base64 - - base64fileprivate - reconstitute private from file containing base64 - - also: - - client - a java net.i2p.client.I2PClient object - (avoids need for temporary client object when creating new dests) - """ - dest = i2p.data.Destination() - JavaWrapper.__init__(self, dest) - self._private = None - - if kw.has_key('dest'): - self._item = kw['dest'] - if kw.has_key('private'): - self._private = kw['private'] - - elif kw.has_key('bin'): - self.fromBin(kw['bin']) - - elif kw.has_key('binfile'): - self.fromBinFilePrivate(kw['binfile']) - - elif kw.has_key('binprivate'): - self.fromBinPrivate(kw['binprivate']) - - elif kw.has_key('binfileprivate'): - self.fromBinFilePrivate(kw['binfileprivate']) - - elif kw.has_key('base64'): - self.fromBase64(kw['base64']) - - elif kw.has_key('base64file'): - self.fromBase64File(kw['base64file']) - - elif kw.has_key('base64private'): - self.fromBase64Private(kw['base64private']) - - elif kw.has_key('base64fileprivate'): - self.fromBase64FilePrivate(kw['base64fileprivate']) - - else: - # create a whole new one, with a temporary client object (if needed) - if kw.has_key('client'): - client = kw['client'] - else: - client = clientFactory.createClient() - bytestream = java.io.ByteArrayOutputStream() - self._item = client.createDestination(bytestream) - self._private = bytestream.toByteArray() - - #@-node:__init__ - #@+node:toBin - def toBin(self): - """ - Returns a binary string of dest - """ - return bytearray2str(self.toByteArray()) - - #@-node:toBin - #@+node:toBinFile - def toBinFile(self, path): - """ - Writes out public binary to a file - """ - f = open(path, "wb") - f.write(self.toBin()) - f.flush() - f.close() - - #@-node:toBinFile - #@+node:toBinPrivate - def toBinPrivate(self): - """ - Returns the private key string as binary - """ - if self._private == None: - raise NoPrivateKey - return bytearray2str(self._private) - - #@-node:toBinPrivate - #@+node:toBinFilePrivate - def toBinFilePrivate(self, path): - """ - Writes out a binary file with the dest info - """ - f = open(path, "wb") - f.write(self.toBinPrivate()) - f.flush() - f.close() - - #@-node:toBinFilePrivate - #@+node:toBase64 - def toBase64(self): - """ - Returns base64 string of public part - """ - return self._item.toBase64() - - #@-node:toBase64 - #@+node:toBase64Private - def toBase64Private(self): - """ - Exports dest as base64, including private stuff - """ - if self._private == None: - raise NoPrivateKey - return i2p.data.Base64.encode(self._private) - - #@-node:toBase64Private - #@+node:toBase64File - def toBase64File(self, path): - """ - Exports dest to file as base64 - """ - f = open(path, "wb") - f.write(self.toBase64()) - f.flush() - f.close() - - #@-node:toBase64File - #@+node:toBase64FilePrivate - def toBase64FilePrivate(self, path): - """ - Writes out a base64 file with the private dest info - """ - f = open(path, "wb") - f.write(self.toBase64Private()) - f.flush() - f.close() - - #@-node:toBase64FilePrivate - #@+node:fromBin - def fromBin(self, bin): - """ - Loads this dest from a binary string - """ - self._item.fromByteArray(str2bytearray(bin)) - self._private = None - - #@-node:fromBin - #@+node:fromBinFile - def fromBinFile(self, path): - """ - Loads public part from file containing binary - """ - f = open(path, "rb") - self.fromBin(f.read()) - f.close() - - #@-node:fromBinFile - #@+node:fromBinPrivate - def fromBinPrivate(self, s): - """ - Loads this dest object from a base64 private key string - """ - bytes = str2bytearray(s) - self._private = bytes - stream = java.io.ByteArrayInputStream(bytes) - self._item.readBytes(stream) - - #@-node:fromBinPrivate - #@+node:fromBinFilePrivate - def fromBinFilePrivate(self, path): - """ - Loads this dest object, given the pathname of a file containing - a binary destkey - """ - self.fromBinPrivate(open(path, "rb").read()) - - #@-node:fromBinFilePrivate - #@+node:fromBase64 - def fromBase64(self, b64): - """ - Loads this dest from a base64 string - """ - self._item.fromBase64(b64) - self._private = None - - #@-node:fromBase64 - #@+node:fromBase64File - def fromBase64File(self, path): - """ - Loads public part from file containing base64 - """ - f = open(path, "rb") - self.fromBase64(f.read()) - f.close() - - #@-node:fromBase64File - #@+node:fromBase64Private - def fromBase64Private(self, s): - """ - Loads this dest object from a base64 private key string - """ - bytes = i2p.data.Base64.decode(s) - self._private = bytes - stream = java.io.ByteArrayInputStream(bytes) - self._item.readBytes(stream) - - #@-node:fromBase64Private - #@+node:fromBase64PrivateFile - def fromBase64FilePrivate(self, path): - """ - Loads this dest from a base64 file containing private key - """ - self.fromBase64Private(open(path, "rb").read()) - - #@-node:fromBase64PrivateFile - #@+node:sign - def sign(self, s): - """ - Signs a string using this dest's priv key - """ - # get byte stream - bytes = str2bytearray(s) - - # stream up our private bytes - stream = java.io.ByteArrayInputStream(self._private) - - # temporary dest object - d = i2p.data.Destination() - - # suck the public part off the stream - d.readBytes(stream) - - # temporary private key object - privkey = i2p.data.PrivateKey() - privkey.readBytes(stream) - - # now we should just have the signing key portion left in the stream - signingkey = i2p.data.SigningPrivateKey() - signingkey.readBytes(stream) - - # create DSA engine - dsa = i2p.crypto.DSAEngine() - - sig = dsa.sign(bytes, signingkey) - - rawsig = bytearray2str(sig.getData()) - - return rawsig - - #@-node:sign - #@+node:verify - def verify(self, s, sig): - """ - Verifies a string against this dest, to test if it was actually - signed by whoever has the dest privkey - """ - # get byte stream from data - databytes = str2bytearray(s) - - # get signature stream from sig - sigstream = java.io.ByteArrayInputStream(str2bytearray(sig)) - - # make a signature object - signature = i2p.data.Signature() - signature.readBytes(sigstream) - - # get signature verify key - pubkey = self.getSigningPublicKey() - - #log(4, "databytes=%s, pubkey=%s" % (repr(databytes), repr(pubkey))) - - # now get a verification - dsa = i2p.crypto.DSAEngine() - result = dsa.verifySignature(signature, databytes, pubkey) - - return result - - - - #@-node:verify - #@+node:hasPrivate - def hasPrivate(self): - """ - Returns True if this dest has private parts, False if not - """ - - if self._private: - return 1 - else: - return 0 - #@-node:hasPrivate - #@-others - -#@-node:class I2PDestination -#@+node:class I2PClient -class I2PClient(JavaWrapper): - """ - jython-comfortable wrapper for java I2P client class - """ - #@ @+others - #@+node:__init__ - def __init__(self, **kw): - """ - I2PClient constructor - - No args or keywords as yet - """ - client = clientFactory.createClient() - JavaWrapper.__init__(self, client) - - #@-node:__init__ - #@+node:createDestination - def createDestination(self, **kw): - """ - Creates a destination, either a new one, or from a bin or base64 file - - Keywords: - - see L{I2PDestination} constructor - """ - return I2PDestination(**kw) - - #@-node:createDestination - #@+node:createSession - def createSession(self, dest, sessionClass=None, **kw): - """ - Create a session - - Arguments: - - dest - an L{I2PDestination} object which MUST contain a private portion - - sessionClass - if given, this should be a subclass - of I2PSession. This allows you to implement your own handlers. - - Keywords: - - session options (refer javadocs) - """ - if sessionClass is None: - sessionClass = I2PSession - - if not dest.hasPrivate(): - raise NoPrivateKey("Dest object has no private key") - - #print kw - #session = self._item.createSession(destStream, dict2props(kw)) - session = sessionClass(client=self, dest=dest, **kw) - return session - #return sessionClass(session=session) - - #@-node:createSession - #@-others - -#@-node:class I2PClient -#@+node:class I2PSession -class I2PSession(JavaWrapper): - """ - Wraps an I2P client session - - You can subclass this, overriding the on_* handler callbacks, - and pass it as an argument to I2PClient.createSession - - In the default 'on_message' callback, message retrieval is - synchronous - inbound messages get written to an internal queue, - which you can checked with numMessages() and retrieved from via - getMessage(). You may override on_message() if you - want to handle incoming messages asynchronously yourself. - - Note - as far as I can tell, this class should be thread-safe. - """ - #@ @+others - #@+node:attributes - host = i2cpHost - port = i2cpPort - #@-node:attributes - #@+node:__init__ - def __init__(self, **kw): - """ - I2PSession constructor - - Keywords: - - either: - - session - a java i2p session object - - or: - - client - an L{I2PClient} object - - dest - an L{I2PDestination} object - Also: - - listener - an L{I2PSessionListener} object. - - Router-level options: - - reliability - one of 'guaranteed' and 'besteffort' (default 'besteffort') - - host - host on which router is running - - port - port on which router is listening - """ - # - # grab options destined for java class - # - options = {} - - reliability = takeKey(kw, 'reliability', 'besteffort') - if reliability == 'guaranteed': - reliability = jI2PClient.PROP_RELIABILITY_GUARANTEED - else: - reliability = jI2PClient.PROP_RELIABILITY_BEST_EFFORT - options[jI2PClient.PROP_RELIABILITY] = reliability - - host = takeKey(kw, 'host', self.host) - options[jI2PClient.PROP_TCP_HOST] = host - - port = takeKey(kw, 'port', self.port) - options[jI2PClient.PROP_TCP_PORT] = str(port) - - if kw.has_key('reliability'): - reliability = kw['reliability'] - - if kw.has_key('listener'): - listener = kw['listener'] - del kw['listener'] - else: - listener = I2PSessionListener() - - #print options - - # - # other keywords handled locally - # - if kw.has_key('session'): - session = kw['session'] - del kw['session'] - JavaWrapper.__init__(self, session) - elif kw.has_key('client') and kw.has_key('dest'): - client = kw['client'] - dest = kw['dest'] - del kw['client'] - del kw['dest'] - destStream = java.io.ByteArrayInputStream(dest._private) - session = self._item = client._item.createSession(destStream, dict2props(options)) - #client.createSession(dest, dict2props(options)) - else: - raise Exception("implementation incomplete") - - # set up a listener - self.setSessionListener(listener) - - # set up a queue for inbound msgs - self.qInbound = Queue.Queue() - self.lockInbound = threading.Lock() - self.nInboundMessages = 0 - - self.lockOutbound = threading.Lock() - - - - #@-node:__init__ - #@+node:sendMessage - def sendMessage(self, dest, payload): - """ - Sends a message to another dest - - Arguments: - - dest - an L{I2PDestination} object - - payload - a string to send - """ - dest = dest._item - payload = str2bytearray(payload) - self.lockOutbound.acquire() - try: - res = self._item.sendMessage(dest, payload) - except: - self.lockOutbound.release() - raise - self.lockOutbound.release() - return res - #@-node:sendMessage - #@+node:numMessages - def numMessages(self): - """ - Returns the number of unretrieved inbound messages - """ - self.lockInbound.acquire() - n = self.nInboundMessages - self.lockInbound.release() - return n - #@-node:numMessages - #@+node:getMessage - def getMessage(self, blocking=1): - """ - Returns the next available inbound message. - - If blocking is set to 1 (default), blocks - till another message comes in. - - If blocking is set to 0, returns None if there - are no available messages. - """ - if blocking: - msg = self.qInbound.get() - #print "getMessage: acquiring lock" - self.lockInbound.acquire() - #print "getMessage: got lock" - self.nInboundMessages -= 1 - else: - #print "getMessage: acquiring lock" - self.lockInbound.acquire() - #print "getMessage: got lock" - if self.nInboundMessages > 0: - msg = self.qInbound.get() - self.nInboundMessages -= 1 - else: - msg = None - self.lockInbound.release() - #print "getMessage: released lock" - return msg - - #@-node:getMessage - #@+node:setSessionListener - def setSessionListener(self, listener): - """ - Designates an L{I2PSessionListener} object to listen to this session - """ - self.listener = listener - listener.addSession(self) - self._item.setSessionListener(listener) - - - #@-node:setSessionListener - #@+node:destroySession - def destroySession(self): - """ - Destroys an existing session - - Note that due to a jython quirk, calls to destroySession might - trigger a TypeError relating to arg mismatch - we ignore such - errors here because by the time the exception happens, the - session has already been successfully closed - """ - try: - self._item.destroySession() - except TypeError: - pass - - #@-node:destroySession - #@+node:CALLBACKS - # - # handler methods which you should override - # - - #@+others - #@+node:on_message - def on_message(self, msg): - """ - Callback for when a message arrives. - - Appends the message to the inbound queue, which you can check - with the numMessages() method, and read with getMessage() - - You should override this if you want to handle inbound messages - asynchronously. - - Arguments: - - msg - a string that was sent by peer - """ - #print "on_message: msg=%s" % msg - self.lockInbound.acquire() - #print "on_message: got lock" - self.qInbound.put(msg) - self.nInboundMessages += 1 - self.lockInbound.release() - #print "on_message: released lock" - - #@-node:on_message - #@+node:on_abuse - def on_abuse(self, severity): - """ - Callback indicating abuse is happening - - Arguments: - - severity - an int of abuse level, 1-100 - """ - print "on_abuse: severity=%s" % severity - - #@-node:on_abuse - #@+node:on_disconnected - def on_disconnected(self): - """ - Callback indicating remote peer disconnected - """ - print "on_disconnected" - - #@-node:on_disconnected - #@+node:on_error - def on_error(self, message, error): - """ - Callback indicating an error occurred - """ - print "on_error: message=%s error=%s" % (message, error) - - #@-node:on_error - #@-others - #@-node:CALLBACKS - #@-others -#@-node:class I2PSession -#@+node:class I2PSessionListener -class I2PSessionListener(i2p.client.I2PSessionListener): - """ - Wraps a java i2p.client.I2PSessionListener object - """ - def __init__(self, *sessions): - self.sessions = list(sessions) - - def addSession(self, session): - """ - Adds an L{I2PSession} object to the list of sessions to listen on - - Note - you must also invoke the session's setSessionListener() method - (see I2PSession.setSessionListener) - """ - if session not in self.sessions: - self.sessions.append(session) - - def delSession(self, session): - """ - Stop listening to a given session - """ - if session in self.sessions: - del self.sessions.index[session] - - def messageAvailable(self, session, msgId, size): - """ - Callback from java:: - public void messageAvailable( - I2PSession session, - int msgId, - long size) - """ - #print "listener - messageAvailable" - - # try to find session in our sessions table - sessions = filter(lambda s, session=session: s._item == session, self.sessions) - if sessions: - #print "compare to self.session->%s" % (session == self.session._item) - - # found a matching session - retrieve it - session = sessions[0] - - # retrieve message and pass to callback - msg = session.receiveMessage(msgId) - msgStr = bytearray2str(msg) - session.on_message(msgStr) - else: - print "messageAvailable: unknown session=%s msgId=%s size=%s" % (session, msgId, size) - - def reportAbuse(self, session, severity): - """ - Callback from java:: - public void reportAbuse( - I2PSession session, - int severity) - """ - if self.session: - self.session.on_abuse(severity) - else: - print "reportAbuse: unknown session=%s severity=%s" % (session, severity) - - def disconnected(self, session): - """ - Callback from java:: - public void disconnected(I2PSession session) - """ - if self.session: - self.session.on_disconnected() - else: - print "disconnected: unknown session=%s" % session - - def errorOccurred(session, message, error): - """ - Callback from java:: - public void errorOccurred( - I2PSession session, - java.lang.String message, - java.lang.Throwable error) - """ - if self.session: - self.session.on_error(message, error) - else: - print "errorOccurred: message=%s error=%s" % (message, error) - -#@-node:class I2PSessionListener -#@+node:class I2PSocket -class I2PSocket: - """ - Wraps I2P streaming API into a form resembling python sockets - """ - #@ @+others - #@+node:attributes - host = i2cpHost - port = i2cpPort - - #@-node:attributes - #@+node:__init__ - def __init__(self, dest=None, **kw): - """ - Create an I2P streaming socket - - Arguments: - - dest - a private destination to associate with this socket - - Keywords: - - host - hostname on which i2cp is listening (default self.host) - - port - port on which i2cp listens (default self.port) - - Internally used keywords (used for wrapping an accept()ed connection): - - dest - - remdest - - sock - - instream - - outstream - """ - # set up null attribs - self.sockmgr = None - self.instream = None - self.outstream = None - self.sock = None - self._connected = 0 - self._blocking = 1 - - # save dest (or lack thereof) - self.dest = dest - - if kw.has_key('sock') \ - and kw.has_key('remdest') \ - and kw.has_key('instream') \ - and kw.has_key('outstream'): - - # wrapping an accept()'ed connection - log(4, "accept()'ed a connection, wrapping...") - - self.sock = kw['sock'] - self.dest = dest - self.remdest = kw['remdest'] - self.instream = kw['instream'] - self.outstream = kw['outstream'] - else: - log(4, "creating new I2PSocket %s" % dest) - - # process keywords - self.host = kw.get('host', self.host) - self.port = int(kw.get('port', self.port)) - - # we need a factory, don't we? - self.sockmgrFact = i2p.client.streaming.I2PSocketManagerFactory() - - #@-node:__init__ - #@+node:bind - def bind(self, dest=None): - """ - 'binds' the socket to a dest - - dest is an I2PDestination object, which you may specify in the constructor - instead of here. However, we give you the option of specifying here for - some semantic compatibility with python sockets. - """ - if dest is not None: - self.dest = dest - elif not self.dest: - # create new dest, client should interrogate it at some time - log(4, "bind: socket has no dest, creating one") - self.dest = I2PDestination() - #@-node:bind - #@+node:listen - def listen(self, *args, **kw): - """ - Sets up the object to receive connections - """ - # sanity checks - if self.sockmgr: - raise I2PSocketError(".sockmgr already present - have you already called listen?") - if not self.dest: - raise I2PSocketError("socket is not bound to a destination") - - log(4, "listening on socket") - - # create the socket manager - self._createSockmgr() - - #@nonl - #@-node:listen - #@+node:accept - def accept(self): - """ - Waits for incoming connections, and returns a new I2PSocket object - with the connection - """ - # sanity check - if not self.sockmgr: - raise I2PSocketError(".listen() has not been called on this socket") - - # accept a conn and get its streams - sock = self.sockmgr.getServerSocket().accept() - instream = sock.getInputStream() - outstream = sock.getOutputStream() - remdest = I2PDestination(dest=sock.getPeerDestination()) - - # wrap it and return it - sockobj = I2PSocket(dest=self.dest, - remdest=remdest, - sock=sock, - instream=instream, - outstream=outstream) - self._connected = 1 - return sockobj - - #@-node:accept - #@+node:connect - def connect(self, remdest): - """ - Connects to a remote destination - - This has one totally major difference from the normal socket - paradigm, and that is that you can have n outbound connections - to different dests. - """ - # sanity check - if self.sockmgr: - raise I2PSocketError(".sockmgr already present - have you already called listen/connect?") - - # create whole new dest if none was provided to constructor - if self.dest is None: - log(4, "connect: creating whole new dest") - self.dest = I2PDestination() - - # create the socket manager - self._createSockmgr() - - # do the connect - #print "remdest._item = %s" % repr(remdest._item) - - opts = net.i2p.client.streaming.I2PSocketOptions() - try: - log(4, "trying to connect to %s" % remdest.toBase64()) - sock = self.sock = self.sockmgr.connect(remdest._item, opts) - self.remdest = remdest - except: - logException(2, "apparent exception, continuing...") - - self.instream = sock.getInputStream() - self.outstream = sock.getOutputStream() - - sockobj = I2PSocket(dest=self.dest, - remdest=remdest, - sock=sock, - instream=self.instream, - outstream=self.outstream) - self._connected = 1 - return sockobj - #@-node:connect - #@+node:recv - def recv(self, nbytes): - """ - Reads nbytes of data from socket - """ - # sanity check - if not self.instream: - raise I2PSocketError("Socket is not connected") - - # for want of better methods, read bytewise - chars = [] - while nbytes > 0: - byte = self.instream.read() - if byte < 0: - break # got all we're gonna get - char = chr(byte) - chars.append(char) - #print "read: got a byte %s (%s)" % (byte, repr(char)) - nbytes -= 1 - - # got it all - buf = "".join(chars) - #print "recv: buf=%s" % repr(buf) - return buf - - - #@-node:recv - #@+node:send - def send(self, buf): - """ - Sends buf thru socket - """ - # sanity check - if not self.outstream: - raise I2PSocketError("Socket is not connected") - - # and write it out - log(4, "send: writing '%s' to outstream..." % repr(buf)) - outstream = self.outstream - for c in buf: - outstream.write(ord(c)) - - # flush just in case - log(4, "send: flushing...") - self.outstream.flush() - - log(4, "send: done") - - #@-node:send - #@+node:available - def available(self): - """ - Returns the number of bytes available for recv() - """ - #print "available: sock is %s" % repr(self.sock) - - return self.instream.available() - - - #@-node:available - #@+node:close - def close(self): - """ - Closes the socket - """ - # sanity check - #if not self._connected: - # raise I2PSocketError("Socket is not connected") - - # shut up everything - try: - self.instream.close() - except: - pass - try: - self.outstream.close() - except: - pass - try: - self.sock.close() - except: - pass - #@-node:close - #@+node:_createSockmgr - def _createSockmgr(self): - - if getattr(self, 'sockmgr', None): - return - - #options = {jI2PClient.PROP_TCP_HOST: self.host, - # jI2PClient.PROP_TCP_PORT: self.port} - options = {} - props = dict2props(options) - - # get a java stream thing from dest - stream = java.io.ByteArrayInputStream(self.dest._private) - - # create socket manager thing - self.sockmgr = self.sockmgrFact.createManager(stream, self.host, self.port, props) - #@-node:_createSockmgr - #@-others -#@-node:class I2PSocket -#@+node:class I2PSamServer -class I2PSamServer(ThreadingTCPServer): - """ - A server which makes I2CP available via a socket - """ - #@ @+others - #@+node:attributes - host = i2psamhost - port = i2psamport - - i2cphost = i2cpHost - i2cpport = i2cpPort - - version = version - - - #@-node:attributes - #@+node:__init__ - def __init__(self, i2pclient=None, **kw): - """ - Create the client listener object - - Arguments: - - i2pclient - an I2PClient object - optional - if not - given, one will be created - - Keywords: - - host - host to listen on for client conns (default self.host ('127.0.0.1') - - port - port to listen on for client conns (default self.port (7656) - - i2cphost - host to talk to i2cp on (default self.i2cphost ('127.0.0.1')) - - i2cpport - port to talk to i2cp on (default self.i2cphost ('127.0.0.1')) - """ - - # create an I2PClient object if none given - if i2pclient is None: - i2pclient = I2PClient() - self.i2pclient = i2pclient - - # get optional host/port for client and i2cp - self.host = kw.get('host', self.host) - self.port = int(kw.get('port', self.port)) - self.i2cphost = kw.get('i2cphost', self.i2cphost) - self.i2cpport = int(kw.get('i2cpport', self.i2cpport)) - - # create record of current sessions, and a lock for it - self.sessions = {} - self.sessionsLock = threading.Lock() - self.streams = {} - self.streamsLock = threading.Lock() - self.samNextId = 1 - self.samNextIdLock = threading.Lock() - - # and create the server - try: - ThreadingTCPServer.__init__( - self, - (self.host, self.port), - I2PSamClientHandler) - except: - log(4, "crashed with host=%s, port=%s" % (self.host, self.port)) - raise - - #@-node:__init__ - #@+node:run - def run(self): - """ - Run the SAM server. - - when connections come in, they are automatically - accepted, and an L{I2PClientHandler} object created, - and its L{handle} method invoked. - """ - log(4, "Listening for client requests on %s:%s" % (self.host, self.port)) - self.serve_forever() - - - #@-node:run - #@+node:finish_request - def finish_request(self, request, client_address): - """Finish one request by instantiating RequestHandlerClass.""" - try: - self.RequestHandlerClass(request, client_address, self) - except: - pass - log(3, "Client session terminated") - #@-node:finish_request - #@+node:samAllocId - def samAllocId(self): - """ - Allocates a new unique id as required by SAM protocol - """ - self.samNextIdLock.acquire() - id = self.samNextId - self.samNextId += 1 - self.samNextIdLock.release() - return id - #@-node:samAllocId - #@-others -#@-node:class I2PSamServer -#@+node:class I2PSamClientHandler -class I2PSamClientHandler(StreamRequestHandler): - r""" - Manages a single socket connection from a client. - - When a client connects to the SAM server, the I2PSamServer - object creates an instance of this class, and invokes its - handle method. See L{handle}. - - Note that if a client terminates its connection to the server, the server - will destroy all current connections initiated by that client - - Size values are decimal - Connection is persistent - """ - #@ @+others - #@+node:handle - def handle(self): - """ - Reads command/data messages from SAM Client, executes these, - and sends back responses. - - Plants callback hooks into I2PSession objects, so that when - data arrives via I2P, it can be immediately sent to the client. - """ - self.localsessions = {} - self.globalsessions = self.server.sessions - - self.localstreams = {} # keyed by sam stream id - self.globalstreams = self.server.streams - - self.samSessionIsOpen = 0 - self.samSessionStyle = '' - - # localise the id allocator - self.samAllocId = self.server.samAllocId - - # need a local sending lock - self.sendLock = threading.Lock() - - log(5, "Got req from %s" % repr(self.client_address)) - - try: - self.namingService = i2p.client.naming.HostsTxtNamingService() - except: - logException(2, "Failed to create naming service object") - - try: - while 1: - # get req - req = self.rfile.readline().strip() - flds = [s.strip() for s in req.split(" ")] - cmd = flds[0] - if cmd in ['HELLO', 'SESSION', 'STREAM', 'DATAGRAM', 'RAW', 'NAMING', 'DEST']: - topic, subtopic, args = self.samParse(flds) - method = getattr(self, "on_"+cmd, None) - method(topic, subtopic, args) - else: - method = getattr(self, "on_"+cmd, None) - if method: - method(flds) - else: - # bad shit - self.wfile.write("error unknown command '%s'\n" % cmd) - - except IOError: - log(3, "Client connection terminated") - except ValueError: - pass - except: - logException(4, "Client req handler crashed") - self.wfile.write("error\n") - - # clean up sessions - for dest in self.localsessions.keys(): - if dest in self.globalsessions.keys(): - log(4, "forgetting global dest %s" % dest[:30]) - del self.globalsessions[dest] - - self.finish() - #thread.exit() - - #@-node:handle - #@+node:on_genkeys - def on_genkeys(self, flds): - - log(4, "entered") - - server = self.server - client = server.i2pclient - globalsessions = server.sessions - sessionsLock = server.sessionsLock - - read = self.rfile.read - readline = self.rfile.readline - write = self.wfile.write - flush = self.wfile.flush - - # genkeys - try: - dest = I2PDestination() - priv = dest.toBase64Private() - pub = dest.toBase64() - write("ok %s %s\n" % (pub, priv)) - except: - write("error exception\n") - #@-node:on_genkeys - #@+node:on_createsession - def on_createsession(self, flds): - - log(4, "entered") - - server = self.server - client = server.i2pclient - globalsessions = server.sessions - sessionsLock = server.sessionsLock - - read = self.rfile.read - readline = self.rfile.readline - write = self.wfile.write - flush = self.wfile.flush - - sessionsLock.acquire() - - try: - b64priv = flds[1] - - # spit if someone else already has this dest - if b64priv in globalsessions.keys(): - write("error dest in use\n") - elif b64priv in self.localsessions.keys(): - # duh, already open locally, treat as ok - write("ok\n") - else: - # whole new session - set it up - dest = I2PDestination(base64private=b64priv) - log(4, "Creating session on dest '%s'" % b64priv[:40]) - session = client.createSession(dest) - log(4, "Connecting session on dest '%s'" % b64priv[:40]) - session.connect() - log(4, "Session on dest '%s' now live" % b64priv[:40]) - - # and remember it - self.localsessions[b64priv] = session - globalsessions[b64priv] = session - - # and tell the client the good news - write("ok\n") - except: - logException(4, "createsession fail") - write("error exception\n") - - sessionsLock.release() - #@-node:on_createsession - #@+node:on_destroysession - def on_destroysession(self, flds): - - log(4, "entered") - - server = self.server - client = server.i2pclient - globalsessions = server.sessions - sessionsLock = server.sessionsLock - - read = self.rfile.read - readline = self.rfile.readline - write = self.wfile.write - flush = self.wfile.flush - - sessionsLock.acquire() - - try: - b64priv = flds[1] - - # spit if session not known - if not globalsessions.has_key(b64priv): - # no such session presently exists anywhere - write("error nosuchsession\n") - elif not self.localsessions.has_key(b64priv): - # session exists, but another client owns it - write("error notyoursession\n") - else: - # session exists and we own it - session = self.localsessions[b64priv] - del self.localsessions[b64priv] - del globalsessions[b64priv] - try: - session.destroySession() - write("ok\n") - except: - raise - except: - logException(4, "destroy session failed") - write("error exception\n") - - sessionsLock.release() - - log(4, "done") - - #@-node:on_destroysession - #@+node:on_send - def on_send(self, flds): - - #log(4, "entered: %s" % repr(flds)) - log(4, "entered") - - server = self.server - client = server.i2pclient - globalsessions = server.sessions - sessionsLock = server.sessionsLock - - read = self.rfile.read - readline = self.rfile.readline - write = self.wfile.write - flush = self.wfile.flush - - sessionsLock.acquire() - - session = None - try: - size = int(flds[1]) - b64priv = flds[2] - b64peer = flds[3] - msg = self._recvbytes(size) - - # spit if session not known - if not globalsessions.has_key(b64priv): - # no such session presently exists anywhere - log(4, "no such session") - write("error nosuchsession\n") - elif not self.localsessions.has_key(b64priv): - # session exists, but another client owns it - write("error notyoursession\n") - else: - session = self.localsessions[b64priv] - except: - logException(2, "Send exception") - write("error exception on send command\n") - - sessionsLock.release() - - if not session: - return - - # now get/instantiate the remote dest - try: - peerDest = I2PDestination(base64=b64peer) - except: - peerDest = None - logException(2, "Send: bad remote dest") - write("error bad remote dest\n") - if not peerDest: - return - - # and do the send - try: - res = session.sendMessage(peerDest, msg) - except: - logException(2, "Send: failed") - write("error exception on send\n") - res = None - - if res is None: - return - - # report result - if res: - write("ok\n") - else: - write("error send failed\n") - - log(4, "done") - - #@-node:on_send - #@+node:on_receive - def on_receive(self, flds): - - log(4, "entered") - - server = self.server - client = server.i2pclient - globalsessions = server.sessions - sessionsLock = server.sessionsLock - - read = self.rfile.read - readline = self.rfile.readline - write = self.wfile.write - flush = self.wfile.flush - - sessionsLock.acquire() - - session = None - try: - b64priv = flds[1] - - # spit if session not known - if not globalsessions.has_key(b64priv): - # no such session presently exists anywhere - write("error nosuchsession\n") - elif not self.localsessions.has_key(b64priv): - # session exists, but another client owns it - write("error notyoursession\n") - else: - session = self.localsessions[b64priv] - except: - logException(4, "receive command error") - write("error exception on receive command\n") - sessionsLock.release() - - if not session: - log(4, "no session matching privdest %s" % b64priv[:30]) - return - - # does this session have any received data? - if session.numMessages() > 0: - msg = session.getMessage() - write("ok %s\n%s" % (len(msg), msg)) - else: - write("ok 0\n") - - log(4, "done") - - return - - #@-node:on_receive - #@+node:on_HELLO - def on_HELLO(self, topic, subtopic, args): - """ - Responds to client PING - """ - log(4, "entered") - self.samSend("HELLO", "PONG") - log(4, "responded to HELLO") - - #@-node:on_HELLO - #@+node:on_SESSION - def on_SESSION(self, topic, subtopic, args): - - log(4, "entered") - - server = self.server - client = server.i2pclient - globalsessions = server.sessions - localsessions = self.localsessions - sessionsLock = server.sessionsLock - - read = self.rfile.read - readline = self.rfile.readline - write = self.wfile.write - flush = self.wfile.flush - - if subtopic == 'CREATE': - - if self.samSessionIsOpen: - self.samSend("SESSION", "STATUS", - RESULT="I2P_ERROR", - MESSAGE="Session_already_created", - ) - return - - # get/validate STYLE arg - style = self.samSessionStyle = args.get('STYLE', None) - if style is None: - self.samSend("SESSION", "STATUS", - RESULT="I2P_ERROR", - MESSAGE="Missing_STYLE_argument", - ) - return - elif style not in ['STREAM', 'DATAGRAM', 'RAW']: - self.samSend("SESSION", "STATUS", - RESULT="I2P_ERROR", - MESSAGE="Invalid_STYLE_argument_'%s'" % style, - ) - return - - # get/validate DESTINATION arg - dest = args.get('DESTINATION', None) - if dest == 'TRANSIENT': - # create new temporary dest - dest = self.samDest = I2PDestination() - destb64 = dest.toBase64Private() - else: - # make sure dest isn't globally or locally known - if dest in globalsessions.keys() or dest in localsessions.keys(): - self.samSend("SESSION", "STATUS", - RESULT="DUPLICATED_DEST", - MESSAGE="Destination_'%s...'_already_in_use" % dest[:20], - ) - return - - # try to reconstitute dest from given base64 - try: - destb64 = dest - dest = I2PDestination(base64private=dest) - except: - self.samSend("SESSION", "STATUS", - RESULT="INVALID_KEY", - MESSAGE="Bad_destination_base64_string_'%s...'" % destb64[:20], - ) - return - - # got valid dest now - self.dest = dest - self.samDestPub = dest.toBase64() - - if style in ['RAW', 'DATAGRAM']: - - if style == 'DATAGRAM': - # we need to know how big binary pub dests and sigs - self.samDestPubBin = dest.toBin() - self.samDestPubBinLen = len(self.samDestPubBin) - self.samSigLen = len(self.dest.sign("nothing")) - - log(4, "binary pub dests are %s bytes, sigs are %s bytes" % ( - self.samDestPubBinLen, self.samSigLen)) - - i2cpHost = args.get('I2CP.HOST', server.i2cphost) - i2cpPort = int(args.get('I2CP.PORT', server.i2cpport)) - - # both these styles require an I2PSession object - session = client.createSession(dest, host=i2cpHost, port=i2cpPort) - - # plug in our inbound message handler - session.on_message = self.on_message - - log(4, "Connecting session on dest '%s'" % destb64[:40]) - try: - session.connect() - except net.i2p.client.I2PSessionException: - self.samSend("SESSION", "STATUS", - RESULT="I2P_ERROR", - MESSAGE="Failed_to_connect_to_i2cp_port", - ) - logException(3, "Failed to connect I2PSession") - return - - log(4, "Session on dest '%s' now live" % destb64[:40]) - - # and remember it - localsessions[destb64] = session - globalsessions[destb64] = session - self.samSession = session - - else: # STREAM - # no need to create session object, because we're using streaming api - log(4, "Creating STREAM session") - - # what kind of stream? - direction = args.get('DIRECTION', 'BOTH') - if direction not in ['BOTH', 'RECEIVE', 'CREATE']: - self.samSend("SESSION", "STATUS", - RESULT="I2P_ERROR", - MESSAGE="Illegal_direction_keyword_%s" % direction.replace(" ","_"), - ) - return - - if direction == 'BOTH': - self.canConnect = 1 - self.canAccept = 1 - elif direction == 'RECEIVE': - self.canConnect = 0 - self.canAccept = 1 - elif direction == 'CREATE': - self.canConnect = 1 - self.canAccept = 0 - - # but we do need to mark it as being in use - localsessions[destb64] = globalsessions[destb64] = None - - # make a local socket - sock = self.samSock = I2PSocket(dest) - - # and we also need to fire up a socket listener, if not CREATE-only - if self.canAccept: - thread.start_new_thread(self.threadSocketListener, (sock, dest)) - - # finally, we can reply with the good news - self.samSend("SESSION", "STATUS", - RESULT="OK", - ) - - else: # subtopic != CREATE - self.samSend("SESSION", "STATUS", - RESULT="I2P_ERROR", - MESSAGE="Invalid_command_'SESSION_%s'" % subtopic, - ) - return - - #@-node:on_SESSION - #@+node:on_SESSION_CREATE - def on_SESSION_CREATE(self, topic, subtopic, args): - - log(4, "entered") - - server = self.server - client = server.i2pclient - globalsessions = server.sessions - localsessions = self.localsessions - sessionsLock = server.sessionsLock - - read = self.rfile.read - readline = self.rfile.readline - write = self.wfile.write - flush = self.wfile.flush - - #@-node:on_SESSION_CREATE - #@+node:on_STREAM - def on_STREAM(self, topic, subtopic, args): - - log(4, "entered") - - server = self.server - client = server.i2pclient - globalsessions = server.sessions - sessionsLock = server.sessionsLock - - read = self.rfile.read - readline = self.rfile.readline - write = self.wfile.write - flush = self.wfile.flush - - if subtopic == 'CONNECT': - # who are we connecting to again? - remdest = I2PDestination(base64=args['DESTINATION']) - id = int(args['ID']) - - try: - log(4, "Trying to connect to remote peer %s..." % args['DESTINATION']) - sock = self.samSock.connect(remdest) - log(4, "Connected to remote peer %s..." % args['DESTINATION']) - self.localstreams[id] = sock - self.samSend("STREAM", "STATUS", - RESULT='OK', - ID=id, - ) - thread.start_new_thread(self.threadSocketReceiver, (sock, id)) - - except: - log(4, "Failed to connect to remote peer %s..." % args['DESTINATION']) - self.samSend("STREAM", "STATUS", - RESULT='I2P_ERROR', - MESSAGE='exception_on_connect', - ID=id, - ) - - elif subtopic == 'SEND': - # send to someone - id = int(args['ID']) - try: - sock = self.localstreams[id] - sock.send(args['DATA']) - except: - logException(4, "send failed") - - - - - #@-node:on_STREAM - #@+node:on_DATAGRAM - def on_DATAGRAM(self, topic, subtopic, args): - r""" - DATAGRAM SEND - DESTINATION=$base64key - SIZE=$numBytes\n[$numBytes of data] - - All datagram messages have a signature/hash header, formatted as: - - sender's binary public dest - - S(H(sender_bin_pubdest + recipient_bin_pubdest + msg)) - """ - log(4, "entered") - - # at this stage of things, we don't know how to handle anything except SEND - if subtopic != 'SEND': - log(3, "Got illegal subtopic '%s' in DATAGRAM command" % subtopic) - return - - # get the details - peerdestb64 = args['DESTINATION'] - peerdest = I2PDestination(base64=peerdestb64) - peerdestBin = base64dec(peerdestb64) - data = args['DATA'] - - # make up the header - log(4, "samDestPubBin (%s) %s" % (type(self.samDestPubBin), repr(self.samDestPubBin))) - log(4, "peerdestBin (%s) %s" % (type(peerdestBin), repr(peerdestBin))) - log(4, "data (%s) %s" % (type(data), repr(data))) - - hashed = shahash(self.samDestPubBin + peerdestBin + data) - log(4, "hashed=%s" % repr(hashed)) - - sig = self.dest.sign(hashed) - log(4, "sig=%s" % repr(sig)) - hdr = self.samDestPubBin + sig - - # send the thing - self.samSession.sendMessage(peerdest, hdr + data) - - #@-node:on_DATAGRAM - #@+node:on_RAW - def on_RAW(self, topic, subtopic, args): - r""" - RAW SEND - DESTINATION=$base64key - SIZE=$numBytes\n[$numBytes of data] - """ - log(4, "entered") - - # at this stage of things, we don't know how to handle anything except SEND - if subtopic != 'SEND': - return - - # get the details - peerdest = I2PDestination(base64=args['DESTINATION']) - msg = args['DATA'] - - # send the thing - self.samSession.sendMessage(peerdest, msg) - #@-node:on_RAW - #@+node:on_NAMING - def on_NAMING(self, topic, subtopic, args): - - log(4, "entered: %s %s %s" % (repr(topic), repr(subtopic), repr(args))) - - # at this stage of things, we don't know how to handle anything except LOOKUP - if subtopic != 'LOOKUP': - return - - # get the details - host = args['NAME'] - - log(4, "looking up host %s" % host) - - # try to lookup - jdest = self.namingService.lookup(host) - - if not jdest: - log(4, "host %s not found" % host) - self.samSend("NAMING", "REPLY", - RESULT="KEY_NOT_FOUND", - NAME=host, - ) - return - - try: - b64 = I2PDestination(dest=jdest).toBase64() - self.samSend("NAMING", "REPLY", - RESULT="OK", - NAME=host, - VALUE=b64, - ) - log(4, "host %s found and valid key returned" % host) - return - except: - log(4, "host %s found but key invalid" % host) - self.samSend("NAMING", "REPLY", - RESULT="INVALID_KEY", - NAME=host, - ) - - #@-node:on_NAMING - #@+node:on_DEST - def on_DEST(self, topic, subtopic, args): - - log(4, "Generating dest") - - dest = I2PDestination() - priv = dest.toBase64Private() - pub = dest.toBase64() - - log(4, "Sending dest to client") - - self.samSend("DEST", "REPLY", PUB=pub, PRIV=priv) - - log(4, "done") - #@-node:on_DEST - #@+node:on_message - def on_message(self, msg): - """ - This callback gets plugged into the I2PSession object, - so we can asychronously notify our client when stuff arrives - """ - if self.samSessionStyle == 'RAW': - self.samSend("RAW", "RECEIVE", msg) - - elif self.samSessionStyle == 'DATAGRAM': - # ain't so simple, we gotta rip and validate the header - remdestBin = msg[:self.samDestPubBinLen] - log(4, "remdestBin=%s" % repr(remdestBin)) - - sig = msg[self.samDestPubBinLen:self.samDestPubBinLen+self.samSigLen] - log(4, "sig=%s" % repr(sig)) - - data = msg[self.samDestPubBinLen+self.samSigLen:] - log(4, "data=%s" % repr(data)) - - # now try to verify - hashed = shahash(remdestBin + self.samDestPubBin + data) - log(4, "hashed=%s" % repr(hashed)) - - remdest = I2PDestination(bin=remdestBin) - if remdest.verify(hashed, sig): - # fine - very good, pass it on - log(4, "sig from peer is valid") - self.samSend("DATAGRAM", "RECEIVE", data, - DESTINATION=remdest.toBase64(), - ) - else: - log(4, "DATAGRAM sig from peer is invalid") - #@-node:on_message - #@+node:threadSocketListener - def threadSocketListener(self, sock, dest): - """ - Listens for incoming socket connections, and - notifies the client accordingly - """ - destb64 = dest.toBase64() - - log(4, "Listening for connections to %s..." % destb64) - - sock.bind() - sock.listen() - - while 1: - log(4, "Awaiting next connection to %s..." % destb64) - newsock = sock.accept() - log(4, "Got connection to %s..." % destb64) - - # need an id, negative - id = - self.server.samAllocId() - - # register it in local and global streams - self.localstreams[id] = self.globalstreams[id] = newsock - - # fire up the receiver thread - thread.start_new_thread(self.threadSocketReceiver, (newsock, id)) - - # who is connected to us? - remdest = newsock.remdest - remdest_b64 = remdest.toBase64() - - # and notify the client - self.samSend("STREAM", "CONNECTED", - DESTINATION=remdest_b64, - ID=id) - - #@-node:threadSocketListener - #@+node:threadSocketReceiver - def threadSocketReceiver(self, sock, id): - """ - One of these gets launched each time a new stream connection - is created. Due to the lack of callback mechanism within the - ministreaming API, we have to actively poll for and send back - received data - """ - while 1: - #avail = sock.available() - #if avail <= 0: - # print "threadSocketReceiver: waiting for data on %s (%s avail)..." % (id, avail) - # time.sleep(5) - # continue - #log(4, "reading a byte") - - try: - buf = sock.recv(1) - except: - logException(4, "Exception reading first byte") - - if buf == '': - log(4, "stream closed") - - # notify a close - self.samSend("STREAM", "CLOSED", - ID=id) - return - - # grab more if there's any available - navail = sock.available() - if navail > 0: - #log(4, "%d more bytes available, reading..." % navail) - rest = sock.recv(navail) - buf += rest - - # send if off - log(4, "got from peer: %s" % repr(buf)) - - self.samSend("STREAM", "RECEIVED", buf, - ID=id, - ) - - - - - #@-node:threadSocketReceiver - #@+node:samParse - def samParse(self, flds): - """ - carves up a SAM command, returns it as a 3-tuple: - - cmd - command string - - subcmd - subcommand string - - dargs - dict of args - """ - cmd = flds[0] - subcmd = flds[1] - args = flds[2:] - - dargs = {} - for arg in args: - try: - name, val = arg.split("=", 1) - except: - logException(3, "failed to process %s in %s" % (repr(arg), repr(flds))) - raise - dargs[name] = val - - # read and add data if any - if dargs.has_key('SIZE'): - size = dargs['SIZE'] = int(dargs['SIZE']) - dargs['DATA'] = self._recvbytes(size) - - #log(4, "\n".join([cmd+" "+subcmd] + [("%s=%s (...)" % (k,v[:40])) for k,v in dargs.items()])) - log(4, "\n".join([cmd+" "+subcmd] + [("%s=%s (...)" % (k,v)) for k,v in dargs.items()])) - - return cmd, subcmd, dargs - - - - #@-node:samParse - #@+node:samSend - def samSend(self, topic, subtopic, data=None, **kw): - """ - Sends a SAM message (reply?) back to client - - Arguments: - - topic - the first word in the reply, eg 'STREAM' - - subtopic - the second word of the reply, eg 'CONNECTED' - - data - a string of raw data to send back (optional) - Keywords: - - extra 'name=value' items to pass back. - - Notes: - 1. SIZE is not required. If sending back data, it will - be sized and a SIZE arg inserted automatically. - 2. a dict of values can be passed to the 'args' keyword, in lieu - of direct keywords. This allows for cases where arg names would - cause python syntax clashes, eg 'tunnels.depthInbound' - """ - items = [topic, subtopic] - - # stick in SIZE if needed - if data is not None: - kw['SIZE'] = str(len(data)) - else: - data = '' # for later - - self.samCreateArgsList(kw, items) - - # and whack it together - buf = " ".join(items) + '\n' + data - - # and ship it - self.sendLock.acquire() - try: - self._sendbytes(buf) - except: - self.sendLock.release() - raise - self.sendLock.release() - - #@-node:samSend - #@+node:samCreateArgsList - def samCreateArgsList(self, kw1, lst): - for k,v in kw1.items(): - if k == 'args': - self.samCreateArgsList(v, lst) - else: - lst.append("=".join([str(k), str(v)])) - #@-node:samCreateArgsList - #@+node:_sendbytes - def _sendbytes(self, raw): - - self.wfile.write(raw) - self.wfile.flush() - #@-node:_sendbytes - #@+node:_recvbytes - def _recvbytes(self, count): - """ - Does a guaranteed read of n bytes - """ - read = self.rfile.read - - chunks = [] - needed = count - while needed > 0: - chunk = read(needed) - chunklen = len(chunk) - needed -= chunklen - chunks.append(chunk) - raw = "".join(chunks) - - # done - return raw - - #@-node:_recvbytes - #@-others -#@nonl -#@-node:class I2PSamClientHandler -#@+node:Exceptions -class NoPrivateKey(Exception): - """Destination object has no private key""" - -class I2PSocketError(Exception): - """Error working with I2PSocket objects""" -#@-node:Exceptions -#@+node:shahash -def shahash(s): - """ - Calculates SHA Hash of a string, as a string, using - I2P hashing facility - """ - h = net.i2p.crypto.SHA256Generator().calculateHash(s) - h = bytearray2str(h.getData()) - return h -#@-node:shahash -#@+node:base64enc -def base64enc(s): - return net.i2p.data.Base64.encode(s) -#@-node:base64enc -#@+node:base64dec -def base64dec(s): - return bytearray2str(net.i2p.data.Base64.decode(s)) - -#@-node:base64dec -#@+node:str2bytearray -def str2bytearray(s): - """ - Convenience - converts python string to java-friendly byte array - """ - a = [] - for c in s: - n = ord(c) - if n >= 128: - n = n - 256 - a.append(n) - return a - -#@-node:str2bytearray -#@+node:bytearray2str -def bytearray2str(a): - """ - Convenience - converts java-friendly byte array to python string - """ - chars = [] - for n in a: - if n < 0: - n += 256 - chars.append(chr(n)) - return "".join(chars) - -#@-node:bytearray2str -#@+node:byteoutstream2str -def byteoutstream2str(bs): - """ - Convenience - converts java-friendly byteoutputstream to python string - """ - chars = [] - while 1: - c = bs.read() - if c >= 0: - chars.append(chr(c)) - else: - break - return "".join(chars) - -#@-node:byteoutstream2str -#@+node:dict2props -def dict2props(d): - """ - Converts a python dict d into a java.util.Properties object - """ - props = java.util.Properties() - for k,v in d.items(): - props[k] = str(v) - return props - - -#@-node:dict2props -#@+node:takeKey -def takeKey(somedict, keyname, default=None): - """ - Utility function to destructively read a key from a given dict. - Same as the dict's 'takeKey' method, except that the key (if found) - sill be deleted from the dictionary. - """ - if somedict.has_key(keyname): - val = somedict[keyname] - del somedict[keyname] - else: - val = default - return val -#@-node:takeKey -#@+node:log -def log(level, msg, nPrev=0): - - # ignore messages that are too trivial for chosen verbosity - if level > verbosity: - return - - loglock.acquire() - try: - # rip the stack - caller = traceback.extract_stack()[-(2+nPrev)] - path, line, func = caller[:3] - path = os.path.split(path)[1] - full = "%s:%s:%s():\n* %s" % ( - path, - line, - func, - msg.replace("\n", "\n + ")) - now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) - msg = "%s %s\n" % (now, full) - - if logfile == sys.stdout: - print msg - else: - file(logfile, "a").write(msg+"\n") - except: - s = StringIO.StringIO() - traceback.print_exc(file=s) - print s.getvalue() - print "Logger crashed" - loglock.release() -#@nonl -#@-node:log -#@+node:logException -def logException(level, msg=''): - s = StringIO.StringIO() - traceback.print_exc(file=s) - log(level, "%s\n%s" % (s.getvalue(), msg), 1) -#@-node:logException -#@+node:testdests -def testdests(): - """ - Demo function which tests out dest generation and import/export - """ - print - print "********************************************" - print "Testing I2P destination create/export/import" - print "********************************************" - print - - print "Generating a destination" - d1 = I2PDestination() - - print "Exporting and importing dest1 in several forms" - - print "public binary string..." - d1_bin = d1.toBin() - d2_bin = I2PDestination(bin=d1_bin) - - print "public binary file..." - d1.toBinFile("temp-d1-bin") - d2_binfile = I2PDestination(binfile="temp-d1-bin") - - print "private binary string..." - d1_binprivate = d1.toBinPrivate() - d2_binprivate = I2PDestination(binprivate=d1_binprivate) - - print "private binary file..." - d1.toBinFilePrivate("temp-d1-bin-private") - d2_binfileprivate = I2PDestination(binfileprivate="temp-d1-bin-private") - - print "public base64 string..." - d1_b64 = d1.toBase64() - d2_b64 = I2PDestination(base64=d1_b64) - - print "public base64 file..." - d1.toBase64File("temp-d1-b64") - d2_b64file = I2PDestination(base64file="temp-d1-b64") - - print "private base64 string..." - d1_base64private = d1.toBase64Private() - d2_b64private = I2PDestination(base64private=d1_base64private) - - print "private base64 file..." - d1.toBase64FilePrivate("temp-d1-b64-private") - d2_b64fileprivate = I2PDestination(base64fileprivate="temp-d1-b64-private") - - print "All destination creation/import/export tests passed!" - - -#@-node:testdests -#@+node:testsigs -def testsigs(): - global d1, d1pub, d1sig, d1res - - print - print "********************************************" - print "Testing I2P dest-based signatures" - print "********************************************" - print - - print "Creating dest..." - d1 = I2PDestination() - - s_good = "original stuff that we're signing" - s_bad = "non-original stuff we're trying to forge" - - print "Signing some shit against d1..." - d1sig = d1.sign(s_good) - - print "Creating public dest d1pub" - d1pub = I2PDestination(bin=d1.toBin()) - - print "Verifying original data with d1pub" - res = d1pub.verify(s_good, d1sig) - print "Result: %s (should be 1)" % repr(res) - - print "Trying to verify on a different string" - res1 = d1pub.verify(s_bad, d1sig) - print "Result: %s (should be 0)" % repr(res1) - - if res and not res1: - print "signing/verifying test passed" - else: - print "SIGNING/VERIFYING TEST FAILED" - -#@-node:testsigs -#@+node:testsession -def testsession(): - - global c, d1, d2, s1, s2 - - print - print "********************************************" - print "Testing I2P dest->dest messaging" - print "********************************************" - print - - print "Creating I2P client..." - c = I2PClient() - - print "Creating destination d1..." - d1 = c.createDestination() - - print "Creating destination d2..." - d2 = c.createDestination() - - print "Creating destination d3..." - d3 = c.createDestination() - - print "Creating session s1 on dest d1..." - s1 = c.createSession(d1, host='localhost', port=7654) - - print "Creating session s2 on dest d2..." - s2 = c.createSession(d2) - - print "Connecting session s1..." - s1.connect() - - print "Connecting session s2..." - s2.connect() - - print "Sending message from s1 to d2..." - s1.sendMessage(d2, "Hi there, s2!!") - - print "Retrieving message from s2..." - print "got: %s" % repr(s2.getMessage()) - - print "Sending second message from s1 to d2..." - s1.sendMessage(d2, "Hi there again, s2!!") - - print "Retrieving message from s2..." - print "got: %s" % repr(s2.getMessage()) - - print "Sending message from s1 to d3 (should take ages then fail)..." - res = s1.sendMessage(d3, "This is futile!!") - print "result of that send was %s (should have been 0)" % res - - print "Destroying session s1..." - s1.destroySession() - - print "Destroying session s2..." - s2.destroySession() - - print "session tests passed!" -#@-node:testsession -#@+node:testsocket -def testsocket(bidirectional=0): - - global d1, d2, s1, s2 - - print - print "********************************************" - print "Testing I2P streaming interface" - print "********************************************" - print - - print "Creating destinations..." - dServer = I2PDestination() - dClient = I2PDestination() - - print "Creating sockets..." - sServer = I2PSocket(dServer) - sClient = I2PSocket(dClient) - - # server thread which simply reads a line at a time, then echoes - # that line back to the client - def servThread(s): - print "server: binding socket" - s.bind() - print "server: setting socket to listen" - s.listen() - print "server: awaiting connection" - sock = s.accept() - print "server: got connection" - - sock.send("Hello, echoing...\n") - buf = '' - while 1: - c = sock.recv(1) - if c == '': - sock.close() - print "server: socket closed" - break - - buf += c - if c == '\n': - sock.send("SERVER: "+buf) - buf = '' - - # client thread which reads lines and prints them to stdout - def clientThread(s): - buf = '' - while 1: - c = s.recv(1) - if c == '': - s.close() - print "client: socket closed" - break - buf += c - if c == '\n': - print "client: got %s" % repr(buf) - buf = '' - - print "launching server thread..." - thread.start_new_thread(servThread, (sServer,)) - - if bidirectional: - # dummy thread which accepts connections TO client socket - def threadDummy(s): - print "dummy: listening" - s.listen() - print "dummy: accepting" - - sock = s.accept() - print "dummy: got connection" - - print "test - launching dummy client accept thread" - thread.start_new_thread(threadDummy, (sClient,)) - - print "client: trying to connect" - sClient.connect(dServer) - - print "client: connected, launching rx thread" - thread.start_new_thread(clientThread, (sClient,)) - - while 1: - line = raw_input("Enter something (q to quit)> ") - if line == 'q': - print "closing client socket" - sClient.close() - break - sClient.send(line+"\n") - - print "I2PSocket test apparently succeeded" - - -#@-node:testsocket -#@+node:usage -def usage(detailed=0): - - print "Usage: %s <options> [<command>]" % sys.argv[0] - if not detailed: - print "Run with '-h' to get detailed help" - sys.exit(0) - - print "I2PSAM is a bridge that allows I2P client programs to access the" - print "I2P network by talking over a plaintext socket connection." - print "References:" - print " - http://www.freenet.org.nz/i2p - source, doco, downloadables" - print " - http://drupal.i2p.net/node/view/144 - I2P SAM specification" - print - print "Options:" - print " -h, -?, --help - display this help" - print " -v, --version - print program version" - print " -V, --verbosity=n - set verbosity to n, default 2, 1==quiet, 4==noisy" - print " -H, --listenhost=host - specify host to listen on for client connections" - print " -P, --listenport=port - port to listen on for client connections" - print " --i2cphost=host - hostname of I2P router's I2CP interface" - print " --i2cpport=port - port of I2P router's I2CP interface" - print - print "Commands:" - print " (run with no commands to launch SAM server)" - print " samserver - runs as a SAM server" - print " test - run a suite of self-tests" - print " testsocket - run only the socket test" - print " testbidirsocket - run socket test in bidirectional mode" - print - - sys.exit(0) -#@-node:usage -#@+node:main -def main(): - - argv = sys.argv - argc = len(argv) - - # ------------------------------------------------- - # do the getopt command line parsing - - try: - opts, args = getopt.getopt(sys.argv[1:], - "h?vV:H:P:", - ['help', 'version', 'verbosity=', - 'listenhost=', 'listenport=', - 'i2cphost=', 'i2cpport=', - ]) - except: - traceback.print_exc(file=sys.stdout) - usage("You entered an invalid option") - - #print "args=%s" % args - - serveropts = {} - for opt, val in opts: - if opt in ['-h', '-?', '--help']: - usage(1) - elif opt in ['-v', '--version']: - print "I2P SAM version %s" % version - sys.exit(0) - elif opt in ['-V', '--verbosity']: - serveropts['verbosity'] = int(val) - elif opt in ['-H', '--listenhost']: - serveropts['host'] = val - elif opt in ['-P', '--listenport']: - serveropts['port'] = int(val) - elif opt in ['--i2cphost']: - serveropts['i2cphost'] = val - elif opt in ['--i2cpport']: - serveropts['i2cpport'] = int(val) - else: - usage(0) - - # -------------------------------------------------- - # now run in required mode, default is 'samserver' - - if len(args) == 0: - cmd = 'samserver' - else: - cmd = args[0] - - if cmd == 'samserver': - - log(2, "Running I2P SAM Server...") - server = I2PSamServer(**serveropts) - server.run() - - elif cmd == 'test': - - print "RUNNING full I2PSAM Jython TEST SUITE" - testsigs() - testdests() - testsession() - testsocket() - - elif cmd == 'testsocket': - - print "RUNNING SOCKET TEST" - testsocket(0) - - elif cmd == 'testbidirsocket': - print "RUNNING BIDIRECTIONAL SOCKET TEST" - testsocket(1) - - else: - # spit at unrecognised option - usage(0) - -#@-node:main -#@+node:MAINLINE -if __name__ == '__main__': - main() - -#@-node:MAINLINE -#@-others - - -#@-node:@file jython/src/i2psam.py -#@-leo diff --git a/apps/sam/python/src/i2psamclient.py b/apps/sam/python/src/i2psamclient.py deleted file mode 100644 index 2070bfd9ab..0000000000 --- a/apps/sam/python/src/i2psamclient.py +++ /dev/null @@ -1,1334 +0,0 @@ -#!/usr/bin/env python -#@+leo-ver=4 -#@+node:@file python/src/i2psamclient.py -#@@first -""" -Implements a client API for I2CP messaging via SAM - -Very simple I2P messaging interface, which should prove easy -to reimplement in your language of choice - -This module can be used from cpython or jython - -Run this module without arguments to see a demo in action -(requires SAM server to be already running) -""" -#@+others -#@+node:imports -import sys, os, socket, thread, threading, Queue, traceback, StringIO, time - -from pdb import set_trace - -#@-node:imports -#@+node:globals -# ----------------------------------------- -# server access settings - -i2psamhost = '127.0.0.1' -i2psamport = 7656 - -# ------------------------------------------ -# logging settings - -# 1=v.quiet, 2=normal, 3=verbose, 4=debug, 5=painful -verbosity = 5 - -# change to a filename to log there instead -logfile = sys.stdout - -# when set to 1, and when logfile != sys.stdout, log msgs are written -# both to logfile and console stdout -log2console = 1 - -# don't touch this! -loglock = threading.Lock() - -#@-node:globals -#@+node:exceptions -class I2PServerFail(Exception): - """ - A failure in connecting to the I2CP server - """ - -class I2PCommandFail(Exception): - """ - A failure in an I2CP command - """ - pass - -class I2PStreamClosed(Exception): - """ - Stream is not open - """ -#@-node:exceptions -#@+node:class I2PSamClient -class I2PSamClient: - """ - Implements a reference client for accessing I2CP via i2psam - - Connects to i2psam's I2PSamServer, sends commands - and receives results - - The primitives should be reasonably self-explanatory - - Usage summary: - 1. create one or more I2PSamClient instances per process (1 should be fine) - 2. invoke the L{genkeys} method to create destination keypairs - 3. create sessions objects via the L{createSession} method - 4. use these session objects to send and receive data - 5. destroy the session objects when you're done - - Refer to the function L{demo} for a simple example - """ - #@ @+others - #@+node:attributes - # server host/port settings exist here in case you might - # have a reason for overriding in a subclass - - host = i2psamhost - port = i2psamport - - i2cpHost = None - i2cpPort = None - - #@-node:attributes - #@+node:__init__ - def __init__(self, **kw): - """ - Creates a client connection to i2psam listener - - Keywords: - - host - host to connect to (default 127.0.0.1) - - port - port to connect to (default 7656) - """ - # get optional host/port - log(4, "entered") - - self.host = kw.get('host', self.host) - self.port = int(kw.get('port', self.port)) - - self.cmdLock = threading.Lock() - - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.lockHello = threading.Lock() - self.sendLock = threading.Lock() - self.qNewDests = Queue.Queue() - self.qSession = Queue.Queue() - self.qDatagrams = Queue.Queue() - self.qRawMessages = Queue.Queue() - - self.namingReplies = {} - self.namingCache = {} - - self.streams = {} # currently open streams, keyed by id - self.streamConnectReplies = {} # holds queues awaiting connect resp, keyed by id - self.qNewStreams = Queue.Queue() # incoming connections - - self.samNextIdLock = threading.Lock() - self.samNextId = 1 - - self.isRunning = 1 - - - log(4, "trying connection to SAM server...") - try: - self.sock.connect((self.host, self.port)) - except: - raise I2PServerFail( - "Connection to i2psam server failed\n" - "(are you sure your I2P router is running, and\n" - "listening for I2CP connections on %s:%s?)" % (self.host, self.port) - ) - - # fire up receiver thread - thread.start_new_thread(self.threadRx, ()) - - # ping the server - try: - log(4, "trying to ping SAM server...") - self.samHello() - except: - logException(4, "Exception on handshaking") - raise I2PServerFail("Failed to handshake with i2psam server") - - # connected fine - log(2, "I2CP Client successfully connected") - #@-node:__init__ - #@+node:createSession - def createSession(self, privdest): - """ - DEPRECATED - use sam* methods instead! - - Creates a session using private destkey - """ - #3. createsession: - # - client->server: - # - createsession <base64private>\n - # - server->client: - # - ok\n OR - # - error[ <reason>]\n - - self.cmdLock.acquire() - try: - self._sendline("createsession %s" % privdest) - respitems = self._recvline().split(" ", 1) - if respitems[0] == 'ok': - res = None - else: - res = respitems[1] - except: - logException(2, "createsession fail") - self.cmdLock.release() - raise - - self.cmdLock.release() - - if res: - raise I2PCommandFail("createsession fail: "+res) - - return I2PRemoteSession(self, privdest) - - #@-node:createSession - #@+node:destroySession - def destroySession(self, privdest): - """ - DEPRECATED - use sam* methods instead! - - Destrlys a session using private destkey - """ - #4. destroysession: - # - client->server: - # - destroysession <base64private>\n - # - server->client: - # - ok\n OR - # - error[ <reason>]\n - - self.cmdLock.acquire() - try: - self._sendline("destroysession %s" % privdest) - respitems = self._recvline().split(" ", 1) - if respitems[0] == 'ok': - res = None - else: - res = respitems[1] - except: - logException(2, "destroysession fail") - self.cmdLock.release() - raise - - self.cmdLock.release() - - if res: - raise I2PCommandFail("destroysession fail: " + res) - - return res - - #@-node:destroySession - #@+node:send - def send(self, privdest, peerdest, msg): - """ - DEPRECATED - use sam* methods instead! - - Sends a block of data from local dest to remote dest - """ - #5. send: - # - client->server: - # - send <size> <localbase64private> <remotebase64dest>\ndata - # - server->client: - # - ok\n OR - # - error[ <reason>]\n - - self.cmdLock.acquire() - try: - self._sendline("send %s %s %s" % (len(msg), privdest, peerdest)) - self._sendbytes(msg) - line = self._recvline() - #print "** %s" % line - respitems = line.split(" ", 1) - if respitems[0] == 'ok': - res = None - else: - res = " ".join(respitems[1:]) - except: - logException(2, "send fail") - self.cmdLock.release() - raise - - self.cmdLock.release() - - if res: - raise I2PCommandFail("send fail: " + res) - - return res - - #@-node:send - #@+node:receive - def receive(self, privdest): - """ - DEPRECATED - use sam* methods instead! - - receives a block of data, returning string, or None if no data available - """ - #6. receive: - # - client->server: - # - receive <localbase64private>\n - # - server->client: - # - ok <size>\ndata OR - # - error[ <reason>]\n - - self.cmdLock.acquire() - try: - self._sendline("receive %s" % privdest) - respitems = self._recvline().split(" ", 1) - if respitems[0] == 'ok': - res = None - size = int(respitems[1]) - msg = self._recvbytes(size) - res = None - else: - res = respitems[1] - except: - logException(2, "receive fail") - self.cmdLock.release() - raise - - self.cmdLock.release() - - if res: - raise I2PCommandFail("destroysession fail: " + res) - - return msg - #@-node:receive - #@+node:samHello - def samHello(self): - """ - Sends a quick HELLO PING to SAM server and awaits response - Arguments: - - none - - Keywords: - - none - - Returns: - - nothing (None) if ping sent and pong received, or raises an exception if - failed - """ - self.lockHello.acquire() - self.samSend("HELLO", "PING") - self.lockHello.acquire() - self.lockHello.release() - #@-node:samHello - #@+node:samSessionCreate - def samSessionCreate(self, style, dest, **kw): - """ - Creates a SAM session - - Arguments: - - style - one of 'STREAM', 'DATAGRAM' or 'RAW' - - dest - base64 private destination - - Keywords: - - direction - only used for STREAM sessions, can be RECEIVE, - CREATE or BOTH (default BOTH) - - i2cphost - hostname for the SAM bridge to contact i2p router on - - i2cpport - port for the SAM bridge to contact i2p router on - - Returns: - - 'OK' if session was created successfully, or a tuple - (keyword, message) if not - """ - kw1 = dict(kw) - kw1['STYLE'] = self.samStyle = style - kw1['DESTINATION'] = dest - if style == 'STREAM': - direction = kw.get('direction', 'BOTH') - kw1['DIRECTION'] = direction - if direction == 'BOTH': - self.canAccept = 1 - self.canConnect = 1 - elif direction == 'RECEIVE': - self.canAccept = 1 - self.canConnect = 0 - elif direction == 'CREATE': - self.canAccept = 0 - self.canConnect = 1 - else: - raise I2PCommandFail("direction keyword must be one of RECEIVE, CREATE or BOTH") - - # stick in i2cp host/port if specified - if kw.has_key('i2cphost'): - kw1['I2CP.HOST'] = kw['i2cphost'] - if kw.has_key('i2cpport'): - kw1['I2CP.PORT'] = kw['i2cpport'] - - self.samSend("SESSION", "CREATE", - **kw1) - subtopic, args = self.qSession.get() - - if args['RESULT'] == 'OK': - return 'OK' - else: - return (args['RESULT'], args['MESSAGE']) - #@-node:samSessionCreate - #@+node:samDestGenerate - def samDestGenerate(self): - """ - Creates a whole new dest and returns an tuple pub, priv as - base64 public and private destination keys - """ - self.samSend("DEST", "GENERATE") - pub, priv = self.qNewDests.get() - return pub, priv - #@-node:samDestGenerate - #@+node:samRawSend - def samRawSend(self, peerdest, msg): - """ - Sends a raw anon message to another peer - - peerdest is the public base64 destination key of the peer - """ - self.samSend("RAW", "SEND", msg, - DESTINATION=peerdest, - ) - #@-node:samRawSend - #@+node:samRawCheck - def samRawCheck(self): - """ - Returns 1 if there are received raw messages available, 0 if not - """ - return not self.qRawMessages.empty() - #@-node:samRawCheck - #@+node:samRawReceive - def samRawReceive(self, blocking=1): - """ - Returns the next raw message available, - blocking if none is available and the blocking arg is set to 0 - - If blocking is 0, and no messages are available, returns None. - - Remember that you can check for availability with - the .samRawCheck() method - """ - if not blocking: - if self.qRawMessages.empty(): - return None - return self.qRawMessages.get() - - #@nonl - #@-node:samRawReceive - #@+node:samDatagramSend - def samDatagramSend(self, peerdest, msg): - """ - Sends a repliable datagram message to another peer - - peerdest is the public base64 destination key of the peer - """ - self.samSend("DATAGRAM", "SEND", msg, - DESTINATION=peerdest, - ) - #@-node:samDatagramSend - #@+node:samDatagramCheck - def samDatagramCheck(self): - """ - Returns 1 if there are datagram messages received messages available, 0 if not - """ - return not self.qDatagrams.empty() - #@-node:samDatagramCheck - #@+node:samDatagramReceive - def samDatagramReceive(self, blocking=1): - """ - Returns the next datagram message available, - blocking if none is available. - - If blocking is set to 0, and no messages are available, - returns None. - - Remember that you can check for availability with - the .samRawCheck() method - - Returns 2-tuple: dest, msg - where dest is the base64 destination of the peer from - whom the message was received - """ - if not blocking: - if self.qDatagrams.empty(): - return None - return self.qDatagrams.get() - #@-node:samDatagramReceive - #@+node:samStreamConnect - def samStreamConnect(self, dest): - """ - Makes a STREAM connection to a remote dest - - STREAM STATUS - RESULT=$result - ID=$id - [MESSAGE=...] - """ - # need an ID - id = self.samAllocId() - - # create queue for connect reply - q = self.streamConnectReplies[id] = Queue.Queue() - - # send req - self.samSend("STREAM", "CONNECT", - ID=id, - DESTINATION=dest, - ) - - # await reply - comes back as a dict - resp = q.get() - - # ditch queue - del self.streamConnectReplies[id] - del q - - # check out response - result = resp['RESULT'] - if result == 'OK': - conn = I2PSAMStream(self, id, dest) - self.streams[id] = conn - return conn - else: - msg = resp.get('MESSAGE', '') - raise I2PCommandFail(result, msg, "STREAM CONNECT") - - #@-node:samStreamConnect - #@+node:samStreamAccept - def samStreamAccept(self): - """ - Waits for an incoming connection, returning a wrapped conn obj - """ - log(4, "waiting for connection") - conn = self.qNewStreams.get() - log(4, "got connection") - return conn - #@-node:samStreamAccept - #@+node:samStreamSend - def samStreamSend(self, conn, data): - """ - DO NOT CALL THIS DIRECTLY - - Invoked by an I2PSAMStream object to transfer data - Use the object's .send() method instead. - - conn is the I2PSAMStream - - STREAM SEND - ID=$id - SIZE=$numBytes\n[$numBytes of data] - """ - # dispatch - self.samSend("STREAM", "SEND", data, ID=conn.id) - - # useless, but mimics socket paradigm - return len(data) - - #@-node:samStreamSend - #@+node:samStreamClose - def samStreamClose(self, conn): - """ - DO NOT CALL DIRECTLY - - Invoked by I2PSAMStream to close stream - Use the object's .send() method instead. - - STREAM CLOSE - ID=$id - """ - self.samSend("STREAM", "CLOSE", ID=conn.id) - del self.streams[conn.id] - - #@-node:samStreamClose - #@+node:samNamingLookup - def samNamingLookup(self, host): - """ - Looks up a host in hosts.txt - """ - # try the cache first - if self.namingCache.has_key(host): - log(4, "found host %s in cache" % host) - return self.namingCache[host] - - # make a queue for reply - q = self.namingReplies[host] = Queue.Queue() - - # send off req - self.samSend("NAMING", "LOOKUP", - NAME=host, - ) - - # get resp - resp = q.get() - - result = resp.get('RESULT', 'none') - if result == 'OK': - log(4, "adding host %s to cache" % host) - val = resp['VALUE'] - self.namingCache[host] = val - return val - else: - raise I2PCommandFail("Error looking up '%s': %s %s" % ( - host, result, resp.get('MESSAGE', ''))) - - #@-node:samNamingLookup - #@+node:samParse - def samParse(self, flds): - """ - carves up a SAM command, returns it as a 3-tuple: - - cmd - command string - - subcmd - subcommand string - - dargs - dict of args - """ - cmd = flds[0] - subcmd = flds[1] - args = flds[2:] - - dargs = {} - for arg in args: - try: - name, val = arg.split("=", 1) - except: - logException(3, "failed to process %s in %s" % (repr(arg), repr(flds))) - raise - dargs[name] = val - - # read and add data if any - if dargs.has_key('SIZE'): - size = dargs['SIZE'] = int(dargs['SIZE']) - dargs['DATA'] = self._recvbytes(size) - - #log(4, "\n".join([cmd+" "+subcmd] + [("%s=%s (...)" % (k,v[:40])) for k,v in dargs.items()])) - log(4, "\n".join([cmd+" "+subcmd] + [("%s=%s (...)" % (k,v)) for k,v in dargs.items()])) - - return cmd, subcmd, dargs - - - - - - #@-node:samParse - #@+node:samSend - def samSend(self, topic, subtopic, data=None, **kw): - """ - Sends a SAM message (reply?) back to client - - Arguments: - - topic - the first word in the reply, eg 'STREAM' - - subtopic - the second word of the reply, eg 'CONNECTED' - - data - a string of raw data to send back (optional) - Keywords: - - extra 'name=value' items to pass back. - - Notes: - 1. SIZE is not required. If sending back data, it will - be sized and a SIZE arg inserted automatically. - 2. a dict of values can be passed to the 'args' keyword, in lieu - of direct keywords. This allows for cases where arg names would - cause python syntax clashes, eg 'tunnels.depthInbound' - """ - items = [topic, subtopic] - - # stick in SIZE if needed - if data is not None: - kw['SIZE'] = str(len(data)) - else: - data = '' # for later - - self.samCreateArgsList(kw, items) - - # and whack it together - buf = " ".join(items) + '\n' + data - - # and ship it - self.sendLock.acquire() - try: - self._sendbytes(buf) - except: - self.sendLock.release() - raise - self.sendLock.release() - - #@-node:samSend - #@+node:samCreateArgsList - def samCreateArgsList(self, kw1, lst): - for k,v in kw1.items(): - if k == 'args': - self.samCreateArgsList(v, lst) - else: - lst.append("=".join([str(k), str(v)])) - #@-node:samCreateArgsList - #@+node:threadRx - def threadRx(self): - """ - Handles all incoming stuff from SAM, storing in - local queues as appropriate - """ - while self.isRunning: - try: - log(4, "Awaiting next message from server") - line = self._recvline() - if line == '': - log(3, "I2P server socket closed") - return - flds = line.split(" ") - topic, subtopic, args = self.samParse(flds) - log(4, "Got %s %s %s" % (topic, subtopic, args)) - handleMsg = getattr(self, "on_"+topic, None) - if handleMsg: - handleMsg(topic, subtopic, args) - else: - log(2, "No handler for '%s' message" % topic) - except: - #logException(3, "Exception handling %s %s\n%s" % (topic, subtopic, args)) - logException(3, "Exception handling %s" % repr(line)) - #@-node:threadRx - #@+node:on_HELLO - def on_HELLO(self, topic, subtopic, args): - """ - Handles HELLO PONG messages from server - """ - # just wake up the caller - log(4, "got HELLO") - self.lockHello.release() - #@-node:on_HELLO - #@+node:on_SESSION - def on_SESSION(self, topic, subtopic, args): - """ - Handles SESSION messages from server - """ - # just stick whatever on the queue and wake up the caller - res = subtopic, args - self.qSession.put(res) - #@-node:on_SESSION - #@+node:on_STREAM - def on_STREAM(self, topic, subtopic, args): - """ - Handles STREAM messages from server - - STREAM STATUS - RESULT=$result - ID=$id - [MESSAGE=...] - - STREAM CONNECTED - DESTINATION=$base64key - ID=$id - - STREAM RECEIVED - ID=$id - SIZE=$numBytes\n[$numBytes of data] - - STREAM CLOSED - RESULT=$result - ID=$id - [MESSAGE=...] - """ - log(4, "got %s %s %s" % (topic, subtopic, args)) - - # which stream? - id = int(args['ID']) - - # result of prior connection attempt - if subtopic == 'STATUS': - # stick it on the queue that the caller is waiting on and let the - # caller interpret the result - self.streamConnectReplies[id].put(args) - return - - # notice of incoming connection - if subtopic == 'CONNECTED': - - # grab details - dest = args['DESTINATION'] - - # wrap it in a stream obj - conn = I2PSAMStream(self, id, dest) - self.streams[id] = conn - - # and put it there for anyone calling samStreamAccept() - self.qNewStreams.put(conn) - - # done - return - - # notice of received data - elif subtopic == 'RECEIVED': - # grab details - data = args['DATA'] - - # lookup the connection - conn = self.streams.get(id, None) - if not conn: - # conn not known, just ditch - log(2, "got data, but don't recall any conn with id %s" % id) - return - - # and post the received data - conn._notifyIncomingData(data) - - log(4, "wrote data to conn's inbound queue") - - # done - return - - elif subtopic == 'CLOSED': - # lookup the connection - conn = self.streams.get(id, None) - if not conn: - # conn not known, just ditch - return - - # mark conn as closed and forget it - conn._notifyIncomingData("") # special signal to close - conn.isOpen = 0 - del self.streams[id] - - # done - return - - - - #@-node:on_STREAM - #@+node:on_DATAGRAM - def on_DATAGRAM(self, topic, subtopic, args): - """ - Handles DATAGRAM messages from server - """ - remdest = args['DESTINATION'] - data = args['DATA'] - - self.qDatagrams.put((remdest, data)) - #@-node:on_DATAGRAM - #@+node:on_RAW - def on_RAW(self, topic, subtopic, args): - """ - Handles RAW messages from server - """ - data = args['DATA'] - - log(3, "Got anonymous datagram %s" % repr(data)) - self.qRawMessages.put(data) - #@-node:on_RAW - #@+node:on_NAMING - def on_NAMING(self, topic, subtopic, args): - """ - Handles NAMING messages from server - """ - # just find out hostname, and stick it on resp q - host = args['NAME'] - self.namingReplies[host].put(args) - #@-node:on_NAMING - #@+node:on_DEST - def on_DEST(self, topic, subtopic, args): - """ - Handles DEST messages from server - """ - pubkey = args['PUB'] - privkey = args['PRIV'] - res = pubkey, privkey - self.qNewDests.put(res) - #@-node:on_DEST - #@+node:samAllocId - def samAllocId(self): - """ - Allocates a new unique id as required by SAM protocol - """ - self.samNextIdLock.acquire() - id = self.samNextId - self.samNextId += 1 - self.samNextIdLock.release() - return id - #@-node:samAllocId - #@+node:_recvline - def _recvline(self): - """ - Guaranteed read of a full line - """ - chars = [] - while 1: - c = self.sock.recv(1) - if c in ['', '\n']: - break - chars.append(c) - return "".join(chars) - #@-node:_recvline - #@+node:_recvbytes - def _recvbytes(self, num): - """ - Guaranteed read of num bytes - """ - if num <= 0: - return "" - - reqd = num - chunks = [] - while reqd > 0: - chunk = self.sock.recv(reqd) - if not chunk: - raise I2PServerFail("Buffer read fail") - chunks.append(chunk) - reqd -= len(chunk) - return "".join(chunks) - #@-node:_recvbytes - #@+node:_sendbytes - def _sendbytes(self, buf): - """ - Guaranteed complete send of a buffer - """ - reqd = len(buf) - while reqd > 0: - nsent = self.sock.send(buf) - if nsent == 0: - raise I2PServerFail("Send to server failed") - buf = buf[nsent:] - reqd -= nsent - #@-node:_sendbytes - #@+node:_sendline - def _sendline(self, line): - """ - just tacks on a newline and sends - """ - self._sendbytes(line+"\n") - #@-node:_sendline - #@-others -#@-node:class I2PSamClient -#@+node:class I2PSAMStream -class I2PSAMStream: - """ - Wrapper for a stream object - """ - #@ @+others - #@+node:__init__ - def __init__(self, client, id, dest): - """ - """ - self.client = client - self.id = id - self.dest = dest - - self.qIncomingData = Queue.Queue() - - self.inbuf = '' - self.isOpen = 1 - #@-node:__init__ - #@+node:send - def send(self, data): - """ - Sends data to a stream connection - """ - # barf if stream not open - if not self.isOpen: - raise I2PStreamClosed - - # can send - return self.client.samStreamSend(self, data) - #@-node:send - #@+node:recv - def recv(self, size): - """ - Retrieves n bytes from peer - """ - chunks = [] - - while self.isOpen and size > 0: - # try internal buffer first - if self.inbuf: - chunk = self.inbuf[:size] - chunklen = len(chunk) - self.inbuf = self.inbuf[chunklen:] - chunks.append(chunk) - size -= chunklen - else: - # replenish input buffer - log(4, "I2PSAMStream.recv: replenishing input buffer") - buf = self.qIncomingData.get() - if buf == '': - # connection closed by peer - self.isOpen = 0 - break - else: - # got more data - log(4, "I2PSAMStream: queue returned %s" % repr(buf)) - self.inbuf += buf - - # return whatever we've got, hopefully all - return "".join(chunks) - - - #@-node:recv - #@+node:readline - def readline(self): - """ - Read a line of text from stream, return the line without trailing newline - - This method really shouldn't exist in a class that's trying to look a bit - like a socket object, but what the hell! - """ - chars = [] - while 1: - char = self.recv(1) - if char in ['', '\n']: - break - chars.append(char) - return "".join(chars) - #@-node:readline - #@+node:close - def close(self): - """ - close this stream connection - """ - log(4, "closing stream") - self.client.samStreamClose(self) - log(4, "stream closed") - self.isOpen = 0 - - # and just to make sure... - self.qIncomingData.put("") # busts out of recv() loops - - #@-node:close - #@+node:__del__ - def __del__(self): - """ - Dropping last ref to this object closes stream - """ - self.close() - #@-node:__del__ - #@+node:_notifyIncomingData - def _notifyIncomingData(self, data): - """ - Called by client receiver to notify incoming data - """ - log(4, "got %s" % repr(data)) - self.qIncomingData.put(data) - #@-node:_notifyIncomingData - #@-others -#@-node:class I2PSAMStream -#@+node:class I2PRemoteSession -class I2PRemoteSession: - """ - DEPRECATED - - Wrapper for I2CP connections - - Do not instantiate this directly - it gets created by - I2PSamClient.createSession() - """ - #@ @+others - #@+node:__init__ - def __init__(self, client, dest): - """ - Do not instantiate this directly - """ - self.client = client - self.dest = dest - #@-node:__init__ - #@+node:send - def send(self, peerdest, msg): - """ - """ - return self.client.send(self.dest, peerdest, msg) - #@-node:send - #@+node:recv - def receive(self): - - return self.client.receive(self.dest) - #@-node:recv - #@+node:destroy - def destroy(self): - - return self.client.destroySession(self.dest) - - #@-node:destroy - #@-others -#@-node:class I2PRemoteSession -#@+node:log -def log(level, msg, nPrev=0): - - # ignore messages that are too trivial for chosen verbosity - if level > verbosity: - return - - loglock.acquire() - try: - # rip the stack - caller = traceback.extract_stack()[-(2+nPrev)] - path, line, func = caller[:3] - path = os.path.split(path)[1] - full = "%s:%s:%s():\n* %s" % ( - path, - line, - func, - msg.replace("\n", "\n + ")) - now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) - msg = "%s %s\n" % (now, full) - - if logfile == sys.stdout: - print msg - else: - file(logfile, "a").write(msg+"\n") - except: - s = StringIO.StringIO() - traceback.print_exc(file=s) - print s.getvalue() - print "Logger crashed" - loglock.release() -#@nonl -#@-node:log -#@+node:logException -def logException(level, msg=''): - s = StringIO.StringIO() - traceback.print_exc(file=s) - log(level, "%s\n%s" % (s.getvalue(), msg), 1) -#@-node:logException -#@+node:demoNAMING -def demoNAMING(): - """ - Demonstrates the NAMING service - """ - print "Starting SAM NAMING demo..." - print - - print "Instantiating client connection..." - c0 = I2PSamClient() - print "Client connection created" - - for host in ['duck.i2p', 'nonexistent.i2p']: - print "Sending query for host '%s'..." % host - try: - res = c0.samNamingLookup(host) - print "query for %s returned:" % host - print repr(res) - except I2PCommandFail, e: - print "got exception: %s" % repr(e.args) - - print - print "---------------------------------" - print "NAMING service tests succeeded" - print "---------------------------------" - print - - -#@-node:demoNAMING -#@+node:demoRAW -def demoRAW(): - """ - Runs a demo of SAM RAW messaging - """ - print "Starting SAM RAW demo..." - print - - print "Instantiating client connections..." - c1 = I2PSamClient() - c2 = I2PSamClient() - - print "Creating dests via SAM" - pub1, priv1 = c1.samDestGenerate() - pub2, priv2 = c2.samDestGenerate() - print "SAM Dests generated ok" - - print "Creating SAM RAW SESSION on connection c1..." - res = c1.samSessionCreate("RAW", priv1) - if res != 'OK': - print "Failed to create session on connection c1: %s" % repr(res) - return - print "Session on connection c1 created successfully" - - print "Creating SAM SESSION on connection c2..." - res = c2.samSessionCreate("RAW", priv2) - if res != 'OK': - print "Failed to create session on connection c2: %s" % repr(res) - return - print "Session on connection c2 created successfully" - - msg = "Hi there!" - print "sending from c1 to c2: %s" % repr(msg) - c1.samRawSend(pub2, msg) - - print "now try to receive from c2 (will block)..." - msg1 = c2.samRawReceive() - print "Connection c2 got %s" % repr(msg1) - - print - print "---------------------------------" - print "RAW data transfer tests succeeded" - print "---------------------------------" - print - -#@-node:demoRAW -#@+node:demoDATAGRAM -def demoDATAGRAM(): - """ - Runs a demo of SAM DATAGRAM messaging - """ - print "Starting SAM DATAGRAM demo..." - print - - print "Instantiating 2 more client connections..." - c3 = I2PSamClient() - c4 = I2PSamClient() - - print "Creating more dests via SAM" - pub3, priv3 = c3.samDestGenerate() - pub4, priv4 = c4.samDestGenerate() - - print "Creating SAM DATAGRAM SESSION on connection c3..." - res = c3.samSessionCreate("DATAGRAM", priv3) - if res != 'OK': - print "Failed to create DATAGRAM session on connection c3: %s" % repr(res) - return - print "DATAGRAM Session on connection c3 created successfully" - - print "Creating SAM DATAGRAM SESSION on connection c4..." - res = c4.samSessionCreate("DATAGRAM", priv4) - if res != 'OK': - print "Failed to create DATAGRAM session on connection c4: %s" % repr(res) - return - print "Session on connection c4 created successfully" - - msg = "Hi there, this is a datagram!" - print "sending from c3 to c4: %s" % repr(msg) - c3.samDatagramSend(pub4, msg) - - print "now try to receive from c4 (will block)..." - remdest, msg1 = c4.samDatagramReceive() - print "Connection c4 got %s from %s..." % (repr(msg1), repr(remdest)) - - - print - print "--------------------------------------" - print "DATAGRAM data transfer tests succeeded" - print "--------------------------------------" - print - -#@-node:demoDATAGRAM -#@+node:demoSTREAM -def demoSTREAM(): - """ - Runs a demo of SAM STREAM messaging - """ - print "Starting SAM STREAM demo..." - print - - print "Instantiating client c6..." - c6 = I2PSamClient() - - print "Creating dest for c6" - pub6, priv6 = c6.samDestGenerate() - - print "Creating SAM STREAM SESSION on connection c6..." - res = c6.samSessionCreate("STREAM", priv6, direction="RECEIVE") - if res != 'OK': - print "Failed to create STREAM session on connection c6: %s" % repr(res) - return - print "STREAM Session on connection c6 created successfully" - - print "Launching acceptor thread..." - thread.start_new_thread(demoSTREAM_thread, (c6,)) - - #print "sleep a while and give the server a chance..." - #time.sleep(10) - - print "----------------------------------------" - - print "Instantiating client c5..." - c5 = I2PSamClient() - - print "Creating dest for c5" - pub5, priv5 = c5.samDestGenerate() - - print "Creating SAM STREAM SESSION on connection c5..." - res = c5.samSessionCreate("STREAM", priv5, direction="CREATE") - if res != 'OK': - print "Failed to create STREAM session on connection c5: %s" % repr(res) - return - print "STREAM Session on connection c5 created successfully" - - print "----------------------------------------" - - print "Making connection from c5 to c6..." - - #set_trace() - - try: - conn_c5 = c5.samStreamConnect(pub6) - except: - print "Stream Connection failed" - return - print "Stream connect succeeded" - - print "Receiving from c5..." - buf = conn_c5.readline() - print "Got %s" % repr(buf) - - #print "Try to accept connection on c6..." - #conn_c6 = c6.sam - - print - print "--------------------------------------" - print "DATAGRAM data transfer tests succeeded" - print "--------------------------------------" - print - - - - - -#@-node:demoSTREAM -#@+node:demoSTREAM_thread -def demoSTREAM_thread(sess): - - while 1: - sock = sess.samStreamAccept() - log(4, "got incoming connection") - - print "**ACCEPTOR SLEEPING 10 secs BEFORE SENDING" - - time.sleep(10) - - sock.send("Hi there, what do you want?\n") - - print "**ACCEPTOR SLEEPING 5 MINS BEFORE CLOSING" - time.sleep(300) - print "**ACCEPTOR CLOSING STREAM" - - sock.close() - -#@-node:demoSTREAM_thread -#@+node:demo -def demo(): - """ - This is a simple and straightforward demo of talking to - the i2psam server socket via the I2PSamClient class. - - Read the source, Luke, it's never been so easy... - """ - print - print "-----------------------------------------" - print "Running i2psamclient demo..." - print "-----------------------------------------" - print - - #demoNAMING() - #demoRAW() - #demoDATAGRAM() - demoSTREAM() - - print - print "-----------------------------------------" - print "Demo Finished" - print "-----------------------------------------" - - return -#@-node:demo -#@+node:MAINLINE -if __name__ == '__main__': - - demo() -#@-node:MAINLINE -#@-others - -#@-node:@file python/src/i2psamclient.py -#@-leo -- GitLab