Compare commits

..

104 Commits

Author SHA1 Message Date
orignal
90a5d02bf6 2.28.0 2019-08-27 10:17:32 -04:00
orignal
44a2549b81 2.28.0 2019-08-27 09:46:54 -04:00
orignal
a2b8d468bc Merge pull request #1412 from nonlinear-chaos-order-etc/openssl
qt: disabled broken UPnP + small qt debugging tweaks
2019-08-27 08:53:44 -04:00
kote
d523f0cadd gitignored autosave files by qtcreator 2019-08-27 19:31:57 +08:00
kote
99116ff097 qt: disabled upnp for now - until upnp fixed 2019-08-27 19:31:28 +08:00
kote
3939ca9eb4 enabled default logging debug option for qt debug builds 2019-08-27 17:51:55 +08:00
kote
b5aa67b491 tweaked debug logging in i2pd_qt 2019-08-27 17:10:53 +08:00
orignal
e42efec220 correct outet plain text length in case of authKeys 2019-08-26 07:35:11 -04:00
orignal
9d06aa2f6a pass authSalt or epk 2019-08-25 20:51:15 -04:00
orignal
80765a797b correct outer cipher text len 2019-08-25 19:14:53 -04:00
orignal
0b5509a1ed correct authClients offset 2019-08-25 14:54:43 -04:00
orignal
478d7b4a83 Merge pull request #1410 from nonlinear-chaos-order-etc/openssl
android various fixes and improvements
2019-08-24 07:31:12 -04:00
kote
9d3b38141a android various fixes and improv 2019-08-24 19:13:10 +08:00
orignal
ab3a4d902e Merge pull request #1409 from nonlinear-chaos-order-etc/openssl
android - show battery optimiz. os dialog menu item added; translated all battery stuff into Russian
2019-08-24 07:03:15 -04:00
kote
5eab5f2437 show battery optimiz. menu item now hidden if not supported by os 2019-08-24 18:00:11 +08:00
kote
80f632c19a show battery optimiz. menu item added; translated all battery stuff into Russian 2019-08-24 17:50:30 +08:00
kote
6e4f18543d added *.local to android/.gitignore 2019-08-24 17:04:04 +08:00
l-n-s
54586c9076 Merge pull request #1408 from docker-and-co/fix-dockerfile-non-existend-deps
fix dockerfile: remove unmet dependencies
2019-08-23 11:02:00 -04:00
orignal
351c899807 cleanup incoming streams on stop 2019-08-23 10:00:49 -04:00
Grigory Kotov
fe45d431d7 fix dockerfile: remove unmet dependencies 2019-08-23 16:20:09 +03:00
orignal
488c2f6d05 bump SDK version 2019-08-22 09:45:49 -04:00
orignal
75ab0909b3 Merge pull request #1405 from nonlinear-chaos-order-etc/openssl
various Android stuff. Fixed #1400
2019-08-22 07:15:36 -04:00
kote
8f82d563c1 various Android stuff. Fixed #1400 2019-08-22 10:00:50 +08:00
orignal
9bbce5dba6 fixed typo 2019-08-21 20:26:19 -04:00
R4SAS
099adab9ed Update README.md
update head badges links, add snapcraft badge
2019-08-16 21:45:44 +03:00
orignal
c8cbf425ac check and send netid for NTCP2 and SSU 2019-08-13 14:55:18 -04:00
orignal
ad9c11cd92 correct parsing of addreses containing # 2019-08-10 22:16:26 -04:00
orignal
3872c2a3f5 use published encrypted instead orig type 2019-08-07 16:18:00 -04:00
orignal
e6a09b49c9 published encrypted flag 2019-08-07 15:43:03 -04:00
orignal
db107602bd handle messages with \r\n 2019-08-02 13:48:39 -04:00
orignal
a6558a61a7 Recognize RedDSA_SHA512_Ed25519 signature type 2019-08-02 12:54:24 -04:00
orignal
254d2b82b3 fixed #1393. store streams by recvStreamID 2019-07-26 14:23:21 -04:00
orignal
97d9795fc9 pass encrepted LeaseSet auth keys 2019-07-16 16:31:17 -04:00
orignal
54071b0e5d set and handle unpublished LeaseSet flag 2019-07-16 11:48:30 -04:00
orignal
925e8316c7 read i2cp.leaseSetAuthType, i2cp.leaseSetClient.dh.nnn and i2cp.leaseSetClient.psk.nnn from tunnel config 2019-07-12 20:58:17 -04:00
orignal
99e1b74023 create encrypted LeaseSet2 with authentication 2019-07-12 15:40:59 -04:00
orignal
7d68ccca53 create encrypted LeaseSet2 with authentication 2019-07-12 15:37:32 -04:00
orignal
a090114066 send data message wih raw type fpr raw datagrams 2019-07-10 13:31:49 -04:00
orignal
a204841abb handle RAW SEND 2019-07-10 13:30:31 -04:00
orignal
cc451809cc send/receive raw datagrams through the SAM 2019-07-10 11:32:56 -04:00
orignal
a605e4bab6 send and recieve raw datagrams 2019-07-09 21:33:55 -04:00
orignal
3f0534134d check for malformed b33 2019-07-04 13:05:39 -04:00
orignal
3acfb129cd 2.27.0 2019-07-03 12:38:55 -04:00
orignal
6ccef66920 call shutdown before close 2019-06-26 10:47:16 -04:00
orignal
e9fa4e94a6 Merge pull request #1374 from rszibele/openssl
BOB: fix status command.
2019-06-25 18:28:43 -04:00
orignal
fecc0c4640 don't call destructor twice 2019-06-25 16:37:06 -04:00
rszibele
b759294975 BOB: fix status command. 2019-06-25 19:18:40 +02:00
rszibele
a23e845c03 BOB: improve comment and remove error log in list command 2019-06-25 19:04:27 +02:00
rszibele
cb8373e487 BOB: status response now correctly starts with "OK DATA". 2019-06-25 17:59:44 +02:00
orignal
8e919ddc8e use monotonic clock for uptime 2019-06-19 11:43:04 -04:00
orignal
832a9ab6b5 don't set random NTCP2 port if already set 2019-06-18 15:47:58 -04:00
orignal
13732ac333 fix #1363 try connect in SSU's thread 2019-06-14 15:43:03 -04:00
orignal
3e932a55f4 fixed typo 2019-06-11 15:09:10 -04:00
orignal
74e8610ec9 DH auth for encrypted LeaseSet2 2019-06-11 10:40:53 -04:00
orignal
089a60ded6 Merge pull request #1367 from rszibele/openssl
fix build on BSDs, as "isset" is a defined macro.
2019-06-10 19:10:34 -04:00
rszibele
c8eeefe194 fix build on BSDs, as "isset" is a defined macro. 2019-06-10 23:04:59 +02:00
orignal
85eeba14c1 Merge branch 'openssl' of https://github.com/PurpleI2P/i2pd into openssl 2019-06-08 21:24:16 -04:00
orignal
f6f45eab39 flood encrypted LeaseSet2 with store hash 2019-06-08 21:23:25 -04:00
R4SAS
a74065f775 [qt] dont build UnixDaemon.cpp 2019-06-09 01:04:43 +03:00
R4SAS
48d02f7e09 [qt] update headers, fix ChaCha source name, remove duplicates 2019-06-08 22:24:11 +03:00
R4SAS
e60549f8df [qt] fix build 2019-06-08 21:16:20 +03:00
orignal
41f4f4713e handle i2cp.leaseSetPrivKey 2019-06-07 14:51:08 -04:00
orignal
213a292fd5 correct offsets for auth data 2019-06-07 11:59:48 -04:00
R4SAS
79630e844b 2.26.0 2019-06-07 17:25:55 +03:00
orignal
1c9e46dbb3 2.26.0 2019-06-07 10:04:57 -04:00
orignal
0a299284f8 correct check for PSK auth 2019-06-06 13:58:31 -04:00
orignal
347a5f7346 pass secret to encrypted LeaseSet2 2019-06-06 12:33:33 -04:00
R4SAS
c6a903572c [HTTP] add PROFIND support 2019-06-06 18:07:17 +03:00
orignal
14f0d6d26b extract client auth data 2019-06-05 15:57:20 -04:00
orignal
485f105555 fixed typo 2019-06-04 15:12:19 -04:00
orignal
686c0b776f common blinding code for public and private keys 2019-06-04 14:47:40 -04:00
orignal
828862ea49 store hash for ECDSA blidning 2019-06-03 12:51:57 -04:00
orignal
c4dffa4dc8 remove obsolete reseeds 2019-06-01 09:37:02 -04:00
orignal
3c1906e3d4 Merge pull request #1360 from khumarahn/openssl
link libi2pd to boost and zlib
2019-06-01 09:28:54 -04:00
Alexey Korepanov
7147a3694c link libi2pd to boost and zlib 2019-06-01 12:57:09 +01:00
orignal
64707dbb22 key blinding test 2019-05-31 11:57:16 -04:00
R4SAS
554e8eeef3 [appveyor] remove gcc-ada and gcc-objc packages
https://github.com/msys2/MINGW-packages/issues/5434#issuecomment-496706950
2019-05-31 16:32:32 +03:00
orignal
5e10549543 disable NTCP by default 2019-05-30 19:18:56 -04:00
orignal
685f45bd76 publish/unpublish NTCP2 address depending on network status 2019-05-30 17:52:44 -04:00
orignal
61d84dd4c1 publish/unpublish NTCP2 address depending on network status 2019-05-30 17:48:49 -04:00
orignal
4d10593bb1 publish/unpublish NTCP2 address depending on network status 2019-05-30 16:11:35 -04:00
orignal
fbb8903774 correct buffer size for ECDSA blinding 2019-05-30 13:57:43 -04:00
orignal
e8cac91bb7 blind ECDSA public key 2019-05-29 15:48:35 -04:00
orignal
7328ffa036 Merge pull request #1359 from rszibele/openssl
BOB modifications.
2019-05-29 14:07:30 -04:00
rszibele
a03e828317 BOB: status: forgot to pass destination for current tunnel. 2019-05-29 18:47:35 +02:00
rszibele
93d4dc70cf BOB fixes. 2019-05-29 18:05:03 +02:00
orignal
8e3d16e9fb update ipv6 addresses from SSU rather than NTCP or NTCP2 2019-05-29 11:36:58 -04:00
orignal
07405e57b9 fixed typo 2019-05-25 14:58:10 -04:00
orignal
354c9187db detect our ipv6 address 2019-05-23 15:59:44 -04:00
orignal
af33df3004 common buffer size 2019-05-23 11:49:54 -04:00
orignal
78bfde237f allocate actual RouterInfo's buffer size 2019-05-23 09:34:04 -04:00
orignal
7b9033d678 allocate actual RouterInfo's buffer size 2019-05-23 09:32:07 -04:00
orignal
f784cfad46 correct RouterInfo buffer size 2019-05-23 06:56:41 -04:00
orignal
e40c139ff1 blind ECDSA private key 2019-05-22 16:15:11 -04:00
orignal
edf4f7695d fix #1352. correct response for 'list' command 2019-05-22 12:45:50 -04:00
orignal
60ec03237e blidning for ECDSA 2019-05-16 15:49:07 -04:00
R4SAS
a91641e427 fix #823, reindent code 2019-05-16 09:39:22 +03:00
orignal
5c3992018f fixed #1350 use GetAddress insted GetIdentHash 2019-05-15 14:22:19 -04:00
R4SAS
f5b682619f [webconsole] move b33 to spoiler, fix typo 2019-05-15 14:15:10 +03:00
orignal
743fa745b7 show b33 address for encrypted LeaseSet2 2019-05-14 14:42:10 -04:00
orignal
39400fd381 move key blinding code from LeaseSet.cpp to Blinding.cpp 2019-05-14 11:42:25 -04:00
orignal
5299ac35a6 create NTCP2 ipv6 address 2019-05-13 11:40:08 -04:00
orignal
ef76ed394c publish SSU ipv6 address if NTCP if disabled 2019-05-12 13:36:26 -04:00
orignal
1472637de7 skip introducers for non-SSU address 2019-05-11 07:27:34 -04:00
69 changed files with 2494 additions and 1330 deletions

View File

