sudo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+