0.1 initial checkin

This commit is contained in:
zzz
2021-12-18 09:02:03 -05:00
commit 92fd1d1e0e
9 changed files with 534 additions and 0 deletions

13
LICENSE.txt Normal file
View File

@ -0,0 +1,13 @@
Copyright 2021 zzz (zzz@mail.i2p)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

8
README.txt Normal file
View File

@ -0,0 +1,8 @@
Socks Outproxy Plugin
---------------------
This is a very simple SOCKS 5 client relaying to a configured
SOCKS 5 server (localhost:9050 by default).
To change the settings or add a username and password for the SOCKS server,
edit the file ~/.i2p/plugins/socksoutproxy/socks.config and restart the plugin.

75
build.xml Normal file
View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<project basedir="." default="all" name="socksoutproxy">
<property file="override.properties"/>
<target name="all" depends="clean,plugin" />
<target name="jar" >
<ant dir="src" target="build" />
</target>
<target name="plugin" depends="jar">
<delete>
<!-- in installer but not update -->
<fileset dir="plugin/" includes="socks.config" />
</delete>
<!-- get version number -->
<buildnumber file="scripts/build.number" />
<property name="release.number" value="0.1" />
<!-- make the update xpi2p -->
<!-- this contains everything except i2ptunnel.config -->
<copy file="LICENSE.txt" todir="plugin/" overwrite="true" />
<copy file="README.txt" todir="plugin/" overwrite="true" />
<copy file="scripts/plugin.config" todir="plugin/" overwrite="true" />
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
<arg value="update-only=true" />
</exec>
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
<arg value="version=${release.number}-b${build.number}" />
</exec>
<copy file="src/build/socksoutproxy.jar" todir="plugin/lib" overwrite="true" />
<input message="Enter su3 signing key password:" addproperty="release.password.su3" />
<fail message="You must enter a password." >
<condition>
<equals arg1="${release.password.su3}" arg2=""/>
</condition>
</fail>
<!-- this will fail if no su3 keys exist, as it needs the password twice -->
<exec executable="scripts/makeplugin.sh" inputstring="${release.password.su3}" failonerror="true" >
<arg value="plugin" />
</exec>
<move file="socksoutproxy.su3" tofile="socksoutproxy-update.su3" overwrite="true" />
<!-- make the install xpi2p -->
<copy file="scripts/plugin.config" todir="plugin/" overwrite="true" />
<!-- Files in installer but not update. Be sure to Add to delete fileset above and clean target below -->
<copy file="scripts/socks.config" todir="plugin/" overwrite="true" />
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
<arg value="version=${release.number}-b${build.number}" />
</exec>
<exec executable="scripts/makeplugin.sh" inputstring="${release.password.su3}" failonerror="true" >
<arg value="plugin" />
</exec>
</target>
<target name="distclean" depends="clean" />
<target name="clean" >
<ant dir="src" target="clean" />
<defaultexcludes remove="**/*~"/>
<delete>
<fileset dir="." includes="*/*.~ **/*.*~ */**/*.*~ *.*~" />
</delete>
<delete file="plugin/plugin.config" />
<delete file="plugin/socks.config" />
<delete file="plugin/lib/socksoutproxy.jar" />
<delete file="plugin/LICENSE.txt" />
<delete file="plugin/README.txt" />
<delete file="socksoutproxy.su3" />
<delete file="socksoutproxy.xpi2p" />
<delete file="socksoutproxy-update.su3" />
<delete file="plugin.zip" />
</target>
</project>

5
plugin/clients.config Normal file
View File

@ -0,0 +1,5 @@
clientApp.0.main=net.i2p.socksoutproxy.SocksOutproxy
clientApp.0.name=Socks Outproxy
clientApp.0.delay=0
clientApp.0.startOnLoad=true
clientApp.0.classpath=$PLUGIN/lib/socksoutproxy.jar

147
scripts/makeplugin.sh Executable file
View File