@@ -1,6 +1,45 @@
# for this file format description,
# see https://github.com/olivierlacan/keep-a-changelog
## [2.28.0] - 2019-08-27
### Added
- RAW datagrams in SAM
- Publishing encrypted LeaseSet2 with DH or PSH authentication
- Ability to disable battery optimization for Android
- Transport Network ID Check
### Changed
- Set and handle published encrypted flag for LeaseSet2
### Fixed
- ReceiveID changes in the same stream
- "\r\n" command terminator in SAM
- Addressbook lines with signatures
## [2.27.0] - 2019-07-03
### Added
- Support of PSK and DH authentication for encrypted LeaseSet2
### Changed
- Uptime is based on monotonic timer
### Fixed
- BOB status command response
- Correct NTCP2 port if NTCP is disabled
- Flood encrypted LeaseSet2 with store hash
## [2.26.0] - 2019-06-07
### Added
- HTTP method "PROPFIND"
- Detection of external ipv6 address through the SSU
- NTCP2 publishing depends on network status
### Changed
- ntcp is disabled by default, ntcp2 is published by default
- Response to BOB's "list" command
- ipv6 address is not longer NTCP's local endpoint's address
- Reseeds list
- HTTP_REFERER stripping in httpproxy (#823)
### Fixed
- Check and handle incorrect BOB input
- Ignore introducers for NTCP or NTCP2 addresses
- RouterInfo check from NTCP2
## [2.25.0] - 2019-05-09
### Added
- Create, publish and handle encrypted LeaseSet2

View File

@@ -1,5 +1,6 @@
![GitHub release](https://img.shields.io/github/release/PurpleI2P/i2pd.svg?label=latest%20release)
![GitHub](https://img.shields.io/github/license/PurpleI2P/i2pd.svg)
[![GitHub release](https://img.shields.io/github/release/PurpleI2P/i2pd.svg?label=latest%20release)](https://github.com/PurpleI2P/i2pd/releases/latest)
[![Snapcraft release](https://snapcraft.io/i2pd/badge.svg)](https://snapcraft.io/i2pd)
[![License](https://img.shields.io/github/license/PurpleI2P/i2pd.svg)](https://github.com/PurpleI2P/i2pd/blob/openssl/LICENSE)
i2pd
====
@@ -66,6 +67,7 @@ Build instructions:
* Mac OS X - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd)
* CentOS / Fedora / Mageia - [![Build Status](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/)
* Docker image - [![Build Status](https://dockerbuildbadges.quelltext.eu/status.svg?organization=meeh&repository=i2pd)](https://hub.docker.com/r/meeh/i2pd/builds/)
* Snap - [![Snap Status](https://build.snapcraft.io/badge/PurpleI2P/i2pd-snap.svg)](https://build.snapcraft.io/user/PurpleI2P/i2pd-snap)
* FreeBSD
* Android
* iOS

View File

@@ -1,5 +1,5 @@
#define I2Pd_AppName "i2pd"
#define I2Pd_ver "2.25.0"
#define I2Pd_ver "2.28.0"
#define I2Pd_Publisher "PurpleI2P"
[Setup]

4
android/.gitignore vendored
View File

@@ -12,5 +12,5 @@ local.properties
build.sh
android.iml
build
*.iml
*.local

View File

@@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application
android:allowBackup="true"

View File

@@ -5,7 +5,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.2'
classpath 'com.android.tools.build:gradle:3.4.2'
}
}
@@ -16,21 +16,22 @@ repositories {
maven {
url 'https://maven.google.com'
}
google()
}
dependencies {
implementation 'com.android.support:support-compat:28.0.0'
implementation 'androidx.core:core:1.0.2'
}
android {
compileSdkVersion 28
compileSdkVersion 29
buildToolsVersion "28.0.3"
defaultConfig {
applicationId "org.purplei2p.i2pd"
targetSdkVersion 28
targetSdkVersion 29
minSdkVersion 14
versionCode 2250
versionName "2.25.0"
versionCode 2280
versionName "2.28.0"
ndk {
abiFilters 'armeabi-v7a'
abiFilters 'x86'
@@ -81,4 +82,8 @@ android {
path './jni/Android.mk'
}
}
compileOptions {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
}

View File

@@ -1 +1,3 @@
android.enableJetifier=true
android.useAndroidX=true
org.gradle.parallel=true

View File

@@ -1,6 +1,6 @@
#Thu Mar 14 18:21:08 MSK 2019
#Tue Aug 20 14:39:08 MSK 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip

View File

@@ -11,4 +11,4 @@
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-28
target=android-29

View File

@@ -3,14 +3,19 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".I2PDActivity">
<item
android:id="@+id/action_graceful_stop"
android:title="@string/action_graceful_stop"
android:orderInCategory="98"
/>
<item
android:id="@+id/action_stop"
android:title="@string/action_stop"
android:orderInCategory="99"
/>
<group android:id="@+id/group_i2pd_control" >
<item
android:id="@+id/action_stop"
android:orderInCategory="99"
android:title="@string/action_stop" />
<item
android:id="@+id/action_graceful_stop"
android:orderInCategory="98"
android:title="@string/action_graceful_stop" />
</group>
<group android:id="@+id/group_various" >
<item
android:id="@+id/action_battery_otimizations"
android:title="@string/menu_item_battery_optimizations_str" />
</group>
</menu>

View File

@@ -17,4 +17,12 @@
<string name="remaining">осталось</string>
<string name="title_activity_i2_pdperms_asker_prompt">Запрос</string>
<string name="permDenied">Права для записи на SD карту отклонены, вам необходимо предоставить их для продолжения</string>
<string name="menu_item_battery_optimizations_str">Оптимизации аккумулятора</string>
<string name="battery_optimizations_enabled">Оптимизации аккумулятора включены</string>
<string name="device_does_not_support_disabling_battery_optimizations">Ваша версия Андроид не поддерживает отключение оптимизаций аккумулятора</string>
<string name="battery_optimizations_enabled_explained">Ваша операционная система осуществляет оптимизации расхода аккумулятора, которые могут приводить к выгрузке I2PD из памяти и прекращению его работы с целью сэкономить заряд аккумулятора.\nРекомендуется отключить эти оптимизации.</string>
<string name="battery_optimizations_enabled_dialog" >Ваша операционная система осуществляет оптимизации расхода аккумулятора, которые могут приводить к выгрузке I2PD из памяти и прекращению его работы с целью сэкономить заряд аккумулятора.\n\nВам сейчас будет предложено разрешить отключение этих оптимизаций.</string>
<string name="continue_str">Продолжить</string>
<string name="os_version_does_not_support_battery_optimizations_show_os_dialog_api">Ваша версия Андроид не поддерживает показ диалога об оптимизациях аккумулятора для приложений.</string>
<string name="shutdown_canceled">Плановая остановка отменена</string>
</resources>

View File

@@ -17,4 +17,12 @@
<string name="remaining">remaining</string>
<string name="title_activity_i2_pdperms_asker_prompt">Prompt</string>
<string name="permDenied">SD card write permission denied, you need to allow this to continue</string>
<string name="battery_optimizations_enabled">Battery optimizations enabled</string>
<string name="battery_optimizations_enabled_explained">Your Android is doing some heavy battery optimizations on I2PD that might lead to daemon closing with no other reason.\nIt is recommended to allow disabling those battery optimizations.</string>
<string name="battery_optimizations_enabled_dialog" >Your Android is doing some heavy battery optimizations on I2PD that might lead to daemon closing with no other reason.\n\nYou will now be asked to allow to disable those.</string>
<string name="continue_str">Continue</string>
<string name="device_does_not_support_disabling_battery_optimizations">Your Android version does not support opting out of battery optimizations</string>
<string name="menu_item_battery_optimizations_str">Battery Optimizations</string>
<string name="os_version_does_not_support_battery_optimizations_show_os_dialog_api">Your Android OS version does not support showing the dialog for battery optimizations for applications.</string>
<string name="shutdown_canceled">Planned shutdown canceled</string>
</resources>

View File

@@ -1,6 +1,5 @@
package org.purplei2p.i2pd;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -11,10 +10,9 @@ import android.content.Intent;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;
public class ForegroundService extends Service {
private static final String TAG="FgService";
@@ -112,14 +110,15 @@ public class ForegroundService extends Service {
// If earlier version channel ID is not used
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
String channelId = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) ? createNotificationChannel() : "";
String channelId = Build.VERSION.SDK_INT >= 26 ? createNotificationChannel() : "";
// Set the info for the views that show in the notification panel.
Notification notification = new NotificationCompat.Builder(this, channelId)
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId)
.setOngoing(true)
.setSmallIcon(R.drawable.itoopie_notification_icon) // the status icon
.setPriority(Notification.PRIORITY_DEFAULT)
.setCategory(Notification.CATEGORY_SERVICE)
.setSmallIcon(R.drawable.itoopie_notification_icon); // the status icon
if(Build.VERSION.SDK_INT >= 16) builder = builder.setPriority(Notification.PRIORITY_DEFAULT);
if(Build.VERSION.SDK_INT >= 21) builder = builder.setCategory(Notification.CATEGORY_SERVICE);
Notification notification = builder
.setTicker(text) // the status text
.setWhen(System.currentTimeMillis()) // the time stamp
.setContentTitle(getText(R.string.app_name)) // the label of the entry
@@ -141,9 +140,10 @@ public class ForegroundService extends Service {
//chan.setLightColor(Color.PURPLE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager service = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
service.createNotificationChannel(chan);
if(service!=null)service.createNotificationChannel(chan);
else Log.e(TAG, "error: NOTIFICATION_SERVICE is null");
return channelId;
}
private static final DaemonSingleton daemon = DaemonSingleton.getInstance();
private static final DaemonSingleton daemon = DaemonSingleton.getInstance();
}

View File

@@ -14,32 +14,44 @@ import java.util.Timer;
import java.util.TimerTask;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.AssetManager;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
// For future package update checking
import org.purplei2p.i2pd.BuildConfig;
import static android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS;
public class I2PDActivity extends Activity {
private static final String TAG = "i2pdActvt";
private static final int MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
public static final int GRACEFUL_DELAY_MILLIS = 10 * 60 * 1000;
public static final String PACKAGE_URI_SCHEME = "package:";
private TextView textView;
private boolean assetsCopied;
@@ -53,32 +65,27 @@ public class I2PDActivity extends Activity {
public void daemonStateUpdate()
{
processAssets();
runOnUiThread(new Runnable(){
@Override
public void run() {
try {
if(textView==null) return;
Throwable tr = daemon.getLastThrowable();
if(tr!=null) {
textView.setText(throwableToString(tr));
return;
}
DaemonSingleton.State state = daemon.getState();
textView.setText(
String.valueOf(getText(state.getStatusStringResourceId()))+
(DaemonSingleton.State.startFailed.equals(state) ? ": "+daemon.getDaemonStartResult() : "")+
(DaemonSingleton.State.gracefulShutdownInProgress.equals(state) ? ": "+formatGraceTimeRemaining()+" "+getText(R.string.remaining) : "")
);
} catch (Throwable tr) {
Log.e(TAG,"error ignored",tr);
}
}
});
runOnUiThread(() -> {
try {
if(textView==null) return;
Throwable tr = daemon.getLastThrowable();
if(tr!=null) {
textView.setText(throwableToString(tr));
return;
}
DaemonSingleton.State state = daemon.getState();
String startResultStr = DaemonSingleton.State.startFailed.equals(state) ? String.format(": %s", daemon.getDaemonStartResult()) : "";
String graceStr = DaemonSingleton.State.gracefulShutdownInProgress.equals(state) ? String.format(": %s %s", formatGraceTimeRemaining(), getText(R.string.remaining)) : "";
textView.setText(String.format("%s%s%s", getText(state.getStatusStringResourceId()), startResultStr, graceStr));
} catch (Throwable tr) {
Log.e(TAG,"error ignored",tr);
}
});
}
};
private static volatile long graceStartedMillis;
private static final Object graceStartedMillis_LOCK=new Object();
private Menu optionsMenu;
private static String formatGraceTimeRemaining() {
long remainingSeconds;
@@ -92,6 +99,7 @@ public class I2PDActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate");
super.onCreate(savedInstanceState);
textView = new TextView(this);
@@ -121,6 +129,8 @@ public class I2PDActivity extends Activity {
}
rescheduleGraceStop(gracefulQuitTimer, gracefulStopAtMillis);
}
openBatteryOptimizationDialogIfNeeded();
}
@Override
@@ -128,7 +138,7 @@ public class I2PDActivity extends Activity {
super.onDestroy();
textView = null;
daemon.removeStateChangeListener(daemonStateUpdatedListener);
//cancelGracefulStop();
//cancelGracefulStop0();
try{
doUnbindService();
}catch(Throwable tr){
@@ -137,24 +147,20 @@ public class I2PDActivity extends Activity {
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
{
switch (requestCode)
{
case MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE:
{
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
Log.e(TAG, "Memory permission granted");
else
Log.e(TAG, "Memory permission declined");
// TODO: terminate
return;
}
default: ;
}
if (requestCode == MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
Log.e(TAG, "WR_EXT_STORAGE perm granted");
else {
Log.e(TAG, "WR_EXT_STORAGE perm declined, stopping i2pd");
i2pdStop();
//TODO must work w/o this perm, ask orignal
}
}
}
private static void cancelGracefulStop() {
private void cancelGracefulStop0() {
Timer gracefulQuitTimer = getGracefulQuitTimer();
if(gracefulQuitTimer!=null) {
gracefulQuitTimer.cancel();
@@ -225,11 +231,17 @@ public class I2PDActivity extends Activity {
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.options_main, menu);
menu.findItem(R.id.action_battery_otimizations).setVisible(isBatteryOptimizationsOpenOsDialogApiAvailable());
this.optionsMenu = menu;
return true;
}
private boolean isBatteryOptimizationsOpenOsDialogApiAvailable() {
return android.os.Build.VERSION.SDK_INT >= 23;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
@@ -240,37 +252,43 @@ public class I2PDActivity extends Activity {
i2pdStop();
return true;
case R.id.action_graceful_stop:
if (getGracefulQuitTimer()!= null)
{
item.setTitle(R.string.action_graceful_stop);
i2pdCancelGracefulStop ();
synchronized (graceStartedMillis_LOCK) {
if (getGracefulQuitTimer() != null)
cancelGracefulStop();
else
i2pdGracefulStop();
}
else
{
item.setTitle(R.string.action_cancel_graceful_stop);
i2pdGracefulStop();
}
return true;
return true;
case R.id.action_battery_otimizations:
onActionBatteryOptimizations();
return true;
}
return super.onOptionsItemSelected(item);
}
private void i2pdStop() {
cancelGracefulStop();
new Thread(new Runnable(){
@Override
public void run() {
Log.d(TAG, "stopping");
try{
daemon.stopDaemon();
}catch (Throwable tr) {
Log.e(TAG, "", tr);
}
private void onActionBatteryOptimizations() {
if (isBatteryOptimizationsOpenOsDialogApiAvailable()) {
try {
startActivity(new Intent(ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS));
} catch (ActivityNotFoundException e) {
Log.e(TAG,"BATT_OPTIM_DIALOG_ActvtNotFound", e);
Toast.makeText(this, R.string.os_version_does_not_support_battery_optimizations_show_os_dialog_api, Toast.LENGTH_SHORT).show();
}
}
}
},"stop").start();
private void i2pdStop() {
cancelGracefulStop0();
new Thread(() -> {
Log.d(TAG, "stopping");
try {
daemon.stopDaemon();
} catch (Throwable tr) {
Log.e(TAG, "", tr);
}
quit(); //TODO make menu items for starting i2pd. On my Android, I need to reboot the OS to restart i2pd.
},"stop").start();
}
private static volatile Timer gracefulQuitTimer;
@@ -288,55 +306,45 @@ public class I2PDActivity extends Activity {
}
Toast.makeText(this, R.string.graceful_stop_is_in_progress,
Toast.LENGTH_SHORT).show();
new Thread(new Runnable(){
@Override
public void run() {
try {
Log.d(TAG, "grac stopping");
if(daemon.isStartedOkay()) {
daemon.stopAcceptingTunnels();
long gracefulStopAtMillis;
synchronized (graceStartedMillis_LOCK) {
graceStartedMillis = System.currentTimeMillis();
gracefulStopAtMillis = graceStartedMillis + GRACEFUL_DELAY_MILLIS;
}
rescheduleGraceStop(null,gracefulStopAtMillis);
} else {
i2pdStop();
}
} catch(Throwable tr) {
Log.e(TAG,"",tr);
}
}
},"gracInit").start();
new Thread(() -> {
try {
Log.d(TAG, "grac stopping");
if(daemon.isStartedOkay()) {
daemon.stopAcceptingTunnels();
long gracefulStopAtMillis;
synchronized (graceStartedMillis_LOCK) {
graceStartedMillis = System.currentTimeMillis();
gracefulStopAtMillis = graceStartedMillis + GRACEFUL_DELAY_MILLIS;
}
rescheduleGraceStop(null,gracefulStopAtMillis);
} else {
i2pdStop();
}
} catch(Throwable tr) {
Log.e(TAG,"",tr);
}
},"gracInit").start();
}
private void i2pdCancelGracefulStop()
private void cancelGracefulStop()
{
cancelGracefulStop();
Toast.makeText(this, R.string.startedOkay, Toast.LENGTH_SHORT).show();
new Thread(new Runnable()
{
@Override
public void run()
{
try
{
Log.d(TAG, "grac stopping cancel");
if(daemon.isStartedOkay())
daemon.startAcceptingTunnels();
else
i2pdStop();
cancelGracefulStop0();
new Thread(() -> {
try
{
Log.d(TAG, "canceling grac stop");
if(daemon.isStartedOkay()) {
daemon.startAcceptingTunnels();
runOnUiThread(() -> Toast.makeText(this, R.string.shutdown_canceled, Toast.LENGTH_SHORT).show());
}
catch(Throwable tr)
{
Log.e(TAG,"",tr);
}
}
},"gracCancel").start();
else
i2pdStop();
}
catch(Throwable tr)
{
Log.e(TAG,"",tr);
}
},"gracCancel").start();
}
private void rescheduleGraceStop(Timer gracefulQuitTimerOld, long gracefulStopAtMillis) {
@@ -364,8 +372,19 @@ public class I2PDActivity extends Activity {
return gracefulQuitTimer;
}
private static void setGracefulQuitTimer(Timer gracefulQuitTimer) {
private void setGracefulQuitTimer(Timer gracefulQuitTimer) {
I2PDActivity.gracefulQuitTimer = gracefulQuitTimer;
runOnUiThread(()-> {
Menu menu = optionsMenu;
if (menu != null) {
MenuItem item = menu.findItem(R.id.action_graceful_stop);
if (item != null) {
synchronized (graceStartedMillis_LOCK) {
item.setTitle(getGracefulQuitTimer() != null ? R.string.action_cancel_graceful_stop : R.string.action_graceful_stop);
}
}
}
});
}
/**
@@ -388,19 +407,22 @@ public class I2PDActivity extends Activity {
// to a file. That doesn't appear to be the case. If the returned array is
// null or has 0 length, we assume the path is to a file. This means empty
// directories will get turned into files.
if (contents == null || contents.length == 0)
throw new IOException();
if (contents == null || contents.length == 0) {
copyFileAsset(path);
return;
}
// Make the directory.
File dir = new File(i2pdpath, path);
dir.mkdirs();
boolean result = dir.mkdirs();
Log.d(TAG, "dir.mkdirs() returned " + result);
// Recurse on the contents.
for (String entry : contents) {
copyAsset(path + "/" + entry);
copyAsset(path + '/' + entry);
}
} catch (IOException e) {
copyFileAsset(path);
Log.e(TAG, "ex ignored for path='" + path + "'", e);
}
}
@@ -413,63 +435,89 @@ public class I2PDActivity extends Activity {
*/
private void copyFileAsset(String path) {
File file = new File(i2pdpath, path);
if(!file.exists()) try {
InputStream in = getAssets().open(path);
OutputStream out = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int read = in.read(buffer);
while (read != -1) {
out.write(buffer, 0, read);
read = in.read(buffer);
if(!file.exists()) {
try {
try (InputStream in = getAssets().open(path) ) {
try (OutputStream out = new FileOutputStream(file)) {
byte[] buffer = new byte[1024];
int read = in.read(buffer);
while (read != -1) {
out.write(buffer, 0, read);
read = in.read(buffer);
}
}
}
} catch (IOException e) {
Log.e(TAG, "", e);
}
out.close();
in.close();
} catch (IOException e) {
Log.e(TAG, "", e);
}
}
private void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
for (File child : fileOrDirectory.listFiles()) {
deleteRecursive(child);
File[] files = fileOrDirectory.listFiles();
if(files!=null) {
for (File child : files) {
deleteRecursive(child);
}
}
}
fileOrDirectory.delete();
boolean deleteResult = fileOrDirectory.delete();
if(!deleteResult)Log.e(TAG, "fileOrDirectory.delete() returned "+deleteResult+", absolute path='"+fileOrDirectory.getAbsolutePath()+"'");
}
private void processAssets() {
if (!assetsCopied) try {
assetsCopied = true; // prevent from running on every state update
File holderfile = new File(i2pdpath, "assets.ready");
File holderFile = new File(i2pdpath, "assets.ready");
String versionName = BuildConfig.VERSION_NAME; // here will be app version, like 2.XX.XX
StringBuilder text = new StringBuilder();
if (holderfile.exists()) try { // if holder file exists, read assets version string
BufferedReader br = new BufferedReader(new FileReader(holderfile));
String line;
if (holderFile.exists()) {
try { // if holder file exists, read assets version string
FileReader fileReader = new FileReader(holderFile);
while ((line = br.readLine()) != null) {
text.append(line);
}
br.close();
}
catch (IOException e) {
Log.e(TAG, "", e);
}
try {
BufferedReader br = new BufferedReader(fileReader);
try {
String line;
while ((line = br.readLine()) != null) {
text.append(line);
}
}finally {
try{
br.close();
} catch (IOException e) {
Log.e(TAG, "", e);
}
}
} finally {
try{
fileReader.close();
} catch (IOException e) {
Log.e(TAG, "", e);
}
}
} catch (IOException e) {
Log.e(TAG, "", e);
}
}
// if version differs from current app version or null, try to delete certificates folder
if (!text.toString().contains(versionName)) try {
holderfile.delete();
File certpath = new File(i2pdpath, "certificates");
deleteRecursive(certpath);
boolean deleteResult = holderFile.delete();
if(!deleteResult)Log.e(TAG, "holderFile.delete() returned "+deleteResult+", absolute path='"+holderFile.getAbsolutePath()+"'");
File certPath = new File(i2pdpath, "certificates");
deleteRecursive(certPath);
}
catch (Throwable tr) {
Log.e(TAG, "", tr);
}
// copy assets. If processed file exists, it won't be overwrited
// copy assets. If processed file exists, it won't be overwritten
copyAsset("addressbook");
copyAsset("certificates");
copyAsset("tunnels.d");
@@ -478,14 +526,95 @@ public class I2PDActivity extends Activity {
copyAsset("tunnels.conf");
// update holder file about successful copying
FileWriter writer = new FileWriter(holderfile);
writer.append(versionName);
writer.flush();
writer.close();
FileWriter writer = new FileWriter(holderFile);
try {
writer.append(versionName);
} finally {
try{
writer.close();
}catch (IOException e){
Log.e(TAG,"on writer close", e);
}
}
}
catch (Throwable tr)
{
Log.e(TAG,"copy assets",tr);
Log.e(TAG,"on assets copying", tr);
}
}
@SuppressLint("BatteryLife")
private void openBatteryOptimizationDialogIfNeeded() {
boolean questionEnabled = getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true);
Log.i(TAG,"BATT_OPTIM_questionEnabled=="+questionEnabled);
if (!isKnownIgnoringBatteryOptimizations()
&& android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M
&& questionEnabled) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.battery_optimizations_enabled);
builder.setMessage(R.string.battery_optimizations_enabled_dialog);
builder.setPositiveButton(R.string.continue_str, (dialog, which) -> {
try {
startActivity(new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Uri.parse(PACKAGE_URI_SCHEME + getPackageName())));
} catch (ActivityNotFoundException e) {
Log.e(TAG,"BATT_OPTIM_ActvtNotFound", e);
Toast.makeText(this, R.string.device_does_not_support_disabling_battery_optimizations, Toast.LENGTH_SHORT).show();
}
});
builder.setOnDismissListener(dialog -> setNeverAskForBatteryOptimizationsAgain());
final AlertDialog dialog = builder.create();
dialog.setCanceledOnTouchOutside(false);
dialog.show();
}
}
private void setNeverAskForBatteryOptimizationsAgain() {
getPreferences().edit().putBoolean(getBatteryOptimizationPreferenceKey(), false).apply();
}
protected boolean isKnownIgnoringBatteryOptimizations() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
if (pm == null) {
Log.i(TAG, "BATT_OPTIM: POWER_SERVICE==null");
return false;
}
boolean ignoring = pm.isIgnoringBatteryOptimizations(getPackageName());
Log.i(TAG, "BATT_OPTIM: ignoring==" + ignoring);
return ignoring;
} else {
Log.i(TAG, "BATT_OPTIM: old sdk version=="+Build.VERSION.SDK_INT);
return false;
}
}
protected SharedPreferences getPreferences() {
return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
}
private String getBatteryOptimizationPreferenceKey() {
@SuppressLint("HardwareIds") String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
return "show_battery_optimization" + (device == null ? "" : device);
}
private void quit() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
finishAndRemoveTask();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
finishAffinity();
} else {
//moveTaskToBack(true);
finish();
}
}catch (Throwable tr) {
Log.e(TAG, "", tr);
}
try{
daemon.stopDaemon();
}catch (Throwable tr) {
Log.e(TAG, "", tr);
}
System.exit(0);
}
}

View File

@@ -1,4 +1,4 @@
version: 2.25.0.{build}
version: 2.28.0.{build}
pull_requests:
do_not_increment_build_number: true
branches:
@@ -17,7 +17,7 @@ environment:
- MSYSTEM: MINGW32
install:
- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc"
- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc"
- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu --force"
- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu --force"

View File

@@ -81,6 +81,7 @@ set (LIBI2PD_SRC
"${LIBI2PD_SRC_DIR}/Poly1305.cpp"
"${LIBI2PD_SRC_DIR}/Ed25519.cpp"
"${LIBI2PD_SRC_DIR}/NTCP2.cpp"
"${LIBI2PD_SRC_DIR}/Blinding.cpp"
)
if (WITH_WEBSOCKETS)
@@ -469,6 +470,7 @@ if (WITH_BINARY)
if (WITH_STATIC)
set(DL_LIB ${CMAKE_DL_LIBS})
endif()
target_link_libraries(libi2pd ${Boost_LIBRARIES} ${ZLIB_LIBRARY})
target_link_libraries( "${PROJECT_NAME}" libi2pd libi2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES})
install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime)

View File

@@ -1,34 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIF5TCCA82gAwIBAgIRANFIiHpTaRY2Z30TQOiuqFcwDQYJKoZIhvcNAQELBQAw
cDELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE
ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxGTAXBgNVBAMM
EGF0b21pa2VAbWFpbC5pMnAwHhcNMTYwODAyMTQyNDEyWhcNMjYwODAyMTQyNDEy
WjBwMQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYD
VQQKExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEZMBcGA1UE
AwwQYXRvbWlrZUBtYWlsLmkycDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
ggIBAMLRmxclaAvm405JLHNNiniUi0aZaBoLJ+afwn2LGfTDUhTD5Y8lW6V9o90n
eTNOCaiid7bWpVBkA1M4gZ9TdUnP0POa99jXZbj4PHFRl1l8k4Ap12PUO3hgwtH7
7j7j+UPaIuE2y+U7hJbmyQ0v7r8yjGWSTtSqs+exNhyr4Mh7DvacZySZ+oqQdXYA
vnfDpBX1dKlN1Nb4XloG0uE1OK1YfJoC+p+v8qXjKagIdZgThdmsWcQ82EGI+Q9u
VfrE4m3CNwJy0X86wMNYqHej88wBHnJMmTm+cZtFLVmZsRqnuLAQL1wrfCbGSltR
zhVQHTysLwMz9+llTXtzMf+R2kcEAYWiPc5IRVU+LvkN/610r5fuHW+OcQ9ZgRVn
PMqlv5PDG2ZxdIOAQQsOd7fH0r5q3MhqlVstVE45Rl33uA+M7wjJK2cvnOoSioxp
szn2GIZliXQXo4dJczgfN2U4PLBGRBGmrB1R2S1YsG6CrSJuMCX14VKJP69Nfm8a
EDA5GKNke+ZpXCszPLaNMB70LVFQc9FmMhsOgLIIoJBgd61uMgokMJJMLaWN0RaK
w1ZduxYGUmg2T2pi/clIkVzZmlcHKViUn0sMcKD+ibEPOvQIB/3HPEEt6iIkanc/
da5IFzikkaykt/Tu6o8rreeEu65HkIxFaCHegSXLHSyxj00BAgMBAAGjejB4MA4G
A1UdDwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYD
VR0TAQH/BAUwAwEB/zAZBgNVHQ4EEgQQYXRvbWlrZUBtYWlsLmkycDAbBgNVHSME
FDASgBBhdG9taWtlQG1haWwuaTJwMA0GCSqGSIb3DQEBCwUAA4ICAQAA0MdWfN/N
1q5CdJqDyw4JQwzdYkA27Wr02qIcmwnqjcCEDPl4uDTyqN9gbEpJ48AcsdXRa6GE
lLh/qJ67I6YDe63LuhndzRULNgxGHVMGS8kBJIssQehb2rOFnbUTp0gMR+0QpXXe
omase4kL90c9uuYX1vXaO/ADssY2/QX49prwJO+UY/jGhcX4YheFI/teA85u6Qko
ero437Shqhl0kbdK+eBkOFf9a7mGxpMT73KE1jFS6433W4fFOkybQ1dcS0qStaUM
3qKC0EQCbAl1seAp3AGuG46swHZB0rZ1WCKVAr5yqCWSWMYO+fL6FosNg9z/VDVh
g6FFfoGrv19yaVFa9AvQsk1ATZ+bwtHProNx2Xet9pnAI30dT16+C5wCctoR6RVf
iOHl6CGqadjOycbMDVvOfJhypNDgWW3gBaCfXiAocJTLpR7hKNZ2bnvcP2xyXH1j
Qz/kiMJoZ3+TV1yC/x/maAHsUIQHqqd6ZRj7x5MgJq0UBdITo2ZQVfXYI0ZGIeNm
fMu+P5448+NdpASa9QoqS8kPFeUaHJMzMFHBKhrr8lTJeZ82hKBXt5jD3Tbef5Ck
n5auKu2D0IjvrzsdIpNMQAhuBPT06TW/LzN/MvardZcaLcBmcutefw6Z7RsedHvj
cGpnw4a2u9sHZIUNHzoGq32+7UWXsBI5Ow==
-----END CERTIFICATE-----

View File

@@ -36,8 +36,8 @@ RUN apk --no-cache --virtual build-dependendencies add make gcc g++ libtool zlib
&& cd /usr/local/bin \
&& strip i2pd \
&& rm -fr /tmp/build && apk --no-cache --purge del build-dependendencies build-base fortify-headers boost-dev zlib-dev openssl-dev \
boost-python3 python3 gdbm boost-unit_test_framework boost-python linux-headers boost-prg_exec_monitor \
boost-serialization boost-signals boost-wave boost-wserialization boost-math boost-graph boost-regex git pcre \
boost-python3 python3 gdbm boost-unit_test_framework linux-headers boost-prg_exec_monitor \
boost-serialization boost-wave boost-wserialization boost-math boost-graph boost-regex git pcre \
libtool g++ gcc pkgconfig
# 2. Adding required libraries to run i2pd to ensure it will run.

View File

@@ -1,7 +1,7 @@
%define git_hash %(git rev-parse HEAD | cut -c -7)
Name: i2pd-git
Version: 2.25.0
Version: 2.28.0
Release: git%{git_hash}%{?dist}
Summary: I2P router written in C++
Conflicts: i2pd
@@ -110,6 +110,15 @@ getent passwd i2pd >/dev/null || \
%changelog
* Tue Aug 27 2019 orignal <i2porignal@yandex.ru> - 2.28.0
- update to 2.28.0
* Wed Jul 3 2019 orignal <i2porignal@yandex.ru> - 2.27.0
- update to 2.27.0
* Fri Jun 7 2019 orignal <i2porignal@yandex.ru> - 2.26.0
- update to 2.26.0
* Thu May 9 2019 orignal <i2porignal@yandex.ru> - 2.25.0
- update to 2.25.0

View File

@@ -1,5 +1,5 @@
Name: i2pd
Version: 2.25.0
Version: 2.28.0
Release: 1%{?dist}
Summary: I2P router written in C++
Conflicts: i2pd-git
@@ -108,6 +108,15 @@ getent passwd i2pd >/dev/null || \
%changelog
* Tue Aug 27 2019 orignal <i2porignal@yandex.ru> - 2.28.0
- update to 2.28.0
* Wed Jul 3 2019 orignal <i2porignal@yandex.ru> - 2.27.0
- update to 2.27.0
* Fri Jun 7 2019 orignal <i2porignal@yandex.ru> - 2.26.0
- update to 2.26.0
* Thu May 9 2019 orignal <i2porignal@yandex.ru> - 2.25.0
- update to 2.25.0

View File

@@ -354,6 +354,14 @@ namespace http {
{
s << "<b>Base64:</b><br>\r\n<textarea readonly=\"readonly\" cols=\"64\" rows=\"11\" wrap=\"on\">";
s << dest->GetIdentity ()->ToBase64 () << "</textarea><br>\r\n<br>\r\n";
if (dest->IsEncryptedLeaseSet ())
{
i2p::data::BlindedPublicKey blinded (dest->GetIdentity ());
s << "<div class='slide'><label for='slide-b33'><b>Encrypted B33 address:</b></label>\r\n<input type='checkbox' id='slide-b33'/>\r\n<p class='content'>\r\n";
s << blinded.ToB33 () << ".b32.i2p<br>\r\n";
s << "</p>\r\n</div>\r\n";
}
if(dest->GetNumRemoteLeaseSets())
{
s << "<div class='slide'><label for='slide-lease'><b>LeaseSets:</b> <i>" << dest->GetNumRemoteLeaseSets () << "</i></label>\r\n<input type='checkbox' id='slide-lease'/>\r\n<p class='content'>\r\n";

18
debian/changelog vendored
View File

@@ -1,3 +1,21 @@
i2pd (2.28.0-1) unstable; urgency=medium
* updated to version 2.28.0/0.9.42
-- orignal <orignal@i2pmail.org> Tue, 27 Aug 2019 16:00:00 +0000
i2pd (2.27.0-1) unstable; urgency=medium
* updated to version 2.27.0/0.9.41
-- orignal <orignal@i2pmail.org> Wed, 3 Jul 2019 16:00:00 +0000
i2pd (2.26.0-1) unstable; urgency=medium
* updated to version 2.26.0
-- orignal <orignal@i2pmail.org> Fri, 7 Jun 2019 16:00:00 +0000
i2pd (2.25.0-1) unstable; urgency=medium
* updated to version 2.25.0/0.9.40

306
libi2pd/Blinding.cpp Normal file
View File

@@ -0,0 +1,306 @@
#include <zlib.h> // for crc32
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <openssl/ec.h>
#include <openssl/bn.h>
#include "Base.h"
#include "Crypto.h"
#include "Log.h"
#include "Timestamp.h"
#include "I2PEndian.h"
#include "Ed25519.h"
#include "Signature.h"
#include "Blinding.h"
namespace i2p
{
namespace data
{
static EC_POINT * BlindPublicKeyECDSA (const EC_GROUP * group, const EC_POINT * pub, const uint8_t * seed)
{
BN_CTX * ctx = BN_CTX_new ();
BN_CTX_start (ctx);
BIGNUM * q = BN_CTX_get (ctx);
EC_GROUP_get_order (group, q, ctx);
// calculate alpha = seed mod q
BIGNUM * alpha = BN_CTX_get (ctx);
BN_bin2bn (seed, 64, alpha); // seed is in BigEndian
BN_mod (alpha, alpha, q, ctx); // % q
// A' = BLIND_PUBKEY(A, alpha) = A + DERIVE_PUBLIC(alpha)
auto p = EC_POINT_new (group);
EC_POINT_mul (group, p, alpha, nullptr, nullptr, ctx); // B*alpha
EC_POINT_add (group, p, pub, p, ctx); // pub + B*alpha
BN_CTX_end (ctx);
BN_CTX_free (ctx);
return p;
}
static void BlindPrivateKeyECDSA (const EC_GROUP * group, const BIGNUM * priv, const uint8_t * seed, BIGNUM * blindedPriv)
{
BN_CTX * ctx = BN_CTX_new ();
BN_CTX_start (ctx);
BIGNUM * q = BN_CTX_get (ctx);
EC_GROUP_get_order (group, q, ctx);
// calculate alpha = seed mod q
BIGNUM * alpha = BN_CTX_get (ctx);
BN_bin2bn (seed, 64, alpha); // seed is in BigEndian
BN_mod (alpha, alpha, q, ctx); // % q
BN_add (alpha, alpha, priv); // alpha = alpha + priv
// a' = BLIND_PRIVKEY(a, alpha) = (a + alpha) mod q
BN_mod (blindedPriv, alpha, q, ctx); // % q
BN_CTX_end (ctx);
BN_CTX_free (ctx);
}
static void BlindEncodedPublicKeyECDSA (size_t publicKeyLen, const EC_GROUP * group, const uint8_t * pub, const uint8_t * seed, uint8_t * blindedPub)
{
BIGNUM * x = BN_bin2bn (pub, publicKeyLen/2, NULL);
BIGNUM * y = BN_bin2bn (pub + publicKeyLen/2, publicKeyLen/2, NULL);
EC_POINT * p = EC_POINT_new (group);
EC_POINT_set_affine_coordinates_GFp (group, p, x, y, NULL);
EC_POINT * p1 = BlindPublicKeyECDSA (group, p, seed);
EC_POINT_free (p);
EC_POINT_get_affine_coordinates_GFp (group, p1, x, y, NULL);
EC_POINT_free (p1);
i2p::crypto::bn2buf (x, blindedPub, publicKeyLen/2);
i2p::crypto::bn2buf (y, blindedPub + publicKeyLen/2, publicKeyLen/2);
BN_free (x); BN_free (y);
}
static void BlindEncodedPrivateKeyECDSA (size_t publicKeyLen, const EC_GROUP * group, const uint8_t * priv, const uint8_t * seed, uint8_t * blindedPriv, uint8_t * blindedPub)
{
BIGNUM * a = BN_bin2bn (priv, publicKeyLen/2, NULL);
BIGNUM * a1 = BN_new ();
BlindPrivateKeyECDSA (group, a, seed, a1);
BN_free (a);
i2p::crypto::bn2buf (a1, blindedPriv, publicKeyLen/2);
auto p = EC_POINT_new (group);
BN_CTX * ctx = BN_CTX_new ();
EC_POINT_mul (group, p, a1, nullptr, nullptr, ctx); // B*a1
BN_CTX_free (ctx);
BN_free (a1);
BIGNUM * x = BN_new(), * y = BN_new();
EC_POINT_get_affine_coordinates_GFp (group, p, x, y, NULL);
EC_POINT_free (p);
i2p::crypto::bn2buf (x, blindedPub, publicKeyLen/2);
i2p::crypto::bn2buf (y, blindedPub + publicKeyLen/2, publicKeyLen/2);
BN_free (x); BN_free (y);
}
template<typename Fn, typename...Args>
static size_t BlindECDSA (i2p::data::SigningKeyType sigType, const uint8_t * key, const uint8_t * seed, Fn blind, Args&&...args)
// blind is BlindEncodedPublicKeyECDSA or BlindEncodedPrivateKeyECDSA
{
size_t publicKeyLength = 0;
EC_GROUP * group = nullptr;
switch (sigType)
{
case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
{
publicKeyLength = i2p::crypto::ECDSAP256_KEY_LENGTH;
group = EC_GROUP_new_by_curve_name (NID_X9_62_prime256v1);
break;
}
case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
{
publicKeyLength = i2p::crypto::ECDSAP384_KEY_LENGTH;
group = EC_GROUP_new_by_curve_name (NID_secp384r1);
break;
}
case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
{
publicKeyLength = i2p::crypto::ECDSAP521_KEY_LENGTH;
group = EC_GROUP_new_by_curve_name (NID_secp521r1);
break;
}
default:
LogPrint (eLogError, "Blinding: signature type ", (int)sigType, " is not ECDSA");
}
if (group)
{
blind (publicKeyLength, group, key, seed, std::forward<Args>(args)...);
EC_GROUP_free (group);
}
return publicKeyLength;
}
BlindedPublicKey::BlindedPublicKey (std::shared_ptr<const IdentityEx> identity)
{
if (!identity) return;
auto len = identity->GetSigningPublicKeyLen ();
m_PublicKey.resize (len);
memcpy (m_PublicKey.data (), identity->GetSigningPublicKeyBuffer (), len);
m_SigType = identity->GetSigningKeyType ();
m_BlindedSigType = m_SigType;
}
BlindedPublicKey::BlindedPublicKey (const std::string& b33)
{
uint8_t addr[40]; // TODO: define length from b33
size_t l = i2p::data::Base32ToByteStream (b33.c_str (), b33.length (), addr, 40);
if (l < 32)
{
LogPrint (eLogError, "Blinding: malformed b33 ", b33);
return;
}
uint32_t checksum = crc32 (0, addr + 3, l - 3);
// checksum is Little Endian
addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16);
uint8_t flag = addr[0];
size_t offset = 1;
if (flag & 0x01) // two bytes signatures
{
m_SigType = bufbe16toh (addr + offset); offset += 2;
m_BlindedSigType = bufbe16toh (addr + offset); offset += 2;
}
else // one byte sig
{
m_SigType = addr[offset]; offset++;
m_BlindedSigType = addr[offset]; offset++;
}
std::unique_ptr<i2p::crypto::Verifier> blindedVerifier (i2p::data::IdentityEx::CreateVerifier (m_SigType));
if (blindedVerifier)
{
auto len = blindedVerifier->GetPublicKeyLen ();
if (offset + len <= l)
{
m_PublicKey.resize (len);
memcpy (m_PublicKey.data (), addr + offset, len);
}
else
LogPrint (eLogError, "Blinding: public key in b33 address is too short for signature type ", (int)m_SigType);
}
else
LogPrint (eLogError, "Blinding: unknown signature type ", (int)m_SigType, " in b33");
}
std::string BlindedPublicKey::ToB33 () const
{
if (m_PublicKey.size () > 32) return ""; // assume 25519
uint8_t addr[35]; char str[60]; // TODO: define actual length
addr[0] = 0; // flags
addr[1] = m_SigType; // sig type
addr[2] = m_BlindedSigType; // blinded sig type
memcpy (addr + 3, m_PublicKey.data (), m_PublicKey.size ());
uint32_t checksum = crc32 (0, addr + 3, m_PublicKey.size ());
// checksum is Little Endian
addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16);
auto l = ByteStreamToBase32 (addr, m_PublicKey.size () + 3, str, 60);
return std::string (str, str + l);
}
void BlindedPublicKey::GetCredential (uint8_t * credential) const
{
// A = destination's signing public key
// stA = signature type of A, 2 bytes big endian
uint16_t stA = htobe16 (GetSigType ());
// stA1 = signature type of blinded A, 2 bytes big endian
uint16_t stA1 = htobe16 (GetBlindedSigType ());
// credential = H("credential", A || stA || stA1)
H ("credential", { {GetPublicKey (), GetPublicKeyLen ()}, {(const uint8_t *)&stA, 2}, {(const uint8_t *)&stA1, 2} }, credential);
}
void BlindedPublicKey::GetSubcredential (const uint8_t * blinded, size_t len, uint8_t * subcredential) const
{
uint8_t credential[32];
GetCredential (credential);
// subcredential = H("subcredential", credential || blindedPublicKey)
H ("subcredential", { {credential, 32}, {blinded, len} }, subcredential);
}
void BlindedPublicKey::GenerateAlpha (const char * date, uint8_t * seed) const
{
uint16_t stA = htobe16 (GetSigType ()), stA1 = htobe16 (GetBlindedSigType ());
uint8_t salt[32];
//seed = HKDF(H("I2PGenerateAlpha", keydata), datestring || secret, "i2pblinding1", 64)
H ("I2PGenerateAlpha", { {GetPublicKey (), GetPublicKeyLen ()}, {(const uint8_t *)&stA, 2}, {(const uint8_t *)&stA1, 2} }, salt);
i2p::crypto::HKDF (salt, (const uint8_t *)date, 8, "i2pblinding1", seed);
}
size_t BlindedPublicKey::GetBlindedKey (const char * date, uint8_t * blindedKey) const
{
uint8_t seed[64];
GenerateAlpha (date, seed);
size_t publicKeyLength = 0;
switch (m_SigType)
{
case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
publicKeyLength = BlindECDSA (m_SigType, GetPublicKey (), seed, BlindEncodedPublicKeyECDSA, blindedKey);
break;
case i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
case i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
i2p::crypto::GetEd25519 ()->BlindPublicKey (GetPublicKey (), seed, blindedKey);
publicKeyLength = i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH;
break;
default:
LogPrint (eLogError, "Blinding: can't blind signature type ", (int)m_SigType);
}
return publicKeyLength;
}
size_t BlindedPublicKey::BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const
{
uint8_t seed[64];
GenerateAlpha (date, seed);
size_t publicKeyLength = 0;
switch (m_SigType)
{
case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
publicKeyLength = BlindECDSA (m_SigType, priv, seed, BlindEncodedPrivateKeyECDSA, blindedPriv, blindedPub);
break;
case i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
i2p::crypto::GetEd25519 ()->BlindPrivateKey (priv, seed, blindedPriv, blindedPub);
publicKeyLength = i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH;
break;
default:
LogPrint (eLogError, "Blinding: can't blind signature type ", (int)m_SigType);
}
return publicKeyLength;
}
void BlindedPublicKey::H (const std::string& p, const std::vector<std::pair<const uint8_t *, size_t> >& bufs, uint8_t * hash) const
{
SHA256_CTX ctx;
SHA256_Init (&ctx);
SHA256_Update (&ctx, p.c_str (), p.length ());
for (const auto& it: bufs)
SHA256_Update (&ctx, it.first, it.second);
SHA256_Final (hash, &ctx);
}
i2p::data::IdentHash BlindedPublicKey::GetStoreHash (const char * date) const
{
i2p::data::IdentHash hash;
uint8_t blinded[128];
size_t publicKeyLength = 0;
if (date)
publicKeyLength = GetBlindedKey (date, blinded);
else
{
char currentDate[9];
i2p::util::GetCurrentDate (currentDate);
publicKeyLength = GetBlindedKey (currentDate, blinded);
}
if (publicKeyLength)
{
auto stA1 = htobe16 (m_BlindedSigType);
SHA256_CTX ctx;
SHA256_Init (&ctx);
SHA256_Update (&ctx, (const uint8_t *)&stA1, 2);
SHA256_Update (&ctx, blinded, publicKeyLength);
SHA256_Final ((uint8_t *)hash, &ctx);
}
else
LogPrint (eLogError, "Blinding: blinded key type ", (int)m_BlindedSigType, " is not supported");
return hash;
}
}
}

45
libi2pd/Blinding.h Normal file
View File

@@ -0,0 +1,45 @@
#ifndef BLINDING_H__
#define BLINDING_H__
#include <inttypes.h>
#include <string>
#include <vector>
#include "Identity.h"
namespace i2p
{
namespace data
{
class BlindedPublicKey // for encrypted LS2
{
public:
BlindedPublicKey (std::shared_ptr<const IdentityEx> identity);
BlindedPublicKey (const std::string& b33); // from b33 without .b32.i2p
std::string ToB33 () const;
const uint8_t * GetPublicKey () const { return m_PublicKey.data (); };
size_t GetPublicKeyLen () const { return m_PublicKey.size (); };
SigningKeyType GetSigType () const { return m_SigType; };
SigningKeyType GetBlindedSigType () const { return m_BlindedSigType; };
void GetSubcredential (const uint8_t * blinded, size_t len, uint8_t * subcredential) const; // 32 bytes
size_t GetBlindedKey (const char * date, uint8_t * blindedKey) const; // date is 8 chars "YYYYMMDD", return public key length
size_t BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const; // date is 8 chars "YYYYMMDD", return public key length
i2p::data::IdentHash GetStoreHash (const char * date = nullptr) const; // date is 8 chars "YYYYMMDD", use current if null
private:
void GetCredential (uint8_t * credential) const; // 32 bytes
void GenerateAlpha (const char * date, uint8_t * seed) const; // 64 bytes, date is 8 chars "YYYYMMDD"
void H (const std::string& p, const std::vector<std::pair<const uint8_t *, size_t> >& bufs, uint8_t * hash) const;
private:
std::vector<uint8_t> m_PublicKey;
i2p::data::SigningKeyType m_SigType, m_BlindedSigType;
};
}
}
#endif

View File

@@ -58,7 +58,7 @@ namespace config {
("floodfill", bool_switch()->default_value(false), "Router will be floodfill (default: disabled)")
("bandwidth", value<std::string>()->default_value(""), "Bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)")
("share", value<int>()->default_value(100), "Limit of transit traffic from max bandwidth in percents. (default: 100)")
("ntcp", value<bool>()->default_value(true), "Enable NTCP transport (default: enabled)")
("ntcp", value<bool>()->default_value(false), "Enable NTCP transport (default: disabled)")
("ssu", value<bool>()->default_value(true), "Enable SSU transport (default: enabled)")
("ntcpproxy", value<std::string>()->default_value(""), "Proxy URL for NTCP transport")
#ifdef _WIN32
@@ -195,10 +195,8 @@ namespace config {
"https://reseed.i2p.net.in/,"
"https://download.xxlspeed.com/,"
"https://reseed-fr.i2pd.xyz/,"
"https://reseed.atomike.ninja/,"
"https://reseed.memcpy.io/,"
"https://reseed.onion.im/,"
"https://itoopie.atomike.ninja/,"
"https://i2pseed.creativecowpat.net:8443/,"
"https://i2p.novg.net/"
), "Reseed URLs, separated by comma")
@@ -237,7 +235,7 @@ namespace config {
options_description ntcp2("NTCP2 Options");
ntcp2.add_options()
("ntcp2.enabled", value<bool>()->default_value(true), "Enable NTCP2 (default: enabled)")
("ntcp2.published", value<bool>()->default_value(false), "Publish NTCP2 (default: disabled)")
("ntcp2.published", value<bool>()->default_value(true), "Publish NTCP2 (default: enabled)")
("ntcp2.port", value<uint16_t>()->default_value(0), "Port to listen for incoming NTCP2 connections (default: auto)")
("ntcp2.addressv6", value<std::string>()->default_value("::"), "Address to bind NTCP2 on")
;

View File

@@ -296,11 +296,20 @@ namespace crypto
#if OPENSSL_X25519
m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, priv, 32);
m_Ctx = EVP_PKEY_CTX_new (m_Pkey, NULL);
memcpy (m_PublicKey, pub, 32); // TODO: verify against m_Pkey
if (pub)
memcpy (m_PublicKey, pub, 32); // TODO: verify against m_Pkey
else
{
size_t len = 32;
EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len);
}
#else
m_Ctx = BN_CTX_new ();
memcpy (m_PrivateKey, priv, 32);
memcpy (m_PublicKey, pub, 32);
m_Ctx = BN_CTX_new ();
if (pub)
memcpy (m_PublicKey, pub, 32);
else
GetEd25519 ()->ScalarMulB (m_PrivateKey, m_PublicKey, m_Ctx);
#endif
}

View File

@@ -74,7 +74,7 @@ namespace crypto
public:
X25519Keys ();
X25519Keys (const uint8_t * priv, const uint8_t * pub); // for RouterContext
X25519Keys (const uint8_t * priv, const uint8_t * pub); // if pub is null, derive from priv
~X25519Keys ();
void GenerateKeys ();

View File

@@ -12,10 +12,8 @@ namespace i2p
namespace datagram
{
DatagramDestination::DatagramDestination (std::shared_ptr<i2p::client::ClientDestination> owner):
m_Owner (owner.get()),
m_Receiver (nullptr)
m_Owner (owner), m_Receiver (nullptr), m_RawReceiver (nullptr)
{
m_Identity.FromBase64 (owner->GetIdentity()->ToBase64());
}
DatagramDestination::~DatagramDestination ()
@@ -28,14 +26,15 @@ namespace datagram
auto owner = m_Owner;
std::vector<uint8_t> v(MAX_DATAGRAM_SIZE);
uint8_t * buf = v.data();
auto identityLen = m_Identity.ToBuffer (buf, MAX_DATAGRAM_SIZE);
auto localIdentity = m_Owner->GetIdentity ();
auto identityLen = localIdentity->ToBuffer (buf, MAX_DATAGRAM_SIZE);
uint8_t * signature = buf + identityLen;
auto signatureLen = m_Identity.GetSignatureLen ();
auto signatureLen = localIdentity->GetSignatureLen ();
uint8_t * buf1 = signature + signatureLen;
size_t headerLen = identityLen + signatureLen;
memcpy (buf1, payload, len);
if (m_Identity.GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1)
if (localIdentity->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1)
{
uint8_t hash[32];
SHA256(buf1, len, hash);
@@ -49,7 +48,13 @@ namespace datagram
session->SendMsg(msg);
}
void DatagramDestination::SendRawDatagramTo(const uint8_t * payload, size_t len, const i2p::data::IdentHash & identity, uint16_t fromPort, uint16_t toPort)
{
auto msg = CreateDataMessage (payload, len, fromPort, toPort, true); // raw
auto session = ObtainSession(identity);
session->SendMsg(msg);
}
void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort,uint8_t * const &buf, size_t len)
{
i2p::data::IdentityEx identity;
@@ -82,6 +87,14 @@ namespace datagram
LogPrint (eLogWarning, "Datagram signature verification failed");
}
void DatagramDestination::HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
{
if (m_RawReceiver)
m_RawReceiver (fromPort, toPort, buf, len);
else
LogPrint (eLogWarning, "DatagramDestination: no receiver for raw datagram");
}
DatagramDestination::Receiver DatagramDestination::FindReceiver(uint16_t port)
{
std::lock_guard<std::mutex> lock(m_ReceiversMutex);
@@ -92,18 +105,24 @@ namespace datagram
return r;
}
void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw)
{
// unzip it
uint8_t uncompressed[MAX_DATAGRAM_SIZE];
size_t uncompressedLen = m_Inflator.Inflate (buf, len, uncompressed, MAX_DATAGRAM_SIZE);
if (uncompressedLen)
HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen);
{
if (isRaw)
HandleRawDatagram (fromPort, toPort, uncompressed, uncompressedLen);
else
HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen);
}
else
LogPrint (eLogWarning, "Datagram: decompression failed");
}
std::shared_ptr<I2NPMessage> DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort)
std::shared_ptr<I2NPMessage> DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort, bool isRaw)
{
auto msg = NewI2NPMessage ();
uint8_t * buf = msg->GetPayload ();
@@ -114,7 +133,7 @@ namespace datagram
htobe32buf (msg->GetPayload (), size); // length
htobe16buf (buf + 4, fromPort); // source port
htobe16buf (buf + 6, toPort); // destination port
buf[9] = i2p::client::PROTOCOL_TYPE_DATAGRAM; // datagram protocol
buf[9] = isRaw ? i2p::client::PROTOCOL_TYPE_RAW : i2p::client::PROTOCOL_TYPE_DATAGRAM; // raw or datagram protocol
msg->len += size + 4;
msg->FillI2NPMessageHeader (eI2NPData);
}
@@ -170,7 +189,7 @@ namespace datagram
return nullptr;
}
DatagramSession::DatagramSession(i2p::client::ClientDestination * localDestination,
DatagramSession::DatagramSession(std::shared_ptr<i2p::client::ClientDestination> localDestination,
const i2p::data::IdentHash & remoteIdent) :
m_LocalDestination(localDestination),
m_RemoteIdent(remoteIdent),

View File

@@ -37,7 +37,7 @@ namespace datagram
class DatagramSession : public std::enable_shared_from_this<DatagramSession>
{
public:
DatagramSession(i2p::client::ClientDestination * localDestination, const i2p::data::IdentHash & remoteIdent);
DatagramSession(std::shared_ptr<i2p::client::ClientDestination> localDestination, const i2p::data::IdentHash & remoteIdent);
void Start ();
void Stop ();
@@ -81,7 +81,7 @@ namespace datagram
void HandleLeaseSetUpdated(std::shared_ptr<i2p::data::LeaseSet> ls);
private:
i2p::client::ClientDestination * m_LocalDestination;
std::shared_ptr<i2p::client::ClientDestination> m_LocalDestination;
i2p::data::IdentHash m_RemoteIdent;
std::shared_ptr<const i2p::data::LeaseSet> m_RemoteLeaseSet;
std::shared_ptr<i2p::garlic::GarlicRoutingSession> m_RoutingSession;
@@ -99,22 +99,28 @@ namespace datagram
class DatagramDestination
{
typedef std::function<void (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)> Receiver;
typedef std::function<void (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)> RawReceiver;
public:
DatagramDestination (std::shared_ptr<i2p::client::ClientDestination> owner);
~DatagramDestination ();
void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0);
void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0);
void SendRawDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0);
void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw = false);
void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; };
void ResetReceiver () { m_Receiver = nullptr; };
void SetReceiver (const Receiver& receiver, uint16_t port) { std::lock_guard<std::mutex> lock(m_ReceiversMutex); m_ReceiversByPorts[port] = receiver; };
void ResetReceiver (uint16_t port) { std::lock_guard<std::mutex> lock(m_ReceiversMutex); m_ReceiversByPorts.erase (port); };
void SetRawReceiver (const RawReceiver& receiver) { m_RawReceiver = receiver; };
void ResetRawReceiver () { m_RawReceiver = nullptr; };
std::shared_ptr<DatagramSession::Info> GetInfoForRemote(const i2p::data::IdentHash & remote);
// clean up stale sessions
@@ -124,17 +130,19 @@ namespace datagram
std::shared_ptr<DatagramSession> ObtainSession(const i2p::data::IdentHash & ident);
std::shared_ptr<I2NPMessage> CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort);
std::shared_ptr<I2NPMessage> CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort, bool isRaw = false);
void HandleDatagram (uint16_t fromPort, uint16_t toPort, uint8_t *const& buf, size_t len);
void HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
/** find a receiver by port, if none by port is found try default receiever, otherwise returns nullptr */
Receiver FindReceiver(uint16_t port);
private:
i2p::client::ClientDestination * m_Owner;
i2p::data::IdentityEx m_Identity;
std::shared_ptr<i2p::client::ClientDestination> m_Owner;
Receiver m_Receiver; // default
RawReceiver m_RawReceiver; // default
std::mutex m_SessionsMutex;
std::map<i2p::data::IdentHash, DatagramSession_ptr > m_Sessions;
std::mutex m_ReceiversMutex;

View File

@@ -70,6 +70,16 @@ namespace client
it = params->find (I2CP_PARAM_LEASESET_TYPE);
if (it != params->end ())
m_LeaseSetType = std::stoi(it->second);
it = params->find (I2CP_PARAM_LEASESET_PRIV_KEY);
if (it != params->end ())
{
m_LeaseSetPrivKey.reset (new i2p::data::Tag<32>());
if (m_LeaseSetPrivKey->FromBase64 (it->second) != 32)
{
LogPrint(eLogError, "Destination: invalid value i2cp.leaseSetPrivKey ", it->second);
m_LeaseSetPrivKey.reset (nullptr);
}
}
}
}
catch (std::exception & ex)
@@ -422,7 +432,7 @@ namespace client
auto it2 = m_LeaseSetRequests.find (key);
if (it2 != m_LeaseSetRequests.end () && it2->second->requestedBlindedKey)
{
auto ls2 = std::make_shared<i2p::data::LeaseSet2> (buf + offset, len - offset, it2->second->requestedBlindedKey);
auto ls2 = std::make_shared<i2p::data::LeaseSet2> (buf + offset, len - offset, it2->second->requestedBlindedKey, m_LeaseSetPrivKey ? *m_LeaseSetPrivKey : nullptr);
if (ls2->IsValid ())
{
m_RemoteLeaseSets[ls2->GetIdentHash ()] = ls2; // ident is not key
@@ -836,7 +846,7 @@ namespace client
ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map<std::string, std::string> * params):
LeaseSetDestination (isPublic, params), m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY),
m_DatagramDestination (nullptr), m_RefCounter (0),
m_ReadyChecker(GetService())
m_ReadyChecker(GetService()), m_AuthType (i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_NONE)
{
if (keys.IsOfflineSignature () && GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // offline keys can be published with LS2 only
@@ -858,12 +868,46 @@ namespace client
if (isPublic)
LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created");
// extract streaming params
if (params)
try
{
if (params)
{
// extract streaming params
auto it = params->find (I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY);
if (it != params->end ())
m_StreamingAckDelay = std::stoi(it->second);
if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
{
// authentication for encrypted LeaseSet
it = params->find (I2CP_PARAM_LEASESET_AUTH_TYPE);
m_AuthType = std::stoi (it->second);
if (m_AuthType > 0)
{
m_AuthKeys = std::make_shared<std::vector<i2p::data::AuthPublicKey> >();
if (m_AuthType == i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_DH)
ReadAuthKey (I2CP_PARAM_LEASESET_CLIENT_DH, params);
else if (m_AuthType == i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_PSK)
ReadAuthKey (I2CP_PARAM_LEASESET_CLIENT_PSK, params);
else
{
LogPrint (eLogError, "Destination: Unexpected auth type ", m_AuthType);
m_AuthType = 0;
}
if (m_AuthKeys->size ())
LogPrint (eLogInfo, "Destination: ", m_AuthKeys->size (), " auth keys read");
else
{
LogPrint (eLogError, "Destination: No auth keys read for auth type ", m_AuthType);
m_AuthKeys = nullptr;
}
}
}
}
}
catch (std::exception & ex)
{
auto it = params->find (I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY);
if (it != params->end ())
m_StreamingAckDelay = std::stoi(it->second);
LogPrint(eLogError, "Destination: unable to parse parameters for destination: ", ex.what());
}
}
@@ -967,6 +1011,13 @@ namespace client
else
LogPrint (eLogError, "Destination: Missing datagram destination");
break;
case PROTOCOL_TYPE_RAW:
// raw datagram
if (m_DatagramDestination)
m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length, true);
else
LogPrint (eLogError, "Destination: Missing raw datagram destination");
break;
default:
LogPrint (eLogError, "Destination: Data: unexpected protocol ", buf[9]);
}
@@ -1129,10 +1180,11 @@ namespace client
{
// standard LS2 (type 3) first
auto keyLen = m_Decryptor ? m_Decryptor->GetPublicKeyLen () : 256;
bool isPublishedEncrypted = GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2;
auto ls2 = std::make_shared<i2p::data::LocalLeaseSet2> (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2,
m_Keys, m_EncryptionKeyType, keyLen, m_EncryptionPublicKey, tunnels);
if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) // encrypt if type 5
ls2 = std::make_shared<i2p::data::LocalEncryptedLeaseSet2> (ls2, m_Keys);
m_Keys, m_EncryptionKeyType, keyLen, m_EncryptionPublicKey, tunnels, IsPublic (), isPublishedEncrypted);
if (isPublishedEncrypted) // encrypt if type 5
ls2 = std::make_shared<i2p::data::LocalEncryptedLeaseSet2> (ls2, m_Keys, m_AuthType, m_AuthKeys);
leaseSet = ls2;
}
SetLeaseSet (leaseSet);
@@ -1151,5 +1203,22 @@ namespace client
LogPrint (eLogError, "Destinations: decryptor is not set");
return false;
}
void ClientDestination::ReadAuthKey (const std::string& group, const std::map<std::string, std::string> * params)
{
for (auto it: *params)
if (it.first.length () >= group.length () && !it.first.compare (0, group.length (), group))
{
auto pos = it.second.find (':');
if (pos != std::string::npos)
{
i2p::data::AuthPublicKey pubKey;
if (pubKey.FromBase64 (it.second.substr (pos+1)))
m_AuthKeys->push_back (pubKey);
else
LogPrint (eLogError, "Destination: Unexpected auth key ", it.second.substr (pos+1));
}
}
}
}
}

