8

Often I find myself needing to write scripts that have to execute some portions as a normal user and other portions as a super user. I am aware of one similar question on SO where the answer was to run the same script twice and execute it as sudo, however that is not sufficient for me. Some times I need to revert to being a normal user after a sudo operation.

I have written the following in Ruby to do this

#!/usr/bin/ruby require 'rubygems' require 'highline/import' require 'pty' require 'expect' def sudorun(command, password) `sudo -k` PTY.spawn("sleep 1; sudo -u root #{command} 2>&1") { | stdin, stdout, pid | begin stdin.expect(/password/) { stdout.write("#{password}\n") puts stdin.read.lstrip } rescue Errno::EIO end } end 

Unfortunately, using that code if the user enters the wrong password the script crashes. Ideally it should give the user 3 tries to get the sudo password right. How do I fix this?

I am running this on Linux Ubuntu BTW.

2

4 Answers 4

9
+50

In my opinion, running a script that does stuff internally with sudo is wrong. A better approach is to have the user run the whole script with sudo, and have the script fork lesser-privileged children to do stuff:

# Drops privileges to that of the specified user def drop_priv user Process.initgroups(user.username, user.gid) Process::Sys.setegid(user.gid) Process::Sys.setgid(user.gid) Process::Sys.setuid(user.uid) end # Execute the provided block in a child process as the specified user # The parent blocks until the child finishes. def do_as_user user unless pid = fork drop_priv(user) yield if block_given? exit! 0 # prevent remainder of script from running in the child process end puts "Child running as PID #{pid} with reduced privs" Process.wait(pid) end at_exit { puts 'Script finished.' } User = Struct.new(:username, :uid, :gid) user = User.new('nobody', 65534, 65534) do_as_user(user) do sleep 1 # do something more useful here exit! 2 # optionally provide an exit code end puts "Child exited with status #{$?.exitstatus}" puts 'Running stuff as root' sleep 1 do_as_user(user) do puts 'Doing stuff as a user' sleep 1 end 

This example script has two helper methods. #drop_priv takes an object with username, uid, and gid defined and properly reduces the permissions of the executing process. The #do_as_user method calls #drop_priv in a child process before yielding to the provided block. Note the use of #exit! to prevent the child from running any part of the script outside of the block while avoiding the at_exit hook.

Often overlooked security concerns to think about:

  • Inheritance of open file descriptors
  • Environment variable filtering
  • Run children in a chroot?

Depending on what the script is doing, any of these may need to be addressed. #drop_priv is an ideal place to handle all of them.

Sign up to request clarification or add additional context in comments.

2 Comments

Awesome, I think this is the best answer. It solves the issue of wrong sudo passwords being missed, it has the additional plus of not needing to store the password in the script, and it lets me execute statements as root or as the user in whatever order I need to without having to prompt the user again.
To get the user name, id, and gid, I used this: uname = logname.strip uid = Integer(id -u '#{uname}'.strip) gid = Integer(id -g '#{uname}'.strip)
6

If it is possible, you could move the stuff you want executed as root to a seperate file and use the system() function to run it as sudo, including the sudo prompt etc:

system("sudo ruby stufftorunasroot.rb") 

The system() function is blocking, so the flow of your program doesn't need to be changed.

3 Comments

The OP said "Often I find myself needing to write scripts that have to execute some portions as a normal user and other portions as a super user". He doesn't want to run the whole script as root.
@IanDickinson You are right, probably read it to fast. Updated my answer.
This is a good answer. I certainly wouldn't give my password to an app unless I knew that the password was going straight to sudo -- no middleman involved.
3

I do not know if this is what you want or need, but have you tried sudo -A (search the web or the man page for SUDO_ASKPASS which might have a value like /usr/lib/openssh/gnome-ssh-askpass or similar)? This is what I use when I need to present a graphical password dialogue to users in GUI environments.

Sorry if this is the wrong answer, maybe you really want to remain on the console.

5 Comments

+1 anyway for the interesting answer... I think that at least GNOME and KDE have those dialogues, so this is pretty portable.
Just gave this a shot on a Linux box that doesn't run KDE or GNOME, and no cigar. Doesn't work on OSX either. Still a good solution though.
I wonder if there are stand-alone binaries doing the same independently of any big desktop framework. Maybe one could even compile a simple, portable solution by oneself. Just speculating though.
You mean one that would encompass the diferences between Linuxes that have sudo, Linuxes that have su, UAC, and OSX sudo?
No, just UNIXoid OS types with sudo -A. Anyway, when I answered it was just spontaneously, not really thinking through portability issues. I mentioned just what usually works for me. And Bernard who asked the main question had mentioned he was on Ubuntu, just like me in most cases.
1
#!/usr/bin/ruby # ... blabla, other code # part which requires sudo: system "sudo -p 'sudo password: ' #{command}" # other stuff # sudo again system "sudo -p 'sudo password: ' #{command}" # usually sudo 'remembers' that you just authenticated yourself successfuly and doesn't ask for the PW again... # some more code... 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.