1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
> Is it possible to limit chronyc to only those commands that
> are readonly plus those necessary to bring a dialup connection up
> and down? That is: online offline dump writertc and password.
This is trivial on the same host and workable for non-local
hosts: use a wrapper program or script. An *untested*
sample follows. To use it, best create a special user (say
chronyc) and a special group (say chronyg). Make the script
chronyc:chronyg, and 4750 (suid, rwxr-x---). Add all users
who may run the script to the group chronyg.
Make a chrony password file e.g.
/usr/local/etc/chrony_password. It should be owned by chronyc
and readable only for the owner, containing only the chrony
password (and maybe a newline) in the first line.
In this way only the script (call it run_chrony, for example)
can read the password. It will allow only those commands you
explicitely allow. You can add a password check -- especially
if you add an internet port so you can access it over the
internet this is advisable. You really want to add logging
to this untested script as well.
BTW, if you use some sort of PPP, you probably can use
/etc/ppp/ip-up and /etc/ppp/ip-down to transparently set chrony
on- and offline as the ip connection goes up and comes down.
This is _far_ more user friendly, IMHO, and a DOS by switching
chrony offline all the time is avoided as well.
#! /usr/bin/perl -T
use v5.6.1;
use warnings;
use strict;
sub laundered_command();
sub order_chrony($$);
sub read_password();
sub usage($);
our $CHRONY = "/usr/local/bin/chronyc";
# NOTE: select the file system protection wisely for the
# PASSWORDFILE!
our $PASSWORDFILE = "/usr/local/etc/chrony_password";
our @ALLOWED_COMMANDS = (
'online', # switch online mode on
'offline', # switch online mode off
'dump', # save measurements to file
'writerc', # save RTC accumulated data
'clients', # which clients are served by us?
'rtcdata', # Quality of RTC measurements
'sources(?: -v)?', # Show our sources (verbose)
'sourcestats(?: -v)?', # How good are our sources (verbose)?
'tracking', # whom do we adjust to?
# 'burst \d+/\d+', # allow them to send bursts?
);
usage("No command given.") unless $ARGV[0];
%ENV = (); # nuke all environment variables. Rather
# drastic, but better safe than sorry!
# Add whatever you really need to get it
# working (again).
$ENV{'PATH'} = '/usr/local/bin:/bin:/usr/bin';
order_chrony(laundered_command(), read_password());
exit 0; # command succeeded
############################################################
sub usage($) {
print STDERR "Error: ", shift, "\n";
# OK, this eats the -v...
print STDERR "Legal commands are:\n\t", join "\n",
map { $_ =~ m:(\w+):; $1 } @ALLOWED_COMMANDS;
exit 1; # error
}
############################################################
sub laundered_command() {
my $regexp = "^(" . join ( "|", @ALLOWED_COMMANDS ) . ")\$";
my $parameters = join " ", @ARGV;
$parameters =~ m:$regexp: or usage("Command $parameters not allowed.");
return $1; # this value, then, is untainted.
};
############################################################
sub read_password() {
open PASS, $PASSWORDFILE
or die "Could not read protected password file: $!";
my $password = <PASS>;
chomp $password;
return $password;
};
############################################################
sub order_chrony($$) {
my ($clean_command, $password) = @_;
open CHRONY, "| $CHRONY &> /dev/null" or die "could not run $CHRONY: $!\n";
print CHRONY "password $password\n";
print CHRONY "$clean_command\n";
close CHRONY
or die "Error running command $clean_command\n", "\ton $CHRONY: $!\n";
}
############################################################
|