Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions ext/gpgme/gpgme_n.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ static const rb_data_type_t gpgme_data_type = {
.dsize = NULL,
},
.data = NULL,
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
.flags = 0,
};

static void
Expand All @@ -122,7 +122,7 @@ static const rb_data_type_t gpgme_ctx_type = {
.dsize = NULL,
},
.data = NULL,
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
.flags = 0,
};

static void
Expand All @@ -140,7 +140,7 @@ static const rb_data_type_t gpgme_key_type = {
.dsize = NULL,
},
.data = NULL,
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
.flags = 0,
};

#if defined(GPGME_VERSION_NUMBER) && GPGME_VERSION_NUMBER < 0x020000
Expand All @@ -159,7 +159,7 @@ static const rb_data_type_t gpgme_trust_item_type = {
.dsize = NULL,
},
.data = NULL,
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
.flags = 0,
};
#endif

Expand Down Expand Up @@ -1210,6 +1210,9 @@ rb_s_gpgme_op_keylist_next (VALUE dummy, VALUE vctx, VALUE rkey)
save_gpgme_key_attrs (vkey, key);
rb_ary_store (rkey, 0, vkey);
}

RB_GC_GUARD(vctx);

return LONG2NUM(err);
}

Expand Down Expand Up @@ -2137,6 +2140,11 @@ rb_s_gpgme_op_decrypt (VALUE dummy, VALUE vctx, VALUE vcipher, VALUE vplain)
UNWRAP_GPGME_DATA(vplain, plain);

err = gpgme_op_decrypt (ctx, cipher, plain);

RB_GC_GUARD(vctx);
RB_GC_GUARD(vcipher);
RB_GC_GUARD(vplain);

return LONG2NUM(err);
}

Expand Down Expand Up @@ -2216,6 +2224,12 @@ rb_s_gpgme_op_verify (VALUE dummy, VALUE vctx, VALUE vsig, VALUE vsigned_text,
UNWRAP_GPGME_DATA(vplain, plain);

err = gpgme_op_verify (ctx, sig, signed_text, plain);

RB_GC_GUARD(vctx);
RB_GC_GUARD(vsig);
RB_GC_GUARD(vsigned_text);
RB_GC_GUARD(vplain);

return LONG2NUM(err);
}

Expand Down Expand Up @@ -2320,6 +2334,11 @@ rb_s_gpgme_op_decrypt_verify (VALUE dummy, VALUE vctx, VALUE vcipher,
UNWRAP_GPGME_DATA(vplain, plain);

err = gpgme_op_decrypt_verify (ctx, cipher, plain);

RB_GC_GUARD(vctx);
RB_GC_GUARD(vcipher);
RB_GC_GUARD(vplain);

return LONG2NUM(err);
}

Expand Down Expand Up @@ -2404,6 +2423,11 @@ rb_s_gpgme_op_sign (VALUE dummy, VALUE vctx, VALUE vplain, VALUE vsig,
UNWRAP_GPGME_DATA(vsig, sig);

err = gpgme_op_sign (ctx, plain, sig, NUM2INT(vmode));

RB_GC_GUARD(vctx);
RB_GC_GUARD(vplain);
RB_GC_GUARD(vsig);

return LONG2NUM(err);
}

Expand Down Expand Up @@ -2513,6 +2537,12 @@ rb_s_gpgme_op_encrypt (VALUE dummy, VALUE vctx, VALUE vrecp, VALUE vflags,
err = gpgme_op_encrypt (ctx, recp, NUM2INT(vflags), plain, cipher);
if (recp)
xfree (recp);

RB_GC_GUARD(vctx);
RB_GC_GUARD(vrecp);
RB_GC_GUARD(vplain);
RB_GC_GUARD(vcipher);

return LONG2NUM(err);
}