@ -0,0 +1,147 @@
#!/bin/sh
#
# basic packaging up of a plugin
#
# usage: makeplugin.sh plugindir
#
# zzz 2010-02
# zzz 2014-08 added support for su3 files
#
if [ -z "$I2P" -a -d "$PWD/../i2p.i2p/pkg-temp" ]; then
export I2P=../i2p.i2p/pkg-temp
fi
if [ ! -d "$I2P" ]; then
echo "Can't locate your I2P installation. Please add a environment variable named I2P with the path to the folder as value"
echo "On OSX this solved with running: export I2P=/Applications/i2p if default install directory is used."
exit 1
fi
PUBKEYDIR=$HOME/.i2p-plugin-keys
PUBKEYFILE=$PUBKEYDIR/plugin-public-signing.key
PRIVKEYFILE=$PUBKEYDIR/plugin-private-signing.key
B64KEYFILE=$PUBKEYDIR/plugin-public-signing.txt
PUBKEYSTORE=$PUBKEYDIR/plugin-su3-public-signing.crt
PRIVKEYSTORE=$PUBKEYDIR/plugin-su3-keystore.ks
KEYTYPE=RSA_SHA512_4096
PLUGINDIR=${1:-plugin}
PC=plugin.config
PCT=${PC}.tmp
if [ ! -d $PLUGINDIR ]
then
echo "You must have a $PLUGINDIR directory"
exit 1
fi
if [ ! -f $PLUGINDIR/$PC ]
then
echo "You must have a $PLUGINDIR/$PC file"
exit 1
fi
SIGNER=`grep '^signer=' $PLUGINDIR/$PC`
if [ "$?" -ne "0" ]
then
echo "You must have a plugin name in $PC"
echo 'For example name=foo'
exit 1
fi
SIGNER=`echo $SIGNER | cut -f 2 -d '='`
if [ ! -f $PRIVKEYFILE ]
then
echo "Creating new XPI2P DSA keys"
mkdir -p $PUBKEYDIR || exit 1
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate keygen $PUBKEYFILE $PRIVKEYFILE || exit 1
java -cp $I2P/lib/i2p.jar net.i2p.data.Base64 encode $PUBKEYFILE $B64KEYFILE || exit 1
rm -rf logs/
chmod 444 $PUBKEYFILE $B64KEYFILE
chmod 400 $PRIVKEYFILE
echo "Created new XPI2P keys: $PUBKEYFILE $PRIVKEYFILE"
fi
if [ ! -f $PRIVKEYSTORE ]
then
echo "Creating new SU3 $KEYTYPE keys for $SIGNER"
java -cp $I2P/lib/i2p.jar net.i2p.crypto.SU3File keygen -t $KEYTYPE $PUBKEYSTORE $PRIVKEYSTORE $SIGNER || exit 1
echo '*** Save your password in a safe place!!! ***'
rm -rf logs/
# copy to the router dir so verify will work
CDIR=$I2P/certificates/plugin
mkdir -p $CDIR || exit 1
CFILE=$CDIR/`echo $SIGNER | sed s/@/_at_/`.crt
cp $PUBKEYSTORE $CFILE
chmod 444 $PUBKEYSTORE
chmod 400 $PRIVKEYSTORE
chmod 644 $CFILE
echo "Created new SU3 keys: $PUBKEYSTORE $PRIVKEYSTORE"
echo "Copied public key to $CFILE for testing"
fi
rm -f plugin.zip
OPWD=$PWD
cd $PLUGINDIR
grep -q '^name=' $PC
if [ "$?" -ne "0" ]
then
echo "You must have a plugin name in $PC"
echo 'For example name=foo'
exit 1
fi
grep -q '^version=' $PC
if [ "$?" -ne "0" ]
then
echo "You must have a version in $PC"
echo 'For example version=0.1.2'
exit 1
fi
# update the date
grep -v '^date=' $PC > $PCT
DATE=`date '+%s000'`
echo "date=$DATE" >> $PCT
mv $PCT $PC || exit 1
# add our Base64 key
grep -v '^key=' $PC > $PCT
B64KEY=`cat $B64KEYFILE`
echo "key=$B64KEY" >> $PCT || exit 1
mv $PCT $PC || exit 1
# zip it
zip -r $OPWD/plugin.zip * || exit 1
# get the version and use it for the sud header
VERSION=`grep '^version=' $PC | cut -f 2 -d '='`
# get the name and use it for the file name
NAME=`grep '^name=' $PC | cut -f 2 -d '='`
XPI2P=${NAME}.xpi2p
SU3=${NAME}.su3
cd $OPWD
# sign it
echo 'Signing. ...'
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate sign plugin.zip $XPI2P $PRIVKEYFILE $VERSION || exit 1
java -cp $I2P/lib/i2p.jar net.i2p.crypto.SU3File sign -c PLUGIN -t $KEYTYPE plugin.zip $SU3 $PRIVKEYSTORE $VERSION $SIGNER || exit 1
rm -f plugin.zip
# verify
echo 'Verifying. ...'
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate showversion $XPI2P || exit 1
java -cp $I2P/lib/i2p.jar -Drouter.trustedUpdateKeys=$B64KEY net.i2p.crypto.TrustedUpdate verifysig $XPI2P || exit 1
java -cp $I2P/lib/i2p.jar net.i2p.crypto.SU3File showversion $SU3 || exit 1
java -cp $I2P/lib/i2p.jar net.i2p.crypto.SU3File verifysig -k $PUBKEYSTORE $SU3 || exit 1
rm -rf logs/
echo 'Plugin files created: '
wc -c $XPI2P
wc -c $SU3
exit 0

