Class: Cinch::DCC::Incoming::Send

Inherits:
Object
  • Object
show all
Defined in:
lib/cinch/dcc/incoming/send.rb

Overview

DCC SEND is a protocol for transferring files, usually found in IRC. While the handshake, i.e. the details of the file transfer, are transferred over IRC, the actual file transfer happens directly between two clients. As such it doesn’t put stress on the IRC server.

When someone tries to send a file to the bot, the ‘:dcc_send` event will be triggered, in which the DCC request can be inspected and optionally accepted.

The event handler receives the plain message object as well as an instance of this class. That instance contains information about the suggested file name (in a sanitized way) and allows for checking the origin.

It is advised to reject transfers that seem to originate from a private IP or the local IP itself unless that is expected. Otherwise, specially crafted requests could cause the bot to connect to internal services.

Finally, the file transfer can be accepted and written to any object that implements a ‘#<<` method, which includes File objects as well as plain strings.

Examples:

Saving a transfer to a temporary file

require "tempfile"

listen_to :dcc_send, method: :incoming_dcc
def incoming_dcc(m, dcc)
  if dcc.from_private_ip? || dcc.from_localhost?
    @bot.loggers.debug "Not accepting potentially dangerous file transfer"
    return
  end

  t = Tempfile.new(dcc.filename)
  dcc.accept(t)
  t.close
end

Since:

  • 2.0.0

Constant Summary collapse

PRIVATE_NETS =

Since:

  • 2.0.0

[IPAddr.new("fc00::/7"),
IPAddr.new("10.0.0.0/8"),
IPAddr.new("172.16.0.0/12"),
IPAddr.new("192.168.0.0/16")]
LOCAL_NETS =

Since:

  • 2.0.0

[IPAddr.new("127.0.0.0/8"),
IPAddr.new("::1/128")]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts) ⇒ Send

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of Send.

Parameters:

  • opts (Hash)

Options Hash (opts):

Since:

  • 2.0.0



80
81
82
# File 'lib/cinch/dcc/incoming/send.rb', line 80

def initialize(opts)
  @user, @filename, @size, @ip, @port = opts.values_at(:user, :filename, :size, :ip, :port)
end

Instance Attribute Details

#filenameString (readonly)

Returns The basename of the file name, with (back)slashes removed.

Returns:

  • (String)

    The basename of the file name, with (back)slashes removed.

Since:

  • 2.0.0



50
51
52
# File 'lib/cinch/dcc/incoming/send.rb', line 50

def filename
  @filename
end

#ipString (readonly)

Returns:

Since:

  • 2.0.0



68
69
70
# File 'lib/cinch/dcc/incoming/send.rb', line 68

def ip
  @ip
end

#portFixnum (readonly)

Returns:

  • (Fixnum)

Since:

  • 2.0.0



71
72
73
# File 'lib/cinch/dcc/incoming/send.rb', line 71

def port
  @port
end

#sizeInteger (readonly)

Returns:

  • (Integer)

Since:

  • 2.0.0



65
66
67
# File 'lib/cinch/dcc/incoming/send.rb', line 65

def size
  @size
end

#userUser (readonly)

Returns:

Since:

  • 2.0.0



62
63
64
# File 'lib/cinch/dcc/incoming/send.rb', line 62

def user
  @user
end

Instance Method Details

#accept(io) ⇒ Boolean

Note:

This method blocks.

This method is used for accepting a DCC SEND offer. It expects an object to save the result to (usually an instance of IO or String).

Examples:

Saving to a file

f = File.open("/tmp/foo", "w")
dcc.accept(f)
f.close

Saving to a string

s = ""
dcc.accept(s)

Parameters:

  • io (#<<)

    The object to write the data to.

Returns:

  • (Boolean)

    True if the transfer finished successfully, false otherwise.

Since:

  • 2.0.0



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
# File 'lib/cinch/dcc/incoming/send.rb', line 106

def accept(io)
  socket = TCPSocket.new(@ip, @port)
  total = 0

  while (buf = socket.readpartial(8192))
    total += buf.bytesize

    begin
      socket.write_nonblock [total].pack("N")
    rescue Errno::EWOULDBLOCK, Errno::AGAIN
      # Nobody cares about ACKs, really. And if the sender
      # couldn't receive it at this point, they probably don't
      # care, either.
    end
    io << buf

    # Break here in case the sender doesn't close the
    # connection on the final ACK.
    break if total == @size
  end

  socket.close
  true
rescue EOFError
  false
end

#from_localhost?Boolean

Returns True if the DCC originates from localhost.

Returns:

  • (Boolean)

    True if the DCC originates from localhost

See Also:

Since:

  • 2.0.0



142
143
144
145
# File 'lib/cinch/dcc/incoming/send.rb', line 142

def from_localhost?
  ip = IPAddr.new(@ip)
  LOCAL_NETS.any? { |n| n.include?(ip) }
end

#from_private_ip?Boolean

Returns True if the DCC originates from a private ip.

Returns:

  • (Boolean)

    True if the DCC originates from a private ip

See Also:

Since:

  • 2.0.0



135
136
137
138
# File 'lib/cinch/dcc/incoming/send.rb', line 135

def from_private_ip?
  ip = IPAddr.new(@ip)
  PRIVATE_NETS.any? { |n| n.include?(ip) }
end