View File

@@ -55,7 +55,11 @@ namespace client
const char I2CP_PARAM_LEASESET_TYPE[] = "i2cp.leaseSetType";
const int DEFAULT_LEASESET_TYPE = 1;
const char I2CP_PARAM_LEASESET_ENCRYPTION_TYPE[] = "i2cp.leaseSetEncType";
const char I2CP_PARAM_LEASESET_PRIV_KEY[] = "i2cp.leaseSetPrivKey"; // PSK decryption key, base64
const char I2CP_PARAM_LEASESET_AUTH_TYPE[] = "i2cp.leaseSetAuthType";
const char I2CP_PARAM_LEASESET_CLIENT_DH[] = "i2cp.leaseSetClient.dh"; // group of i2cp.leaseSetClient.dh.nnn
const char I2CP_PARAM_LEASESET_CLIENT_PSK[] = "i2cp.leaseSetClient.psk"; // group of i2cp.leaseSetClient.psk.nnn
// latency
const char I2CP_PARAM_MIN_TUNNEL_LATENCY[] = "latency.min";
const int DEFAULT_MIN_TUNNEL_LATENCY = 0;
@@ -130,6 +134,7 @@ namespace client
void SetLeaseSet (std::shared_ptr<const i2p::data::LocalLeaseSet> newLeaseSet);
int GetLeaseSetType () const { return m_LeaseSetType; };
void SetLeaseSetType (int leaseSetType) { m_LeaseSetType = leaseSetType; };
bool IsPublic () const { return m_IsPublic; };
virtual void CleanupDestination () {}; // additional clean up in derived classes
// I2CP
virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0;
@@ -175,12 +180,14 @@ namespace client
m_PublishDelayTimer, m_CleanupTimer;
std::string m_Nickname;
int m_LeaseSetType;
std::unique_ptr<i2p::data::Tag<32> > m_LeaseSetPrivKey; // non-null if presented
public:
// for HTTP only
int GetNumRemoteLeaseSets () const { return m_RemoteLeaseSets.size (); };
const decltype(m_RemoteLeaseSets)& GetLeaseSets () const { return m_RemoteLeaseSets; };
bool IsEncryptedLeaseSet () const { return m_LeaseSetType == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2; };
};
class ClientDestination: public LeaseSetDestination
@@ -245,6 +252,9 @@ namespace client
void ScheduleCheckForReady(ReadyPromise * p);
void HandleCheckForReady(const boost::system::error_code & ecode, ReadyPromise * p);
#endif
void ReadAuthKey (const std::string& group, const std::map<std::string, std::string> * params);
private:
i2p::data::PrivateKeys m_Keys;
@@ -260,6 +270,9 @@ namespace client
boost::asio::deadline_timer m_ReadyChecker;
int m_AuthType;
std::shared_ptr<std::vector<i2p::data::AuthPublicKey> > m_AuthKeys;
public:
// for HTTP only

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2017, The PurpleI2P Project
* Copyright (c) 2013-2019, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -15,282 +15,283 @@
namespace i2p {
namespace http {
const std::vector<std::string> HTTP_METHODS = {
"GET", "HEAD", "POST", "PUT", "PATCH",
"DELETE", "OPTIONS", "CONNECT"
};
const std::vector<std::string> HTTP_VERSIONS = {
"HTTP/1.0", "HTTP/1.1"
};
const std::vector<const char *> weekdays = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
const std::vector<const char *> months = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
const std::vector<std::string> HTTP_METHODS = {
"GET", "HEAD", "POST", "PUT", "PATCH",
"DELETE", "OPTIONS", "CONNECT", "PROPFIND"
};
const std::vector<std::string> HTTP_VERSIONS = {
"HTTP/1.0", "HTTP/1.1"
};
const std::vector<const char *> weekdays = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
const std::vector<const char *> months = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
inline bool is_http_version(const std::string & str) {
return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS);
}
inline bool is_http_method(const std::string & str) {
return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS);
}
void strsplit(const std::string & line, std::vector<std::string> &tokens, char delim, std::size_t limit = 0) {
std::size_t count = 0;
std::stringstream ss(line);
std::string token;
while (1) {
count++;
if (limit > 0 && count >= limit)
delim = '\n'; /* reset delimiter */
if (!std::getline(ss, token, delim))
break;
tokens.push_back(token);
}
}
static std::pair<std::string, std::string> parse_header_line(const std::string& line)
{
std::size_t pos = 0;
std::size_t len = 1; /*: */
std::size_t max = line.length();
if ((pos = line.find(':', pos)) == std::string::npos)
return std::make_pair("", ""); // no ':' found
if (pos + 1 < max) // ':' at the end of header is valid
{
while ((pos + len) < max && isspace(line.at(pos + len)))
len++;
if (len == 1) return std::make_pair("", ""); // no following space, but something else
inline bool is_http_version(const std::string & str) {
return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS);
}
return std::make_pair(line.substr(0, pos), line.substr(pos + len));
}
void gen_rfc7231_date(std::string & out) {
std::time_t now = std::time(nullptr);
char buf[128];
std::tm *tm = std::gmtime(&now);
snprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT",
weekdays[tm->tm_wday], tm->tm_mday, months[tm->tm_mon],
tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec
);
out = buf;
}
inline bool is_http_method(const std::string & str) {
return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS);
}
bool URL::parse(const char *str, std::size_t len) {
std::string url(str, len ? len : strlen(str));
return parse(url);
}
void strsplit(const std::string & line, std::vector<std::string> &tokens, char delim, std::size_t limit = 0) {
std::size_t count = 0;
std::stringstream ss(line);
std::string token;
while (1) {
count++;
if (limit > 0 && count >= limit)
delim = '\n'; /* reset delimiter */
if (!std::getline(ss, token, delim))
break;
tokens.push_back(token);
}
}
bool URL::parse(const std::string& url) {
std::size_t pos_p = 0; /* < current parse position */
std::size_t pos_c = 0; /* < work position */
if(url.at(0) != '/' || pos_p > 0) {
std::size_t pos_s = 0;
/* schema */
pos_c = url.find("://");
if (pos_c != std::string::npos) {
schema = url.substr(0, pos_c);
pos_p = pos_c + 3;
}
/* user[:pass] */
pos_s = url.find('/', pos_p); /* find first slash */
pos_c = url.find('@', pos_p); /* find end of 'user' or 'user:pass' part */
if (pos_c != std::string::npos && (pos_s == std::string::npos || pos_s > pos_c)) {
std::size_t delim = url.find(':', pos_p);
if (delim && delim != std::string::npos && delim < pos_c) {
user = url.substr(pos_p, delim - pos_p);
delim += 1;
pass = url.substr(delim, pos_c - delim);
} else if(delim) {
user = url.substr(pos_p, pos_c - pos_p);
}
pos_p = pos_c + 1;
}
/* hostname[:port][/path] */
pos_c = url.find_first_of(":/", pos_p);
if (pos_c == std::string::npos) {
/* only hostname, without post and path */
host = url.substr(pos_p, std::string::npos);
return true;
} else if (url.at(pos_c) == ':') {
host = url.substr(pos_p, pos_c - pos_p);
/* port[/path] */
pos_p = pos_c + 1;
pos_c = url.find('/', pos_p);
std::string port_str = (pos_c == std::string::npos)
? url.substr(pos_p, std::string::npos)
: url.substr(pos_p, pos_c - pos_p);
/* stoi throws exception on failure, we don't need it */
for (char c : port_str) {
if (c < '0' || c > '9')
return false;
port *= 10;
port += c - '0';
}
if (pos_c == std::string::npos)
return true; /* no path part */
pos_p = pos_c;
} else {
/* start of path part found */
host = url.substr(pos_p, pos_c - pos_p);
pos_p = pos_c;
}
}
static std::pair<std::string, std::string> parse_header_line(const std::string& line)
{
std::size_t pos = 0;
std::size_t len = 1; /*: */
std::size_t max = line.length();
if ((pos = line.find(':', pos)) == std::string::npos)
return std::make_pair("", ""); // no ':' found
if (pos + 1 < max) // ':' at the end of header is valid
{
while ((pos + len) < max && isspace(line.at(pos + len)))
len++;
if (len == 1)
return std::make_pair("", ""); // no following space, but something else
}
return std::make_pair(line.substr(0, pos), line.substr(pos + len));
}
/* pos_p now at start of path part */
pos_c = url.find_first_of("?#", pos_p);
if (pos_c == std::string::npos) {
/* only path, without fragment and query */
path = url.substr(pos_p, std::string::npos);
return true;
} else if (url.at(pos_c) == '?') {
/* found query part */
path = url.substr(pos_p, pos_c - pos_p);
pos_p = pos_c + 1;
pos_c = url.find('#', pos_p);
if (pos_c == std::string::npos) {
/* no fragment */
query = url.substr(pos_p, std::string::npos);
return true;
} else {
query = url.substr(pos_p, pos_c - pos_p);
pos_p = pos_c + 1;
}
} else {
/* found fragment part */
path = url.substr(pos_p, pos_c - pos_p);
pos_p = pos_c + 1;
}
void gen_rfc7231_date(std::string & out) {
std::time_t now = std::time(nullptr);
char buf[128];
std::tm *tm = std::gmtime(&now);
snprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT",
weekdays[tm->tm_wday], tm->tm_mday, months[tm->tm_mon],
tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec
);
out = buf;
}
/* pos_p now at start of fragment part */
frag = url.substr(pos_p, std::string::npos);
return true;
}
bool URL::parse(const char *str, std::size_t len) {
std::string url(str, len ? len : strlen(str));
return parse(url);
}
bool URL::parse_query(std::map<std::string, std::string> & params) {
std::vector<std::string> tokens;
strsplit(query, tokens, '&');
bool URL::parse(const std::string& url) {
std::size_t pos_p = 0; /* < current parse position */
std::size_t pos_c = 0; /* < work position */
if(url.at(0) != '/' || pos_p > 0) {
std::size_t pos_s = 0;
/* schema */
pos_c = url.find("://");
if (pos_c != std::string::npos) {
schema = url.substr(0, pos_c);
pos_p = pos_c + 3;
}
/* user[:pass] */
pos_s = url.find('/', pos_p); /* find first slash */
pos_c = url.find('@', pos_p); /* find end of 'user' or 'user:pass' part */
if (pos_c != std::string::npos && (pos_s == std::string::npos || pos_s > pos_c)) {
std::size_t delim = url.find(':', pos_p);
if (delim && delim != std::string::npos && delim < pos_c) {
user = url.substr(pos_p, delim - pos_p);
delim += 1;
pass = url.substr(delim, pos_c - delim);
} else if(delim) {
user = url.substr(pos_p, pos_c - pos_p);
}
pos_p = pos_c + 1;
}
/* hostname[:port][/path] */
pos_c = url.find_first_of(":/", pos_p);
if (pos_c == std::string::npos) {
/* only hostname, without post and path */
host = url.substr(pos_p, std::string::npos);
return true;
} else if (url.at(pos_c) == ':') {
host = url.substr(pos_p, pos_c - pos_p);
/* port[/path] */
pos_p = pos_c + 1;
pos_c = url.find('/', pos_p);
std::string port_str = (pos_c == std::string::npos)
? url.substr(pos_p, std::string::npos)
: url.substr(pos_p, pos_c - pos_p);
/* stoi throws exception on failure, we don't need it */
for (char c : port_str) {
if (c < '0' || c > '9')
return false;
port *= 10;
port += c - '0';
}
if (pos_c == std::string::npos)
return true; /* no path part */
pos_p = pos_c;
} else {
/* start of path part found */
host = url.substr(pos_p, pos_c - pos_p);
pos_p = pos_c;
}
}
params.clear();
for (const auto& it : tokens) {
std::size_t eq = it.find ('=');
if (eq != std::string::npos) {
auto e = std::pair<std::string, std::string>(it.substr(0, eq), it.substr(eq + 1));
params.insert(e);
} else {
auto e = std::pair<std::string, std::string>(it, "");
params.insert(e);
}
}
return true;
}
/* pos_p now at start of path part */
pos_c = url.find_first_of("?#", pos_p);
if (pos_c == std::string::npos) {
/* only path, without fragment and query */
path = url.substr(pos_p, std::string::npos);
return true;
} else if (url.at(pos_c) == '?') {
/* found query part */
path = url.substr(pos_p, pos_c - pos_p);
pos_p = pos_c + 1;
pos_c = url.find('#', pos_p);
if (pos_c == std::string::npos) {
/* no fragment */
query = url.substr(pos_p, std::string::npos);
return true;
} else {
query = url.substr(pos_p, pos_c - pos_p);
pos_p = pos_c + 1;
}
} else {
/* found fragment part */
path = url.substr(pos_p, pos_c - pos_p);
pos_p = pos_c + 1;
}
std::string URL::to_string() {
std::string out = "";
if (schema != "") {
out = schema + "://";
if (user != "" && pass != "") {
out += user + ":" + pass + "@";
} else if (user != "") {
out += user + "@";
}
if (port) {
out += host + ":" + std::to_string(port);
} else {
out += host;
}
}
out += path;
if (query != "")
out += "?" + query;
if (frag != "")
out += "#" + frag;
return out;
}
/* pos_p now at start of fragment part */
frag = url.substr(pos_p, std::string::npos);
return true;
}
bool URL::parse_query(std::map<std::string, std::string> & params) {
std::vector<std::string> tokens;
strsplit(query, tokens, '&');
params.clear();
for (const auto& it : tokens) {
std::size_t eq = it.find ('=');
if (eq != std::string::npos) {
auto e = std::pair<std::string, std::string>(it.substr(0, eq), it.substr(eq + 1));
params.insert(e);
} else {
auto e = std::pair<std::string, std::string>(it, "");
params.insert(e);
}
}
return true;
}
std::string URL::to_string() {
std::string out = "";
if (schema != "") {
out = schema + "://";
if (user != "" && pass != "") {
out += user + ":" + pass + "@";
} else if (user != "") {
out += user + "@";
}
if (port) {
out += host + ":" + std::to_string(port);
} else {
out += host;
}
}
out += path;
if (query != "")
out += "?" + query;
if (frag != "")
out += "#" + frag;
return out;
}
bool URL::is_i2p() const
{
return host.rfind(".i2p") == ( host.size() - 4 );
}
void HTTPMsg::add_header(const char *name, std::string & value, bool replace) {
add_header(name, value.c_str(), replace);
}
void HTTPMsg::add_header(const char *name, std::string & value, bool replace) {
add_header(name, value.c_str(), replace);
}
void HTTPMsg::add_header(const char *name, const char *value, bool replace) {
std::size_t count = headers.count(name);
if (count && !replace)
return;
if (count) {
headers[name] = value;
return;
}
headers.insert(std::pair<std::string, std::string>(name, value));
}
void HTTPMsg::add_header(const char *name, const char *value, bool replace) {
std::size_t count = headers.count(name);
if (count && !replace)
return;
if (count) {
headers[name] = value;
return;
}
headers.insert(std::pair<std::string, std::string>(name, value));
}
void HTTPMsg::del_header(const char *name) {
headers.erase(name);
}
void HTTPMsg::del_header(const char *name) {
headers.erase(name);
}
int HTTPReq::parse(const char *buf, size_t len) {
std::string str(buf, len);
return parse(str);
}
int HTTPReq::parse(const char *buf, size_t len) {
std::string str(buf, len);
return parse(str);
}
int HTTPReq::parse(const std::string& str) {
enum { REQ_LINE, HEADER_LINE } expect = REQ_LINE;
std::size_t eoh = str.find(HTTP_EOH); /* request head size */
std::size_t eol = 0, pos = 0;
URL url;
int HTTPReq::parse(const std::string& str) {
enum { REQ_LINE, HEADER_LINE } expect = REQ_LINE;
std::size_t eoh = str.find(HTTP_EOH); /* request head size */
std::size_t eol = 0, pos = 0;
URL url;
if (eoh == std::string::npos)
return 0; /* str not contains complete request */
if (eoh == std::string::npos)
return 0; /* str not contains complete request */
while ((eol = str.find(CRLF, pos)) != std::string::npos) {
if (expect == REQ_LINE) {
std::string line = str.substr(pos, eol - pos);
std::vector<std::string> tokens;
strsplit(line, tokens, ' ');
if (tokens.size() != 3)
return -1;
if (!is_http_method(tokens[0]))
return -1;
if (!is_http_version(tokens[2]))
return -1;
if (!url.parse(tokens[1]))
return -1;
/* all ok */
method = tokens[0];
uri = tokens[1];
version = tokens[2];
expect = HEADER_LINE;
}
else
{
std::string line = str.substr(pos, eol - pos);
auto p = parse_header_line(line);
if (p.first.length () > 0)
headers.push_back (p);
else
return -1;
}
pos = eol + strlen(CRLF);
if (pos >= eoh)
break;
}
return eoh + strlen(HTTP_EOH);
}
while ((eol = str.find(CRLF, pos)) != std::string::npos) {
if (expect == REQ_LINE) {
std::string line = str.substr(pos, eol - pos);
std::vector<std::string> tokens;
strsplit(line, tokens, ' ');
if (tokens.size() != 3)
return -1;
if (!is_http_method(tokens[0]))
return -1;
if (!is_http_version(tokens[2]))
return -1;
if (!url.parse(tokens[1]))
return -1;
/* all ok */
method = tokens[0];
uri = tokens[1];
version = tokens[2];
expect = HEADER_LINE;
}
else
{
std::string line = str.substr(pos, eol - pos);
auto p = parse_header_line(line);
if (p.first.length () > 0)
headers.push_back (p);
else
return -1;
}
pos = eol + strlen(CRLF);
if (pos >= eoh)
break;
}
return eoh + strlen(HTTP_EOH);
}
void HTTPReq::write(std::ostream & o)
{
o << method << " " << uri << " " << version << CRLF;
for (auto & h : headers)
o << h.first << ": " << h.second << CRLF;
o << CRLF;
}
void HTTPReq::write(std::ostream & o)
{
o << method << " " << uri << " " << version << CRLF;
for (auto & h : headers)
o << h.first << ": " << h.second << CRLF;
o << CRLF;
}
std::string HTTPReq::to_string()
{
@@ -306,7 +307,7 @@ namespace http {
void HTTPReq::UpdateHeader (const std::string& name, const std::string& value)
{
for (auto& it : headers)
for (auto& it : headers)
if (it.first == name)
{
it.second = value;
@@ -327,177 +328,176 @@ namespace http {
std::string HTTPReq::GetHeader (const std::string& name) const
{
for (auto& it : headers)
for (auto& it : headers)
if (it.first == name)
return it.second;
return "";
}
bool HTTPRes::is_chunked() const
{
auto it = headers.find("Transfer-Encoding");
if (it == headers.end())
return false;
if (it->second.find("chunked") == std::string::npos)
return true;
return false;
}
bool HTTPRes::is_chunked() const
{
auto it = headers.find("Transfer-Encoding");
if (it == headers.end())
return false;
if (it->second.find("chunked") == std::string::npos)
return true;
return false;
}
bool HTTPRes::is_gzipped(bool includingI2PGzip) const
{
auto it = headers.find("Content-Encoding");
if (it == headers.end())
return false; /* no header */
if (it->second.find("gzip") != std::string::npos)
return true; /* gotcha! */
if (includingI2PGzip && it->second.find("x-i2p-gzip") != std::string::npos)
return true;
return false;
}
bool HTTPRes::is_gzipped(bool includingI2PGzip) const
{
auto it = headers.find("Content-Encoding");
if (it == headers.end())
return false; /* no header */
if (it->second.find("gzip") != std::string::npos)
return true; /* gotcha! */
if (includingI2PGzip && it->second.find("x-i2p-gzip") != std::string::npos)
return true;
return false;
}
long int HTTPMsg::content_length() const
{
unsigned long int length = 0;
auto it = headers.find("Content-Length");
if (it == headers.end())
return -1;
errno = 0;
length = std::strtoul(it->second.c_str(), (char **) NULL, 10);
if (errno != 0)
return -1;
return length;
}
long int HTTPMsg::content_length() const
{
unsigned long int length = 0;
auto it = headers.find("Content-Length");
if (it == headers.end())
return -1;
errno = 0;
length = std::strtoul(it->second.c_str(), (char **) NULL, 10);
if (errno != 0)
return -1;
return length;
}
int HTTPRes::parse(const char *buf, size_t len) {
std::string str(buf, len);
return parse(str);
}
int HTTPRes::parse(const char *buf, size_t len) {
std::string str(buf, len);
return parse(str);
}
int HTTPRes::parse(const std::string& str) {
enum { RES_LINE, HEADER_LINE } expect = RES_LINE;
std::size_t eoh = str.find(HTTP_EOH); /* request head size */
std::size_t eol = 0, pos = 0;
int HTTPRes::parse(const std::string& str) {
enum { RES_LINE, HEADER_LINE } expect = RES_LINE;
std::size_t eoh = str.find(HTTP_EOH); /* request head size */
std::size_t eol = 0, pos = 0;
if (eoh == std::string::npos)
return 0; /* str not contains complete request */
if (eoh == std::string::npos)
return 0; /* str not contains complete request */
while ((eol = str.find(CRLF, pos)) != std::string::npos) {
if (expect == RES_LINE) {
std::string line = str.substr(pos, eol - pos);
std::vector<std::string> tokens;
strsplit(line, tokens, ' ', 3);
if (tokens.size() != 3)
return -1;
if (!is_http_version(tokens[0]))
return -1;
code = atoi(tokens[1].c_str());
if (code < 100 || code >= 600)
return -1;
/* all ok */
version = tokens[0];
status = tokens[2];
expect = HEADER_LINE;
} else {
std::string line = str.substr(pos, eol - pos);
auto p = parse_header_line(line);
if (p.first.length () > 0)
headers.insert (p);
else
return -1;
}
pos = eol + strlen(CRLF);
if (pos >= eoh)
break;
}
while ((eol = str.find(CRLF, pos)) != std::string::npos) {
if (expect == RES_LINE) {
std::string line = str.substr(pos, eol - pos);
std::vector<std::string> tokens;
strsplit(line, tokens, ' ', 3);
if (tokens.size() != 3)
return -1;
if (!is_http_version(tokens[0]))
return -1;
code = atoi(tokens[1].c_str());
if (code < 100 || code >= 600)
return -1;
/* all ok */
version = tokens[0];
status = tokens[2];
expect = HEADER_LINE;
} else {
std::string line = str.substr(pos, eol - pos);
auto p = parse_header_line(line);
if (p.first.length () > 0)
headers.insert (p);
else
return -1;
}
pos = eol + strlen(CRLF);
if (pos >= eoh)
break;
}
return eoh + strlen(HTTP_EOH);
}
return eoh + strlen(HTTP_EOH);
}
std::string HTTPRes::to_string() {
if (version == "HTTP/1.1" && headers.count("Date") == 0) {
std::string date;
gen_rfc7231_date(date);
add_header("Date", date.c_str());
}
if (status == "OK" && code != 200)
status = HTTPCodeToStatus(code); // update
if (body.length() > 0 && headers.count("Content-Length") == 0)
add_header("Content-Length", std::to_string(body.length()).c_str());
/* build response */
std::stringstream ss;
ss << version << " " << code << " " << status << CRLF;
for (auto & h : headers) {
ss << h.first << ": " << h.second << CRLF;
}
ss << CRLF;
if (body.length() > 0)
ss << body;
return ss.str();
}
std::string HTTPRes::to_string() {
if (version == "HTTP/1.1" && headers.count("Date") == 0) {
std::string date;
gen_rfc7231_date(date);
add_header("Date", date.c_str());
}
if (status == "OK" && code != 200)
status = HTTPCodeToStatus(code); // update
if (body.length() > 0 && headers.count("Content-Length") == 0)
add_header("Content-Length", std::to_string(body.length()).c_str());
/* build response */
std::stringstream ss;
ss << version << " " << code << " " << status << CRLF;
for (auto & h : headers) {
ss << h.first << ": " << h.second << CRLF;
}
ss << CRLF;
if (body.length() > 0)
ss << body;
return ss.str();
}
const char * HTTPCodeToStatus(int code) {
const char *ptr;
switch (code) {
case 105: ptr = "Name Not Resolved"; break;
/* success */
case 200: ptr = "OK"; break;
case 206: ptr = "Partial Content"; break;
/* redirect */
case 301: ptr = "Moved Permanently"; break;
case 302: ptr = "Found"; break;
case 304: ptr = "Not Modified"; break;
case 307: ptr = "Temporary Redirect"; break;
/* client error */
case 400: ptr = "Bad Request"; break;
case 401: ptr = "Unauthorized"; break;
case 403: ptr = "Forbidden"; break;
case 404: ptr = "Not Found"; break;
case 407: ptr = "Proxy Authentication Required"; break;
case 408: ptr = "Request Timeout"; break;
/* server error */
case 500: ptr = "Internal Server Error"; break;
case 502: ptr = "Bad Gateway"; break;
case 503: ptr = "Not Implemented"; break;
case 504: ptr = "Gateway Timeout"; break;
default: ptr = "Unknown Status"; break;
}
return ptr;
}
const char * HTTPCodeToStatus(int code) {
const char *ptr;
switch (code) {
case 105: ptr = "Name Not Resolved"; break;
/* success */
case 200: ptr = "OK"; break;
case 206: ptr = "Partial Content"; break;
/* redirect */
case 301: ptr = "Moved Permanently"; break;
case 302: ptr = "Found"; break;
case 304: ptr = "Not Modified"; break;
case 307: ptr = "Temporary Redirect"; break;
/* client error */
case 400: ptr = "Bad Request"; break;
case 401: ptr = "Unauthorized"; break;
case 403: ptr = "Forbidden"; break;
case 404: ptr = "Not Found"; break;
case 407: ptr = "Proxy Authentication Required"; break;
case 408: ptr = "Request Timeout"; break;
/* server error */
case 500: ptr = "Internal Server Error"; break;
case 502: ptr = "Bad Gateway"; break;
case 503: ptr = "Not Implemented"; break;
case 504: ptr = "Gateway Timeout"; break;
default: ptr = "Unknown Status"; break;
}
return ptr;
}
std::string UrlDecode(const std::string& data, bool allow_null) {
std::string UrlDecode(const std::string& data, bool allow_null) {
std::string decoded(data);
size_t pos = 0;
while ((pos = decoded.find('%', pos)) != std::string::npos) {
char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16);
if (c == '\0' && !allow_null) {
pos += 3;
continue;
}
decoded.replace(pos, 3, 1, c);
pos++;
}
return decoded;
}
size_t pos = 0;
while ((pos = decoded.find('%', pos)) != std::string::npos) {
char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16);
if (c == '\0' && !allow_null) {
pos += 3;
continue;
}
decoded.replace(pos, 3, 1, c);
pos++;
}
return decoded;
}
bool MergeChunkedResponse (std::istream& in, std::ostream& out) {
std::string hexLen;
while (!in.eof ()) {
std::getline (in, hexLen);
errno = 0;
long int len = strtoul(hexLen.c_str(), (char **) NULL, 16);
if (errno != 0)
return false; /* conversion error */
if (len == 0)
return true; /* end of stream */
if (len < 0 || len > 10 * 1024 * 1024) /* < 10Mb */
return false; /* too large chunk */
char * buf = new char[len];
in.read (buf, len);
out.write (buf, len);
delete[] buf;
std::getline (in, hexLen); // read \r\n after chunk
}
return true;
}
bool MergeChunkedResponse (std::istream& in, std::ostream& out) {
std::string hexLen;
while (!in.eof ()) {
std::getline (in, hexLen);
errno = 0;
long int len = strtoul(hexLen.c_str(), (char **) NULL, 16);
if (errno != 0)
return false; /* conversion error */
if (len == 0)
return true; /* end of stream */
if (len < 0 || len > 10 * 1024 * 1024) /* < 10Mb */
return false; /* too large chunk */
char * buf = new char[len];
in.read (buf, len);
out.write (buf, len);
delete[] buf;
std::getline (in, hexLen); // read \r\n after chunk
}
return true;
}
} // http
} // i2p

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2016, The PurpleI2P Project
* Copyright (c) 2013-2019, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -20,152 +20,152 @@ namespace i2p
{
namespace http
{
const char CRLF[] = "\r\n"; /**< HTTP line terminator */
const char HTTP_EOH[] = "\r\n\r\n"; /**< HTTP end-of-headers mark */
extern const std::vector<std::string> HTTP_METHODS; /**< list of valid HTTP methods */
extern const std::vector<std::string> HTTP_VERSIONS; /**< list of valid HTTP versions */
const char CRLF[] = "\r\n"; /**< HTTP line terminator */
const char HTTP_EOH[] = "\r\n\r\n"; /**< HTTP end-of-headers mark */
extern const std::vector<std::string> HTTP_METHODS; /**< list of valid HTTP methods */
extern const std::vector<std::string> HTTP_VERSIONS; /**< list of valid HTTP versions */
struct URL
{
std::string schema;
std::string user;
std::string pass;
std::string host;
unsigned short int port;
std::string path;
std::string query;
std::string frag;
struct URL
{
std::string schema;
std::string user;
std::string pass;
std::string host;
unsigned short int port;
std::string path;
std::string query;
std::string frag;
URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), query(""), frag("") {};
URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), query(""), frag("") {};
/**
* @brief Tries to parse url from string
* @return true on success, false on invalid url
*/
bool parse (const char *str, std::size_t len = 0);
bool parse (const std::string& url);
/**
* @brief Tries to parse url from string
* @return true on success, false on invalid url
*/
bool parse (const char *str, std::size_t len = 0);
bool parse (const std::string& url);
/**
* @brief Parse query part of url to key/value map
* @note Honestly, this should be implemented with std::multimap
*/
bool parse_query(std::map<std::string, std::string> & params);
/**
* @brief Parse query part of url to key/value map
* @note Honestly, this should be implemented with std::multimap
*/
bool parse_query(std::map<std::string, std::string> & params);
/**
* @brief Serialize URL structure to url
* @note Returns relative url if schema if empty, absolute url otherwise
*/
std::string to_string ();
/**
* @brief Serialize URL structure to url
* @note Returns relative url if schema if empty, absolute url otherwise
*/
std::string to_string ();
/**
* @brief return true if the host is inside i2p
*/
bool is_i2p() const;
};
/**
* @brief return true if the host is inside i2p
*/
bool is_i2p() const;
};
struct HTTPMsg
{
std::map<std::string, std::string> headers;
struct HTTPMsg
{
std::map<std::string, std::string> headers;
void add_header(const char *name, std::string & value, bool replace = false);
void add_header(const char *name, const char *value, bool replace = false);
void del_header(const char *name);
void add_header(const char *name, std::string & value, bool replace = false);
void add_header(const char *name, const char *value, bool replace = false);
void del_header(const char *name);
/** @brief Returns declared message length or -1 if unknown */
long int content_length() const;
};
/** @brief Returns declared message length or -1 if unknown */
long int content_length() const;
};
struct HTTPReq
{
std::list<std::pair<std::string, std::string> > headers;
std::string version;
std::string method;
std::string uri;
struct HTTPReq
{
std::list<std::pair<std::string, std::string> > headers;
std::string version;
std::string method;
std::string uri;
HTTPReq (): version("HTTP/1.0"), method("GET"), uri("/") {};
HTTPReq (): version("HTTP/1.0"), method("GET"), uri("/") {};
/**
* @brief Tries to parse HTTP request from string
* @return -1 on error, 0 on incomplete query, >0 on success
* @note Positive return value is a size of header
*/
int parse(const char *buf, size_t len);
int parse(const std::string& buf);
/**
* @brief Tries to parse HTTP request from string
* @return -1 on error, 0 on incomplete query, >0 on success
* @note Positive return value is a size of header
*/
int parse(const char *buf, size_t len);
int parse(const std::string& buf);
/** @brief Serialize HTTP request to string */
std::string to_string();
/** @brief Serialize HTTP request to string */
std::string to_string();
void write(std::ostream & o);
void AddHeader (const std::string& name, const std::string& value);
void UpdateHeader (const std::string& name, const std::string& value);
void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt
void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); };
std::string GetHeader (const std::string& name) const;
};
void AddHeader (const std::string& name, const std::string& value);
void UpdateHeader (const std::string& name, const std::string& value);
void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt
void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); };
std::string GetHeader (const std::string& name) const;
};
struct HTTPRes : HTTPMsg {
std::string version;
std::string status;
unsigned short int code;
/**
* @brief Simplifies response generation
*
* If this variable is set, on @a to_string() call:
* * Content-Length header will be added if missing,
* * contents of @a body will be included in generated response
*/
std::string body;
struct HTTPRes : HTTPMsg {
std::string version;
std::string status;
unsigned short int code;
/**
* @brief Simplifies response generation
*
* If this variable is set, on @a to_string() call:
* * Content-Length header will be added if missing,
* * contents of @a body will be included in generated response
*/
std::string body;
HTTPRes (): version("HTTP/1.1"), status("OK"), code(200) {}
HTTPRes (): version("HTTP/1.1"), status("OK"), code(200) {}
/**
* @brief Tries to parse HTTP response from string
* @return -1 on error, 0 on incomplete query, >0 on success
* @note Positive return value is a size of header
*/
int parse(const char *buf, size_t len);
int parse(const std::string& buf);
/**
* @brief Tries to parse HTTP response from string
* @return -1 on error, 0 on incomplete query, >0 on success
* @note Positive return value is a size of header
*/
int parse(const char *buf, size_t len);
int parse(const std::string& buf);
/**
* @brief Serialize HTTP response to string
* @note If @a version is set to HTTP/1.1, and Date header is missing,
* it will be generated based on current time and added to headers
* @note If @a body is set and Content-Length header is missing,
* this header will be added, based on body's length
*/
std::string to_string();
/**
* @brief Serialize HTTP response to string
* @note If @a version is set to HTTP/1.1, and Date header is missing,
* it will be generated based on current time and added to headers
* @note If @a body is set and Content-Length header is missing,
* this header will be added, based on body's length
*/
std::string to_string();
void write(std::ostream & o);
/** @brief Checks that response declared as chunked data */
bool is_chunked() const ;
/** @brief Checks that response declared as chunked data */
bool is_chunked() const ;
/** @brief Checks that response contains compressed data */
bool is_gzipped(bool includingI2PGzip = true) const;
};
/** @brief Checks that response contains compressed data */
bool is_gzipped(bool includingI2PGzip = true) const;
};
/**
* @brief returns HTTP status string by integer code
* @param code HTTP code [100, 599]
* @return Immutable string with status
*/
const char * HTTPCodeToStatus(int code);
/**
* @brief returns HTTP status string by integer code
* @param code HTTP code [100, 599]
* @return Immutable string with status
*/
const char * HTTPCodeToStatus(int code);
/**
* @brief Replaces %-encoded characters in string with their values
* @param data Source string
* @param null If set to true - decode also %00 sequence, otherwise - skip
* @return Decoded string
*/
std::string UrlDecode(const std::string& data, bool null = false);
/**
* @brief Replaces %-encoded characters in string with their values
* @param data Source string
* @param null If set to true - decode also %00 sequence, otherwise - skip
* @return Decoded string
*/
std::string UrlDecode(const std::string& data, bool null = false);
/**
* @brief Merge HTTP response content with Transfer-Encoding: chunked
* @param in Input stream
* @param out Output stream
* @return true on success, false otherwise
*/
bool MergeChunkedResponse (std::istream& in, std::ostream& out);
/**
* @brief Merge HTTP response content with Transfer-Encoding: chunked
* @param in Input stream
* @param out Output stream
* @return true on success, false otherwise
*/
bool MergeChunkedResponse (std::istream& in, std::ostream& out);
} // http
} // i2p