Expand Down Expand Up @@ -2612,6 +2642,12 @@ rb_s_gpgme_op_encrypt_sign (VALUE dummy, VALUE vctx, VALUE vrecp, VALUE vflags,
err = gpgme_op_encrypt_sign (ctx, recp, NUM2INT(vflags), plain, cipher);
if (recp)
xfree (recp);

RB_GC_GUARD(vctx);
RB_GC_GUARD(vrecp);
RB_GC_GUARD(vplain);
RB_GC_GUARD(vcipher);

return LONG2NUM(err);
}

Expand Down
57 changes: 56 additions & 1 deletion lib/gpgme.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require 'gpgme_n'
require 'monitor'

# TODO without this call one can't GPGME::Ctx.new, find out why
# This call initializes the GPGME library and must happen before
# any GPGME operations (e.g. Ctx.new) can succeed.
GPGME::gpgme_check_version(nil)

require 'gpgme/constants'
Expand All @@ -19,8 +21,61 @@
require 'gpgme/crypto'

module GPGME

# Mutex for serializing GPGME operations when thread safety is enabled.
# While the underlying GPGME C library supports separate contexts in
# separate threads, the communication with gpg-agent over Unix domain
# sockets can produce "Bad file descriptor" errors under heavy concurrent
# load. Thread-safe mode is enabled by default and can be disabled
# if not needed (e.g. single-threaded applications).
#
# A Monitor is used instead of a Mutex because GPGME operations are
# reentrant — e.g. Crypto#sign calls Ctx.new, and within that block,
# Key.find calls Ctx.new again.
#
# @example Disable thread-safe mode for single-threaded apps
# GPGME.thread_safe = false
#
@thread_safe_mutex = Monitor.new
@thread_safe = true

class << self

# Enable or disable thread-safe mode. Enabled by default. When
# enabled, all high-level GPGME operations (encrypt, decrypt, sign,
# verify, key listing, etc.) are serialized through a global monitor
# to prevent concurrent access to gpg-agent from causing "Bad file
# descriptor" errors. Disable for single-threaded apps if the
# synchronization overhead is undesirable.
#
# @param [Boolean] value false to disable thread-safe mode
attr_writer :thread_safe

# Returns true if thread-safe mode is enabled.
def thread_safe?
@thread_safe
end

# The mutex used for thread-safe serialization. Can be used directly
# if you need finer-grained control over locking.
#
# @example manual locking
# GPGME.synchronize do
# # multiple GPGME operations atomically
# end
attr_reader :thread_safe_mutex

# Execute a block with the GPGME mutex held if thread-safe mode is
# enabled. If thread-safe mode is disabled, the block is executed
# directly without locking.
def synchronize(&block)
if @thread_safe
@thread_safe_mutex.synchronize(&block)
else
yield
end
end

# From the c extension
alias pubkey_algo_name gpgme_pubkey_algo_name
alias hash_algo_name gpgme_hash_algo_name
Expand Down
25 changes: 19 additions & 6 deletions lib/gpgme/ctx.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,12 @@ def self.new(options = {})
end

if block_given?
begin
yield ctx
ensure
GPGME::gpgme_release(ctx)
GPGME.synchronize do
begin
yield ctx
ensure
GPGME::gpgme_release(ctx)
end
end
else
ctx
Expand Down Expand Up @@ -618,10 +620,21 @@ def inspect
private

def self.pass_function(pass, uid_hint, passphrase_info, prev_was_bad, fd)
# Write the passphrase directly using IO.for_fd. We set autoclose=false
# to prevent Ruby from closing the fd (which belongs to GPGME/gpg-agent).
# The IO object is used only within this method scope and not stored,
# so we also ensure it isn't prematurely collected by keeping a strong
# reference until we're done.
io = IO.for_fd(fd, 'w')
io.autoclose = false
io.puts pass
io.flush
begin
io.write "#{pass}\n"
io.flush
rescue => e
# If the fd has become invalid (e.g. agent communication error),
# re-raise as a more descriptive error.
raise e
end
end

end
Expand Down