source: trunk/scripts/yuleadmin.pl.in@ 574

Last change on this file since 574 was 550, checked in by katerina, 5 years ago

Fix for ticket #442 (support for OpenBSD signify).

  • Property svn:executable set to *
File size: 7.8 KB
RevLine 
[121]1#! /usr/bin/perl
2
3# Copyright (c) 2007 Riccardo Murri <riccardo.murri@gmail.com>
4#
5# License Information:
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19#
20
21use warnings;
22use strict;
23use Getopt::Long;
24use File::Basename;
25use File::Copy;
26use File::Temp qw/ tempfile /;
27use IO::File;
28
29# Do I/O to the data file in binary mode (so it
30# wouldn't complain about invalid UTF-8 characters).
31use bytes;
32
33File::Temp->safe_level( File::Temp::HIGH );
34
35my %opts = ();
36my $outfile;
37my $verbose;
38my $base = basename($0);
39
40my $cfgfile = "@myconffile@";
41my $yule = "@sbindir@/@install_name@";
42
43$cfgfile =~ s/^REQ_FROM_SERVER//;
44
45sub usage() {
46 print <<__END_OF_TEXT__
47Usage:
48 $base { -a | --add } [options] HOSTNAME [PASSWORD]
49 Add client HOSTNAME to configuration file. If PASSWORD is
50 omitted, it is read from stdin. If HOSTNAME already exists
51 in the configuration file, an error is given.
52
53 $base { -d | --delete } [options] HOSTNAME
54 Remove client HOSTNAME from configuration file.
55
56 $base { -l | --list } [options]
57 List clients in the yule configuration file.
58
59 $base { -r | --replace } [options] HOSTNAME [PASSWORD]
60 Replace password of existing client HOSTNAME in configuration file.
61 If PASSWORD is omitted, it is read from stdin. If HOSTNAME does not
62 already exist in the configuration file, an error is given.
63
64 $base { -u | --update } [options] HOSTNAME [PASSWORD]
65 Add client HOSTNAME to config file or replace its password with a new one.
66 If PASSWORD is omitted, it is read from stdin.
67
68Options:
69 -c CFGFILE --cfgfile CFGFILE
70 Select an alternate configuration file. (default: $cfgfile)
71
72 -o OUTFILE --output OUTFILE
73 Write modified configuration to OUTFILE. If this option is
74 omitted, $base will rename the original configuration file
75 to '$cfgfile.BAK' and overwrite it with the modified content.
76
77 -Y YULECMD --yule YULECMD
78 Use command YULECMD to generate the client key from the password.
79 (default: $yule)
80
81 -v --verbose
82 Verbose output.
83
84__END_OF_TEXT__
85;
86 return;
87}
88
89
90## subroutines
91
92sub read_clients ($) {
93 my $cfgfile = shift || '-';
94 my %clients;
95
96 open INPUT, "<$cfgfile"
97 or die ("Cannot read configuration file '$cfgfile'. Aborting");
98
99 my $section;
100 while (<INPUT>) {
101 # skip comment and blank lines
102 next if m{^\s*#};
103 next if m{^\s*$};
104
105 # match section headers
106 $section = $1 if m{^\s*\[([a-z0-9 ]+)\]}i;
107
108 # ok, list matching lines
109 if ($section =~ m/Clients/) {
110 if (m{^\s*Client=}i) {
111 chomp;
112 s{^\s*Client=}{}i;
113 my ($client, $key) = split /@/,$_,2;
114
115 $clients{lc($client)} = $key;
116 }
117 }
118 }
119
120 close INPUT;
121 return \%clients;
122}
123
124
125sub write_clients ($$$) {
126 my $cfgfile_in = shift || '-';
127 my $cfgfile_out = shift || $cfgfile_in;
128 my $clients = shift;
129
130 my @lines;
131 my $in_clients_section;
132
133 # copy-pass input file
134 my $section = '';
135 open INPUT, "<$cfgfile_in"
136 or die ("Cannot read configuration file '$cfgfile_in'. Aborting");
137 while (<INPUT>) {
138 # match section headers
139 if (m{^\s*\[([a-z0-9 ]+)\]}i) {
140 if ($in_clients_section and ($section ne $1)) {
141 # exiting [Clients] section, output remaining ones
142 foreach my $hostname (keys %{$clients}) {
143 push @lines,
144 'Client=' . $hostname . '@'
145 . $clients->{lc($hostname)} . "\n";
146 delete $clients->{lc($hostname)};
147 }
148 }
149 # update section title
150 $section = $1;
151 if ($section =~ m/Clients/i) {
152 $in_clients_section = 1;
153 } else {
154 $in_clients_section = 0;
155 }
156 }
157
158 # process entries in [Clients] section
159 if ($in_clients_section) {
160 if (m{^\s*Client=}i) {
161 my ($hostname, undef) = split /@/,$_,2;
162 $hostname =~ s{^\s*Client=}{}i;
163 if (defined($clients->{lc($hostname)})) {
164 # output (possibly) modified key
165 $_ = 'Client=' . $hostname . '@' . $clients->{lc($hostname)} . "\n";
166 delete $clients->{lc($hostname)};
167 }
168 else {
169 # client deleted, skip this line from output
170 $_ = '';
171 }
172 }
173 }
174
175 # copy input to output
176 push @lines, $_;
177 }
178 close INPUT;
179
180 # if end-of-file reached within [Clients] section, output remaining ones
181 if ($in_clients_section) {
182 foreach my $hostname (keys %{$clients}) {
183 push @lines, 'Client=' . $hostname . '@'
184 . $clients->{lc($hostname)} . "\n";
185 }
186 }
187
188 # if necessary, replace input file with output file
189 if ($cfgfile_in eq $cfgfile_out) {
190 copy($cfgfile_in, $cfgfile_in . '.BAK')
191 or die("Cannot backup config file '$cfgfile_in'. Aborting");
192 }
193 open OUTPUT, ">$cfgfile_out"
194 or die ("Cannot write to file '$cfgfile_out'. Aborting");
195 # overwrite config file line by line
196 foreach my $line (@lines) { print OUTPUT $line; }
197 close OUTPUT;
198}
199
200
201sub new_client_key ($) {
202 my $password = shift;
203 my $yulecmd = shift || $yule;
204
205 my (undef, $key) = split /@/, `$yulecmd -P $password`, 2;
206 chomp $key;
207 return $key;
208}
209
210
211## main
212
213Getopt::Long::Configure ("posix_default");
214Getopt::Long::Configure ("bundling");
215# Getopt::Long::Configure ("debug");
216
217GetOptions (\%opts,
218 'Y|yule=s',
219 'a|add',
220 'c|cfgfile=s',
221 'd|delete',
222 'h|help',
223 'l|list',
224 'o|output=s',
225 'r|replace',
226 'u|update',
227 'v|verbose',
228 );
229
230if (defined ($opts{'h'})) {
231 usage();
232 exit;
233}
234
235if (defined($opts{'c'})) {
236 $cfgfile = $opts{'c'};
237 $outfile = $cfgfile unless defined($outfile);
238}
239if (defined($opts{'Y'})) {
240 $yule = $opts{'Y'};
241}
242if (defined($opts{'v'})) {
243 $verbose = 1;
244}
245if (defined($opts{'o'})) {
246 $outfile = $opts{'o'};
247}
248
249if (defined($opts{'l'})) {
250 # list contents
251 my $clients = read_clients($cfgfile);
252
253 foreach my $client (keys %{$clients}) {
254 print "$client";
255 print " ${$clients}{$client}" if $verbose;
256 print "\n";
257 }
258}
259elsif (defined($opts{'a'})
260 or defined($opts{'u'})
261 or defined($opts{'r'})) {
262 # add HOSTNAME
263 my $hostname = $ARGV[0]
264 or die("Actions --add/--replace/--update require at least argument HOSTNAME. Aborting");
265
266 my $password;
267 if (defined($ARGV[1])) {
268 $password = uc($ARGV[1]);
269 } else {
270 $password = uc(<STDIN>);
271 # remove leading and trailing space
272 $password =~ s{\s*}{}g;
273 }
274 # sanity check
275 die ("Argument PASSWORD must be a 16-digit hexadecimal string. Aborting")
276 unless ($password =~ m/[[:xdigit:]]{16}/);
277
278 my $add = defined($opts{'a'});
279 my $replace = defined($opts{'r'});
280
281 my $clients = read_clients($cfgfile);
282 die ("Client '$hostname' already present in config file - cannot add. Aborting")
283 if ($add and defined(${$clients}{$hostname}));
284 die ("Client '$hostname' not already present in config file - cannot replace. Aborting")
285 if ($replace and not defined(${$clients}{$hostname}));
286
287 $clients->{$hostname} = new_client_key($password)
288 or die ("Cannot get key for the given password. Aborting");
289 write_clients($cfgfile, $outfile, $clients);
290}
291elsif (defined($opts{'d'})) {
292 # remove HOSTNAME
293 my $hostname = $ARGV[0]
294 or die("Action --delete requires one argument HOSTNAME. Aborting");
295
296 my $clients = read_clients($cfgfile);
297 delete ${$clients}{$hostname};
298 write_clients($cfgfile, $outfile, $clients);
299}
300else {
301 usage();
302 die ("You must specify one of --list, --add or --remove options. Aborting");
303}
Note: See TracBrowser for help on using the repository browser.