View File

@@ -258,12 +258,12 @@ namespace i2p
return m;
}
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::LeaseSet> leaseSet)
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (const i2p::data::IdentHash& storeHash, std::shared_ptr<const i2p::data::LeaseSet> leaseSet)
{
if (!leaseSet) return nullptr;
auto m = NewI2NPShortMessage ();
uint8_t * payload = m->GetPayload ();
memcpy (payload + DATABASE_STORE_KEY_OFFSET, leaseSet->GetIdentHash (), 32);
memcpy (payload + DATABASE_STORE_KEY_OFFSET, storeHash, 32);
payload[DATABASE_STORE_TYPE_OFFSET] = leaseSet->GetStoreType (); // 1 for LeaseSet
htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0);
size_t size = DATABASE_STORE_HEADER_SIZE;

View File

@@ -247,7 +247,7 @@ namespace tunnel
std::shared_ptr<I2NPMessage> CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector<i2p::data::IdentHash> routers);
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::RouterInfo> router = nullptr, uint32_t replyToken = 0);
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::LeaseSet> leaseSet); // for floodfill only
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (const i2p::data::IdentHash& storeHash, std::shared_ptr<const i2p::data::LeaseSet> leaseSet); // for floodfill only
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::LocalLeaseSet> leaseSet, uint32_t replyToken = 0, std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel = nullptr);
bool IsRouterInfoMsg (std::shared_ptr<I2NPMessage> msg);

