ruby / ruby/net-imap

Missing timeout for the CAPABILITY command

Open
#278 8 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Ruby
Stars
118
Forks
43
Avg merge
1d 23h
Merged PRs (30d)
26

Description

Version: net-imap (0.4.8)

Ruby: 3.2.2

Description

I'm using net-imap for fetching emails in some background jobs. There are rare cases where the email provider Microsoft Exchange returns no or an unexpected answer for the CAPABILITY command. This blocks my background jobs for a long time, sometimes more than 30 minutes. At some point of time a Errno::ECONNRESET (Connection reset by peer) exception is thrown, I assume when Microsoft Exchange closes the connection.

Backtrace

/home/foo/.rbenv/versions/3.2.2/lib/ruby/3.2.0/monitor.rb:108:in `sleep'
/home/foo/.rbenv/versions/3.2.2/lib/ruby/3.2.0/monitor.rb:108:in `wait'
/home/foo/.rbenv/versions/3.2.2/lib/ruby/3.2.0/monitor.rb:108:in `wait_for_cond'
/home/foo/.rbenv/versions/3.2.2/lib/ruby/3.2.0/monitor.rb:108:in `wait'
/var/www/foo/shared/bundle/ruby/3.2.0/gems/net-imap-0.4.8/lib/net/imap.rb:2701:in `get_tagged_response'
/var/www/foo/shared/bundle/ruby/3.2.0/gems/net-imap-0.4.8/lib/net/imap.rb:2794:in `block in send_command'
/home/foo/.rbenv/versions/3.2.2/lib/ruby/3.2.0/monitor.rb:202:in `synchronize'
/home/foo/.rbenv/versions/3.2.2/lib/ruby/3.2.0/monitor.rb:202:in `mon_synchronize'
/var/www/foo/shared/bundle/ruby/3.2.0/gems/net-imap-0.4.8/lib/net/imap.rb:2776:in `send_command'
/var/www/foo/shared/bundle/ruby/3.2.0/gems/net-imap-0.4.8/lib/net/imap.rb:1077:in `block in capability'
/home/foo/.rbenv/versions/3.2.2/lib/ruby/3.2.0/monitor.rb:202:in `synchronize'
/home/foo/.rbenv/versions/3.2.2/lib/ruby/3.2.0/monitor.rb:202:in `mon_synchronize'
/var/www/foo/shared/bundle/ruby/3.2.0/gems/net-imap-0.4.8/lib/net/imap.rb:1076:in `capability'
/var/www/foo/shared/bundle/ruby/3.2.0/gems/net-imap-0.4.8/lib/net/imap.rb:987:in `capabilities'
/var/www/foo/shared/bundle/ruby/3.2.0/gems/net-imap-0.4.8/lib/net/imap.rb:972:in `capable?'
/var/www/foo/shared/bundle/ruby/3.2.0/gems/net-imap-0.4.8/lib/net/imap.rb:1310:in `authenticate'

Feature request

Does it make sense, that one can configure a global timeout or a timeout for individual actions (e.g. https://github.com/ruby/net-imap/blob/v0.4.8/lib/net/imap.rb#L2689) within this gem? Or do you recommend e.g. wrapping gem code into own Timeout blocks?

Example

require 'ostruct'
require 'net/imap'
require 'socket'
require 'active_support/all'

class MailServer
  CRLF = "\r\n".freeze

  def initialize(port)
    @server = TCPServer.new(port)
    puts "Mail server started on port #{port}"
  end

  def start
    loop do
      Thread.start(@server.accept) do |client|
        puts "Connection established with #{client.peeraddr[2]}"

        client.puts "* OK The Microsoft Exchange IMAP4 service is ready.#{CRLF}"

        loop do
          request = client.gets&.chomp
          break if request.blank?

          case request
          when 'RUBY0001 CAPABILITY'
            client.puts "* CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN AUTH=XOAUTH2 SASL-IR UIDPLUS ID UNSELECT CHILDREN IDLE NAMESPACE LITERAL+#{CRLF}"
            client.puts "RUBY0001 OK CAPABILITY completed.#{CRLF}"
          when 'RUBY0002 AUTHENTICATE XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIHRlc3QxMjMBAQ=='
            client.puts "RUBY0002 OK AUTHENTICATE completed.#{CRLF}"
          else
            client.puts "* ERR Unknown command #{request}" + CRLF
          end
        end

        client.close
        puts "Connection closed with #{client.peeraddr[2]}"
      end
    end
  end

  def stop
    @server&.close
    puts 'Mail server stopped'
  end
end

server = MailServer.new(2000)
Thread.new { server.start }
sleep 1

Net::IMAP.new('localhost', port: 2000).authenticate('XOAUTH2', 'user', 'test123')

Dropping the line client.puts "RUBY0001 OK CAPABILITY completed.#{CRLF}" will block the script for unlimited time.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in lib/net/imap.rb at capability, send_command, and get_tagged_response, which appear in the reported backtrace. Reproduce the hanging CAPABILITY exchange with the example server and inspect the surrounding command and response handling. Done means an appropriate configurable timeout is specified and enforced for the affected operation.

Written by the indexing model from the issue text.

Assessment

Tech stack
ruby
Domain
api, networking
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.