8
scripts/plugin.config Normal file
View File

@ -0,0 +1,8 @@
name=socksoutproxy
signer=zzz-plugin@mail.i2p
description=Socks Outproxy
author=zzz
updateURL.su3=http://stats.i2p/i2p/plugins/socksoutproxy-update.su3
websiteURL=http://zzz.i2p/forums/16
license=Apache 2.0
min-i2p-version=0.9.11

5
scripts/socks.config Normal file
View File

@ -0,0 +1,5 @@
# socks proxy configuration
host=localhost
port=9050
#user=
#password=

44
src/build.xml Normal file
View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="socksoutproxy">
<property name="i2pbase" value="../../i2p.i2p"/>
<property name="i2plib" value="${i2pbase}/build"/>
<path id="cp">
<pathelement path="${java.class.path}" />
<pathelement location="${i2plib}/i2p.jar" />
</path>
<target name="all" depends="clean, build" />
<target name="build" depends="jar" />
<target name="builddep">
</target>
<property name="javac.compilerargs" value="" />
<property name="javac.version" value="1.8" />
<target name="compile">
<mkdir dir="./build" />
<mkdir dir="./build/obj" />
<javac
srcdir="./java"
debug="true" deprecation="on" source="${javac.version}" target="${javac.version}"
destdir="./build/obj"
includeAntRuntime="false"
classpath="${i2plib}/i2p.jar" >
<compilerarg line="${javac.compilerargs}" />
</javac>
</target>
<target name="jar" depends="builddep, compile">
<jar destfile="build/socksoutproxy.jar" basedir="./build/obj" includes="**/*.class" >
</jar>
</target>
<target name="clean">
<delete dir="./build" />
</target>
<target name="cleandep" depends="clean">
</target>
<target name="distclean" depends="clean">
</target>
</project>

View File