View File

@@ -1,11 +1,8 @@
#include <string.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <zlib.h> // for crc32
#include "I2PEndian.h"
#include "Crypto.h"
#include "Ed25519.h"
#include "Log.h"
#include "Tag.h"
#include "Timestamp.h"
#include "NetDb.hpp"
#include "Tunnel.h"
@@ -254,159 +251,20 @@ namespace data
memcpy (m_Buffer, buf, len);
}
BlindedPublicKey::BlindedPublicKey (std::shared_ptr<const IdentityEx> identity, SigningKeyType blindedKeyType):
m_BlindedSigType (blindedKeyType)
{
if (!identity) return;
auto len = identity->GetSigningPublicKeyLen ();
m_PublicKey.resize (len);
memcpy (m_PublicKey.data (), identity->GetSigningPublicKeyBuffer (), len);
m_SigType = identity->GetSigningKeyType ();
}
BlindedPublicKey::BlindedPublicKey (const std::string& b33)
{
uint8_t addr[40]; // TODO: define length from b33
size_t l = i2p::data::Base32ToByteStream (b33.c_str (), b33.length (), addr, 40);
uint32_t checksum = crc32 (0, addr + 3, l - 3);
// checksum is Little Endian
addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16);
uint8_t flag = addr[0];
size_t offset = 1;
if (flag & 0x01) // two bytes signatures
{
m_SigType = bufbe16toh (addr + offset); offset += 2;
m_BlindedSigType = bufbe16toh (addr + offset); offset += 2;
}
else // one byte sig
{
m_SigType = addr[offset]; offset++;
m_BlindedSigType = addr[offset]; offset++;
}
std::unique_ptr<i2p::crypto::Verifier> blindedVerifier (i2p::data::IdentityEx::CreateVerifier (m_SigType));
if (blindedVerifier)
{
auto len = blindedVerifier->GetPublicKeyLen ();
if (offset + len <= l)
{
m_PublicKey.resize (len);
memcpy (m_PublicKey.data (), addr + offset, len);
}
else
LogPrint (eLogError, "LeaseSet2: public key in b33 address is too short for signature type ", (int)m_SigType);
}
else
LogPrint (eLogError, "LeaseSet2: unknown signature type ", (int)m_SigType, " in b33");
}
std::string BlindedPublicKey::ToB33 () const
{
if (m_PublicKey.size () > 32) return ""; // assume 25519
uint8_t addr[35]; char str[60]; // TODO: define actual length
addr[0] = 0; // flags
addr[1] = m_SigType; // sig type
addr[2] = m_BlindedSigType; // blinded sig type
memcpy (addr + 3, m_PublicKey.data (), m_PublicKey.size ());
uint32_t checksum = crc32 (0, addr + 3, m_PublicKey.size ());
// checksum is Little Endian
addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16);
auto l = ByteStreamToBase32 (addr, m_PublicKey.size () + 3, str, 60);
return std::string (str, str + l);
}
void BlindedPublicKey::GetCredential (uint8_t * credential) const
{
// A = destination's signing public key
// stA = signature type of A, 2 bytes big endian
uint16_t stA = htobe16 (GetSigType ());
// stA1 = signature type of blinded A, 2 bytes big endian
uint16_t stA1 = htobe16 (GetBlindedSigType ());
// credential = H("credential", A || stA || stA1)
H ("credential", { {GetPublicKey (), GetPublicKeyLen ()}, {(const uint8_t *)&stA, 2}, {(const uint8_t *)&stA1, 2} }, credential);
}
void BlindedPublicKey::GetSubcredential (const uint8_t * blinded, size_t len, uint8_t * subcredential) const
{
uint8_t credential[32];
GetCredential (credential);
// subcredential = H("subcredential", credential || blindedPublicKey)
H ("subcredential", { {credential, 32}, {blinded, len} }, subcredential);
}
void BlindedPublicKey::GenerateAlpha (const char * date, uint8_t * seed) const
{
uint16_t stA = htobe16 (GetSigType ()), stA1 = htobe16 (GetBlindedSigType ());
uint8_t salt[32];
//seed = HKDF(H("I2PGenerateAlpha", keydata), datestring || secret, "i2pblinding1", 64)
H ("I2PGenerateAlpha", { {GetPublicKey (), GetPublicKeyLen ()}, {(const uint8_t *)&stA, 2}, {(const uint8_t *)&stA1, 2} }, salt);
i2p::crypto::HKDF (salt, (const uint8_t *)date, 8, "i2pblinding1", seed);
}
void BlindedPublicKey::GetBlindedKey (const char * date, uint8_t * blindedKey) const
{
uint8_t seed[64];
GenerateAlpha (date, seed);
i2p::crypto::GetEd25519 ()->BlindPublicKey (GetPublicKey (), seed, blindedKey);
}
void BlindedPublicKey::BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const
{
uint8_t seed[64];
GenerateAlpha (date, seed);
i2p::crypto::GetEd25519 ()->BlindPrivateKey (priv, seed, blindedPriv, blindedPub);
}
void BlindedPublicKey::H (const std::string& p, const std::vector<std::pair<const uint8_t *, size_t> >& bufs, uint8_t * hash) const
{
SHA256_CTX ctx;
SHA256_Init (&ctx);
SHA256_Update (&ctx, p.c_str (), p.length ());
for (const auto& it: bufs)
SHA256_Update (&ctx, it.first, it.second);
SHA256_Final (hash, &ctx);
}
i2p::data::IdentHash BlindedPublicKey::GetStoreHash (const char * date) const
{
i2p::data::IdentHash hash;
if (m_BlindedSigType == i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519 ||
m_BlindedSigType == SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519)
{
uint8_t blinded[32];
if (date)
GetBlindedKey (date, blinded);
else
{
char currentDate[9];
i2p::util::GetCurrentDate (currentDate);
GetBlindedKey (currentDate, blinded);
}
auto stA1 = htobe16 (m_BlindedSigType);
SHA256_CTX ctx;
SHA256_Init (&ctx);
SHA256_Update (&ctx, (const uint8_t *)&stA1, 2);
SHA256_Update (&ctx, blinded, 32);
SHA256_Final ((uint8_t *)hash, &ctx);
}
else
LogPrint (eLogError, "LeaseSet2: blinded key type ", (int)m_BlindedSigType, " is not supported");
return hash;
}
LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases):
LeaseSet (storeLeases), m_StoreType (storeType), m_OrigStoreType (storeType)
LeaseSet (storeLeases), m_StoreType (storeType)
{
SetBuffer (buf, len);
if (storeType == NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
ReadFromBufferEncrypted (buf, len, nullptr);
ReadFromBufferEncrypted (buf, len, nullptr, nullptr);
else
ReadFromBuffer (buf, len);
}
LeaseSet2::LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key):
LeaseSet (true), m_StoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2), m_OrigStoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
LeaseSet2::LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key, const uint8_t * secret):
LeaseSet (true), m_StoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
{
ReadFromBufferEncrypted (buf, len, key);
ReadFromBufferEncrypted (buf, len, key, secret);
}
void LeaseSet2::Update (const uint8_t * buf, size_t len, bool verifySignature)
@@ -444,6 +302,12 @@ namespace data
return;
}
}
if (flags & LEASESET2_FLAG_UNPUBLISHED_LEASESET) m_IsPublic = false;
if (flags & LEASESET2_FLAG_PUBLISHED_ENCRYPTED)
{
m_IsPublishedEncrypted = true;
m_IsPublic = true;
}
// type specific part
size_t s = 0;
switch (m_StoreType)
@@ -563,7 +427,7 @@ namespace data
return offset;
}
void LeaseSet2::ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key)
void LeaseSet2::ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key, const uint8_t * secret)
{
size_t offset = 0;
// blinded key
@@ -606,19 +470,24 @@ namespace data
if (verified && key && lenOuterCiphertext >= 32)
{
SetIsValid (false); // we must verify it again in Layer 2
if (blindedKeyType == i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519)
if (blindedKeyType == key->GetBlindedSigType ())
{
// verify blinding
char date[9];
i2p::util::GetDateString (m_PublishedTimestamp, date);
uint8_t blinded[32];
key->GetBlindedKey (date, blinded);
if (memcmp (blindedPublicKey, blinded, 32))
std::vector<uint8_t> blinded (blindedKeyLen);
key->GetBlindedKey (date, blinded.data ());
if (memcmp (blindedPublicKey, blinded.data (), blindedKeyLen))
{
LogPrint (eLogError, "LeaseSet2: blinded public key doesn't match");
return;
}
}
else
{
LogPrint (eLogError, "LeaseSet2: Unexpected blinded key type ", blindedKeyType, " instead ", key->GetBlindedSigType ());
return;
}
// outer key
// outerInput = subcredential || publishedTimestamp
uint8_t subcredential[36];
@@ -635,17 +504,26 @@ namespace data
std::vector<uint8_t> outerPlainText (lenOuterPlaintext);
i2p::crypto::ChaCha20 (outerCiphertext + 32, lenOuterPlaintext, keys, keys + 32, outerPlainText.data ());
// inner key
// innerInput = authCookie || subcredential || publishedTimestamp, TODO: non-empty authCookie
// innerInput = authCookie || subcredential || publishedTimestamp
// innerSalt = innerCiphertext[0:32]
// keys = HKDF(innerSalt, innerInput, "ELS2_L2K", 44)
// skip 1 byte flags
i2p::crypto::HKDF (outerPlainText.data () + 1, subcredential, 36, "ELS2_L2K", keys); // no authCookie
uint8_t innerInput[68];
size_t authDataLen = ExtractClientAuthData (outerPlainText.data (), lenOuterPlaintext, secret, subcredential, innerInput);
if (authDataLen > 0)
{
memcpy (innerInput + 32, subcredential, 36);
i2p::crypto::HKDF (outerPlainText.data () + 1 + authDataLen, innerInput, 68, "ELS2_L2K", keys);
}
else
// no authData presented, innerInput = subcredential || publishedTimestamp
// skip 1 byte flags
i2p::crypto::HKDF (outerPlainText.data () + 1, subcredential, 36, "ELS2_L2K", keys); // no authCookie
// decrypt Layer 2
// innerKey = keys[0:31]
// innerIV = keys[32:43]
size_t lenInnerPlaintext = lenOuterPlaintext - 32 - 1;
size_t lenInnerPlaintext = lenOuterPlaintext - 32 - 1 - authDataLen;
std::vector<uint8_t> innerPlainText (lenInnerPlaintext);
i2p::crypto::ChaCha20 (outerPlainText.data () + 32 + 1, lenInnerPlaintext, keys, keys + 32, innerPlainText.data ());
i2p::crypto::ChaCha20 (outerPlainText.data () + 32 + 1 + authDataLen, lenInnerPlaintext, keys, keys + 32, innerPlainText.data ());
if (innerPlainText[0] == NETDB_STORE_TYPE_STANDARD_LEASESET2 || innerPlainText[0] == NETDB_STORE_TYPE_META_LEASESET2)
{
// override store type and buffer
@@ -659,6 +537,85 @@ namespace data
}
}
// helper for ExtractClientAuthData
static inline bool GetAuthCookie (const uint8_t * authClients, int numClients, const uint8_t * okm, uint8_t * authCookie)
{
// try to find clientCookie_i for clientID_i = okm[44:51]
for (int i = 0; i < numClients; i++)
{
if (!memcmp (okm + 44, authClients + i*40, 8)) // clientID_i
{
// clientKey_i = okm[0:31]
// clientIV_i = okm[32:43]
i2p::crypto::ChaCha20 (authClients + i*40 + 8, 32, okm, okm + 32, authCookie); // clientCookie_i
return true;
}
}
return false;
}
size_t LeaseSet2::ExtractClientAuthData (const uint8_t * buf, size_t len, const uint8_t * secret, const uint8_t * subcredential, uint8_t * authCookie) const
{
size_t offset = 0;
uint8_t flag = buf[offset]; offset++; // flag
if (flag & 0x01) // client auth
{
if (!(flag & 0x0E)) // DH, bit 1-3 all zeroes
{
const uint8_t * ephemeralPublicKey = buf + offset; offset += 32; // ephemeralPublicKey
uint16_t numClients = bufbe16toh (buf + offset); offset += 2; // clients
const uint8_t * authClients = buf + offset; offset += numClients*40; // authClients
if (offset > len)
{
LogPrint (eLogError, "LeaseSet2: Too many clients ", numClients, " in DH auth data");
return 0;
}
// calculate authCookie
if (secret)
{
i2p::crypto::X25519Keys ck (secret, nullptr); // derive cpk_i from csk_i
uint8_t authInput[100];
ck.Agree (ephemeralPublicKey, authInput); // sharedSecret is first 32 bytes of authInput
memcpy (authInput + 32, ck.GetPublicKey (), 32); // cpk_i
memcpy (authInput + 64, subcredential, 36);
uint8_t okm[64]; // 52 actual data
i2p::crypto::HKDF (ephemeralPublicKey, authInput, 100, "ELS2_XCA", okm);
if (!GetAuthCookie (authClients, numClients, okm, authCookie))
LogPrint (eLogError, "LeaseSet2: Client cookie DH not found");
}
else
LogPrint (eLogError, "LeaseSet2: Can't calculate authCookie: csk_i is not provided");
}
else if (flag & 0x02) // PSK, bit 1 is set to 1
{
const uint8_t * authSalt = buf + offset; offset += 32; // authSalt
uint16_t numClients = bufbe16toh (buf + offset); offset += 2; // clients
const uint8_t * authClients = buf + offset; offset += numClients*40; // authClients
if (offset > len)
{
LogPrint (eLogError, "LeaseSet2: Too many clients ", numClients, " in PSK auth data");
return 0;
}
// calculate authCookie
if (secret)
{
uint8_t authInput[68];
memcpy (authInput, secret, 32);
memcpy (authInput + 32, subcredential, 36);
uint8_t okm[64]; // 52 actual data
i2p::crypto::HKDF (authSalt, authInput, 68, "ELS2PSKA", okm);
if (!GetAuthCookie (authClients, numClients, okm, authCookie))
LogPrint (eLogError, "LeaseSet2: Client cookie PSK not found");
}
else
LogPrint (eLogError, "LeaseSet2: Can't calculate authCookie: psk_i is not provided");
}
else
LogPrint (eLogError, "LeaseSet2: unknown client auth type ", (int)flag);
}
return offset - 1;
}
void LeaseSet2::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const
{
auto encryptor = m_Encryptor; // TODO: atomic
@@ -790,7 +747,8 @@ namespace data
LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys,
uint16_t keyType, uint16_t keyLen, const uint8_t * encryptionPublicKey,
std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels):
std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels,
bool isPublic, bool isPublishedEncrypted):
LocalLeaseSet (keys.GetPublic (), nullptr, 0)
{
auto identity = keys.GetPublic ();
@@ -805,6 +763,12 @@ namespace data
flags |= LEASESET2_FLAG_OFFLINE_KEYS;
m_BufferLen += keys.GetOfflineSignature ().size ();
}
if (isPublishedEncrypted)
{
flags |= LEASESET2_FLAG_PUBLISHED_ENCRYPTED;
isPublic = true;
}
if (!isPublic) flags |= LEASESET2_FLAG_UNPUBLISHED_LEASESET;
m_Buffer = new uint8_t[m_BufferLen + 1];
m_Buffer[0] = storeType;
@@ -858,24 +822,34 @@ namespace data
m_Buffer[0] = storeType;
}
LocalEncryptedLeaseSet2::LocalEncryptedLeaseSet2 (std::shared_ptr<const LocalLeaseSet2> ls, const i2p::data::PrivateKeys& keys, i2p::data::SigningKeyType blindedKeyType):
LocalEncryptedLeaseSet2::LocalEncryptedLeaseSet2 (std::shared_ptr<const LocalLeaseSet2> ls, const i2p::data::PrivateKeys& keys,
int authType, std::shared_ptr<std::vector<AuthPublicKey> > authKeys):
LocalLeaseSet2 (ls->GetIdentity ()), m_InnerLeaseSet (ls)
{
size_t lenInnerPlaintext = ls->GetBufferLen () + 1, lenOuterPlaintext = lenInnerPlaintext + 32 + 1,
lenOuterCiphertext = lenOuterPlaintext + 32;
m_BufferLen = 2/*blinded sig type*/ + 32/*blinded pub key*/ + 4/*published*/ + 2/*expires*/ + 2/*flags*/ + 2/*lenOuterCiphertext*/ + lenOuterCiphertext + 64/*signature*/;
size_t lenInnerPlaintext = ls->GetBufferLen () + 1, lenOuterPlaintext = lenInnerPlaintext + 32 + 1;
uint8_t layer1Flags = 0;
if (authKeys)
{
if (authType == ENCRYPTED_LEASESET_AUTH_TYPE_DH) layer1Flags |= 0x01; // DH, authentication scheme 0, auth bit 1
else if (authType == ENCRYPTED_LEASESET_AUTH_TYPE_PSK) layer1Flags |= 0x03; // PSK, authentication scheme 1, auth bit 1
if (layer1Flags)
lenOuterPlaintext += 32 + 2 + authKeys->size ()*40; // auth data len
}
size_t lenOuterCiphertext = lenOuterPlaintext + 32;
m_BufferLen = 2/*blinded sig type*/ + 32/*blinded pub key*/ + 4/*published*/ + 2/*expires*/ + 2/*flags*/ + 2/*lenOuterCiphertext*/ + lenOuterCiphertext + 64/*signature*/;
m_Buffer = new uint8_t[m_BufferLen + 1];
m_Buffer[0] = NETDB_STORE_TYPE_ENCRYPTED_LEASESET2;
BlindedPublicKey blindedKey (ls->GetIdentity ());
auto timestamp = i2p::util::GetSecondsSinceEpoch ();
char date[9];
i2p::util::GetDateString (timestamp, date);
uint8_t blindedPriv[32], blindedPub[32];
blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub);
std::unique_ptr<i2p::crypto::Signer> blindedSigner (i2p::data::PrivateKeys::CreateSigner (blindedKeyType, blindedPriv));
uint8_t blindedPriv[64], blindedPub[128]; // 64 and 128 max
size_t publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub);
std::unique_ptr<i2p::crypto::Signer> blindedSigner (i2p::data::PrivateKeys::CreateSigner (blindedKey.GetBlindedSigType (), blindedPriv));
auto offset = 1;
htobe16buf (m_Buffer + offset, blindedKeyType); offset += 2; // Blinded Public Key Sig Type
memcpy (m_Buffer + offset, blindedPub, 32); offset += 32; // Blinded Public Key
htobe16buf (m_Buffer + offset, blindedKey.GetBlindedSigType ()); offset += 2; // Blinded Public Key Sig Type
memcpy (m_Buffer + offset, blindedPub, publicKeyLen); offset += publicKeyLen; // Blinded Public Key
htobe32buf (m_Buffer + offset, timestamp); offset += 4; // published timestamp (seconds)
auto nextMidnight = (timestamp/86400LL + 1)*86400LL; // 86400 = 24*3600 seconds
auto expirationTime = ls->GetExpirationTime ()/1000LL;
@@ -896,12 +870,26 @@ namespace data
i2p::crypto::HKDF (m_Buffer + offset, subcredential, 36, "ELS2_L1K", keys1);
offset += 32; // outerSalt
uint8_t * outerPlainText = m_Buffer + offset;
m_Buffer[offset] = 0; offset++; // flag
m_Buffer[offset] = layer1Flags; offset++; // layer 1 flags
// auth data
uint8_t innerInput[68]; // authCookie || subcredential || publishedTimestamp
if (layer1Flags)
{
RAND_bytes (innerInput, 32); // authCookie
CreateClientAuthData (subcredential, authType, authKeys, innerInput, m_Buffer + offset);
offset += 32 + 2 + authKeys->size ()*40; // auth clients
}
// Layer 2
// keys = HKDF(outerSalt, outerInput, "ELS2_L2K", 44)
uint8_t keys2[64]; // 44 bytes actual data
RAND_bytes (m_Buffer + offset, 32); // innerSalt = CSRNG(32)
i2p::crypto::HKDF (m_Buffer + offset, subcredential, 36, "ELS2_L2K", keys2);
if (layer1Flags)
{
memcpy (innerInput + 32, subcredential, 36); // + subcredential || publishedTimestamp
i2p::crypto::HKDF (m_Buffer + offset, innerInput, 68, "ELS2_L2K", keys2);
}
else
i2p::crypto::HKDF (m_Buffer + offset, subcredential, 36, "ELS2_L2K", keys2); // no authCookie
offset += 32; // innerSalt
m_Buffer[offset] = ls->GetStoreType ();
memcpy (m_Buffer + offset + 1, ls->GetBuffer (), ls->GetBufferLen ());
@@ -928,6 +916,44 @@ namespace data
else
LogPrint (eLogError, "LeaseSet2: couldn't extract inner layer");
}
void LocalEncryptedLeaseSet2::CreateClientAuthData (const uint8_t * subcredential, int authType, std::shared_ptr<std::vector<AuthPublicKey> > authKeys, const uint8_t * authCookie, uint8_t * authData) const
{
if (authType == ENCRYPTED_LEASESET_AUTH_TYPE_DH)
{
i2p::crypto::X25519Keys ek;
ek.GenerateKeys (); // esk and epk
memcpy (authData, ek.GetPublicKey (), 32); authData += 32; // epk
htobe16buf (authData, authKeys->size ()); authData += 2; // num clients
uint8_t authInput[100]; // sharedSecret || cpk_i || subcredential || publishedTimestamp
memcpy (authInput + 64, subcredential, 36);
for (auto& it: *authKeys)
{
ek.Agree (it, authInput); // sharedSecret = DH(esk, cpk_i)
memcpy (authInput + 32, it, 32);
uint8_t okm[64]; // 52 actual data
i2p::crypto::HKDF (ek.GetPublicKey (), authInput, 100, "ELS2_XCA", okm);
memcpy (authData, okm + 44, 8); authData += 8; // clientID_i
i2p::crypto::ChaCha20 (authCookie, 32, okm, okm + 32, authData); authData += 32; // clientCookie_i
}
}
else // assume PSK
{
uint8_t authSalt[32];
RAND_bytes (authSalt, 32);
memcpy (authData, authSalt, 32); authData += 32; // authSalt
htobe16buf (authData, authKeys->size ()); authData += 2; // num clients
uint8_t authInput[68]; // authInput = psk_i || subcredential || publishedTimestamp
memcpy (authInput + 32, subcredential, 36);
for (auto& it: *authKeys)
{
memcpy (authInput, it, 32);
uint8_t okm[64]; // 52 actual data
i2p::crypto::HKDF (authSalt, authInput, 68, "ELS2PSKA", okm);
memcpy (authData, okm + 44, 8); authData += 8; // clientID_i
i2p::crypto::ChaCha20 (authCookie, 32, okm, okm + 32, authData); authData += 32; // clientCookie_i
}
}
}
}
}

View File

