summaryrefslogtreecommitdiffhomepage
path: root/Storage/Network.hs
blob: 9739f2f24d8fdb628b30485b9df74f061336a3dd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
{- Copyright 2016 Joey Hess <id@joeyh.name>
 -
 - Licensed under the GNU AGPL version 3 or higher.
 -}

{-# LANGUAGE OverloadedStrings #-}

module Storage.Network (
	Server(..),
	networkServers,
	networkStorage,
	torableManager
) where

import Types
import Types.Storage
import Types.Cost
import Data.List
import Data.Char
import HTTP
import HTTP.Client
import HTTP.ProofOfWork
import Servant.Client
import Network.Wai.Handler.Warp (Port)
import Network.HTTP.Client hiding (port, host)
import Network.HTTP.Client.Internal (Connection, makeConnection)
import Control.Monad.Trans.Except (ExceptT, runExceptT)
import qualified Network.Socket
import Network.Socket.ByteString (sendAll, recv)
import Network.Socks5
import qualified Data.ByteString.UTF8 as BU8

type HostName = String

data Server = Server
	{ serverName :: HostName
	, serverPort :: Port
	}

serverUrl :: Server -> BaseUrl
serverUrl srv = BaseUrl Http (serverName srv) (serverPort srv) ""

-- | These can be either tor .onion addresses, or regular hostnames.
-- Using tor is highly recommended, to avoid correlation attacks.
networkServers :: IO [Server]
networkServers = return 
	[ Server "vzgrspuxbtnlrtup.onion" 4242 -- keysafe.joeyh.name
	, Server "localhost" 4242
	, Server "localhost" 4242
	]

networkStorage :: Server -> Storage
networkStorage server = Storage
	{ storeShare = store server
	, retrieveShare = retrieve server
	, obscureShares = obscure server
	, countShares = count server
	, moveShares = move server
	}

store :: Server -> StorableObjectIdent -> Share -> IO StoreResult
store srv i (Share _n o) = 
	serverRequest srv StoreFailure id i $ \pow ->
		putObject i pow o

retrieve :: Server -> ShareNum -> StorableObjectIdent -> IO RetrieveResult
retrieve srv n i = 
	serverRequest srv RetrieveFailure (RetrieveSuccess . Share n) i $
		getObject i

-- | Servers should automatically obscure, so do nothing.
-- (Could upload chaff.)
obscure :: Server -> IO ObscureResult
obscure _ = return ObscureSuccess

count :: Server -> IO CountResult
count srv = serverRequest srv CountFailure id NoPOWIdent countObjects

-- | Not needed for servers.
move :: Server -> Storage -> IO ()
move _ _ = error "move is not implemented for servers"

serverRequest
	:: POWIdent p
	=> Server
	-> (String -> a)
	-> (r -> a)
	-> p
	-> (Maybe ProofOfWork -> Manager -> BaseUrl -> ExceptT ServantError IO (POWGuarded r))
	-> IO a
serverRequest srv onerr onsuccess p a = go Nothing maxProofOfWork
  where
	go pow (Seconds timeleft)
		| timeleft <= 0 = return $ onerr "server asked for too much proof of work; gave up"
		| otherwise = do
			res <- serverRequest' srv (a pow)
			case res of
				Left err -> return $ onerr err
				Right (Result r) -> return $ onsuccess r
				Right (NeedProofOfWork req) -> go
					(Just $ genProofOfWork req p)
					(Seconds timeleft - generationTime req)

serverRequest'
	:: Server
	-> (Manager -> BaseUrl -> ExceptT ServantError IO r)
	-> IO (Either String r)
serverRequest' srv a = do
	-- A new Manager is allocated for each request, rather than reusing
	-- any connection. This is a feature; it makes correlation attacks
	-- harder because the server can't tell if two connections
	-- accessing different objects came from the same user, except by
	-- comparing IP addresses (which are masked somewhat by using tor).
	manager <- torableManager
	res <- runExceptT $ a manager url
	return $ case res of
		Left err -> Left $ "server failure: " ++ show err
		Right r -> Right r
  where
	url = serverUrl srv

-- | HTTP Manager supporting tor .onion and regular hosts
torableManager :: IO Manager
torableManager = newManager $ defaultManagerSettings
	{ managerRawConnection = return conn
	}
  where
	conn addr host port
		| ".onion" `isSuffixOf` map toLower host = torConnection host port
		| otherwise = do
			regular <- managerRawConnection defaultManagerSettings
			regular addr host port

torConnection :: HostName -> Port -> IO Connection
torConnection onionaddress p = do
	(socket, _) <- socksConnect torsockconf socksaddr
	socketConnection socket 8192
  where
	torsocksport = 9050
	torsockconf = defaultSocksConf "127.0.0.1" torsocksport
	socksdomain = SocksAddrDomainName (BU8.fromString onionaddress)
	socksaddr = SocksAddress socksdomain (fromIntegral p)

socketConnection :: Network.Socket.Socket -> Int -> IO Connection
socketConnection socket chunksize = makeConnection
	(recv socket chunksize)
	(sendAll socket)
	(Network.Socket.close socket)