I agree,
sudo and aliases are the best option.
If you don't want or cannot use sudo, you can use C wrapper programs. (This is the Programming forum, after all

-- I would not mention this in any other forum. Consider this just an interesting side note,
not a suggestion on how to do it in a normal situation.)
poweroff.c:
Code:
#define _GNU_SOURCE
#include <unistd.h>
int main(int argc, char *argv[])
{
if (setresgid(0, 0, 0)) return 126;
if (setresuid(0, 0, 0)) return 125;
execl("/sbin/shutdown", "shutdown", "-P", "now", NULL);
return 127;
}
reboot.c:
Code:
#define _GNU_SOURCE
#include <unistd.h>
int main(int argc, char *argv[])
{
if (setresgid(0, 0, 0)) return 126;
if (setresuid(0, 0, 0)) return 125;
execl("/sbin/shutdown", "shutdown", "-r", "now", NULL);
return 127;
}
Compile and install as e.g. /usr/local/bin/user-reboot and /usr/local/bin/user-poweroff using
Code:
gcc poweroff.c -Wall -O3 -s -o user-poweroff
gcc reboot.c -Wall -O3 -s -o user-reboot
sudo install -m 04750 -o root -g group user-poweroff /usr/local/bin/user-poweroff
sudo install -m 04750 -o root -g group user-reboot /usr/local/bin/user-reboot
and any user belonging to the group
group can now power off and reboot the computer.
The way this works is that the wrapper programs are set setuid-root (u+s, the 04000 in the mode), but only root and the group are allowed to run them.
The setresgid() and setresuid() calls in the wrappers are not required, actually, but I like to be thorough. The fact that the setuid bit sets the effective user id to root should suffice for /sbin/shutdown. The calls just leverage the effective rootness to set all ids to root.
If you want to name individual users, or more than one group, use POSIX ACLs. You'll need to add the
acl mount option to the filesystem in
/etc/fstab. Then, to allow user
someuser and/or group
somegroup, use
Code:
setfacl -m u:someuser:rx /usr/local/bin/user-{reboot,poweroff}
setfacl -m g:somegroup:rx /usr/local/bin/user-{reboot,poweroff}
As with sudo, you can add aliases for
shutdown,
poweroff and
reboot to the user shell startup files (format and location depends on which shells they use). This way system stuff is not affected, only users see the added functionality.
One might be tempted to allow any user to execute the wrapper, but check the real user id in the C program. Do not do that;
sudo was developed to do exactly that, and it does it extremely well. No need to reinvent the wheel.
Finally, if you need to reboot or shutdown from C code, just execute either
sudo shutdown ... or the wrappers above, using
Code:
#include <unistd.h>
execl("/usr/bin/sudo", "sudo", "/usr/sbin/shutdown", "-r", "now", NULL); /* Reboot */
or
execl("/usr/local/bin/user-reboot", "user-reboot", NULL); /* Reboot */
or
execl("/usr/bin/sudo", "sudo", "/usr/sbin/shutdown", "-P", "now", NULL); /* Poweroff */
or
execl("/usr/local/bin/user-poweroff", "user-poweroff", NULL); /* Poweroff */
Although normally you would fork() before calling the exec, in these cases your program should be ready to exit anyway, so instead of exiting, you could just call the poweroff/reboot function.
If you had an application suite, say an embedded device GUI, it might be better to use the wrapper programs instead of sudo. In that case, you'd use
/usr/lib/application/poweroff and
/usr/lib/application/reboot for the wrappers, only allowing access to the dedicated group that runs the application.