@@ -9,6 +9,7 @@
#include "Identity.h"
#include "Timestamp.h"
#include "I2PEndian.h"
#include "Blinding.h"
namespace i2p
{
@@ -78,9 +79,9 @@ namespace data
bool operator== (const LeaseSet& other) const
{ return m_BufferLen == other.m_BufferLen && !memcmp (m_Buffer, other.m_Buffer, m_BufferLen); };
virtual uint8_t GetStoreType () const { return NETDB_STORE_TYPE_LEASESET; };
virtual uint8_t GetOrigStoreType () const { return NETDB_STORE_TYPE_LEASESET; };
virtual uint32_t GetPublishedTimestamp () const { return 0; }; // should be set for LeaseSet2 only
virtual std::shared_ptr<const i2p::crypto::Verifier> GetTransientVerifier () const { return nullptr; };
virtual bool IsPublishedEncrypted () const { return false; };
// implements RoutingDestination
std::shared_ptr<const IdentityEx> GetIdentity () const { return m_Identity; };
@@ -128,46 +129,19 @@ namespace data
const uint8_t NETDB_STORE_TYPE_META_LEASESET2 = 7;
const uint16_t LEASESET2_FLAG_OFFLINE_KEYS = 0x0001;
const uint16_t LEASESET2_FLAG_UNPUBLISHED_LEASESET = 0x0002;
const uint16_t LEASESET2_FLAG_PUBLISHED_ENCRYPTED = 0x0004;
class BlindedPublicKey // for encrypted LS2
{
public:
BlindedPublicKey (std::shared_ptr<const IdentityEx> identity, SigningKeyType blindedKeyType = i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519);
BlindedPublicKey (const std::string& b33); // from b33 without .b32.i2p
std::string ToB33 () const;
const uint8_t * GetPublicKey () const { return m_PublicKey.data (); };
size_t GetPublicKeyLen () const { return m_PublicKey.size (); };
SigningKeyType GetSigType () const { return m_SigType; };
SigningKeyType GetBlindedSigType () const { return m_BlindedSigType; };
void GetSubcredential (const uint8_t * blinded, size_t len, uint8_t * subcredential) const; // 32 bytes
void GetBlindedKey (const char * date, uint8_t * blindedKey) const; // blinded key 32 bytes, date is 8 chars "YYYYMMDD"
void BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const; // blinded key 32 bytes, date is 8 chars "YYYYMMDD"
i2p::data::IdentHash GetStoreHash (const char * date = nullptr) const; // date is 8 chars "YYYYMMDD", use current if null
private:
void GetCredential (uint8_t * credential) const; // 32 bytes
void GenerateAlpha (const char * date, uint8_t * seed) const; // 64 bytes, date is 8 chars "YYYYMMDD"
void H (const std::string& p, const std::vector<std::pair<const uint8_t *, size_t> >& bufs, uint8_t * hash) const;
private:
std::vector<uint8_t> m_PublicKey;
i2p::data::SigningKeyType m_SigType, m_BlindedSigType;
};
class LeaseSet2: public LeaseSet
{
public:
LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases = true);
LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key); // store type 5, called from local netdb only
LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key, const uint8_t * secret = nullptr); // store type 5, called from local netdb only
uint8_t GetStoreType () const { return m_StoreType; };
uint8_t GetOrigStoreType () const { return m_OrigStoreType; };
uint32_t GetPublishedTimestamp () const { return m_PublishedTimestamp; };
bool IsPublic () const { return m_IsPublic; };
bool IsPublishedEncrypted () const { return m_IsPublishedEncrypted; };
std::shared_ptr<const i2p::crypto::Verifier> GetTransientVerifier () const { return m_TransientVerifier; };
void Update (const uint8_t * buf, size_t len, bool verifySignature);
@@ -177,7 +151,7 @@ namespace data
private:
void ReadFromBuffer (const uint8_t * buf, size_t len, bool readIdentity = true, bool verifySignature = true);
void ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key);
void ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key, const uint8_t * secret);
size_t ReadStandardLS2TypeSpecificPart (const uint8_t * buf, size_t len);
size_t ReadMetaLS2TypeSpecificPart (const uint8_t * buf, size_t len);
@@ -185,11 +159,13 @@ namespace data
bool VerifySignature (Verifier& verifier, const uint8_t * buf, size_t len, size_t signatureOffset);
uint64_t ExtractTimestamp (const uint8_t * buf, size_t len) const;
size_t ExtractClientAuthData (const uint8_t * buf, size_t len, const uint8_t * secret, const uint8_t * subcredential, uint8_t * authCookie) const; // subcredential is subcredential + timestamp, return length of autData without flag
private:
uint8_t m_StoreType, m_OrigStoreType;
uint8_t m_StoreType;
uint32_t m_PublishedTimestamp = 0;
bool m_IsPublic = true, m_IsPublishedEncrypted = false;
std::shared_ptr<i2p::crypto::Verifier> m_TransientVerifier;
std::shared_ptr<i2p::crypto::CryptoKeyEncryptor> m_Encryptor; // for standardLS2
};
@@ -255,7 +231,8 @@ namespace data
LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys,
uint16_t keyType, uint16_t keyLen, const uint8_t * encryptionPublicKey,
std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels);
std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels,
bool isPublic, bool isPublishedEncrypted = false);
LocalLeaseSet2 (uint8_t storeType, std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len); // from I2CP
virtual ~LocalLeaseSet2 () { delete[] m_Buffer; };
@@ -275,17 +252,28 @@ namespace data
size_t m_BufferLen;
};
const int ENCRYPTED_LEASESET_AUTH_TYPE_NONE = 0;
const int ENCRYPTED_LEASESET_AUTH_TYPE_DH = 1;
const int ENCRYPTED_LEASESET_AUTH_TYPE_PSK = 2;
typedef i2p::data::Tag<32> AuthPublicKey;
class LocalEncryptedLeaseSet2: public LocalLeaseSet2
{
public:
LocalEncryptedLeaseSet2 (std::shared_ptr<const LocalLeaseSet2> ls, const i2p::data::PrivateKeys& keys, i2p::data::SigningKeyType blindedKeyType = i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519);
LocalEncryptedLeaseSet2 (std::shared_ptr<const LocalLeaseSet2> ls, const i2p::data::PrivateKeys& keys, int authType = ENCRYPTED_LEASESET_AUTH_TYPE_NONE, std::shared_ptr<std::vector<AuthPublicKey> > authKeys = nullptr);
LocalEncryptedLeaseSet2 (std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len); // from I2CP
const IdentHash& GetStoreHash () const { return m_StoreHash; };
std::shared_ptr<const LocalLeaseSet> GetInnerLeaseSet () const { return m_InnerLeaseSet; };
private:
void CreateClientAuthData (const uint8_t * subcredential, int authType, std::shared_ptr<std::vector<AuthPublicKey> > authKeys, const uint8_t * authCookie, uint8_t * authData) const;
private:
IdentHash m_StoreHash;

View File

@@ -161,6 +161,7 @@ namespace transport
// fill options
uint8_t options[32]; // actual options size is 16 bytes
memset (options, 0, 16);
options[0] = i2p::context.GetNetID (); // network ID
options[1] = 2; // ver
htobe16buf (options + 2, paddingLength); // padLen
// m3p2Len
@@ -248,6 +249,11 @@ namespace transport
if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionRequestBuffer + 32, 16, m_H, 32, m_K, nonce, options, 16, false)) // decrypt
{
// options
if (options[0] && options[0] != i2p::context.GetNetID ())
{
LogPrint (eLogWarning, "NTCP2: SessionRequest networkID ", (int)options[0], " mismatch. Expected ", i2p::context.GetNetID ());
return false;
}
if (options[1] == 2) // ver is always 2
{
paddingLen = bufbe16toh (options + 2);
@@ -396,6 +402,10 @@ namespace transport
{
m_IsTerminated = true;
m_IsEstablished = false;
boost::system::error_code ec;
m_Socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
if (ec)
LogPrint (eLogDebug, "NTCP2: Couldn't shutdown socket: ", ec.message ());
m_Socket.close ();
transports.PeerDisconnected (shared_from_this ());
m_Server.RemoveNTCP2Session (shared_from_this ());
@@ -1304,8 +1314,6 @@ namespace transport
else
{
LogPrint (eLogDebug, "NTCP2: Connected to ", conn->GetSocket ().remote_endpoint ());
if (conn->GetSocket ().local_endpoint ().protocol () == boost::asio::ip::tcp::v6()) // ipv6
context.UpdateNTCP2V6Address (conn->GetSocket ().local_endpoint ().address ());
conn->ClientLogin ();
}
}

View File

@@ -1079,8 +1079,6 @@ namespace transport
else
{
LogPrint (eLogDebug, "NTCP: Connected to ", conn->GetSocket ().remote_endpoint ());
if (conn->GetSocket ().local_endpoint ().protocol () == boost::asio::ip::tcp::v6()) // ipv6
context.UpdateNTCPV6Address (conn->GetSocket ().local_endpoint ().address ());
conn->ClientLogin ();
}
}

View File

@@ -307,10 +307,18 @@ namespace data
if (it == m_LeaseSets.end () || it->second->GetStoreType () != storeType ||
leaseSet->GetPublishedTimestamp () > it->second->GetPublishedTimestamp ())
{
// TODO: implement actual update
LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32());
m_LeaseSets[ident] = leaseSet;
return true;
if (leaseSet->IsPublic ())
{
// TODO: implement actual update
LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32());
m_LeaseSets[ident] = leaseSet;
return true;
}
else
{
LogPrint (eLogWarning, "NetDb: Unpublished LeaseSet2 received: ", ident.ToBase32());
m_LeaseSets.erase (ident);
}
}
}
else
@@ -523,9 +531,10 @@ namespace data
auto total = m_RouterInfos.size ();
uint64_t expirationTimeout = NETDB_MAX_EXPIRATION_TIMEOUT*1000LL;
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch();
auto uptime = i2p::context.GetUptime ();
// routers don't expire if less than 90 or uptime is less than 1 hour
bool checkForExpiration = total > NETDB_MIN_ROUTERS && ts > (i2p::context.GetStartupTime () + 600)*1000LL; // 10 minutes
if (checkForExpiration && ts > (i2p::context.GetStartupTime () + 3600)*1000LL) // 1 hour
bool checkForExpiration = total > NETDB_MIN_ROUTERS && uptime > 600; // 10 minutes
if (checkForExpiration && uptime > 3600) // 1 hour
expirationTimeout = i2p::context.IsFloodfill () ? NETDB_FLOODFILL_EXPIRATION_TIMEOUT*1000LL :
NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total;
@@ -698,14 +707,14 @@ namespace data
LogPrint (eLogDebug, "NetDb: store request: RouterInfo");
size_t size = bufbe16toh (buf + offset);
offset += 2;
if (size > 2048 || size > len - offset)
if (size > MAX_RI_BUFFER_SIZE || size > len - offset)
{
LogPrint (eLogError, "NetDb: invalid RouterInfo length ", (int)size);
return;
}
uint8_t uncompressed[2048];
size_t uncompressedSize = m_Inflator.Inflate (buf + offset, size, uncompressed, 2048);
if (uncompressedSize && uncompressedSize < 2048)
uint8_t uncompressed[MAX_RI_BUFFER_SIZE];
size_t uncompressedSize = m_Inflator.Inflate (buf + offset, size, uncompressed, MAX_RI_BUFFER_SIZE);
if (uncompressedSize && uncompressedSize < MAX_RI_BUFFER_SIZE)
updated = AddRouterInfo (ident, uncompressed, uncompressedSize);
else
{
@@ -909,7 +918,7 @@ namespace data
else if (!leaseSet->IsExpired ()) // we don't send back our LeaseSets
{
LogPrint (eLogDebug, "NetDb: requested LeaseSet ", key, " found");
replyMsg = CreateDatabaseStoreMsg (leaseSet);
replyMsg = CreateDatabaseStoreMsg (ident, leaseSet);
}
}
@@ -1076,6 +1085,15 @@ namespace data
});
}
std::shared_ptr<const RouterInfo> NetDb::GetRandomSSUV6Router () const
{
return GetRandomRouter (
[](std::shared_ptr<const RouterInfo> router)->bool
{
return !router->IsHidden () && router->IsSSUV6 ();
});
}
std::shared_ptr<const RouterInfo> NetDb::GetRandomIntroducer () const
{
return GetRandomRouter (

View File

@@ -72,6 +72,7 @@ namespace data
std::shared_ptr<const RouterInfo> GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith) const;
std::shared_ptr<const RouterInfo> GetHighBandwidthRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith) const;
std::shared_ptr<const RouterInfo> GetRandomPeerTestRouter (bool v4only = true) const;
std::shared_ptr<const RouterInfo> GetRandomSSUV6Router () const; // TODO: change to v6 peer test later
std::shared_ptr<const RouterInfo> GetRandomIntroducer () const;
std::shared_ptr<const RouterInfo> GetClosestFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded, bool closeThanUsOnly = false) const;
std::vector<IdentHash> GetClosestFloodfills (const IdentHash& destination, size_t num,

View File

@@ -19,7 +19,7 @@ namespace i2p
RouterContext::RouterContext ():
m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false),
m_StartupTime (0), m_ShareRatio (100), m_Status (eRouterStatusOK),
m_ShareRatio (100), m_Status (eRouterStatusOK),
m_Error (eRouterErrorNone), m_NetID (I2PD_NET_ID)
{
}
@@ -27,7 +27,7 @@ namespace i2p
void RouterContext::Init ()
{
srand (i2p::util::GetMillisecondsSinceEpoch () % 1000);
m_StartupTime = i2p::util::GetSecondsSinceEpoch ();
m_StartupTime = std::chrono::steady_clock::now();
if (!Load ())
CreateNewRouter ();
m_Decryptor = m_Keys.CreateDecryptor (nullptr);
@@ -110,7 +110,17 @@ namespace i2p
{
bool published; i2p::config::GetOption("ntcp2.published", published);
if (published)
{
PublishNTCP2Address (port, true);
if (ipv6)
{
// add NTCP2 ipv6 address
std::string host = "::1";
if (!i2p::config::IsDefault ("ntcp2.addressv6"))
i2p::config::GetOption ("ntcp2.addressv6", host);
m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v6::from_string (host), port);
}
}
}
}
}
@@ -170,20 +180,21 @@ namespace i2p
UpdateRouterInfo ();
}
void RouterContext::PublishNTCP2Address (int port, bool publish)
void RouterContext::PublishNTCP2Address (int port, bool publish, bool v4only)
{
if (!m_NTCP2Keys) return;
if (!port)
{
port = rand () % (30777 - 9111) + 9111; // I2P network ports range
if (port == 9150) port = 9151; // Tor browser
}
bool updated = false;
for (auto& address : m_RouterInfo.GetAddresses ())
{
if (address->IsNTCP2 () && (address->port != port || address->ntcp2->isPublished != publish))
if (address->IsNTCP2 () && (address->port != port || address->ntcp2->isPublished != publish) && (!v4only || address->host.is_v4 ()))
{
address->port = port;
if (!port && !address->port)
{
// select random port only if address's port is not set
port = rand () % (30777 - 9111) + 9111; // I2P network ports range
if (port == 9150) port = 9151; // Tor browser
}
if (port) address->port = port;
address->cost = publish ? 3 : 14;
address->ntcp2->isPublished = publish;
address->ntcp2->iv = m_NTCP2Keys->iv;
@@ -228,6 +239,20 @@ namespace i2p
if (address->host != host && address->IsCompatible (host))
{
address->host = host;
if (host.is_v6 () && address->transportStyle == i2p::data::RouterInfo::eTransportSSU)
{
// update MTU
auto mtu = i2p::util::net::GetMTU (host);
if (mtu)
{
LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu);
if (mtu > 1472) { // TODO: magic constant
mtu = 1472;
LogPrint(eLogWarning, "Router: MTU dropped to upper limit of 1472 bytes");
}
if (address->ssu) address->ssu->mtu = mtu;
}
}
updated = true;
}
}
@@ -397,13 +422,25 @@ namespace i2p
caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill
caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer
m_RouterInfo.SetCaps (caps);
// remove NTCP v4 address
PublishNTCPAddress (false);
uint16_t port = 0;
// delete previous introducers
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr : addresses)
if (addr->ssu)
{
addr->ssu->introducers.clear ();
port = addr->port;
}
// remove NTCP or NTCP2 v4 address
bool ntcp; i2p::config::GetOption("ntcp", ntcp);
if (ntcp)
PublishNTCPAddress (false);
else
{
bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2);
if (ntcp2)
PublishNTCP2Address (port, false, true);
}
// update
UpdateRouterInfo ();
}
@@ -418,15 +455,34 @@ namespace i2p
if (m_IsFloodfill)
caps |= i2p::data::RouterInfo::eFloodfill;
m_RouterInfo.SetCaps (caps);
// insert NTCP back
bool ntcp; i2p::config::GetOption("ntcp", ntcp);
if (ntcp)
PublishNTCPAddress (true);
uint16_t port = 0;
// delete previous introducers
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr : addresses)
if (addr->ssu)
{
addr->ssu->introducers.clear ();
port = addr->port;
}
// insert NTCP or NTCP2 back
bool ntcp; i2p::config::GetOption("ntcp", ntcp);
if (ntcp)
PublishNTCPAddress (true);
else
{
// ntcp2
bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2);
if (ntcp2)
{
bool published; i2p::config::GetOption ("ntcp2.published", published);
if (published)
{
uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port);
if (!ntcp2Port) ntcp2Port = port;
PublishNTCP2Address (ntcp2Port, true, true);
}
}
}
// update
UpdateRouterInfo ();
}
@@ -434,7 +490,66 @@ namespace i2p
void RouterContext::SetSupportsV6 (bool supportsV6)
{
if (supportsV6)
{
m_RouterInfo.EnableV6 ();
// insert v6 addresses if necessary
bool foundSSU = false, foundNTCP = false, foundNTCP2 = false;
uint16_t port = 0;
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr: addresses)
{
if (addr->host.is_v6 ())
{
if (addr->transportStyle == i2p::data::RouterInfo::eTransportSSU)
foundSSU = true;
else if (addr->IsNTCP2 ())
{
if (addr->IsPublishedNTCP2 ()) foundNTCP2 = true;
}
else
foundNTCP = true;
}
port = addr->port;
}
if (!port) i2p::config::GetOption("port", port);
// SSU
if (!foundSSU)
{
bool ssu; i2p::config::GetOption("ssu", ssu);
if (ssu)
{
std::string host = "::1"; // TODO: read host
m_RouterInfo.AddSSUAddress (host.c_str (), port, GetIdentHash ());
}
}
// NTCP2
if (!foundNTCP2)
{
bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2);
bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published);
if (ntcp2 && ntcp2Published)
{
std::string ntcp2Host;
if (!i2p::config::IsDefault ("ntcp2.addressv6"))
i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host);
else
ntcp2Host = "::1";
uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port);
if (!ntcp2Port) ntcp2Port = port;
m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (ntcp2Host), ntcp2Port);
}
}
// NTCP
if (!foundNTCP)
{
bool ntcp; i2p::config::GetOption("ntcp", ntcp);
if (ntcp)
{
std::string host = "::1";
m_RouterInfo.AddNTCPAddress (host.c_str (), port);
}
}
}
else
m_RouterInfo.DisableV6 ();
UpdateRouterInfo ();
@@ -449,50 +564,9 @@ namespace i2p
UpdateRouterInfo ();
}
void RouterContext::UpdateNTCPV6Address (const boost::asio::ip::address& host)
{
bool updated = false, found = false;
int port = 0;
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr: addresses)
{
if (addr->host.is_v6 () && addr->transportStyle == i2p::data::RouterInfo::eTransportNTCP)
{
if (addr->host != host)
{
addr->host = host;
updated = true;
}
found = true;
}
else
port = addr->port;
}
if (!found)
{
// create new address
m_RouterInfo.AddNTCPAddress (host.to_string ().c_str (), port);
auto mtu = i2p::util::net::GetMTU (host);
if (mtu)
{
LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu);
if (mtu > 1472) { // TODO: magic constant
mtu = 1472;
LogPrint(eLogWarning, "Router: MTU dropped to upper limit of 1472 bytes");
}
}
m_RouterInfo.AddSSUAddress (host.to_string ().c_str (), port, GetIdentHash (), mtu ? mtu : 1472); // TODO
updated = true;
}
if (updated)
UpdateRouterInfo ();
}
void RouterContext::UpdateNTCP2V6Address (const boost::asio::ip::address& host)
{
bool updated = false, found = false;
int port = 0;
bool updated = false;
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr: addresses)
{
@@ -505,19 +579,11 @@ namespace i2p
addr->host = host;
updated = true;
}
found = true;
break;
}
else
port = addr->port; // NTCP2 v4
}
}
if (!found && port) // we have found NTCP2 v4 but not v6
{
m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, host, port);
updated = true;
}
if (updated)
UpdateRouterInfo ();
}
@@ -650,7 +716,7 @@ namespace i2p
uint32_t RouterContext::GetUptime () const
{
return i2p::util::GetSecondsSinceEpoch () - m_StartupTime;
return std::chrono::duration_cast<std::chrono::seconds> (std::chrono::steady_clock::now() - m_StartupTime).count ();
}
bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const

View File

@@ -5,6 +5,7 @@
#include <string>
#include <memory>
#include <mutex>
#include <chrono>
#include <boost/asio.hpp>
#include "Identity.h"
#include "RouterInfo.h"
@@ -64,8 +65,7 @@ namespace i2p
const uint8_t * GetNTCP2IV () const { return m_NTCP2Keys ? m_NTCP2Keys->iv : nullptr; };
i2p::crypto::X25519Keys& GetStaticKeys ();
uint32_t GetUptime () const;
uint32_t GetStartupTime () const { return m_StartupTime; };
uint32_t GetUptime () const; // in seconds
uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; };
uint64_t GetBandwidthLimit () const { return m_BandwidthLimit; };
uint64_t GetTransitBandwidthLimit () const { return (m_BandwidthLimit*m_ShareRatio)/100LL; };
@@ -79,7 +79,7 @@ namespace i2p
void UpdatePort (int port); // called from Daemon
void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon
void PublishNTCP2Address (int port, bool publish = true);
void PublishNTCP2Address (int port, bool publish = true, bool v4only = false);
void UpdateNTCP2Address (bool enable);
void PublishNTCPAddress (bool publish, bool v4only = true);
bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer);
@@ -101,8 +101,7 @@ namespace i2p
void SetSupportsV6 (bool supportsV6);
void SetSupportsV4 (bool supportsV4);
void UpdateNTCPV6Address (const boost::asio::ip::address& host); // called from NTCP session
void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from NTCP2 session
void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from Daemon. TODO: remove
void UpdateStats ();
void UpdateTimestamp (uint64_t ts); // in seconds, called from NetDb before publishing
void CleanupDestination (); // garlic destination
@@ -138,7 +137,7 @@ namespace i2p
std::shared_ptr<i2p::crypto::CryptoKeyDecryptor> m_Decryptor;
uint64_t m_LastUpdateTime; // in seconds
bool m_AcceptsTunnels, m_IsFloodfill;
uint64_t m_StartupTime; // in seconds since epoch
std::chrono::time_point<std::chrono::steady_clock> m_StartupTime;
uint64_t m_BandwidthLimit; // allowed bandwidth
int m_ShareRatio;
RouterStatus m_Status;

View File

@@ -38,10 +38,19 @@ namespace data
m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0)
{
m_Addresses = boost::make_shared<Addresses>(); // create empty list
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
memcpy (m_Buffer, buf, len);
m_BufferLen = len;
ReadFromBuffer (true);
if (len <= MAX_RI_BUFFER_SIZE)
{
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
memcpy (m_Buffer, buf, len);
m_BufferLen = len;
ReadFromBuffer (true);
}
else
{
LogPrint (eLogError, "RouterInfo: Buffer is too long ", len, ". Ignored");
m_Buffer = nullptr;
m_IsUnreachable = true;
}
}
RouterInfo::~RouterInfo ()
@@ -49,8 +58,14 @@ namespace data
delete[] m_Buffer;
}
void RouterInfo::Update (const uint8_t * buf, int len)
void RouterInfo::Update (const uint8_t * buf, size_t len)
{
if (len > MAX_RI_BUFFER_SIZE)
{
LogPrint (eLogError, "RouterInfo: Buffer is too long ", len);
m_IsUnreachable = true;
return;
}
// verify signature since we have identity already
int l = len - m_RouterIdentity->GetSignatureLen ();
if (m_RouterIdentity->Verify (buf, l, buf + l))
@@ -100,8 +115,7 @@ namespace data
return false;
}
s.seekg(0, std::ios::beg);
if (!m_Buffer)
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
if (!m_Buffer) m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
s.read((char *)m_Buffer, m_BufferLen);
}
else
@@ -259,6 +273,11 @@ namespace data
else if (key[0] == 'i')
{
// introducers
if (!address->ssu)
{
LogPrint (eLogError, "RouterInfo: Introducer is presented for non-SSU address. Skipped");
continue;
}
introducers = true;
size_t l = strlen(key);
unsigned char index = key[l-1] - '0'; // TODO:
@@ -602,15 +621,21 @@ namespace data
std::stringstream s;
uint8_t ident[1024];
auto identLen = privateKeys.GetPublic ()->ToBuffer (ident, 1024);
auto signatureLen = privateKeys.GetPublic ()->GetSignatureLen ();
s.write ((char *)ident, identLen);
WriteToStream (s);
m_BufferLen = s.str ().size ();
if (!m_Buffer)
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
memcpy (m_Buffer, s.str ().c_str (), m_BufferLen);
// signature
privateKeys.Sign ((uint8_t *)m_Buffer, m_BufferLen, (uint8_t *)m_Buffer + m_BufferLen);
m_BufferLen += privateKeys.GetPublic ()->GetSignatureLen ();
if (m_BufferLen + signatureLen < MAX_RI_BUFFER_SIZE)
{
memcpy (m_Buffer, s.str ().c_str (), m_BufferLen);
// signature
privateKeys.Sign ((uint8_t *)m_Buffer, m_BufferLen, (uint8_t *)m_Buffer + m_BufferLen);
m_BufferLen += signatureLen;
}
else
LogPrint (eLogError, "RouterInfo: Our RouterInfo is too long ", m_BufferLen + signatureLen);
}
bool RouterInfo::SaveToFile (const std::string& fullPath)
@@ -784,6 +809,11 @@ namespace data
return m_SupportedTransports & (eSSUV4 | eSSUV6);
}
bool RouterInfo::IsSSUV6 () const
{
return m_SupportedTransports & eSSUV6;
}
bool RouterInfo::IsNTCP2 (bool v4only) const
{
if (v4only)
@@ -794,7 +824,7 @@ namespace data
bool RouterInfo::IsV6 () const
{
return m_SupportedTransports & (eNTCPV6 | eSSUV6);
return m_SupportedTransports & (eNTCPV6 | eSSUV6 | eNTCP2V6);
}
bool RouterInfo::IsV4 () const

View File

@@ -38,7 +38,7 @@ namespace data
const char CAPS_FLAG_SSU_TESTING = 'B';
const char CAPS_FLAG_SSU_INTRODUCER = 'C';
const int MAX_RI_BUFFER_SIZE = 2048;
const int MAX_RI_BUFFER_SIZE = 2048; // if RouterInfo exceeds 2048 we consider it as malformed, might be changed later
class RouterInfo: public RoutingDestination
{
public:
@@ -161,6 +161,7 @@ namespace data
bool IsReachable () const { return m_Caps & Caps::eReachable; };
bool IsNTCP (bool v4only = true) const;
bool IsSSU (bool v4only = true) const;
bool IsSSUV6 () const;
bool IsNTCP2 (bool v4only = true) const;
bool IsV6 () const;
bool IsV4 () const;
@@ -196,7 +197,7 @@ namespace data
std::shared_ptr<RouterProfile> GetProfile () const;
void SaveProfile () { if (m_Profile) m_Profile->Save (GetIdentHash ()); };
void Update (const uint8_t * buf, int len);
void Update (const uint8_t * buf, size_t len);
void DeleteBuffer () { delete[] m_Buffer; m_Buffer = nullptr; };
bool IsNewer (const uint8_t * buf, size_t len) const;

View File

@@ -1,4 +1,5 @@
#include <boost/bind.hpp>
#include "version.h"
#include "Crypto.h"
#include "Log.h"
#include "Timestamp.h"
@@ -729,7 +730,8 @@ namespace transport
encryption.Encrypt (encrypted, encryptedLen, encrypted);
// assume actual buffer size is 18 (16 + 2) bytes more
memcpy (buf + len, iv, 16);
htobe16buf (buf + len + 16, encryptedLen);
uint16_t netid = i2p::context.GetNetID ();
htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8));
i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, header->mac);
}
@@ -750,7 +752,8 @@ namespace transport
m_SessionKeyEncryption.Encrypt (encrypted, encryptedLen, encrypted);
// assume actual buffer size is 18 (16 + 2) bytes more
memcpy (buf + len, header->iv, 16);
htobe16buf (buf + len + 16, encryptedLen);
uint16_t netid = i2p::context.GetNetID ();
htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8));
i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, m_MacKey, header->mac);
}
@@ -799,7 +802,8 @@ namespace transport
uint16_t encryptedLen = len - (encrypted - buf);
// assume actual buffer size is 18 (16 + 2) bytes more
memcpy (buf + len, header->iv, 16);
htobe16buf (buf + len + 16, encryptedLen);
uint16_t netid = i2p::context.GetNetID ();
htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8));
uint8_t digest[16];
i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, digest);
return !memcmp (header->mac, digest, 16);

View File

