File Coverage

lib/Sysync/File.pm
Criterion Covered Total %
statement 111 196 56.6
branch 28 90 31.1
condition 6 18 33.3
subroutine 11 17 64.7
pod 13 13 100.0
total 169 334 50.6


line stmt bran cond sub pod time code
1             package Sysync::File;
2 1     1   957 use strict;
  1         2  
  1         29  
3 1     1   860 use YAML;
  1         8656  
  1         70  
4 1     1   10 use base 'Sysync';
  1         2  
  1         2694  
5              
6             =head1 NAME
7              
8             Sysync::File - Use Sysync with flat-files on the backend.
9              
10             =head1 SYNOPSIS
11              
12             See: http://sysync.nongnu.org/tutorial.html
13              
14             =head1 METHODS
15              
16             =head3 get_user_password
17              
18             Return a user's encrypted password.
19              
20             =cut
21              
22              
23             sub get_user_password
24             {
25 78     78 1 103 my ($self, $username) = @_;
26 78         150 my $sysdir = $self->sysdir;
27 78         246 return $self->read_file_contents("$sysdir/users/$username.passwd");
28             }
29              
30             =head3 set_user_password
31              
32             Set a user's password.
33              
34             =cut
35              
36             sub set_user_password
37             {
38 0     0 1 0 my ($self, $username, $passwd) = @_;
39 0         0 my $sysdir = $self->sysdir;
40 0         0 open(F, ">$sysdir/users/$username.passwd");
41 0         0 print F $passwd;
42 0         0 close(F);
43              
44 0         0 return 1;
45             }
46              
47             =head3 get_host_files
48              
49             Generate a list of files with their content.
50              
51             Returns hashref:
52             '/etc/filename.conf' => {
53             mode => 600,
54             gid => 0,
55             uid => 0,
56             data => 'data is here'
57             }
58              
59             =cut
60              
61             sub get_host_files
62             {
63 1     1 1 3 my ($self, $host) = @_;
64 1 50       2 return unless $self->is_valid_host($host);
65              
66 1         2 my %files;
67 1         4 my $sysdir = $self->sysdir;
68 1         3 my $default_host_config = {};
69 1 50       18 if (-e "$sysdir/hosts/default.conf")
70             {
71 0         0 $default_host_config = Load($self->read_file_contents("$sysdir/hosts/default.conf"));
72             }
73              
74 1   50     24 my $default_files = $self->_process_file_block(($default_host_config->{files} || []), $host);
75              
76 1         2 my $host_config = {};
77 1 50       7 if ($self->is_valid_host($host))
78             {
79 1         7 $host_config = Load($self->read_file_contents("$sysdir/hosts/$host.conf"));
80             }
81              
82 1   50     36919 my $host_files = $self->_process_file_block(($host_config->{files} || []), $host);
83              
84 1         4 my $files = { %$default_files, %$host_files };
85              
86 1         34 return $files;
87             }
88              
89             # returns hashref
90             #
91             # '/etc/filename.conf' => {
92             # mode => 600,
93             # owner => root,
94             # group => root,
95             # data => '',
96             # }
97             #
98              
99             sub _process_file_block
100             {
101 2     2   7 my ($self, $block, $host, $stack) = @_;
102 2         8 my $sysdir = $self->sysdir;
103 2 50       9 $stack = [] unless defined $stack;
104              
105 2         17 my $users = $self->get_host_users($host);
106 2         16 my $groups = $self->get_host_groups($host);
107              
108              
109 2         4 my %block;
110              
111 2 50       4 for my $item (@{$block || []})
  2         9  
112             {
113 1 50       7 if (my $file = $item->{file})
    0          
    0          
114             {
115 1 50 33     10 if ($item->{directory} or $item->{'import'})
116             {
117 0         0 die "[$host: error] malformed file ($file) item with conflicting types\n";
118             }
119              
120 1 50       5 if ($item->{source})
121             {
122 0 0       0 if ($item->{source} =~ /^\//)
123             {
124 0         0 $item->{data} = $self->read_file_contents($item->{source}, must_exist => 1);
125             }
126             else # file is localized
127             {
128 0         0 $item->{data} = $self->read_file_contents("$sysdir/$item->{source}", must_exist => 1);
129             }
130             }
131              
132 1         5 my $owner = $users->{$item->{owner}};
133 1         2 my $group = $groups->{$item->{group}};
134              
135 1 50       5 if (not $owner)
136             {
137 0         0 die "[$host: error] Invalid owner ($item->{owner}) for $file\n";
138             }
139 1 50       3 if (not $group)
140             {
141 0         0 die "[$host: error] Invalid group ($item->{group}) for $file\n";
142             }
143 1 50       6 if (not $item->{mode})
144             {
145 0         0 die "[$host: error] Mode missing for $file\n";
146             }
147 1         4 $item->{uid} = $owner->{uid};
148 1         3 $item->{gid} = $group->{gid};
149              
150 1         5 $block{$file} = $item;
151             }
152             elsif (my $directory = $item->{directory})
153             {
154 0         0 my $owner = $users->{$item->{owner}};
155 0         0 my $group = $groups->{$item->{group}};
156              
157 0 0       0 if (not $owner)
158             {
159 0         0 die "[$host: error] Invalid owner ($item->{owner}) for $file\n";
160             }
161 0 0       0 if (not $group)
162             {
163 0         0 die "[$host: error] Invalid group ($item->{group}) for $file\n";
164             }
165 0         0 $item->{uid} = $owner->{uid};
166 0         0 $item->{gid} = $group->{gid};
167              
168 0         0 $block{$directory} = $item;
169             }
170             elsif (my $type = $item->{'import'})
171             {
172 0 0       0 if ($type eq 'host')
    0          
173             {
174 0         0 my $h = $item->{host};
175              
176 0 0       0 if ($self->is_valid_host($h))
177             {
178 0         0 my $fetch_host = Load($self->read_file_contents("$sysdir/hosts/$h.conf"));
179 0 0       0 if (my $file_block = $fetch_host->{files})
180             {
181 0 0       0 if (grep { $_ eq "$sysdir/hosts/$h.conf" } @$stack)
  0         0  
182             {
183 0         0 die "[$host: error] recursion found importing host $h\n";
184             }
185 0         0 push @$stack, "$sysdir/hosts/$h.conf";
186 0         0 my $remote_conf = $self->_process_file_block($file_block, $host, $stack);
187 0         0 pop @$stack;
188              
189 0         0 %block = (%block, %$remote_conf);
190             }
191             }
192             else
193             {
194 0         0 die "[$host: error] invalid host ($h) used in import\n";
195             }
196             }
197             elsif ($type eq 'config')
198             {
199 0         0 my $source = $item->{config};
200 0         0 my $config;
201 0 0       0 if ($source =~ /^\//)
202             {
203 0         0 $config = Load($self->read_file_contents($source, must_exist => 1));
204             }
205             else # file is localized
206             {
207 0         0 $config = Load($self->read_file_contents("$sysdir/$source", must_exist => 1));
208             }
209 0 0       0 next unless $config;
210              
211 0 0       0 if (my $file_block = $config->{files})
212             {
213 0 0       0 if (grep { $_ eq $source } @$stack)
  0         0  
214             {
215 0         0 die "[$host: error] recursion found importing config $source\n";
216             }
217              
218 0         0 push @$stack, $source;
219 0         0 my $remote_conf = $self->_process_file_block($file_block, $host, $stack);
220 0         0 pop @$stack;
221              
222 0         0 %block = (%block, %$remote_conf);
223             }
224             }
225             }
226              
227             }
228              
229 2         81 return \%block;
230             }
231              
232             =head3 is_valid_host
233              
234             Returns true if host is valid.
235              
236             =cut
237              
238             sub is_valid_host
239             {
240 14     14 1 33 my ($self, $host) = @_;
241 14         43 my $sysdir = $self->sysdir;
242 14         365 return -e "$sysdir/hosts/$host.conf";
243             }
244              
245             =head3 is_valid_user
246              
247             Returns true if user is valid.
248              
249             =cut
250              
251             sub is_valid_user
252             {
253 0     0 1 0 my ($self, $username) = @_;
254 0         0 my $sysdir = $self->sysdir;
255 0         0 return -e "$sysdir/users/$username.conf";
256             }
257              
258             =head3 get_host_users_groups
259              
260             Get both users and groups for a specific host.
261              
262             =cut
263              
264             sub get_host_users_groups
265             {
266 7     7 1 18 my ($self, $host) = @_;
267              
268 7         21 my $sysdir = $self->sysdir;
269 7         15 my $default_host_config = {};
270 7 50       145 if (-e "$sysdir/hosts/default.conf")
271             {
272 0         0 $default_host_config = Load($self->read_file_contents("$sysdir/hosts/default.conf"));
273             }
274              
275 7         17 my $host_config = {};
276 7 50       25 if ($self->is_valid_host($host))
277             {
278 7         47 $host_config = Load($self->read_file_contents("$sysdir/hosts/$host.conf"));
279             }
280              
281 7         283247 my (%host_users, %host_groups);
282             # merge default users and host users via config
283              
284 7 50       21 $host_users{$_->{username}} = $_ for (@{ $default_host_config->{users} || [ ] });
  7         79  
285 7 50       19 $host_users{$_->{username}} = $_ for (@{ $host_config->{users} || [ ] });
  7         188  
286              
287 7 50       18 $host_groups{$_->{groupname}} = $_ for (@{ $default_host_config->{groups} || [ ] });
  7         51  
288 7 50       14 $host_groups{$_->{groupname}} = $_ for (@{ $host_config->{groups} || [ ] });
  7         231  
289              
290 7   33     27 my $user_groups = $host_config->{user_groups} || $default_host_config->{user_groups};
291              
292 7 50       13 for my $group (@{$user_groups || []})
  7         23  
293             {
294 7         11 my @users;
295 7 50       23 if ($group eq 'all')
296             {
297 7         39 @users = $self->get_all_users;
298             }
299             else
300             {
301 0         0 @users = $self->get_users_from_group($group);
302             }
303              
304 7         15 for my $username (@users)
305             {
306 7         31 my $user = $self->get_user($username);
307 7 50       32 next unless $user;
308              
309 7         43 $host_users{$username} = $user;
310             }
311             }
312              
313 264         373 my @users = sort { $a->{uid} <=> $b->{uid} }
  98         160  
314 7         51 map { $host_users{$_} } keys %host_users;
315              
316             # add all groups with applicable users
317 7         46 for my $group ($self->get_all_groups)
318             {
319             # trust what we have if something is degined already
320 7 50       21 next if $host_groups{$group};
321              
322 7         41 my $group = Load($self->read_file_contents("$sysdir/groups/$group.conf"));
323 7         16062 $host_groups{$group->{groupname}} = $group;
324             }
325              
326             # add magical per-user groups
327 7         30 for my $user (@users)
328             {
329 98 100 66     386 if (not $host_groups{$user->{username}} and not $user->{gid})
330             {
331 84         418 $host_groups{$user->{username}} = {
332             gid => $user->{uid},
333             groupname => $user->{username},
334             users => [ ],
335             };
336             }
337             }
338              
339 1003         1253 my @groups = sort { $a->{gid} <=> $b->{gid} }
  252         331  
340 7         63 map { $host_groups{$_} } keys %host_groups;
341              
342             return {
343 7         213 users => \@users,
344             groups => \@groups,
345             };
346             }
347              
348             =head3 get_user
349              
350             Returns hashref of user information.
351              
352             Unless "hard-coded", the user's password will not be returned in this hashref.
353              
354             =cut
355              
356             sub get_user
357             {
358 7     7 1 13 my ($self, $username) = @_;
359 7         23 my $sysdir = $self->sysdir;
360 7 50       193 return unless -e "$sysdir/users/$username.conf";
361              
362 7         44 my $user_conf = Load($self->read_file_contents("$sysdir/users/$username.conf"));
363              
364 7         28682 return $user_conf;
365             }
366              
367             =head3 get_all_users
368              
369             Returns an array of all usernames.
370              
371             =cut
372              
373             sub get_all_users
374             {
375 7     7 1 12 my $self = shift;
376 7         37 my $sysdir = $self->sysdir;
377 7         10 my @users;
378 7         532 opendir(DIR, "$sysdir/users");
379 7         114 while (my $file = readdir(DIR))
380             {
381 21 100       96 if ($file =~ /(.*?)\.conf$/)
382             {
383 7         85 push @users, $1;
384             }
385             }
386 7         266 closedir(DIR);
387 7         35 return @users;
388             }
389              
390             =head3 get_all_hosts
391              
392             =cut
393              
394             sub get_all_hosts
395             {
396 0     0 1 0 my $self = shift;
397 0         0 my $sysdir = $self->sysdir;
398 0   0     0 return Load($self->read_file_contents("$sysdir/hosts.conf")) || {};
399             }
400              
401             =head3 get_all_groups
402              
403             Returns array of groups
404              
405             =cut
406              
407             sub get_all_groups
408             {
409 7     7 1 13 my $self = shift;
410 7         28 my $sysdir = $self->sysdir;
411 7         12 my @groups;
412 7         287 opendir(DIR, "$sysdir/groups");
413 7         96 while (my $file = readdir(DIR))
414             {
415 21 100       116 if ($file =~ /(.*?)\.conf$/)
416             {
417 7         37 push @groups, $1;
418             }
419             }
420 7         105 closedir(DIR);
421 7         25 return @groups;
422             }
423              
424             =head3 get_users_from_group
425              
426             Returns array of users in a given group
427              
428             =cut
429              
430             sub get_users_from_group
431             {
432 0     0 1   my ($self, $group) = @_;
433 0           my $sysdir = $self->sysdir;
434 0 0         return () unless -e "$sysdir/groups/$group.conf";
435              
436 0           my $group_conf = Load($self->read_file_contents("$sysdir/groups/$group.conf"));
437              
438 0 0 0       return () unless $group_conf->{users} and ref($group_conf->{users}) eq 'ARRAY';
439              
440 0           return @{ $group_conf->{users} };
  0            
441             }
442              
443             =head3 must_refresh
444              
445             Returns true if sysync must refresh.
446              
447             Passing 1 or 0 as an argument sets whether this returns true.
448              
449             =cut
450              
451             sub must_refresh
452             {
453 0     0 1   my $self = shift;
454 0           my $stagedir = $self->stagedir;
455              
456 0 0         if (scalar @_ >= 1)
457             {
458 0 0         if ($_[0])
459             {
460 0           open(F, ">$stagedir/.refreshnow");
461 0           close(F);
462 0           return 1;
463             }
464             else
465             {
466 0           unlink("$stagedir/.refreshnow");
467 0           return 0;
468             }
469             }
470             else
471             {
472 0           return -e "$stagedir/.refreshnow";
473             }
474             }
475              
476             =head3 must_refresh_files
477              
478             Returns true if sysync must refresh managed files.
479              
480             Passing 1 or 0 as an argument sets whether this returns true.
481              
482             =cut
483              
484             sub must_refresh_files
485             {
486 0     0 1   my $self = shift;
487 0           my $stagefilesdir = $self->stagefilesdir;
488              
489 0 0         if (scalar @_ >= 1)
490             {
491 0 0         if ($_[0])
492             {
493 0           open(F, ">$stagefilesdir/.refreshnow");
494 0           close(F);
495 0           return 1;
496             }
497             else
498             {
499 0           unlink("$stagefilesdir/.refreshnow");
500 0           return 0;
501             }
502             }
503             else
504             {
505 0           return -e "$stagefilesdir/.refreshnow";
506             }
507             }
508              
509             1;
510              
511             =head1 COPYRIGHT
512              
513             L L
514              
515             =head1 LICENSE
516              
517             Copyright (C) 2012, 2013 Bizowie
518              
519             This file is part of Sysync.
520            
521             Sysync is free software: you can redistribute it and/or modify
522             it under the terms of the GNU Affero General Public License as
523             published by the Free Software Foundation, either version 3 of the
524             License, or (at your option) any later version.
525            
526             Sysync is distributed in the hope that it will be useful,
527             but WITHOUT ANY WARRANTY; without even the implied warranty of
528             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
529             GNU Affero General Public License for more details.
530            
531             You should have received a copy of the GNU Affero General Public License
532             along with this program. If not, see .
533              
534             =head1 AUTHOR
535              
536             Michael J. Flickinger, C<< >>
537              
538             =cut
539