@ -0,0 +1,229 @@
package net.i2p.socksoutproxy;
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.app.*;
import static net.i2p.app.ClientAppState.*;
import net.i2p.data.DataHelper;
import net.i2p.socks.SOCKS5Client;
import net.i2p.socks.SOCKSException;
import static net.i2p.socks.SOCKS5Constants.*;
import net.i2p.util.Log;
/**
*
* @author zzz
*/
public class SocksOutproxy implements ClientApp, Outproxy {
private final I2PAppContext _context;
private final Log _log;
private final ClientAppManager _mgr;
private String _phost;
private int _pport;
private String _puser;
private String _ppw;
private ClientAppState _state = UNINITIALIZED;
public SocksOutproxy(I2PAppContext ctx, ClientAppManager mgr, String args[]) {
_context = ctx;
_log = ctx.logManager().getLog(SocksOutproxy.class);
_mgr = mgr;
_state = INITIALIZED;
}
private void loadConfig() {
File cfile = new File(_context.getAppDir(), "plugins/socksoutproxy/socks.config");
Properties props = new Properties();
if (cfile.exists()) {
try {
DataHelper.loadProps(props, cfile);
} catch (IOException ioe) {
_log.error("Failed loading zzzot config from " + cfile, ioe);
}
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("No config file " + cfile);
}
_phost = props.getProperty("host", "localhost").trim();
String sport = props.getProperty("port", "9050").trim();
_pport = Integer.parseInt(sport);
if (_pport <= 0 || _pport > 65535)
throw new IllegalArgumentException("Bad port " + _pport);
String puser = props.getProperty("user");
if (puser != null) {
puser = puser.trim();
if (puser.length() <= 0)
puser = null;
}
_puser = puser;
String ppw = props.getProperty("pw");
if (ppw != null) {
ppw = ppw.trim();
if (ppw.length() <= 0)
ppw = null;
}
_ppw = ppw;
}
private void test() throws IOException {
// test that the socks 5 proxy is there and auth, if any, works
Socket s = null;
OutputStream out = null;
InputStream in = null;
try {
s = new Socket();
s.connect(new InetSocketAddress(_phost, _pport), 10*1000);
out = s.getOutputStream();
boolean authAvail = _puser != null && _ppw != null;
// send the init
out.write(SOCKS_VERSION_5);
if (authAvail) {
out.write(2);
out.write(Method.USERNAME_PASSWORD);
} else {
out.write(1);
}
out.write(Method.NO_AUTH_REQUIRED);
out.flush();
// read init reply
in = s.getInputStream();
int hisVersion = in.read();
if (hisVersion != SOCKS_VERSION_5)
throw new SOCKSException("SOCKS proxy is not Version 5");
int method = in.read();
if (method == Method.NO_AUTH_REQUIRED) {
// good
} else if (method == Method.USERNAME_PASSWORD) {
if (authAvail) {
// send the auth
out.write(AUTH_VERSION);
byte[] user = _puser.getBytes("UTF-8");
byte[] pw = _ppw.getBytes("UTF-8");
out.write(user.length);
out.write(user);
out.write(pw.length);
out.write(pw);
out.flush();
// read the auth reply
if (in.read() != AUTH_VERSION)
throw new SOCKSException("Bad auth version from proxy");
if (in.read() != AUTH_SUCCESS)
throw new SOCKSException("Proxy authorization failure");
} else {
throw new SOCKSException("Proxy requires authorization, please configure username/password");
}
} else {
throw new SOCKSException("Proxy authorization failure");
}
} catch (IOException ioe) {
String msg = "Failed to connect to SOCKS proxy at " + _phost + ':' + _pport;
_log.error(msg);
changeState(START_FAILED, msg, ioe);
throw ioe;
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
if (out != null) try { out.close(); } catch (IOException ioe) {}
if (s != null) try { s.close(); } catch (IOException ioe) {}
}
}
/**
* Not supported
*/
public synchronized static void main(String args[]) {
throw new UnsupportedOperationException("Must use ClientApp interface");
}
/////// Outproxy method
public Socket connect(String host, int port) throws IOException {
Socket s;
try {
s = new Socket();
s.connect(new InetSocketAddress(_phost, _pport), 10*1000);
} catch (IOException ioe) {
String msg = "Failed to connect to SOCKS proxy at " + _phost + ':' + _pport;
_log.error(msg);
changeState(STOPPING, msg, ioe);
shutdown(null);
throw ioe;
}
SOCKS5Client.connect(s, host, port, _puser, _ppw);
s.setSoTimeout(5*60*1000);
return s;
}
/////// ClientApp methods
public synchronized void startup() throws Exception {
if (_state != STOPPED && _state != INITIALIZED && _state != START_FAILED) {
_log.error("Start while state = " + _state);
return;
}
loadConfig();
test();
changeState(RUNNING);
_mgr.register(this);
}
public synchronized void shutdown(String[] args) {
if (_state == STOPPED)
return;
changeState(STOPPING);
_mgr.unregister(this);
changeState(STOPPED);
}
public ClientAppState getState() {
return _state;
}
public String getName() {
return "outproxy";
}
public String getDisplayName() {
return "Socks Outproxy";
}
/////// end ClientApp methods
private synchronized void changeState(ClientAppState state) {
if (state == _state)
return;
_state = state;
_mgr.notify(this, state, null, null);
}
private synchronized void changeState(ClientAppState state, String msg, Exception e) {
if (state == _state)
return;
_state = state;
_mgr.notify(this, state, msg, e);
}
}