@@ -918,7 +918,7 @@ namespace stream
{
expired = false;
// time to request
if (m_RemoteLeaseSet->GetOrigStoreType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
if (m_RemoteLeaseSet->IsPublishedEncrypted ())
m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet (
std::make_shared<i2p::data::BlindedPublicKey>(m_RemoteIdentity));
else
@@ -964,7 +964,6 @@ namespace stream
StreamingDestination::StreamingDestination (std::shared_ptr<i2p::client::ClientDestination> owner, uint16_t localPort, bool gzip):
m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip),
m_LastIncomingReceiveStreamID (0),
m_PendingIncomingTimer (m_Owner->GetService ())
{
}
@@ -991,6 +990,7 @@ namespace stream
{
std::unique_lock<std::mutex> l(m_StreamsMutex);
m_Streams.clear ();
m_IncomingStreams.clear ();
}
}
@@ -1013,18 +1013,17 @@ namespace stream
if (packet->IsSYN () && !packet->GetSeqn ()) // new incoming stream
{
uint32_t receiveStreamID = packet->GetReceiveStreamID ();
if (receiveStreamID == m_LastIncomingReceiveStreamID)
auto it1 = m_IncomingStreams.find (receiveStreamID);
if (it1 != m_IncomingStreams.end ())
{
// already pending
LogPrint(eLogWarning, "Streaming: Incoming streaming with rSID=", receiveStreamID, " already exists");
DeletePacket (packet); // drop it, because previous should be connected
return;
}
auto incomingStream = CreateNewIncomingStream ();
auto incomingStream = CreateNewIncomingStream (receiveStreamID);
incomingStream->HandleNextPacket (packet); // SYN
auto ident = incomingStream->GetRemoteIdentity();
m_LastIncomingReceiveStreamID = receiveStreamID;
// handle saved packets if any
{
@@ -1062,13 +1061,13 @@ namespace stream
else // follow on packet without SYN
{
uint32_t receiveStreamID = packet->GetReceiveStreamID ();
for (auto& it: m_Streams)
if (it.second->GetSendStreamID () == receiveStreamID)
{
// found
it.second->HandleNextPacket (packet);
return;
}
auto it1 = m_IncomingStreams.find (receiveStreamID);
if (it1 != m_IncomingStreams.end ())
{
// found
it1->second->HandleNextPacket (packet);
return;
}
// save follow on packet
auto it = m_SavedPackets.find (receiveStreamID);
if (it != m_SavedPackets.end ())
@@ -1105,11 +1104,12 @@ namespace stream
return s;
}
std::shared_ptr<Stream> StreamingDestination::CreateNewIncomingStream ()
std::shared_ptr<Stream> StreamingDestination::CreateNewIncomingStream (uint32_t receiveStreamID)
{
auto s = std::make_shared<Stream> (m_Owner->GetService (), *this);
std::unique_lock<std::mutex> l(m_StreamsMutex);
m_Streams[s->GetRecvStreamID ()] = s;
m_IncomingStreams[receiveStreamID] = s;
return s;
}
@@ -1118,9 +1118,8 @@ namespace stream
if (stream)
{
std::unique_lock<std::mutex> l(m_StreamsMutex);
auto it = m_Streams.find (stream->GetRecvStreamID ());
if (it != m_Streams.end ())
m_Streams.erase (it);
m_Streams.erase (stream->GetRecvStreamID ());
m_IncomingStreams.erase (stream->GetSendStreamID ());
}
}

View File

@@ -269,8 +269,10 @@ namespace stream
void AcceptOnceAcceptor (std::shared_ptr<Stream> stream, Acceptor acceptor, Acceptor prev);
private:
void HandleNextPacket (Packet * packet);
std::shared_ptr<Stream> CreateNewIncomingStream ();
std::shared_ptr<Stream> CreateNewIncomingStream (uint32_t receiveStreamID);
void HandlePendingIncomingTimer (const boost::system::error_code& ecode);
private:
@@ -280,8 +282,8 @@ namespace stream
bool m_Gzip; // gzip compression of data messages
std::mutex m_StreamsMutex;
std::map<uint32_t, std::shared_ptr<Stream> > m_Streams; // sendStreamID->stream
std::map<uint32_t, std::shared_ptr<Stream> > m_IncomingStreams; // receiveStreamID->stream
Acceptor m_Acceptor;
uint32_t m_LastIncomingReceiveStreamID;
std::list<std::shared_ptr<Stream> > m_PendingIncomingStreams;
boost::asio::deadline_timer m_PendingIncomingTimer;
std::map<uint32_t, std::list<Packet *> > m_SavedPackets; // receiveStreamID->packets, arrived before SYN

View File

@@ -71,14 +71,14 @@ public:
return std::string (str, str + l);
}
void FromBase32 (const std::string& s)
size_t FromBase32 (const std::string& s)
{
i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz);
return i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz);
}
void FromBase64 (const std::string& s)
size_t FromBase64 (const std::string& s)
{
i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz);
return i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz);
}
private:

View File

@@ -552,6 +552,23 @@ namespace transport
m_SSUServer->CreateSession (router); // no peer test
}
}
if (i2p::context.SupportsV6 ())
{
// try to connect to few v6 addresses to get our address back
for (int i = 0; i < 3; i++)
{
auto router = i2p::data::netdb.GetRandomSSUV6Router ();
if (router)
{
auto addr = router->GetSSUV6Address ();
if (addr)
m_SSUServer->GetServiceV6 ().post ([this, router, addr]
{
m_SSUServer->CreateDirectSession (router, { addr->host, (uint16_t)addr->port }, false);
});
}
}
}
}
else
LogPrint (eLogError, "Transports: Can't detect external IP. SSU is not available");

View File

@@ -46,7 +46,7 @@ namespace util
{
auto tmp = m_Head;
m_Head = static_cast<T*>(*(void * *)m_Head); // next
delete tmp;
::operator delete ((void *)tmp);
}
}

View File

@@ -7,7 +7,7 @@
#define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c)
#define I2PD_VERSION_MAJOR 2
#define I2PD_VERSION_MINOR 25
#define I2PD_VERSION_MINOR 28
#define I2PD_VERSION_MICRO 0
#define I2PD_VERSION_PATCH 0
#define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO)
@@ -21,7 +21,7 @@
#define I2P_VERSION_MAJOR 0
#define I2P_VERSION_MINOR 9
#define I2P_VERSION_MICRO 40
#define I2P_VERSION_MICRO 42
#define I2P_VERSION_PATCH 0
#define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO)

View File

@@ -422,9 +422,9 @@ namespace client
std::string name = s.substr(0, pos++);
std::string addr = s.substr(pos);
size_t pos = s.find('#');
size_t pos = addr.find('#');
if (pos != std::string::npos)
addr = addr.substr(pos); // remove comments
addr = addr.substr(0, pos); // remove comments
auto ident = std::make_shared<i2p::data::IdentityEx> ();
if (!ident->FromBase64(addr)) {

View File

@@ -50,7 +50,7 @@ namespace client
void BOBI2PInboundTunnel::ReceiveAddress (std::shared_ptr<AddressReceiver> receiver)
{
receiver->socket->async_read_some (boost::asio::buffer(
receiver->buffer + receiver->bufferOffset,
receiver->buffer + receiver->bufferOffset,
BOB_COMMAND_BUFFER_SIZE - receiver->bufferOffset),
std::bind(&BOBI2PInboundTunnel::HandleReceivedAddress, this,
std::placeholders::_1, std::placeholders::_2, receiver));
@@ -119,9 +119,9 @@ namespace client
connection->I2PConnect (receiver->data, receiver->dataLen);
}
BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& address, int port,
BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& outhost, int port,
std::shared_ptr<ClientDestination> localDestination, bool quiet): BOBI2PTunnel (localDestination),
m_Endpoint (boost::asio::ip::address::from_string (address), port), m_IsQuiet (quiet)
m_Endpoint (boost::asio::ip::address::from_string (outhost), port), m_IsQuiet (quiet)
{
}
@@ -154,9 +154,13 @@ namespace client
}
}
BOBDestination::BOBDestination (std::shared_ptr<ClientDestination> localDestination):
BOBDestination::BOBDestination (std::shared_ptr<ClientDestination> localDestination,
const std::string &nickname, const std::string &inhost, const std::string &outhost,
const int inport, const int outport, const bool quiet):
m_LocalDestination (localDestination),
m_OutboundTunnel (nullptr), m_InboundTunnel (nullptr)
m_OutboundTunnel (nullptr), m_InboundTunnel (nullptr),
m_Nickname(nickname), m_InHost(inhost), m_OutHost(outhost),
m_InPort(inport), m_OutPort(outport), m_Quiet(quiet)
{
}
@@ -195,15 +199,18 @@ namespace client
}
}
void BOBDestination::CreateInboundTunnel (int port, const std::string& address)
void BOBDestination::CreateInboundTunnel (int port, const std::string& inhost)
{
if (!m_InboundTunnel)
{
// update inport and inhost (user can stop tunnel and change)
m_InPort = port;
m_InHost = inhost;
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::tcp::v4(), port);
if (!address.empty ())
if (!inhost.empty ())
{
boost::system::error_code ec;
auto addr = boost::asio::ip::address::from_string (address, ec);
auto addr = boost::asio::ip::address::from_string (inhost, ec);
if (!ec)
ep.address (addr);
else
@@ -213,15 +220,21 @@ namespace client
}
}
void BOBDestination::CreateOutboundTunnel (const std::string& address, int port, bool quiet)
void BOBDestination::CreateOutboundTunnel (const std::string& outhost, int port, bool quiet)
{
if (!m_OutboundTunnel)
m_OutboundTunnel = new BOBI2POutboundTunnel (address, port, m_LocalDestination, quiet);
{
// update outport and outhost (user can stop tunnel and change)
m_OutPort = port;
m_OutHost = outhost;
m_OutboundTunnel = new BOBI2POutboundTunnel (outhost, port, m_LocalDestination, quiet);
}
}
BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner):
m_Owner (owner), m_Socket (m_Owner.GetService ()),
m_ReceiveBufferOffset (0), m_IsOpen (true), m_IsQuiet (false), m_IsActive (false),
m_ReceiveBuffer(BOB_COMMAND_BUFFER_SIZE + 1), m_SendBuffer(BOB_COMMAND_BUFFER_SIZE + 1),
m_IsOpen (true), m_IsQuiet (false), m_IsActive (false),
m_InPort (0), m_OutPort (0), m_CurrentDestination (nullptr)
{
}
@@ -238,65 +251,48 @@ namespace client
void BOBCommandSession::Receive ()
{
m_Socket.async_read_some (boost::asio::buffer(m_ReceiveBuffer + m_ReceiveBufferOffset, BOB_COMMAND_BUFFER_SIZE - m_ReceiveBufferOffset),
std::bind(&BOBCommandSession::HandleReceived, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
boost::asio::async_read_until(m_Socket, m_ReceiveBuffer, '\n',
std::bind(&BOBCommandSession::HandleReceivedLine, shared_from_this(),
std::placeholders::_1, std::placeholders::_2));
}
void BOBCommandSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
void BOBCommandSession::HandleReceivedLine(const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
if(ecode)
{
LogPrint (eLogError, "BOB: command channel read error: ", ecode.message ());
LogPrint (eLogError, "BOB: command channel read error: ", ecode.message());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
size_t size = m_ReceiveBufferOffset + bytes_transferred;
m_ReceiveBuffer[size] = 0;
char * eol = strchr (m_ReceiveBuffer, '\n');
if (eol)
std::string line;
std::istream is(&m_ReceiveBuffer);
std::getline(is, line);
std::string command, operand;
std::istringstream iss(line);
iss >> command >> operand;
// process command
auto& handlers = m_Owner.GetCommandHandlers();
auto it = handlers.find(command);
if(it != handlers.end())
{
*eol = 0;
char * operand = strchr (m_ReceiveBuffer, ' ');
if (operand)
{
*operand = 0;
operand++;
}
else
operand = eol;
// process command
auto& handlers = m_Owner.GetCommandHandlers ();
auto it = handlers.find (m_ReceiveBuffer);
if (it != handlers.end ())
(this->*(it->second))(operand, eol - operand);
else
{
LogPrint (eLogError, "BOB: unknown command ", m_ReceiveBuffer);
SendReplyError ("unknown command");
}
m_ReceiveBufferOffset = size - (eol - m_ReceiveBuffer) - 1;
memmove (m_ReceiveBuffer, eol + 1, m_ReceiveBufferOffset);
(this->*(it->second))(operand.c_str(), operand.length());
}
else
{
if (size < BOB_COMMAND_BUFFER_SIZE)
m_ReceiveBufferOffset = size;
else
{
LogPrint (eLogError, "BOB: Malformed input of the command channel");
Terminate ();
}
LogPrint (eLogError, "BOB: unknown command ", command.c_str());
SendReplyError ("unknown command");
}
}
}
void BOBCommandSession::Send (size_t len)
void BOBCommandSession::Send ()
{
boost::asio::async_write (m_Socket, boost::asio::buffer (m_SendBuffer, len),
boost::asio::async_write (m_Socket, m_SendBuffer,
boost::asio::transfer_all (),
std::bind(&BOBCommandSession::HandleSent, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
@@ -305,7 +301,7 @@ namespace client
void BOBCommandSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
{
LogPrint (eLogError, "BOB: command channel send error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
@@ -321,39 +317,66 @@ namespace client
void BOBCommandSession::SendReplyOK (const char * msg)
{
#ifdef _MSC_VER
size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_OK, msg);
#else
size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_OK, msg);
#endif
Send (len);
std::ostream os(&m_SendBuffer);
os << "OK";
if(msg)
{
os << " " << msg;
}
os << std::endl;
Send ();
}
void BOBCommandSession::SendReplyError (const char * msg)
{
#ifdef _MSC_VER
size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_ERROR, msg);
#else
size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_ERROR, msg);
#endif
Send (len);
std::ostream os(&m_SendBuffer);
os << "ERROR " << msg << std::endl;
Send ();
}
void BOBCommandSession::SendVersion ()
{
size_t len = strlen (BOB_VERSION);
memcpy (m_SendBuffer, BOB_VERSION, len);
Send (len);
std::ostream os(&m_SendBuffer);
os << "BOB 00.00.10" << std::endl;
SendReplyOK();
}
void BOBCommandSession::SendData (const char * nickname)
void BOBCommandSession::SendRaw (const char * data)
{
#ifdef _MSC_VER
size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_DATA, nickname);
#else
size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_DATA, nickname);
#endif
Send (len);
std::ostream os(&m_SendBuffer);
os << data << std::endl;
}
void BOBCommandSession::BuildStatusLine(bool currentTunnel, BOBDestination *dest, std::string &out)
{
// helper lambdas
const auto issetStr = [](const std::string &str) { return str.empty() ? "not_set" : str; }; // for inhost, outhost
const auto issetNum = [&issetStr](const int p) { return issetStr(p == 0 ? "" : std::to_string(p)); }; // for inport, outport
const auto destExists = [](const BOBDestination * const dest) { return dest != nullptr; };
const auto destReady = [](const BOBDestination * const dest) { return dest->GetLocalDestination()->IsReady(); };
const auto bool_str = [](const bool v) { return v ? "true" : "false"; }; // bool -> str
// tunnel info
const std::string nickname = currentTunnel ? m_Nickname : dest->GetNickname();
const bool quiet = currentTunnel ? m_IsQuiet : dest->GetQuiet();
const std::string inhost = issetStr(currentTunnel ? m_InHost : dest->GetInHost());
const std::string outhost = issetStr(currentTunnel ? m_OutHost : dest->GetOutHost());
const std::string inport = issetNum(currentTunnel ? m_InPort : dest->GetInPort());
const std::string outport = issetNum(currentTunnel ? m_OutPort : dest->GetOutPort());
const bool keys = destExists(dest); // key must exist when destination is created
const bool starting = destExists(dest) && !destReady(dest);
const bool running = destExists(dest) && destReady(dest);
const bool stopping = false;
// build line
std::stringstream ss;
ss << "DATA "
<< "NICKNAME: " << nickname << " " << "STARTING: " << bool_str(starting) << " "
<< "RUNNING: " << bool_str(running) << " " << "STOPPING: " << bool_str(stopping) << " "
<< "KEYS: " << bool_str(keys) << " " << "QUIET: " << bool_str(quiet) << " "
<< "INPORT: " << inport << " " << "INHOST: " << inhost << " "
<< "OUTPORT: " << outport << " " << "OUTHOST: " << outhost;
out = ss.str();
}
void BOBCommandSession::ZapCommandHandler (const char * operand, size_t len)
@@ -377,15 +400,50 @@ namespace client
SendReplyError ("tunnel is active");
return;
}
if (!m_Keys.GetPublic ()) // keys are set ?
{
SendReplyError("Keys must be set.");
return;
}
if (m_InPort == 0
&& m_OutHost.empty() && m_OutPort == 0)
{
SendReplyError("(inhost):inport or outhost:outport must be set.");
return;
}
if(!m_InHost.empty())
{
// TODO: FIXME: temporary validation, until hostname support is added
boost::system::error_code ec;
boost::asio::ip::address::from_string(m_InHost, ec);
if (ec)
{
SendReplyError("inhost must be a valid IPv4 address.");
return;
}
}
if(!m_OutHost.empty())
{
// TODO: FIXME: temporary validation, until hostname support is added
boost::system::error_code ec;
boost::asio::ip::address::from_string(m_OutHost, ec);
if (ec)
{
SendReplyError("outhost must be a IPv4 address.");
return;
}
}
if (!m_CurrentDestination)
{
m_CurrentDestination = new BOBDestination (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options));
m_CurrentDestination = new BOBDestination (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options), // deleted in clear command
m_Nickname, m_InHost, m_OutHost, m_InPort, m_OutPort, m_IsQuiet);
m_Owner.AddDestination (m_Nickname, m_CurrentDestination);
}
if (m_InPort)
m_CurrentDestination->CreateInboundTunnel (m_InPort, m_Address);
if (m_OutPort && !m_Address.empty ())
m_CurrentDestination->CreateOutboundTunnel (m_Address, m_OutPort, m_IsQuiet);
m_CurrentDestination->CreateInboundTunnel (m_InPort, m_InHost);
if (m_OutPort && !m_OutHost.empty ())
m_CurrentDestination->CreateOutboundTunnel (m_OutHost, m_OutPort, m_IsQuiet);
m_CurrentDestination->Start ();
SendReplyOK ("Tunnel starting");
m_IsActive = true;
@@ -496,7 +554,7 @@ namespace client
void BOBCommandSession::OuthostCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: outhost ", operand);
m_Address = operand;
m_OutHost = operand;
SendReplyOK ("outhost set");
}
@@ -513,7 +571,7 @@ namespace client
void BOBCommandSession::InhostCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: inhost ", operand);
m_Address = operand;
m_InHost = operand;
SendReplyOK ("inhost set");
}
@@ -591,9 +649,23 @@ namespace client
void BOBCommandSession::ListCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: list");
std::string statusLine;
bool sentCurrent = false;
const auto& destinations = m_Owner.GetDestinations ();
for (const auto& it: destinations)
SendData (it.first.c_str ());
{
BuildStatusLine(false, it.second, statusLine);
SendRaw(statusLine.c_str());
if(m_Nickname.compare(it.second->GetNickname()) == 0)
sentCurrent = true;
}
if(!sentCurrent && !m_Nickname.empty())
{
// add the current tunnel to the list.
// this is for the incomplete tunnel which has not been started yet.
BuildStatusLine(true, m_CurrentDestination, statusLine);
SendRaw(statusLine.c_str());
}
SendReplyOK ("Listing done");
}
@@ -619,34 +691,55 @@ namespace client
void BOBCommandSession::StatusCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: status ", operand);
if (m_Nickname == operand)
const std::string name = operand;
std::string statusLine;
// always prefer destination
auto ptr = m_Owner.FindDestination(name);
if(ptr != nullptr)
{
std::stringstream s;
s << "DATA"; s << " NICKNAME: "; s << m_Nickname;
if (m_CurrentDestination)
{
if (m_CurrentDestination->GetLocalDestination ()->IsReady ())
s << " STARTING: false RUNNING: true STOPPING: false";
else
s << " STARTING: true RUNNING: false STOPPING: false";
}
else
s << " STARTING: false RUNNING: false STOPPING: false";
s << " KEYS: true"; s << " QUIET: "; s << (m_IsQuiet ? "true":"false");
if (m_InPort)
{
s << " INPORT: " << m_InPort;
s << " INHOST: " << (m_Address.length () > 0 ? m_Address : "127.0.0.1");
}
if (m_OutPort)
{
s << " OUTPORT: " << m_OutPort;
s << " OUTHOST: " << (m_Address.length () > 0 ? m_Address : "127.0.0.1");
}
SendReplyOK (s.str().c_str());
// tunnel destination exists
BuildStatusLine(false, ptr, statusLine);
SendReplyOK(statusLine.c_str());
}
else
SendReplyError ("no nickname has been set");
{
if(m_Nickname == name && !name.empty())
{
// tunnel is incomplete / has not been started yet
BuildStatusLine(true, nullptr, statusLine);
SendReplyOK(statusLine.c_str());
}
else
{
SendReplyError("no nickname has been set");
}
}
}
void BOBCommandSession::HelpCommandHandler (const char * operand, size_t len)
{
auto helpStrings = m_Owner.GetHelpStrings();
if(len == 0)
{
std::stringstream ss;
ss << "COMMANDS:";
for (auto const& x : helpStrings)
{
ss << " " << x.first;
}
const std::string &str = ss.str();
SendReplyOK(str.c_str());
}
else
{
auto it = helpStrings.find(operand);
if (it != helpStrings.end ())
{
SendReplyOK(it->second.c_str());
return;
}
SendReplyError("No such command");
}
}
BOBCommandChannel::BOBCommandChannel (const std::string& address, int port):
@@ -674,6 +767,29 @@ namespace client
m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler;
m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler;
m_CommandHandlers[BOB_COMMAND_STATUS] = &BOBCommandSession::StatusCommandHandler;
m_CommandHandlers[BOB_COMMAND_HELP] = &BOBCommandSession::HelpCommandHandler;
// command -> help string
m_HelpStrings[BOB_COMMAND_ZAP] = BOB_HELP_ZAP;
m_HelpStrings[BOB_COMMAND_QUIT] = BOB_HELP_QUIT;
m_HelpStrings[BOB_COMMAND_START] = BOB_HELP_START;
m_HelpStrings[BOB_COMMAND_STOP] = BOB_HELP_STOP;
m_HelpStrings[BOB_COMMAND_SETNICK] = BOB_HELP_SETNICK;
m_HelpStrings[BOB_COMMAND_GETNICK] = BOB_HELP_GETNICK;
m_HelpStrings[BOB_COMMAND_NEWKEYS] = BOB_HELP_NEWKEYS;
m_HelpStrings[BOB_COMMAND_GETKEYS] = BOB_HELP_GETKEYS;
m_HelpStrings[BOB_COMMAND_SETKEYS] = BOB_HELP_SETKEYS;
m_HelpStrings[BOB_COMMAND_GETDEST] = BOB_HELP_GETDEST;
m_HelpStrings[BOB_COMMAND_OUTHOST] = BOB_HELP_OUTHOST;
m_HelpStrings[BOB_COMMAND_OUTPORT] = BOB_HELP_OUTPORT;
m_HelpStrings[BOB_COMMAND_INHOST] = BOB_HELP_INHOST;
m_HelpStrings[BOB_COMMAND_INPORT] = BOB_HELP_INPORT;
m_HelpStrings[BOB_COMMAND_QUIET] = BOB_HELP_QUIET;
m_HelpStrings[BOB_COMMAND_LOOKUP] = BOB_HELP_LOOKUP;
m_HelpStrings[BOB_COMMAND_CLEAR] = BOB_HELP_CLEAR;
m_HelpStrings[BOB_COMMAND_LIST] = BOB_HELP_LIST;
m_HelpStrings[BOB_COMMAND_OPTION] = BOB_HELP_OPTION;
m_HelpStrings[BOB_COMMAND_STATUS] = BOB_HELP_STATUS;
m_HelpStrings[BOB_COMMAND_HELP] = BOB_HELP_HELP;
}
BOBCommandChannel::~BOBCommandChannel ()

View File

@@ -37,11 +37,29 @@ namespace client
const char BOB_COMMAND_LIST[] = "list";
const char BOB_COMMAND_OPTION[] = "option";
const char BOB_COMMAND_STATUS[] = "status";
const char BOB_VERSION[] = "BOB 00.00.10\nOK\n";
const char BOB_REPLY_OK[] = "OK %s\n";
const char BOB_REPLY_ERROR[] = "ERROR %s\n";
const char BOB_DATA[] = "NICKNAME %s\n";
const char BOB_COMMAND_HELP[] = "help";
const char BOB_HELP_ZAP[] = "zap - Shuts down BOB.";
const char BOB_HELP_QUIT[] = "quit - Quits this session with BOB.";
const char BOB_HELP_START[] = "start - Starts the current nicknamed tunnel.";
const char BOB_HELP_STOP[] = "stop - Stops the current nicknamed tunnel.";
const char BOB_HELP_SETNICK[] = "setnick <NICKNAME> - Creates a new nickname.";
const char BOB_HELP_GETNICK[] = "getnick <TUNNELNAME> - Sets the nickname from the database.";
const char BOB_HELP_NEWKEYS[] = "newkeys - Generate a new keypair for the current nickname.";
const char BOB_HELP_GETKEYS[] = "getkeys - Return the keypair for the current nickname.";
const char BOB_HELP_SETKEYS[] = "setkeys <BASE64_KEYPAIR> - Sets the keypair for the current nickname.";
const char BOB_HELP_GETDEST[] = "getdest - Return the destination for the current nickname.";
const char BOB_HELP_OUTHOST[] = "outhost <HOSTNAME|IP> - Set the outhound hostname or IP.";
const char BOB_HELP_OUTPORT[] = "outport <PORT_NUMBER> - Set the outbound port that nickname contacts.";
const char BOB_HELP_INHOST[] = "inhost <HOSTNAME|IP> - Set the inbound hostname or IP.";
const char BOB_HELP_INPORT[] = "inport <PORT_NUMBER> - Set the inbound port number nickname listens on.";
const char BOB_HELP_QUIET[] = "quiet <True|False> - Wether to send the incoming destination.";
const char BOB_HELP_LOOKUP[] = "lookup <I2P_HOSTNAME> - Look up an I2P hostname.";
const char BOB_HELP_CLEAR[] = "clear - Clear the current nickname out of the list.";
const char BOB_HELP_LIST[] = "list - List all tunnels.";
const char BOB_HELP_OPTION[] = "option <KEY>=<VALUE> - Set an option. NOTE: Don't use any spaces.";
const char BOB_HELP_STATUS[] = "status <NICKNAME> - Display status of a nicknamed tunnel.";
const char BOB_HELP_HELP [] = "help <COMMAND> - Get help on a command.";
class BOBI2PTunnel: public I2PService
{
@@ -96,7 +114,7 @@ namespace client
{
public:
BOBI2POutboundTunnel (const std::string& address, int port, std::shared_ptr<ClientDestination> localDestination, bool quiet);
BOBI2POutboundTunnel (const std::string& outhost, int port, std::shared_ptr<ClientDestination> localDestination, bool quiet);
void Start ();
void Stop ();
@@ -119,14 +137,22 @@ namespace client
{
public:
BOBDestination (std::shared_ptr<ClientDestination> localDestination);
BOBDestination (std::shared_ptr<ClientDestination> localDestination,
const std::string &nickname, const std::string &inhost, const std::string &outhost,
const int inport, const int outport, const bool quiet);
~BOBDestination ();
void Start ();
void Stop ();
void StopTunnels ();
void CreateInboundTunnel (int port, const std::string& address);
void CreateOutboundTunnel (const std::string& address, int port, bool quiet);
void CreateInboundTunnel (int port, const std::string& inhost);
void CreateOutboundTunnel (const std::string& outhost, int port, bool quiet);
const std::string& GetNickname() const { return m_Nickname; }
const std::string& GetInHost() const { return m_InHost; }
const std::string& GetOutHost() const { return m_OutHost; }
int GetInPort() const { return m_InPort; }
int GetOutPort() const { return m_OutPort; }
bool GetQuiet() const { return m_Quiet; }
const i2p::data::PrivateKeys& GetKeys () const { return m_LocalDestination->GetPrivateKeys (); };
std::shared_ptr<ClientDestination> GetLocalDestination () const { return m_LocalDestination; };
@@ -135,6 +161,11 @@ namespace client
std::shared_ptr<ClientDestination> m_LocalDestination;
BOBI2POutboundTunnel * m_OutboundTunnel;
BOBI2PInboundTunnel * m_InboundTunnel;
std::string m_Nickname;
std::string m_InHost, m_OutHost;
int m_InPort, m_OutPort;
bool m_Quiet;
};
class BOBCommandChannel;
@@ -170,26 +201,29 @@ namespace client
void ListCommandHandler (const char * operand, size_t len);
void OptionCommandHandler (const char * operand, size_t len);
void StatusCommandHandler (const char * operand, size_t len);
void HelpCommandHandler (const char * operand, size_t len);
private:
void Receive ();
void HandleReceivedLine(const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void Send (size_t len);
void Send ();
void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void SendReplyOK (const char * msg);
void SendReplyOK (const char * msg = nullptr);
void SendReplyError (const char * msg);
void SendData (const char * nickname);
void SendRaw (const char * data);
void BuildStatusLine(bool currentTunnel, BOBDestination *destination, std::string &out);
private:
BOBCommandChannel& m_Owner;
boost::asio::ip::tcp::socket m_Socket;
char m_ReceiveBuffer[BOB_COMMAND_BUFFER_SIZE + 1], m_SendBuffer[BOB_COMMAND_BUFFER_SIZE + 1];
size_t m_ReceiveBufferOffset;
boost::asio::streambuf m_ReceiveBuffer, m_SendBuffer;
bool m_IsOpen, m_IsQuiet, m_IsActive;
std::string m_Nickname, m_Address;
std::string m_Nickname, m_InHost, m_OutHost;
int m_InPort, m_OutPort;
i2p::data::PrivateKeys m_Keys;
std::map<std::string, std::string> m_Options;
@@ -226,10 +260,12 @@ namespace client
boost::asio::ip::tcp::acceptor m_Acceptor;
std::map<std::string, BOBDestination *> m_Destinations;
std::map<std::string, BOBCommandHandler> m_CommandHandlers;
std::map<std::string, std::string> m_HelpStrings;
public:
const decltype(m_CommandHandlers)& GetCommandHandlers () const { return m_CommandHandlers; };
const decltype(m_HelpStrings)& GetHelpStrings () const { return m_HelpStrings; };
const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; };
};
}

