sudo 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,101 @@
1
+ = Ruby Sudo
2
+
3
+ Give Ruby objects superuser privileges.
4
+
5
+ Based on dRuby[http://ruby-doc.org/ruby-1.9/classes/DRb.html] and
6
+ sudo[http://www.sudo.ws/].
7
+
8
+ Only tested with {MRI 1.9}[http://en.wikipedia.org/wiki/Ruby_MRI] .
9
+
10
+ == REQUIREMENTS
11
+
12
+ Your user must be allowed, in +/etc/sudoers+, to run +ruby+ and +kill+
13
+ commands as root.
14
+
15
+ A password will be required from the console, or not, depending on
16
+ the +NOPASSWD+ options in +/etc/sudoers+.
17
+
18
+ == USAGE
19
+
20
+ === DSL style
21
+
22
+ require 'fileutils'
23
+ require 'sudo'
24
+
25
+ include Sudo::DSL
26
+
27
+ # The String will be passed as options to sudo-ed Ruby interpreter
28
+ sudo_start "-rfileutils"
29
+
30
+ # only readable by root
31
+ puts sudo(File).read '/etc/shadow'
32
+
33
+ # write into the /
34
+ sudo(FileUtils).mkdir_p '/TEST_DIR/SUB_DIR'
35
+
36
+ # Stop the dRuby server (whish is running as root), as soon as you can
37
+ sudo_stop
38
+
39
+ === Explicit creation of a Wrapper object, block given
40
+
41
+ require 'fileutils'
42
+ require 'sudo'
43
+
44
+ Sudo::Wrapper.run('-rfileutils) do |su|
45
+ # here you use square brackets [] :
46
+ # su is an object, not a (top-level) method.
47
+ su[FileUtils].mkdir_p '/ONLY/ROOT/CAN/DO/THAT'
48
+ end
49
+ # Sockets and processes are closed automatically when the block exits
50
+
51
+ === Explicit creation of a Wrapper object, without block
52
+
53
+ require 'mygem/myclass'
54
+ require 'sudo'
55
+
56
+ obj = MyGem::MyClass.new
57
+
58
+ sudo = Sudo::Wrapper.new(-rmygem/myclass -rmygem/myclass2)
59
+
60
+ sudo.start!
61
+
62
+ sudo[obj].method # will be run as root (well, a sudo-ed copy)
63
+
64
+ # when you've done:
65
+ sudo.stop!
66
+
67
+ == PRINCIPLES OF OPERATION
68
+
69
+ Spawns a sudo-ed Ruby process running a
70
+ DRb[http://ruby-doc.org/ruby-1.9/classes/DRb.html] server. Communication is
71
+ done via a Unix socket (and, of course, permissions are set to +0600+).
72
+
73
+ No long-running daemons involved, everything is created on demand.
74
+
75
+ Access control is entirely delegated to +sudo+.
76
+
77
+ == TODO
78
+
79
+ * +sudo+ has a +-A+ option to accept password via an external program
80
+ (maybe graphical): support this feature.
81
+
82
+ * more options in Sudo::Wrapper.new, maybe a Hash.
83
+
84
+ == THANKS
85
+
86
+ Thanks to Tony Arcieri and Brian Candler for suggestions on
87
+ ruby-talk[http://www.ruby-forum.com/topic/262655].
88
+
89
+ == AUTHOR
90
+
91
+ Copyright (c) 2010 {Guido De Rosa}[http://github.com/gderosa/].
92
+
93
+ Sponsored by {VEMAR s.a.s.}[http://www.vemarsas.it/]
94
+
95
+ == LICENSE
96
+
97
+ Ruby's.
98
+
99
+
100
+
101
+
@@ -0,0 +1,18 @@
1
+ require 'fileutils'
2
+ require 'sudo'
3
+
4
+ Sudo::Wrapper.run('-rfileutils') do |su|
5
+
6
+ su[File].open '/TEST', 'w' do |f|
7
+ f.puts "Hello from UID=#{su[Process].uid}!"
8
+ end
9
+
10
+ su[FileUtils].cp '/etc/shadow', '/etc/shadow2'
11
+
12
+ end
13
+
14
+
15
+
16
+
17
+
18
+
@@ -0,0 +1,13 @@
1
+ require 'sudo'
2
+
3
+ include Sudo::DSL
4
+
5
+ sudo_start "-rfileutils"
6
+
7
+ puts sudo(File).read '/etc/shadow'
8
+
9
+ sudo(FileUtils).mkdir_p '/TEST_DIR/SUB_DIR'
10
+
11
+ #sudo_stop # automatic clenup, when out of scope, if not explicit
12
+
13
+
@@ -0,0 +1,24 @@
1
+ require 'fileutils'
2
+ require 'sudo'
3
+
4
+ su = Sudo::Wrapper.new('-rfileutils')
5
+
6
+ su.start!
7
+
8
+ su[File].open '/TEST', 'w' do |f|
9
+ f.puts "Hello from UID=#{su[Process].uid}!"
10
+ end
11
+
12
+ su[FileUtils].cp '/etc/shadow', '/etc/shadow2'
13
+
14
+ # i you don't call stop! explicitly, the corresponding process and file
15
+ # cleanup will be done automatically, when the object gets out of scope
16
+ #
17
+ # su.stop!
18
+
19
+
20
+
21
+
22
+
23
+
24
+
@@ -0,0 +1,44 @@
1
+ require 'drb/drb'
2
+ require 'sudo/support/kernel'
3
+ require 'sudo/support/object'
4
+ require 'sudo/support/process'
5
+ require 'sudo/wrapper'
6
+
7
+ module Sudo
8
+
9
+ VERSION = '0.0.1'
10
+ ROOTDIR = File.expand_path File.join File.dirname(__FILE__), '..'
11
+ LIBDIR = File.join ROOTDIR, 'lib'
12
+ SERVER_SCRIPT = File.join ROOTDIR, 'libexec/server.rb'
13
+
14
+ class RuntimeError < RuntimeError; end
15
+
16
+ module DSL
17
+ def sudo_start(*args, &blk)
18
+ @__default_sudo_wrapper = Sudo::Wrapper.new(*args, &blk).start!
19
+ end
20
+ def sudo_stop
21
+ @__default_sudo_wrapper.stop!
22
+ end
23
+ def sudo(object)
24
+ @__default_sudo_wrapper[object]
25
+ end
26
+ end
27
+
28
+ class MethodProxy
29
+ def initialize(object, proxy)
30
+ @object = object
31
+ @proxy = proxy
32
+ end
33
+ def method_missing(method=:self, *args, &blk)
34
+ @proxy.proxy @object, method, *args, &blk
35
+ end
36
+ end
37
+
38
+ class Proxy
39
+ def proxy(object, method=:self, *args, &blk)
40
+ object.send method, *args, &blk
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,20 @@
1
+ module Kernel
2
+ def wait_for(conf)
3
+ start = Time.now
4
+ defaults = {
5
+ :timeout => nil,
6
+ :step => 0.125
7
+ }
8
+ conf = defaults.update conf
9
+ condition = false
10
+ loop do
11
+ condition = yield
12
+
13
+ break if condition
14
+ break if conf[:timeout] and Time.now - start > conf[:timeout]
15
+
16
+ sleep conf[:step]
17
+ end
18
+ condition
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ class Object
2
+ def self
3
+ self
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ module Process
2
+ class << self
3
+ # Thanks to:
4
+ # http://stackoverflow.com/questions/141162/how-can-i-determine-if-a-different-process-id-is-running-using-java-or-jruby-on-l
5
+ def exists?(pid)
6
+ begin
7
+ Process.getpgid( pid )
8
+ true
9
+ rescue Errno::ESRCH
10
+ false
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,120 @@
1
+ require 'drb/drb'
2
+ require 'sudo/support/kernel'
3
+ require 'sudo/support/object'
4
+ require 'sudo/support/process'
5
+
6
+ begin
7
+ DRb.current_server
8
+ rescue DRb::DRbServerNotFound
9
+ DRb.start_service
10
+ end
11
+
12
+ module Sudo
13
+ class Wrapper
14
+
15
+ class RuntimeError < RuntimeError; end
16
+ class NotRunning < RuntimeError; end
17
+ class SudoFailed < RuntimeError; end
18
+ class SocketStillExists < RuntimeError; end
19
+ class SudoProcessExists < RuntimeError; end
20
+ class SudoProcessAlreadyExists < SudoProcessExists; end
21
+ class SudoProcessStillExists < RuntimeError; end
22
+ class NoValidSocket < RuntimeError; end
23
+ class SocketNotFound < NoValidSocket; end
24
+ class NoValidSudoPid < RuntimeError; end
25
+ class SudoProcessNotFound < NoValidSudoPid; end
26
+
27
+ class << self
28
+
29
+ # with blocks
30
+ def run(*args)
31
+ sudo = new(*args)
32
+ yield sudo.start!
33
+ sudo.stop!
34
+ end
35
+
36
+ # Not an instance method, so it may act as a finalizer
37
+ # (as in ObjectSpace.define_finalizer)
38
+ def cleanup!(h)
39
+ if h[:pid] and Process.exists? h[:pid]
40
+ system "sudo kill #{h[:pid]}" or
41
+ system "sudo kill -9 #{h[:pid]}" or
42
+ raise SudoProcessStillExists,
43
+ "Couldn't kill sudo process (PID=#{h[:pid]})"
44
+ end
45
+ if h[:socket] and File.exists? h[:socket]
46
+ system "sudo rm -f #{h[:socket]}" or
47
+ raise SocketStillExists,
48
+ "Couldn't delete socket #{h[:socket]}"
49
+ end
50
+ end
51
+
52
+ end
53
+
54
+ def initialize(ruby_opts='')
55
+ @proxy = nil
56
+ @socket = "/tmp/rubysu-#{Process.pid}-#{object_id}"
57
+ @sudo_pid = nil
58
+ @ruby_opts = ruby_opts
59
+ end
60
+
61
+ def server_uri; "drbunix:#{@socket}"; end
62
+
63
+ def start!
64
+ # just to check if we can sudo; and we'll receive a sudo token
65
+ raise SudoFailed unless system "sudo ruby -e ''"
66
+
67
+ raise SudoProcessAlreadyExists if @sudo_pid and Process.exists? @sudo_pid
68
+
69
+ @sudo_pid = spawn(
70
+ "sudo ruby -I#{LIBDIR} #{@ruby_opts} #{SERVER_SCRIPT} #{@socket} #{Process.uid}"
71
+ )
72
+ Process.detach(@sudo_pid) if @sudo_pid # avoid zombies
73
+ ObjectSpace.define_finalizer self, Finalizer.new(
74
+ :pid => @sudo_pid, :socket => @socket
75
+ )
76
+
77
+ if wait_for(:timeout => 1){File.exists? @socket}
78
+ @proxy = DRbObject.new_with_uri(server_uri)
79
+ else
80
+ raise RuntimeError, "Couldn't create DRb socket #{@socket}"
81
+ end
82
+ self
83
+ end
84
+
85
+ def running?
86
+ true if (
87
+ @sudo_pid and Process.exists? @sudo_pid and
88
+ @socket and File.exists? @socket and
89
+ @proxy
90
+ )
91
+ end
92
+
93
+ def stop!
94
+ self.class.cleanup!(:pid => @sudo_pid, :socket => @socket)
95
+ @proxy = nil
96
+
97
+ end
98
+
99
+ def [](object)
100
+ if running?
101
+ MethodProxy.new object, @proxy
102
+ else
103
+ raise NotRunning
104
+ end
105
+ end
106
+
107
+ # Inspired by Remover class in tmpfile.rb (Ruby std library)
108
+ class Finalizer
109
+ def initialize(h)
110
+ @data = h
111
+ end
112
+
113
+ # mimic proc-like behavior (walk like a duck)
114
+ def call(*args)
115
+ Sudo::Wrapper.cleanup! @data
116
+ end
117
+ end
118
+
119
+ end
120
+ end
@@ -0,0 +1,18 @@
1
+ require 'drb/drb'
2
+ require 'fileutils'
3
+ require 'sudo'
4
+
5
+ socket = ARGV[0]
6
+
7
+ owner = ARGV[1]
8
+
9
+ uri = "drbunix:#{socket}"
10
+
11
+ DRb.start_service(uri, Sudo::Proxy.new)
12
+
13
+ FileUtils.chown owner, 0, socket
14
+ FileUtils.chmod 0600, socket
15
+
16
+ DRb.thread.join
17
+
18
+
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sudo
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Guido De Rosa
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-10-22 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: |
22
+ Give Ruby objects superuser privileges.
23
+
24
+ Based on dRuby and sudo (the Unix program).
25
+
26
+ email: guido.derosa@vemarsas.it
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/sudo/support/object.rb
35
+ - lib/sudo/support/kernel.rb
36
+ - lib/sudo/support/process.rb
37
+ - lib/sudo/wrapper.rb
38
+ - lib/sudo.rb
39
+ - libexec/server.rb
40
+ - examples/block.rb
41
+ - examples/dsl.rb
42
+ - examples/new.rb
43
+ - README.rdoc
44
+ has_rdoc: true
45
+ homepage: http://github.com/gderosa/rubysu
46
+ licenses: []
47
+
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --charset=UTF-8
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.3.7
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: Give Ruby objects superuser privileges
76
+ test_files: []
77
+