Compare commits

..

228 Commits

Author SHA1 Message Date
zzz
c3ae3f2895 build fix 2012-12-17 15:29:26 +00:00
zzz
8b41956091 0.9.4 2012-12-17 14:52:02 +00:00
str4d
264e27ab3f Correct url for forum.i2p 2012-12-16 03:23:16 +00:00
zzz
74f6abc97a bump 2012-12-15 14:54:32 +00:00
zzz
8edbfc5198 replace call to Arrays.copyOf(), not in Java 5 2012-12-15 14:41:42 +00:00
kytv
8513d1f22b merge of '482fcb3afd2e52160588dbf9e253ff594e0d5ce3'
and 'a63132b861cf363158a5ac2e1897b4636321d536'
2012-12-15 00:37:09 +00:00
str4d
cb75e3dc7e Documented required and optional test-related properties in build.properties 2012-12-14 22:33:37 +00:00
kytv
a8926dae57 ship all of the *BSD jcpuid files in the installer 2012-12-14 22:20:20 +00:00
kytv
c5502737f2 Debian: changelog and minor initscript updates 2012-12-14 16:14:46 +00:00
kytv
206cea8b56 update geoip.txt
Update geoip.txt based on Maxmind GeoLite Country database from 2012-12-04
2012-12-14 16:00:09 +00:00
kytv
003a8b07e1 German and Swedish translation updates from Transifex 2012-12-14 15:58:59 +00:00
zab
c5d69eb231 jenkins test passed! 2012-12-14 08:34:27 +00:00
zab
78864ab380 test to see if the jenkins server by skydrome detects broken compilation 2012-12-14 08:29:08 +00:00
zzz
ec22a6ec6b copy armv6 wrapper in preppkg-linux target 2012-12-13 12:33:53 +00:00
kytv
b435857e15 don't try to copy armv7 wrapper in preppkg-linux target 2012-12-12 20:34:37 +00:00
zzz
8198419156 build fixup 2012-12-12 15:43:42 +00:00
zzz
60718dbf72 Drop custom-built armv7 wrapper.
Tanuki-built arm wrapper works on armv5 and armv7 but not on Raspberry Pi armv6.
Wrapper we built for Raspberry Pi does not work on Trimslice armv7.
2012-12-11 19:27:56 +00:00
zab
1fa00a5738 Restore javadocs 2012-12-10 18:05:52 +00:00
zab
d2b2600e5e VersionComparator w/o object churn, ticket #789
tests
2012-12-10 10:07:34 +00:00
zab
d062db3c17 Object churn improvements, ticket #787 2012-12-10 09:46:05 +00:00
zab
32a8bb7a3e more VersionComparator tests 2012-12-09 16:59:51 +00:00
zab
d8417cbf71 more tests for VersionComparator 2012-12-09 13:28:06 +00:00
zzz
863a05b33d * susimail: Button CSS spacing tweak 2012-12-08 12:12:29 +00:00
str4d
3fc3abe7a5 Moved susimail.properties out of the build path, and set build.xml to copy it in 2012-12-08 02:02:04 +00:00
zzz
96fcaf9385 javadoc 2012-12-07 14:21:30 +00:00
zzz
0b14981163 fix forum urls 2012-12-07 14:20:43 +00:00
zzz
87a56a6fac * RouterClock: Reduce log level (ticket #790) 2012-12-07 14:20:02 +00:00
str4d
0fa938e096 merge of '60726592fdfe50d6d8051846e0034b4b40a6761e'
and 'ad92f5811a7ff6ceab5ab09572d716f00f9100ea'
2012-12-06 00:17:13 +00:00
meeh
b7e3a60fbc Ticket #802
Added https://euve5653.vserver.de/netDb/ to reseed host list.
Certificate using www.cacert.org, so no need to add a new crt file.
2012-12-05 20:57:33 +00:00
kytv
653ccaae49 typo fix + updated UK translation 2012-12-05 14:38:56 +00:00
zzz
ca00b34314 * I2CP: Fix external I2CP apps, including i2ping, caused by 0 nonce value,
broken in 0.9.2 (tickets #799, #801). Allow nonces == 0.
   Javadocs and cleanups.
2012-12-05 00:03:27 +00:00
zzz
0c5811801f * SSU: Fix rare NPE (ticket #798) 2012-12-05 00:01:49 +00:00
zzz
d9727c901c * Reseed: Don't go on to the next host if we have enough http://zzz.i2p/topics/1287 2012-12-05 00:00:55 +00:00
zzz
63b8e7101f * GarlicMessage: Fix notes and log in GarlicMessageHandler and HandleGarlicMessageJob,
they are used for netdb messages received by floodfills http://zzz.i2p/topics/1282
2012-12-05 00:00:06 +00:00
kytv
4f5da775d4 Chinese, French, Italian, Polish, and Ukrainian translation updates from
Transifex.
2012-11-28 23:27:16 +00:00
kytv
3464ad6e5e remove extraneous space 2012-11-28 19:39:31 +00:00
kytv
d28480dd92 bumping build to -12 2012-11-28 10:34:19 +00:00
kytv
4902b4ecba merge of '32a936bfa4c9048f8d96461990da03f7f35cb676'
and '9c7cae316969219b1f2d74c20dbb4a12a94857a9'
2012-11-28 10:28:30 +00:00
zab
0e0a38460e Revert to using ArrayList in RouterInfo 2012-11-28 08:19:34 +00:00
str4d
4266a10ffb Added more tests to VersionComparatorSpec to further cover the implementation 2012-11-26 00:29:02 +00:00
str4d
31fc55eca7 Added tests for VersionComparator 2012-11-25 02:44:01 +00:00
str4d
4d389f75a2 Changed summary bar ordering <input type="image">s to <button>s
Fixes a bug which caused the ordering to be non-functional.
2012-11-25 02:39:49 +00:00
zzz
abe29e044f Remove org.mortbay.http.Version.paranoid property not recognized by Jetty 6. 2012-11-24 20:01:07 +00:00
zzz
c5a6ed3179 final 2012-11-24 17:30:20 +00:00
zzz
99058ee135 * Codel: Make stats non-required (ticket #786) 2012-11-24 16:41:55 +00:00
zzz
b2e335fbba * Profiles: Small optimization in coalesceOnly() (ticket #765)
javadoc, detab
2012-11-24 16:41:12 +00:00
zzz
1d3bbfd250 * Addressbook: Disable unused wakeup via http 2012-11-24 13:56:45 +00:00
zzz
916e328e10 javadoc, volatile 2012-11-24 13:55:47 +00:00
zzz
fe02145fed typo 2012-11-24 13:53:12 +00:00
str4d
ad41b25be5 merge of '138eae0135999a3f8e20b08183500a2318287cd4'
and '314f5c9d4fc6b5dd82d6ee09a207686f52e66a2c'
2012-11-23 20:12:11 +00:00
str4d
d2b1103e26 Removed a hard-coded jsp link I missed 2012-11-23 20:10:52 +00:00
kytv
0b05cd761c i2prouter: fix block location (thanks k0e) 2012-11-23 18:08:04 +00:00
kytv
28ba7880e4 merge of '15d44385349738e5c84f8efcdb797d98b4fbaed0'
and '586f7a71f1e187cb041d873c013fbe91d0184b08'
2012-11-23 18:07:07 +00:00
str4d
4680fd118b Added remaining .project and .classpath files
The project files for jetty are in the apps/jetty folder. Dependencies will be
resolved once the project has been built normally once via e.g. "ant updater".
2012-11-23 12:31:02 +00:00
str4d
9dcfe98437 Added .project and .classpath Eclipse files to most sections of the source
To import a branch of trunk into Eclipse, create a new workspace based in the
root directory of the checked-out branch, and then select "File -> Import..."
then "General -> Existing Projects into Workspace", then for "Select root
directory" choose the root directory of the branch (and of the workspace).
Select all projects that appear, so that dependencies are satisfied.

Currently left out are i2psnark, routerconsole and susimail, because they all
depend on jars in apps/jetty/jettylib, which seems to be auto-generated. Need
to check whether the existence of that folder (from having Eclipse files in it)
will prevent the jars being populated or not.
2012-11-23 12:20:26 +00:00
zab
55c264916b kill a string allocation hotspot 2012-11-23 07:52:03 +00:00
zab
0ec77f5514 Use the cached iterator list to remove Iterator allocation hotspots 2012-11-23 07:22:58 +00:00
zab
f238d0514f test removal 2012-11-23 07:13:21 +00:00
zab
d8613d2285 more appropriate junit test 2012-11-23 07:06:01 +00:00
zab
1e83028702 An ArrayList that reuses a single iterator 2012-11-22 21:50:48 +00:00
zab
e974d3bc55 propagate from branch 'i2p.i2p.zab.782' (head 64415601890b9c494a8f06379f9feefbc855e07c)
to branch 'i2p.i2p' (head 0e92cf3a3844e7b738ca9c2486112867fc663b6f)
2012-11-22 20:53:03 +00:00
zab
7c96044d18 javadoc 2012-11-22 20:47:54 +00:00
zzz
d5d70f1b40 Wrapper 3.5.16 compiled on Raspberry Pi:
GPLv2
binaries stripped

gcc (Debian 4.6.3-12+rpi1) 4.6.3

java version "1.6.0_24"
OpenJDK Runtime Environment (IcedTea6 1.11.5) (6b24-1.11.5-1+rpi1)
OpenJDK Zero VM (build 20.0-b12, mixed mode)

Processor	: ARMv6-compatible processor rev 7 (v6l)
BogoMIPS	: 697.95
Features	: swp half thumb fastmult vfp edsp java tls 
CPU implementer	: 0x41
CPU architecture: 7
CPU variant	: 0x0
CPU part	: 0xb76
CPU revision	: 7

Hardware	: BCM2708
Revision	: 0002
2012-11-22 00:36:45 +00:00
kytv
34e0b36401 updates to 'i2prouter' based on changes to Tanuki's example script 2012-11-21 21:03:55 +00:00
kytv
2fbe0e8bb1 update wrapper to v3.5.16
- Windows: Self-compiled with VS2010 in Windows 7. The icon has been
  changed from Tanuki's default to Itoopie.
- FreeBSD: Self-compiled in FreeBSD 7.4 to eliminate the dependency on the
  compat6x port and stripped.
- Linux PPC32: Self-compiled in Debian Squeeze and stripped
- Linux x86, Linux x64, Linux ARMv5, MacOSX & Solaris: Binares are from the
  "community edition" deltapack offered by Tanuki. The Linux binaries have
  been stripped.
2012-11-21 21:01:45 +00:00
zab
33ee8a38ca Ticket #765 - optimize locking during profile reorg 2012-11-21 15:45:38 +00:00
zzz
5f4562467e * Transport: Fix bug that inadvertently reduced default max
SSU connections in 0.9.2, cutting network capacity in half and
   harming tunnel build success rates
2012-11-20 01:17:02 +00:00
kytv
56ef4cda82 Addi an exception for core2 & corei to NBI on 32-bit kFreeBSD, NetBSD, and OpenBSD
These binaries are identical on 32-bit kFreeBSD, NetBSD, and OpenBSD systems.
If a corei CPU is found on these systems we'll use the core2 jbigi binary.

194868,ad47c3d909d0fb85242566f3c7b4be5b,libjbigi-kfreebsd-core2.so
194868,ad47c3d909d0fb85242566f3c7b4be5b,libjbigi-kfreebsd-corei.so
202848,57aa013ca310f3aae990f5ee78c100bd,libjbigi-netbsd-core2.so
202848,57aa013ca310f3aae990f5ee78c100bd,libjbigi-netbsd-corei.so
207657,01483211b6e077057302e256f185f7e7,libjbigi-openbsd-core2.so
207657,01483211b6e077057302e256f185f7e7,libjbigi-openbsd-corei.so

The I2P project does not currently ship these binaries, but they can be found
in unofficial jbigi packages.
2012-11-19 23:47:55 +00:00
kytv
5975b69b42 Add jcpuid binaries for OpenBSD, NetBSD, and kFreeBSD.
Support for these has already been added to CPUID.java.
2012-11-19 22:44:58 +00:00
kytv
d0a3c7256a Improved support for GNU/kFreeBSD
- add kFreeBSD to NBI and CPUID
- add kFreeBSD to jcpuid/jbigi build scripts
- refresh debian patches to compensate for kFreeBSD changes
- i2prouter: Detect kFreeBSD and normalize its name
- clean up osid (switching to "elif") and adding support for detecting kFreeBSD
- update postinstall.sh; I2P cannot be installed using gij so postinstall.sh
  will not be run. If/when openjdk finally comes to kFreeBSD, we'll be ready for it.
2012-11-19 22:41:54 +00:00
zzz
d94c14967c move HashDistance to router/util 2012-11-19 16:22:09 +00:00
zzz
f15828fa95 * NetDB: Add negative lookup cache 2012-11-19 16:10:02 +00:00
zzz
f64eacefe3 * BuildHandler: Disable CoDel, wasn't helping 2012-11-19 16:06:59 +00:00
zzz
c8f2effca8 * Profiles: Split up files into subdirectories 2012-11-19 16:04:33 +00:00
kytv
74f4859e13 explicitly prefer openjdk-*-headless over default-jre-headless.
On Debian Squeeze the default-jre-* packages point to gij/gcj which is suboptimal.
Openjdk cannot be forced since not all platforms--such as kFreeBSD--have it as
an available option.
2012-11-18 18:52:13 +00:00
zab
8c987fc0d2 Add javadocs to getters
Make setters package-private
	Small noop tweak to the computeAverages
2012-11-18 15:01:43 +00:00
zab
efc202d2ee more use of the new methods 2012-11-17 19:22:23 +00:00
zab
3cbca7c0ac more use of the computeAverages method 2012-11-17 18:51:28 +00:00
zab
82e4244473 more refactoring 2012-11-17 18:36:55 +00:00
zab
836620c375 javadoc 2012-11-17 18:03:10 +00:00
zzz
addfff8626 * Tunnels: Set default priorities for tunnels (ticket #719)
Exploratory: +30
   IRC: +15
   HTTP Proxy: +10
   I2PSnark: -10
2012-11-17 17:32:24 +00:00
zzz
3836742e7d stat cleanup 2012-11-17 17:24:44 +00:00
zzz
74fd171131 * i2psnark: Clear PEX peers set after use, cause of bad peer counts 2012-11-17 17:11:39 +00:00
zzz
d511bf2cd8 * error500.jsp: Add servlet version 2012-11-17 17:10:31 +00:00
zzz
0cbbedd250 javadoc fix 2012-11-17 17:09:37 +00:00
zab
4824cae36c Properly synchronize the Rate class
Add a new class to store results from rate calculations
	Add a new method to compute average, last and current measurements
	Use the new method in RouterThrottleImpl
2012-11-17 00:04:08 +00:00
kytv
b67359aca6 Match *FreeBSD* instead of just FreeBSD*.
(spotted at http://pastethis.i2p/show/2280/ and jcpuid already matches
*FreeBSD* so this fixes a minor consistency issue. Thanks to the anonymous
paster.)
2012-11-16 21:24:03 +00:00
zzz
99179edae2 add reseed to event log 2012-11-16 15:33:38 +00:00
zzz
ae6dad6e48 * NetDB:
- Implement automatic reseeding (ticket #521)
   - Increase minimum routers from 15 to 50
2012-11-16 14:47:55 +00:00
zzz
6902a8392f * i2psnark: Fix rare IOOBE (ticket #777) 2012-11-16 14:45:05 +00:00
zzz
4991c5a1ad * Tunnels: Fix outbound tunnel message priority (ticket #719)
(copy/paste error)
2012-11-16 14:43:46 +00:00
zab
a3e3001d49 * Sync fixes to Frequency
* Removal of warnings related to generics in StatManager
2012-11-14 21:53:54 +00:00
zzz
4fdf1c2411 * stats.jsp: Link to graph page, not single image 2012-11-13 20:40:15 +00:00
zzz
ea00c0af50 * SSU: Fix bug that would drop 512 byte messages
The bug has been there forever but never happened before
   0.9.3 because the buffers were all 32KB and the largest
   fragment was about 1500 bytes. In 0.9.3, there are multiple
   buffer sizes, the smallest is 512 bytes, and a packet
   of exactly 512 bytes would be silently dropped.
   Thanks zab for finding it.
2012-11-13 20:39:29 +00:00
zzz
e6dbd7ddda * SOCKS: Reduce log level of connect errors 2012-11-13 20:36:42 +00:00
zzz
9741d127a9 * NTCP:
- Fix NPE with more syncing (hopefully) (ticket #770)
   - Use ByteCache for 16KB buffers
2012-11-13 20:35:47 +00:00
zzz
8efc7e9369 * HTTP Proxy: Store referrer of new addresses in address book 2012-11-13 20:33:37 +00:00
zzz
da009f8d22 * Bandwidth Limiter: Fix stats broken in -1 2012-11-13 20:32:39 +00:00
zzz
f8133b7abf log tweak 2012-11-13 20:30:56 +00:00
kytv
2362862f31 eepget: If java binary is not found, try to determine where java.exe is located.
In my testing:
32 bit Windows (and, of course, 32 bit JRE) = Java added to the PATH
64 bit Windows and 64 bit JRE = Java added to the PATH
64 bit Windows and 32 bit JRE = Java *not* added to the PATH.

So...with this check-in:

- If the environment variable JAVA is set in the script, we'll use that
  manually specified Java. We will not look in the registry, but we'll check to
  make sure that the binary exists.
- If Java is found in the system path, we'll use it instead. We will not look in the
  registry.
- If the variable is not set manually and Java is not in the system path we'll
  look in the registry to find the java binary.

I've tested this in Windows XP, Vista, and 7 but it should work in any supported version
of Windows.
2012-11-09 23:53:02 +00:00
str4d
f287ed48ed merge of '6f719ac61e6f1afbd935f3fdab862c2e5cc7f5d8'
and 'fd3c457f0a834ba87fead3cbdf22e31253cd4e7c'
2012-11-07 19:29:34 +00:00
meeh
b8a9caeb4c Cleanup in reseed list, also removed HH's ssl host, since it expires 21-11-2012 22:34:10 GMT+1. And
I've failed to get in contact with him for a renewal of his certificate. Errors might appear in logs 
on installs after that date, just remove https://euve5653.vserver.de from /configreseed in console 
and you wont get errors.
2012-11-06 22:00:00 +00:00
str4d
dccd8445e6 More changes to finish first test in UpdateBehaviors 2012-11-06 11:23:57 +00:00
str4d
c5fb009c83 merge of 'd09201283ea0356bf5b1d3aedc4795a202414930'
and 'e2f50f8cb50f8593ca882e94cb661c54b87d2468'
2012-11-05 21:40:35 +00:00
str4d
4d8973b0a5 Assorted fixes to router Junit tests for changes in the source 2012-11-05 21:31:40 +00:00
str4d
f57d91ac16 Added missing DateAndFlagsTest - no errors in core junit tests now 2012-11-05 21:00:20 +00:00
str4d
ccc5923ab3 Drop unused DummyPooledRandomSource, moved to i2p.scripts 2012-11-05 20:43:47 +00:00
str4d
31debe6bbf CryptixRijndael_Algorithm._BLOCK_SIZE is private, so specify value directly 2012-11-05 19:53:00 +00:00
str4d
40d1507237 Fixed imports on core JUnit tests to use Hamcrest matchers provided with Junit4 2012-11-05 19:50:32 +00:00
zzz
ea2be02a29 * RequestLeaseSetJob: Only disconnect client after multiple dropped
lease set requests; reduce timeout, other cleanups
2012-11-05 17:23:32 +00:00
zzz
c21a6a54f8 * PeerManager: Don't reorganize as often if it takes too long (ticket #765) 2012-11-05 17:20:47 +00:00
zzz
70a2e330ef * i2psnark:
- More DHT limits
   - Announce to backup trackers if DHT is empty
   - Use PEX and DHT info in torrent peer count
   - Don't use temp files for announces
   - TrackerClient refactoring
   - cleanups
2012-11-05 17:20:07 +00:00
zzz
d5c70676b0 * Console:
- Fix NPE after restart (ticket #763)
   - Move more nonces out of system properties
2012-11-05 17:17:31 +00:00
zzz
202c92a42d * Unsigned Update: Fix notification on failure 2012-11-05 17:16:02 +00:00
str4d
3cb4d35cee propagate from branch 'i2p.i2p.zzz.update' (head 1ca3b931ebecd4ec80e7e135b634d085934c092b)
to branch 'i2p.i2p' (head c917793878189c29441f69133e029cfdfe3c0895)
2012-11-05 10:38:54 +00:00
str4d
3d35984cf5 Started filling out UpdaterBehaviors 2012-11-05 10:37:18 +00:00
str4d
2217d1ab95 Moved *streaming and i2ptunnel tests to match convention 2012-11-04 11:23:12 +00:00
kytv
75ddc12390 de, pt, and se updates from Transifex. Updated en po files to push to tx. 2012-11-02 19:54:49 +00:00
zzz
d48fab9d98 * I2CP:
- Better fix for logging dropped messages (ticket #758)
   - Implement fast receive to reduce per-message handshakes
   - Make messageReliability=none the default
2012-11-02 16:37:23 +00:00
zzz
d30aeb3902 * KeyManager: Eliminate races, buffer I/O, eliminate periodic syncing 2012-11-02 16:01:44 +00:00
zzz
d479c4ae7d * configstats: Fix group sorting, translate groups 2012-11-02 16:00:41 +00:00
zzz
9c220e08f8 * i2ptunnel:
- Better privkey backup file name
   - Revert increment of privkey tunnel name
   - Move deleted privkeys to backup dir
   - Fix jsp build dependencies
   - Fix layout issue on Chrome (ticket #757)
2012-11-02 15:59:51 +00:00
zzz
eee38a626d * i2psnark:
- Split buckets correctly
   - More exploration fixes
2012-11-02 15:58:26 +00:00
zzz
f29a45a2c2 * PriBlockingQueue: Enforce max size 2012-10-31 16:15:32 +00:00
zzz
a5b68d4fb0 * I2CP: Reduce log level when outbound queue is full (ticket #758) 2012-10-31 16:09:28 +00:00
zzz
8a7d119962 * FIFOBandwidthRefiller: Reduce refill interval to smooth output 2012-10-31 16:07:11 +00:00
zzz
84a0793a10 * Streaming: New disableRejectLogging option (default false), enable for snark 2012-10-31 15:56:02 +00:00
zzz
2f4eeda397 * i2ptunnel: Fix NPE in zzzot plugin 2012-10-31 15:53:57 +00:00
zzz
96ed7abdc5 javadoc, final, private, volatile 2012-10-31 15:52:12 +00:00
str4d
6a91918e6f Stubbed out Specs for net.i2p.router.update.* in routerconsole
*Behaviors.scala should really go in net.i2p.update.* in core, but ScalaTest
doesn't seem to be picking up the cross-dependency properly and just ignores
any Spec which includes them; they will move once the build.xml is fixed.
2012-10-31 00:22:15 +00:00
kytv
2c3edc0503 merge of '2b4768d9966695ad845dad4e28ef426d781e718f'
and '8489000cfeee5a6aa5a250b48bda4f6e2fb16b03'
2012-10-30 19:41:47 +00:00
kytv
f6bac8a08e redirect output to /dev/null (in case /proc/1/comm doesn't exist) 2012-10-30 19:41:35 +00:00
zzz
4ce11a174a * SSU:
- Adjust RTT/RTO calculations
   - Better bandwidth tracking
   - Cleanup of OutboundMessageState
   - Stat tweaks
 * Transports: Increase min peer port to 1024
2012-10-30 18:16:37 +00:00
zzz
d92f5e6508 merge of 'b2b4c1ba1f799d81d6d164698cb28aa9b837d390'
and 'c2b60a59c73835b51357a706da377862d8bd5ebc'
2012-10-30 15:06:38 +00:00
zzz
513821123e remove space in javascript urls 2012-10-30 13:18:54 +00:00
sponge
f56c804e86 cleanups as requested 2012-10-30 11:03:11 +00:00
meeh
fb50f7adb4 Adding two new reseed hosts. Thanks to h2ik and SWAT
* reseed.info - SWAT
* i2p.feared.eu - h2ik
2012-10-30 02:27:16 +00:00
str4d
a99bf60cea Added Mockito to ScalaTest classpath, and removed unneeded entry
Put mockito-all.jar (or a link to the actual version) in the same directory
as the ScalaTest lib files (passed in the command line as scalatest.libs).
2012-10-29 22:30:53 +00:00
zzz
40d981df25 * OutNetMessage: Properly clean up when dropped by codel (but unused for now
since codel is disabled for ONM)
 * Tunnels: Implement per-client outbound tunnel message priority (ticket #719)
 * ClientTunnelSettings cleanup
2012-10-29 22:21:50 +00:00
zzz
f5165cfae5 log tweak 2012-10-29 22:17:38 +00:00
zzz
055bae0450 * StatisticsManager: Publish stats less often 2012-10-29 22:16:29 +00:00
zzz
74e5ea6e20 * Installer: Drop news.xml and old certs 2012-10-29 22:12:30 +00:00
zzz
32f3ca0568 * logs.jsp:
- Don't display dup message if last
   - Spacing tweaks
2012-10-29 22:10:42 +00:00
zzz
fd3423fe09 * i2ptunnel:
- Create backup privkey files (ticket #752)
   - Fix NPE in Android startup
2012-10-29 22:09:55 +00:00
zzz
05d299816b * i2psnark:
- Add kbucket debugging
   - Eliminate redundant explore keys
   - Add more limits to DHT tracker
   - Delay expiration at startup
   - Only enable updates for dev builds and 1% of release builds
 * Update Manager: Warn on dup registration
2012-10-29 22:08:38 +00:00
zzz
2b80d450fa drop old fortuna build script 2012-10-29 22:06:03 +00:00
str4d
9a31115eff Classpath change in router build.xml to get routerconsole test harness to work 2012-10-29 12:14:04 +00:00
zzz
4baf3b6913 Fixups after props from:
i2p.i2p.zzz.pcap
	i2p.i2p.zzz.test
	i2p.i2p.zzz.test2
	i2p.i2p.zzz.update
Javadoc fixes
Checklist tweak
-1
2012-10-28 13:08:02 +00:00
zzz
5e48331eae propagate from branch 'i2p.i2p.zzz.update' (head 267311f29e501fcc8b3d674a93e78b5520ac985e)
to branch 'i2p.i2p' (head edeca2ab47e734c2314ff394609292d8bd3d5293)
2012-10-28 12:48:35 +00:00
zzz
5766db2c09 propagate from branch 'i2p.i2p.zzz.pcap' (head fff5fc864e5905ed77f8d60f7d0892ed5c2447b4)
to branch 'i2p.i2p' (head cc74e6e08096cc7fdb8563b2eae82df2a000ab01)
2012-10-28 12:26:52 +00:00
zzz
c4f6f48eeb propagate from branch 'i2p.i2p.zzz.test2' (head a002e8957b5bf3a44149203d6842ef4b35107aa7)
to branch 'i2p.i2p' (head 0f6e2b3b8643fe7797e8727329345c1ed4cf741b)
2012-10-28 12:24:07 +00:00
zzz
943e2d7fe7 propagate from branch 'i2p.i2p.zzz.test' (head 48448fc896d1e0859f481e98d0e80e764cc40736)
to branch 'i2p.i2p' (head aedb9b8335d6de72dd633e79716fff6ffec263a1)
2012-10-28 12:17:38 +00:00
zzz
c4fa8fabb2 - Continue work to use priorities in FIFOBandwidthLimiter
- Log tweaks
2012-10-28 12:10:24 +00:00
zzz
6868047ee4 * i2ptunnel:
- Refactor TCG to use ClientApp interface
  - Remove 'reload config' button
  - Synchronization fixes
  - Don't instantiate early, to allow router to hold
    a reference. TCG.getInstance() may now
    return null when in RouterContext.
  - Jsps display message when TCG not initialized
2012-10-27 18:51:50 +00:00
zzz
80e7ee46fb enable pw when adding one 2012-10-27 18:45:16 +00:00
zzz
61ee957add pcap:
- Buffer output
 - Separate methods for inbound and outbound, so we
   don't need to use PacketLocal for inbound
 - Cleanups after prop
 - Finals etc.
2012-10-27 18:03:54 +00:00
kytv
6e66d377f6 changelog/patch updates 2012-10-27 18:00:10 +00:00
zzz
99e759a5be propagate from branch 'i2p.i2p' (head 6e6de141ddbaddfcecf8a66ad8cf65f247f41f94)
to branch 'i2p.i2p.zzz.pcap' (head ae8977bcc33f75ee36505e739e9e4a194f5d9074)
2012-10-27 16:12:57 +00:00
zzz
0e2fd0c6f5 tweak 2012-10-27 12:47:07 +00:00
zzz
0ccf65fcf8 banlist 2012-10-26 16:24:31 +00:00
zzz
af06fded73 - Add password enabled property
- Bypass nonce checking if passwords enabled
  - Add message about cookies if nonce fails
  - Minor susidns cleanup
2012-10-26 13:08:23 +00:00
zzz
2f69d16828 - Thread magnet start if not connected
- Don't lose all DHT peers if we stop quickly
- Explore a kbucket if it's less than 3/4 full
- Change release torrent file names
2012-10-23 19:34:35 +00:00
zzz
bb2363f68a - Fix DummyHandler
- Notes on news.xml enhancements
- Fix handling existing torrent
- Add dn to magnet link generation
- Fix progress info
2012-10-23 14:09:14 +00:00
zzz
724f4f9b37 - Several plugin install fixes
- Remove unused UpdateTypes
- Only try applicable updaters when updating
- Javadoc fixes
2012-10-23 02:34:24 +00:00
zzz
6f790d99c9 exit 1 on failure 2012-10-22 22:56:40 +00:00
zzz
efb986ffd9 - Handle case where we already have torrent
- New Storage.main() for use in the release process
- Make torrent files in release process
- Stop tunnel after fatal if no snarks are running
2012-10-22 22:55:36 +00:00
zzz
bd9ad9982b - Fix spacing in summary bar
- Add start() in UpdateTask so things happen in the right order
- Add toString() in UpdateTask for better debugging
- Fix getID() for plugin UpdateTasks
2012-10-22 20:25:01 +00:00
zzz
1538e6ec4e - Fix VersionComparator (thx zab)
- Add debug output
2012-10-22 17:13:23 +00:00
zzz
95e0c37222 - Add fail timers
- Add progress indication
- Listener cleanup
2012-10-22 14:51:41 +00:00
zzz
8b2889e317 - Only fail after all URLs are tried
- Move registration from servlet to manager and delay
- Fix plugin updates
- More logging
2012-10-21 17:14:54 +00:00
zzz
0fc452b683 - Improved parsing of news file
- Add magnet links to news file
2012-10-21 14:59:52 +00:00
zzz
6e19854e4c - NPE fix on signed udpates
- More work on snark updater
- Clean up imports
2012-10-21 13:34:23 +00:00
zzz
6331cb2374 stub of a torrent updater 2012-10-21 03:13:31 +00:00
zzz
983537b0fd refactor CompleteListener out of Snark.java 2012-10-21 02:34:46 +00:00
zzz
58fd2dddf8 refactor magnet parsing out of servlet 2012-10-21 02:08:34 +00:00
zzz
49b2fbd2b0 tweak 2012-10-20 22:52:11 +00:00
zzz
68814e31e7 * Console:
- Store form handler nonces in the servlet session instead of system properties,
    to prevent cross-session interference
2012-10-20 21:28:17 +00:00
zzz
429739837b * Console:
- Consolidate all the jsp formhandler boilerplate in the new
    formhandler.jsi, in preparation for further improvements
2012-10-20 20:52:45 +00:00
zzz
fef1440865 * Transport:
- Add a simple network monitor
  - Add new reachability state for network disconnected
  - Prevent any tunnel building when disconnected (ticket #519)
  - Don't unleash watchdog when disconnected
2012-10-20 17:28:00 +00:00
zzz
afd29715fa * Addresses:
- Add methods for connectivity detection
  - Remove Hamachi restriction
2012-10-20 15:30:12 +00:00
zzz
fea3bb63c1 - Save available unsigned version across restarts
- Fix status display after downloaded
- Don't display update buttons unless HTTP proxy is up
- Pass the manager down thru the constructors
2012-10-19 20:26:08 +00:00
zzz
4f936f958d add the other getProperty(); more tweaks 2012-10-18 21:07:36 +00:00
zzz
0b4401e64b - Lots of fixes for notifying when updates and checks are complete
- Fixes for NewsHelper stored timestamps
- Add getProperty(String, long) to context for sanity
- New methods and types
- Logging improvements
- Add failsafe TaskCleaner
2012-10-18 14:28:14 +00:00
zzz
2b50c5aaf4 comment out test code 2012-10-18 14:26:30 +00:00
zzz
da4ea77c2a more fixes 2012-10-18 02:20:39 +00:00
zzz
af4786ce0e fixes 2012-10-18 01:29:14 +00:00
zzz
f9b8f0528d - Straighten out some confusion on versions, RFC 822 dates,
etc. on news and unsigned updates. Stored versions are always
  Long.toString(modtime). Only convert to RFC 822 for eepget or display.
2012-10-17 23:45:44 +00:00
zzz
b9d717b9f9 - Split up Updater and Checker interfaces
- Update router after check
2012-10-17 22:24:15 +00:00
zzz
cbc9165afd - Add a jetty starter that can be stopped later
- Include jetty-i2p.jar in the updaters
2012-10-17 17:37:45 +00:00
zzz
a9e18620b9 - Convert HTTP and CONNECT proxies to MD5 authentication
- Allow multiple users
  - Migrate passwords on first save
2012-10-16 19:17:06 +00:00
zzz
613dd77d2c only display tracker error if no peers 2012-10-15 21:30:46 +00:00
zzz
9b6d5daeef more work on proxy digest auth 2012-10-15 21:04:49 +00:00
zzz
d01aae7860 HTTP Proxy:
- Move error page methods to base
 - Preliminary code for digest auth
2012-10-15 15:37:13 +00:00
zzz
50cb427377 split out md5Sum for use in i2ptunnel 2012-10-15 13:57:09 +00:00
zzz
977cdee046 - Move MD5 functions to core util where i2ptunnel can use them 2012-10-15 12:28:45 +00:00
zzz
4db4010abf propagate from branch 'i2p.i2p' (head 2da3b585b42d058e25909bc303d72277ae2463b5)
to branch 'i2p.i2p.zzz.update' (head ebbad994215dc2822e9a1776399864ed77a0e5a0)
2012-10-14 22:42:00 +00:00
zzz
ba37839adf fixes while rechecking storage 2012-10-14 20:05:04 +00:00
zzz
c9196fda03 compile fixes after prop 2012-10-14 20:03:56 +00:00
zzz
b03b4745db propagate from branch 'i2p.i2p' (head 2da3b585b42d058e25909bc303d72277ae2463b5)
to branch 'i2p.i2p.zzz.test' (head 2785f3832a7d1b8adb2f106d049949beb9b88838)
2012-10-14 19:50:51 +00:00
zzz
5e5dc35a1e moved i2cp password to PasswordManager 2012-10-13 22:42:26 +00:00
zzz
24b7b6fabd - Don't migrate any plaintext passwords to obfuscated, it's too messy 2012-10-13 21:42:52 +00:00
zzz
c5ab6b9993 * Passwords:
- Add remove method
    - Add console password form to configui.jsp
    - Consolidate multiple setSettings()/getJettyString() in FormHandler
    - Some form message tweaks
2012-10-13 21:20:16 +00:00
zzz
05740f7903 - Fix MD5 passwords after testing
- Remove unused password fallback in FormHandler
2012-10-13 15:41:57 +00:00
zzz
fc7f995bd2 propagate from branch 'i2p.i2p' (head 2ab4ae45aa60b379e85fca378522966c090a1a27)
to branch 'i2p.i2p.zzz.test' (head 220477e37d4df782b9a8bb30d12669d146dc6226)
2012-10-13 14:23:29 +00:00
zzz
d99a39e5d5 convert to ClientApp interface. Untested. 2012-10-13 13:54:30 +00:00
zzz
0b897fdc98 * RouterConsoleRunner:
- Prep for ClientApp interface by storing context in a field,
      shuffle around what's static and what's not
      (ticket #347)
    - Remove ports from port mapper on shutdown, other changes to
      track actual ports better
      (ticket #731)
    - Hook in password manager using MD5, untested.
      (ticket #731)
2012-10-13 13:06:22 +00:00
zzz
a475a912e6 * New password manager for storing passwords in router.config
in consitent ways, including salting and hashing if possible.
    Not hooked in to console yet, lightly tested.
    (ticket #731)
2012-10-13 12:56:43 +00:00
zzz
8f17b73091 changes due to LoadClientAppsJob changes for ClientApp interface 2012-10-13 12:51:24 +00:00
zzz
d198ae9ef1 * New interface for clients started via clients.config, and a new
manager to track the lifecycle and start/stop clients on demand.
    Not hooked in to console yet, untested.
    (ticket #347)
2012-10-13 12:45:08 +00:00
zzz
6f509967bf Making FIFOBandwithLimiter.Request unidirectional, static,
remove logging, other cleanups (ticket #719)
2012-10-09 14:15:04 +00:00
zzz
56574c41be propagate from branch 'i2p.i2p' (head cbca70618d2083a5fcdead2390e9d30060080e74)
to branch 'i2p.i2p.zzz.test' (head 1affab2e83613f326d269370de6e5aed40ecae52)
2012-10-09 13:36:32 +00:00
zzz
3cdfc2d33a Split up NTCPConnection's single _bwRequests Set into inbound and outbound,
in prep for making FIFOBandwithLimiter.Request unidirectional
and support priorities (ticket #719)
2012-10-09 13:36:14 +00:00
zzz
20279d1597 propagate from branch 'i2p.i2p' (head 52d5a19210a344e0de43f6fe4d898d34f6c41829)
to branch 'i2p.i2p.zzz.update' (head d88c6abf9b4988ba892e435594cd74917ab9ab7f)
2012-09-25 15:04:49 +00:00
str4d
b464ef0ac3 propagate from branch 'i2p.i2p.unittests' (head 58a62605ce8542f7e5d5daf0c2e171ed0c7e1a74)
to branch 'i2p.i2p.zzz.update' (head 269547972f0e02fe545296823602995465bb0691)
2012-08-08 00:15:55 +00:00
str4d
7f09206a47 Fixed jarScalaTest targets to actually package all classes 2012-08-08 00:07:17 +00:00
str4d
65573eafac Use ScalaTest jars in routerconsole tests 2012-08-07 23:12:37 +00:00
str4d
aab2c0601d propagate from branch 'i2p.i2p.unittests' (head fb681c6fa25bcf9f7287a661b3ce626fd5a280bb)
to branch 'i2p.i2p.zzz.update' (head f3c8cb8ad1d68cc6a66d544f1e287eead786c5ce)
2012-08-07 12:42:48 +00:00
str4d
5355e5bbfd Added targets to build i2p.jar and router.jar with ScalaTest classes included 2012-08-07 11:58:11 +00:00
str4d
07e18c07ac Added ScalaTest support to routerconsole build.xml
To run (once tests exist) execute something like

ant -Dclasspath=/usr/share/java/mockito-core.jar
    -Dscalatest.libs=./lib
    -Dwith.cobertura=/usr/share/java/cobertura.jar
    fulltest
2012-08-05 22:20:19 +00:00
zzz
94d51bd56f log tweaks 2012-08-03 19:58:38 +00:00
zzz
72ed1bc1ac fixups after prop 2012-08-03 18:56:53 +00:00
zzz
4a1b83961d propagate from branch 'i2p.i2p' (head d2198c4bc21a9d06194cdb2dce24945ebc9d1542)
to branch 'i2p.i2p.zzz.update' (head 88ac67dc4e166b7e9dec0d3224e58bec4894440d)
2012-08-03 18:30:39 +00:00
zzz
e62b76d2cc Big refactor of the router console update subsystem, in preparation for
implementing out-of-console updaters like i2psnark.

- Add new update interfaces in net.i2p.update
- All update implementations moved to routerconsole update/
- Implement an UpdateManager that registers with the RouterContext
- UpdateManager handles multiple types of things to update
  (router, plugins, news, ...) and methods of updating (HTTP, ...)
- UpdateManager maintains list of installed, downloaded, and available versions of everything
- Define Updaters that can check for a new version and/or download an item
- Individual Updaters register with the UpdateManager obtained from
  I2PAppContext, identifying the type of update item and
  update method they can handle.
- Updaters need only core libs, no router.jar or routerconsole access required.
- All checks and updates are initiated via the UpdateManager.
- All status on checks and updates in-progress or completed are
  obtained from the UpdateManager. No more use of System properties
  to broadcast update state.
- All update and checker tasks are intantiated on demand and threaded;
  no more static references left over.
- Split out the Runners and Checkers from the Handlers and make the inheritance more sane.
- No more permanent NewsFetcher thread; run on the SimpleScheduler queue
  and thread a checker task only to fetch the news.
- No more static NewsFetcher instance in routerconsole.
  All helper methods that are still required are moved to NewsHelper.

The UpdateManager implements the policy for when to check and download.
All requests go through the UpdateManager.

For each update type, there's several parts:
    - The xxxUpdateHandler implements the Updater
    - The xxxUpdateChecker implements the UpdateTask for checking
    - The xxxUpdateRunner implements the UpdateTask for downloading

New and moved classes:

web/				update/
----				-------
new				ConsoleUpdateManager.java

new				PluginUpdateChecker.java from PluginUpdateChecker
PluginUpdateChecker 		-> PluginUpdateHandler.java
PluginUpdateHandler.java	-> PluginUpdateRunner

new				UnsignedUpdateHandler.java
UnsignedUpdateHandler		->  UnsignedUpdateRunner.java
new				UnsignedUpdateChecker from NewsFetcher

UpdateHandler.java remains
new				UpdateHandler.java
new				UpdateRunner.java from UpdateHandler

move				NewsHandler from NewsFetcher
new				NewsFetcher
new				NewsTimerTask

new				DummyHandler


Initial checkin. Unfinished, untested, unpolished.
2012-06-18 22:09:45 +00:00
zzz
9d91b90d3c propagate from branch 'i2p.i2p' (head d32b82100cf6076e8f3de30b6a0edfbb034caac7)
to branch 'i2p.i2p.zzz.pcap' (head 551957edb05526df88ff3a2b3c717faed4aac906)
2009-12-27 15:07:45 +00:00
zzz
9203663abf propagate from branch 'i2p.i2p' (head 3f19ceea830345f8c34cbccfef3c759d48cd2f7e)
to branch 'i2p.i2p.zzz.pcap' (head fe70e551db5af3ebac9564b4868a28a1ebab7227)
2009-11-23 13:39:27 +00:00
zzz
d078ed396f move init, add config 2009-11-06 19:16:23 +00:00
zzz
7c36c0c8e7 add TCP options block 2009-11-06 16:39:05 +00:00
zzz
404754bc90 streaming lib packet capture first cut 2009-11-06 12:47:21 +00:00
486 changed files with 53586 additions and 107789 deletions

View File

@@ -123,6 +123,7 @@ trans.cs = debian/po/cs.po
trans.de = debian/po/de.po
trans.el = debian/po/el.po
trans.es = debian/po/es.po
trans.fr = debian/po/fr.po
trans.it = debian/po/it.po
trans.hu = debian/po/hu.po
trans.pl = debian/po/pl.po

View File

@@ -11,7 +11,7 @@ you may use:
lynx http://localhost:7657/
to configure the router.
If you're having trouble, swing by http://forum.i2p2.de/, check the
If you're having trouble, swing by http://forum.i2p/, check the
website at http://www.i2p2.de/, or get on irc://irc.freenode.net/#i2p
I2P will create and store files and configuration data in the user directory

View File

@@ -32,7 +32,7 @@ FAQ:
Need help?
IRC irc.freenode.net #i2p
http://forum.i2p2.de/
http://forum.i2p/
Licenses:
See LICENSE.txt

View File

@@ -128,7 +128,7 @@ cat $CWD/slack-desc > $PKG/install/slack-desc
cd $PKG
#
# requiredbuilder fucks up REALLY bad, and thinks java is perl?!
# requiredbuilder messes up REALLY bad, and thinks java is perl?!
# It also did not catch the shell requirements! BOOOOOOOOOOO! HISSSSSSSS!
#
# requiredbuilder -v -y -s $CWD $PKG

View File

@@ -1,17 +1,9 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* WTFPL
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
@@ -19,7 +11,7 @@
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
* ...for any additional details and license questions.
*/
package net.i2p.BOB.Demos.echo.echoclient;
@@ -55,7 +47,7 @@ public class Main {
// exit on anything not legal
break;
}
c = (char)(b & 0x7f); // We only really give a fuck about ASCII
c = (char)(b & 0x7f); // We only care about ASCII
S = new String(S + c);
}
return S;

View File

@@ -1,17 +1,9 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* WTFPL
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
@@ -19,7 +11,7 @@
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
* ...for any additional details and license questions.
*/
package net.i2p.BOB.Demos.echo.echoserver;
@@ -52,7 +44,7 @@ public class Main {
if(b < 20) {
break;
}
c = (char)(b & 0x7f); // We only really give a fuck about ASCII
c = (char)(b & 0x7f); // We only care about ASCII
S = new String(S + c);
}
return S;

View File

@@ -1,17 +1,9 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* WTFPL
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
@@ -19,7 +11,7 @@
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
* ...for any additional details and license questions.
*/
package net.i2p.BOB;

View File

@@ -1,17 +1,9 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* WTFPL
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
@@ -19,7 +11,7 @@
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
* ...for any additional details and license questions.
*/
package net.i2p.BOB;

View File

@@ -1,17 +1,9 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* WTFPL
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
@@ -19,7 +11,7 @@
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
* ...for any additional details and license questions.
*/
package net.i2p.BOB;
@@ -94,7 +86,7 @@ public class I2Plistener implements Runnable {
}
} catch (I2PException e) {
// bad shit
// bad stuff
System.out.println("Exception " + e);
}
} finally {

View File

@@ -1,17 +1,9 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* WTFPL
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
@@ -19,7 +11,7 @@
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
* ...for any additional details and license questions.
*/
package net.i2p.BOB;

View File

@@ -1,17 +1,9 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* WTFPL
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
@@ -19,7 +11,7 @@
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
* ...for any additional details and license questions.
*/
package net.i2p.BOB;

View File

@@ -1,17 +1,9 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* WTFPL
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
@@ -19,7 +11,7 @@
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
* ...for any additional details and license questions.
*/
package net.i2p.BOB;
@@ -302,14 +294,14 @@ public class MUXlisten implements Runnable {
// Hopefully nuke stuff here...
{
String boner = tg.getName();
String groupName = tg.getName();
try {
_log.warn("destroySocketManager " + boner);
_log.warn("destroySocketManager " + groupName);
socketManager.destroySocketManager();
_log.warn("destroySocketManager Successful" + boner);
_log.warn("destroySocketManager Successful" + groupName);
} catch (Exception e) {
// nop
_log.warn("destroySocketManager Failed" + boner);
_log.warn("destroySocketManager Failed" + groupName);
_log.warn(e.toString());
}
}
@@ -333,25 +325,25 @@ public class MUXlisten implements Runnable {
// Wait around till all threads are collected.
if (tg != null) {
String boner = tg.getName();
// System.out.println("BOB: MUXlisten: Starting thread collection for: " + boner);
_log.warn("BOB: MUXlisten: Starting thread collection for: " + boner);
String groupName = tg.getName();
// System.out.println("BOB: MUXlisten: Starting thread collection for: " + groupName);
_log.warn("BOB: MUXlisten: Starting thread collection for: " + groupName);
if (tg.activeCount() + tg.activeGroupCount() != 0) {
// visit(tg, 0, boner);
// visit(tg, 0, groupName);
int foo = tg.activeCount() + tg.activeGroupCount();
// hopefully no longer needed!
// int bar = lives;
// System.out.println("BOB: MUXlisten: Waiting on threads for " + boner);
// System.out.println("\nBOB: MUXlisten: ThreadGroup dump BEGIN " + boner);
// visit(tg, 0, boner);
// System.out.println("BOB: MUXlisten: ThreadGroup dump END " + boner + "\n");
// System.out.println("BOB: MUXlisten: Waiting on threads for " + groupName);
// System.out.println("\nBOB: MUXlisten: ThreadGroup dump BEGIN " + groupName);
// visit(tg, 0, groupName);
// System.out.println("BOB: MUXlisten: ThreadGroup dump END " + groupName + "\n");
// Happily spin forever :-(
while (foo != 0) {
foo = tg.activeCount() + tg.activeGroupCount();
// if (lives != bar && lives != 0) {
// System.out.println("\nBOB: MUXlisten: ThreadGroup dump BEGIN " + boner);
// visit(tg, 0, boner);
// System.out.println("BOB: MUXlisten: ThreadGroup dump END " + boner + "\n");
// System.out.println("\nBOB: MUXlisten: ThreadGroup dump BEGIN " + groupName);
// visit(tg, 0, groupName);
// System.out.println("BOB: MUXlisten: ThreadGroup dump END " + groupName + "\n");
// }
// bar = lives;
try {
@@ -361,8 +353,8 @@ public class MUXlisten implements Runnable {
}
}
}
// System.out.println("BOB: MUXlisten: Threads went away. Success: " + boner);
_log.warn("BOB: MUXlisten: Threads went away. Success: " + boner);
// System.out.println("BOB: MUXlisten: Threads went away. Success: " + groupName);
_log.warn("BOB: MUXlisten: Threads went away. Success: " + groupName);
tg.destroy();
// Zap reference to the ThreadGroup so the JVM can GC it.
tg = null;

View File

@@ -1,17 +1,9 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* WTFPL
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
@@ -19,7 +11,7 @@
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
* ...for any additional details and license questions.
*/
package net.i2p.BOB;

View File

@@ -1,17 +1,9 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* WTFPL
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
@@ -19,7 +11,7 @@
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
* ...for any additional details and license questions.
*/
package net.i2p.BOB;

View File

@@ -1,17 +1,9 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* WTFPL
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
@@ -19,7 +11,7 @@
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
* ...for any additional details and license questions.
*/
package net.i2p.BOB;

View File

@@ -1,17 +1,9 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* WTFPL
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
@@ -19,7 +11,7 @@
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
* ...for any additional details and license questions.
*/
package net.i2p.BOB;

View File

@@ -1,17 +1,9 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* WTFPL
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
@@ -19,7 +11,7 @@
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
* ...for any additional details and license questions.
*/
package net.i2p.BOB;
@@ -94,7 +86,7 @@ public class TCPtoI2P implements Runnable {
// exit on anything not legal
break;
}
c = (char) (b & 0x7f); // We only really give a fuck about ASCII
c = (char) (b & 0x7f); // We only care about ASCII
S = new String(S + c);
}
return S;

View File

@@ -1,17 +1,9 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* WTFPL
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
@@ -19,7 +11,7 @@
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
* ...for any additional details and license questions.
*/
package net.i2p.BOB;

View File

@@ -42,8 +42,8 @@ import javax.servlet.http.HttpServletResponse;
*/
public class Servlet extends HttpServlet {
private DaemonThread thread;
private String nonce;
private static final String PROP_NONCE = "addressbook.nonce";
//private String nonce;
//private static final String PROP_NONCE = "addressbook.nonce";
/**
* Hack to allow susidns to kick the daemon when the subscription list changes.
@@ -54,15 +54,15 @@ public class Servlet extends HttpServlet {
*/
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
//System.err.println("Got request nonce = " + request.getParameter("nonce"));
if (this.thread != null && request.getParameter("wakeup") != null &&
this.nonce != null && this.nonce.equals(request.getParameter("nonce"))) {
//System.err.println("Sending interrupt");
this.thread.interrupt();
// no output
} else {
//if (this.thread != null && request.getParameter("wakeup") != null &&
// this.nonce != null && this.nonce.equals(request.getParameter("nonce"))) {
// //System.err.println("Sending interrupt");
// this.thread.interrupt();
// // no output
//} else {
PrintWriter out = response.getWriter();
out.write("I2P addressbook OK");
}
//}
}
/* (non-Javadoc)
@@ -75,9 +75,9 @@ public class Servlet extends HttpServlet {
} catch (ServletException exp) {
System.err.println("Addressbook init exception: " + exp);
}
this.nonce = "" + Math.abs((new Random()).nextLong());
//this.nonce = "" + Math.abs((new Random()).nextLong());
// put the nonce where susidns can get it
System.setProperty(PROP_NONCE, this.nonce);
//System.setProperty(PROP_NONCE, this.nonce);
String[] args = new String[1];
args[0] = config.getInitParameter("home");
this.thread = new DaemonThread(args);

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry combineaccessrules="false" kind="src" path="/i2p_router"/>
<classpathentry combineaccessrules="false" kind="src" path="/i2p_sdk"/>
<classpathentry kind="lib" path="/lib/wrapper/all/wrapper.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="build"/>
</classpath>

17
apps/desktopgui/.project Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>desktopgui</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -1,106 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="fortuna">
<property name="cvs.base.dir" value="java/gnu-crypto" />
<property name="cvs.etc.dir" value="${cvs.base.dir}/etc" />
<property name="cvs.lib.dir" value="${cvs.base.dir}/lib" />
<property name="cvs.object.dir" value="${cvs.base.dir}/classes" />
<property name="cvs.base.crypto.object.dir" value="${cvs.object.dir}/gnu/crypto" />
<property name="cvs.cipher.object.dir" value="${cvs.base.crypto.object.dir}/cipher" />
<property name="cvs.hash.object.dir" value="${cvs.base.crypto.object.dir}/hash" />
<property name="cvs.prng.object.dir" value="${cvs.base.crypto.object.dir}/prng" />
<patternset id="fortuna.files">
<include name="${cvs.base.crypto.object.dir}/Registry.class"/>
<include name="${cvs.prng.object.dir}/Fortuna*.class"/>
<include name="${cvs.prng.object.dir}/BasePRNG.class"/>
<include name="${cvs.prng.object.dir}/RandomEventListener.class"/>
<include name="${cvs.prng.object.dir}/IRandom.class"/>
<include name="${cvs.cipher.object.dir}/CipherFactory.class"/>
<include name="${cvs.cipher.object.dir}/IBlockCipher.class"/>
<include name="${cvs.hash.object.dir}/HashFactory.class"/>
<include name="${cvs.hash.object.dir}/IMessageDigest.class"/>
</patternset>
<target name="all" depends="build,jar"
description="Create and test the custom Fortuna library" />
<target name="build" depends="-init,checkout"
description="Build the source and tests">
<ant dir="${cvs.base.dir}" target="jar" />
</target>
<target name="builddep" />
<target name="checkout" depends="-init" unless="cvs.source.available"
description="Check out GNU Crypto sources from CVS HEAD">
<cvs cvsRoot=":ext:anoncvs@savannah.gnu.org:/cvsroot/gnu-crypto"
cvsRsh="ssh"
dest="java"
package="gnu-crypto" />
</target>
<target name="clean"
description="Remove generated tests and object files">
<ant dir="${cvs.base.dir}" target="clean" />
</target>
<target name="cleandep" />
<target name="compile" />
<target name="distclean" depends="clean"
description="Remove all generated files">
<delete dir="build" />
<delete dir="jartemp" />
<!--
Annoyingly the GNU Crypto distclean task called here doesn't clean
*all* derived files from java/gnu-crypto/lib like it should.....
-->
<ant dir="${cvs.base.dir}" target="distclean" />
<!--
.....and so we mop up the rest ourselves.
-->
<delete dir="${cvs.lib.dir}" />
</target>
<target name="-init">
<available property="cvs.source.available" file="${cvs.base.dir}" />
</target>
<target name="jar" depends="build"
description="Create the custom Fortuna jar library">
<delete dir="build" />
<delete dir="jartemp" />
<mkdir dir="build" />
<mkdir dir="jartemp/${cvs.object.dir}" />
<copy todir="jartemp">
<fileset dir=".">
<patternset refid="fortuna.files" />
</fileset>
</copy>
<jar basedir="jartemp/${cvs.object.dir}" jarfile="build/fortuna.jar">
<manifest>
<section name="fortuna">
<attribute name="Implementation-Title" value="I2P Custom GNU Crypto Fortuna Library" />
<attribute name="Implementation-Version" value="CVS HEAD" />
<attribute name="Implementation-Vendor" value="Free Software Foundation" />
<attribute name="Implementation-Vendor-Id" value="FSF" />
<attribute name="Implementation-URL" value="http://www.gnu.org/software/gnu-crypto" />
</section>
</manifest>
</jar>
<delete dir="jartemp" />
</target>
<target name="test" depends="jar"
description="Perform crypto tests on custom Fortuna jar library" />
<!--
Add this when Fortuna tests are added to GNU Crypto, else write some
-->
<target name="update" depends="checkout"
description="Update GNU Crypto sources to latest CVS HEAD">
<cvs command="update -d" cvsRsh="ssh" dest="java/gnu-crypto" />
</target>
</project>

11
apps/i2psnark/.classpath Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="java/src"/>
<classpathentry combineaccessrules="false" kind="src" path="/i2p_sdk"/>
<classpathentry combineaccessrules="false" kind="src" path="/ministreaming"/>
<classpathentry kind="lib" path="/jetty/jettylib/javax.servlet.jar"/>
<classpathentry kind="lib" path="/jetty/jettylib/jetty-util.jar"/>
<classpathentry kind="lib" path="/jetty/jettylib/org.mortbay.jetty.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="java/build/obj"/>
</classpath>

17
apps/i2psnark/.project Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>i2psnark</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -80,7 +80,7 @@
<isset property="jar.uptodate" />
</not>
<not>
<isset property="wjar.uptodate" />
<isset property="war.uptodate" />
</not>
<isset property="mtn.available" />
</and>

View File

@@ -209,10 +209,10 @@ public class KBucketSet<T extends SimpleDataStructure> {
int s1, e1, s2, e2;
s1 = b0.getRangeBegin();
e2 = b0.getRangeEnd();
if (B_FACTOR > 1 &&
(s1 & (B_FACTOR - 1)) == 0 &&
((e2 + 1) & (B_FACTOR - 1)) == 0 &&
e2 > s1 + B_FACTOR) {
if (B_VALUE == 1 ||
((s1 & (B_FACTOR - 1)) == 0 &&
((e2 + 1) & (B_FACTOR - 1)) == 0 &&
e2 > s1 + B_FACTOR)) {
// The bucket is a "whole" kbucket with a range > B_FACTOR,
// so it should be split into two "whole" kbuckets each with
// a range >= B_FACTOR.
@@ -529,7 +529,10 @@ public class KBucketSet<T extends SimpleDataStructure> {
getReadLock();
try {
for (KBucket b : _buckets) {
if (b.getLastChanged() < old || b.getKeyCount() < BUCKET_SIZE * 3 / 4)
int curSize = b.getKeyCount();
// Always explore the closest bucket
if ((b.getRangeBegin() == 0) ||
(b.getLastChanged() < old || curSize < BUCKET_SIZE * 3 / 4))
rv.add(generateRandomKey(b));
}
} finally { releaseReadLock(); }
@@ -759,8 +762,8 @@ public class KBucketSet<T extends SimpleDataStructure> {
public String toString() {
StringBuilder buf = new StringBuilder(1024);
buf.append("Bucket set rooted on: ").append(_us.toString())
.append(" K= ").append(BUCKET_SIZE)
.append(" B= ").append(B_VALUE)
.append(" K=").append(BUCKET_SIZE)
.append(" B=").append(B_VALUE)
.append(" with ").append(size())
.append(" keys in ").append(_buckets.size()).append(" buckets:\n");
getReadLock();

View File

@@ -59,7 +59,6 @@ public class BitField
// cleared or clear them explicitly ourselves.
System.arraycopy(bitfield, 0, this.bitfield, 0, arraysize);
this.count = 0;
for (int i = 0; i < size; i++)
if (get(i))
this.count++;

View File

@@ -0,0 +1,60 @@
/* CompleteListener - Callback for Snark events
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
/**
* Callback for Snark events.
* @since 0.9.4 moved from Snark.java
*/
public interface CompleteListener {
public void torrentComplete(Snark snark);
public void updateStatus(Snark snark);
/**
* We transitioned from magnet mode, we have now initialized our
* metainfo and storage. The listener should now call getMetaInfo()
* and save the data to disk.
*
* @return the new name for the torrent or null on error
* @since 0.8.4
*/
public String gotMetaInfo(Snark snark);
/**
* @since 0.9
*/
public void fatal(Snark snark, String error);
/**
* @since 0.9.2
*/
public void addMessage(Snark snark, String message);
/**
* @since 0.9.4
*/
public void gotPiece(Snark snark);
// not really listeners but the easiest way to get back to an optional SnarkManager
public long getSavedTorrentTime(Snark snark);
public BitField getSavedTorrentBitField(Snark snark);
}

View File

@@ -1,5 +1,6 @@
package org.klomp.snark;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -58,7 +59,7 @@ public class I2PSnarkUtil {
private volatile I2PSocketManager _manager;
private boolean _configured;
private volatile boolean _connecting;
private final Set<Hash> _shitlist;
private final Set<Hash> _banlist;
private int _maxUploaders;
private int _maxUpBW;
private int _maxConnections;
@@ -86,7 +87,7 @@ public class I2PSnarkUtil {
_opts = new HashMap();
//setProxy("127.0.0.1", 4444);
setI2CPConfig("127.0.0.1", 7654, null);
_shitlist = new ConcurrentHashSet();
_banlist = new ConcurrentHashSet();
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
_maxUpBW = DEFAULT_MAX_UP_BW;
_maxConnections = MAX_CONNECTIONS;
@@ -218,6 +219,8 @@ public class I2PSnarkUtil {
opts.setProperty("inbound.nickname", "I2PSnark");
if (opts.getProperty("outbound.nickname") == null)
opts.setProperty("outbound.nickname", "I2PSnark");
if (opts.getProperty("outbound.priority") == null)
opts.setProperty("outbound.priority", "-10");
// Dont do this for now, it is set in I2PSocketEepGet for announces,
// we don't need fast handshake for peer connections.
//if (opts.getProperty("i2p.streaming.connectDelay") == null)
@@ -244,6 +247,8 @@ public class I2PSnarkUtil {
opts.setProperty("i2p.streaming.maxConnsPerHour", "20");
if (opts.getProperty("i2p.streaming.enforceProtocol") == null)
opts.setProperty("i2p.streaming.enforceProtocol", "true");
if (opts.getProperty("i2p.streaming.disableRejectLogging") == null)
opts.setProperty("i2p.streaming.disableRejectLogging", "true");
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
_connecting = false;
}
@@ -283,7 +288,7 @@ public class I2PSnarkUtil {
I2PSocketManager mgr = _manager;
// FIXME this can cause race NPEs elsewhere
_manager = null;
_shitlist.clear();
_banlist.clear();
if (mgr != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Disconnecting from I2P", new Exception("I did it"));
@@ -306,24 +311,24 @@ public class I2PSnarkUtil {
if (addr.equals(getMyDestination()))
throw new IOException("Attempt to connect to myself");
Hash dest = addr.calculateHash();
if (_shitlist.contains(dest))
throw new IOException("Not trying to contact " + dest.toBase64() + ", as they are shitlisted");
if (_banlist.contains(dest))
throw new IOException("Not trying to contact " + dest.toBase64() + ", as they are banlisted");
try {
I2PSocket rv = _manager.connect(addr);
if (rv != null)
_shitlist.remove(dest);
_banlist.remove(dest);
return rv;
} catch (I2PException ie) {
_shitlist.add(dest);
_context.simpleScheduler().addEvent(new Unshitlist(dest), 10*60*1000);
_banlist.add(dest);
_context.simpleScheduler().addEvent(new Unbanlist(dest), 10*60*1000);
throw new IOException("Unable to reach the peer " + peer + ": " + ie.getMessage());
}
}
private class Unshitlist implements SimpleTimer.TimedEvent {
private class Unbanlist implements SimpleTimer.TimedEvent {
private Hash _dest;
public Unshitlist(Hash dest) { _dest = dest; }
public void timeReached() { _shitlist.remove(_dest); }
public Unbanlist(Hash dest) { _dest = dest; }
public void timeReached() { _banlist.remove(_dest); }
}
/**
@@ -391,6 +396,46 @@ public class I2PSnarkUtil {
}
}
/**
* Fetch to memory
* @param retries if < 0, set timeout to a few seconds
* @param initialSize buffer size
* @param maxSize fails if greater
* @return null on error
* @since 0.9.4
*/
public byte[] get(String url, boolean rewrite, int retries, int initialSize, int maxSize) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fetching [" + url + "] to memory");
String fetchURL = url;
if (rewrite)
fetchURL = rewriteAnnounce(url);
int timeout;
if (retries < 0) {
if (!connected())
return null;
timeout = EEPGET_CONNECT_TIMEOUT_SHORT;
retries = 0;
} else {
timeout = EEPGET_CONNECT_TIMEOUT;
if (!connected()) {
if (!connect())
return null;
}
}
ByteArrayOutputStream out = new ByteArrayOutputStream(initialSize);
EepGet get = new I2PSocketEepGet(_context, _manager, retries, -1, maxSize, null, out, fetchURL);
if (get.fetch(timeout)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fetch successful [" + url + "]: size=" + out.size());
return out.toByteArray();
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Fetch failed [" + url + "]");
return null;
}
}
public I2PServerSocket getServerSocket() {
I2PSocketManager mgr = _manager;
if (mgr != null)
@@ -521,6 +566,15 @@ public class I2PSnarkUtil {
return Collections.EMPTY_LIST;
return _openTrackers;
}
/**
* List of open trackers to use as backups even if disabled
* @return non-null
* @since 0.9.4
*/
public List<String> getBackupTrackers() {
return _openTrackers;
}
public void setUseOpenTrackers(boolean yes) {
_shouldUseOT = yes;

View File

@@ -0,0 +1,212 @@
package org.klomp.snark;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import net.i2p.data.Base32;
/**
*
* @since 0.9.4 moved from I2PSnarkServlet
*/
public class MagnetURI {
private final String _tracker;
private final String _name;
private final byte[] _ih;
/** BEP 9 */
public static final String MAGNET = "magnet:";
public static final String MAGNET_FULL = MAGNET + "?xt=urn:btih:";
/** http://sponge.i2p/files/maggotspec.txt */
public static final String MAGGOT = "maggot://";
/**
* @param url non-null
*/
public MagnetURI(I2PSnarkUtil util, String url) throws IllegalArgumentException {
String ihash;
String name;
String trackerURL = null;
if (url.startsWith(MAGNET)) {
// magnet:?xt=urn:btih:0691e40aae02e552cfcb57af1dca56214680c0c5&tr=http://tracker2.postman.i2p/announce.php
String xt = getParam("xt", url);
if (xt == null || !xt.startsWith("urn:btih:"))
throw new IllegalArgumentException();
ihash = xt.substring("urn:btih:".length());
trackerURL = getTrackerParam(url);
name = util.getString("Magnet") + ' ' + ihash;
String dn = getParam("dn", url);
if (dn != null)
name += " (" + Storage.filterName(dn) + ')';
} else if (url.startsWith(MAGGOT)) {
// maggot://0691e40aae02e552cfcb57af1dca56214680c0c5:0b557bbdf8718e95d352fbe994dec3a383e2ede7
ihash = url.substring(MAGGOT.length()).trim();
int col = ihash.indexOf(':');
if (col >= 0)
ihash = ihash.substring(0, col);
name = util.getString("Magnet") + ' ' + ihash;
} else {
throw new IllegalArgumentException();
}
byte[] ih = null;
if (ihash.length() == 32) {
ih = Base32.decode(ihash);
} else if (ihash.length() == 40) {
// Like DataHelper.fromHexString() but ensures no loss of leading zero bytes
ih = new byte[20];
try {
for (int i = 0; i < 20; i++) {
ih[i] = (byte) (Integer.parseInt(ihash.substring(i*2, (i*2) + 2), 16) & 0xff);
}
} catch (NumberFormatException nfe) {
ih = null;
}
}
if (ih == null || ih.length != 20)
throw new IllegalArgumentException();
_ih = ih;
_name = name;
_tracker = trackerURL;
}
/**
* @return 20 bytes or null
*/
public byte[] getInfoHash() {
return _ih;
}
/**
* @return pretty name or null
*/
public String getName() {
return _name;
}
/**
* @return tracker url or null
*/
public String getTrackerURL() {
return _tracker;
}
/**
* @return first decoded parameter or null
*/
private static String getParam(String key, String uri) {
int idx = uri.indexOf('?' + key + '=');
if (idx >= 0) {
idx += key.length() + 2;
} else {
idx = uri.indexOf('&' + key + '=');
if (idx >= 0)
idx += key.length() + 2;
}
if (idx < 0 || idx > uri.length())
return null;
String rv = uri.substring(idx);
idx = rv.indexOf('&');
if (idx >= 0)
rv = rv.substring(0, idx);
else
rv = rv.trim();
return decode(rv);
}
/**
* @return all decoded parameters or null
* @since 0.9.1
*/
private static List<String> getMultiParam(String key, String uri) {
int idx = uri.indexOf('?' + key + '=');
if (idx >= 0) {
idx += key.length() + 2;
} else {
idx = uri.indexOf('&' + key + '=');
if (idx >= 0)
idx += key.length() + 2;
}
if (idx < 0 || idx > uri.length())
return null;
List<String> rv = new ArrayList();
while (true) {
String p = uri.substring(idx);
uri = p;
idx = p.indexOf('&');
if (idx >= 0)
p = p.substring(0, idx);
else
p = p.trim();
rv.add(decode(p));
idx = uri.indexOf('&' + key + '=');
if (idx < 0)
break;
idx += key.length() + 2;
}
return rv;
}
/**
* @return first valid I2P tracker or null
* @since 0.9.1
*/
private static String getTrackerParam(String uri) {
List<String> trackers = getMultiParam("tr", uri);
if (trackers == null)
return null;
for (String t : trackers) {
try {
URI u = new URI(t);
String protocol = u.getScheme();
String host = u.getHost();
if (protocol == null || host == null ||
!protocol.toLowerCase(Locale.US).equals("http") ||
!host.toLowerCase(Locale.US).endsWith(".i2p"))
continue;
return t;
} catch(URISyntaxException use) {}
}
return null;
}
/**
* Decode %xx encoding, convert to UTF-8 if necessary
* Copied from i2ptunnel LocalHTTPServer
* @since 0.9.1
*/
private static String decode(String s) {
if (!s.contains("%"))
return s;
StringBuilder buf = new StringBuilder(s.length());
boolean utf8 = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c != '%') {
buf.append(c);
} else {
try {
int val = Integer.parseInt(s.substring(++i, (++i) + 1), 16);
if ((val & 0x80) != 0)
utf8 = true;
buf.append((char) val);
} catch (IndexOutOfBoundsException ioobe) {
break;
} catch (NumberFormatException nfe) {
break;
}
}
}
if (utf8) {
try {
return new String(buf.toString().getBytes("ISO-8859-1"), "UTF-8");
} catch (UnsupportedEncodingException uee) {}
}
return buf.toString();
}
}

View File

@@ -377,7 +377,10 @@ class PeerCoordinator implements PeerListener
public boolean needOutboundPeers() {
//return wantedBytes != 0 && needPeers();
// minus one to make it a little easier for new peers to get in on large swarms
return wantedBytes != 0 && !halted && peers.size() < getMaxConnections() - 1;
return wantedBytes != 0 &&
!halted &&
peers.size() < getMaxConnections() - 1 &&
(storage == null || !storage.isChecking());
}
/**
@@ -515,8 +518,8 @@ class PeerCoordinator implements PeerListener
peerCount = peers.size();
unchokePeer();
if (listener != null)
listener.peerChange(this, peer);
//if (listener != null)
// listener.peerChange(this, peer);
}
}
if (toDisconnect != null) {
@@ -652,8 +655,8 @@ class PeerCoordinator implements PeerListener
*/
public boolean gotHave(Peer peer, int piece)
{
if (listener != null)
listener.peerChange(this, peer);
//if (listener != null)
// listener.peerChange(this, peer);
synchronized(wantedPieces) {
for (Piece pc : wantedPieces) {
@@ -672,8 +675,8 @@ class PeerCoordinator implements PeerListener
*/
public boolean gotBitField(Peer peer, BitField bitfield)
{
if (listener != null)
listener.peerChange(this, peer);
//if (listener != null)
// listener.peerChange(this, peer);
boolean rv = false;
synchronized(wantedPieces) {
@@ -931,8 +934,8 @@ class PeerCoordinator implements PeerListener
{
uploaded += size;
if (listener != null)
listener.peerChange(this, peer);
//if (listener != null)
// listener.peerChange(this, peer);
}
/**
@@ -942,8 +945,8 @@ class PeerCoordinator implements PeerListener
{
downloaded += size;
if (listener != null)
listener.peerChange(this, peer);
//if (listener != null)
// listener.peerChange(this, peer);
}
/**
@@ -955,16 +958,11 @@ class PeerCoordinator implements PeerListener
*/
public boolean gotPiece(Peer peer, PartialPiece pp)
{
if (metainfo == null || storage == null) {
if (metainfo == null || storage == null || storage.isChecking() || halted) {
pp.release();
return true;
}
int piece = pp.getPiece();
if (halted) {
_log.info("Got while-halted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
pp.release();
return true; // We don't actually care anymore.
}
synchronized(wantedPieces)
{
@@ -985,6 +983,7 @@ class PeerCoordinator implements PeerListener
try
{
// this takes forever if complete, as it rechecks
if (storage.putPiece(pp))
{
if (_log.shouldLog(Log.INFO))
@@ -1057,8 +1056,8 @@ class PeerCoordinator implements PeerListener
if (_log.shouldLog(Log.INFO))
_log.info("Got choke(" + choke + "): " + peer);
if (listener != null)
listener.peerChange(this, peer);
//if (listener != null)
// listener.peerChange(this, peer);
}
public void gotInterest(Peer peer, boolean interest)
@@ -1077,8 +1076,8 @@ class PeerCoordinator implements PeerListener
}
}
if (listener != null)
listener.peerChange(this, peer);
//if (listener != null)
// listener.peerChange(this, peer);
}
public void disconnected(Peer peer)
@@ -1098,8 +1097,8 @@ class PeerCoordinator implements PeerListener
peerCount = peers.size();
}
if (listener != null)
listener.peerChange(this, peer);
//if (listener != null)
// listener.peerChange(this, peer);
}
/** Called when a peer is removed, to prevent it from being used in
@@ -1190,6 +1189,8 @@ class PeerCoordinator implements PeerListener
public PartialPiece getPartialPiece(Peer peer, BitField havePieces) {
if (metainfo == null)
return null;
if (storage != null && storage.isChecking())
return null;
synchronized(wantedPieces) {
// sorts by remaining bytes, least first
Collections.sort(partialPieces);
@@ -1438,6 +1439,7 @@ class PeerCoordinator implements PeerListener
/**
* Called by TrackerClient
* @return the Set itself, modifiable, not a copy, caller should clear()
* @since 0.8.4
*/
Set<PeerID> getPEXPeers() {

View File

@@ -356,22 +356,21 @@ class PeerState implements DataLoader
+ piece + "," + begin + "," + length + ") from "
+ peer);
int r = getFirstOutstandingRequest(piece);
// Unrequested piece number?
if (r == -1)
{
if (_log.shouldLog(Log.INFO))
_log.info("Unrequested 'piece: " + piece + ", "
+ begin + ", " + length + "' received from "
+ peer);
return null;
}
// Lookup the correct piece chunk request from the list.
Request req;
synchronized(this)
{
int r = getFirstOutstandingRequest(piece);
// Unrequested piece number?
if (r == -1) {
if (_log.shouldLog(Log.INFO))
_log.info("Unrequested 'piece: " + piece + ", "
+ begin + ", " + length + "' received from "
+ peer);
return null;
}
req = outstandingRequests.get(r);
while (req.getPiece() == piece && req.off != begin
&& r < outstandingRequests.size() - 1)

View File

@@ -1115,7 +1115,12 @@ public class Snark
}
}
///////////// Begin StorageListener methods
//private boolean allocating = false;
/** does nothing */
public void storageCreateFile(Storage storage, String name, long length)
{
//if (allocating)
@@ -1129,6 +1134,7 @@ public class Snark
// How much storage space has been allocated
private long allocated = 0;
/** does nothing */
public void storageAllocated(Storage storage, long length)
{
//allocating = true;
@@ -1140,7 +1146,8 @@ public class Snark
private boolean allChecked = false;
private boolean checking = false;
private boolean prechecking = true;
//private boolean prechecking = true;
public void storageChecked(Storage storage, int num, boolean checked)
{
//allocating = false;
@@ -1158,6 +1165,8 @@ public class Snark
if (!checking) {
if (_log.shouldLog(Log.INFO))
_log.info("Got " + (checked ? "" : "BAD ") + "piece: " + num);
if (completeListener != null)
completeListener.gotPiece(this);
}
}
@@ -1187,6 +1196,9 @@ public class Snark
coordinator.setWantedPieces();
}
///////////// End StorageListener methods
/** SnarkSnutdown callback unused */
public void shutdown()
{
@@ -1204,34 +1216,6 @@ public class Snark
completeListener.addMessage(this, message);
}
public interface CompleteListener {
public void torrentComplete(Snark snark);
public void updateStatus(Snark snark);
/**
* We transitioned from magnet mode, we have now initialized our
* metainfo and storage. The listener should now call getMetaInfo()
* and save the data to disk.
*
* @return the new name for the torrent or null on error
* @since 0.8.4
*/
public String gotMetaInfo(Snark snark);
/**
* @since 0.9
*/
public void fatal(Snark snark, String error);
/**
* @since 0.9.2
*/
public void addMessage(Snark snark, String message);
// not really listeners but the easiest way to get back to an optional SnarkManager
public long getSavedTorrentTime(Snark snark);
public BitField getSavedTorrentBitField(Snark snark);
}
/** Maintain a configurable total uploader cap
* coordinatorListener

View File

@@ -27,6 +27,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.update.*;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.FileUtil;
import net.i2p.util.I2PAppThread;
@@ -42,7 +43,7 @@ import org.klomp.snark.dht.DHT;
/**
* Manage multiple snarks
*/
public class SnarkManager implements Snark.CompleteListener {
public class SnarkManager implements CompleteListener {
/**
* Map of (canonical) filename of the .torrent file to Snark instance.
@@ -65,6 +66,8 @@ public class SnarkManager implements Snark.CompleteListener {
private volatile boolean _running;
private volatile boolean _stopping;
private final Map<String, Tracker> _trackerMap;
private UpdateManager _umgr;
private UpdateHandler _uhandler;
public static final String PROP_I2CP_HOST = "i2psnark.i2cpHost";
public static final String PROP_I2CP_PORT = "i2psnark.i2cpPort";
@@ -149,10 +152,28 @@ public class SnarkManager implements Snark.CompleteListener {
_connectionAcceptor = new ConnectionAcceptor(_util);
_monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor", true);
_monitor.start();
// delay until UpdateManager is there
_context.simpleScheduler().addEvent(new Register(), 4*60*1000);
// Not required, Jetty has a shutdown hook
//_context.addShutdownTask(new SnarkManagerShutdown());
}
/** @since 0.9.4 */
private class Register implements SimpleTimer.TimedEvent {
public void timeReached() {
if (!_running)
return;
_umgr = _context.updateManager();
if (_umgr != null) {
_uhandler = new UpdateHandler(_context, _umgr, SnarkManager.this);
_umgr.register(_uhandler, UpdateType.ROUTER_SIGNED, UpdateMethod.TORRENT, 10);
_log.warn("Registering with update manager");
} else {
_log.warn("No update manager to register with");
}
}
}
/*
* Called by the webapp at Jetty shutdown.
* Stops all torrents. Does not close the tunnel, so the announces have a chance.
@@ -160,6 +181,10 @@ public class SnarkManager implements Snark.CompleteListener {
* Runs inline.
*/
public void stop() {
if (_umgr != null && _uhandler != null) {
//_uhandler.shutdown();
_umgr.unregister(_uhandler, UpdateType.ROUTER_SIGNED, UpdateMethod.TORRENT);
}
_running = false;
_monitor.interrupt();
_connectionAcceptor.halt();
@@ -723,6 +748,14 @@ public class SnarkManager implements Snark.CompleteListener {
*/
public Snark getTorrent(String filename) { synchronized (_snarks) { return _snarks.get(filename); } }
/**
* Unmodifiable
* @since 0.9.4
*/
public Collection<Snark> getTorrents() {
return Collections.unmodifiableCollection(_snarks.values());
}
/**
* Grab the torrent given the base name of the storage
* @return Snark or null
@@ -889,7 +922,27 @@ public class SnarkManager implements Snark.CompleteListener {
* @since 0.8.4
*/
public void addMagnet(String name, byte[] ih, String trackerURL, boolean updateStatus) {
Snark torrent = new Snark(_util, name, ih, trackerURL, this,
addMagnet(name, ih, trackerURL, updateStatus, shouldAutoStart(), this);
}
/**
* Add a torrent with the info hash alone (magnet / maggot)
* External use is for UpdateRunner.
*
* @param name hex or b32 name from the magnet link
* @param ih 20 byte info hash
* @param trackerURL may be null
* @param updateStatus should we add this magnet to the config file,
* to save it across restarts, in case we don't get
* the metadata before shutdown?
* @param listener to intercept callbacks, should pass through to this
* @return the new Snark or null on failure
* @throws RuntimeException via Snark.fatal()
* @since 0.9.4
*/
public Snark addMagnet(String name, byte[] ih, String trackerURL, boolean updateStatus,
boolean autoStart, CompleteListener listener) {
Snark torrent = new Snark(_util, name, ih, trackerURL, listener,
_peerCoordinatorSet, _connectionAcceptor,
false, getDataDir().getPath());
@@ -897,7 +950,7 @@ public class SnarkManager implements Snark.CompleteListener {
Snark snark = getTorrentByInfoHash(ih);
if (snark != null) {
addMessage(_("Torrent with this info hash is already running: {0}", snark.getBaseName()));
return;
return null;
}
// Tell the dir monitor not to delete us
_magnets.add(name);
@@ -905,8 +958,8 @@ public class SnarkManager implements Snark.CompleteListener {
saveMagnetStatus(ih);
_snarks.put(name, torrent);
}
if (shouldAutoStart()) {
torrent.startTorrent();
if (autoStart) {
startTorrent(ih);
addMessage(_("Fetching {0}", name));
DHT dht = _util.getDHT();
boolean shouldWarn = _util.connected() &&
@@ -918,7 +971,8 @@ public class SnarkManager implements Snark.CompleteListener {
}
} else {
addMessage(_("Adding {0}", name));
}
}
return torrent;
}
/**
@@ -1471,6 +1525,12 @@ public class SnarkManager implements Snark.CompleteListener {
addMessage(message);
}
/**
* A Snark.CompleteListener method.
* @since 0.9.4
*/
public void gotPiece(Snark snark) {}
// End Snark.CompleteListeners
/**

View File

@@ -21,6 +21,7 @@
package org.klomp.snark;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
@@ -34,8 +35,10 @@ import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import net.i2p.I2PAppContext;
import net.i2p.crypto.SHA1;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
import net.i2p.util.ByteCache;
import net.i2p.util.Log;
import net.i2p.util.SecureFile;
@@ -982,6 +985,8 @@ public class Storage
/**
* Put the piece in the Storage if it is correct.
* Warning - takes a LONG time if complete as it does the recheck here.
* TODO thread the recheck?
*
* @return true if the piece was correct (sha metainfo hash
* matches), otherwise false.
@@ -999,9 +1004,9 @@ public class Storage
// TODO alternative - check hash on the fly as we write to the file,
// to save another I/O pass
boolean correctHash = metainfo.checkPiece(pp);
if (listener != null)
listener.storageChecked(this, piece, correctHash);
if (!correctHash) {
if (listener != null)
listener.storageChecked(this, piece, false);
return false;
}
@@ -1063,6 +1068,9 @@ public class Storage
complete = needed == 0;
}
}
// tell listener after counts are updated
if (listener != null)
listener.storageChecked(this, piece, true);
if (complete) {
// do we also need to close all of the files and reopen
@@ -1200,4 +1208,43 @@ public class Storage
rafs[i] = null;
}
/**
* Create a metainfo.
* Used in the installer build process; do not comment out.
* @since 0.9.4
*/
public static void main(String[] args) {
if (args.length < 1 || args.length > 2) {
System.err.println("Usage: Storage file-or-dir [announceURL]");
System.exit(1);
}
File base = new File(args[0]);
String announce = args.length == 2 ? args[1] : null;
I2PAppContext ctx = I2PAppContext.getGlobalContext();
I2PSnarkUtil util = new I2PSnarkUtil(ctx);
File file = null;
FileOutputStream out = null;
try {
Storage storage = new Storage(util, base, announce, false, null);
MetaInfo meta = storage.getMetaInfo();
file = new File(storage.getBaseName() + ".torrent");
out = new FileOutputStream(file);
out.write(meta.getTorrentData());
String hex = DataHelper.toString(meta.getInfoHash());
System.out.println("Created: " + file);
System.out.println("InfoHash: " + hex);
String basename = base.getName().replace(" ", "%20");
String magnet = MagnetURI.MAGNET_FULL + hex + "&dn=" + basename;
if (announce != null)
magnet += "&tr=" + announce;
System.out.println("Magnet: " + magnet);
} catch (IOException ioe) {
if (file != null)
file.delete();
ioe.printStackTrace();
System.exit(1);
} finally {
try { if (out != null) out.close(); } catch (IOException ioe) {}
}
}
}

View File

@@ -20,6 +20,7 @@
package org.klomp.snark;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -43,6 +44,7 @@ import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
import org.klomp.snark.bencode.InvalidBEncodingException;
import org.klomp.snark.dht.DHT;
/**
@@ -70,6 +72,8 @@ public class TrackerClient implements Runnable {
private static final String COMPLETED_EVENT = "completed";
private static final String STOPPED_EVENT = "stopped";
private static final String NOT_REGISTERED = "torrent not registered"; //bytemonsoon
/** this is our equivalent to router.utorrent.com for bootstrap */
private static final String DEFAULT_BACKUP_TRACKER = "http://tracker.welterde.i2p/a";
private final static int SLEEP = 5; // 5 minutes.
private final static int DELAY_MIN = 2000; // 2 secs.
@@ -78,7 +82,7 @@ public class TrackerClient implements Runnable {
private final static int INITIAL_SLEEP = 90*1000;
private final static int MAX_CONSEC_FAILS = 5; // slow down after this
private final static int LONG_SLEEP = 30*60*1000; // sleep a while after lots of fails
private final static long MIN_TRACKER_ANNOUNCE_INTERVAL = 10*60*1000;
private final static long MIN_TRACKER_ANNOUNCE_INTERVAL = 15*60*1000;
private final static long MIN_DHT_ANNOUNCE_INTERVAL = 10*60*1000;
private final I2PSnarkUtil _util;
@@ -106,6 +110,7 @@ public class TrackerClient implements Runnable {
private volatile boolean _fastUnannounce;
private long lastDHTAnnounce;
private final List<Tracker> trackers;
private final List<Tracker> backupTrackers;
/**
* Call start() to start it.
@@ -131,6 +136,7 @@ public class TrackerClient implements Runnable {
this.infoHash = urlencode(snark.getInfoHash());
this.peerID = urlencode(snark.getID());
this.trackers = new ArrayList(2);
this.backupTrackers = new ArrayList(2);
}
public synchronized void start() {
@@ -233,7 +239,7 @@ public class TrackerClient implements Runnable {
if (!_initialized) {
_initialized = true;
// FIXME only when starting everybody at once, not for a single torrent
long delay = I2PAppContext.getGlobalContext().random().nextInt(30*1000);
long delay = _util.getContext().random().nextInt(30*1000);
try {
Thread.sleep(delay);
} catch (InterruptedException ie) {}
@@ -267,18 +273,20 @@ public class TrackerClient implements Runnable {
if (primary != null) {
if (isValidAnnounce(primary)) {
trackers.add(new Tracker(primary, true));
_log.debug("Announce: [" + primary + "] infoHash: " + infoHash);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Announce: [" + primary + "] infoHash: " + infoHash);
} else {
_log.warn("Skipping invalid or non-i2p announce: " + primary);
if (_log.shouldLog(Log.WARN))
_log.warn("Skipping invalid or non-i2p announce: " + primary);
}
} else {
_log.warn("No primary announce");
primary = "";
}
List tlist = _util.getOpenTrackers();
if (tlist != null && (meta == null || !meta.isPrivate())) {
if (meta == null || !meta.isPrivate()) {
List<String> tlist = _util.getOpenTrackers();
for (int i = 0; i < tlist.size(); i++) {
String url = (String)tlist.get(i);
String url = tlist.get(i);
if (!isValidAnnounce(url)) {
_log.error("Bad announce URL: [" + url + "]");
continue;
@@ -301,9 +309,37 @@ public class TrackerClient implements Runnable {
continue;
// opentrackers are primary if we don't have primary
trackers.add(new Tracker(url, primary.equals("")));
_log.debug("Additional announce: [" + url + "] for infoHash: " + infoHash);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Additional announce: [" + url + "] for infoHash: " + infoHash);
}
}
// backup trackers if DHT needs bootstrapping
if (trackers.isEmpty() && (meta == null || !meta.isPrivate())) {
List<String> tlist = _util.getBackupTrackers();
for (int i = 0; i < tlist.size(); i++) {
String url = tlist.get(i);
if (!isValidAnnounce(url)) {
_log.error("Bad announce URL: [" + url + "]");
continue;
}
int slash = url.indexOf('/', 7);
if (slash <= 7) {
_log.error("Bad announce URL: [" + url + "]");
continue;
}
String dest = _util.lookup(url.substring(7, slash));
if (dest == null) {
_log.error("Announce host unknown: [" + url.substring(7, slash) + "]");
continue;
}
backupTrackers.add(new Tracker(url, false));
if (_log.shouldLog(Log.DEBUG))
_log.debug("Backup announce: [" + url + "] for infoHash: " + infoHash);
}
if (backupTrackers.isEmpty())
backupTrackers.add(new Tracker(DEFAULT_BACKUP_TRACKER, false));
}
this.completed = coordinator.getLeft() == 0;
}
@@ -315,7 +351,7 @@ public class TrackerClient implements Runnable {
private void loop() {
try
{
Random r = I2PAppContext.getGlobalContext().random();
// normally this will only go once, then call queueLoop() and return
while(!stop)
{
if (!verifyConnected()) {
@@ -325,187 +361,25 @@ public class TrackerClient implements Runnable {
// Local DHT tracker announce
DHT dht = _util.getDHT();
if (dht != null)
if (dht != null && (meta == null || !meta.isPrivate()))
dht.announce(snark.getInfoHash());
long uploaded = coordinator.getUploaded();
long downloaded = coordinator.getDownloaded();
long left = coordinator.getLeft(); // -1 in magnet mode
// First time we got a complete download?
String event;
if (!completed && left == 0)
{
completed = true;
event = COMPLETED_EVENT;
}
else
event = NO_EVENT;
// *** loop once for each tracker
int maxSeenPeers = 0;
for (Tracker tr : trackers) {
if ((!stop) && (!tr.stop) &&
(completed || coordinator.needOutboundPeers() || !tr.started) &&
(event.equals(COMPLETED_EVENT) || System.currentTimeMillis() > tr.lastRequestTime + tr.interval))
{
try
{
if (!tr.started)
event = STARTED_EVENT;
TrackerInfo info = doRequest(tr, infoHash, peerID,
uploaded, downloaded, left,
event);
snark.setTrackerProblems(null);
tr.trackerProblems = null;
tr.registerFails = 0;
tr.consecutiveFails = 0;
if (tr.isPrimary)
consecutiveFails = 0;
runStarted = true;
tr.started = true;
Set<Peer> peers = info.getPeers();
tr.seenPeers = info.getPeerCount();
if (snark.getTrackerSeenPeers() < tr.seenPeers) // update rising number quickly
snark.setTrackerSeenPeers(tr.seenPeers);
// pass everybody over to our tracker
dht = _util.getDHT();
if (dht != null) {
for (Peer peer : peers) {
dht.announce(snark.getInfoHash(), peer.getPeerID().getDestHash());
}
}
if (coordinator.needOutboundPeers()) {
// we only want to talk to new people if we need things
// from them (duh)
List<Peer> ordered = new ArrayList(peers);
Collections.shuffle(ordered, r);
Iterator<Peer> it = ordered.iterator();
while ((!stop) && it.hasNext() && coordinator.needOutboundPeers()) {
Peer cur = it.next();
// FIXME if id == us || dest == us continue;
// only delay if we actually make an attempt to add peer
if(coordinator.addPeer(cur) && it.hasNext()) {
int delay = r.nextInt(DELAY_RAND) + DELAY_MIN;
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
}
}
}
}
catch (IOException ioe)
{
// Probably not fatal (if it doesn't last to long...)
if (_log.shouldLog(Log.WARN))
_log.warn
("WARNING: Could not contact tracker at '"
+ tr.announce + "': " + ioe);
tr.trackerProblems = ioe.getMessage();
// don't show secondary tracker problems to the user
if (tr.isPrimary)
snark.setTrackerProblems(tr.trackerProblems);
if (tr.trackerProblems.toLowerCase(Locale.US).startsWith(NOT_REGISTERED)) {
// Give a guy some time to register it if using opentrackers too
if (trackers.size() == 1) {
stop = true;
snark.stopTorrent();
} else { // hopefully each on the opentrackers list is really open
if (tr.registerFails++ > MAX_REGISTER_FAILS)
tr.stop = true;
}
}
if (++tr.consecutiveFails == MAX_CONSEC_FAILS) {
tr.seenPeers = 0;
if (tr.interval < LONG_SLEEP)
tr.interval = LONG_SLEEP; // slow down
}
}
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Not announcing to " + tr.announce + " last announce was " +
new Date(tr.lastRequestTime) + " interval is " + DataHelper.formatDuration(tr.interval));
}
if ((!tr.stop) && maxSeenPeers < tr.seenPeers)
maxSeenPeers = tr.seenPeers;
} // *** end of trackers loop here
// Get peers from PEX
if (coordinator.needOutboundPeers() && (meta == null || !meta.isPrivate()) && !stop) {
Set<PeerID> pids = coordinator.getPEXPeers();
if (!pids.isEmpty()) {
if (_log.shouldLog(Log.INFO))
_log.info("Got " + pids.size() + " from PEX");
List<Peer> peers = new ArrayList(pids.size());
for (PeerID pID : pids) {
peers.add(new Peer(pID, snark.getID(), snark.getInfoHash(), snark.getMetaInfo()));
}
Collections.shuffle(peers, r);
Iterator<Peer> it = peers.iterator();
while ((!stop) && it.hasNext() && coordinator.needOutboundPeers()) {
Peer cur = it.next();
if (coordinator.addPeer(cur) && it.hasNext()) {
int delay = r.nextInt(DELAY_RAND) + DELAY_MIN;
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
}
}
}
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Not getting PEX peers");
if (!trackers.isEmpty())
maxSeenPeers = getPeersFromTrackers(trackers);
int p = getPeersFromPEX();
if (p > maxSeenPeers)
maxSeenPeers = p;
p = getPeersFromDHT();
if (p > maxSeenPeers)
maxSeenPeers = p;
// backup if DHT needs bootstrapping
if (trackers.isEmpty() && !backupTrackers.isEmpty() && dht != null && dht.size() < 16) {
p = getPeersFromTrackers(backupTrackers);
if (p > maxSeenPeers)
maxSeenPeers = p;
}
// Get peers from DHT
// FIXME this needs to be in its own thread
dht = _util.getDHT();
if (dht != null && (meta == null || !meta.isPrivate()) && (!stop) &&
_util.getContext().clock().now() > lastDHTAnnounce + MIN_DHT_ANNOUNCE_INTERVAL) {
int numwant;
if (event.equals(STOPPED_EVENT) || !coordinator.needOutboundPeers())
numwant = 1;
else
numwant = _util.getMaxConnections();
Collection<Hash> hashes = dht.getPeers(snark.getInfoHash(), numwant, 2*60*1000);
if (!hashes.isEmpty()) {
runStarted = true;
lastDHTAnnounce = _util.getContext().clock().now();
}
if (_log.shouldLog(Log.INFO))
_log.info("Got " + hashes + " from DHT");
// announce ourselves while the token is still good
// FIXME this needs to be in its own thread
if (!stop) {
// announce only to the 1 closest
int good = dht.announce(snark.getInfoHash(), 1, 5*60*1000);
if (_log.shouldLog(Log.INFO))
_log.info("Sent " + good + " good announces to DHT");
}
// now try these peers
if ((!stop) && !hashes.isEmpty()) {
List<Peer> peers = new ArrayList(hashes.size());
for (Hash h : hashes) {
PeerID pID = new PeerID(h.getData(), _util);
peers.add(new Peer(pID, snark.getID(), snark.getInfoHash(), snark.getMetaInfo()));
}
Collections.shuffle(peers, r);
Iterator<Peer> it = peers.iterator();
while ((!stop) && it.hasNext() && coordinator.needOutboundPeers()) {
Peer cur = it.next();
if (coordinator.addPeer(cur) && it.hasNext()) {
int delay = r.nextInt(DELAY_RAND) + DELAY_MIN;
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
}
}
}
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Not getting DHT peers");
}
// we could try and total the unique peers but that's too hard for now
snark.setTrackerSeenPeers(maxSeenPeers);
@@ -516,6 +390,7 @@ public class TrackerClient implements Runnable {
// Sleep some minutes...
// Sleep the minimum interval for all the trackers, but 60s minimum
int delay;
Random r = _util.getContext().random();
int random = r.nextInt(120*1000);
if (completed && runStarted)
delay = 3*SLEEP*60*1000 + random;
@@ -547,6 +422,213 @@ public class TrackerClient implements Runnable {
}
}
/**
* @return max peers seen
*/
private int getPeersFromTrackers(List<Tracker> trckrs) {
long uploaded = coordinator.getUploaded();
long downloaded = coordinator.getDownloaded();
long left = coordinator.getLeft(); // -1 in magnet mode
// First time we got a complete download?
String event;
if (!completed && left == 0)
{
completed = true;
event = COMPLETED_EVENT;
}
else
event = NO_EVENT;
// *** loop once for each tracker
int maxSeenPeers = 0;
for (Tracker tr : trckrs) {
if ((!stop) && (!tr.stop) &&
(completed || coordinator.needOutboundPeers() || !tr.started) &&
(event.equals(COMPLETED_EVENT) || System.currentTimeMillis() > tr.lastRequestTime + tr.interval))
{
try
{
if (!tr.started)
event = STARTED_EVENT;
TrackerInfo info = doRequest(tr, infoHash, peerID,
uploaded, downloaded, left,
event);
snark.setTrackerProblems(null);
tr.trackerProblems = null;
tr.registerFails = 0;
tr.consecutiveFails = 0;
if (tr.isPrimary)
consecutiveFails = 0;
runStarted = true;
tr.started = true;
Set<Peer> peers = info.getPeers();
tr.seenPeers = info.getPeerCount();
if (snark.getTrackerSeenPeers() < tr.seenPeers) // update rising number quickly
snark.setTrackerSeenPeers(tr.seenPeers);
// pass everybody over to our tracker
DHT dht = _util.getDHT();
if (dht != null) {
for (Peer peer : peers) {
dht.announce(snark.getInfoHash(), peer.getPeerID().getDestHash());
}
}
if (coordinator.needOutboundPeers()) {
// we only want to talk to new people if we need things
// from them (duh)
List<Peer> ordered = new ArrayList(peers);
Random r = _util.getContext().random();
Collections.shuffle(ordered, r);
Iterator<Peer> it = ordered.iterator();
while ((!stop) && it.hasNext() && coordinator.needOutboundPeers()) {
Peer cur = it.next();
// FIXME if id == us || dest == us continue;
// only delay if we actually make an attempt to add peer
if(coordinator.addPeer(cur) && it.hasNext()) {
int delay = r.nextInt(DELAY_RAND) + DELAY_MIN;
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
}
}
}
}
catch (IOException ioe)
{
// Probably not fatal (if it doesn't last to long...)
if (_log.shouldLog(Log.WARN))
_log.warn
("WARNING: Could not contact tracker at '"
+ tr.announce + "': " + ioe);
tr.trackerProblems = ioe.getMessage();
// don't show secondary tracker problems to the user
if (tr.isPrimary)
snark.setTrackerProblems(tr.trackerProblems);
if (tr.trackerProblems.toLowerCase(Locale.US).startsWith(NOT_REGISTERED)) {
// Give a guy some time to register it if using opentrackers too
//if (trckrs.size() == 1) {
// stop = true;
// snark.stopTorrent();
//} else { // hopefully each on the opentrackers list is really open
if (tr.registerFails++ > MAX_REGISTER_FAILS)
tr.stop = true;
//
}
if (++tr.consecutiveFails == MAX_CONSEC_FAILS) {
tr.seenPeers = 0;
if (tr.interval < LONG_SLEEP)
tr.interval = LONG_SLEEP; // slow down
}
}
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Not announcing to " + tr.announce + " last announce was " +
new Date(tr.lastRequestTime) + " interval is " + DataHelper.formatDuration(tr.interval));
}
if ((!tr.stop) && maxSeenPeers < tr.seenPeers)
maxSeenPeers = tr.seenPeers;
} // *** end of trackers loop here
return maxSeenPeers;
}
/**
* @return max peers seen
*/
private int getPeersFromPEX() {
// Get peers from PEX
int rv = 0;
if (coordinator.needOutboundPeers() && (meta == null || !meta.isPrivate()) && !stop) {
Set<PeerID> pids = coordinator.getPEXPeers();
if (!pids.isEmpty()) {
if (_log.shouldLog(Log.INFO))
_log.info("Got " + pids.size() + " from PEX");
List<Peer> peers = new ArrayList(pids.size());
for (PeerID pID : pids) {
peers.add(new Peer(pID, snark.getID(), snark.getInfoHash(), snark.getMetaInfo()));
}
Random r = _util.getContext().random();
Collections.shuffle(peers, r);
Iterator<Peer> it = peers.iterator();
while ((!stop) && it.hasNext() && coordinator.needOutboundPeers()) {
Peer cur = it.next();
if (coordinator.addPeer(cur) && it.hasNext()) {
int delay = r.nextInt(DELAY_RAND) + DELAY_MIN;
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
}
}
rv = pids.size();
pids.clear();
}
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Not getting PEX peers");
}
return rv;
}
/**
* @return max peers seen
*/
private int getPeersFromDHT() {
// Get peers from DHT
// FIXME this needs to be in its own thread
int rv = 0;
DHT dht = _util.getDHT();
if (dht != null && (meta == null || !meta.isPrivate()) && (!stop) &&
_util.getContext().clock().now() > lastDHTAnnounce + MIN_DHT_ANNOUNCE_INTERVAL) {
int numwant;
if (!coordinator.needOutboundPeers())
numwant = 1;
else
numwant = _util.getMaxConnections();
Collection<Hash> hashes = dht.getPeers(snark.getInfoHash(), numwant, 2*60*1000);
if (!hashes.isEmpty()) {
runStarted = true;
lastDHTAnnounce = _util.getContext().clock().now();
rv = hashes.size();
}
if (_log.shouldLog(Log.INFO))
_log.info("Got " + hashes + " from DHT");
// announce ourselves while the token is still good
// FIXME this needs to be in its own thread
if (!stop) {
// announce only to the 1 closest
int good = dht.announce(snark.getInfoHash(), 1, 5*60*1000);
if (_log.shouldLog(Log.INFO))
_log.info("Sent " + good + " good announces to DHT");
}
// now try these peers
if ((!stop) && !hashes.isEmpty()) {
List<Peer> peers = new ArrayList(hashes.size());
for (Hash h : hashes) {
try {
PeerID pID = new PeerID(h.getData(), _util);
peers.add(new Peer(pID, snark.getID(), snark.getInfoHash(), snark.getMetaInfo()));
} catch (InvalidBEncodingException ibe) {}
}
Random r = _util.getContext().random();
Collections.shuffle(peers, r);
Iterator<Peer> it = peers.iterator();
while ((!stop) && it.hasNext() && coordinator.needOutboundPeers()) {
Peer cur = it.next();
if (coordinator.addPeer(cur) && it.hasNext()) {
int delay = r.nextInt(DELAY_RAND) + DELAY_MIN;
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
}
}
}
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Not getting DHT peers");
}
return rv;
}
/**
* Creates a thread for each tracker in parallel if tunnel is still open
* @since 0.9.1
@@ -630,7 +712,8 @@ public class TrackerClient implements Runnable {
if (! event.equals(NO_EVENT))
buf.append("&event=").append(event);
buf.append("&numwant=");
if (left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needOutboundPeers())
boolean small = left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needOutboundPeers();
if (small)
buf.append('0');
else
buf.append(_util.getMaxConnections());
@@ -641,14 +724,12 @@ public class TrackerClient implements Runnable {
tr.lastRequestTime = System.currentTimeMillis();
// Don't wait for a response to stopped when shutting down
boolean fast = _fastUnannounce && event.equals(STOPPED_EVENT);
File fetched = _util.get(s, true, fast ? -1 : 0);
byte[] fetched = _util.get(s, true, fast ? -1 : 0, small ? 128 : 1024, small ? 1024 : 8*1024);
if (fetched == null) {
throw new IOException("Error fetching " + s);
}
InputStream in = null;
try {
in = new FileInputStream(fetched);
InputStream in = new ByteArrayInputStream(fetched);
TrackerInfo info = new TrackerInfo(in, snark.getID(),
snark.getInfoHash(), snark.getMetaInfo(), _util);
@@ -661,10 +742,6 @@ public class TrackerClient implements Runnable {
tr.interval = Math.max(MIN_TRACKER_ANNOUNCE_INTERVAL, info.getInterval() * 1000l);
return info;
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
fetched.delete();
}
}
/**

View File

@@ -0,0 +1,52 @@
package org.klomp.snark;
import java.net.URI;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.update.*;
/**
* <p>Handles the request to update the router by firing up a magnet.
* {@link net.i2p.util.EepGet} calls to download the latest signed update file
* and displaying the status to anyone who asks.
* </p>
* <p>After the download completes the signed update file is verified with
* {@link net.i2p.crypto.TrustedUpdate}, and if it's authentic the payload
* of the signed update file is unpacked and the router is restarted to complete
* the update process.
* </p>
*
* This does not do any checking, that is handled by the NewsFetcher.
*
* @since 0.9.4
*/
class UpdateHandler implements Updater {
private final I2PAppContext _context;
private final UpdateManager _umgr;
private final SnarkManager _smgr;
public UpdateHandler(I2PAppContext ctx, UpdateManager umgr, SnarkManager smgr) {
_context = ctx;
_umgr = umgr;
_smgr = smgr;
}
/**
* Start a download and return a handle to the download task.
* Should not block.
*
* @param id plugin name or ignored
* @param maxTime how long you have
* @return active task or null if unable to download
*/
public UpdateTask update(UpdateType type, UpdateMethod method, List<URI> updateSources,
String id, String newVersion, long maxTime) {
if (type != UpdateType.ROUTER_SIGNED ||
method != UpdateMethod.TORRENT || updateSources.isEmpty())
return null;
UpdateRunner update = new UpdateRunner(_context, _umgr, _smgr, updateSources, newVersion);
_umgr.notifyProgress(update, "<b>" + _smgr.util().getString("Updating") + "</b>");
return update;
}
}

View File

@@ -0,0 +1,302 @@
package org.klomp.snark;
import java.io.File;
import java.net.URI;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.crypto.TrustedUpdate;
import net.i2p.data.DataHelper;
import net.i2p.update.*;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
import net.i2p.util.VersionComparator;
/**
* The downloader for router signed updates.
*
* @since 0.9.4
*/
class UpdateRunner implements UpdateTask, CompleteListener {
private final I2PAppContext _context;
private final Log _log;
private final UpdateManager _umgr;
private final SnarkManager _smgr;
private final List<URI> _urls;
private volatile boolean _isRunning;
private volatile boolean _hasMetaInfo;
private volatile boolean _isComplete;
private final String _newVersion;
private URI _currentURI;
private Snark _snark;
private static final long MAX_LENGTH = 30*1024*1024;
private static final long METAINFO_TIMEOUT = 30*60*1000;
private static final long COMPLETE_TIMEOUT = 3*60*60*1000;
private static final long CHECK_INTERVAL = 3*60*1000;
public UpdateRunner(I2PAppContext ctx, UpdateManager umgr, SnarkManager smgr,
List<URI> uris, String newVersion) {
_context = ctx;
_log = ctx.logManager().getLog(getClass());
_umgr = umgr;
_smgr = smgr;
_urls = uris;
_newVersion = newVersion;
}
//////// begin UpdateTask methods
public boolean isRunning() { return _isRunning; }
public void shutdown() {
_isRunning = false;
if (_snark != null) {
}
}
public UpdateType getType() { return UpdateType.ROUTER_SIGNED; }
public UpdateMethod getMethod() { return UpdateMethod.TORRENT; }
public URI getURI() { return _currentURI; }
public String getID() { return ""; }
//////// end UpdateTask methods
public void start() {
_isRunning = true;
update();
}
/**
* Loop through the entire list of update URLs.
* For each one, first get the version from the first 56 bytes and see if
* it is newer than what we are running now.
* If it is, get the whole thing.
*/
private void update() {
for (URI uri : _urls) {
_currentURI = uri;
String updateURL = uri.toString();
try {
MagnetURI magnet = new MagnetURI(_smgr.util(), updateURL);
byte[] ih = magnet.getInfoHash();
// do we already have it?
_snark = _smgr.getTorrentByInfoHash(ih);
if (_snark != null) {
if (_snark.getMetaInfo() != null) {
_hasMetaInfo = true;
Storage storage = _snark.getStorage();
if (storage != null && storage.complete())
processComplete(_snark);
}
if (!_isComplete) {
if (_snark.isStopped() && !_snark.isStarting())
_snark.startTorrent();
// we aren't a listener so we must poll
new Watcher();
}
break;
}
String name = magnet.getName();
String trackerURL = magnet.getTrackerURL();
if (trackerURL == null && !_smgr.util().shouldUseDHT() &&
!_smgr.util().shouldUseOpenTrackers()) {
// but won't we use OT as a failsafe even if disabled?
_umgr.notifyAttemptFailed(this, "No tracker, no DHT, no OT", null);
continue;
}
_snark = _smgr.addMagnet(name, ih, trackerURL, true, true, this);
if (_snark != null) {
updateStatus("<b>" + _smgr.util().getString("Updating from {0}", updateURL) + "</b>");
new Timeout();
break;
}
} catch (IllegalArgumentException iae) {
_log.error("Invalid update URL", iae);
}
}
if (_snark == null)
fatal("No valid URLs");
}
/**
* This will run twice, once at the metainfo timeout and
* once at the complete timeout.
*/
private class Timeout extends SimpleTimer2.TimedEvent {
private final long _start = _context.clock().now();
public Timeout() {
super(_context.simpleTimer2(), METAINFO_TIMEOUT);
}
public void timeReached() {
if (_isComplete || !_isRunning)
return;
if (!_hasMetaInfo) {
fatal("Metainfo timeout");
return;
}
if (_context.clock().now() - _start >= COMPLETE_TIMEOUT) {
fatal("Complete timeout");
return;
}
reschedule(COMPLETE_TIMEOUT - METAINFO_TIMEOUT);
}
}
/**
* Rarely used - only if the user added the torrent, so
* we aren't a complete listener.
* This will periodically until the complete timeout.
*/
private class Watcher extends SimpleTimer2.TimedEvent {
private final long _start = _context.clock().now();
public Watcher() {
super(_context.simpleTimer2(), CHECK_INTERVAL);
}
public void timeReached() {
if (_hasMetaInfo && _snark.getRemainingLength() == 0 && !_isComplete)
processComplete(_snark);
if (_isComplete || !_isRunning)
return;
if (_context.clock().now() - _start >= METAINFO_TIMEOUT && !_hasMetaInfo) {
fatal("Metainfo timeout");
return;
}
if (_context.clock().now() - _start >= COMPLETE_TIMEOUT) {
fatal("Complete timeout");
return;
}
notifyProgress();
reschedule(CHECK_INTERVAL);
}
}
private void fatal(String error) {
if (_snark != null) {
if (_hasMetaInfo) {
_smgr.stopTorrent(_snark, true);
String file = _snark.getName();
_smgr.removeTorrent(file);
// delete torrent file
File f = new File(_smgr.getDataDir(), file);
f.delete();
// delete data
file = _snark.getBaseName();
f = new File(_smgr.getDataDir(), file);
f.delete();
} else {
_smgr.deleteMagnet(_snark);
}
}
_umgr.notifyTaskFailed(this, error, null);
_log.error(error);
_isRunning = false;
// stop the tunnel if we were the only one running
if (_smgr.util().connected() && !_smgr.util().isConnecting()) {
for (Snark s : _smgr.getTorrents()) {
if (!s.isStopped())
return;
}
_smgr.util().disconnect();
}
}
private void processComplete(Snark snark) {
String dataFile = snark.getBaseName();
File f = new File(_smgr.getDataDir(), dataFile);
String sudVersion = TrustedUpdate.getVersionString(f);
if (_newVersion.equals(sudVersion))
_umgr.notifyComplete(this, _newVersion, f);
else
fatal("version mismatch");
_isComplete = true;
}
private void notifyProgress() {
if (_hasMetaInfo) {
long total = _snark.getTotalLength();
long remaining = _snark.getRemainingLength();
String status = "<b>" + _smgr.util().getString("Updating") + "</b>";
_umgr.notifyProgress(this, status, total - remaining, total);
}
}
//////// begin CompleteListener methods
//////// all pass through to SnarkManager
public void torrentComplete(Snark snark) {
processComplete(snark);
_smgr.torrentComplete(snark);
}
/**
* This is called by stopTorrent() among others
*/
public void updateStatus(Snark snark) {
if (snark.isStopped()) {
if (!_isComplete)
fatal("stopped by user");
}
_smgr.updateStatus(snark);
}
public String gotMetaInfo(Snark snark) {
MetaInfo info = snark.getMetaInfo();
if (info.getFiles() != null) {
fatal("more than 1 file");
return null;
}
if (info.isPrivate()) {
fatal("private torrent");
return null;
}
if (info.getTotalLength() > MAX_LENGTH) {
fatal("too big");
return null;
}
_hasMetaInfo = true;
notifyProgress();
return _smgr.gotMetaInfo(snark);
}
public void fatal(Snark snark, String error) {
fatal(error);
_smgr.fatal(snark, error);
}
public void addMessage(Snark snark, String message) {
_smgr.addMessage(snark, message);
}
public void gotPiece(Snark snark) {
notifyProgress();
_smgr.gotPiece(snark);
}
public long getSavedTorrentTime(Snark snark) {
return _smgr.getSavedTorrentTime(snark);
}
public BitField getSavedTorrentBitField(Snark snark) {
return _smgr.getSavedTorrentBitField(snark);
}
//////// end CompleteListener methods
private void updateStatus(String s) {
_umgr.notifyProgress(this, s);
}
@Override
public String toString() {
return getClass().getName() + ' ' + getType() + ' ' + getID() + ' ' + getMethod() + ' ' + getURI();
}
}

View File

@@ -69,6 +69,9 @@ class DHTNodes {
// begin ConcurrentHashMap methods
/**
* @return known nodes, not total net size
*/
public int size() {
return _nodeMap.size();
}
@@ -86,8 +89,13 @@ class DHTNodes {
* @return the old value if present, else null
*/
public NodeInfo putIfAbsent(NodeInfo nInfo) {
_kad.add(nInfo.getNID());
return _nodeMap.putIfAbsent(nInfo.getNID(), nInfo);
NodeInfo rv = _nodeMap.putIfAbsent(nInfo.getNID(), nInfo);
// ensure same object in both places
if (rv != null)
_kad.add(rv.getNID());
else
_kad.add(nInfo.getNID());
return rv;
}
public NodeInfo remove(NID nid) {
@@ -128,11 +136,19 @@ class DHTNodes {
return _kad.getExploreKeys(MAX_BUCKET_AGE);
}
/**
* Debug info, HTML formatted
* @since 0.9.4
*/
public void renderStatusHTML(StringBuilder buf) {
buf.append(_kad.toString().replace("\n", "<br>\n"));
}
/** */
private class Cleaner extends SimpleTimer2.TimedEvent {
public Cleaner() {
super(SimpleTimer2.getInstance(), CLEAN_TIME);
super(SimpleTimer2.getInstance(), 5 * CLEAN_TIME);
}
public void timeReached() {

View File

@@ -39,6 +39,8 @@ class DHTTracker {
private static final long DELTA_EXPIRE_TIME = 3*60*1000;
private static final int MAX_PEERS = 2000;
private static final int MAX_PEERS_PER_TORRENT = 150;
private static final int ABSOLUTE_MAX_PER_TORRENT = MAX_PEERS_PER_TORRENT * 2;
private static final int MAX_TORRENTS = 400;
DHTTracker(I2PAppContext ctx) {
_context = ctx;
@@ -62,17 +64,29 @@ class DHTTracker {
_log.debug("Announce " + hash + " for " + ih);
Peers peers = _torrents.get(ih);
if (peers == null) {
if (_torrents.size() >= MAX_TORRENTS)
return;
peers = new Peers();
Peers peers2 = _torrents.putIfAbsent(ih, peers);
if (peers2 != null)
peers = peers2;
}
Peer peer = new Peer(hash.getData());
Peer peer2 = peers.putIfAbsent(peer, peer);
if (peer2 != null)
peer = peer2;
peer.setLastSeen(_context.clock().now());
if (peers.size() < ABSOLUTE_MAX_PER_TORRENT) {
Peer peer = new Peer(hash.getData());
Peer peer2 = peers.putIfAbsent(peer, peer);
if (peer2 != null)
peer = peer2;
peer.setLastSeen(_context.clock().now());
} else {
// We could update setLastSeen if he is already
// in there, but that would tend to keep
// the same set of peers.
// So let it expire so new ones can come in.
//Peer peer = peers.get(hash);
//if (peer != null)
// peer.setLastSeen(_context.clock().now());
}
}
void unannounce(InfoHash ih, Hash hash) {
@@ -113,7 +127,7 @@ class DHTTracker {
private class Cleaner extends SimpleTimer2.TimedEvent {
public Cleaner() {
super(SimpleTimer2.getInstance(), CLEAN_TIME);
super(SimpleTimer2.getInstance(), 2 * CLEAN_TIME);
}
public void timeReached() {
@@ -122,6 +136,7 @@ class DHTTracker {
long now = _context.clock().now();
int torrentCount = 0;
int peerCount = 0;
boolean tooMany = false;
for (Iterator<Peers> iter = _torrents.values().iterator(); iter.hasNext(); ) {
Peers p = iter.next();
int recent = 0;
@@ -136,6 +151,7 @@ class DHTTracker {
}
if (recent > MAX_PEERS_PER_TORRENT) {
// too many, delete at random
// TODO sort and remove oldest?
// TODO per-torrent adjustable expiration?
for (Iterator<Peer> iterp = p.values().iterator(); iterp.hasNext() && p.size() > MAX_PEERS_PER_TORRENT; ) {
iterp.next();
@@ -143,6 +159,7 @@ class DHTTracker {
peerCount--;
}
torrentCount++;
tooMany = true;
} else if (recent <= 0) {
iter.remove();
} else {
@@ -151,6 +168,8 @@ class DHTTracker {
}
if (peerCount > MAX_PEERS)
tooMany = true;
if (tooMany)
_expireTime = Math.max(_expireTime - DELTA_EXPIRE_TIME, MIN_EXPIRE_TIME);
else
_expireTime = Math.min(_expireTime + DELTA_EXPIRE_TIME, MAX_EXPIRE_TIME);
@@ -162,7 +181,7 @@ class DHTTracker {
DataHelper.formatDuration(_expireTime) + " expiration");
_peerCount = peerCount;
_torrentCount = torrentCount;
schedule(CLEAN_TIME);
schedule(tooMany ? CLEAN_TIME / 3 : CLEAN_TIME);
}
}
}

View File

@@ -143,6 +143,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
/** how long since generated do we delete - BEP 5 says 10 minutes */
private static final long MAX_TOKEN_AGE = 10*60*1000;
private static final long MAX_INBOUND_TOKEN_AGE = MAX_TOKEN_AGE - 2*60*1000;
private static final int MAX_OUTBOUND_TOKENS = 5000;
/** how long since sent do we wait for a reply */
private static final long MAX_MSGID_AGE = 2*60*1000;
/** how long since sent do we wait for a reply */
@@ -613,6 +614,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
"Rcvd tokens: ").append(_incomingTokens.size()).append("<br>" +
"Pending queries: ").append(_sentQueries.size()).append("<br>");
_tracker.renderStatusHTML(buf);
_knownNodes.renderStatusHTML(buf);
return buf.toString();
}
@@ -1107,8 +1109,12 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
if (nInfo.equals(_myNodeInfo))
return _myNodeInfo;
NodeInfo rv = _knownNodes.putIfAbsent(nInfo);
if (rv == null)
if (rv == null) {
rv = nInfo;
// if we didn't know about it before, set the timestamp
// so it isn't immediately removed by the DHTNodes cleaner
rv.getNID().setLastSeen();
}
return rv;
}
@@ -1203,7 +1209,8 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
/**
* Handle and respond to the query.
* We have no node info here, it came on response port, we have to get it from the token
* We have no node info here, it came on response port, we have to get it from the token.
* So we can't verify that it came from the same peer, as BEP 5 specifies.
*/
private void receiveAnnouncePeer(MsgID msgID, InfoHash ih, byte[] tok) throws InvalidBEncodingException {
Token token = new Token(tok);
@@ -1211,8 +1218,8 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
if (nInfo == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unknown token in announce_peer: " + token);
if (_log.shouldLog(Log.INFO))
_log.info("Current known tokens: " + _outgoingTokens.keySet());
//if (_log.shouldLog(Log.INFO))
// _log.info("Current known tokens: " + _outgoingTokens.keySet());
return;
}
if (_log.shouldLog(Log.INFO))
@@ -1277,8 +1284,9 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
* @throws NPE, IllegalArgumentException, and others too
*/
private List<NodeInfo> receiveNodes(NodeInfo nInfo, byte[] ids) throws InvalidBEncodingException {
List<NodeInfo> rv = new ArrayList(ids.length / NodeInfo.LENGTH);
for (int off = 0; off < ids.length; off += NodeInfo.LENGTH) {
int max = Math.min(K, ids.length / NodeInfo.LENGTH);
List<NodeInfo> rv = new ArrayList(max);
for (int off = 0; off < ids.length && rv.size() < max; off += NodeInfo.LENGTH) {
NodeInfo nInf = new NodeInfo(ids, off);
if (_blacklist.contains(nInf.getNID())) {
if (_log.shouldLog(Log.INFO))
@@ -1300,12 +1308,15 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
private List<Hash> receivePeers(NodeInfo nInfo, List<BEValue> peers) throws InvalidBEncodingException {
if (_log.shouldLog(Log.INFO))
_log.info("Rcvd peers from: " + nInfo);
List<Hash> rv = new ArrayList(peers.size());
int max = Math.min(MAX_WANT, peers.size());
List<Hash> rv = new ArrayList(max);
for (BEValue bev : peers) {
byte[] b = bev.getBytes();
//Hash h = new Hash(b);
Hash h = Hash.create(b);
rv.add(h);
if (rv.size() >= max)
break;
}
if (_log.shouldLog(Log.INFO))
_log.info("Rcvd peers from: " + nInfo + ": " + DataHelper.toString(rv));
@@ -1518,7 +1529,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
private class Cleaner extends SimpleTimer2.TimedEvent {
public Cleaner() {
super(SimpleTimer2.getInstance(), CLEAN_TIME);
super(SimpleTimer2.getInstance(), 7 * CLEAN_TIME);
}
public void timeReached() {
@@ -1530,20 +1541,28 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
_blacklist.size() + " in blacklist, " +
_outgoingTokens.size() + " sent Tokens, " +
_incomingTokens.size() + " rcvd Tokens");
int cnt = 0;
long expire = now - MAX_TOKEN_AGE;
for (Iterator<Token> iter = _outgoingTokens.keySet().iterator(); iter.hasNext(); ) {
Token tok = iter.next();
if (tok.lastSeen() < now - MAX_TOKEN_AGE)
// just delete at random if we have too many
// TODO reduce the expire time and iterate again?
if (tok.lastSeen() < expire || cnt >= MAX_OUTBOUND_TOKENS)
iter.remove();
else
cnt++;
}
expire = now - MAX_INBOUND_TOKEN_AGE;
for (Iterator<Token> iter = _incomingTokens.values().iterator(); iter.hasNext(); ) {
Token tok = iter.next();
if (tok.lastSeen() < now - MAX_INBOUND_TOKEN_AGE)
if (tok.lastSeen() < expire)
iter.remove();
}
expire = now - BLACKLIST_CLEAN_TIME;
for (Iterator<NID> iter = _blacklist.iterator(); iter.hasNext(); ) {
NID nid = iter.next();
// lastSeen() is actually when-added
if (now > nid.lastSeen() + BLACKLIST_CLEAN_TIME)
if (nid.lastSeen() < expire)
iter.remove();
}
// TODO sent queries?

View File

@@ -28,13 +28,13 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.i2p.I2PAppContext;
import net.i2p.data.Base32;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import org.klomp.snark.I2PSnarkUtil;
import org.klomp.snark.MagnetURI;
import org.klomp.snark.MetaInfo;
import org.klomp.snark.Peer;
import org.klomp.snark.Snark;
@@ -64,11 +64,6 @@ public class I2PSnarkServlet extends DefaultServlet {
private String _lastAnnounceURL = "";
public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
/** BEP 9 */
private static final String MAGNET = "magnet:";
private static final String MAGNET_FULL = MAGNET + "?xt=urn:btih:";
/** http://sponge.i2p/files/maggotspec.txt */
private static final String MAGGOT = "maggot://";
@Override
public void init(ServletConfig cfg) throws ServletException {
@@ -564,12 +559,13 @@ public class I2PSnarkServlet extends DefaultServlet {
if (newURL.startsWith("http://")) {
FetchAndAdd fetch = new FetchAndAdd(_context, _manager, newURL);
_manager.addDownloader(fetch);
} else if (newURL.startsWith(MAGNET) || newURL.startsWith(MAGGOT)) {
} else if (newURL.startsWith(MagnetURI.MAGNET) || newURL.startsWith(MagnetURI.MAGGOT)) {
addMagnet(newURL);
} else if (newURL.length() == 40 && newURL.replaceAll("[a-fA-F0-9]", "").length() == 0) {
addMagnet(MAGNET_FULL + newURL);
addMagnet(MagnetURI.MAGNET_FULL + newURL);
} else {
_manager.addMessage(_("Invalid URL: Must start with \"http://\", \"{0}\", or \"{1}\"", MAGNET, MAGGOT));
_manager.addMessage(_("Invalid URL: Must start with \"http://\", \"{0}\", or \"{1}\"",
MagnetURI.MAGNET, MagnetURI.MAGGOT));
}
} else {
// no file or URL specified
@@ -999,14 +995,16 @@ public class I2PSnarkServlet extends DefaultServlet {
} else if (snark.isAllocating()) {
statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "stalled.png\" title=\"" + _("Allocating") + "\"></td>" +
"<td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Allocating");
} else if (err != null) {
if (isRunning && curPeers > 0 && !showPeers)
statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "trackererror.png\" title=\"" + err + "\"></td>" +
"<td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Tracker Error") +
": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + "\">" +
curPeers + thinsp(noThinsp) +
ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
else if (isRunning)
} else if (err != null && curPeers == 0) {
// let's only show this if we have no peers, otherwise PEX and DHT should bail us out, user doesn't care
//if (isRunning && curPeers > 0 && !showPeers)
// statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "trackererror.png\" title=\"" + err + "\"></td>" +
// "<td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Tracker Error") +
// ": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + "\">" +
// curPeers + thinsp(noThinsp) +
// ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
//else if (isRunning)
if (isRunning)
statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "trackererror.png\" title=\"" + err + "\"></td>" +
"<td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Tracker Error") +
": " + curPeers + thinsp(noThinsp) +
@@ -1153,7 +1151,7 @@ public class I2PSnarkServlet extends DefaultServlet {
out.write("</a>");
out.write("<td align=\"right\" class=\"snarkTorrentETA " + rowClass + "\">");
if(isRunning && remainingSeconds > 0)
if(isRunning && remainingSeconds > 0 && !snark.isChecking())
out.write(DataHelper.formatDuration2(Math.max(remainingSeconds, 10) * 1000)); // (eta 6h)
out.write("</td>\n\t");
out.write("<td align=\"right\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
@@ -1640,7 +1638,7 @@ public class I2PSnarkServlet extends DefaultServlet {
out.write("\" ></td></tr>\n" +
"<tr><td>");
out.write(_("Enable DHT") + " (**BETA**)");
out.write(_("Enable DHT"));
out.write(": <td><input type=\"checkbox\" class=\"optbox\" name=\"useDHT\" value=\"true\" "
+ (useDHT ? "checked " : "")
+ "title=\"");
@@ -1790,165 +1788,15 @@ public class I2PSnarkServlet extends DefaultServlet {
* @since 0.8.4
*/
private void addMagnet(String url) {
String ihash;
String name;
String trackerURL = null;
if (url.startsWith(MAGNET)) {
// magnet:?xt=urn:btih:0691e40aae02e552cfcb57af1dca56214680c0c5&tr=http://tracker2.postman.i2p/announce.php
String xt = getParam("xt", url);
if (xt == null || !xt.startsWith("urn:btih:")) {
_manager.addMessage(_("Invalid magnet URL {0}", url));
return;
}
ihash = xt.substring("urn:btih:".length());
trackerURL = getTrackerParam(url);
name = _("Magnet") + ' ' + ihash;
String dn = getParam("dn", url);
if (dn != null)
name += " (" + Storage.filterName(dn) + ')';
} else if (url.startsWith(MAGGOT)) {
// maggot://0691e40aae02e552cfcb57af1dca56214680c0c5:0b557bbdf8718e95d352fbe994dec3a383e2ede7
ihash = url.substring(MAGGOT.length()).trim();
int col = ihash.indexOf(':');
if (col >= 0)
ihash = ihash.substring(0, col);
name = _("Magnet") + ' ' + ihash;
} else {
return;
try {
MagnetURI magnet = new MagnetURI(_manager.util(), url);
String name = magnet.getName();
byte[] ih = magnet.getInfoHash();
String trackerURL = magnet.getTrackerURL();
_manager.addMagnet(name, ih, trackerURL, true);
} catch (IllegalArgumentException iae) {
_manager.addMessage(_("Invalid magnet URL {0}", url));
}
byte[] ih = null;
if (ihash.length() == 32) {
ih = Base32.decode(ihash);
} else if (ihash.length() == 40) {
// Like DataHelper.fromHexString() but ensures no loss of leading zero bytes
ih = new byte[20];
try {
for (int i = 0; i < 20; i++) {
ih[i] = (byte) (Integer.parseInt(ihash.substring(i*2, (i*2) + 2), 16) & 0xff);
}
} catch (NumberFormatException nfe) {
ih = null;
}
}
if (ih == null || ih.length != 20) {
_manager.addMessage(_("Invalid info hash in magnet URL {0}", url));
return;
}
_manager.addMagnet(name, ih, trackerURL, true);
}
/**
* @return first decoded parameter or null
*/
private static String getParam(String key, String uri) {
int idx = uri.indexOf('?' + key + '=');
if (idx >= 0) {
idx += key.length() + 2;
} else {
idx = uri.indexOf('&' + key + '=');
if (idx >= 0)
idx += key.length() + 2;
}
if (idx < 0 || idx > uri.length())
return null;
String rv = uri.substring(idx);
idx = rv.indexOf('&');
if (idx >= 0)
rv = rv.substring(0, idx);
else
rv = rv.trim();
return decode(rv);
}
/**
* @return all decoded parameters or null
* @since 0.9.1
*/
private static List<String> getMultiParam(String key, String uri) {
int idx = uri.indexOf('?' + key + '=');
if (idx >= 0) {
idx += key.length() + 2;
} else {
idx = uri.indexOf('&' + key + '=');
if (idx >= 0)
idx += key.length() + 2;
}
if (idx < 0 || idx > uri.length())
return null;
List<String> rv = new ArrayList();
while (true) {
String p = uri.substring(idx);
uri = p;
idx = p.indexOf('&');
if (idx >= 0)
p = p.substring(0, idx);
else
p = p.trim();
rv.add(decode(p));
idx = uri.indexOf('&' + key + '=');
if (idx < 0)
break;
idx += key.length() + 2;
}
return rv;
}
/**
* @return first valid I2P tracker or null
* @since 0.9.1
*/
private static String getTrackerParam(String uri) {
List<String> trackers = getMultiParam("tr", uri);
if (trackers == null)
return null;
for (String t : trackers) {
try {
URI u = new URI(t);
String protocol = u.getScheme();
String host = u.getHost();
if (protocol == null || host == null ||
!protocol.toLowerCase(Locale.US).equals("http") ||
!host.toLowerCase(Locale.US).endsWith(".i2p"))
continue;
return t;
} catch(URISyntaxException use) {}
}
return null;
}
/**
* Decode %xx encoding, convert to UTF-8 if necessary
* Copied from i2ptunnel LocalHTTPServer
* @since 0.9.1
*/
private static String decode(String s) {
if (!s.contains("%"))
return s;
StringBuilder buf = new StringBuilder(s.length());
boolean utf8 = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c != '%') {
buf.append(c);
} else {
try {
int val = Integer.parseInt(s.substring(++i, (++i) + 1), 16);
if ((val & 0x80) != 0)
utf8 = true;
buf.append((char) val);
} catch (IndexOutOfBoundsException ioobe) {
break;
} catch (NumberFormatException nfe) {
break;
}
}
}
if (utf8) {
try {
return new String(buf.toString().getBytes("ISO-8859-1"), "UTF-8");
} catch (UnsupportedEncodingException uee) {}
}
return buf.toString();
}
/** copied from ConfigTunnelsHelper */
@@ -2171,11 +2019,11 @@ public class I2PSnarkServlet extends DefaultServlet {
String hex = I2PSnarkUtil.toHex(snark.getInfoHash());
if (meta == null || !meta.isPrivate()) {
buf.append("<tr><td><a href=\"")
.append(MAGNET_FULL).append(hex).append("\">")
.append(MagnetURI.MAGNET_FULL).append(hex).append("\">")
.append(toImg("magnet", _("Magnet link")))
.append("</a> <b>Magnet:</b> <a href=\"")
.append(MAGNET_FULL).append(hex).append("\">")
.append(MAGNET_FULL).append(hex).append("</a>")
.append(MagnetURI.MAGNET_FULL).append(hex).append("\">")
.append(MagnetURI.MAGNET_FULL).append(hex).append("</a>")
.append("</td></tr>\n");
} else {
buf.append("<tr><td>")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="test/junit"/>
<classpathentry combineaccessrules="false" kind="src" path="/i2p_sdk"/>
<classpathentry combineaccessrules="false" kind="src" path="/ministreaming"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="output" path="build/obj"/>
</classpath>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>i2ptunnel</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -179,6 +179,7 @@
<pathelement location="${ant.home}/lib/ant.jar" />
<pathelement location="build/i2ptunnel.jar" />
<pathelement location="build/temp-beans.jar" />
<pathelement location="../../../core/java/build/i2p.jar" />
</classpath>
<arg value="-d" />
<arg value="../jsp/WEB-INF/classes" />
@@ -202,6 +203,7 @@
<pathelement location="../../jetty/jettylib/jsp-api.jar" />
<pathelement location="build/i2ptunnel.jar" />
<pathelement location="build/temp-beans.jar" />
<pathelement location="../../../core/java/build/i2p.jar" />
</classpath>
</javac>
<copy file="../jsp/web.xml" tofile="../jsp/web-out.xml" />
@@ -213,6 +215,7 @@
<uptodate property="precompilejsp.uptodate" targetfile="../jsp/web-out.xml">
<srcfiles dir= "../jsp" includes="*.jsp, *.html, web.xml"/>
<srcfiles dir= "src/net/i2p/i2ptunnel/web" includes="*.java"/>
</uptodate>
<target name="javadoc">
@@ -230,7 +233,7 @@
<mkdir dir="./build" />
<mkdir dir="./build/obj" />
<!-- We need the ant runtime, as it includes junit -->
<javac srcdir="./src:./test" debug="true" source="1.5" target="1.5"
<javac srcdir="./src:./test/junit" debug="true" source="1.5" target="1.5"
includeAntRuntime="true"
deprecation="on" destdir="./build/obj" >
<compilerarg line="${javac.compilerargs}" />
@@ -248,7 +251,7 @@
<pathelement location="../../../core/java/build/i2p.jar" />
</classpath>
<batchtest>
<fileset dir="./test/">
<fileset dir="./test/junit/">
<include name="**/*Test.java" />
</fileset>
</batchtest>

View File

@@ -13,6 +13,7 @@ import java.util.Locale;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketOptions;
@@ -20,7 +21,6 @@ import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.EventDispatcher;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
import net.i2p.util.PortMapper;
@@ -58,6 +58,8 @@ import net.i2p.util.PortMapper;
*/
public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements Runnable {
public static final String AUTH_REALM = "I2P SSL Proxy";
private final static byte[] ERR_DESTINATION_UNKNOWN =
("HTTP/1.1 503 Service Unavailable\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
@@ -89,17 +91,6 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>")
.getBytes();
private final static byte[] ERR_AUTH =
("HTTP/1.1 407 Proxy Authentication Required\r\n"+
"Content-Type: text/html; charset=UTF-8\r\n"+
"Cache-control: no-cache\r\n"+
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.5\r\n" + // try to get a UTF-8-encoded response back for the password
"Proxy-Authenticate: Basic realm=\"I2P SSL Proxy\"\r\n" +
"\r\n"+
"<html><body><H1>I2P ERROR: PROXY AUTHENTICATION REQUIRED</H1>"+
"This proxy is configured to require authentication.<BR>")
.getBytes();
private final static byte[] SUCCESS_RESPONSE =
("HTTP/1.1 200 Connection Established\r\n"+
"Proxy-agent: I2P\r\n"+
@@ -165,6 +156,11 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
return super.close(forced);
}
/** @since 0.9.4 */
protected String getRealm() {
return AUTH_REALM;
}
protected void clientConnectionRun(Socket s) {
InputStream in = null;
OutputStream out = null;
@@ -237,10 +233,10 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
_log.debug(getPrefix(requestId) + "REST :" + restofline + ":");
_log.debug(getPrefix(requestId) + "DEST :" + destination + ":");
}
} else if (line.toLowerCase(Locale.US).startsWith("proxy-authorization: basic ")) {
} else if (line.toLowerCase(Locale.US).startsWith("proxy-authorization: ")) {
// strip Proxy-Authenticate from the response in HTTPResponseOutputStream
// save for auth check below
authorization = line.substring(27); // "proxy-authorization: basic ".length()
authorization = line.substring(21); // "proxy-authorization: ".length()
line = null;
} else if (line.length() > 0) {
// Additional lines - shouldn't be too many. Firefox sends:
@@ -281,30 +277,26 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
}
// Authorization
if (!authorize(s, requestId, authorization)) {
AuthResult result = authorize(s, requestId, method, authorization);
if (result != AuthResult.AUTH_GOOD) {
if (_log.shouldLog(Log.WARN)) {
if (authorization != null)
_log.warn(getPrefix(requestId) + "Auth failed, sending 407 again");
else
_log.warn(getPrefix(requestId) + "Auth required, sending 407");
}
writeErrorMessage(ERR_AUTH, out);
out.write(getAuthError(result == AuthResult.AUTH_STALE).getBytes());
s.close();
return;
}
Destination clientDest = _context.namingService().lookup(destination);
if (clientDest == null) {
String str;
byte[] header;
if (usingWWWProxy)
str = FileUtil.readTextFile((new File(_errorDir, "dnfp-header.ht")).getAbsolutePath(), 100, true);
header = getErrorPage("dnfp-header.ht", ERR_DESTINATION_UNKNOWN);
else
str = FileUtil.readTextFile((new File(_errorDir, "dnfh-header.ht")).getAbsolutePath(), 100, true);
if (str != null)
header = str.getBytes();
else
header = ERR_DESTINATION_UNKNOWN;
header = getErrorPage("dnfh-header.ht", ERR_DESTINATION_UNKNOWN);
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination);
s.close();
return;
@@ -341,12 +333,13 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
}
private static class OnTimeout implements Runnable {
private Socket _socket;
private OutputStream _out;
private String _target;
private boolean _usingProxy;
private String _wwwProxy;
private long _requestId;
private final Socket _socket;
private final OutputStream _out;
private final String _target;
private final boolean _usingProxy;
private final String _wwwProxy;
private final long _requestId;
public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy, String wwwProxy, long id) {
_socket = s;
_out = out;
@@ -355,6 +348,7 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
_wwwProxy = wwwProxy;
_requestId = id;
}
public void run() {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Timeout occured requesting " + _target);
@@ -391,17 +385,12 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
boolean usingWWWProxy, String wwwProxy, long requestId) {
if (out == null)
return;
byte[] header;
if (usingWWWProxy)
header = getErrorPage(I2PAppContext.getGlobalContext(), "dnfp-header.ht", ERR_DESTINATION_UNKNOWN);
else
header = getErrorPage(I2PAppContext.getGlobalContext(), "dnf-header.ht", ERR_DESTINATION_UNKNOWN);
try {
String str;
byte[] header;
if (usingWWWProxy)
str = FileUtil.readTextFile((new File(_errorDir, "dnfp-header.ht")).getAbsolutePath(), 100, true);
else
str = FileUtil.readTextFile((new File(_errorDir, "dnf-header.ht")).getAbsolutePath(), 100, true);
if (str != null)
header = str.getBytes();
else
header = ERR_DESTINATION_UNKNOWN;
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy);
} catch (IOException ioe) {}
}

View File

@@ -1,17 +1,9 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* WTFPL
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
@@ -19,9 +11,8 @@
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
* ...for any additional details and license questions.
*/
package net.i2p.i2ptunnel;
// import java.util.ArrayList;

View File

@@ -3,9 +3,7 @@
*/
package net.i2p.i2ptunnel;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -73,10 +71,14 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
* via address helper links
*/
private final ConcurrentHashMap<String, String> addressHelpers = new ConcurrentHashMap(8);
/**
* Used to protect actions via http://proxy.i2p/
*/
private final String _proxyNonce;
public static final String AUTH_REALM = "I2P HTTP Proxy";
/**
* These are backups if the xxx.ht error page is missing.
*/
@@ -167,15 +169,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
"\r\n" +
"<html><body><H1>I2P ERROR: REQUEST DENIED</H1>" +
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>").getBytes();
private final static byte[] ERR_AUTH =
("HTTP/1.1 407 Proxy Authentication Required\r\n" +
"Content-Type: text/html; charset=UTF-8\r\n" +
"Cache-control: no-cache\r\n" +
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.5\r\n" + // try to get a UTF-8-encoded response back for the password
"Proxy-Authenticate: Basic realm=\"I2P HTTP Proxy\"\r\n" +
"\r\n" +
"<html><body><H1>I2P ERROR: PROXY AUTHENTICATION REQUIRED</H1>" +
"This proxy is configured to require authentication.<BR>").getBytes();
/**
* This constructor always starts the tunnel (ignoring the i2cp.delayOpen option).
@@ -300,6 +293,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
}
return rv;
}
/** @since 0.9.4 */
protected String getRealm() {
return AUTH_REALM;
}
private static final String HELPER_PARAM = "i2paddresshelper";
public static final String LOCAL_SERVER = "proxy.i2p";
private static final boolean DEFAULT_GZIP = true;
@@ -337,6 +336,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
String userAgent = null;
String authorization = null;
int remotePort = 0;
String referer = null;
while((line = reader.readLine(method)) != null) {
line = line.trim();
if(_log.shouldLog(Log.DEBUG)) {
@@ -745,12 +745,15 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
// browser to browser
line = null;
continue;
} else if(lowercaseLine.startsWith("referer: ") &&
!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_REFERER))) {
// Shouldn't we be more specific, like accepting in-site referers ?
//line = "Referer: i2p";
line = null;
continue; // completely strip the line
} else if (lowercaseLine.startsWith("referer: ")) {
// save for address helper form below
referer = line.substring(9);
if (!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_REFERER))) {
// Shouldn't we be more specific, like accepting in-site referers ?
//line = "Referer: i2p";
line = null;
continue; // completely strip the line
}
} else if(lowercaseLine.startsWith("via: ") &&
!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_VIA))) {
//line = "Via: i2p";
@@ -769,10 +772,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
// hop-by-hop header, and we definitely want to block Windows NTLM after a far-end 407.
// Response to far-end shouldn't happen, as we
// strip Proxy-Authenticate from the response in HTTPResponseOutputStream
if(lowercaseLine.startsWith("proxy-authorization: basic ")) // save for auth check below
{
authorization = line.substring(27); // "proxy-authorization: basic ".length()
}
authorization = line.substring(21); // "proxy-authorization: ".length()
line = null;
continue;
} else if(lowercaseLine.startsWith("icy")) {
@@ -850,7 +850,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
}
// Authorization
if(!authorize(s, requestId, authorization)) {
AuthResult result = authorize(s, requestId, method, authorization);
if (result != AuthResult.AUTH_GOOD) {
if(_log.shouldLog(Log.WARN)) {
if(authorization != null) {
_log.warn(getPrefix(requestId) + "Auth failed, sending 407 again");
@@ -858,7 +859,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
_log.warn(getPrefix(requestId) + "Auth required, sending 407");
}
}
out.write(getErrorPage("auth", ERR_AUTH));
out.write(getAuthError(result == AuthResult.AUTH_STALE).getBytes());
writeFooter(out);
s.close();
return;
@@ -950,7 +951,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
if(ahelperNew && "GET".equals(method) &&
(userAgent == null || !userAgent.startsWith("Wget")) &&
!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER))) {
writeHelperSaveForm(out, destination, ahelperKey, targetRequest);
writeHelperSaveForm(out, destination, ahelperKey, targetRequest, referer);
s.close();
return;
}
@@ -1014,7 +1015,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
}
/** @since 0.8.7 */
private void writeHelperSaveForm(OutputStream out, String destination, String ahelperKey, String targetRequest) throws IOException {
private void writeHelperSaveForm(OutputStream out, String destination, String ahelperKey,
String targetRequest, String referer) throws IOException {
if(out == null) {
return;
}
@@ -1045,6 +1047,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
out.write(("<br><button type=\"submit\" name=\"master\" value=\"master\">" + _("Save {0} to master address book and continue to eepsite", destination) + "</button><br>\n").getBytes("UTF-8"));
out.write(("<button type=\"submit\" name=\"private\" value=\"private\">" + _("Save {0} to private address book and continue to eepsite", destination) + "</button>\n").getBytes("UTF-8"));
}
// Firefox (and others?) don't send referer to meta refresh target, which is
// what the jump servers use, so this isn't that useful.
if (referer != null)
out.write(("<input type=\"hidden\" name=\"referer\" value=\"" + referer + "\">\n").getBytes("UTF-8"));
out.write(("<input type=\"hidden\" name=\"url\" value=\"" + targetRequest + "\">\n" +
"</form></div></div>").getBytes());
writeFooter(out);
@@ -1095,61 +1101,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
return Base32.encode(_dest.calculateHash().getData()) + ".b32.i2p";
}
/**
* foo => errordir/foo-header_xx.ht for lang xx, or errordir/foo-header.ht,
* or the backup byte array on fail.
*
* .ht files must be UTF-8 encoded and use \r\n terminators so the
* HTTP headers are conformant.
* We can't use FileUtil.readFile() because it strips \r
*
* @return non-null
*/
private byte[] getErrorPage(String base, byte[] backup) {
return getErrorPage(_context, base, backup);
}
private static byte[] getErrorPage(I2PAppContext ctx, String base, byte[] backup) {
File errorDir = new File(ctx.getBaseDir(), "docs");
String lang = ctx.getProperty("routerconsole.lang", Locale.getDefault().getLanguage());
if(lang != null && lang.length() > 0 && !lang.equals("en")) {
File file = new File(errorDir, base + "-header_" + lang + ".ht");
try {
return readFile(file);
} catch(IOException ioe) {
// try the english version now
}
}
File file = new File(errorDir, base + "-header.ht");
try {
return readFile(file);
} catch(IOException ioe) {
return backup;
}
}
private static byte[] readFile(File file) throws IOException {
FileInputStream fis = null;
byte[] buf = new byte[512];
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
try {
int len = 0;
fis = new FileInputStream(file);
while((len = fis.read(buf)) > 0) {
baos.write(buf, 0, len);
}
return baos.toByteArray();
} finally {
try {
if(fis != null) {
fis.close();
}
} catch(IOException foo) {
}
}
// we won't ever get here
}
/**
* Public only for LocalHTTPServer, not for general use
*/
@@ -1163,12 +1114,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
private static class OnTimeout implements Runnable {
private Socket _socket;
private OutputStream _out;
private String _target;
private boolean _usingProxy;
private String _wwwProxy;
private long _requestId;
private final Socket _socket;
private final OutputStream _out;
private final String _target;
private final boolean _usingProxy;
private final String _wwwProxy;
private final long _requestId;
public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy, String wwwProxy, long id) {
_socket = s;

View File

@@ -3,19 +3,26 @@
*/
package net.i2p.i2ptunnel;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.ArrayList;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import net.i2p.I2PAppContext;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.util.EventDispatcher;
import net.i2p.util.InternalSocket;
import net.i2p.util.Log;
import net.i2p.util.PasswordManager;
/**
* Common things for HTTPClient and ConnectClient
@@ -25,6 +32,25 @@ import net.i2p.util.Log;
*/
public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implements Runnable {
private static final int PROXYNONCE_BYTES = 8;
private static final int MD5_BYTES = 16;
/** 24 */
private static final int NONCE_BYTES = DataHelper.DATE_LENGTH + MD5_BYTES;
private static final long MAX_NONCE_AGE = 30*24*60*60*1000L;
private static final String ERR_AUTH1 =
"HTTP/1.1 407 Proxy Authentication Required\r\n" +
"Content-Type: text/html; charset=UTF-8\r\n" +
"Cache-control: no-cache\r\n" +
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.5\r\n" + // try to get a UTF-8-encoded response back for the password
"Proxy-Authenticate: ";
// put the auth type and realm in between
private static final String ERR_AUTH2 =
"\r\n" +
"\r\n" +
"<html><body><H1>I2P ERROR: PROXY AUTHENTICATION REQUIRED</H1>" +
"This proxy is configured to require authentication.";
protected final List<String> _proxyList;
protected final static byte[] ERR_NO_OUTPROXY =
@@ -40,7 +66,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
/** used to assign unique IDs to the threads / clients. no logic or functionality */
protected static volatile long __clientId = 0;
protected static final File _errorDir = new File(I2PAppContext.getGlobalContext().getBaseDir(), "docs");
private final byte[] _proxyNonce;
protected String getPrefix(long requestId) { return "Client[" + _clientId + "/" + requestId + "]: "; }
@@ -63,6 +89,8 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
I2PTunnel tunnel) throws IllegalArgumentException {
super(localPort, ownDest, l, notifyThis, handlerName, tunnel);
_proxyList = new ArrayList(4);
_proxyNonce = new byte[PROXYNONCE_BYTES];
_context.random().nextBytes(_proxyNonce);
}
/**
@@ -76,8 +104,12 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
throws IllegalArgumentException {
super(localPort, l, sktMgr, tunnel, notifyThis, clientId);
_proxyList = new ArrayList(4);
_proxyNonce = new byte[PROXYNONCE_BYTES];
_context.random().nextBytes(_proxyNonce);
}
//////// Authorization stuff
/** all auth @since 0.8.2 */
public static final String PROP_AUTH = "proxyAuth";
public static final String PROP_USER = "proxyUsername";
@@ -90,68 +122,349 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
/** passwords for specific outproxies may be added with outproxyUsername.fooproxy.i2p=user and outproxyPassword.fooproxy.i2p=pw */
public static final String PROP_OUTPROXY_USER_PREFIX = PROP_OUTPROXY_USER + '.';
public static final String PROP_OUTPROXY_PW_PREFIX = PROP_OUTPROXY_PW + '.';
/** new style MD5 auth */
public static final String PROP_PROXY_DIGEST_PREFIX = "proxy.auth.";
public static final String PROP_PROXY_DIGEST_SUFFIX = ".md5";
public static final String BASIC_AUTH = "basic";
public static final String DIGEST_AUTH = "digest";
protected abstract String getRealm();
protected enum AuthResult {AUTH_BAD_REQ, AUTH_BAD, AUTH_STALE, AUTH_GOOD}
/**
* @param authorization may be null
* @since 0.9.4
*/
protected boolean isDigestAuthRequired() {
String authRequired = getTunnel().getClientOptions().getProperty(PROP_AUTH);
if (authRequired == null)
return false;
return authRequired.toLowerCase(Locale.US).equals("digest");
}
/**
* Authorization
* Ref: RFC 2617
* If the socket is an InternalSocket, no auth required.
*
* @param method GET, POST, etc.
* @param authorization may be null, the full auth line e.g. "Basic lskjlksjf"
* @return success
*/
protected boolean authorize(Socket s, long requestId, String authorization) {
// Authorization
// Ref: RFC 2617
// If the socket is an InternalSocket, no auth required.
protected AuthResult authorize(Socket s, long requestId, String method, String authorization) {
String authRequired = getTunnel().getClientOptions().getProperty(PROP_AUTH);
if (Boolean.parseBoolean(authRequired) ||
(authRequired != null && "basic".equals(authRequired.toLowerCase(Locale.US)))) {
if (s instanceof InternalSocket) {
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix(requestId) + "Internal access, no auth required");
return true;
} else if (authorization != null) {
// hmm safeDecode(foo, true) to use standard alphabet is private in Base64
byte[] decoded = Base64.decode(authorization.replace("/", "~").replace("+", "="));
if (decoded != null) {
// We send Accept-Charset: UTF-8 in the 407 so hopefully it comes back that way inside the B64 ?
try {
String dec = new String(decoded, "UTF-8");
String[] parts = dec.split(":");
String user = parts[0];
String pw = parts[1];
// first try pw for that user
String configPW = getTunnel().getClientOptions().getProperty(PROP_PW_PREFIX + user);
if (configPW == null) {
// if not, look at default user and pw
String configUser = getTunnel().getClientOptions().getProperty(PROP_USER);
if (user.equals(configUser))
configPW = getTunnel().getClientOptions().getProperty(PROP_PW);
}
if (configPW != null) {
if (pw.equals(configPW)) {
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix(requestId) + "Good auth - user: " + user + " pw: " + pw);
return true;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix(requestId) + "Bad auth, pw mismatch - user: " + user + " pw: " + pw + " expected: " + configPW);
}
} else {
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix(requestId) + "Bad auth, no stored pw for user: " + user + " pw: " + pw);
}
} catch (UnsupportedEncodingException uee) {
_log.error(getPrefix(requestId) + "No UTF-8 support? B64: " + authorization, uee);
} catch (ArrayIndexOutOfBoundsException aioobe) {
// no ':' in response
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix(requestId) + "Bad auth B64: " + authorization, aioobe);
if (authRequired == null)
return AuthResult.AUTH_GOOD;
authRequired = authRequired.toLowerCase(Locale.US);
if (authRequired.equals("false"))
return AuthResult.AUTH_GOOD;
if (s instanceof InternalSocket) {
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix(requestId) + "Internal access, no auth required");
return AuthResult.AUTH_GOOD;
}
if (authorization == null)
return AuthResult.AUTH_BAD;
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix(requestId) + "Auth: " + authorization);
String authLC = authorization.toLowerCase(Locale.US);
if (authRequired.equals("true") || authRequired.equals(BASIC_AUTH)) {
if (!authLC.startsWith("basic "))
return AuthResult.AUTH_BAD;
authorization = authorization.substring(6);
// hmm safeDecode(foo, true) to use standard alphabet is private in Base64
byte[] decoded = Base64.decode(authorization.replace("/", "~").replace("+", "="));
if (decoded != null) {
// We send Accept-Charset: UTF-8 in the 407 so hopefully it comes back that way inside the B64 ?
try {
String dec = new String(decoded, "UTF-8");
String[] parts = dec.split(":");
String user = parts[0];
String pw = parts[1];
// first try pw for that user
String configPW = getTunnel().getClientOptions().getProperty(PROP_PW_PREFIX + user);
if (configPW == null) {
// if not, look at default user and pw
String configUser = getTunnel().getClientOptions().getProperty(PROP_USER);
if (user.equals(configUser))
configPW = getTunnel().getClientOptions().getProperty(PROP_PW);
}
} else {
if (configPW != null) {
if (pw.equals(configPW)) {
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix(requestId) + "Good auth - user: " + user + " pw: " + pw);
return AuthResult.AUTH_GOOD;
}
}
_log.logAlways(Log.WARN, "PROXY AUTH FAILURE: user " + user);
} catch (UnsupportedEncodingException uee) {
_log.error(getPrefix(requestId) + "No UTF-8 support? B64: " + authorization, uee);
} catch (ArrayIndexOutOfBoundsException aioobe) {
// no ':' in response
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix(requestId) + "Bad auth B64: " + authorization);
_log.warn(getPrefix(requestId) + "Bad auth B64: " + authorization, aioobe);
return AuthResult.AUTH_BAD_REQ;
}
return AuthResult.AUTH_BAD;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix(requestId) + "Bad auth B64: " + authorization);
return AuthResult.AUTH_BAD_REQ;
}
return false;
} else if (authRequired.equals(DIGEST_AUTH)) {
if (!authLC.startsWith("digest "))
return AuthResult.AUTH_BAD;
authorization = authorization.substring(7);
Map<String, String> args = parseArgs(authorization);
AuthResult rv = validateDigest(method, args);
return rv;
} else {
return true;
_log.error("Unknown proxy authorization type configured: " + authRequired);
return AuthResult.AUTH_BAD_REQ;
}
}
/**
* Verify all of it.
* Ref: RFC 2617
* @since 0.9.4
*/
private AuthResult validateDigest(String method, Map<String, String> args) {
String user = args.get("username");
String realm = args.get("realm");
String nonce = args.get("nonce");
String qop = args.get("qop");
String uri = args.get("uri");
String cnonce = args.get("cnonce");
String nc = args.get("nc");
String response = args.get("response");
if (user == null || realm == null || nonce == null || qop == null ||
uri == null || cnonce == null || nc == null || response == null) {
if (_log.shouldLog(Log.INFO))
_log.info("Bad digest request: " + DataHelper.toString(args));
return AuthResult.AUTH_BAD_REQ;
}
// nonce check
AuthResult check = verifyNonce(nonce);
if (check != AuthResult.AUTH_GOOD) {
if (_log.shouldLog(Log.INFO))
_log.info("Bad digest nonce: " + check + ' ' + DataHelper.toString(args));
return check;
}
// get H(A1) == stored password
String ha1 = getTunnel().getClientOptions().getProperty(PROP_PROXY_DIGEST_PREFIX + user +
PROP_PROXY_DIGEST_SUFFIX);
if (ha1 == null) {
_log.logAlways(Log.WARN, "PROXY AUTH FAILURE: user " + user);
return AuthResult.AUTH_BAD;
}
// get H(A2)
String a2 = method + ':' + uri;
String ha2 = PasswordManager.md5Hex(a2);
// response check
String kd = ha1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2;
String hkd = PasswordManager.md5Hex(kd);
if (!response.equals(hkd)) {
_log.logAlways(Log.WARN, "PROXY AUTH FAILURE: user " + user);
if (_log.shouldLog(Log.INFO))
_log.info("Bad digest auth: " + DataHelper.toString(args));
return AuthResult.AUTH_BAD;
}
if (_log.shouldLog(Log.INFO))
_log.info("Good digest auth - user: " + user);
return AuthResult.AUTH_GOOD;
}
/**
* The Base 64 of 24 bytes: (now, md5 of (now, proxy nonce))
* @since 0.9.4
*/
private String getNonce() {
byte[] b = new byte[DataHelper.DATE_LENGTH + PROXYNONCE_BYTES];
byte[] n = new byte[NONCE_BYTES];
long now = _context.clock().now();
DataHelper.toLong(b, 0, DataHelper.DATE_LENGTH, now);
System.arraycopy(_proxyNonce, 0, b, DataHelper.DATE_LENGTH, PROXYNONCE_BYTES);
System.arraycopy(b, 0, n, 0, DataHelper.DATE_LENGTH);
byte[] md5 = PasswordManager.md5Sum(b);
System.arraycopy(md5, 0, n, DataHelper.DATE_LENGTH, MD5_BYTES);
return Base64.encode(n);
}
/**
* Verify the Base 64 of 24 bytes: (now, md5 of (now, proxy nonce))
* @since 0.9.4
*/
private AuthResult verifyNonce(String b64) {
byte[] n = Base64.decode(b64);
if (n == null || n.length != NONCE_BYTES)
return AuthResult.AUTH_BAD;
long now = _context.clock().now();
long stamp = DataHelper.fromLong(n, 0, DataHelper.DATE_LENGTH);
if (now - stamp > MAX_NONCE_AGE)
return AuthResult.AUTH_STALE;
byte[] b = new byte[DataHelper.DATE_LENGTH + PROXYNONCE_BYTES];
System.arraycopy(n, 0, b, 0, DataHelper.DATE_LENGTH);
System.arraycopy(_proxyNonce, 0, b, DataHelper.DATE_LENGTH, PROXYNONCE_BYTES);
byte[] md5 = PasswordManager.md5Sum(b);
if (!DataHelper.eq(md5, 0, n, DataHelper.DATE_LENGTH, MD5_BYTES))
return AuthResult.AUTH_BAD;
return AuthResult.AUTH_GOOD;
}
/**
* What to send if digest auth fails
* @since 0.9.4
*/
protected String getAuthError(boolean isStale) {
boolean isDigest = isDigestAuthRequired();
return
ERR_AUTH1 +
(isDigest ? "Digest" : "Basic") +
" realm=\"" + getRealm() + '"' +
(isDigest ? ", nonce=\"" + getNonce() + "\"," +
" algorithm=MD5," +
" qop=\"auth\"" +
(isStale ? ", stale=true" : "")
: "") +
ERR_AUTH2;
}
/**
* Modified from LoadClientAppsJob.
* All keys are mapped to lower case.
* Ref: RFC 2617
*
* @param args non-null
* @since 0.9.4
*/
private static Map<String, String> parseArgs(String args) {
Map<String, String> rv = new HashMap(8);
char data[] = args.toCharArray();
StringBuilder buf = new StringBuilder(32);
boolean isQuoted = false;
String key = null;
for (int i = 0; i < data.length; i++) {
switch (data[i]) {
case '\"':
if (isQuoted) {
// keys never quoted
if (key != null) {
rv.put(key, buf.toString().trim());
key = null;
}
buf.setLength(0);
}
isQuoted = !isQuoted;
break;
case ' ':
case '\r':
case '\n':
case '\t':
case ',':
// whitespace - if we're in a quoted section, keep this as part of the quote,
// otherwise use it as a delim
if (isQuoted) {
buf.append(data[i]);
} else {
if (key != null) {
rv.put(key, buf.toString().trim());
key = null;
}
buf.setLength(0);
}
break;
case '=':
if (isQuoted) {
buf.append(data[i]);
} else {
key = buf.toString().trim().toLowerCase(Locale.US);
buf.setLength(0);
}
break;
default:
buf.append(data[i]);
break;
}
}
if (key != null)
rv.put(key, buf.toString().trim());
return rv;
}
//////// Error page stuff
/**
* foo => errordir/foo-header_xx.ht for lang xx, or errordir/foo-header.ht,
* or the backup byte array on fail.
*
* .ht files must be UTF-8 encoded and use \r\n terminators so the
* HTTP headers are conformant.
* We can't use FileUtil.readFile() because it strips \r
*
* @return non-null
* @since 0.9.4 moved from I2PTunnelHTTPClient
*/
protected byte[] getErrorPage(String base, byte[] backup) {
return getErrorPage(_context, base, backup);
}
/**
* foo => errordir/foo-header_xx.ht for lang xx, or errordir/foo-header.ht,
* or the backup byte array on fail.
*
* .ht files must be UTF-8 encoded and use \r\n terminators so the
* HTTP headers are conformant.
* We can't use FileUtil.readFile() because it strips \r
*
* @return non-null
* @since 0.9.4 moved from I2PTunnelHTTPClient
*/
protected static byte[] getErrorPage(I2PAppContext ctx, String base, byte[] backup) {
File errorDir = new File(ctx.getBaseDir(), "docs");
String lang = ctx.getProperty("routerconsole.lang", Locale.getDefault().getLanguage());
if(lang != null && lang.length() > 0 && !lang.equals("en")) {
File file = new File(errorDir, base + "-header_" + lang + ".ht");
try {
return readFile(file);
} catch(IOException ioe) {
// try the english version now
}
}
File file = new File(errorDir, base + "-header.ht");
try {
return readFile(file);
} catch(IOException ioe) {
return backup;
}
}
/**
* @since 0.9.4 moved from I2PTunnelHTTPClient
*/
private static byte[] readFile(File file) throws IOException {
FileInputStream fis = null;
byte[] buf = new byte[2048];
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
try {
int len = 0;
fis = new FileInputStream(file);
while((len = fis.read(buf)) > 0) {
baos.write(buf, 0, len);
}
return baos.toByteArray();
} finally {
try {
if(fis != null) {
fis.close();
}
} catch(IOException foo) {
}
}
// we won't ever get here
}
}

View File

@@ -79,11 +79,9 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
public I2PTunnelIRCServer(InetAddress host, int port, File privkey, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privkey, privkeyname, l, notifyThis, tunnel);
initCloak(tunnel);
}
/** generate a random 32 bytes, or the hash of the passphrase */
private void initCloak(I2PTunnel tunnel) {
// generate a random 32 bytes, or the hash of the passphrase
// get the properties of this server-tunnel
Properties opts = tunnel.getClientOptions();
@@ -236,9 +234,9 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
return buf.toString();
}
private byte[] cloakKey; // 32 bytes of stuff to scramble the dest with
private String hostname;
private String method;
private String webircPassword;
private String webircSpoofIP;
private final byte[] cloakKey; // 32 bytes of stuff to scramble the dest with
private final String hostname;
private final String method;
private final String webircPassword;
private final String webircSpoofIP;
}

View File

@@ -19,8 +19,10 @@ import net.i2p.client.I2PSession;
import net.i2p.data.Base32;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.socks.I2PSOCKSTunnel;
import net.i2p.util.FileUtil;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.SecureFile;
import net.i2p.util.SecureFileOutputStream;
/**
@@ -43,6 +45,8 @@ public class TunnelController implements Logging {
private boolean _running;
private boolean _starting;
public static final String KEY_BACKUP_DIR = "i2ptunnel-keyBackup";
/**
* Create a new controller for a tunnel out of the specific config options.
* The config may contain a large number of options - only ones that begin in
@@ -102,8 +106,17 @@ public class TunnelController implements Logging {
Destination dest = client.createDestination(fos);
String destStr = dest.toBase64();
log("Private key created and saved in " + keyFile.getAbsolutePath());
log("You should backup this file in a secure place.");
log("New destination: " + destStr);
log("Base32: " + Base32.encode(dest.calculateHash().getData()) + ".b32.i2p");
String b32 = Base32.encode(dest.calculateHash().getData()) + ".b32.i2p";
log("Base32: " + b32);
File backupDir = new SecureFile(I2PAppContext.getGlobalContext().getConfigDir(), KEY_BACKUP_DIR);
if (backupDir.isDirectory() || backupDir.mkdir()) {
String name = b32 + '-' + I2PAppContext.getGlobalContext().clock().now() + ".dat";
File backup = new File(backupDir, name);
if (FileUtil.copy(keyFile, backup, false, true))
log("Private key backup saved to " + backup.getAbsolutePath());
}
} catch (I2PException ie) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error creating new destination", ie);
@@ -307,7 +320,9 @@ public class TunnelController implements Logging {
I2PSession session = sessions.get(i);
if (_log.shouldLog(Log.INFO))
_log.info("Acquiring session " + session);
TunnelControllerGroup.getInstance().acquire(this, session);
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
if (group != null)
group.acquire(this, session);
}
_sessions = sessions;
} else {
@@ -326,7 +341,9 @@ public class TunnelController implements Logging {
I2PSession s = _sessions.get(i);
if (_log.shouldLog(Log.INFO))
_log.info("Releasing session " + s);
TunnelControllerGroup.getInstance().release(this, s);
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
if (group != null)
group.release(this, s);
}
// _sessions.clear() ????
} else {

View File

@@ -12,27 +12,35 @@ import java.util.Properties;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.app.*;
import static net.i2p.app.ClientAppState.*;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.data.DataHelper;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
import net.i2p.util.SystemVersion;
/**
* Coordinate a set of tunnels within the JVM, loading and storing their config
* to disk, and building new ones as requested.
*
* Warning - this is a singleton. Todo: fix
* This is the entry point from clients.config.
*/
public class TunnelControllerGroup {
private Log _log;
private static TunnelControllerGroup _instance;
public class TunnelControllerGroup implements ClientApp {
private final Log _log;
private volatile ClientAppState _state;
private final I2PAppContext _context;
private final ClientAppManager _mgr;
private static volatile TunnelControllerGroup _instance;
static final String DEFAULT_CONFIG_FILE = "i2ptunnel.config";
private final List<TunnelController> _controllers;
private String _configFile = DEFAULT_CONFIG_FILE;
private final String _configFile;
private static final String REGISTERED_NAME = "i2ptunnel";
/**
* Map of I2PSession to a Set of TunnelController objects
* using the session (to prevent closing the session until
@@ -41,48 +49,143 @@ public class TunnelControllerGroup {
*/
private final Map<I2PSession, Set<TunnelController>> _sessions;
/**
* In I2PAppContext will instantiate if necessary and always return non-null.
* As of 0.9.4, when in RouterContext, will return null (except in Android)
* if the TCG has not yet been started by the router.
*
* @throws IllegalArgumentException if unable to load from i2ptunnel.config
*/
public static TunnelControllerGroup getInstance() {
synchronized (TunnelControllerGroup.class) {
if (_instance == null)
_instance = new TunnelControllerGroup(DEFAULT_CONFIG_FILE);
if (_instance == null) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
if (SystemVersion.isAndroid() || !ctx.isRouterContext()) {
_instance = new TunnelControllerGroup(ctx, null, null);
_instance.startup();
} // else wait for the router to start it
}
return _instance;
}
}
private TunnelControllerGroup(String configFile) {
_log = I2PAppContext.getGlobalContext().logManager().getLog(TunnelControllerGroup.class);
_controllers = Collections.synchronizedList(new ArrayList());
_configFile = configFile;
/**
* Instantiation only. Caller must call startup().
* Config file problems will not throw exception until startup().
*
* @param mgr may be null
* @param args one arg, the config file, if not absolute will be relative to the context's config dir,
* if empty or null, the default is i2ptunnel.config
* @since 0.9.4
*/
public TunnelControllerGroup(I2PAppContext context, ClientAppManager mgr, String[] args) {
_state = UNINITIALIZED;
_context = context;
_mgr = mgr;
_log = _context.logManager().getLog(TunnelControllerGroup.class);
_controllers = new ArrayList();
if (args == null || args.length <= 0)
_configFile = DEFAULT_CONFIG_FILE;
else if (args.length == 1)
_configFile = args[0];
else
throw new IllegalArgumentException("Usage: TunnelControllerGroup [filename]");
_sessions = new HashMap(4);
loadControllers(_configFile);
I2PAppContext.getGlobalContext().addShutdownTask(new Shutdown());
synchronized (TunnelControllerGroup.class) {
if (_instance == null)
_instance = this;
}
if (_instance != this) {
_log.logAlways(Log.WARN, "New TunnelControllerGroup, now you have two");
if (_log.shouldLog(Log.WARN))
_log.warn("I did it", new Exception());
}
_state = INITIALIZED;
}
/**
* @param args one arg, the config file, if not absolute will be relative to the context's config dir,
* if no args, the default is i2ptunnel.config
* @throws IllegalArgumentException if unable to load from config from file
*/
public static void main(String args[]) {
synchronized (TunnelControllerGroup.class) {
if (_instance != null) return; // already loaded through the web
if ( (args == null) || (args.length <= 0) ) {
_instance = new TunnelControllerGroup(DEFAULT_CONFIG_FILE);
} else if (args.length == 1) {
_instance = new TunnelControllerGroup(args[0]);
} else {
System.err.println("Usage: TunnelControllerGroup [filename]");
return;
}
_instance = new TunnelControllerGroup(I2PAppContext.getGlobalContext(), null, args);
_instance.startup();
}
}
/**
* ClientApp interface
* @throws IllegalArgumentException if unable to load config from file
* @since 0.9.4
*/
public void startup() {
loadControllers(_configFile);
if (_mgr != null)
_mgr.register(this);
_context.addShutdownTask(new Shutdown());
}
/**
* ClientApp interface
* @since 0.9.4
*/
public ClientAppState getState() {
return _state;
}
/**
* ClientApp interface
* @since 0.9.4
*/
public String getName() {
return REGISTERED_NAME;
}
/**
* ClientApp interface
* @since 0.9.4
*/
public String getDisplayName() {
return REGISTERED_NAME;
}
/**
* @since 0.9.4
*/
private void changeState(ClientAppState state) {
changeState(state, null);
}
/**
* @since 0.9.4
*/
private synchronized void changeState(ClientAppState state, Exception e) {
_state = state;
if (_mgr != null)
_mgr.notify(this, state, null, e);
}
/**
* Warning - destroys the singleton!
* @since 0.8.8
*/
private static class Shutdown implements Runnable {
private class Shutdown implements Runnable {
public void run() {
shutdown();
}
}
/**
* ClientApp interface
* @since 0.9.4
*/
public void shutdown(String[] args) {
shutdown();
}
/**
* Warning - destroys the singleton!
* Caller must root a new context before calling instance() or main() again.
@@ -91,28 +194,31 @@ public class TunnelControllerGroup {
*
* @since 0.8.8
*/
public static void shutdown() {
public void shutdown() {
changeState(STOPPING);
if (_mgr != null)
_mgr.unregister(this);
unloadControllers();
synchronized (TunnelControllerGroup.class) {
if (_instance == null) return;
_instance.unloadControllers();
_instance._log = null;
_instance = null;
if (_instance == this)
_instance = null;
}
/// fixme static
I2PTunnelClientBase.killClientExecutor();
changeState(STOPPED);
}
/**
* Load up all of the tunnels configured in the given file (but do not start
* them)
*
* DEPRECATED for use outside this class. Use startup() or getInstance().
*
* @throws IllegalArgumentException if unable to load from file
*/
public void loadControllers(String configFile) {
public synchronized void loadControllers(String configFile) {
changeState(STARTING);
Properties cfg = loadConfig(configFile);
if (cfg == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to load the config from " + configFile);
return;
}
int i = 0;
while (true) {
String type = cfg.getProperty("tunnel." + i + ".type");
@@ -127,20 +233,28 @@ public class TunnelControllerGroup {
if (_log.shouldLog(Log.INFO))
_log.info(i + " controllers loaded from " + configFile);
changeState(RUNNING);
}
private class StartControllers implements Runnable {
public void run() {
for (int i = 0; i < _controllers.size(); i++) {
TunnelController controller = _controllers.get(i);
if (controller.getStartOnLoad())
controller.startTunnel();
synchronized(TunnelControllerGroup.this) {
for (int i = 0; i < _controllers.size(); i++) {
TunnelController controller = _controllers.get(i);
if (controller.getStartOnLoad())
controller.startTunnel();
}
}
}
}
public void reloadControllers() {
/**
* Stop all tunnels, reload config, and restart those configured to do so.
* WARNING - Does NOT simply reload the configuration!!! This is probably not what you want.
*
* @throws IllegalArgumentException if unable to reload config file
*/
public synchronized void reloadControllers() {
unloadControllers();
loadControllers(_configFile);
}
@@ -150,7 +264,7 @@ public class TunnelControllerGroup {
* file or do other silly things)
*
*/
public void unloadControllers() {
public synchronized void unloadControllers() {
stopAllControllers();
_controllers.clear();
if (_log.shouldLog(Log.INFO))
@@ -162,14 +276,14 @@ public class TunnelControllerGroup {
* a config file or start it or anything)
*
*/
public void addController(TunnelController controller) { _controllers.add(controller); }
public synchronized void addController(TunnelController controller) { _controllers.add(controller); }
/**
* Stop and remove the given tunnel
*
* @return list of messages from the controller as it is stopped
*/
public List<String> removeController(TunnelController controller) {
public synchronized List<String> removeController(TunnelController controller) {
if (controller == null) return new ArrayList();
controller.stopTunnel();
List<String> msgs = controller.clearMessages();
@@ -183,7 +297,7 @@ public class TunnelControllerGroup {
*
* @return list of messages the tunnels generate when stopped
*/
public List<String> stopAllControllers() {
public synchronized List<String> stopAllControllers() {
List<String> msgs = new ArrayList();
for (int i = 0; i < _controllers.size(); i++) {
TunnelController controller = _controllers.get(i);
@@ -200,7 +314,7 @@ public class TunnelControllerGroup {
*
* @return list of messages the tunnels generate when started
*/
public List<String> startAllControllers() {
public synchronized List<String> startAllControllers() {
List<String> msgs = new ArrayList();
for (int i = 0; i < _controllers.size(); i++) {
TunnelController controller = _controllers.get(i);
@@ -218,7 +332,7 @@ public class TunnelControllerGroup {
*
* @return list of messages the tunnels generate when restarted
*/
public List<String> restartAllControllers() {
public synchronized List<String> restartAllControllers() {
List<String> msgs = new ArrayList();
for (int i = 0; i < _controllers.size(); i++) {
TunnelController controller = _controllers.get(i);
@@ -235,7 +349,7 @@ public class TunnelControllerGroup {
*
* @return list of messages the tunnels have generated
*/
public List<String> clearAllMessages() {
public synchronized List<String> clearAllMessages() {
List<String> msgs = new ArrayList();
for (int i = 0; i < _controllers.size(); i++) {
TunnelController controller = _controllers.get(i);
@@ -257,8 +371,7 @@ public class TunnelControllerGroup {
* Save the configuration of all known tunnels to the given file
*
*/
public void saveConfig(String configFile) throws IOException {
_configFile = configFile;
public synchronized void saveConfig(String configFile) throws IOException {
File cfgFile = new File(configFile);
if (!cfgFile.isAbsolute())
cfgFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), configFile);
@@ -279,16 +392,17 @@ public class TunnelControllerGroup {
/**
* Load up the config data from the file
*
* @return properties loaded or null if there was an error
* @return properties loaded
* @throws IllegalArgumentException if unable to load from file
*/
private Properties loadConfig(String configFile) {
private synchronized Properties loadConfig(String configFile) {
File cfgFile = new File(configFile);
if (!cfgFile.isAbsolute())
cfgFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), configFile);
if (!cfgFile.exists()) {
if (_log.shouldLog(Log.ERROR))
_log.error("Unable to load the controllers from " + cfgFile.getAbsolutePath());
return null;
throw new IllegalArgumentException("Unable to load the controllers from " + cfgFile.getAbsolutePath());
}
Properties props = new Properties();
@@ -298,7 +412,7 @@ public class TunnelControllerGroup {
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error reading the controllers from " + cfgFile.getAbsolutePath(), ioe);
return null;
throw new IllegalArgumentException("Error reading the controllers from " + cfgFile.getAbsolutePath(), ioe);
}
}
@@ -307,7 +421,9 @@ public class TunnelControllerGroup {
*
* @return list of TunnelController objects
*/
public List<TunnelController> getControllers() { return _controllers; }
public synchronized List<TunnelController> getControllers() {
return new ArrayList(_controllers);
}
/**

View File

@@ -17,6 +17,7 @@ import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
import net.i2p.util.Translate;
/**
@@ -133,6 +134,7 @@ public abstract class LocalHTTPServer {
String host = opts.get("host");
String b64Dest = opts.get("dest");
String nonce = opts.get("nonce");
String referer = opts.get("referer");
String book = "privatehosts.txt";
if (opts.get("master") != null)
book = "userhosts.txt";
@@ -156,7 +158,12 @@ public abstract class LocalHTTPServer {
NamingService ns = I2PAppContext.getGlobalContext().namingService();
Properties nsOptions = new Properties();
nsOptions.setProperty("list", book);
nsOptions.setProperty("s", _("Added via address helper"));
if (referer != null && referer.startsWith("http")) {
String from = "<a href=\"" + referer + "\">" + referer + "</a>";
nsOptions.setProperty("s", _("Added via address helper from {0}", from));
} else {
nsOptions.setProperty("s", _("Added via address helper"));
}
boolean success = ns.put(host, dest, nsOptions);
writeRedirectPage(out, success, host, book, url);
return;

View File

@@ -15,6 +15,7 @@ import net.i2p.i2ptunnel.irc.IrcOutboundFilter;
import net.i2p.i2ptunnel.Logging;
import net.i2p.util.EventDispatcher;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
/*
* Pipe SOCKS IRC connections through I2PTunnelIRCClient filtering,
@@ -56,7 +57,8 @@ public class I2PSOCKSIRCTunnel extends I2PSOCKSTunnel {
"SOCKS IRC Client " + __clientId + " out", true);
out.start();
} catch (SOCKSException e) {
_log.error("Error from SOCKS connection", e);
if (_log.shouldLog(Log.WARN))
_log.warn("Error from SOCKS connection", e);
closeSocket(s);
}
}

View File

@@ -22,6 +22,7 @@ import net.i2p.i2ptunnel.I2PTunnelClientBase;
import net.i2p.i2ptunnel.I2PTunnelRunner;
import net.i2p.i2ptunnel.Logging;
import net.i2p.util.EventDispatcher;
import net.i2p.util.Log;
public class I2PSOCKSTunnel extends I2PTunnelClientBase {
@@ -55,7 +56,8 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase {
I2PSocket destSock = serv.getDestinationI2PSocket(this);
new I2PTunnelRunner(clientSock, destSock, sockLock, null, mySockets);
} catch (SOCKSException e) {
_log.error("Error from SOCKS connection", e);
if (_log.shouldLog(Log.WARN))
_log.warn("Error from SOCKS connection", e);
closeSocket(s);
}
}

View File

@@ -29,8 +29,8 @@ import net.i2p.util.Addresses;
/**
* Ugly little accessor for the edit page
*
* Warning - This class is not part of the i2ptunnel API, and at some point
* it will be moved from the jar to the war.
* Warning - This class is not part of the i2ptunnel API,
* it has been moved from the jar to the war.
* Usage by classes outside of i2ptunnel.war is deprecated.
*/
public class EditBean extends IndexBean {
@@ -38,6 +38,8 @@ public class EditBean extends IndexBean {
public static boolean staticIsClient(int tunnel) {
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
if (group == null)
return false;
List controllers = group.getControllers();
if (controllers.size() > tunnel) {
TunnelController cur = (TunnelController)controllers.get(tunnel);
@@ -55,6 +57,7 @@ public class EditBean extends IndexBean {
else
return "127.0.0.1";
}
public String getTargetPort(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null && tun.getTargetPort() != null)
@@ -62,6 +65,7 @@ public class EditBean extends IndexBean {
else
return "";
}
public String getSpoofedHost(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null && tun.getSpoofedHost() != null)
@@ -69,12 +73,13 @@ public class EditBean extends IndexBean {
else
return "";
}
public String getPrivateKeyFile(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null && tun.getPrivKeyFile() != null)
return tun.getPrivKeyFile();
if (tunnel < 0)
tunnel = _group.getControllers().size();
tunnel = _group == null ? 999 : _group.getControllers().size();
return "i2ptunnel" + tunnel + "-privKeys.dat";
}
@@ -221,19 +226,7 @@ public class EditBean extends IndexBean {
/** all proxy auth @since 0.8.2 */
public boolean getProxyAuth(int tunnel) {
return getBooleanProperty(tunnel, I2PTunnelHTTPClientBase.PROP_AUTH) &&
getProxyUsername(tunnel).length() > 0 &&
getProxyPassword(tunnel).length() > 0;
}
public String getProxyUsername(int tunnel) {
return getProperty(tunnel, I2PTunnelHTTPClientBase.PROP_USER, "");
}
public String getProxyPassword(int tunnel) {
if (getProxyUsername(tunnel).length() <= 0)
return "";
return getProperty(tunnel, I2PTunnelHTTPClientBase.PROP_PW, "");
return getProperty(tunnel, I2PTunnelHTTPClientBase.PROP_AUTH, "false") != "false";
}
public boolean getOutproxyAuth(int tunnel) {
@@ -354,10 +347,17 @@ public class EditBean extends IndexBean {
if (opts == null) return "";
StringBuilder buf = new StringBuilder(64);
int i = 0;
boolean isMD5Proxy = "httpclient".equals(tun.getType()) ||
"connectclient".equals(tun.getType());
for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
if (_noShowSet.contains(key))
continue;
// leave in for HTTP and Connect so it can get migrated to MD5
// hide for SOCKS until migrated to MD5
if ((!isMD5Proxy) &&
_nonProxyNoShowSet.contains(key))
continue;
String val = opts.getProperty(key);
if (i != 0) buf.append(' ');
buf.append(key).append('=').append(val);

View File

@@ -27,6 +27,7 @@ import net.i2p.data.Certificate;
import net.i2p.data.Destination;
import net.i2p.data.PrivateKeyFile;
import net.i2p.data.SessionKey;
import net.i2p.i2ptunnel.I2PTunnelConnectClient;
import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
import net.i2p.i2ptunnel.I2PTunnelIRCClient;
@@ -36,18 +37,21 @@ import net.i2p.util.Addresses;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
import net.i2p.util.PasswordManager;
import net.i2p.util.SecureFile;
/**
* Simple accessor for exposing tunnel info, but also an ugly form handler
*
* Warning - This class is not part of the i2ptunnel API, and at some point
* it will be moved from the jar to the war.
* Warning - This class is not part of the i2ptunnel API,
* it has been moved from the jar to the war.
* Usage by classes outside of i2ptunnel.war is deprecated.
*/
public class IndexBean {
protected final I2PAppContext _context;
protected final Log _log;
protected final TunnelControllerGroup _group;
private final String _fatalError;
private String _action;
private int _tunnel;
//private long _prevNonce;
@@ -83,15 +87,14 @@ public class IndexBean {
private int _hashCashValue;
private int _certType;
private String _certSigner;
private String _newProxyUser;
private String _newProxyPW;
public static final int RUNNING = 1;
public static final int STARTING = 2;
public static final int NOT_RUNNING = 3;
public static final int STANDBY = 4;
/** deprecated unimplemented, now using routerconsole realm */
//public static final String PROP_TUNNEL_PASSPHRASE = "i2ptunnel.passphrase";
public static final String PROP_TUNNEL_PASSPHRASE = "consolePassword";
//static final String PROP_NONCE = IndexBean.class.getName() + ".nonce";
//static final String PROP_NONCE_OLD = PROP_NONCE + '2';
/** 3 wasn't enough for some browsers. They are reloading the page for some reason - maybe HEAD? @since 0.8.1 */
@@ -104,11 +107,23 @@ public class IndexBean {
public static final String DEFAULT_THEME = "light";
public static final String PROP_CSS_DISABLED = "routerconsole.css.disabled";
public static final String PROP_JS_DISABLED = "routerconsole.javascript.disabled";
private static final String PROP_PW_ENABLE = "routerconsole.auth.enable";
public IndexBean() {
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(IndexBean.class);
_group = TunnelControllerGroup.getInstance();
TunnelControllerGroup tcg;
String error;
try {
tcg = TunnelControllerGroup.getInstance();
error = tcg == null ? _("Tunnels are not initialized yet, please reload in two minutes.")
: null;
} catch (IllegalArgumentException iae) {
tcg = null;
error = iae.toString();
}
_group = tcg;
_fatalError = error;
_tunnel = -1;
_curNonce = "-1";
addNonce();
@@ -116,6 +131,13 @@ public class IndexBean {
_otherOptions = new ConcurrentHashMap(4);
}
/**
* @since 0.9.4
*/
public boolean isInitialized() {
return _group != null;
}
public static String getNextNonce() {
synchronized (_nonces) {
return _nonces.get(0);
@@ -145,14 +167,11 @@ public class IndexBean {
}
}
/** deprecated unimplemented, now using routerconsole realm */
public void setPassphrase(String phrase) {
}
public void setAction(String action) {
if ( (action == null) || (action.trim().length() <= 0) ) return;
_action = action;
}
public void setTunnel(String tunnel) {
if ( (tunnel == null) || (tunnel.trim().length() <= 0) ) return;
try {
@@ -162,17 +181,17 @@ public class IndexBean {
}
}
/** just check if console password option is set, jetty will do auth */
private boolean validPassphrase() {
String pass = _context.getProperty(PROP_TUNNEL_PASSPHRASE);
return pass != null && pass.trim().length() > 0;
}
private String processAction() {
if ( (_action == null) || (_action.trim().length() <= 0) || ("Cancel".equals(_action)))
return "";
if ( (!haveNonce(_curNonce)) && (!validPassphrase()) )
return _("Invalid form submission, probably because you used the 'back' or 'reload' button on your browser. Please resubmit.");
if (_group == null)
return "Error - tunnels are not initialized yet";
// If passwords are turned on, all is assumed good
if (!_context.getBooleanProperty(PROP_PW_ENABLE) &&
!haveNonce(_curNonce))
return _("Invalid form submission, probably because you used the 'back' or 'reload' button on your browser. Please resubmit.")
+ ' ' +
_("If the problem persists, verify that you have cookies enabled in your browser.");
if ("Stop all".equals(_action))
return stopAll();
else if ("Start all".equals(_action))
@@ -200,33 +219,33 @@ public class IndexBean {
else
return "Action " + _action + " unknown";
}
private String stopAll() {
if (_group == null) return "";
List<String> msgs = _group.stopAllControllers();
return getMessages(msgs);
}
private String startAll() {
if (_group == null) return "";
List<String> msgs = _group.startAllControllers();
return getMessages(msgs);
}
private String restartAll() {
if (_group == null) return "";
List<String> msgs = _group.restartAllControllers();
return getMessages(msgs);
}
private String reloadConfig() {
if (_group == null) return "";
_group.reloadControllers();
return _("Configuration reloaded for all tunnels");
}
private String start() {
if (_tunnel < 0) return "Invalid tunnel";
List controllers = _group.getControllers();
List<TunnelController> controllers = _group.getControllers();
if (_tunnel >= controllers.size()) return "Invalid tunnel";
TunnelController controller = (TunnelController)controllers.get(_tunnel);
TunnelController controller = controllers.get(_tunnel);
controller.startTunnelBackground();
// give the messages a chance to make it to the window
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
@@ -237,9 +256,9 @@ public class IndexBean {
private String stop() {
if (_tunnel < 0) return "Invalid tunnel";
List controllers = _group.getControllers();
List<TunnelController> controllers = _group.getControllers();
if (_tunnel >= controllers.size()) return "Invalid tunnel";
TunnelController controller = (TunnelController)controllers.get(_tunnel);
TunnelController controller = controllers.get(_tunnel);
controller.stopTunnel();
// give the messages a chance to make it to the window
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
@@ -268,10 +287,10 @@ public class IndexBean {
// if the current tunnel is shared, and of supported type
if (Boolean.parseBoolean(cur.getSharedClient()) && isClient(cur.getType())) {
// all clients use the same I2CP session, and as such, use the same I2CP options
List controllers = _group.getControllers();
List<TunnelController> controllers = _group.getControllers();
for (int i = 0; i < controllers.size(); i++) {
TunnelController c = (TunnelController)controllers.get(i);
TunnelController c = controllers.get(i);
// Current tunnel modified by user, skip
if (c == cur) continue;
@@ -355,10 +374,13 @@ public class IndexBean {
name = Long.toString(_context.clock().now());
}
}
name = "i2ptunnel-deleted-" + name.replace(' ', '_') + "-privkeys.dat";
File to = new File(_context.getConfigDir(), name);
if (to.exists())
to = new File(_context.getConfigDir(), name + '-' + _context.clock().now());
name = "i2ptunnel-deleted-" + name.replace(' ', '_') + '-' + _context.clock().now() + "-privkeys.dat";
File backupDir = new SecureFile(_context.getConfigDir(), TunnelController.KEY_BACKUP_DIR);
File to;
if (backupDir.isDirectory() || backupDir.mkdir())
to = new File(backupDir, name);
else
to = new File(_context.getConfigDir(), name);
boolean success = FileUtil.rename(pkf, to);
if (success)
msgs.add("Private key file " + pkf.getAbsolutePath() +
@@ -375,7 +397,7 @@ public class IndexBean {
*/
public String getMessages() {
if (_group == null)
return "";
return _fatalError;
StringBuilder buf = new StringBuilder(512);
if (_action != null) {
@@ -804,21 +826,22 @@ public class IndexBean {
/** all proxy auth @since 0.8.2 */
public void setProxyAuth(String s) {
_booleanOptions.add(I2PTunnelHTTPClientBase.PROP_AUTH);
if (s != null)
_otherOptions.put(I2PTunnelHTTPClientBase.PROP_AUTH, I2PTunnelHTTPClientBase.DIGEST_AUTH);
}
public void setProxyUsername(String s) {
if (s != null)
_otherOptions.put(I2PTunnelHTTPClientBase.PROP_USER, s.trim());
_newProxyUser = s.trim();
}
public void setProxyPassword(String s) {
if (s != null)
_otherOptions.put(I2PTunnelHTTPClientBase.PROP_PW, s.trim());
_newProxyPW = s.trim();
}
public void setOutproxyAuth(String s) {
_booleanOptions.add(I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH);
_otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH, I2PTunnelHTTPClientBase.DIGEST_AUTH);
}
public void setOutproxyUsername(String s) {
@@ -1040,6 +1063,45 @@ public class IndexBean {
config.setProperty("proxyList", _proxyList);
}
// Proxy auth including migration to MD5
if ("httpclient".equals(_type) || "connectclient".equals(_type)) {
// Migrate even if auth is disabled
// go get the old from custom options that updateConfigGeneric() put in there
String puser = "option." + I2PTunnelHTTPClientBase.PROP_USER;
String user = config.getProperty(puser);
String ppw = "option." + I2PTunnelHTTPClientBase.PROP_PW;
String pw = config.getProperty(ppw);
if (user != null && pw != null && user.length() > 0 && pw.length() > 0) {
String pmd5 = "option." + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_PREFIX +
user + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_SUFFIX;
if (config.getProperty(pmd5) == null) {
// not in there, migrate
String realm = _type.equals("httpclient") ? I2PTunnelHTTPClient.AUTH_REALM
: I2PTunnelConnectClient.AUTH_REALM;
String hex = PasswordManager.md5Hex(realm, user, pw);
if (hex != null) {
config.setProperty(pmd5, hex);
config.remove(puser);
config.remove(ppw);
}
}
}
// New user/password
String auth = _otherOptions.get(I2PTunnelHTTPClientBase.PROP_AUTH);
if (auth != null && !auth.equals("false")) {
if (_newProxyUser != null && _newProxyPW != null &&
_newProxyUser.length() > 0 && _newProxyPW.length() > 0) {
String pmd5 = "option." + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_PREFIX +
_newProxyUser + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_SUFFIX;
String realm = _type.equals("httpclient") ? I2PTunnelHTTPClient.AUTH_REALM
: I2PTunnelConnectClient.AUTH_REALM;
String hex = PasswordManager.md5Hex(realm, _newProxyUser, _newProxyPW);
if (hex != null)
config.setProperty(pmd5, hex);
}
}
}
if ("ircclient".equals(_type) || "client".equals(_type) || "streamrclient".equals(_type)) {
if (_targetDestination != null)
config.setProperty("targetDestination", _targetDestination);
@@ -1084,15 +1146,16 @@ public class IndexBean {
"i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.newDestOnResume", "persistentClientKey", "i2cp.delayOpen"
};
private static final String _booleanProxyOpts[] = {
I2PTunnelHTTPClientBase.PROP_AUTH, I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH
I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH
};
private static final String _booleanServerOpts[] = {
"i2cp.reduceOnIdle", "i2cp.encryptLeaseSet", PROP_ENABLE_ACCESS_LIST, PROP_ENABLE_BLACKLIST
};
private static final String _otherClientOpts[] = {
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime",
"proxyUsername", "proxyPassword", "outproxyUsername", "outproxyPassword",
I2PTunnelHTTPClient.PROP_JUMP_SERVERS
"outproxyUsername", "outproxyPassword",
I2PTunnelHTTPClient.PROP_JUMP_SERVERS,
I2PTunnelHTTPClientBase.PROP_AUTH
};
private static final String _otherServerOpts[] = {
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.leaseSetKey", "i2cp.accessList",
@@ -1101,7 +1164,17 @@ public class IndexBean {
PROP_MAX_STREAMS
};
/**
* do NOT add these to noShoOpts, we must leave them in for HTTPClient and ConnectCLient
* so they will get migrated to MD5
* TODO migrate socks to MD5
*/
private static final String _otherProxyOpts[] = {
"proxyUsername", "proxyPassword"
};
protected static final Set _noShowSet = new HashSet(64);
protected static final Set _nonProxyNoShowSet = new HashSet(4);
static {
_noShowSet.addAll(Arrays.asList(_noShowOpts));
_noShowSet.addAll(Arrays.asList(_booleanClientOpts));
@@ -1109,6 +1182,7 @@ public class IndexBean {
_noShowSet.addAll(Arrays.asList(_booleanServerOpts));
_noShowSet.addAll(Arrays.asList(_otherClientOpts));
_noShowSet.addAll(Arrays.asList(_otherServerOpts));
_nonProxyNoShowSet.addAll(Arrays.asList(_otherProxyOpts));
}
private void updateConfigGeneric(Properties config) {
@@ -1139,6 +1213,12 @@ public class IndexBean {
String key = pair.substring(0, eq);
if (_noShowSet.contains(key))
continue;
// leave in for HTTP and Connect so it can get migrated to MD5
// hide for SOCKS until migrated to MD5
if ((!"httpclient".equals(_type)) &&
(! "connectclient".equals(_type)) &&
_nonProxyNoShowSet.contains(key))
continue;
String val = pair.substring(eq+1);
config.setProperty("option." + key, val);
}
@@ -1190,9 +1270,9 @@ public class IndexBean {
protected TunnelController getController(int tunnel) {
if (tunnel < 0) return null;
if (_group == null) return null;
List controllers = _group.getControllers();
List<TunnelController> controllers = _group.getControllers();
if (controllers.size() > tunnel)
return (TunnelController)controllers.get(tunnel);
return controllers.get(tunnel);
else
return null;
}

View File

@@ -31,7 +31,11 @@
<body id="tunnelEditPage">
<div id="pageHeader">
</div>
<%
if (editBean.isInitialized()) {
%>
<form method="post" action="list">
<div id="tunnelEditPanel" class="panel">
@@ -429,19 +433,19 @@
<label>
<%=intl._("Enable")%>:
</label>
<input value="1" type="checkbox" id="proxyAuth" name="proxyAuth" title="Check to require authorization for this service"<%=(editBean.getProxyAuth(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
<input value="1" type="checkbox" id="startOnLoad" name="proxyAuth" title="Check to require authorization for this service"<%=(editBean.getProxyAuth(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
</div>
<div id="portField" class="rowItem">
<label>
<%=intl._("Username")%>:
</label>
<input type="text" id="clientPort" name="proxyUsername" title="Set username for this service" value="<%=editBean.getProxyUsername(curTunnel)%>" class="freetext" />
<input type="text" id="clientPort" name="proxyUsername" title="Set username for this service" value="" class="freetext" />
</div>
<div id="portField" class="rowItem">
<label>
<%=intl._("Password")%>:
</label>
<input type="password" id="clientPort" name="proxyPassword" title="Set password for this service" value="<%=editBean.getProxyPassword(curTunnel)%>" class="freetext" />
<input type="password" id="clientPort" name="proxyPassword" title="Set password for this service" value="" class="freetext" />
</div>
<div class="subdivider">
<hr />
@@ -453,7 +457,7 @@
<label>
<%=intl._("Enable")%>:
</label>
<input value="1" type="checkbox" id="outproxyAuth" name="outproxyAuth" title="Check if the outproxy requires authorization"<%=(editBean.getOutproxyAuth(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
<input value="1" type="checkbox" id="startOnLoad" name="outproxyAuth" title="Check if the outproxy requires authorization"<%=(editBean.getOutproxyAuth(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
</div>
<div id="portField" class="rowItem">
<label>
@@ -508,5 +512,12 @@
</form>
<div id="pageFooter">
</div>
<%
} else {
%>Tunnels are not initialized yet, please reload in two minutes.<%
} // isInitialized()
%>
</body>
</html>

View File

@@ -31,7 +31,11 @@
<body id="tunnelEditPage">
<div id="pageHeader">
</div>
<%
if (editBean.isInitialized()) {
%>
<form method="post" action="list">
<div id="tunnelEditPanel" class="panel">
@@ -518,5 +522,12 @@
</form>
<div id="pageFooter">
</div>
<%
} else {
%>Tunnels are not initialized yet, please reload in two minutes.<%
} // isInitialized()
%>
</body>
</html>

View File

@@ -55,12 +55,23 @@
</div>
</div>
</div>
<%
if (indexBean.isInitialized()) {
%>
<div id="globalOperationsPanel" class="panel">
<div class="header"></div>
<div class="footer">
<div class="toolbox">
<a class="control" href="wizard"><%=intl._("Tunnel Wizard")%></a> <a class="control" href="list?nonce=<%=indexBean.getNextNonce()%>&amp;action=Stop%20all"><%=intl._("Stop All")%></a> <a class="control" href="list?nonce=<%=indexBean.getNextNonce()%>&amp;action=Start%20all"><%=intl._("Start All")%></a> <a class="control" href="list?nonce=<%=indexBean.getNextNonce()%>&amp;action=Restart%20all"><%=intl._("Restart All")%></a> <a class="control" href="list?nonce=<%=indexBean.getNextNonce()%>&amp;action=Reload%20configuration"><%=intl._("Reload Config")%></a>
<a class="control" href="wizard"><%=intl._("Tunnel Wizard")%></a>
<a class="control" href="list?nonce=<%=indexBean.getNextNonce()%>&amp;action=Stop%20all"><%=intl._("Stop All")%></a>
<a class="control" href="list?nonce=<%=indexBean.getNextNonce()%>&amp;action=Start%20all"><%=intl._("Start All")%></a>
<a class="control" href="list?nonce=<%=indexBean.getNextNonce()%>&amp;action=Restart%20all"><%=intl._("Restart All")%></a>
<%--
//this is really bad because it stops and restarts all tunnels, which is probably not what you want
<a class="control" href="list?nonce=<%=indexBean.getNextNonce()%>&amp;action=Reload%20configuration"><%=intl._("Reload Config")%></a>
--%>
</div>
</div>
</div>
@@ -329,6 +340,11 @@
</form>
</div>
</div>
<%
} // isInitialized()
%>
<div id="pageFooter">
</div>
</body>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

10
apps/jetty/.classpath Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="java/src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry combineaccessrules="false" kind="src" path="/i2p_sdk"/>
<classpathentry kind="lib" path="jettylib/javax.servlet.jar"/>
<classpathentry kind="lib" path="jettylib/jetty-util.jar"/>
<classpathentry kind="lib" path="jettylib/org.mortbay.jetty.jar"/>
<classpathentry kind="output" path="build/obj"/>
</classpath>

17
apps/jetty/.project Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>jetty</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,170 @@
package net.i2p.jetty;
// Contains code from org.mortbay.xml.XmlConfiguation:
// ========================================================================
// Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// 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.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.app.*;
import static net.i2p.app.ClientAppState.*;
import org.mortbay.component.LifeCycle;
import org.mortbay.resource.Resource;
import org.mortbay.xml.XmlConfiguration;
/**
* Start Jetty where the args are one or more XML files.
* Save a reference to the Server so it can be cleanly stopped later.
*
* This is like XmlConfiguration.main(), which is essentially what
* org.mortbay.start.Main does.
*
* @since 0.9.4
*/
public class JettyStart implements ClientApp {
private final I2PAppContext _context;
private final ClientAppManager _mgr;
private final String[] _args;
private final List<LifeCycle> _jettys;
private volatile ClientAppState _state;
/**
* All args must be XML file names.
* Does not support any of the other argument types from org.mortbay.start.Main.
*/
public JettyStart(I2PAppContext context, ClientAppManager mgr, String[] args) throws Exception {
_state = UNINITIALIZED;
_context = context;
_mgr = mgr;
_args = args;
_jettys = new ArrayList(args.length);
parseArgs(args);
_state = INITIALIZED;
}
/**
* Modified from XmlConfiguration.main()
*/
public void parseArgs(String[] args) throws Exception {
Properties properties=new Properties();
XmlConfiguration last=null;
for (int i = 0; i < args.length; i++) {
if (args[i].toLowerCase().endsWith(".properties")) {
properties.load(Resource.newResource(args[i]).getInputStream());
} else {
XmlConfiguration configuration = new XmlConfiguration(Resource.newResource(args[i]).getURL());
if (last!=null)
configuration.getIdMap().putAll(last.getIdMap());
if (properties.size()>0)
configuration.setProperties(properties);
Object o = configuration.configure();
if (o instanceof LifeCycle)
_jettys.add((LifeCycle)o);
last=configuration;
}
}
}
public void startup() {
if (_state != INITIALIZED)
return;
if (_jettys.isEmpty()) {
changeState(START_FAILED);
} else {
(new Starter()).start();
}
}
private class Starter extends Thread {
public Starter() {
super("JettyStarter");
}
/**
* Modified from XmlConfiguration.main()
*/
public void run() {
changeState(STARTING);
for (LifeCycle lc : _jettys) {
if (!lc.isRunning()) {
try {
lc.start();
} catch (Exception e) {
changeState(START_FAILED, e);
return;
}
}
}
changeState(RUNNING);
}
}
public void shutdown(String[] args) {
if (_state != RUNNING)
return;
if (_jettys.isEmpty()) {
changeState(STOPPED);
} else {
(new Stopper()).start();
}
}
private class Stopper extends Thread {
public Stopper() {
super("JettyStopper");
}
public void run() {
changeState(STOPPING);
for (LifeCycle lc : _jettys) {
if (lc.isRunning()) {
try {
lc.stop();
} catch (Exception e) {
changeState(STOPPING, e);
}
}
}
changeState(STOPPED);
}
}
public ClientAppState getState() {
return _state;
}
public String getName() {
return "Jetty";
}
public String getDisplayName() {
return "Jetty " + Arrays.toString(_args);
}
private void changeState(ClientAppState state) {
changeState(state, null);
}
private synchronized void changeState(ClientAppState state, Exception e) {
_state = state;
_mgr.notify(this, state, null, e);
}
}

4
apps/jrobin/.classpath Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="output" path="build"/>
</classpath>

17
apps/jrobin/.project Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>jrobin</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="test/junit"/>
<classpathentry combineaccessrules="false" kind="src" path="/i2p_sdk"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="build/obj"/>
</classpath>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>ministreaming</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry combineaccessrules="false" kind="src" path="/i2p_router"/>
<classpathentry combineaccessrules="false" kind="src" path="/i2p_sdk"/>
<classpathentry combineaccessrules="false" kind="src" path="/desktopgui"/>
<classpathentry combineaccessrules="false" kind="src" path="/systray"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="/lib/wrapper/all/wrapper.jar"/>
<classpathentry kind="lib" path="/jrobin/jrobin-1.5.9.1.jar"/>
<classpathentry kind="lib" path="/jetty/jettylib/javax.servlet.jar"/>
<classpathentry kind="lib" path="/jetty/jettylib/jetty-java5-threadpool.jar"/>
<classpathentry kind="lib" path="/jetty/jettylib/jetty-sslengine.jar"/>
<classpathentry kind="lib" path="/jetty/jettylib/jetty-util.jar"/>
<classpathentry kind="lib" path="/jetty/jettylib/org.mortbay.jetty.jar"/>
<classpathentry kind="output" path="build/obj"/>
</classpath>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>routerconsole</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -330,6 +330,118 @@
splitindex="true"
windowtitle="Router Console" />
</target>
<!-- scala paths -->
<target name="scala.init">
<property name="scala-library.jar" value="${scalatest.libs}/scala-library.jar" />
<property name="scalatest.jar" value="${scalatest.libs}/scalatest.jar" />
<taskdef resource="scala/tools/ant/antlib.xml">
<classpath>
<pathelement location="${scalatest.libs}/scala-compiler.jar" />
<pathelement location="${scala-library.jar}" />
</classpath>
</taskdef>
</target>
<!-- unit tests -->
<target name="builddepscalatest">
<ant dir="../../../router/java/" target="jar" />
<ant dir="../../../router/java/" target="jarScalaTest" />
</target>
<target name="scalatest.compileTest" depends="builddepscalatest, compile, scala.init">
<mkdir dir="./build" />
<mkdir dir="./build/obj_scala" />
<scalac srcdir="./test/scalatest" destdir="./build/obj_scala" deprecation="on" >
<classpath>
<pathelement location="${scala-library.jar}" />
<pathelement location="${scalatest.jar}" />
<pathelement location="${scalatest.libs}/mockito-all.jar" />
<pathelement location="../../../core/java/build/i2pscalatest.jar" />
<pathelement location="../../../router/java/build/routerscalatest.jar" />
<pathelement location="./build/obj" />
</classpath>
</scalac>
</target>
<!-- preparation of code coverage tool of choice -->
<target name="prepareClover" depends="compile" if="with.clover">
<taskdef resource="clovertasks"/>
<mkdir dir="../../../reports/apps/routerconsole/clover" />
<clover-setup initString="../../../reports/apps/routerconsole/clover/coverage.db"/>
</target>
<target name="prepareCobertura" depends="compile" if="with.cobertura">
<taskdef classpath="${with.cobertura}" resource="tasks.properties" onerror="report" />
<mkdir dir="./build/obj_cobertura" />
<delete file="./cobertura.ser" />
<cobertura-instrument todir="./build/obj_cobertura">
<fileset dir="./build/obj">
<include name="**/*.class"/>
<exclude name="**/*Test.class" />
</fileset>
</cobertura-instrument>
</target>
<target name="prepareTest" depends="prepareClover, prepareCobertura" />
<!-- end preparation of code coverage tool -->
<target name="scalatest.test" depends="clean, scalatest.compileTest, prepareTest">
<mkdir dir="../../../reports/apps/routerconsole/scalatest/" />
<delete>
<fileset dir="../../../reports/apps/routerconsole/scalatest">
<include name="TEST-*.xml"/>
</fileset>
</delete>
<taskdef name="scalatest" classname="org.scalatest.tools.ScalaTestAntTask">
<classpath>
<pathelement location="${classpath}" />
<pathelement location="${scala-library.jar}" />
<pathelement location="${scalatest.jar}" />
<pathelement location="./build/obj_cobertura" />
<pathelement location="./build/obj" />
<pathelement location="${with.clover}" />
<pathelement location="${with.cobertura}" />
</classpath>
</taskdef>
<scalatest runpath="./build/obj_scala" fork="yes" maxmemory="384M">
<tagsToExclude>
SlowTests
</tagsToExclude>
<reporter type="stdout" />
<reporter type="junitxml" directory="../../../reports/apps/routerconsole/scalatest/" />
</scalatest>
<!-- fetch the real hostname of this machine -->
<exec executable="hostname" outputproperty="host.name"/>
<!-- set if unset -->
<property name="host.fakename" value="i2ptester" />
<!-- replace hostname that junit inserts into reports with fake one -->
<replace dir="../../../reports/apps/routerconsole/scalatest/" token="${host.name}" value="${host.fakename}"/>
</target>
<target name="test" depends="scalatest.test"/>
<!-- test reports -->
<target name="scalatest.report">
<junitreport todir="../../../reports/apps/routerconsole/scalatest">
<fileset dir="../../../reports/apps/routerconsole/scalatest">
<include name="TEST-*.xml"/>
</fileset>
<report format="frames" todir="../../../reports/apps/routerconsole/html/scalatest"/>
</junitreport>
</target>
<target name="clover.report" depends="test" if="with.clover">
<clover-report>
<current outfile="../../../reports/apps/routerconsole/html/clover">
<format type="html"/>
</current>
</clover-report>
</target>
<target name="cobertura.report" depends="test" if="with.cobertura">
<mkdir dir="../../../reports/apps/routerconsole/cobertura" />
<cobertura-report format="xml" srcdir="./src" destdir="../../../reports/apps/routerconsole/cobertura" />
<mkdir dir="../../../reports/apps/routerconsole/html/cobertura" />
<cobertura-report format="html" srcdir="./src" destdir="../../../reports/apps/routerconsole/html/cobertura" />
<delete file="./cobertura.ser" />
</target>
<target name="test.report" depends="scalatest.report, clover.report, cobertura.report"/>
<!-- end test reports -->
<target name="fulltest" depends="cleandep, test, test.report" />
<!-- end unit tests -->
<target name="clean">
<delete dir="./build" />
<delete dir="../jsp/WEB-INF/" />

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
package net.i2p.router.update;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import net.i2p.router.RouterContext;
import net.i2p.update.*;
/**
* Dummy to lock up the updates for a period of time
*
* @since 0.9.4
*/
class DummyHandler implements Checker, Updater {
private final RouterContext _context;
private final ConsoleUpdateManager _mgr;
public DummyHandler(RouterContext ctx, ConsoleUpdateManager mgr) {
_context = ctx;
_mgr = mgr;
}
/**
* Spins off an UpdateTask that sleeps
*/
public UpdateTask check(UpdateType type, UpdateMethod method,
String id, String currentVersion, long maxTime) {
if (type != UpdateType.TYPE_DUMMY)
return null;
return new DummyRunner(_context, _mgr, maxTime);
}
/**
* Spins off an UpdateTask that sleeps
*/
public UpdateTask update(UpdateType type, UpdateMethod method, List<URI> updateSources,
String id, String newVersion, long maxTime) {
if (type != UpdateType.TYPE_DUMMY)
return null;
return new DummyRunner(_context, _mgr, maxTime);
}
/**
* Use for both check and update
*/
private static class DummyRunner extends UpdateRunner {
private final long _delay;
public DummyRunner(RouterContext ctx, ConsoleUpdateManager mgr, long maxTime) {
super(ctx, mgr, Collections.EMPTY_LIST);
_delay = maxTime;
}
@Override
public UpdateType getType() { return UpdateType.TYPE_DUMMY; }
@Override
public UpdateMethod getMethod() { return UpdateMethod.METHOD_DUMMY; }
@Override
protected void update() {
try {
Thread.sleep(_delay);
} catch (InterruptedException ie) {}
_mgr.notifyCheckComplete(this, false, false);
_mgr.notifyTaskFailed(this, "dummy", null);
}
}
}

View File

@@ -0,0 +1,310 @@
package net.i2p.router.update;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import net.i2p.crypto.TrustedUpdate;
import net.i2p.data.DataHelper;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.RouterVersion;
import net.i2p.router.util.RFC822Date;
import net.i2p.router.web.ConfigUpdateHandler;
import net.i2p.router.web.ConfigUpdateHelper;
import net.i2p.router.web.NewsHelper;
import net.i2p.update.*;
import static net.i2p.update.UpdateType.*;
import static net.i2p.update.UpdateMethod.*;
import net.i2p.util.EepGet;
import net.i2p.util.EepHead;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
/**
* Task to fetch updates to the news.xml, and to keep
* track of whether that has an announcement for a new version.
*
* @since 0.9.4 moved from NewsFetcher and make an Updater
*/
class NewsFetcher extends UpdateRunner {
private String _lastModified;
private final File _newsFile;
private final File _tempFile;
/** is the news newer */
private boolean _isNewer;
private boolean _success;
private static final String TEMP_NEWS_FILE = "news.xml.temp";
public NewsFetcher(RouterContext ctx, ConsoleUpdateManager mgr, List<URI> uris) {
super(ctx, mgr, uris);
_newsFile = new File(ctx.getRouterDir(), NewsHelper.NEWS_FILE);
_tempFile = new File(ctx.getTempDir(), "tmp-" + ctx.random().nextLong() + TEMP_NEWS_FILE);
long lastMod = NewsHelper.lastChecked(ctx);
if (lastMod > 0)
_lastModified = RFC822Date.to822Date(lastMod);
}
@Override
public UpdateType getType() { return NEWS; }
private boolean dontInstall() {
return NewsHelper.dontInstall(_context);
}
private boolean shouldInstall() {
String policy = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_POLICY);
if ("notify".equals(policy) || dontInstall())
return false;
File zip = new File(_context.getRouterDir(), Router.UPDATE_FILE);
return !zip.exists();
}
@Override
public void run() {
_isRunning = true;
try {
fetchNews();
} finally {
_mgr.notifyCheckComplete(this, _isNewer, _success);
_isRunning = false;
}
}
public void fetchNews() {
boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
int proxyPort = ConfigUpdateHandler.proxyPort(_context);
for (URI uri : _urls) {
_currentURI = uri;
String newsURL = uri.toString();
if (_tempFile.exists())
_tempFile.delete();
try {
EepGet get;
if (shouldProxy)
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), newsURL, true, null, _lastModified);
else
get = new EepGet(_context, false, null, 0, 0, _tempFile.getAbsolutePath(), newsURL, true, null, _lastModified);
get.addStatusListener(this);
long start = _context.clock().now();
if (get.fetch()) {
_context.router().saveConfig(NewsHelper.PROP_LAST_CHECKED,
Long.toString(start));
return;
}
} catch (Throwable t) {
_log.error("Error fetching the news", t);
}
}
}
// Fake XML parsing
// Line must contain this, and full entry must be on one line
private static final String VERSION_PREFIX = "<i2p.release ";
// all keys mapped to lower case by parseArgs()
private static final String VERSION_KEY = "version";
private static final String MIN_VERSION_KEY = "minversion";
private static final String SUD_KEY = "sudtorrent";
private static final String SU2_KEY = "su2torrent";
private static final String CLEARNET_SUD_KEY = "sudclearnet";
private static final String CLEARNET_SU2_KEY = "su2clearnet";
private static final String I2P_SUD_KEY = "sudi2p";
private static final String I2P_SU2_KEY = "su2i2p";
/**
* Parse the installed (not the temp) news file for the latest version.
* TODO: Real XML parsing
* TODO: Check minVersion, use backup URLs specified
*/
void checkForUpdates() {
FileInputStream in = null;
try {
in = new FileInputStream(_newsFile);
StringBuilder buf = new StringBuilder(128);
while (DataHelper.readLine(in, buf)) {
int index = buf.indexOf(VERSION_PREFIX);
if (index >= 0) {
Map<String, String> args = parseArgs(buf.substring(index+VERSION_PREFIX.length()));
String ver = args.get(VERSION_KEY);
if (ver != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Found version: [" + ver + "]");
if (TrustedUpdate.needsUpdate(RouterVersion.VERSION, ver)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Our version is out of date, update!");
// TODO if minversion > our version, continue
// and look for a second entry with clearnet URLs
// TODO clearnet URLs, notify with HTTP_CLEARNET and/or HTTPS_CLEARNET
_mgr.notifyVersionAvailable(this, _currentURI,
ROUTER_SIGNED, "", HTTP,
_mgr.getUpdateURLs(ROUTER_SIGNED, "", HTTP),
ver, "");
String key = FileUtil.isPack200Supported() ? SU2_KEY : SUD_KEY;
String murl = args.get(key);
if (murl != null) {
List<URI> uris = tokenize(murl);
if (!uris.isEmpty()) {
Collections.shuffle(uris, _context.random());
_mgr.notifyVersionAvailable(this, _currentURI,
ROUTER_SIGNED, "", TORRENT,
uris, ver, "");
}
}
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Our version is current");
}
return;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("No version in " + buf.toString());
}
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("No match in " + buf.toString());
}
buf.setLength(0);
}
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error checking the news for an update", ioe);
return;
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
if (_log.shouldLog(Log.WARN))
_log.warn("No version found in news.xml file");
}
/**
* Modified from LoadClientAppsJob and I2PTunnelHTTPClientBase
* All keys are mapped to lower case.
*
* @param args non-null
* @since 0.9.4
*/
private static Map<String, String> parseArgs(String args) {
Map<String, String> rv = new HashMap(8);
char data[] = args.toCharArray();
StringBuilder buf = new StringBuilder(32);
boolean isQuoted = false;
String key = null;
for (int i = 0; i < data.length; i++) {
switch (data[i]) {
case '\'':
case '"':
if (isQuoted) {
// keys never quoted
if (key != null) {
rv.put(key, buf.toString().trim());
key = null;
}
buf.setLength(0);
}
isQuoted = !isQuoted;
break;
case ' ':
case '\r':
case '\n':
case '\t':
case ',':
// whitespace - if we're in a quoted section, keep this as part of the quote,
// otherwise use it as a delim
if (isQuoted) {
buf.append(data[i]);
} else {
if (key != null) {
rv.put(key, buf.toString().trim());
key = null;
}
buf.setLength(0);
}
break;
case '=':
if (isQuoted) {
buf.append(data[i]);
} else {
key = buf.toString().trim().toLowerCase(Locale.US);
buf.setLength(0);
}
break;
default:
buf.append(data[i]);
break;
}
}
if (key != null)
rv.put(key, buf.toString().trim());
return rv;
}
private static List<URI> tokenize(String URLs) {
StringTokenizer tok = new StringTokenizer(URLs, " ,\r\n");
List<URI> rv = new ArrayList();
while (tok.hasMoreTokens()) {
try {
rv.add(new URI(tok.nextToken().trim()));
} catch (URISyntaxException use) {}
}
return rv;
}
/** override to prevent status update */
@Override
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {}
/**
* Copies the file from temp dir to the news location,
* calls checkForUpdates()
*/
@Override
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
if (_log.shouldLog(Log.INFO))
_log.info("News fetched from " + url + " with " + (alreadyTransferred+bytesTransferred));
long now = _context.clock().now();
if (_tempFile.exists()) {
boolean copied = FileUtil.copy(_tempFile, _newsFile, true, false);
_tempFile.delete();
if (copied) {
String newVer = Long.toString(now);
_context.router().saveConfig(NewsHelper.PROP_LAST_UPDATED, newVer);
_mgr.notifyVersionAvailable(this, _currentURI, NEWS, "", HTTP,
null, newVer, "");
_isNewer = true;
checkForUpdates();
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("Failed to copy the news file!");
}
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Transfer complete, but no file? - probably 304 Not Modified");
}
_success = true;
}
/** override to prevent status update */
@Override
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {}
}

View File

@@ -0,0 +1,51 @@
package net.i2p.router.update;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import net.i2p.router.RouterContext;
import net.i2p.router.web.ConfigUpdateHelper;
import net.i2p.update.*;
import static net.i2p.update.UpdateType.*;
import static net.i2p.update.UpdateMethod.*;
/**
* Task to periodically look for updates to the news.xml, and to keep
* track of whether that has an announcement for a new version.
*
* Overrides UpdateRunner for convenience, this is not an Updater
*
* @since 0.9.4 moved from NewsFetcher
*/
class NewsHandler extends UpdateHandler implements Checker {
/** @since 0.7.14 not configurable */
private static final String BACKUP_NEWS_URL = "http://www.i2p2.i2p/_static/news/news.xml";
public NewsHandler(RouterContext ctx, ConsoleUpdateManager mgr) {
super(ctx, mgr);
}
/**
* This will check for news or router updates (it does the same thing).
* Should not block.
* @param currentVersion ignored, stored locally
*/
public UpdateTask check(UpdateType type, UpdateMethod method,
String id, String currentVersion, long maxTime) {
if ((type != ROUTER_SIGNED && type != NEWS) ||
method != HTTP)
return null;
List<URI> updateSources = new ArrayList(2);
try {
updateSources.add(new URI(ConfigUpdateHelper.getNewsURL(_context)));
} catch (URISyntaxException use) {}
try {
updateSources.add(new URI(BACKUP_NEWS_URL));
} catch (URISyntaxException use) {}
UpdateRunner update = new NewsFetcher(_context, _mgr, updateSources);
return update;
}
}

View File

@@ -0,0 +1,118 @@
package net.i2p.router.update;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import net.i2p.crypto.TrustedUpdate;
import net.i2p.data.DataHelper;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.RouterVersion;
import net.i2p.router.web.ConfigUpdateHandler;
import net.i2p.router.web.ConfigUpdateHelper;
import net.i2p.router.web.NewsHelper;
import static net.i2p.update.UpdateType.*;
import net.i2p.util.EepGet;
import net.i2p.util.EepHead;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
/**
* Task to periodically look for updates to the news.xml, and to keep
* track of whether that has an announcement for a new version.
* Also looks for unsigned updates.
*
* Runs forever on instantiation, can't be stopped.
*
* @since 0.9.4 moved from NewsFetcher
*/
class NewsTimerTask implements SimpleTimer.TimedEvent {
private final RouterContext _context;
private final Log _log;
private final ConsoleUpdateManager _mgr;
private static final long INITIAL_DELAY = 5*60*1000;
private static final long RUN_DELAY = 10*60*1000;
public NewsTimerTask(RouterContext ctx, ConsoleUpdateManager mgr) {
_context = ctx;
_log = ctx.logManager().getLog(NewsTimerTask.class);
_mgr = mgr;
ctx.simpleScheduler().addPeriodicEvent(this,
INITIAL_DELAY + _context.random().nextLong(INITIAL_DELAY),
RUN_DELAY);
// UpdateManager calls NewsFetcher to check the existing news at startup
}
public void timeReached() {
if (shouldFetchNews()) {
// blocking
fetchNews();
if (shouldFetchUnsigned()) {
// give it a sec for the download to kick in, if it's going to
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
if (!_mgr.isCheckInProgress() && !_mgr.isUpdateInProgress())
// nonblocking
fetchUnsignedHead();
}
}
}
private boolean shouldFetchNews() {
if (_context.router().gracefulShutdownInProgress())
return false;
if (_mgr.isCheckInProgress() || _mgr.isUpdateInProgress())
return false;
long lastFetch = NewsHelper.lastChecked(_context);
String freq = _context.getProperty(ConfigUpdateHandler.PROP_REFRESH_FREQUENCY,
ConfigUpdateHandler.DEFAULT_REFRESH_FREQUENCY);
try {
long ms = Long.parseLong(freq);
if (ms <= 0)
return false;
if (lastFetch + ms < _context.clock().now()) {
return true;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Last fetched " + DataHelper.formatDuration(_context.clock().now() - lastFetch) + " ago");
return false;
}
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Invalid refresh frequency: " + freq);
return false;
}
}
/** blocking */
private void fetchNews() {
_mgr.checkAvailable(NEWS, 60*1000);
}
private boolean shouldFetchUnsigned() {
String url = _context.getProperty(ConfigUpdateHandler.PROP_ZIP_URL);
return url != null && url.length() > 0 &&
_context.getBooleanProperty(ConfigUpdateHandler.PROP_UPDATE_UNSIGNED) &&
!NewsHelper.dontInstall(_context);
}
/**
* HEAD the update url, and if the last-mod time is newer than the last update we
* downloaded, as stored in the properties, then we download it using eepget.
*
* Non-blocking
*/
private void fetchUnsignedHead() {
_mgr.check(ROUTER_UNSIGNED);
}
}

View File

@@ -0,0 +1,105 @@
package net.i2p.router.update;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.net.URI;
import java.util.List;
import java.util.Properties;
import net.i2p.crypto.TrustedUpdate;
import net.i2p.router.RouterContext;
import net.i2p.router.web.ConfigUpdateHandler;
import net.i2p.update.*;
import net.i2p.util.EepGet;
import net.i2p.util.I2PAppThread;
import net.i2p.util.PartialEepGet;
import net.i2p.util.VersionComparator;
/**
* Check for an updated version of a plugin.
* A plugin is a standard .sud file with a 40-byte signature,
* a 16-byte version, and a .zip file.
*
* So we get the current version and update URL for the installed plugin,
* then fetch the first 56 bytes of the URL, extract the version,
* and compare.
*
* Moved from web/ and turned into an UpdateTask.
*
* @since 0.7.12
*/
class PluginUpdateChecker extends UpdateRunner {
private final ByteArrayOutputStream _baos;
private final String _appName;
private final String _oldVersion;
public PluginUpdateChecker(RouterContext ctx, ConsoleUpdateManager mgr,
List<URI> uris, String appName, String oldVersion ) {
super(ctx, mgr, uris);
_baos = new ByteArrayOutputStream(TrustedUpdate.HEADER_BYTES);
if (!uris.isEmpty())
_currentURI = uris.get(0);
_appName = appName;
_oldVersion = oldVersion;
}
@Override
public UpdateType getType() { return UpdateType.PLUGIN; }
@Override
public String getID() { return _appName; }
@Override
public void run() {
_isRunning = true;
try {
update();
} finally {
_isRunning = false;
}
}
@Override
protected void update() {
// must be set for super
_isPartial = true;
updateStatus("<b>" + _("Checking for update of plugin {0}", _appName) + "</b>");
// use the same settings as for updater
// always proxy, or else FIXME
//boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
int proxyPort = ConfigUpdateHandler.proxyPort(_context);
_baos.reset();
try {
_get = new PartialEepGet(_context, proxyHost, proxyPort, _baos, _currentURI.toString(), TrustedUpdate.HEADER_BYTES);
_get.addStatusListener(this);
_get.fetch(CONNECT_TIMEOUT);
} catch (Throwable t) {
_log.error("Error checking update for plugin", t);
}
}
@Override
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
}
@Override
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
// super sets _newVersion if newer
boolean newer = _newVersion != null;
if (newer) {
_mgr.notifyVersionAvailable(this, _currentURI, UpdateType.PLUGIN, _appName, UpdateMethod.HTTP,
_urls, _newVersion, _oldVersion);
}
_mgr.notifyCheckComplete(this, newer, true);
}
@Override
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
File f = new File(_updateFile);
f.delete();
_mgr.notifyCheckComplete(this, false, false);
}
}

View File

@@ -0,0 +1,83 @@
package net.i2p.router.update;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import net.i2p.router.RouterContext;
import net.i2p.router.web.PluginStarter;
import net.i2p.update.*;
/**
* Check for or download an updated version of a plugin.
* A plugin is a standard .sud file with a 40-byte signature,
* a 16-byte version, and a .zip file.
*
* So we get the current version and update URL for the installed plugin,
* then fetch the first 56 bytes of the URL, extract the version,
* and compare.
*
* Moved from web/ and turned into an Updater.
*
* @since 0.7.12
* @author zzz
*/
class PluginUpdateHandler implements Checker, Updater {
private final RouterContext _context;
private final ConsoleUpdateManager _mgr;
public PluginUpdateHandler(RouterContext ctx, ConsoleUpdateManager mgr) {
_context = ctx;
_mgr = mgr;
}
/** check a single plugin */
@Override
public UpdateTask check(UpdateType type, UpdateMethod method,
String appName, String currentVersion, long maxTime) {
if ((type != UpdateType.PLUGIN) ||
method != UpdateMethod.HTTP || appName.length() <= 0)
return null;
Properties props = PluginStarter.pluginProperties(_context, appName);
String oldVersion = props.getProperty("version");
String xpi2pURL = props.getProperty("updateURL");
List<URI> updateSources = null;
if (xpi2pURL != null) {
try {
updateSources = Collections.singletonList(new URI(xpi2pURL));
} catch (URISyntaxException use) {}
}
if (oldVersion == null || updateSources == null) {
//updateStatus("<b>" + _("Cannot check, plugin {0} is not installed", appName) + "</b>");
return null;
}
UpdateRunner update = new PluginUpdateChecker(_context, _mgr, updateSources, appName, oldVersion);
return update;
}
/** download a single plugin */
@Override
public UpdateTask update(UpdateType type, UpdateMethod method, List<URI> updateSources,
String appName, String newVersion, long maxTime) {
if (type != UpdateType.PLUGIN ||
method != UpdateMethod.HTTP || updateSources.isEmpty())
return null;
Properties props = PluginStarter.pluginProperties(_context, appName);
String oldVersion = props.getProperty("version");
if (oldVersion == null) {
// assume new install
oldVersion = "0";
}
UpdateRunner update = new PluginUpdateRunner(_context, _mgr, updateSources, appName, oldVersion);
// set status before thread to ensure UI feedback
_mgr.notifyProgress(update, "<b>" + _mgr._("Updating") + "</b>");
return update;
}
}

View File

@@ -1,7 +1,9 @@
package net.i2p.router.web;
package net.i2p.router.update;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Properties;
@@ -9,6 +11,12 @@ import net.i2p.CoreVersion;
import net.i2p.crypto.TrustedUpdate;
import net.i2p.data.DataHelper;
import net.i2p.router.RouterContext;
import net.i2p.router.web.ConfigClientsHelper;
import net.i2p.router.web.ConfigUpdateHandler;
import net.i2p.router.web.LogsHelper;
import net.i2p.router.web.Messages;
import net.i2p.router.web.PluginStarter;
import net.i2p.update.*;
import net.i2p.util.EepGet;
import net.i2p.util.FileUtil;
import net.i2p.util.I2PAppThread;
@@ -19,113 +27,66 @@ import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
import net.i2p.util.VersionComparator;
/**
* Download and install a plugin.
* Check for an updated version of a plugin.
* A plugin is a standard .sud file with a 40-byte signature,
* a 16-byte version, and a .zip file.
* Unlike for router updates, we need not have the public key
* for the signature in advance.
*
* The zip file must have a standard directory layout, with
* a plugin.config file at the top level.
* The config file contains properties for the package name, version,
* signing public key, and other settings.
* The zip file will typically contain a webapps/ or lib/ dir,
* and a webapps.config and/or clients.config file.
* So we get the current version and update URL for the installed plugin,
* then fetch the first 56 bytes of the URL, extract the version,
* and compare.
*
* @since 0.7.12
* @author zzz
* Moved from web/ and turned into an UpdateTask.
*
* @since 0.9.4 moved from PluginUpdateHandler
*/
public class PluginUpdateHandler extends UpdateHandler {
private static PluginUpdateRunner _pluginUpdateRunner;
private String _xpi2pURL;
private String _appStatus;
private volatile boolean _updated;
class PluginUpdateRunner extends UpdateRunner {
private String _appName;
private final String _oldVersion;
private final URI _uri;
private final String _xpi2pURL;
private boolean _updated;
private String _errMsg = "";
private static final String XPI2P = "app.xpi2p";
private static final String ZIP = XPI2P + ".zip";
public static final String PLUGIN_DIR = "plugins";
public static final String PLUGIN_DIR = PluginStarter.PLUGIN_DIR;
private static PluginUpdateHandler _instance;
public static final synchronized PluginUpdateHandler getInstance(RouterContext ctx) {
if (_instance != null)
return _instance;
_instance = new PluginUpdateHandler(ctx);
return _instance;
public PluginUpdateRunner(RouterContext ctx, ConsoleUpdateManager mgr, List<URI> uris,
String appName, String oldVersion ) {
super(ctx, mgr, uris);
if (uris.isEmpty())
_uri = null;
else
_uri = uris.get(0);
_xpi2pURL = _uri.toString();
_appName = appName;
_oldVersion = oldVersion;
}
private PluginUpdateHandler(RouterContext ctx) {
super(ctx);
_appStatus = "";
}
public void update(String xpi2pURL) {
// don't block waiting for the other one to finish
if ("true".equals(System.getProperty(PROP_UPDATE_IN_PROGRESS))) {
_log.error("Update already running");
return;
}
synchronized (UpdateHandler.class) {
if (_pluginUpdateRunner == null)
_pluginUpdateRunner = new PluginUpdateRunner(_xpi2pURL);
if (_pluginUpdateRunner.isRunning())
return;
_xpi2pURL = xpi2pURL;
_updateFile = (new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + XPI2P)).getAbsolutePath();
System.setProperty(PROP_UPDATE_IN_PROGRESS, "true");
I2PAppThread update = new I2PAppThread(_pluginUpdateRunner, "AppDownload");
update.start();
}
}
public String getAppStatus() {
return _appStatus;
}
public boolean isRunning() {
return _pluginUpdateRunner != null && _pluginUpdateRunner.isRunning();
@Override
public UpdateType getType() {
return UpdateType.PLUGIN;
}
@Override
public boolean isDone() {
// FIXME
return false;
}
public URI getURI() { return _uri; }
/** @since 0.8.13 */
public boolean wasUpdateSuccessful() {
return _updated;
}
private void scheduleStatusClean(String msg) {
_context.simpleScheduler().addEvent(new Cleaner(msg), 20*60*1000);
}
private class Cleaner implements SimpleTimer.TimedEvent {
private final String _msg;
public Cleaner(String msg) {
_msg = msg;
}
public void timeReached() {
if (_msg.equals(getStatus()))
updateStatus("");
}
}
public class PluginUpdateRunner extends UpdateRunner implements Runnable, EepGet.StatusListener {
public PluginUpdateRunner(String url) {
super();
}
@Override
public String getID() { return _appName; }
@Override
protected void update() {
_updated = false;
if(_xpi2pURL.startsWith("file://")) {
updateStatus("<b>" + _("Attempting to install from file {0}", _xpi2pURL) + "</b>");
// strip off "file://"
String xpi2pfile = _xpi2pURL.substring(7);
if(xpi2pfile.length() == 0) { // This is actually what String.isEmpty() does, so it should be safe.
if(xpi2pfile.length() == 0) {
statusDone("<b>" + _("No file specified {0}", _xpi2pURL) + "</b>");
} else {
// copy the contents of from to _updateFile
@@ -139,7 +100,7 @@ public class PluginUpdateHandler extends UpdateHandler {
} else {
updateStatus("<b>" + _("Downloading plugin from {0}", _xpi2pURL) + "</b>");
// use the same settings as for updater
boolean shouldProxy = Boolean.parseBoolean(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY));
boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
int proxyPort = ConfigUpdateHandler.proxyPort(_context);
try {
@@ -154,21 +115,10 @@ public class PluginUpdateHandler extends UpdateHandler {
_log.error("Error downloading plugin", t);
}
}
}
@Override
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
StringBuilder buf = new StringBuilder(64);
buf.append("<b>").append(_("Downloading plugin")).append(' ');
double pct = ((double)alreadyTransferred + (double)currentWrite) /
((double)alreadyTransferred + (double)currentWrite + bytesRemaining);
synchronized (_pct) {
buf.append(_pct.format(pct));
}
buf.append(": ");
buf.append(_("{0}B transferred", DataHelper.formatSize2(currentWrite + alreadyTransferred)));
buf.append("</b>");
updateStatus(buf.toString());
if (_updated)
_mgr.notifyComplete(this, _newVersion, null);
else
_mgr.notifyTaskFailed(this, _errMsg, null);
}
@Override
@@ -293,6 +243,9 @@ public class PluginUpdateHandler extends UpdateHandler {
statusDone("<b>" + _("Plugin {0} has mismatched versions", appName) + "</b>");
return;
}
// set so notifyComplete() will work
_appName = appName;
_newVersion = version;
String minVersion = ConfigClientsHelper.stripHTML(props, "min-i2p-version");
if (minVersion != null &&
@@ -313,7 +266,7 @@ public class PluginUpdateHandler extends UpdateHandler {
boolean wasRunning = false;
File destDir = new SecureDirectory(appDir, appName);
if (destDir.exists()) {
if (Boolean.parseBoolean(props.getProperty("install-only"))) {
if (Boolean.valueOf(props.getProperty("install-only")).booleanValue()) {
to.delete();
statusDone("<b>" + _("Downloaded plugin is for new installs only, but the plugin is already installed", url) + "</b>");
return;
@@ -374,7 +327,7 @@ public class PluginUpdateHandler extends UpdateHandler {
return;
}
// do we defer extraction and installation?
if (Boolean.parseBoolean(props.getProperty("router-restart-required"))) {
if (Boolean.valueOf(props.getProperty("router-restart-required")).booleanValue()) {
// Yup!
try {
if(!FileUtil.copy(to, (new SecureFile( new SecureFile(appDir.getCanonicalPath() +"/" + appName +"/"+ ZIP).getCanonicalPath())) , true, true)) {
@@ -405,7 +358,7 @@ public class PluginUpdateHandler extends UpdateHandler {
}
update = true;
} else {
if (Boolean.parseBoolean(props.getProperty("update-only"))) {
if (Boolean.valueOf(props.getProperty("update-only")).booleanValue()) {
to.delete();
statusDone("<b>" + _("Plugin is for upgrades only, but the plugin is not installed") + "</b>");
return;
@@ -426,7 +379,7 @@ public class PluginUpdateHandler extends UpdateHandler {
_updated = true;
to.delete();
// install != update. Changing the user's settings like this is probabbly a bad idea.
if (Boolean.parseBoolean( props.getProperty("dont-start-at-install"))) {
if (Boolean.valueOf( props.getProperty("dont-start-at-install")).booleanValue()) {
statusDone("<b>" + _("Plugin {0} installed", appName) + "</b>");
if(!update) {
Properties pluginProps = PluginStarter.pluginProperties();
@@ -467,16 +420,10 @@ public class PluginUpdateHandler extends UpdateHandler {
}
private void statusDone(String msg) {
// if we fail, we will pass this back in notifyTaskFailed()
_errMsg = msg;
updateStatus(msg);
scheduleStatusClean(msg);
}
}
@Override
protected void updateStatus(String s) {
super.updateStatus(s);
_appStatus = s;
}
}

View File

@@ -0,0 +1,90 @@
package net.i2p.router.update;
import java.io.File;
import java.net.URI;
import java.util.List;
import net.i2p.router.RouterContext;
import net.i2p.router.util.RFC822Date;
import net.i2p.router.web.ConfigUpdateHandler;
import net.i2p.update.*;
import net.i2p.util.EepHead;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
/**
* Does a simple EepHead to get the last-modified header.
* Moved from NewsFetcher and turned into an UpdateTask.
*
* Overrides UpdateRunner for convenience, does not use super's Eepget StatusListener
*
* @since 0.9.4
*/
class UnsignedUpdateChecker extends UpdateRunner {
private final long _ms;
private boolean _unsignedUpdateAvailable;
protected static final String SIGNED_UPDATE_FILE = "i2pupdate.sud";
public UnsignedUpdateChecker(RouterContext ctx, ConsoleUpdateManager mgr,
List<URI> uris, long lastUpdateTime) {
super(ctx, mgr, uris);
_ms = lastUpdateTime;
}
//////// begin UpdateTask methods
@Override
public UpdateType getType() { return UpdateType.ROUTER_UNSIGNED; }
//////// end UpdateTask methods
@Override
public void run() {
_isRunning = true;
boolean success = false;
try {
success = fetchUnsignedHead();
} finally {
_mgr.notifyCheckComplete(this, _unsignedUpdateAvailable, success);
_isRunning = false;
}
}
/**
* HEAD the update url, and if the last-mod time is newer than the last update we
* downloaded, as stored in the properties, then we download it using eepget.
*/
private boolean fetchUnsignedHead() {
if (_urls.isEmpty())
return false;
_currentURI = _urls.get(0);
String url = _currentURI.toString();
// assume always proxied for now
//boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
int proxyPort = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_PORT, ConfigUpdateHandler.DEFAULT_PROXY_PORT_INT);
try {
EepHead get = new EepHead(_context, proxyHost, proxyPort, 0, url);
if (get.fetch()) {
String lastmod = get.getLastModified();
if (lastmod != null) {
long modtime = RFC822Date.parse822Date(lastmod);
if (modtime <= 0) return false;
if (_ms <= 0) return false;
if (modtime > _ms) {
_unsignedUpdateAvailable = true;
_mgr.notifyVersionAvailable(this, _urls.get(0), getType(), "", getMethod(), _urls,
Long.toString(modtime), "");
}
}
return true;
}
} catch (Throwable t) {
_log.error("Error fetching the unsigned update", t);
}
return false;
}
}

Some files were not shown because too many files have changed in this diff Show More