nesquena / dante

Turn any ruby code into a daemon.
MIT License
313 stars 17 forks source link

Fix for logging, privilege reassignment, and a cleaner shutdown message #1

Closed mckern closed 12 years ago

mckern commented 12 years ago

I had to use these particular features of Dante, and I noticed that user and group reassignment weren't functioning correctly. While fixing them I also discovered that logfile redirection wasn't working right either (logs were being buffered and flushed on close, instead of being synced to disk in real time). I rewrote both pieces of functionality.

Privilege dropping was rewritten following the guidelines laid out by Joe Damato, and work as expected (with the added bonus that user and group will accept UID or GID numbers in addition to user names now). The original behavior was verified using this script:

#!/usr/bin/env ruby

require 'rubygems'
require 'dante'

Dante.run('dante_test',
          :log_path => './test.log',
          :pid_path => './test.pid'
         ) do |opts|

  puts "uid #{Process.uid}, euid #{Process.euid}, gid #{Process.gid}"

end

With Dante 0.1.3, that results in this outcome:

redrobot tmp # ./dante_test_privs.rb
Starting dante_test service...
uid 0, euid 0, gid 0

redrobot tmp # ./dante_test_privs.rb --user ryan
/usr/lib64/ruby/gems/1.9.1/gems/dante-0.1.3/lib/dante/runner.rb:56:in `euid=': can't convert String into Integer (TypeError)
  from /usr/lib64/ruby/gems/1.9.1/gems/dante-0.1.3/lib/dante/runner.rb:56:in `execute'
  from /usr/lib64/ruby/gems/1.9.1/gems/dante-0.1.3/lib/dante.rb:24:in `run'
  from ./dante_test_privs.rb:6:in `
'

After this patch, behavior is as expected; UID/GID switching requires that the daemon be started as root, the same as any other UNIX/Linux daemon and GID changing has been moved ahead of UID changing, because if the UID is dropped than the ability the change GID is effectively limited. The results of that script are now:

redrobot tmp # ./dante_test_privs.rb 
Starting dante_test service...
uid 0, euid 0, gid 0

redrobot tmp # ./dante_test_privs.rb --user ryan
Starting dante_test service...
uid 500, euid 500, gid 0

redrobot tmp # ./dante_test_privs.rb --group ryan
Starting dante_test service...
uid 0, euid 0, gid 500

Log file redirection has been slightly reworked to be a little bit more concise and to flush to disk in realtime and the interrupt method now catches the interrupt flag and processes it cleanly, without the accompanying stack trace.

nesquena commented 12 years ago

Thank you for sending in these fixes much appreciated! Will release a new version

nesquena commented 12 years ago

@mckern Confused about why would you rescue the interrupt right after raising the Interrupt? It also made all the unit tests (around testing interruption) fail so I commented that part out for now.

mckern commented 12 years ago

The behavior that I got from rescuing there was this:

Script:

#!/usr/bin/env ruby

require 'rubygems'
require 'dante'

Dante.run('dante_test',
          :log_path => './test.log',
          :pid_path => './test.pid'
         ) do |opts|
  i = 0
  loop do
    puts "Iteration number #{i}"
    i = i + 1
    sleep 5
  end
  puts "uid #{Process.uid}, euid #{Process.euid}, gid #{Process.gid}"

end
redrobot tmp # ./dante_test_logging.rb 
Starting dante_test service...
Iteration number 0
Iteration number 1
^CInterrupt received; stopping dante_test

Instead of this (which is 0.1.3 does now):

redrobot tmp # ./dante_test_logging.rb 
Starting dante_test service...
Iteration number 0
Iteration number 1
^C/usr/lib64/ruby/gems/1.9.1/gems/dante-0.1.3/lib/dante/runner.rb:118:in `interrupt': Interrupt (Interrupt)
    from /usr/lib64/ruby/gems/1.9.1/gems/dante-0.1.3/lib/dante/runner.rb:90:in `block in start'
    from ./dante_test_logging.rb:15:in `call'
    from ./dante_test_logging.rb:15:in `sleep'
    from ./dante_test_logging.rb:15:in `block (2 levels) in 
' from ./dante_test_logging.rb:12:in `loop' from ./dante_test_logging.rb:12:in `block in
' from /usr/lib64/ruby/gems/1.9.1/gems/dante-0.1.3/lib/dante/runner.rb:98:in `call' from /usr/lib64/ruby/gems/1.9.1/gems/dante-0.1.3/lib/dante/runner.rb:98:in `start' from /usr/lib64/ruby/gems/1.9.1/gems/dante-0.1.3/lib/dante/runner.rb:59:in `execute' from /usr/lib64/ruby/gems/1.9.1/gems/dante-0.1.3/lib/dante.rb:24:in `run' from ./dante_test_logging.rb:7:in `
'

The daemon still stopped as expected, so the behavior didn't seem aberrant. I'll take another look at the interrupt method and related unit tests and see if I can't work in a way to pass tests AND get a cleaner shutdown method.