View File

@@ -381,6 +381,16 @@ namespace client
return section.second.get (boost::property_tree::ptree::path_type (name, '/'), value);
}
template<typename Section>
void ClientContext::ReadI2CPOptionsGroup (const Section& section, const std::string& group, std::map<std::string, std::string>& options) const
{
for (auto it: section.second)
{
if (it.first.length () >= group.length () && !it.first.compare (0, group.length (), group))
options[it.first] = it.second.get_value ("");
}
}
template<typename Section>
void ClientContext::ReadI2CPOptions (const Section& section, std::map<std::string, std::string>& options) const
{
@@ -395,6 +405,17 @@ namespace client
options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption(section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE);
std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "");
if (encType.length () > 0) options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = encType;
std::string privKey = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_PRIV_KEY, "");
if (privKey.length () > 0) options[I2CP_PARAM_LEASESET_PRIV_KEY] = privKey;
auto authType = GetI2CPOption(section, I2CP_PARAM_LEASESET_AUTH_TYPE, 0);
if (authType != "0") // auth is set
{
options[I2CP_PARAM_LEASESET_AUTH_TYPE] = authType;
if (authType == "1") // DH
ReadI2CPOptionsGroup (section, I2CP_PARAM_LEASESET_CLIENT_DH, options);
else if (authType == "2") // PSK
ReadI2CPOptionsGroup (section, I2CP_PARAM_LEASESET_CLIENT_PSK, options);
}
}
void ClientContext::ReadI2CPOptionsFromConfig (const std::string& prefix, std::map<std::string, std::string>& options) const
@@ -608,8 +629,8 @@ namespace client
// I2CP
std::map<std::string, std::string> options;
ReadI2CPOptions (section, options);
ReadI2CPOptions (section, options);
std::shared_ptr<ClientDestination> localDestination = nullptr;
i2p::data::PrivateKeys k;
if(!LoadPrivateKeys (k, keys, sigType, cryptoType))

View File

@@ -95,6 +95,8 @@ namespace client
template<typename Section>
std::string GetI2CPStringOption (const Section& section, const std::string& name, const std::string& value) const; // GetI2CPOption with string default value
template<typename Section>
void ReadI2CPOptionsGroup (const Section& section, const std::string& group, std::map<std::string, std::string>& options) const;
template<typename Section>
void ReadI2CPOptions (const Section& section, std::map<std::string, std::string>& options) const; // for tunnels
void ReadI2CPOptionsFromConfig (const std::string& prefix, std::map<std::string, std::string>& options) const; // for HTTP and SOCKS proxy

View File

@@ -1,3 +1,11 @@
/*
* Copyright (c) 2013-2019, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <cstring>
#include <cassert>
#include <string>
@@ -5,6 +13,7 @@
#include <memory>
#include <set>
#include <boost/asio.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <mutex>
#include "I2PService.h"
@@ -211,16 +220,28 @@ namespace proxy {
void HTTPReqHandler::SanitizeHTTPRequest(i2p::http::HTTPReq & req)
{
/* drop common headers */
req.RemoveHeader("Referrer");
req.RemoveHeader("Via");
req.RemoveHeader("From");
req.RemoveHeader("Forwarded");
req.RemoveHeader("Accept", "Accept-Encoding"); // Accept*, but Accept-Encoding
/* drop proxy-disclosing headers */
req.RemoveHeader("X-Forwarded");
req.RemoveHeader("Proxy-"); // Proxy-*
req.RemoveHeader("Proxy-"); // Proxy-*
/* replace headers */
req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)");
/**
* according to i2p ticket #1862:
* leave Referrer if requested URL with same schema, host and port,
* otherwise, drop it.
*/
if(req.GetHeader("Referrer") != "") {
i2p::http::URL reqURL; reqURL.parse(req.uri);
i2p::http::URL refURL; refURL.parse(req.GetHeader("Referrer"));
if(!boost::iequals(reqURL.schema, refURL.schema) || !boost::iequals(reqURL.host, refURL.host) || reqURL.port != refURL.port)
req.RemoveHeader("Referrer");
}
/* add headers */
/* close connection, if not Connection: (U|u)pgrade (for websocket) */
auto h = req.GetHeader ("Connection");

View File

@@ -239,6 +239,7 @@ namespace client
char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred);
if (eol)
{
if (eol > m_Buffer && eol[-1] == '\r') eol--;
*eol = 0;
char * separator = strchr (m_Buffer, ' ');
if (separator)
@@ -259,7 +260,7 @@ namespace client
ProcessDestGenerate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP))
ProcessNamingLookup (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND))
else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND) || !strcmp (m_Buffer, SAM_RAW_SEND))
{
size_t len = bytes_transferred - (separator - m_Buffer) - 1;
size_t processed = ProcessDatagramSend (separator + 1, len, eol + 1);
@@ -337,8 +338,20 @@ namespace client
return;
}
SAMSessionType type = eSAMSessionTypeUnknown;
if (style == SAM_VALUE_STREAM) type = eSAMSessionTypeStream;
else if (style == SAM_VALUE_DATAGRAM) type = eSAMSessionTypeDatagram;
else if (style == SAM_VALUE_RAW) type = eSAMSessionTypeRaw;
if (type == eSAMSessionTypeUnknown)
{
// unknown style
SendI2PError("Unknown STYLE");
return;
}
std::shared_ptr<boost::asio::ip::udp::endpoint> forward = nullptr;
if (style == SAM_VALUE_DATAGRAM && params.find(SAM_VALUE_HOST) != params.end() && params.find(SAM_VALUE_PORT) != params.end())
if ((type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) &&
params.find(SAM_VALUE_HOST) != params.end() && params.find(SAM_VALUE_PORT) != params.end())
{
// udp forward selected
boost::system::error_code e;
@@ -379,16 +392,20 @@ namespace client
}
// create destination
auto session = m_Owner.CreateSession (id, destination == SAM_VALUE_TRANSIENT ? "" : destination, &params);
auto session = m_Owner.CreateSession (id, type, destination == SAM_VALUE_TRANSIENT ? "" : destination, &params);
if (session)
{
m_SocketType = eSAMSocketTypeSession;
if (style == SAM_VALUE_DATAGRAM)
if (type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw)
{
session->UDPEndpoint = forward;
auto dest = session->localDestination->CreateDatagramDestination ();
dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
if (type == eSAMSessionTypeDatagram)
dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
else // raw
dest->SetRawReceiver (std::bind (&SAMSocket::HandleI2PRawDatagramReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
}
if (session->localDestination->IsReady ())
@@ -550,7 +567,10 @@ namespace client
{
i2p::data::IdentityEx dest;
dest.FromBase64 (params[SAM_PARAM_DESTINATION]);
d->SendDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ());
if (session->Type == eSAMSessionTypeDatagram)
d->SendDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ());
else // raw
d->SendRawDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ());
}
else
LogPrint (eLogError, "SAM: missing datagram destination");
@@ -926,16 +946,44 @@ namespace client
}
}
void SAMSocket::HandleI2PRawDatagramReceive (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
{
LogPrint (eLogDebug, "SAM: raw datagram received ", len);
auto session = m_Owner.FindSession(m_ID);
if(session)
{
auto ep = session->UDPEndpoint;
if (ep)
// udp forward enabled
m_Owner.SendTo(buf, len, ep);
else
{
#ifdef _MSC_VER
size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len);
#else
size_t l = snprintf ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len);
#endif
if (len < SAM_SOCKET_BUFFER_SIZE - l)
{
memcpy (m_StreamBuffer + l, buf, len);
WriteI2PData(len + l);
}
else
LogPrint (eLogWarning, "SAM: received raw datagram size ", len," exceeds buffer");
}
}
}
void SAMSocket::HandleStreamSend(const boost::system::error_code & ec)
{
m_Owner.GetService ().post (std::bind( !ec ? &SAMSocket::Receive : &SAMSocket::TerminateClose, shared_from_this()));
}
SAMSession::SAMSession (SAMBridge & parent, const std::string & id, std::shared_ptr<ClientDestination> dest):
SAMSession::SAMSession (SAMBridge & parent, const std::string & id, SAMSessionType type, std::shared_ptr<ClientDestination> dest):
m_Bridge(parent),
localDestination (dest),
UDPEndpoint(nullptr),
Name(id)
Name(id), Type (type)
{
}
@@ -964,7 +1012,8 @@ namespace client
{"ECDSA_SHA256_P521", i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521},
{"EdDSA_SHA512_Ed25519", i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519},
{"GOST_GOSTR3411256_GOSTR3410CRYPTOPROA", i2p::data::SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256},
{"GOST_GOSTR3411512_GOSTR3410TC26A512", i2p::data::SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512}
{"GOST_GOSTR3411512_GOSTR3410TC26A512", i2p::data::SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512},
{"RedDSA_SHA512_Ed25519", i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519},
}
{
}
@@ -1061,8 +1110,8 @@ namespace client
Accept ();
}
std::shared_ptr<SAMSession> SAMBridge::CreateSession (const std::string& id, const std::string& destination,
const std::map<std::string, std::string> * params)
std::shared_ptr<SAMSession> SAMBridge::CreateSession (const std::string& id, SAMSessionType type,
const std::string& destination, const std::map<std::string, std::string> * params)
{
std::shared_ptr<ClientDestination> localDestination = nullptr;
if (destination != "")
@@ -1102,7 +1151,7 @@ namespace client
if (localDestination)
{
localDestination->Acquire ();
auto session = std::make_shared<SAMSession>(*this, id, localDestination);
auto session = std::make_shared<SAMSession>(*this, id, type, localDestination);
std::unique_lock<std::mutex> l(m_SessionsMutex);
auto ret = m_Sessions.insert (std::make_pair(id, session));
if (!ret.second)
@@ -1193,8 +1242,12 @@ namespace client
{
i2p::data::IdentityEx dest;
dest.FromBase64 (destination);
session->localDestination->GetDatagramDestination ()->
SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ());
if (session->Type == eSAMSessionTypeDatagram)
session->localDestination->GetDatagramDestination ()->
SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ());
else // raw
session->localDestination->GetDatagramDestination ()->
SendRawDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ());
}
else
LogPrint (eLogError, "SAM: Session ", sessionID, " not found");

View File

@@ -40,12 +40,14 @@ namespace client
const char SAM_STREAM_STATUS_I2P_ERROR[] = "STREAM STATUS RESULT=I2P_ERROR\n";
const char SAM_STREAM_ACCEPT[] = "STREAM ACCEPT";
const char SAM_DATAGRAM_SEND[] = "DATAGRAM SEND";
const char SAM_RAW_SEND[] = "RAW SEND";
const char SAM_DEST_GENERATE[] = "DEST GENERATE";
const char SAM_DEST_REPLY[] = "DEST REPLY PUB=%s PRIV=%s\n";
const char SAM_DEST_REPLY_I2P_ERROR[] = "DEST REPLY RESULT=I2P_ERROR\n";
const char SAM_NAMING_LOOKUP[] = "NAMING LOOKUP";
const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=ME VALUE=%s\n";
const char SAM_DATAGRAM_RECEIVED[] = "DATAGRAM RECEIVED DESTINATION=%s SIZE=%lu\n";
const char SAM_RAW_RECEIVED[] = "RAW RECEIVED SIZE=%lu\n";
const char SAM_NAMING_REPLY_INVALID_KEY[] = "NAMING REPLY RESULT=INVALID_KEY NAME=%s\n";
const char SAM_NAMING_REPLY_KEY_NOT_FOUND[] = "NAMING REPLY RESULT=KEY_NOT_FOUND NAME=%s\n";
const char SAM_PARAM_MIN[] = "MIN";
@@ -111,6 +113,7 @@ namespace client
void HandleI2PAccept (std::shared_ptr<i2p::stream::Stream> stream);
void HandleWriteI2PData (const boost::system::error_code& ecode, size_t sz);
void HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
void HandleI2PRawDatagramReceive (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
void ProcessSessionCreate (char * buf, size_t len);
void ProcessStreamConnect (char * buf, size_t len, size_t rem);
@@ -149,14 +152,23 @@ namespace client
std::shared_ptr<i2p::stream::Stream> m_Stream;
};
enum SAMSessionType
{
eSAMSessionTypeUnknown,
eSAMSessionTypeStream,
eSAMSessionTypeDatagram,
eSAMSessionTypeRaw
};
struct SAMSession
{
SAMBridge & m_Bridge;
std::shared_ptr<ClientDestination> localDestination;
std::shared_ptr<boost::asio::ip::udp::endpoint> UDPEndpoint;
std::string Name;
SAMSessionType Type;
SAMSession (SAMBridge & parent, const std::string & name, std::shared_ptr<ClientDestination> dest);
SAMSession (SAMBridge & parent, const std::string & name, SAMSessionType type, std::shared_ptr<ClientDestination> dest);
~SAMSession ();
void CloseStreams ();
@@ -173,7 +185,7 @@ namespace client
void Stop ();
boost::asio::io_service& GetService () { return m_Service; };
std::shared_ptr<SAMSession> CreateSession (const std::string& id, const std::string& destination, // empty string means transient
std::shared_ptr<SAMSession> CreateSession (const std::string& id, SAMSessionType type, const std::string& destination, // empty string means transient
const std::map<std::string, std::string> * params);
void CloseSession (const std::string& id);
std::shared_ptr<SAMSession> FindSession (const std::string& id) const;

View File

@@ -81,11 +81,14 @@ namespace client
void CreateStreamTo(const std::string & addr, int port, StreamConnectFunc complete)
{
auto & addressbook = i2p::client::context.GetAddressBook();
i2p::data::IdentHash ident;
if(addressbook.GetIdentHash(addr, ident)) {
// address found
m_Dest->CreateStream(complete, ident, port);
} else {
auto a = addressbook.GetAddress (addr);
if (a && a->IsIdentHash ())
{
// address found
m_Dest->CreateStream(complete, a->identHash, port);
}
else
{
// not found
complete(nullptr);
}
@@ -443,12 +446,12 @@ namespace client
addr = line.substr(0, itr);
port = std::atoi(line.substr(itr+1).c_str());
}
i2p::data::IdentHash ident;
if(addressbook.GetIdentHash(addr, ident))
auto a = addressbook.GetAddress (addr);
if (a && a->IsIdentHash ())
{
const char * data = payload.c_str() + idx + 1;
size_t len = payload.size() - (1 + line.size());
m_Datagram->SendDatagramTo((const uint8_t*)data, len, ident, m_RemotePort, port);
m_Datagram->SendDatagramTo((const uint8_t*)data, len, a->identHash, m_RemotePort, port);
}
} else {
// wtf?

View File

@@ -6,4 +6,6 @@ i2pd_qt
Makefile*
*.stash
object_script.*
i2pd_qt_plugin_import.cpp
i2pd_qt_plugin_import.cpp
i2pd_qt.pro.autosave*

View File

@@ -11,6 +11,8 @@
#include <QMutexLocker>
#include <QThread>
//#define DEBUG_WITH_DEFAULT_LOGGING (1)
namespace i2p
{
namespace qt
@@ -151,10 +153,16 @@ namespace qt
int result;
{
std::shared_ptr<std::iostream> logstreamptr=std::make_shared<std::stringstream>();
std::shared_ptr<std::iostream> logstreamptr=
#ifdef DEBUG_WITH_DEFAULT_LOGGING
nullptr
#else
std::make_shared<std::stringstream>()
#endif
;
//TODO move daemon init deinit to a bg thread
DaemonQTImpl daemon;
(*logstreamptr) << "Initialising the daemon..." << std::endl;
if(logstreamptr) (*logstreamptr) << "Initialising the daemon..." << std::endl;
bool daemonInitSuccess = daemon.init(argc, argv, logstreamptr);
if(!daemonInitSuccess)
{

View File

@@ -35,6 +35,9 @@
<translation type="qt" />
<releases>
<release version="2.28.0" date="2019-08-27" />
<release version="2.27.0" date="2019-07-03" />
<release version="2.26.0" date="2019-06-07" />
<release version="2.25.0" date="2019-05-09" />
<release version="2.24.0" date="2019-03-21" />
<release version="2.23.0" date="2019-01-21" />

View File

@@ -4,19 +4,27 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = i2pd_qt
TEMPLATE = app
QMAKE_CXXFLAGS *= -std=c++11 -ggdb
DEFINES += USE_UPNP
QMAKE_CXXFLAGS *= -std=c++11 -Wno-unused-parameter -Wno-maybe-uninitialized
# For now, disable UPnP which currently crashes on Stop() -- https://github.com/PurpleI2P/i2pd/issues/1387
#DEFINES += USE_UPNP
DEFINES -= USE_UPNP
debug: DEFINES += DEBUG_WITH_DEFAULT_LOGGING
SOURCES += DaemonQT.cpp mainwindow.cpp \
../../libi2pd/api.cpp \
../../libi2pd/Base.cpp \
../../libi2pd/Blinding.cpp \
../../libi2pd/BloomFilter.cpp \
../../libi2pd/ChaCha20.cpp \
../../libi2pd/Config.cpp \
../../libi2pd/CPU.cpp \
../../libi2pd/Crypto.cpp \
../../libi2pd/CryptoKey.cpp \
../../libi2pd/CryptoKey.cpp \
../../libi2pd/Datagram.cpp \
../../libi2pd/Destination.cpp \
../../libi2pd/Ed25519.cpp \
../../libi2pd/Event.cpp \
../../libi2pd/Family.cpp \
../../libi2pd/FS.cpp \
@@ -31,7 +39,9 @@ SOURCES += DaemonQT.cpp mainwindow.cpp \
../../libi2pd/Log.cpp \
../../libi2pd/NetDb.cpp \
../../libi2pd/NetDbRequests.cpp \
../../libi2pd/NTCP2.cpp \
../../libi2pd/NTCPSession.cpp \
../../libi2pd/Poly1305.cpp \
../../libi2pd/Profiling.cpp \
../../libi2pd/Reseed.cpp \
../../libi2pd/RouterContext.cpp \
@@ -49,9 +59,6 @@ SOURCES += DaemonQT.cpp mainwindow.cpp \
../../libi2pd/TunnelGateway.cpp \
../../libi2pd/TunnelPool.cpp \
../../libi2pd/util.cpp \
../../libi2pd/Ed25519.cpp \
../../libi2pd/Chacha20.cpp \
../../libi2pd/Poly1305.cpp \
../../libi2pd_client/AddressBook.cpp \
../../libi2pd_client/BOB.cpp \
../../libi2pd_client/ClientContext.cpp \
@@ -64,42 +71,37 @@ SOURCES += DaemonQT.cpp mainwindow.cpp \
../../libi2pd_client/SOCKS.cpp \
../../libi2pd_client/Websocket.cpp \
../../libi2pd_client/WebSocks.cpp \
../../daemon/Daemon.cpp \
../../daemon/HTTPServer.cpp \
../../daemon/I2PControl.cpp \
../../daemon/i2pd.cpp \
../../daemon/UPnP.cpp \
ClientTunnelPane.cpp \
MainWindowItems.cpp \
ServerTunnelPane.cpp \
SignatureTypeComboboxFactory.cpp \
TunnelConfig.cpp \
TunnelPane.cpp \
../../daemon/Daemon.cpp \
../../daemon/HTTPServer.cpp \
../../daemon/i2pd.cpp \
../../daemon/I2PControl.cpp \
../../daemon/UnixDaemon.cpp \
../../daemon/UPnP.cpp \
textbrowsertweaked1.cpp \
pagewithbackbutton.cpp \
widgetlock.cpp \
widgetlockregistry.cpp \
logviewermanager.cpp \
../../libi2pd/NTCP2.cpp
#qt creator does not handle this well
#SOURCES += $$files(../../libi2pd/*.cpp)
#SOURCES += $$files(../../libi2pd_client/*.cpp)
#SOURCES += $$files(../../daemon/*.cpp)
#SOURCES += $$files(./*.cpp)
SOURCES -= ../../daemon/UnixDaemon.cpp
logviewermanager.cpp
HEADERS += DaemonQT.h mainwindow.h \
../../libi2pd/api.h \
../../libi2pd/Base.h \
../../libi2pd/Blinding.h \
../../libi2pd/BloomFilter.h \
../../libi2pd/ChaCha20.h \
../../libi2pd/Config.h \
../../libi2pd/CPU.h \
../../libi2pd/Crypto.h \
../../libi2pd/CryptoKey.h \
../../libi2pd/CryptoKey.h \
../../libi2pd/CryptoWorker.h \
../../libi2pd/Datagram.h \
../../libi2pd/Destination.h \
../../libi2pd/Ed25519.h \
../../libi2pd/Event.h \
../../libi2pd/Family.h \
../../libi2pd/FS.h \
@@ -115,13 +117,16 @@ HEADERS += DaemonQT.h mainwindow.h \
../../libi2pd/Log.h \
../../libi2pd/NetDb.hpp \
../../libi2pd/NetDbRequests.h \
../../libi2pd/NTCP2.h \
../../libi2pd/NTCPSession.h \
../../libi2pd/Poly1305.h \
../../libi2pd/Profiling.h \
../../libi2pd/Queue.h \
../../libi2pd/Reseed.h \
../../libi2pd/RouterContext.h \
../../libi2pd/RouterInfo.h \
../../libi2pd/Signature.h \
../../libi2pd/Siphash.h \
../../libi2pd/SSU.h \
../../libi2pd/SSUData.h \
../../libi2pd/SSUSession.h \
@@ -151,6 +156,10 @@ HEADERS += DaemonQT.h mainwindow.h \
../../libi2pd_client/SOCKS.h \
../../libi2pd_client/Websocket.h \
../../libi2pd_client/WebSocks.h \
../../daemon/Daemon.h \
../../daemon/HTTPServer.h \
../../daemon/I2PControl.h \
../../daemon/UPnP.h \
ClientTunnelPane.h \
MainWindowItems.h \
ServerTunnelPane.h \
@@ -158,16 +167,11 @@ HEADERS += DaemonQT.h mainwindow.h \
TunnelConfig.h \
TunnelPane.h \
TunnelsPageUpdateListener.h \
../../daemon/Daemon.h \
../../daemon/HTTPServer.h \
../../daemon/I2PControl.h \
../../daemon/UPnP.h \
textbrowsertweaked1.h \
pagewithbackbutton.h \
widgetlock.h \
widgetlockregistry.h \
i2pd.rc \
i2pd.rc \
logviewermanager.h
INCLUDEPATH += ../../libi2pd

View File

@@ -18,7 +18,7 @@ namespace logviewer {
QString Worker::pollAndShootATimerForInfiniteRetries() {
std::shared_ptr<std::iostream> logStream=logViewerManager.getLogStream();
assert(logStream!=nullptr);
if(!logStream)return "";
std::streamsize MAX_SZ=64*1024;
char*buf=(char*)malloc(MAX_SZ*sizeof(char));
if(buf==nullptr)return "";

View File

@@ -1,6 +1,6 @@
CXXFLAGS += -Wall -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -I../libi2pd/ -pthread -Wl,--unresolved-symbols=ignore-in-object-files
TESTS = test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305
TESTS = test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding
all: $(TESTS) run
@@ -22,6 +22,9 @@ test-x25519: ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp
test-aeadchacha20poly1305: ../libi2pd/Crypto.cpp ../libi2pd/ChaCha20.cpp ../libi2pd/Poly1305.cpp test-aeadchacha20poly1305.cpp
$(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system
test-blinding: ../libi2pd/Crypto.cpp ../libi2pd/Blinding.cpp ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp ../libi2pd/util.cpp ../libi2pd/Identity.cpp ../libi2pd/Signature.cpp ../libi2pd/Timestamp.cpp test-blinding.cpp
$(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system
run: $(TESTS)
@for TEST in $(TESTS); do ./$$TEST ; done

43
tests/test-blinding.cpp Normal file
View File

@@ -0,0 +1,43 @@
#include <cassert>
#include <memory>
#include <string.h>
#include "Blinding.h"
#include "Identity.h"
#include "Timestamp.h"
using namespace i2p::data;
using namespace i2p::util;
using namespace i2p::crypto;
void BlindTest (SigningKeyType sigType)
{
auto keys = PrivateKeys::CreateRandomKeys (sigType);
BlindedPublicKey blindedKey (keys.GetPublic ());
auto timestamp = GetSecondsSinceEpoch ();
char date[9];
GetDateString (timestamp, date);
uint8_t blindedPriv[64], blindedPub[128];
auto publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub);
uint8_t blindedPub1[128];
blindedKey.GetBlindedKey (date, blindedPub1);
// check if public key produced from private blinded key matches blided public key
assert (!memcmp (blindedPub, blindedPub1, publicKeyLen));
// try to sign and verify
std::unique_ptr<Signer> blindedSigner (PrivateKeys::CreateSigner (sigType, blindedPriv));
uint8_t buf[100], signature[128];
memset (buf, 1, 100);
blindedSigner->Sign (buf, 100, signature);
std::unique_ptr<Verifier> blindedVerifier (IdentityEx::CreateVerifier (sigType));
blindedVerifier->SetPublicKey (blindedPub1);
assert (blindedVerifier->Verify (buf, 100, signature));
}
int main ()
{
// RedDSA test
BlindTest (SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519);
// P256 test
BlindTest (SIGNING_KEY_TYPE_ECDSA_SHA256_P256);
// P384 test
BlindTest (SIGNING_KEY_TYPE_ECDSA_SHA384_P384);
}

View File

@@ -21,12 +21,14 @@ int main ()
i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410TC26A512, priv, pub);
i2p::crypto::GOSTR3410_512_Signer signer (i2p::crypto::eGOSTR3410TC26A512, priv);
signer.Sign (example2, 72, signature);
i2p::crypto::GOSTR3410_512_Verifier verifier (i2p::crypto::eGOSTR3410TC26A512, pub);
i2p::crypto::GOSTR3410_512_Verifier verifier (i2p::crypto::eGOSTR3410TC26A512);
verifier.SetPublicKey (pub);
assert (verifier.Verify (example2, 72, signature));
i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410CryptoProA, priv, pub);
i2p::crypto::GOSTR3410_256_Signer signer1 (i2p::crypto::eGOSTR3410CryptoProA, priv);
signer1.Sign (example2, 72, signature);
i2p::crypto::GOSTR3410_256_Verifier verifier1 (i2p::crypto::eGOSTR3410CryptoProA, pub);
i2p::crypto::GOSTR3410_256_Verifier verifier1 (i2p::crypto::eGOSTR3410CryptoProA);
verifier1.SetPublicKey (pub);
assert (verifier1.Verify (example2, 72, signature));
}