network_clipboard 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8563a699ffa840eaa49ab7029b1c69dc5895b986
4
+ data.tar.gz: 37be775586fe6006e9d965e0b78860b69d34077a
5
+ SHA512:
6
+ metadata.gz: ee2e998ccc19888dacf81e883cdb21f5057bfc61c90a0ad1c7e157dd94cd3695045fa323d4d07d2018f617ff681994b40d08b40ec4fcadf07fd5b010ab5a60ab
7
+ data.tar.gz: d15b51f1788896d289ef0bbd5dfa038936a7da4a23871bc517b4850486987df44af070da79e30a3b18a0926f63ba3acca8a17c653a386e98424bb8b828235400
checksums.yaml.gz.sig ADDED
Binary file
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'network_clipboard'
4
+
5
+ NetworkClipboard::Client.run
@@ -0,0 +1,121 @@
1
+ require_relative 'config'
2
+ require_relative 'discovery'
3
+ require_relative 'connection'
4
+
5
+ require 'clipboard'
6
+ require 'socket'
7
+ require 'logger'
8
+
9
+ module NetworkClipboard
10
+ class Client
11
+ LOGGER = Logger.new(STDOUT)
12
+ LOGGER.level = Logger::WARN
13
+
14
+ attr_writer :running
15
+
16
+ def self.run
17
+ c = Client.new
18
+ Signal.trap('INT'){c.running = false}
19
+ c.loop
20
+ end
21
+
22
+ def initialize
23
+ @config = Config.new
24
+ @discovery = Discovery.new(@config)
25
+ @tcp_server = TCPServer.new(@config.port)
26
+ @connections = {}
27
+ @running = true
28
+ end
29
+
30
+ def loop
31
+ while @running
32
+ [:announce,
33
+ :discover,
34
+ :watch_incoming,
35
+ :fetch_clipboard,
36
+ :find_incoming,
37
+ :wait,
38
+ ].each do |action|
39
+ send(action) if @running
40
+ end
41
+ end
42
+ end
43
+
44
+ def fetch_clipboard
45
+ update_clipboard(Clipboard.paste)
46
+ end
47
+
48
+ def update_clipboard(new_value)
49
+ @new_value,@last_value = new_value,@new_value
50
+ if @new_value != @last_value
51
+ @connections.values.each{|c| send_new_clipboard(c)}
52
+ end
53
+ end
54
+
55
+ def send_new_clipboard(connection)
56
+ connection.send(@new_value)
57
+ end
58
+
59
+ def watch_incoming
60
+ @connections.values.each{|c| receive_clipboard(c)}
61
+ end
62
+
63
+ def receive_clipboard(connection)
64
+ inbound = connection.receive(false)
65
+ Clipboard.copy(inbound) if inbound
66
+ end
67
+
68
+ def announce
69
+ @discovery.announce
70
+ end
71
+
72
+ def discover
73
+ @discovery.get_peer_announcements do |remote_client_id,address|
74
+ next if @connections[remote_client_id] or remote_client_id < @config.client_id
75
+ LOGGER.info("New Peer -> #{remote_client_id}")
76
+
77
+ aes_connection = AESConnection.new(@config,TCPSocket.new(address,@config.port))
78
+
79
+ if aes_connection.remote_client_id != remote_client_id
80
+ LOGGER.error("Client Id #{aes_connection.remote_client_id} doesn't match original value #{remote_client_id}")
81
+ aes_connection.close
82
+ next
83
+ end
84
+
85
+ if @connections[aes_connection.remote_client_id]
86
+ LOGGER.error("Duplicate connections #{aes_connection} and #{@connections[aes_connection.remote_client_id]}")
87
+ aes_connection.close
88
+ next
89
+ end
90
+
91
+ @connections[aes_connection.remote_client_id] = aes_connection
92
+ end
93
+ end
94
+
95
+ def find_incoming
96
+ while true
97
+ begin
98
+ incoming = @tcp_server.accept_nonblock
99
+ rescue IO::WaitReadable
100
+ return
101
+ end
102
+ aes_connection = AESConnection.new(@config,incoming)
103
+ LOGGER.info("New Peer <- #{aes_connection.remote_client_id}")
104
+
105
+ if @connections[aes_connection.remote_client_id]
106
+ aes_connection.close
107
+ next
108
+ end
109
+
110
+ @connections[aes_connection.remote_client_id] = aes_connection
111
+ end
112
+ end
113
+
114
+ def wait
115
+ return IO.select([
116
+ @discovery.receive_socket,
117
+ @tcp_server,
118
+ ] + @connections.values.collect(&:socket), [], [], 5)
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,41 @@
1
+ require 'yaml'
2
+ require 'securerandom'
3
+
4
+ module NetworkClipboard
5
+ class Config
6
+ DEFAULTS = {
7
+ # http://tools.ietf.org/html/rfc2365
8
+ # Ultimately though, we'd need to reserve a specific address
9
+ # or implemend MADCAP or ZMAAP (Except nothing supports those).
10
+ multicast_ip: '239.255.193.172',
11
+ # Randomly picked by mashing the keyboard.
12
+ port: 53712,
13
+ # This file needs to be shared to enable clipboard transfer.
14
+ secret_file: '~/.networkclipboard.secret',
15
+ }
16
+
17
+ attr_reader :multicast_ip, :port, :secret, :client_id
18
+
19
+ def initialize(filename='~/.networkclipboard.conf')
20
+ filename = File.expand_path(filename)
21
+ begin
22
+ parsed = YAML.load(File.read(filename))
23
+ rescue Errno::ENOENT
24
+ parsed = {}
25
+ end
26
+ config = DEFAULTS.merge(parsed)
27
+
28
+ secret_filename = File.expand_path(config[:secret_file])
29
+ begin
30
+ @secret = [File.read(secret_filename)].pack('H*')
31
+ rescue Errno::ENOENT
32
+ @secret = SecureRandom.random_bytes(32)
33
+ File.open(secret_filename,'w',0400){|f|f.write(secret.unpack('H*')[0])}
34
+ end
35
+
36
+ @multicast_ip = config[:multicast_ip]
37
+ @port = config[:port]
38
+ @client_id = SecureRandom.uuid.gsub('-','')
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,80 @@
1
+ require 'socket'
2
+ require 'openssl'
3
+
4
+ module NetworkClipboard
5
+ HANDSHAKE_STRING = "NetworkClipboard Handshake"
6
+
7
+ class HandshakeException < Exception
8
+ end
9
+
10
+ class AESConnection
11
+
12
+ attr_reader :remote_client_id, :socket
13
+
14
+ def initialize(config,socket)
15
+ @socket = socket
16
+
17
+ @encryptor = OpenSSL::Cipher::AES.new(128, :CBC)
18
+ @encryptor.encrypt
19
+ @encryptor.key = config.secret
20
+
21
+ @decryptor = OpenSSL::Cipher::AES.new(128, :CBC)
22
+ @decryptor.decrypt
23
+ @decryptor.key = config.secret
24
+
25
+ handshake(config.client_id)
26
+ end
27
+
28
+ def handshake(client_id)
29
+ iv = @encryptor.random_iv
30
+ @socket.send([iv.size].pack('N'),0)
31
+ @socket.send(iv,0)
32
+ iv_size = @socket.recv(4).unpack('N')[0]
33
+ @decryptor.iv = @socket.recv(iv_size)
34
+
35
+ # Verify it all worked.
36
+ send(HANDSHAKE_STRING)
37
+ raise HandshakeException unless receive == HANDSHAKE_STRING
38
+
39
+ send(client_id)
40
+ @remote_client_id = receive
41
+ end
42
+
43
+ def send(new_content)
44
+ ciphertext = @encryptor.update(new_content) + @encryptor.final
45
+ @socket.send([ciphertext.size].pack('N'),0)
46
+ @socket.send(ciphertext,0)
47
+ @encryptor.reset
48
+ end
49
+
50
+ def receive(blocking=true)
51
+ begin
52
+ @partial_read ||= ''
53
+ while @partial_read.size < 4
54
+ @partial_read += @socket.recv_nonblock(4 - @partial_read.size)
55
+ end
56
+ while @partial_read.size < (total_size = 4 + @partial_read.unpack('N')[0])
57
+ @partial_read += @socket.recv_nonblock(total_size - @partial_read.size)
58
+ end
59
+ rescue IO::WaitReadable
60
+ if blocking
61
+ IO.select([@socket])
62
+ retry
63
+ else
64
+ return nil
65
+ end
66
+ end
67
+
68
+ ciphertext,@partial_read = @partial_read.slice(4,@partial_read.size-4),nil
69
+
70
+ plaintext = @decryptor.update(ciphertext) + @decryptor.final
71
+ @decryptor.reset
72
+
73
+ return plaintext
74
+ end
75
+
76
+ def close
77
+ @socket.close
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,63 @@
1
+ require 'socket'
2
+ require 'ipaddr'
3
+ require 'digest'
4
+
5
+ module NetworkClipboard
6
+ class Discovery
7
+ attr_reader :receive_socket
8
+
9
+ def initialize(config)
10
+ @config = config
11
+
12
+ @receive_socket = UDPSocket.new()
13
+ @receive_socket.setsockopt(:IPPROTO_IP, :IP_ADD_MEMBERSHIP, multicast_addr.hton + bind_addr.hton)
14
+ @receive_socket.bind(bind_addr.to_s,port)
15
+
16
+ @send_socket = UDPSocket.new()
17
+ @send_socket.setsockopt(:IPPROTO_IP, :IP_MULTICAST_TTL, 1)
18
+ @send_socket.connect(multicast_addr.to_s,port)
19
+
20
+ @authenticated_client_id = [
21
+ @config.client_id,
22
+ Digest::HMAC.digest(@config.secret,@config.client_id,Digest::SHA256),
23
+ ].pack("H32A32")
24
+ end
25
+
26
+ def port
27
+ @port ||= @config.port
28
+ end
29
+
30
+ def bind_addr
31
+ @bind_addr ||= IPAddr.new('0.0.0.0')
32
+ end
33
+
34
+ def multicast_addr
35
+ @multicast_addr ||= IPAddr.new(@config.multicast_ip)
36
+ end
37
+
38
+ def announce
39
+ @send_socket.send(@authenticated_client_id,0)
40
+ end
41
+
42
+ def get_peer_announcements
43
+ return enum_for(__method__) if !block_given?
44
+ begin
45
+ while true
46
+ msg,ip = @receive_socket.recvfrom_nonblock(65536)
47
+ other_client_id,other_digest = msg.unpack('H32A32')
48
+
49
+ next if other_client_id == @config.client_id
50
+
51
+ # We could do a constant time string compare, but an attacker can
52
+ # just listen to announces and rebroadcast them as his own anyway.
53
+ # This is just to skip other honest clients with different secrets
54
+ # on the network, to avoid wasting time on a handshake.
55
+ next unless other_digest == Digest::HMAC.digest(@config.secret,other_client_id,Digest::SHA256)
56
+
57
+ yield [other_client_id,ip[2]]
58
+ end
59
+ rescue IO::WaitReadable
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1 @@
1
+ require_relative 'network_clipboard/client'
data.tar.gz.sig ADDED
Binary file
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: network_clipboard
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Christophe Biocca
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIDljCCAn6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBIMRowGAYDVQQDDBFjaHJp
14
+ c3RvcGhlLmJpb2NjYTEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPy
15
+ LGQBGRYDY29tMB4XDTE0MDkyMjA0MTAwMloXDTE1MDkyMjA0MTAwMlowSDEaMBgG
16
+ A1UEAwwRY2hyaXN0b3BoZS5iaW9jY2ExFTATBgoJkiaJk/IsZAEZFgVnbWFpbDET
17
+ MBEGCgmSJomT8ixkARkWA2NvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
18
+ ggEBAMO5U6bV+OsGcszf0Y5inNKNrJlnxaRJ+mOnk3OYlzqf0Ylpp9v65SNNxntA
19
+ NY75ZKTzBUkF5o8FkiXJ+eueKqyaDmqjuyFavfihQ/1e1MXnn76EI7JYZW3Mkjox
20
+ r6d9BIlY4rv55llkPBut11WZ0AoCSku3eTLB/Y4TgYpiQvsSjmHO28nTuiUmcbmm
21
+ PR7/EP000pbphXXo/GSLOZ+jy4wc09jsN/1AFB6TErvh3JFh1bW6NAy7vd/JyVbH
22
+ TLZ59kOD3ThhW1ZS+K/n/uYtFNzQRvRb9X/9yfifPAKzJpPiueJnofCBJ0VyPQ3j
23
+ dxoWi/m3N1Z9VS7/FSJ1g6gn+tECAwEAAaOBijCBhzAJBgNVHRMEAjAAMAsGA1Ud
24
+ DwQEAwIEsDAdBgNVHQ4EFgQUgihGjqqGQaGMoqaiqV2b0F0BBvEwJgYDVR0RBB8w
25
+ HYEbY2hyaXN0b3BoZS5iaW9jY2FAZ21haWwuY29tMCYGA1UdEgQfMB2BG2Nocmlz
26
+ dG9waGUuYmlvY2NhQGdtYWlsLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAgOeBPrTF
27
+ lvsQZVb+mqAI9WdF+nzRtjEUm7G1fGGWdOwT/DQO9g1jZ25R0/TNDax2tVupcRFE
28
+ OXvcOxJ1DkxJVe85HOybQsPucOCta4/hvdbA15uRKA5Jn3scM1RUfGxgwGHQLXjb
29
+ ntT9ySByz4fkLUp+NGYVL+VTob4XOlqCoJOrXkWlwkEKXtLXYvRL7/WpTb6qaIOc
30
+ L0Swr8sZae6mM5w1yyW/EpxG0JR90oqvw83ObXBmQVgffPLwJWHe4Ioysg7/UhY+
31
+ jeq3OvJDSi2MARCkZcHB7bVpVXSw+nwsP3J4RPs3JFn3m6utnIpc9lV+lt3XcR+9
32
+ o5+PMiZrAMX0bA==
33
+ -----END CERTIFICATE-----
34
+ date: 2014-05-21 00:00:00.000000000 Z
35
+ dependencies:
36
+ - !ruby/object:Gem::Dependency
37
+ name: clipboard
38
+ requirement: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '1.0'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '1.0'
50
+ description: |
51
+ Allows sharing of clipboard between multiple machines on the same network.
52
+ Encrypted using AES-128-CBC, relies on pre-shared secret file.
53
+ Internal API not stable yet. Only use the executable.
54
+ email: christophe.biocca@gmail.com
55
+ executables:
56
+ - network_clipboard
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - bin/network_clipboard
61
+ - lib/network_clipboard.rb
62
+ - lib/network_clipboard/client.rb
63
+ - lib/network_clipboard/config.rb
64
+ - lib/network_clipboard/connection.rb
65
+ - lib/network_clipboard/discovery.rb
66
+ homepage: http://github.com/christophebiocca/network_clipboard
67
+ licenses:
68
+ - Apache-2.0
69
+ metadata: {}
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 2.2.2
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: Network Clipboard Sharing
90
+ test_files: []
metadata.gz.sig ADDED
Binary file