authenkey: conf.pm

File conf.pm, 113.5 KB (added by krit, 3 years ago)
Line 
1package Thruk::Controller::conf;
2
3use warnings;
4use strict;
5use Data::Dumper qw/Dumper/;
6use Encode qw/decode_utf8 encode_utf8/;
7use File::Copy;
8use Socket qw/inet_ntoa/;
9use Storable qw/dclone/;
10
11use Monitoring::Config::File ();
12use Monitoring::Config::Object ();
13use Thruk::Action::AddDefaults ();
14use Thruk::Authentication::User ();
15use Thruk::Backend::Peer ();
16use Thruk::Utils::Auth ();
17use Thruk::Utils::Conf ();
18use Thruk::Utils::Conf::Defaults ();
19use Thruk::Utils::Crypt ();
20use Thruk::Utils::External ();
21use Thruk::Utils::Log qw/:all/;
22use Thruk::Utils::Plugin ();
23use Thruk::Utils::References ();
24use Thruk::Utils::Status ();
25
26#use Thruk::Timer qw/timing_breakpoint/;
27
28=head1 NAME
29
30Thruk::Controller::conf - Thruk Controller
31
32=head1 DESCRIPTION
33
34Thruk Controller.
35
36=head1 METHODS
37
38=cut
39
40##########################################################
41
42=head2 index
43
44=cut
45sub index {
46    my($c) = @_;
47
48    # Safe Defaults required for changing backends
49    return unless Thruk::Action::AddDefaults::add_defaults($c, Thruk::Constants::ADD_SAFE_DEFAULTS);
50    #&timing_breakpoint('index start');
51
52    my $subcat = $c->req->parameters->{'sub'}    || '';
53    my $action = $c->req->parameters->{'action'} || 'show';
54
55    # check permissions
56    if($action eq 'user_password') {
57        # ok
58    }
59    elsif(!$c->check_user_roles("admin")) {
60        if(    !defined $c->db()
61            || !defined $c->db->{'backends'}
62            || ref $c->db->{'backends'} ne 'ARRAY'
63            || scalar @{$c->db->{'backends'}} == 0 ) {
64            # no backends configured or thruk config not possible
65            if($c->config->{'Thruk::Plugin::ConfigTool'}->{'thruk'}) {
66                return $c->detach("/error/index/14");
67            }
68        }
69        # no permissions at all
70        return $c->detach('/error/index/8');
71    }
72
73    $c->stash->{'no_auto_reload'}      = 1;
74    $c->stash->{title}                 = 'Config Tool';
75    $c->stash->{page}                  = 'config';
76    $c->stash->{template}              = 'conf.tt';
77    $c->stash->{subtitle}              = 'Config Tool';
78    $c->stash->{infoBoxTitle}          = 'Config Tool';
79    $c->stash->{'last_changed'}        = 0;
80    $c->stash->{'needs_commit'}        = 0;
81    $c->stash->{'show_save_reload'}    = 0;
82    $c->stash->{'has_jquery_ui'}       = 1;
83    $c->stash->{'disable_backspace'}   = 1;
84    $c->stash->{'has_refs'}            = 0;
85    $c->stash->{'link_obj'}            = \&Thruk::Utils::Conf::link_obj;
86    $c->stash->{no_tt_trim}            = 1;
87    $c->stash->{post_obj_save_cmd}     = $c->config->{'Thruk::Plugin::ConfigTool'}->{'post_obj_save_cmd'}   // '';
88    $c->stash->{show_summary_prompt}   = $c->config->{'Thruk::Plugin::ConfigTool'}->{'show_summary_prompt'} // 1;
89
90    Thruk::Utils::ssi_include($c);
91
92    # check if we have at least one file configured
93    if(   !defined $c->config->{'Thruk::Plugin::ConfigTool'}
94       || ref($c->config->{'Thruk::Plugin::ConfigTool'}) ne 'HASH'
95       || scalar keys %{$c->config->{'Thruk::Plugin::ConfigTool'}} == 0
96    ) {
97        Thruk::Utils::set_message( $c, { style => 'fail_message', msg => 'Config Tool is disabled.<br>Please have a look at the <a href="'.$c->stash->{'url_prefix'}.'documentation.html#_component_thruk_plugin_configtool">config tool setup instructions</a>.', escape => 0 } );
98    }
99
100    if(exists $c->req->parameters->{'edit'} and defined $c->req->parameters->{'host'}) {
101        $subcat = 'objects';
102    }
103
104    # workaround for when specified more than once in the url...
105    $subcat = shift @{$subcat} if ref $subcat eq 'ARRAY';
106
107    $c->stash->{sub}          = $subcat;
108    $c->stash->{action}       = $action;
109    $c->stash->{conf_config}  = $c->config->{'Thruk::Plugin::ConfigTool'} || {};
110    $c->stash->{has_obj_conf} = scalar keys %{Thruk::Utils::Conf::get_backends_with_obj_config($c)};
111
112    #&timing_breakpoint('index starting subs');
113    # set default
114    $c->stash->{conf_config}->{'show_plugin_syntax_helper'} = 1 unless defined $c->stash->{conf_config}->{'show_plugin_syntax_helper'};
115
116    if($action eq 'user_password') {
117        return _process_user_password_page($c);
118    }
119    elsif($action eq 'cgi_contacts') {
120        return _process_cgiusers_page($c);
121    }
122    elsif($action eq 'json') {
123        return _process_json_page($c);
124    }
125
126    # show settings page
127    if($subcat eq 'cgi') {
128        return if Thruk::Action::AddDefaults::die_when_no_backends($c);
129        _process_cgi_page($c);
130    }
131    elsif($subcat eq 'thruk') {
132        _process_thruk_page($c);
133    }
134    elsif($subcat eq 'users') {
135        return if Thruk::Action::AddDefaults::die_when_no_backends($c);
136        _process_users_page($c);
137    }
138    elsif($subcat eq 'plugins') {
139        _process_plugins_page($c);
140    }
141    elsif($subcat eq 'backends') {
142        _process_backends_page($c);
143    }
144    elsif($subcat eq 'objects') {
145        $c->stash->{'obj_model_changed'} = 0;
146        _process_objects_page($c);
147        Thruk::Utils::Conf::store_model_retention($c, $c->stash->{'param_backend'}) if $c->stash->{'obj_model_changed'};
148        $c->stash->{'parse_errors'} = $c->{'obj_db'}->{'parse_errors'};
149    }
150
151    #&timing_breakpoint('index done');
152    return 1;
153}
154
155
156##########################################################
157# return json list for ajax search
158sub _process_json_page {
159    my( $c ) = @_;
160
161    return unless Thruk::Utils::Conf::set_object_model($c);
162
163    my $type = $c->req->parameters->{'type'} || 'hosts';
164    $type    =~ s/s$//gmxo;
165
166    # name resolver
167    if($type eq 'dig') {
168        return unless Thruk::Utils::check_csrf($c);
169        my $resolved = 'unknown';
170        if(defined $c->req->parameters->{'host'} and $c->req->parameters->{'host'} ne '') {
171            my @addresses = gethostbyname($c->req->parameters->{'host'});
172            @addresses = map { inet_ntoa($_) } @addresses[4 .. $#addresses];
173            if(scalar @addresses > 0) {
174                $resolved = join(' ', @addresses);
175            }
176        }
177        my $json = { 'address' => $resolved };
178        return $c->render(json => $json);
179    }
180
181    # icons?
182    if($type eq 'icon') {
183        my $objects = [];
184        my $themes_dir = $c->config->{'themes_path'} || $c->config->{'home'}."/themes";
185        my $icon_dirs  = Thruk::Base::list($c->config->{'physical_logo_path'} || $themes_dir."/themes-available/Thruk/images/logos");
186        for my $dir (@{$icon_dirs}) {
187            $dir =~ s/\/$//gmx;
188            next unless -d $dir.'/.';
189            my $files = _find_files($c, $dir, '\.(png|gif|jpg)$');
190            for my $file (@{$files}) {
191                $file =~ s/$dir\///gmx;
192                push @{$objects}, $file." - ".$c->stash->{'logo_path_prefix'}.$file;
193            }
194        }
195        my $json = [ { 'name' => $type.'s', 'data' => $objects } ];
196        return $c->render(json => $json);
197    }
198
199    # macros
200    if($type eq 'macro') {
201        # common macros
202        my $objects = [
203            '$HOSTADDRESS$',
204            '$HOSTALIAS$',
205            '$HOSTNAME$',
206            '$HOSTSTATE$',
207            '$HOSTSTATEID$',
208            '$HOSTATTEMPT$',
209            '$HOSTOUTPUT$',
210            '$LONGHOSTOUTPUT$',
211            '$HOSTPERFDATA$',
212            '$SERVICEDESC$',
213            '$SERVICESTATE$',
214            '$SERVICESTATEID$',
215            '$SERVICESTATETYPE$',
216            '$SERVICEATTEMPT$',
217            '$SERVICEOUTPUT$',
218            '$LONGSERVICEOUTPUT$',
219            '$SERVICEPERFDATA$',
220        ];
221        if(defined $c->req->parameters->{'withargs'}) {
222            push @{$objects}, ('$ARG1$', '$ARG2$', '$ARG3$', '$ARG4$', '$ARG5$');
223        }
224        if(defined $c->req->parameters->{'withuser'}) {
225            my $user_macros = Thruk::Utils::read_resource_file($c->{'obj_db'}->{'config'}->{'obj_resource_file'});
226            push @{$objects}, keys %{$user_macros};
227        }
228        for my $type (qw/host service/) {
229            for my $macro (keys %{$c->{'obj_db'}->{'macros'}->{$type}}) {
230                push @{$objects}, '$_'.uc($type).uc(substr($macro, 1)).'$';
231            }
232        }
233        @{$objects} = sort @{$objects};
234        my $json            = [ { 'name' => 'macros', 'data' => $objects } ];
235        if($c->stash->{conf_config}->{'show_plugin_syntax_helper'}) {
236            if(defined $c->req->parameters->{'plugin'} and $c->req->parameters->{'plugin'} ne '') {
237                my $help = $c->{'obj_db'}->get_plugin_help($c, $c->req->parameters->{'plugin'});
238                my @options = $help =~ m/(\-[\w\d]|\-\-[\d\w\-_]+)[=|,|\s|\$]/gmx;
239                push @{$json}, { 'name' => 'arguments', 'data' => Thruk::Base::array_uniq(\@options) } if scalar @options > 0;
240            }
241        }
242        return $c->render(json => $json);
243    }
244
245    # plugins
246    if($type eq 'plugin') {
247        my $plugins = $c->{'obj_db'}->get_plugins($c);
248        my $json    = [ { 'name' => 'plugins', 'data' => [ sort keys %{$plugins} ] } ];
249        return $c->render(json => $json);
250    }
251
252    # plugin help
253    if($type eq 'pluginhelp' and $c->stash->{conf_config}->{'show_plugin_syntax_helper'}) {
254        return unless Thruk::Utils::check_csrf($c);
255        my $help = $c->{'obj_db'}->get_plugin_help($c, $c->req->parameters->{'plugin'});
256        my $json = [ { 'plugin_help' => $help } ];
257        return $c->render(json => $json);
258    }
259
260    # plugin preview
261    if($type eq 'pluginpreview' and $c->stash->{conf_config}->{'show_plugin_syntax_helper'}) {
262        return unless Thruk::Utils::check_csrf($c);
263        my $output = $c->{'obj_db'}->get_plugin_preview($c,
264                                         $c->req->parameters->{'command'},
265                                         $c->req->parameters->{'args'},
266                                         $c->req->parameters->{'host'},
267                                         $c->req->parameters->{'service'},
268                                     );
269        my $json   = [ { 'plugin_output' => $output } ];
270        return $c->render(json => $json);
271    }
272
273    # command line
274    if($type eq 'commanddetail') {
275        return unless Thruk::Utils::check_csrf($c);
276        my $name    = $c->req->parameters->{'command'};
277        my $objects = $c->{'obj_db'}->get_objects_by_name('command', $name);
278        my $json = [ { 'cmd_line' => '' } ];
279        if(defined $objects->[0]) {
280            $json = [ { 'cmd_line' => $objects->[0]->{'conf'}->{'command_line'} } ];
281        }
282        return $c->render(json => $json);
283    }
284
285    # servicemembers
286    if($type eq 'servicemember') {
287        return unless Thruk::Utils::Conf::set_object_model($c);
288        $c->{'obj_db'}->read_rc_file();
289        my $members = [];
290        my $objects = $c->{'obj_db'}->get_objects_by_type('host');
291        for my $host (@{$objects}) {
292            my $hostname = $host->get_name();
293            my $services = $c->{'obj_db'}->get_services_for_host($host);
294            for my $svc (keys %{$services->{'group'}}, keys %{$services->{'host'}}) {
295                push @{$members}, $hostname.','.$svc;
296            }
297        }
298        my $json = [{ 'name' => $type.'s',
299                      'data' => [ sort @{Thruk::Base::array_uniq($members)} ],
300                   }];
301        return $c->render(json => $json);
302    }
303
304    # service_description, same host service descriptions
305    if($type eq 'service_description') {
306        return unless Thruk::Utils::Conf::set_object_model($c);
307        $c->{'obj_db'}->read_rc_file();
308        my $descriptions = [];
309        my $objects;
310        my $ref = $c->req->parameters->{'ref'} ? $c->{'obj_db'}->get_object_by_id($c->req->parameters->{'ref'}) : undef;
311        if($ref) {
312            my $hosts = $c->{'obj_db'}->get_hosts_for_service($ref);
313            for my $id (values %{$hosts}) {
314                my $hst = $c->{'obj_db'}->get_object_by_id($id);
315                push @{$objects}, $hst if $hst;
316            }
317        } else {
318            $objects = $c->{'obj_db'}->get_objects_by_type('host');
319        }
320        for my $host (@{$objects}) {
321            my $services = $c->{'obj_db'}->get_services_for_host($host);
322            for my $svc (keys %{$services->{'group'}}, keys %{$services->{'host'}}) {
323                push @{$descriptions}, $svc;
324            }
325        }
326        my $json = [{ 'name' => $type.'s',
327                      'data' => [ sort @{Thruk::Base::array_uniq($descriptions)} ],
328                   }];
329        return $c->render(json => $json);
330    }
331
332    # objects attributes
333    if($type eq 'attribute') {
334        my $for  = $c->req->parameters->{'obj'};
335        my $attr = $c->{'obj_db'}->get_default_keys($for, { no_alias => 1 });
336        if($c->stash->{conf_config}->{'extra_custom_var_'.$for}) {
337            for my $extra (@{Thruk::Base::list($c->stash->{conf_config}->{'extra_custom_var_'.$for})}) {
338                my @extras = split/\s*,\s*/mx, $extra;
339                push @{$attr}, @extras;
340            }
341        }
342        $attr = [ sort @{Thruk::Base::array_uniq($attr)} ];
343
344        # add existing custom variables from this type
345        my $vars = Thruk::Utils::Status::get_custom_variable_names($c, $for, 0);
346        for my $v (@{$vars}) {
347            $v =~ s/^_*/_/gmx;
348            push @{$attr}, $v;
349        }
350
351        my $json = [{ 'name' => $type.'s',
352                      'data' => $attr,
353                   }];
354        return $c->render(json => $json);
355    }
356
357    # objects
358    my $json;
359    my $objects   = [];
360    my $templates = [];
361    my $filter    = $c->req->parameters->{'filter'};
362    my $use_long  = $c->req->parameters->{'long'};
363    if(defined $filter) {
364        $json       = [];
365        my $types   = {};
366        my $objects = $c->{'obj_db'}->get_objects_by_type($type,$filter);
367        for my $subtype (keys %{$objects}) {
368            for my $name (keys %{$objects->{$subtype}}) {
369                $types->{$subtype}->{$name} = 1 unless substr($name,0,1) eq '!';
370            }
371        }
372        for my $typ (sort keys %{$types}) {
373            push @{$json}, {
374                  'name' => _translate_type($typ)."s",
375                  'data' => [ sort keys %{$types->{$typ}} ],
376            };
377        }
378    } else {
379        for my $dat (@{$c->{'obj_db'}->get_objects_by_type($type)}) {
380            my $name = $use_long ? $dat->get_long_name(undef, '  -  ') : $dat->get_name();
381            if(defined $name) {
382                if($dat->{'disabled'}) { $name = $name.' (disabled)' }
383                push @{$objects}, $name;
384            } else {
385                _warn("object without a name in ".$dat->{'file'}->{'path'}.":".$dat->{'line'}." -> ".Dumper($dat->{'conf'}));
386            }
387        }
388        for my $dat (@{$c->{'obj_db'}->get_templates_by_type($type)}) {
389            my $name = $dat->get_template_name();
390            if(defined $name) {
391                push @{$templates}, $name;
392            } else {
393                _warn("template without a name in ".$dat->{'file'}->{'path'}.":".$dat->{'line'}." -> ".Dumper($dat->{'conf'}));
394            }
395        }
396        $json = [ { 'name' => $type.'s',
397                    'data' => [ sort @{Thruk::Base::array_uniq($objects)} ],
398                  },
399                  { 'name' => $type.' templates',
400                    'data' => [ sort @{Thruk::Base::array_uniq($templates)} ],
401                  },
402                ];
403    }
404    return $c->render(json => $json);
405}
406
407
408##########################################################
409# create the cgi.cfg config page
410sub _process_cgiusers_page {
411    my( $c ) = @_;
412
413    my $contacts        = Thruk::Utils::Conf::get_cgi_user_list($c);
414    delete $contacts->{'*'}; # we dont need this user here
415    my $data            = [ values %{$contacts} ];
416    my $json            = [ { 'name' => "contacts", 'data' => $data } ];
417    return $c->render(json => $json);
418}
419
420
421##########################################################
422# create the cgi.cfg config page
423sub _process_cgi_page {
424    my( $c ) = @_;
425
426    my $file     = $c->config->{'Thruk::Plugin::ConfigTool'}->{'cgi.cfg'};
427    return unless defined $file;
428    $c->stash->{'readonly'} = (-w $file) ? 0 : 1;
429
430    # create a default config from the current used cgi.cfg
431    if(!-e $file && $file ne $c->config->{'cgi.cfg_effective'}) {
432        copy($c->config->{'cgi.cfg_effective'}, $file) or die('cannot copy '.$c->config->{'cgi.cfg_effective'}.' to '.$file.': '.$!);
433    }
434
435    my $defaults = Thruk::Utils::Conf::Defaults->get_cgi_cfg();
436
437    # save changes
438    if($c->stash->{action} eq 'store') {
439        if($c->stash->{'readonly'}) {
440            Thruk::Utils::set_message( $c, 'fail_message', 'file is readonly' );
441            return $c->redirect_to('conf.cgi?sub=cgi');
442        }
443        return unless Thruk::Utils::check_csrf($c);
444
445        my $data = Thruk::Utils::Conf::get_data_from_param($c->req->parameters, $defaults);
446        # check for empty multi selects
447        for my $key (keys %{$defaults}) {
448            next if $key !~ m/^authorized_for_/mx;
449            $data->{$key} = [] unless defined $data->{$key};
450        }
451        _store_changes($c, $file, $data, $defaults);
452        return $c->redirect_to('conf.cgi?sub=cgi');
453    }
454
455    my($content, $data, $hex) = Thruk::Utils::Conf::read_conf($file, $defaults);
456
457    # get list of cgi users
458    my $tmp = Thruk::Utils::Conf::get_cgi_user_list($c);
459    my $cgi_contacts = {};
460    for my $u (values %{$tmp}) {
461        if($u->{'alias'}) {
462            $cgi_contacts->{$u->{'name'}} = $u->{'name'}.' - '.$u->{'alias'};
463        } else {
464            $cgi_contacts->{$u->{'name'}} = $u->{'name'};
465        }
466    }
467
468    for my $key (keys %{$data}) {
469        next unless $key =~ m/^authorized_for_/mx;
470        $data->{$key}->[2] = $cgi_contacts;
471    }
472
473    # get list of cgi users
474    my $cgi_groups = Thruk::Utils::Conf::get_cgi_group_list($c);
475    for my $key (keys %{$data}) {
476        next unless $key =~ m/^authorized_contactgroup_for_/mx;
477        $data->{$key}->[2] = $cgi_groups;
478    }
479
480    my $authkeys = [qw/
481        use_authentication
482        use_ssl_authentication
483        default_user_name
484        lock_author_names
485    /];
486    my $authgroupkeys = [];
487    for my $key (@{$Thruk::Constants::possible_roles}) {
488        push @{$authkeys}, $key;
489        my $groupkey = $key;
490        $groupkey =~ s/^authorized_for_/authorized_contactgroup_for_/gmx;
491        push @{$authgroupkeys}, $groupkey;
492    }
493
494    my $keys = [
495        [ 'CGI Settings', [qw/
496                        show_context_help
497                        use_pending_states
498                        refresh_rate
499                        escape_html_tags
500                        action_url_target
501                        notes_url_target
502                    /],
503        ],
504        [ 'Authorization', $authkeys],
505        [ 'Authorization Groups', $authgroupkeys],
506    ];
507
508    $c->stash->{'keys'}     = $keys;
509    $c->stash->{'data'}     = $data;
510    $c->stash->{'hex'}      = $hex;
511    $c->stash->{'subtitle'} = "CGI &amp; Access Configuration";
512    $c->stash->{'template'} = 'conf_data.tt';
513
514    return 1;
515}
516
517##########################################################
518# create the thruk config page
519sub _process_thruk_page {
520    my( $c ) = @_;
521
522    my $file     = $c->config->{'Thruk::Plugin::ConfigTool'}->{'thruk'};
523    return unless defined $file;
524    my $defaults = Thruk::Utils::Conf::Defaults->get_thruk_cfg($c);
525    $c->stash->{'readonly'} = (-w $file) ? 0 : 1;
526
527    # save changes
528    if($c->stash->{action} eq 'store') {
529        if($c->stash->{'readonly'}) {
530            Thruk::Utils::set_message( $c, 'fail_message', 'file is readonly' );
531            return $c->redirect_to('conf.cgi?sub=thruk');
532        }
533        return unless Thruk::Utils::check_csrf($c);
534
535        my $data = Thruk::Utils::Conf::get_data_from_param($c->req->parameters, $defaults);
536        if(_store_changes($c, $file, $data, $defaults, $c)) {
537            return Thruk::Utils::restart_later($c, $c->stash->{url_prefix}.'cgi-bin/conf.cgi?sub=thruk');
538        } else {
539            return $c->redirect_to('conf.cgi?sub=thruk');
540        }
541    }
542
543    my($content, $data, $hex) = Thruk::Utils::Conf::read_conf($file, $defaults);
544
545    my $keys = [
546        [ 'General', [qw/
547                        title_prefix
548                        use_wait_feature
549                        wait_timeout
550                        use_frames
551                        server_timezone
552                        default_user_timezone
553                        use_strict_host_authorization
554                        show_long_plugin_output
555                        info_popup_event_type
556                        info_popup_options
557                        resource_file
558                        can_submit_commands
559                     /],
560        ],
561        [ 'Paths', [qw/
562                        tmp_path
563                        ssi_path
564                        plugin_path
565                        user_template_path
566                    /],
567        ],
568        [ 'Menu', [qw/
569                        start_page
570                        documentation_link
571                        all_problems_link
572                        allowed_frame_links
573                    /],
574        ],
575        [ 'Display', [qw/
576                        default_theme
577                        strict_passive_mode
578                        show_notification_number
579                        show_backends_in_table
580                        show_full_commandline
581                        shown_inline_pnp
582                        show_modified_attributes
583                        datetime_format
584                        datetime_format_today
585                        datetime_format_long
586                        datetime_format_log
587                        datetime_format_trends
588                        use_new_command_box
589                        show_custom_vars
590                        expose_custom_vars
591                    /],
592        ],
593        [ 'Paging', [qw/
594                        use_pager
595                        paging_steps
596                        group_paging_overview
597                        group_paging_summary
598                        group_paging_grid
599                    /],
600        ],
601    ];
602
603    $c->stash->{'keys'}     = $keys;
604    $c->stash->{'data'}     = $data;
605    $c->stash->{'hex'}      = $hex;
606    $c->stash->{'subtitle'} = "Thruk Configuration";
607    $c->stash->{'template'} = 'conf_data.tt';
608
609    return 1;
610}
611
612##########################################################
613# create the users config page
614sub _process_users_page {
615    my( $c ) = @_;
616
617    my $file     = $c->config->{'Thruk::Plugin::ConfigTool'}->{'cgi.cfg'};
618    return unless defined $file;
619    my $defaults = Thruk::Utils::Conf::Defaults->get_cgi_cfg();
620    $c->stash->{'readonly'} = (-w $file) ? 0 : 1;
621
622    my $user = $c->req->parameters->{'data.username'} || '';
623    my($name, $alias, $profile_file);
624    if($user ne '') {
625        ($name, $alias) = split(/\ \-\ /mx,$user, 2);
626        $profile_file = $c->config->{'var_path'}."/users/".$name;
627        $c->stash->{'profile_file'} = $profile_file;
628        $c->stash->{'profile_file_exists'} = -e $profile_file ? 1 : 0;
629    }
630
631    # save changes to user
632    if($user ne '' and defined $file and $c->stash->{action} eq 'store') {
633        my($name, $alias) = split(/\ \-\ /mx,$user, 2);
634        return unless Thruk::Utils::check_csrf($c);
635        my $redirect = 'conf.cgi?action=change&sub=users&data.username='.$user;
636        if($c->stash->{'readonly'}) {
637            Thruk::Utils::set_message( $c, 'fail_message', 'file is readonly' );
638            return $c->redirect_to($redirect);
639        }
640        my $msg = _update_password($c);
641        if(defined $msg) {
642            Thruk::Utils::set_message( $c, 'fail_message', $msg );
643            return $c->redirect_to($redirect);
644        }
645
646        my $send = $c->req->parameters->{'send'} || '';
647        if($send eq 'lock account' || $send eq 'unlock account') {
648            my $userdata = Thruk::Utils::get_user_data($c, $name);
649            if($send eq 'unlock account') {
650                delete $userdata->{'login'}->{'locked'};
651                $userdata->{'login'}->{'failed'} = 0;
652            } else {
653                $userdata->{'login'}->{'locked'} = 1;
654            }
655            Thruk::Utils::store_user_data($c, $userdata, $name);
656        }
657        if($send eq 'remove user profile data') {
658            if(!Thruk::Base::check_for_nasty_filename($name)) {
659                unlink($profile_file);
660                Thruk::Utils::set_message( $c, 'success_message', 'profile removed successfully' );
661            }
662            return $c->redirect_to($redirect);
663        }
664
665        # save changes to cgi.cfg
666        my($content, $data, $hex) = Thruk::Utils::Conf::read_conf($file, $defaults);
667        my $new_data              = {};
668        for my $key (keys %{$c->req->parameters}) {
669            next unless $key =~ m/data\.authorized_for_/mx;
670            $key =~ s/^data\.//gmx;
671            my $users = {};
672            for my $usr (@{$data->{$key}->[1]}) {
673                $users->{$usr} = 1;
674            }
675            if($c->req->parameters->{'data.'.$key}) {
676                $users->{$user} = 1;
677            } else {
678                delete $users->{$user};
679            }
680            @{$new_data->{$key}} = sort keys %{$users};
681        }
682        _store_changes($c, $file, $new_data, $defaults);
683
684        Thruk::Utils::set_message( $c, 'success_message', 'User saved successfully' );
685        return $c->redirect_to($redirect);
686    }
687
688    $c->stash->{'show_user'}  = 0;
689    $c->stash->{'user_name'}  = '';
690
691    if($c->stash->{action} eq 'change' and $user ne '') {
692        my($content, $data, $hex) = Thruk::Utils::Conf::read_conf($file, $defaults);
693        $c->stash->{'show_user'}  = 1;
694        $c->stash->{'user_name'}  = $name;
695        $c->stash->{'hex'}        = $hex;
696        $c->stash->{'roles'}      = {};
697        my $roles = $Thruk::Constants::possible_roles;
698        $c->stash->{'role_keys'}  = $roles;
699        for my $role (@{$roles}) {
700            $c->stash->{'roles'}->{$role} = 0;
701            for my $tst (@{$data->{$role}->[1]}) {
702                $c->stash->{'roles'}->{$role}++ if $tst eq $name;
703            }
704        }
705
706        $c->stash->{'has_htpasswd_entry'} = 0;
707        $c->stash->{'htpasswd_file'}      = $c->config->{'Thruk::Plugin::ConfigTool'}->{'htpasswd'} // '';
708        if(defined $c->config->{'Thruk::Plugin::ConfigTool'}->{'htpasswd'}) {
709            my $htpasswd = Thruk::Utils::Conf::read_htpasswd($c->config->{'Thruk::Plugin::ConfigTool'}->{'htpasswd'});
710            $c->stash->{'has_htpasswd_entry'} = 1 if defined $htpasswd->{$name};
711        }
712
713        $c->stash->{'has_contact'} = 0;
714        my $contacts = $c->db->get_contacts( filter => [ Thruk::Utils::Auth::get_auth_filter( $c, 'contact' ), name => $name ] );
715        if(defined $contacts and scalar @{$contacts} >= 1) {
716            $c->stash->{'has_contact'}    = 1;
717            $c->stash->{'contact'}        = $contacts->[0];
718        }
719
720        $c->stash->{'profile_user'} = Thruk::Authentication::User->new($c, $name)->set_dynamic_attributes($c);
721    }
722
723    $c->stash->{'subtitle'} = "User Configuration";
724    $c->stash->{'template'} = 'conf_data_users.tt';
725
726    return 1;
727}
728
729##########################################################
730# create the plugins config page
731sub _process_plugins_page {
732    my( $c ) = @_;
733
734    my $project_root         = $c->config->{home};
735    my $plugin_dir           = $c->config->{'plugin_path'} || $project_root."/plugins";
736    my $plugin_enabled_dir   = $plugin_dir.'/plugins-enabled';
737    my $plugin_available_dir = $project_root.'/plugins/plugins-available';
738
739    $c->stash->{'readonly'}  = 0;
740    if(! -d $plugin_enabled_dir || ! -w $plugin_enabled_dir ) {
741        $c->stash->{'readonly'}  = 1;
742    }
743
744    my $plugins = Thruk::Utils::Plugin::get_plugins($c);
745
746    if($c->stash->{action} eq 'preview') {
747        my $pic = $c->req->parameters->{'pic'} || die("missing pic");
748        if($pic !~ m/^[a-zA-Z0-9_\ \-]+$/gmx) {
749            die("unknown pic: ".$pic);
750        }
751        my $path = $plugin_enabled_dir.'/'.$pic.'/preview.png';
752        if(!-e $path) {
753            $path = $plugin_available_dir.'/'.$pic.'/preview.png';
754        }
755        $c->res->headers->content_type('image/png');
756        $c->stash->{'text'} = "";
757        if(-e $path) {
758            $c->stash->{'text'} = Thruk::Utils::IO::read($path);
759        }
760        $c->stash->{'template'} = 'passthrough.tt';
761        return 1;
762    }
763    elsif($c->stash->{action} eq 'save') {
764        return unless Thruk::Utils::check_csrf($c);
765        # don't store in demo mode
766        if($c->config->{'demo_mode'}) {
767            Thruk::Utils::set_message( $c, 'fail_message', "save is disabled in demo mode" );
768            return $c->redirect_to('conf.cgi?sub=plugins');
769        }
770        if(! -d $plugin_enabled_dir || ! -w $plugin_enabled_dir ) {
771            Thruk::Utils::set_message( $c, 'fail_message', 'Make sure plugins folder ('.$plugin_enabled_dir.') is writeable: '.$! );
772        }
773        else {
774            for my $addon (glob($plugin_available_dir.'/*/')) {
775                my($addon_name, $dir) = Thruk::Utils::Plugin::nice_addon_name($addon);
776                if(!defined $c->req->parameters->{'plugin.'.$dir} || $c->req->parameters->{'plugin.'.$dir} == 0) {
777                    Thruk::Utils::Plugin::disable_plugin($c, $dir) if $plugins->{$dir}->{'enabled'};
778                }
779                if(defined $c->req->parameters->{'plugin.'.$dir} and $c->req->parameters->{'plugin.'.$dir} == 1) {
780                    Thruk::Utils::Plugin::enable_plugin($c, $dir) unless $plugins->{$dir}->{'enabled'};
781                }
782            }
783            Thruk::Utils::set_message( $c, 'success_message', 'Plugins changed successfully.' );
784            return Thruk::Utils::restart_later($c, $c->stash->{url_prefix}.'cgi-bin/conf.cgi?sub=plugins&reload_nav=1');
785        }
786    }
787
788    $c->stash->{'plugins'}  = $plugins;
789    $c->stash->{'subtitle'} = "Thruk Addons &amp; Plugin Manager";
790    $c->stash->{'template'} = 'conf_plugins.tt';
791
792    return 1;
793}
794
795##########################################################
796# create the backends config page
797sub _process_backends_page {
798    my( $c ) = @_;
799
800    my $file = $c->config->{'Thruk::Plugin::ConfigTool'}->{'thruk'};
801    return unless $file;
802    # non existing file gives readonly, so try to create it
803    if(!-e $file) {
804        open(my $fh, '>', $file);
805        if($fh) {
806            print $fh '';
807            Thruk::Utils::IO::close($fh, $file);
808        }
809    }
810    $c->stash->{'readonly'} = (-w $file) ? 0 : 1;
811
812    if($c->stash->{action} eq 'save') {
813        return unless Thruk::Utils::check_csrf($c);
814        # don't store in demo mode
815        if($c->config->{'demo_mode'}) {
816            Thruk::Utils::set_message( $c, 'fail_message', "save is disabled in demo mode" );
817            return $c->redirect_to('conf.cgi?sub=backends');
818        }
819        if($c->stash->{'readonly'}) {
820            Thruk::Utils::set_message( $c, 'fail_message', 'file is readonly' );
821            return $c->redirect_to('conf.cgi?sub=backends');
822        }
823
824        my $numbers = [];
825        for my $key (sort keys %{$c->req->parameters}) {
826            if($key =~ m/^name(\d+)/mx) {
827                push @{$numbers}, $1;
828            }
829        }
830        my $backends = {};
831        my $has_new = 0;
832        for my $x (sort { $a <=> $b } @{$numbers}) {
833            my $backend = {
834                'name'    => $c->req->parameters->{'name'.$x},
835                'type'    => $c->req->parameters->{'type'.$x},
836                'id'      => $c->req->parameters->{'id'.$x},
837                'hidden'  => defined $c->req->parameters->{'hidden'.$x} ? $c->req->parameters->{'hidden'.$x} : 0,
838                'section' => $c->req->parameters->{'section'.$x},
839                'options' => {},
840            };
841            $backend->{'options'}->{'peer'}         = $c->req->parameters->{'peer'.$x}        if $c->req->parameters->{'peer'.$x};
842            $backend->{'options'}->{'auth'}         = $c->req->parameters->{'auth'.$x}        if $c->req->parameters->{'auth'.$x};
843            $backend->{'options'}->{'proxy'}        = $c->req->parameters->{'proxy'.$x}       if $c->req->parameters->{'proxy'.$x};
844            $backend->{'options'}->{'remote_name'}  = $c->req->parameters->{'remote_name'.$x} if $c->req->parameters->{'remote_name'.$x};
845            $x++;
846            $backend->{'name'} = 'backend '.$x if(!$backend->{'name'} && $backend->{'options'}->{'peer'});
847            next unless $backend->{'name'};
848            delete $backend->{'id'} if $backend->{'id'} eq '';
849
850            $backend->{'options'}->{'peer'} = Thruk::Base::list($backend->{'options'}->{'peer'});
851
852            for my $p (@{$backend->{'options'}->{'peer'}}) {
853                if($backend->{'type'} eq 'livestatus' and $p =~ m/^\d+\.\d+\.\d+\.\d+$/mx) {
854                    $p .= ':6557';
855                }
856            }
857
858            # add values from existing backend config
859            my $savefile = $file;
860            if(defined $backend->{'id'}) {
861                my $peer = $c->db->get_peer_by_key($backend->{'id'});
862                $backend->{'options'}->{'resource_file'} = $peer->{'resource_file'} if defined $peer->{'resource_file'};
863                $backend->{'options'}->{'fallback_peer'} = $peer->{'config'}->{'options'}->{'fallback_peer'} if defined $peer->{'config'}->{'options'}->{'fallback_peer'};
864                $backend->{'groups'}     = $peer->{'groups'}     if defined $peer->{'groups'};
865                $backend->{'configtool'} = $peer->{'configtool'} if defined $peer->{'configtool'};
866                $backend->{'logcache_fetchlogs_command'} = $peer->{'peer_config'}->{'logcache_fetchlogs_command'} if defined $peer->{'peer_config'}->{'logcache_fetchlogs_command'};
867                $savefile = $peer->{'peer_config'}->{'_FILE'} // $file;
868            }
869            $has_new = 1 if $x == 1;
870            $savefile =~ s|/thruk\.conf$|/thruk_local.conf|gmx;
871            $backends->{$savefile} = [] unless $backends->{$savefile};
872            push @{$backends->{$savefile}}, $backend;
873        }
874        # put new one at the end
875        if($has_new) { push(@{$backends->{$file}}, shift(@{$backends->{$file}})) }
876        for my $f (sort keys %{$backends}) {
877            my $string = Thruk::Utils::Conf::get_component_as_string($backends->{$f});
878            Thruk::Utils::Conf::replace_block($f, $string, '<Component\s+Thruk::Backend>', '<\/Component>\s*');
879        }
880        Thruk::Utils::set_message( $c, 'success_message', 'Backends changed successfully.' );
881        return Thruk::Utils::restart_later($c, $c->stash->{url_prefix}.'cgi-bin/conf.cgi?sub=backends');
882    }
883    if($c->stash->{action} eq 'check_con') {
884        return unless Thruk::Utils::check_csrf($c);
885        my $peer        = $c->req->parameters->{'peer'};
886        my $type        = $c->req->parameters->{'type'};
887        my $auth        = $c->req->parameters->{'auth'};
888        my $proxy       = $c->req->parameters->{'proxy'};
889        my $remote_name = $c->req->parameters->{'remote_name'};
890        my @test;
891        my $con;
892        eval {
893            local $ENV{'THRUK_USE_LMD'} = "";
894            $con = Thruk::Backend::Peer->new({
895                                                 type    => $type,
896                                                 name    => 'test connection',
897                                                 options => { peer => $peer, auth => $auth, proxy => $proxy, remote_name => $remote_name },
898                                                }, $c->config);
899            @test   = $con->{'class'}->get_processinfo();
900        };
901        my $json;
902        if(scalar @test >= 2 and ref $test[0] eq 'HASH' and scalar keys %{$test[0]} == 1 and scalar keys %{$test[0]->{(keys %{$test[0]})[0]}} > 0) {
903            $json = { ok => 1 };
904            if($type eq 'http') {
905                eval {
906                    my $who = $con->{'class'}->rest_request("/thruk/whoami", "POST", {});
907                    $json->{'whoami'} = $who;
908                };
909            }
910            $json->{'data'} = $test[0]->{(keys %{$test[0]})[0]};
911        } else {
912            my $error = $@;
913            $error =~ s/\s+at\s\/.*//gmx;
914            $error = 'got no valid result' if $error eq '';
915            $json = { ok => 0, error => $error };
916        }
917        return $c->render(json => $json);
918    }
919
920    my $backends = [];
921    my $peers    = $c->db->get_peers(1);
922    $peers = [{}] if scalar @{$peers} == 0;
923    for my $p (@{$peers}) {
924        my $b = Thruk::Utils::dclone($p->{'peer_config'});
925        next if(!$b->{'_LINE'} && $p->{'federation'});
926        $b->{'name'}        = $b->{'name'} // '';
927        $b->{'type'}        = lc($b->{'type'} // 'livestatus');
928        $b->{'id'}          = $p->{'key'}  // substr(Thruk::Utils::Crypt::hexdigest(($b->{'options'}->{'peer'} || '')." ".$b->{'name'}), 0, 5);
929        $b->{'addr'}        = $b->{'options'}->{'peer'}  || '';
930        $b->{'auth'}        = $b->{'options'}->{'auth'}  || '';
931        $b->{'proxy'}       = $b->{'options'}->{'proxy'} || '';
932        $b->{'remote_name'} = $b->{'options'}->{'remote_name'} || '';
933        $b->{'hidden'}      = $b->{'hidden'}  // 0;
934        $b->{'section'}     = $b->{'section'} // '';
935        $b->{'file'}        = $b->{'_FILE'}   // $file;
936        $b->{'lineno'}      = $b->{'_LINE'}   // '';
937        push @{$backends}, $b;
938    }
939    $c->stash->{'conf_file'}  = $file;
940    $c->stash->{'conf_sites'} = $backends;
941    $c->stash->{'subtitle'}   = "Thruk Backends Manager";
942    $c->stash->{'template'}   = 'conf_backends.tt';
943
944    return 1;
945}
946
947##########################################################
948# create the objects config page
949sub _process_objects_page {
950    my( $c ) = @_;
951
952    my $rc = Thruk::Utils::Conf::set_object_model($c);
953    if($rc == -1) {
954        $c->stash->{errorMessage}       = "config tool unavailable";
955        $c->stash->{errorDescription}   = $c->stash->{set_object_model_err} || '';
956        return $c->detach('/error/index/99');
957    } elsif($rc == 0) {
958        return;
959    }
960
961    _check_external_reload($c);
962
963    $c->stash->{'subtitle'}         = "Object Configuration";
964    $c->stash->{'template'}         = 'conf_objects.tt';
965    $c->stash->{'file_link'}        = "";
966    $c->stash->{'coretype'}         = $c->{'obj_db'}->{'coretype'};
967    $c->stash->{'bare'}             = $c->req->parameters->{'bare'} || 0;
968    $c->stash->{'has_history'}      = 0;
969
970    # start editing files
971    if(defined $c->req->parameters->{'start_edit'}) {
972        my $file = Thruk::Utils::Conf::start_file_edit($c, $c->req->parameters->{'start_edit'});
973        if($file) {
974            return $c->render(json => {'ok' => 1, hex => $file->{'hex'} });
975        }
976        return $c->render(json => {'ok' => 0 });
977    }
978
979    $c->{'obj_db'}->read_rc_file();
980
981    # check if we have a history for our configs
982    my $files_root = _set_files_stash($c);
983    my $dir        = $c->{'obj_db'}->{'config'}->{'git_base_dir'} || $c->config->{'Thruk::Plugin::ConfigTool'}->{'git_base_dir'} || $files_root;
984    if(-d $dir) {
985        my $cmd       = "cd '".$dir."' && git log --pretty='format:%H' -1 2>&1";
986        my($rc, $out) = Thruk::Utils::IO::cmd($c, $cmd);
987        $c->stash->{'has_history'} = 1 if $rc == 0;
988    }
989
990    # apply changes?
991    if(defined $c->req->parameters->{'apply'}) {
992        return if _apply_config_changes($c);
993    }
994
995    # tools menu
996    if(defined $c->req->parameters->{'tools'}) {
997        return if _process_tools_page($c);
998    }
999
1000    # get object from params
1001    $c->stash->{cloned}    = 0;
1002    $c->stash->{clone_ref} = Thruk::Base::list($c->req->parameters->{'clone_ref'} || []);
1003    my $obj = _get_context_object($c);
1004    if(defined $obj) {
1005
1006        # revert all changes from one file
1007        if($c->stash->{action} eq 'revert') {
1008            return unless Thruk::Utils::check_csrf($c);
1009            return if _object_revert($c, $obj);
1010        }
1011
1012        # save this object
1013        elsif($c->stash->{action} eq 'store') {
1014            return unless Thruk::Utils::check_csrf($c);
1015            my $rc = _object_save($c, $obj);
1016            if($rc && defined $c->req->parameters->{'cloned'}) {
1017                if(!$obj->is_template()) {
1018                    _clone_refs($c, $obj, $c->req->parameters->{'cloned'}, $c->req->parameters->{'clone_ref'});
1019                }
1020            }
1021
1022            # save changes to cgi.cfg
1023            my $type = $obj->get_type();
1024            my $file = $c->config->{'Thruk::Plugin::ConfigTool'}->{'cgi.cfg'};
1025            if($c->stash->{'conf_config'}->{'cgi.cfg'} && ($type eq 'contactgroup' || $type eq 'contact') && -w $file) {
1026                my $name     = $obj->get_name();
1027                my $defaults = Thruk::Utils::Conf::Defaults->get_cgi_cfg();
1028                my($content, $data, $hex) = Thruk::Utils::Conf::read_conf($file, $defaults);
1029                my $new_data              = {};
1030                for my $key (keys %{$c->req->parameters}) {
1031                    next unless $key =~ m/authdata\.authorized_for_/mx;
1032                    $key =~ s/^authdata\.//gmx;
1033                    my $key2 = $key;
1034                    if($type eq 'contactgroup') {
1035                        $key2 =~ s/^authorized_for_/authorized_contactgroup_for_/gmx;
1036                    }
1037                    my $users = {};
1038                    for my $usr (@{$data->{$key2}->[1]}) {
1039                        $users->{$usr} = 1;
1040                    }
1041                    if($c->req->parameters->{'authdata.'.$key}) {
1042                        $users->{$name} = 1;
1043                    } else {
1044                        delete $users->{$name};
1045                    }
1046                    @{$new_data->{$key2}} = sort keys %{$users};
1047                }
1048                _store_changes($c, $file, $new_data, $defaults, undef, 1);
1049            }
1050
1051            if(defined $c->req->parameters->{'save_and_reload'}) {
1052                return if _apply_config_changes($c);
1053            }
1054            return if !defined $rc;
1055            $c->stash->{cloned} = $c->req->parameters->{'cloned'} || 0;
1056        }
1057
1058        # disable this object temporarily
1059        elsif($c->stash->{action} eq 'disable') {
1060            return unless Thruk::Utils::check_csrf($c);
1061            return if _object_disable($c, $obj);
1062        }
1063
1064        # enable this object
1065        elsif($c->stash->{action} eq 'enable') {
1066            return unless Thruk::Utils::check_csrf($c);
1067            return if _object_enable($c, $obj);
1068        }
1069
1070        # delete this object
1071        elsif($c->stash->{action} eq 'delete') {
1072            return unless Thruk::Utils::check_csrf($c);
1073            return if _object_delete($c, $obj);
1074        }
1075
1076        # move objects
1077        elsif(   $c->stash->{action} eq 'move'
1078              or $c->stash->{action} eq 'movefile') {
1079            return if _object_move($c, $obj);
1080        }
1081
1082        # clone this object
1083        elsif($c->stash->{action} eq 'clone') {
1084            return unless Thruk::Utils::check_csrf($c);
1085            $c->stash->{cloned} = $obj->get_id() || 0;
1086            # select refs to clone if there are some
1087            if(!$obj->is_template() && !$c->req->parameters->{'clone_ref'}) {
1088                my $cloned_name = $obj->get_name();
1089                my $clonables   = $c->{'obj_db'}->clone_refs($obj, $obj, $cloned_name, $cloned_name."_2", undef, 1);
1090                if($clonables && scalar keys %{$clonables} > 0) {
1091                    $c->stash->{object}     = $obj;
1092                    $c->stash->{clonables}  = $clonables;
1093                    $c->stash->{'template'} = 'conf_choose_clone.tt';
1094                    return;
1095                }
1096            }
1097            $obj = _object_clone($c, $obj);
1098        }
1099
1100        # list services for host
1101        elsif($c->stash->{action} eq 'listservices' and $obj->get_type() eq 'host') {
1102            return if _host_list_services($c, $obj);
1103        }
1104
1105        # list references
1106        elsif($c->stash->{action} eq 'listref') {
1107            return if _list_references($c, $obj);
1108        }
1109    }
1110
1111    # create new object
1112    if($c->stash->{action} eq 'new') {
1113        $obj = _object_new($c);
1114    }
1115
1116    # browse files
1117    elsif($c->stash->{action} eq 'browser') {
1118        return if _file_browser($c);
1119    }
1120
1121    # object tree
1122    elsif($c->stash->{action} eq 'tree') {
1123        return if _object_tree($c);
1124    }
1125
1126    # object tree content
1127    elsif($c->stash->{action} eq 'tree_objects') {
1128        return if _object_tree_objects($c);
1129    }
1130
1131    # file editor
1132    elsif($c->stash->{action} eq 'editor') {
1133        return if _file_editor($c);
1134    }
1135
1136    # history
1137    elsif($c->stash->{action} eq 'history') {
1138        return if _file_history($c);
1139    }
1140
1141    # save changed files from editor
1142    elsif($c->stash->{action} eq 'savefile') {
1143        return unless Thruk::Utils::check_csrf($c);
1144        return if _file_save($c);
1145    }
1146
1147    # delete files/folders from browser
1148    elsif($c->stash->{action} eq 'deletefiles') {
1149        return unless Thruk::Utils::check_csrf($c);
1150        return if _file_delete($c);
1151    }
1152
1153    # undelete files/folders from browser
1154    elsif($c->stash->{action} eq 'undeletefiles') {
1155        return unless Thruk::Utils::check_csrf($c);
1156        return if _file_undelete($c);
1157    }
1158
1159    # set type and name of object
1160    if(defined $obj) {
1161        $c->stash->{'show_object'}    = 1;
1162        $c->stash->{'object'}         = $obj;
1163        $c->stash->{'data_name'}      = $obj->get_name();
1164        $c->stash->{'type'}           = $obj->get_type();
1165        $c->stash->{'used_templates'} = $obj->get_used_templates($c->{'obj_db'});
1166        # make sure object has either a real file or none
1167        if(!defined $obj->{'file'} || !defined $obj->{'file'}->{'path'}) {
1168            delete $obj->{'file'};
1169        }
1170        if($obj->{'file'} && $obj->{'file'}->{'path'} && !$obj->{'file'}->readonly()) {
1171            Thruk::Utils::Conf::start_file_edit($c, $obj->{'file'}->{'path'});
1172        }
1173        $c->stash->{'file_link'} = $obj->{'file'}->{'display'} if defined $obj->{'file'};
1174        _gather_references($c, $obj);
1175
1176        # add roles
1177        if($c->stash->{'conf_config'}->{'cgi.cfg'} && ($c->stash->{'type'} eq 'contactgroup' || $c->stash->{'type'} eq 'contact')) {
1178            my $file     = $c->config->{'Thruk::Plugin::ConfigTool'}->{'cgi.cfg'};
1179            my $defaults = Thruk::Utils::Conf::Defaults->get_cgi_cfg();
1180            my($content, $data, $hex) = Thruk::Utils::Conf::read_conf($file, $defaults);
1181            my $roles = $Thruk::Constants::possible_roles;
1182            $c->stash->{'hex'}       = $hex;
1183            $c->stash->{'role_keys'} = $roles;
1184            for my $role (@{$roles}) {
1185                $c->stash->{'roles'}->{$role} = 0;
1186                my $tstrole = $role;
1187                if($c->stash->{'type'} eq 'contactgroup') {
1188                    $tstrole =~ s/^authorized_for_/authorized_contactgroup_for_/gmx;
1189                }
1190                for my $tst (@{$data->{$tstrole}->[1]}) {
1191                    $c->stash->{'roles'}->{$role}++ if $tst eq $c->stash->{'data_name'};
1192                }
1193            }
1194        }
1195    }
1196
1197    # set default type for start page
1198    if($c->stash->{action} eq 'show' and $c->stash->{type} eq '') {
1199        $c->stash->{type} = 'host';
1200    }
1201
1202    $c->stash->{'needs_commit'}      = $c->{'obj_db'}->{'needs_commit'};
1203    $c->stash->{'last_changed'}      = $c->{'obj_db'}->{'last_changed'};
1204    $c->stash->{'referer'}           = $c->req->parameters->{'referer'} || '';
1205    $c->{'obj_db'}->_reset_errors(1);
1206    return 1;
1207}
1208
1209##########################################################
1210# apply config changes
1211sub _apply_config_changes {
1212    my ( $c ) = @_;
1213
1214    $c->stash->{'subtitle'}      = "Apply Config Changes";
1215    $c->stash->{'template'}      = 'conf_objects_apply.tt';
1216    $c->stash->{'output'}        = '';
1217    $c->stash->{'changed_files'} = $c->{'obj_db'}->get_changed_files();
1218
1219    local $ENV{'THRUK_SUMMARY_MESSAGE'} = $c->req->parameters->{'summary'}     if $c->req->parameters->{'summary'};
1220    local $ENV{'THRUK_SUMMARY_DETAILS'} = $c->req->parameters->{'summarydesc'} if $c->req->parameters->{'summarydesc'};
1221
1222    if(defined $c->req->parameters->{'save_and_reload'}) {
1223        return unless Thruk::Utils::check_csrf($c);
1224        # don't store in demo mode
1225        if($c->config->{'demo_mode'}) {
1226            Thruk::Utils::set_message( $c, 'fail_message', "save is disabled in demo mode" );
1227            return $c->redirect_to('conf.cgi?sub=objects&apply=yes');
1228        }
1229        if($c->{'obj_db'}->commit($c)) {
1230            # update changed files
1231            $c->stash->{'changed_files'} = $c->{'obj_db'}->get_changed_files();
1232            # set flag to do the reload
1233            $c->req->parameters->{'reload'} = 'yes';
1234        } else {
1235            return $c->redirect_to('conf.cgi?sub=objects&apply=yes');
1236        }
1237    }
1238
1239    # get diff of changed files
1240    if(defined $c->req->parameters->{'diff'}) {
1241        my $ignore_whitespace_changes = $c->req->parameters->{'ignore_whitespace'} // 0;
1242        my $diffs = {};
1243        for my $file (@{$c->stash->{'changed_files'}}) {
1244            my $diff = $file->diff($ignore_whitespace_changes);
1245            if($diff ne '') {
1246                $diffs->{$file->{'display'}} = $diff;
1247            }
1248        }
1249
1250        $c->stash->{'output'} .= "<hr style='margin: 0;'>\n";
1251        $c->stash->{'output'} .= "<form action='conf.cgi#output' method='POST' class='diffoptions'>\n";
1252        $c->stash->{'output'} .= "<input type='hidden' name='diff' value='1'>\n";
1253        $c->stash->{'output'} .= "<input type='hidden' name='apply' value='yes'>\n";
1254        $c->stash->{'output'} .= "<input type='hidden' name='sub' value='objects'>\n";
1255        $c->stash->{'output'} .= "<input type='checkbox' name='ignore_whitespace' id='ignore_whitespace' value='1'".($ignore_whitespace_changes ? ' checked' : '')."><label for='ignore_whitespace'>Ignore Whitespace Changes</label>\n";
1256        $c->stash->{'output'} .= "<input type='submit' value='update'>\n";
1257        $c->stash->{'output'} .= "</form>\n";
1258
1259        if(scalar keys %{$diffs} == 0) {
1260            if($ignore_whitespace_changes) {
1261                return $c->redirect_to('conf.cgi?sub=objects&apply=yes&diff=1&ignore_whitespace=0');
1262            }
1263            $c->stash->{'output'} .= "<br><br>no changes\n";
1264            return;
1265        }
1266
1267        $c->stash->{'output'} .= "<ul>\n";
1268        for my $file_display (sort keys %{$diffs}) {
1269            $c->stash->{'output'} .= "<li><a href='#".Thruk::Utils::Filter::name2id($file_display)."'>".$file_display."</a></li>\n";
1270        }
1271        $c->stash->{'output'} .= "</ul>\n";
1272
1273        for my $file_display (sort keys %{$diffs}) {
1274            $c->stash->{'output'} .= "<hr><a id='".Thruk::Utils::Filter::name2id($file_display)."'></a><pre>\n";
1275            $c->stash->{'output'} .= Thruk::Utils::Filter::escape_html($diffs->{$file_display});
1276            $c->stash->{'output'} .= "</pre><br>\n";
1277        }
1278
1279        $c->stash->{'output'} = Thruk::Utils::beautify_diff($c->stash->{'output'});
1280    }
1281
1282    # config check
1283    elsif(defined $c->req->parameters->{'check'}) {
1284        return unless Thruk::Utils::check_csrf($c);
1285        if(defined $c->stash->{'peer_conftool'}->{'obj_check_cmd'}) {
1286            $c->stash->{'parse_errors'} = $c->{'obj_db'}->{'parse_errors'};
1287            Thruk::Utils::External::perl($c, { expr    => 'Thruk::Controller::conf::_config_check($c)',
1288                                               message => 'please stand by while configuration is being checked...',
1289                                        });
1290            return;
1291        } else {
1292            Thruk::Utils::set_message( $c, 'fail_message', "please set 'obj_check_cmd' in your thruk_local.conf" );
1293        }
1294    }
1295
1296    # config reload
1297    elsif(defined $c->req->parameters->{'reload'}) {
1298        return unless Thruk::Utils::check_csrf($c);
1299        # don't store in demo mode
1300        if($c->config->{'demo_mode'}) {
1301            Thruk::Utils::set_message( $c, 'fail_message', "reload is disabled in demo mode" );
1302            return $c->redirect_to('conf.cgi?sub=objects&apply=yes');
1303        }
1304        if(defined $c->stash->{'peer_conftool'}->{'obj_reload_cmd'} or $c->db->get_peer_by_key($c->stash->{'param_backend'})->{'type'} ne 'configonly') {
1305            $c->stash->{'parse_errors'} = $c->{'obj_db'}->{'parse_errors'};
1306            Thruk::Utils::External::perl($c, { expr    => 'Thruk::Controller::conf::_config_reload($c)',
1307                                               message => 'please stand by while configuration is being reloaded...',
1308                                        });
1309            return;
1310        } else {
1311            Thruk::Utils::set_message( $c, 'fail_message', "please set 'obj_reload_cmd' in your thruk_local.conf" );
1312        }
1313    }
1314
1315    # save changes to file
1316    elsif(defined $c->req->parameters->{'save'}) {
1317        return unless Thruk::Utils::check_csrf($c);
1318        # don't store in demo mode
1319        if($c->config->{'demo_mode'}) {
1320            Thruk::Utils::set_message( $c, 'fail_message', "save is disabled in demo mode" );
1321            return $c->redirect_to('conf.cgi?sub=objects&apply=yes');
1322        }
1323        if($c->{'obj_db'}->commit($c)) {
1324            $c->stash->{'obj_model_changed'} = 1;
1325            Thruk::Utils::set_message( $c, 'success_message', 'Changes saved to disk successfully' );
1326        }
1327        return $c->redirect_to('conf.cgi?sub=objects&apply=yes');
1328    }
1329
1330    # discard changes
1331    if($c->req->parameters->{'discard'}) {
1332        return unless Thruk::Utils::check_csrf($c);
1333        $c->{'obj_db'}->discard_changes();
1334        $c->stash->{'obj_model_changed'} = 1;
1335        Thruk::Utils::set_message( $c, 'success_message', 'Changes have been discarded' );
1336        return $c->redirect_to('conf.cgi?sub=objects&apply=yes');
1337    }
1338
1339    $c->stash->{'needs_commit'} = $c->{'obj_db'}->{'needs_commit'};
1340    $c->stash->{'last_changed'} = $c->{'obj_db'}->{'last_changed'};
1341    $c->stash->{'files'}        = $c->{'obj_db'}->get_files();
1342    return;
1343}
1344
1345##########################################################
1346# show tools page
1347sub _process_tools_page {
1348    my ( $c ) = @_;
1349
1350    $c->stash->{'subtitle'}  = 'Config Tools';
1351    $c->stash->{'template'}  = 'conf_objects_tools.tt';
1352    $c->stash->{'output'}    = '';
1353    $c->stash->{'action'}    = 'tools';
1354    $c->stash->{'results'}   = [];
1355    my $tool                 = $c->req->parameters->{'tools'} || '';
1356    $tool                    =~ s/[^a-zA-Z_]//gmx;
1357    my $tools                = _get_tools($c);
1358    $c->stash->{'tools'}     = $tools;
1359    my $ignore_file          = $c->config->{'var_path'}.'/conf_tools_ignore';
1360    my $ignores              = -s $ignore_file ? Thruk::Utils::IO::json_lock_retrieve($ignore_file) : {};
1361    $c->stash->{'tool'}      = $tool;
1362
1363    $c->stats->profile(begin => "tool: ".$tool);
1364
1365    if($tool eq 'start') {
1366    }
1367    elsif($tool eq 'reset_ignores') {
1368        $tool = $c->req->parameters->{'oldtool'};
1369        if(defined $tools->{$tool}) {
1370            $ignores->{$tool} = {};
1371            Thruk::Utils::IO::json_lock_store($ignore_file, $ignores);
1372            Thruk::Utils::set_message( $c, 'success_message', "successfully reset ignores" );
1373        }
1374        $c->stats->profile(end => "tool: ".$tool);
1375        return $c->redirect_to('conf.cgi?sub=objects&tools='.$tool);
1376    }
1377    elsif(defined $tools->{$tool}) {
1378        if($c->req->parameters->{'ignore'}) {
1379            $ignores->{$tool}->{$c->req->parameters->{'ident'}} = 1;
1380            Thruk::Utils::IO::json_lock_store($ignore_file, $ignores);
1381            my $json = {'ok' => 1};
1382            $c->stats->profile(end => "tool: ".$tool);
1383            return $c->render(json => $json);
1384        }
1385        # this might take a while
1386        $c->stash->{'parse_errors'} = $c->{'obj_db'}->{'parse_errors'};
1387        return if Thruk::Utils::External::render_page_in_background($c);
1388
1389        $c->stash->{'toolobj'} = $tools->{$tool};
1390        if($c->req->parameters->{'cleanup'} && $c->req->parameters->{'ident'}) {
1391            $c->stats->profile(begin => "tool cleanup");
1392            $tools->{$tool}->cleanup($c, $c->req->parameters->{'ident'}, $ignores->{$tool});
1393            $c->stats->profile(end => "tool cleanup");
1394            Thruk::Utils::Conf::store_model_retention($c, $c->stash->{'param_backend'}); # directly store because its directly called from render_page_in_background()
1395            if($c->req->parameters->{'ident'} eq 'all') {
1396                $c->stats->profile(end => "tool: ".$tool);
1397                return $c->redirect_to('conf.cgi?sub=objects&tools='.$tool);
1398            }
1399            my $json = {'ok' => 1};
1400            $c->stats->profile(end => "tool: ".$tool);
1401            return $c->render(json => $json);
1402        } else {
1403            $c->stats->profile(begin => "tool get_list");
1404            my($hidden, $results) = $tools->{$tool}->get_list($c, $ignores->{$tool});
1405            $c->stats->profile(end => "tool get_list");
1406            @{$results} = sort { $a->{'type'} cmp $b->{'type'} || $a->{'name'} cmp $b->{'name'} } @{$results};
1407            $c->stash->{'results'} = $results;
1408            $c->stash->{'hidden'}  = $hidden;
1409        }
1410    } else {
1411        $c->stash->{'tool'} = "start";
1412    }
1413
1414    # sort tools into categories
1415    my $tools_by_category = {};
1416    for my $name (keys %{$tools}) {
1417        my $t = $tools->{$name};
1418        $tools_by_category->{$t->{category}}->{$name} = $t;
1419    }
1420    $c->stash->{'tools_by_category'} = $tools_by_category;
1421
1422    $c->stats->profile(end => "tool: ".$tool);
1423    return;
1424}
1425
1426##########################################################
1427# create the users password page
1428sub _process_user_password_page {
1429    my($c) = @_;
1430
1431    my $referer = $c->req->parameters->{'referer'} || $c->stash->{'url_prefix'}.'main.html';
1432    if($c->config->{'disable_user_password_change'}) {
1433        Thruk::Utils::set_message($c, 'fail_message', "Changing passwords is disabled.");
1434        return $c->redirect_to($referer);
1435    }
1436
1437    my $htpw_file = $c->config->{'Thruk::Plugin::ConfigTool'}->{'htpasswd'};
1438    if(!$htpw_file || !-w $htpw_file) {
1439        Thruk::Utils::set_message($c, 'fail_message', "Changing passwords is disabled.");
1440        return $c->redirect_to($referer);
1441    }
1442
1443    my $binary = _get_htpasswd();
1444    if(!$binary) {
1445        Thruk::Utils::set_message($c, 'fail_message', 'could not find htpasswd or htpasswd2, cannot update passwords');
1446        return $c->redirect_to($referer);
1447    }
1448    my $help        = Thruk::Utils::IO::cmd("$binary --help 2>&1");
1449    my $has_minus_v = $help =~ m|\s+\-v\s+|gmx;
1450
1451    my $user     = $c->user->get('username');
1452    my $htpasswd = Thruk::Utils::Conf::read_htpasswd($htpw_file);
1453    if(!defined $htpasswd->{$user}) {
1454        Thruk::Utils::set_message($c, 'fail_message', "Your password cannot be changed.");
1455        return $c->redirect_to($referer);
1456    }
1457
1458    # change password?
1459    if($c->req->parameters->{'save'}) {
1460        return unless Thruk::Utils::check_csrf($c);
1461
1462        my $old        = $c->req->parameters->{'data.old'}        || '';
1463        my $pass1      = $c->req->parameters->{'data.password'}   || '';
1464        my $pass2      = $c->req->parameters->{'data.password2'}  || '';
1465        my $min_length = $c->config->{'user_password_min_length'} || 5;
1466        if($has_minus_v && !$old) {
1467            Thruk::Utils::set_message($c, 'fail_message', "Current password missing");
1468        }
1469        elsif($pass1 eq '' || $pass2 eq '') {
1470            Thruk::Utils::set_message($c, 'fail_message', "New password cannot be empty");
1471        }
1472        elsif($pass1 !~ /[A-Z]/ ) {
1473            Thruk::Utils::set_message($c, 'fail_message', "New password should contain some UPPER case");
1474        }
1475        elsif($pass1 !~ /[0-9]/ ) {
1476            Thruk::Utils::set_message($c, 'fail_message', "New password should contain some Digit ");
1477        }
1478        elsif($pass1 !~ /[@#*=]/ ) {
1479            Thruk::Utils::set_message($c, 'fail_message', "New password should contain some symbol @#*= ");
1480        }
1481        elsif($pass1 =~ / / ) {
1482            Thruk::Utils::set_message($c, 'fail_message', "New password contain some white space ");
1483        }
1484        #elsif($pass1 =~ /^\d*$/) {
1485        #    Thruk::Utils::set_message($c, 'fail_message', "New password contain only number");
1486        #}
1487        #elsif($pass1 =~ /[^\w\s]/) {
1488        #    Thruk::Utils::set_message($c, 'fail_message', "New password contain non char");
1489        #}
1490        elsif(length($pass1) < $min_length) {
1491            Thruk::Utils::set_message($c, 'fail_message', "New password must have at least ".$min_length." characters.");
1492        }
1493        #elsif($pass1 ne '' && $pass1 eq $pass2) {
1494        elsif($pass1 ne '' && $pass1 eq $pass2 ) {
1495            my $err = _htpasswd_password($c, $user, $pass1, $old);
1496            if($err) {
1497                _error("changing password for ".$user." failed: ".$err);
1498                Thruk::Utils::set_message($c, 'fail_message', "Password change failed.");
1499            } else {
1500                _audit_log("configtool", "new password set for user ".$user);
1501                Thruk::Utils::set_message($c, 'success_message', "Password changed successfully");
1502            }
1503        }
1504        return $c->redirect_to('conf.cgi?action=user_password');
1505    }
1506
1507    $c->stash->{'show_old_pass'} = $has_minus_v ? 1 : 0;
1508    $c->stash->{'subtitle'}      = "Change Password";
1509    $c->stash->{'template'}      = 'conf_user_password.tt';
1510
1511    return 1;
1512}
1513
1514##########################################################
1515# get list of all tools
1516sub _get_tools {
1517    my ($c) = @_;
1518
1519    my $modules = Thruk::Utils::find_modules('/Thruk/Utils/Conf/Tools/*.pm');
1520    my $tools   = {};
1521    for my $file (@{$modules}) {
1522        require $file;
1523        my $class = $file;
1524        $class    =~ s|/|::|gmx;
1525        $class    =~ s|\.pm$||gmx;
1526        $class->import;
1527        my $tool = $class->new();
1528        my $name = $class;
1529        $name =~ s|Thruk::Utils::Conf::Tools::||gmx;
1530        $tools->{$name} = $tool;
1531    }
1532
1533    return($tools);
1534}
1535
1536##########################################################
1537# update a users password
1538sub _update_password {
1539    my ( $c ) = @_;
1540
1541    my $user = $c->req->parameters->{'data.username'};
1542    my $send = $c->req->parameters->{'send'} || 'save';
1543    if(defined $c->config->{'Thruk::Plugin::ConfigTool'}->{'htpasswd'}) {
1544        # remove password?
1545        if($send eq 'remove password') {
1546            return unless Thruk::Utils::check_csrf($c);
1547            my $err = _htpasswd_password($c, $user, undef);
1548            return $err if $err;
1549            _audit_log("configtool", "password removed for user ".$user);
1550            return;
1551        }
1552
1553        # change password?
1554        my $pass1 = $c->req->parameters->{'data.password'}  || '';
1555        my $pass2 = $c->req->parameters->{'data.password2'} || '';
1556        if($pass1 ne '') {
1557            return unless Thruk::Utils::check_csrf($c);
1558            if($pass1 eq $pass2) {
1559                my $err = _htpasswd_password($c, $user, $pass1);
1560                return $err if $err;
1561                _audit_log("configtool", "new password set for user ".$user);
1562                return;
1563            } else {
1564                return('Passwords do not match');
1565            }
1566        }
1567    }
1568    return;
1569}
1570
1571##########################################################
1572# store changes to a file
1573sub _htpasswd_password {
1574    my($c, $user, $password, $oldpassword) = @_;
1575
1576    my $htpasswd = _get_htpasswd();
1577    return('could not find htpasswd or htpasswd2, cannot update passwords') unless $htpasswd;
1578
1579    if(!defined $password) {
1580        my $cmd = [ $htpasswd, '-D', $c->config->{'Thruk::Plugin::ConfigTool'}->{'htpasswd'}, $user ];
1581        if(_cmd($c, $cmd)) {
1582            return;
1583        }
1584        _error("failed to remove password.");
1585        _error("cmd: ".join(" ", @{$cmd}));
1586        _error($c->stash->{'output'});
1587        return( 'failed to remove password, check the logfile!' );
1588    }
1589
1590    # check if htpasswd support -i switch
1591    my $help = Thruk::Utils::IO::cmd("$htpasswd --help 2>&1");
1592    my $has_minus_i = $help =~ m|\s+\-i\s+|gmx;
1593    my $has_minus_v = $help =~ m|\s+\-v\s+|gmx;
1594
1595    # check old password first?
1596    if($has_minus_v && $oldpassword) {
1597        my $cmd = [$htpasswd];
1598        push @{$cmd}, '-c' unless -s $c->config->{'Thruk::Plugin::ConfigTool'}->{'htpasswd'};
1599        push @{$cmd}, '-i' if  $has_minus_i;
1600        push @{$cmd}, '-b' if !$has_minus_i;
1601        push @{$cmd}, '-v';
1602        push @{$cmd}, $c->config->{'Thruk::Plugin::ConfigTool'}->{'htpasswd'};
1603        push @{$cmd}, $user;
1604        push @{$cmd}, $oldpassword if !$has_minus_i;
1605        if(!_cmd($c, $cmd, $has_minus_i ? $oldpassword : undef)) {
1606            return('old password did not match');
1607        }
1608    }
1609
1610    my $cmd = [$htpasswd];
1611    push @{$cmd}, '-c' unless -s $c->config->{'Thruk::Plugin::ConfigTool'}->{'htpasswd'};
1612    push @{$cmd}, '-i' if $has_minus_i;
1613    push @{$cmd}, '-b' if !$has_minus_i;
1614    push @{$cmd}, $c->config->{'Thruk::Plugin::ConfigTool'}->{'htpasswd'};
1615    push @{$cmd}, $user;
1616    push @{$cmd}, $password if !$has_minus_i;
1617    if(_cmd($c, $cmd, $has_minus_i ? $password : undef)) {
1618        return;
1619    }
1620    if(!$has_minus_i) {
1621        pop @{$cmd};
1622        push @{$cmd}, '****';
1623    }
1624    _error("failed to update password.");
1625    _error("cmd: ".join(" ", @{$cmd}));
1626    _error($c->stash->{'output'});
1627    return('failed to update password, check the logfile!');
1628}
1629
1630##########################################################
1631# returns htpasswd path
1632sub _get_htpasswd {
1633    # htpasswd is usually somewhere in sbin
1634    # SLES11: htpasswd2 /usr/bin
1635    local $ENV{'PATH'} = ($ENV{'PATH'} || '').':/usr/sbin:/sbin:/usr/bin';
1636    my $htpasswd = Thruk::Utils::which('htpasswd2') || Thruk::Utils::which('htpasswd');
1637    return($htpasswd);
1638}
1639
1640##########################################################
1641# store changes to a file
1642sub _store_changes {
1643    my ( $c, $file, $data, $defaults, $update_in_conf, $ignore_no_changes_made) = @_;
1644    return unless Thruk::Utils::check_csrf($c);
1645    my $old_hex = $c->req->parameters->{'hex'};
1646    if(!defined $old_hex || $old_hex eq '') {
1647        Thruk::Utils::set_message( $c, 'success_message', "no changes made." );
1648        return;
1649    }
1650    # don't store in demo mode
1651    if($c->config->{'demo_mode'}) {
1652        Thruk::Utils::set_message( $c, 'fail_message', "save is disabled in demo mode." );
1653        return;
1654    }
1655    _debug("saving config changes to ".$file);
1656    my $res = Thruk::Utils::Conf::update_conf($file, $data, $old_hex, $defaults, $update_in_conf);
1657    if(defined $res) {
1658        if($res eq "no changes made." && $ignore_no_changes_made) {
1659            return;
1660        }
1661        Thruk::Utils::set_message( $c, 'fail_message', $res );
1662        return;
1663    } else {
1664        Thruk::Utils::set_message( $c, 'success_message', 'Saved successfully.' );
1665    }
1666    return 1;
1667}
1668
1669##########################################################
1670# execute cmd
1671sub _cmd {
1672    my($c, $cmd, $stdin) = @_;
1673
1674    my($rc, $output) = Thruk::Utils::IO::cmd($c, $cmd, $stdin);
1675    $c->stash->{'output'} = $output;
1676    if($rc != 0) {
1677        return 0;
1678    }
1679    return 1;
1680}
1681
1682##########################################################
1683sub _find_files {
1684    my($c, $dir, $types) = @_;
1685    my $files = $c->{'obj_db'}->_get_files_for_folder($dir, $types);
1686    return $files;
1687}
1688
1689##########################################################
1690sub _get_context_object {
1691    my($c) = @_;
1692
1693    my $obj;
1694
1695    $c->stash->{'type'}          = $c->req->parameters->{'type'}       || '';
1696    $c->stash->{'subcat'}        = $c->req->parameters->{'subcat'}     || 'config';
1697    $c->stash->{'data_name'}     = $c->req->parameters->{'data.name'}  || '';
1698    $c->stash->{'data_name2'}    = $c->req->parameters->{'data.name2'} || '';
1699    $c->stash->{'data_id'}       = $c->req->parameters->{'data.id'}    || '';
1700    $c->stash->{'file_name'}     = $c->req->parameters->{'file'};
1701    $c->stash->{'file_line'}     = $c->req->parameters->{'line'};
1702    $c->stash->{'data_name'}     =~ s/^(.*)\ \ \-\ \ .*$/$1/gmx;
1703    $c->stash->{'data_name'}     =~ s/\ \(disabled\)$//gmx;
1704    $c->stash->{'type'}          = lc $c->stash->{'type'};
1705    $c->stash->{'show_object'}   = 0;
1706    $c->stash->{'show_secondary_select'} = 0;
1707
1708    if(defined $c->req->parameters->{'service'} and defined $c->req->parameters->{'host'}) {
1709        $c->stash->{'type'} = 'service';
1710        my $objs = $c->{'obj_db'}->get_services_by_name($c->req->parameters->{'host'}, $c->req->parameters->{'service'});
1711        if(defined $objs->[0]) {
1712            $c->stash->{'data_id'} = $objs->[0]->get_id();
1713        }
1714    }
1715    elsif(defined $c->req->parameters->{'host'}) {
1716        $c->stash->{'type'} = 'host';
1717        $c->stash->{'data_name'}  = $c->req->parameters->{'host'};
1718    }
1719
1720    # remove leading plus signs (used to append to lists) and leading ! (used to negate in lists)
1721    $c->stash->{'data_name'} =~ s/^(\+|\!)//mx;
1722
1723    # new object
1724    if($c->stash->{'data_id'} and $c->stash->{'data_id'} eq 'new') {
1725        $obj = Monitoring::Config::Object->new( type     => $c->stash->{'type'},
1726                                                coretype => $c->{'obj_db'}->{'coretype'},
1727                                              );
1728        my $new_file   = $c->req->parameters->{'data.file'} || '';
1729        my $file = get_context_file($c, $obj, $new_file);
1730        return $obj unless $file;
1731        $obj->set_file($file);
1732        $obj->set_uniq_id($c->{'obj_db'});
1733        return $obj;
1734    }
1735
1736    # object by id
1737    if($c->stash->{'data_id'}) {
1738        $obj = $c->{'obj_db'}->get_object_by_id($c->stash->{'data_id'});
1739    }
1740
1741    # link from file to an object?
1742    if(!defined $obj && defined $c->stash->{'file_name'} && defined $c->stash->{'file_line'} && $c->stash->{'file_line'} =~ m/^\d+$/mx) {
1743        $obj = $c->{'obj_db'}->get_object_by_location($c->stash->{'file_name'}, $c->stash->{'file_line'});
1744        unless(defined $obj) {
1745            Thruk::Utils::set_message( $c, 'fail_message', 'No such object found in this file' );
1746        }
1747    }
1748
1749    # object by name
1750    my @objs;
1751    if(!defined $obj && $c->stash->{'data_name'} ) {
1752        my $templates;
1753        if($c->stash->{'data_name'} =~ m/^ht:/mx or $c->stash->{'data_name'} =~ m/^st:/mx) {
1754            $templates=1; # only templates
1755        }
1756        if($c->stash->{'data_name'} =~ m/^ho:/mx or $c->stash->{'data_name'} =~ m/^se:/mx) {
1757            $templates=2; # no templates
1758        }
1759        $c->stash->{'data_name'} =~ s/^\w{2}://gmx;
1760        my $objs = $c->{'obj_db'}->get_objects_by_name($c->stash->{'type'}, $c->stash->{'data_name'}, 0, $c->stash->{'data_name2'});
1761        if(defined $templates) {
1762            my @newobjs;
1763            for my $o (@{$objs}) {
1764                if($templates == 1) {
1765                    push @newobjs, $o if $o->is_template();
1766                }
1767                if($templates == 2) {
1768                    push @newobjs, $o if !defined $o->{'conf'}->{'register'} || $o->{'conf'}->{'register'} != 0;
1769                }
1770            }
1771            @{$objs} = @newobjs;
1772        }
1773        if(defined $objs->[1]) {
1774            @objs = @{$objs};
1775            $c->stash->{'show_secondary_select'} = 1;
1776        }
1777        elsif(defined $objs->[0]) {
1778            $obj = $objs->[0];
1779        }
1780        elsif(!defined $obj) {
1781            Thruk::Utils::set_message( $c, { style => 'fail_message', msg => 'No such object. <a href="conf.cgi?sub=objects&action=new&amp;type='.Thruk::Utils::Filter::escape_html($c->stash->{'type'}).'&amp;data.name='.Thruk::Utils::Filter::escape_html($c->stash->{'data_name'}).'">Create it.</a>', escape => 0 } );
1782        }
1783    }
1784
1785    return $obj;
1786}
1787
1788##########################################################
1789
1790=head2 get_context_file
1791
1792    returns file for name, creates new file unless already existing.
1793
1794=cut
1795sub get_context_file {
1796    my($c, $obj, $new_file) = @_;
1797    my $files_root = $c->{'obj_db'}->get_files_root();
1798    if($files_root eq '') {
1799        $c->stash->{'new_file'} = '';
1800        Thruk::Utils::set_message($c, 'fail_message', 'Failed to create new file: please set at least one directory in your obj config.');
1801        return;
1802    }
1803    my $fullpath   = $files_root.'/'.$new_file;
1804    $fullpath      =~ s|\/+|\/|gmx;
1805    my $file       = $c->{'obj_db'}->get_file_by_path($fullpath);
1806    if(defined $file) {
1807        if($file->readonly()) {
1808            Thruk::Utils::set_message( $c, 'fail_message', 'File matches readonly pattern' );
1809            $c->stash->{'new_file'} = '/'.$new_file;
1810            return;
1811        }
1812    } else {
1813        # new file
1814        my $remotepath = $fullpath;
1815        my $localpath  = $remotepath;
1816        if($c->{'obj_db'}->is_remote()) {
1817            $localpath  = $c->{'obj_db'}->{'config'}->{'localdir'}.'/'.$localpath;
1818        }
1819        $file = Monitoring::Config::File->new($localpath, $c->{'obj_db'}->{'config'}->{'obj_readonly'}, $c->{'obj_db'}->{'coretype'}, undef, $remotepath);
1820        if(defined $file and $file->readonly()) {
1821            Thruk::Utils::set_message( $c, 'fail_message', 'Failed to create new file: file matches readonly pattern' );
1822            $c->stash->{'new_file'} = '/'.$new_file;
1823            return;
1824        }
1825        elsif(defined $file) {
1826            $c->{'obj_db'}->file_add($file);
1827        }
1828        else {
1829            $c->stash->{'new_file'} = '';
1830            Thruk::Utils::set_message( $c, 'fail_message', 'Failed to create new file: invalid path' );
1831            return;
1832        }
1833    }
1834    return $file;
1835}
1836
1837##########################################################
1838sub _translate_type {
1839    my($type) = @_;
1840    my $tt   = {
1841        'host_name'      => 'host',
1842        'hostgroup_name' => 'hostgroup',
1843    };
1844    return $tt->{$type} if defined $type;
1845    return;
1846}
1847
1848##########################################################
1849sub _files_to_path {
1850    my($c, $files) = @_;
1851
1852    my $folder = { 'dirs' => {}, 'files' => {}, 'path' => '', 'date' => '' };
1853
1854    my $ro_pattern = $c->{'obj_db'}->{'config'}->{'obj_readonly'};
1855    $ro_pattern = [] unless defined $ro_pattern;
1856    if(ref $ro_pattern eq '') { $ro_pattern = [$ro_pattern]; }
1857
1858    for my $file (@{$files}) {
1859        my @parts    = split(/\//mx, $file->{'display'});
1860        my $filename = pop @parts;
1861        my $subdir = $folder;
1862        for my $dir (@parts) {
1863            $dir = $dir."/";
1864            unless(defined $subdir->{'dirs'}->{$dir}) {
1865                my $readonly = 0;
1866                my $fulldir = $subdir->{'path'}.$dir;
1867                for my $p (@{$ro_pattern}) {
1868                    if($fulldir =~ m/$p/mx) {
1869                        $readonly = 1;
1870                        last;
1871                    }
1872                }
1873                my @stat = stat($fulldir);
1874                $subdir->{'dirs'}->{$dir} = {
1875                                             'dirs'     => {},
1876                                             'files'    => {},
1877                                             'path'     => $fulldir,
1878                                             'date'     => Thruk::Utils::Filter::date_format($c, $stat[9]),
1879                                             'readonly' => $readonly,
1880                                            };
1881            }
1882            $subdir = $subdir->{'dirs'}->{$dir};
1883        }
1884        $subdir->{'files'}->{$filename} = {
1885                                           'date'     => Thruk::Utils::Filter::date_format($c, $file->{'mtime'}),
1886                                           'deleted'  => $file->{'deleted'},
1887                                           'readonly' => $file->{'readonly'},
1888                                        };
1889    }
1890
1891    while(scalar keys %{$folder->{'files'}} == 0 && scalar keys %{$folder->{'dirs'}} == 1) {
1892        my @subdirs = keys %{$folder->{'dirs'}};
1893        my $dir = shift @subdirs;
1894        $folder = $folder->{'dirs'}->{$dir};
1895    }
1896
1897    return($folder);
1898}
1899
1900##########################################################
1901sub _set_files_stash {
1902    my($c, $skip_readonly_files) = @_;
1903
1904    my $all_files  = $c->{'obj_db'}->get_files();
1905    my $files_tree = _files_to_path($c, $all_files);
1906    my $files_root = $files_tree->{'path'};
1907    my @filenames;
1908    for my $file (@{$all_files}) {
1909        next if($skip_readonly_files and $file->{'readonly'});
1910        my $filename = $file->{'display'};
1911        $filename    =~ s/^$files_root/\//gmx;
1912        push @filenames, $filename;
1913    }
1914
1915    # file root is empty when there are no files (yet)
1916    if($files_root eq '') {
1917        my $dirs = Thruk::Base::list($c->{'obj_db'}->{'config'}->{'obj_dir'});
1918        if(defined $dirs->[0]) {
1919            $files_root = $dirs->[0];
1920            $files_root =~ s/\/*$//gmx;
1921            $files_root = $files_root.'/';
1922        }
1923    }
1924
1925    $c->stash->{'filenames_json'} = Thruk::Utils::Filter::json_encode([{ name => 'files', data => [ sort @filenames ]}]);
1926    $c->stash->{'files_json'}     = Thruk::Utils::Filter::json_encode($files_tree);
1927    $c->stash->{'files_tree'}     = $files_tree;
1928
1929    return $files_root;
1930}
1931
1932##########################################################
1933sub _object_revert {
1934    my($c, $obj) = @_;
1935
1936    my $id = $obj->get_id();
1937    if(-e $obj->{'file'}->{'path'}) {
1938        my $oldobj;
1939        my $tmpfile = Monitoring::Config::File->new($obj->{'file'}->{'path'}, undef, $c->{'obj_db'}->{'coretype'});
1940        $tmpfile->update_objects();
1941        for my $o (@{$tmpfile->{'objects'}}) {
1942            if($id eq $o->get_id()) {
1943                $oldobj = $o;
1944                last;
1945            }
1946        }
1947        if(defined $oldobj) {
1948            $c->stash->{'obj_model_changed'} = 1;
1949            $c->{'obj_db'}->update_object($obj, dclone($oldobj->{'conf'}), join("\n", @{$oldobj->{'comments'}}));
1950            Thruk::Utils::set_message( $c, 'success_message', ucfirst($obj->get_type()).' reverted successfully' );
1951        } else {
1952            Thruk::Utils::set_message( $c, 'fail_message', 'Cannot revert new objects, you can just delete them.' );
1953        }
1954    } else {
1955        Thruk::Utils::set_message( $c, 'fail_message', 'Cannot revert new objects, you can just delete them.' );
1956    }
1957
1958    return $c->redirect_to('conf.cgi?sub=objects&data.id='.$obj->get_id());
1959}
1960
1961##########################################################
1962sub _object_disable {
1963    my($c, $obj) = @_;
1964
1965    my $id = $obj->get_id();
1966    $obj->{'disabled'}               = 1;
1967    $obj->{'file'}->{'changed'}      = 1;
1968    $c->{'obj_db'}->{'needs_commit'} = 1;
1969    $c->stash->{'obj_model_changed'} = 1;
1970    Thruk::Utils::set_message( $c, 'success_message', ucfirst($obj->get_type()).' disabled successfully' );
1971
1972    # store log message
1973    $c->{'obj_db'}->{'logs'} = [] unless $c->{'obj_db'}->{'logs'};
1974    push @{$c->{'obj_db'}->{'logs'}},
1975        sprintf("[config][%s][%s] disabled %s '%s'",
1976                                $c->db->get_peer_by_key($c->stash->{'param_backend'})->{'name'},
1977                                $c->stash->{'remote_user'},
1978                                $obj->get_type(),
1979                                $obj->get_name(),
1980    );
1981
1982    return $c->redirect_to('conf.cgi?sub=objects&data.id='.$obj->get_id());
1983}
1984
1985##########################################################
1986sub _object_enable {
1987    my($c, $obj) = @_;
1988
1989    my $id = $obj->get_id();
1990    $obj->{'disabled'}               = 0;
1991    $obj->{'file'}->{'changed'}      = 1;
1992    $c->{'obj_db'}->{'needs_commit'} = 1;
1993    $c->stash->{'obj_model_changed'} = 1;
1994    Thruk::Utils::set_message( $c, 'success_message', ucfirst($obj->get_type()).' enabled successfully' );
1995
1996    # create log message
1997    $c->{'obj_db'}->{'logs'} = [] unless $c->{'obj_db'}->{'logs'};
1998    push @{$c->{'obj_db'}->{'logs'}},
1999        sprintf("[config][%s][%s] enabled %s '%s'",
2000                                $c->db->get_peer_by_key($c->stash->{'param_backend'})->{'name'},
2001                                $c->stash->{'remote_user'},
2002                                $obj->get_type(),
2003                                $obj->get_name(),
2004    );
2005
2006    return $c->redirect_to('conf.cgi?sub=objects&data.id='.$obj->get_id());
2007}
2008
2009##########################################################
2010sub _object_delete {
2011    my($c, $obj) = @_;
2012
2013    my $refs       = $c->{'obj_db'}->get_references($obj);
2014    my $other_refs = _get_non_config_tool_references($c, $obj);
2015
2016    if(!$c->req->parameters->{'force'}) {
2017        if(scalar keys %{$refs} || scalar keys %{$other_refs}) {
2018            return $c->redirect_to('conf.cgi?sub=objects&action=listref&data.id='.$obj->get_id().'&show_force=1');
2019        }
2020    }
2021
2022    if($c->req->parameters->{'ref'}) {
2023        my $name        = $obj->get_name();
2024        my $refs_delete = Thruk::Base::list($c->req->parameters->{'ref'});
2025        for my $id (@{$refs_delete}) {
2026            for my $type (keys %{$refs}) {
2027                if($refs->{$type}->{$id}) {
2028                    my $ref_obj = $c->{'obj_db'}->get_object_by_id($id);
2029                    for my $attr (keys %{$refs->{$type}->{$id}}) {
2030                        if(ref $ref_obj->{'conf'}->{$attr} eq 'ARRAY') {
2031                            $ref_obj->{'conf'}->{$attr} = [grep(!/^\Q$name\E$/mx, @{$ref_obj->{'conf'}->{$attr}})];
2032                        } elsif(ref $ref_obj->{'conf'}->{$attr} eq '') {
2033                            delete $ref_obj->{'conf'}->{$attr};
2034                        }
2035                        $c->{'obj_db'}->update_object($ref_obj, $ref_obj->{'conf'});
2036                        $ref_obj->{'file'}->{'changed'}  = 1;
2037                        $c->{'obj_db'}->{'needs_commit'} = 1;
2038                        # remove if its unused now
2039                        $c->{'obj_db'}->delete_object($ref_obj) if($ref_obj->can('is_unused') && $ref_obj->is_unused($c->{'obj_db'}));
2040                    }
2041                }
2042            }
2043        }
2044    }
2045
2046    $c->{'obj_db'}->delete_object($obj);
2047    $c->stash->{'obj_model_changed'} = 1;
2048
2049    # create log message
2050    $c->{'obj_db'}->{'logs'} = [] unless $c->{'obj_db'}->{'logs'};
2051    push @{$c->{'obj_db'}->{'logs'}},
2052        sprintf("[config][%s][%s] removed %s '%s'",
2053                                $c->db->get_peer_by_key($c->stash->{'param_backend'})->{'name'},
2054                                $c->stash->{'remote_user'},
2055                                $obj->get_type(),
2056                                $obj->get_name(),
2057    );
2058
2059    Thruk::Utils::set_message( $c, 'success_message', ucfirst($obj->get_type()).' removed successfully' );
2060    return $c->redirect_to('conf.cgi?sub=objects&type='.$obj->get_type());
2061}
2062
2063##########################################################
2064sub _object_save {
2065    my($c, $obj) = @_;
2066
2067    my $data        = $obj->get_data_from_param($c->req->parameters);
2068    my $old_comment = join("\n", @{$obj->{'comments'}});
2069    my $new_comment = $c->req->parameters->{'conf_comment'} || '';
2070    $new_comment    =~ s/\r//gmx;
2071    my $new         = $c->req->parameters->{'data.id'} eq 'new' ? 1 : 0;
2072
2073    # create copy of object to get references later if renamed
2074    my $old_obj = Monitoring::Config::Object->new(
2075        id       => $obj->get_id(),
2076        type     => $obj->get_type(),
2077        conf     => $obj->{'conf'},
2078        coretype => $c->{'obj_db'}->{'coretype'},
2079    );
2080
2081    # save object
2082    $obj->{'file'}->{'errors'} = [];
2083    $c->{'obj_db'}->update_object($obj, $data, $new_comment);
2084    $c->stash->{'data_name'} = $obj->get_name();
2085
2086    # just display the normal edit page if save failed
2087    if($obj->get_id() eq 'new') {
2088        $c->stash->{action} = '';
2089        return;
2090    }
2091
2092    $c->{'obj_db'}->{'logs'} = [] unless $c->{'obj_db'}->{'logs'};
2093    push @{$c->{'obj_db'}->{'logs'}},
2094        sprintf("[config][%s][%s] %s %s '%s'",
2095                                $c->db->get_peer_by_key($c->stash->{'param_backend'})->{'name'},
2096                                $c->stash->{'remote_user'},
2097                                $new ? 'created' : 'changed',
2098                                $obj->get_type(),
2099                                ($c->stash->{'data_name'} || 'undefined'),
2100    );
2101    $c->stash->{'obj_model_changed'} = 1;
2102
2103    # only save or continue to raw edit?
2104    if(defined $c->req->parameters->{'send'} and $c->req->parameters->{'send'} eq 'raw edit') {
2105        return $c->redirect_to('conf.cgi?sub=objects&action=editor&file='.encode_utf8($obj->{'file'}->{'display'}).'&line='.$obj->{'line'}.'&data.id='.$obj->get_id().'&back=edit');
2106    }
2107
2108    if(scalar @{$obj->{'file'}->{'errors'}} > 0) {
2109        Thruk::Utils::set_message( $c, 'fail_message', ucfirst($c->stash->{'type'}).' changed with errors', $obj->{'file'}->{'errors'} );
2110        return; # return, otherwise details would not be displayed
2111    }
2112
2113    # does the object have a name?
2114    if(!defined $c->stash->{'data_name'} || $c->stash->{'data_name'} eq '') {
2115        $obj->set_name('undefined');
2116        $c->{'obj_db'}->_rebuild_index();
2117        Thruk::Utils::set_message( $c, 'fail_message', sprintf('%s %s without a name', ucfirst($c->stash->{'type'}), $new ? 'created' : 'changed'));
2118    } else {
2119        Thruk::Utils::set_message( $c, 'success_message', sprintf('%s %s successfully', ucfirst($c->stash->{'type'}), $new ? 'created' : 'changed'));
2120    }
2121
2122    if($c->req->parameters->{'referer'}) {
2123        return $c->redirect_to($c->req->parameters->{'referer'});
2124    }
2125
2126    # list outside dependencies after renaming object
2127    if(!$new && $c->stash->{'data_name'} ne $old_obj->get_name()) {
2128        my $other_refs = _get_non_config_tool_references($c, $old_obj);
2129        if(scalar keys %{$other_refs} > 0) {
2130            Thruk::Utils::set_message( $c, 'success_message', ucfirst($c->stash->{'type'}).' saved successfully. Please check external references.' );
2131            $c->stash->{object} = $old_obj;
2132            _list_references($c, $old_obj);
2133            $c->stash->{'show_incoming'} = 0;
2134            $c->stash->{'show_outgoing'} = 0;
2135            $c->stash->{'show_renamed'}  = 1;
2136            return;
2137        }
2138    }
2139
2140    return $c->redirect_to('conf.cgi?sub=objects&data.id='.$obj->get_id());
2141}
2142
2143##########################################################
2144sub _object_move {
2145    my($c, $obj) = @_;
2146
2147    my $files_root = _set_files_stash($c, 1);
2148    if($c->stash->{action} eq 'movefile') {
2149        return unless Thruk::Utils::check_csrf($c);
2150        my $new_file = $c->req->parameters->{'newfile'};
2151        my $file     = get_context_file($c, $obj, $new_file);
2152        if(defined $file and $c->{'obj_db'}->move_object($obj, $file)) {
2153            Thruk::Utils::set_message( $c, 'success_message', ucfirst($c->stash->{'type'}).' \''.$obj->get_name().'\' moved successfully' );
2154        }
2155
2156        # create log message
2157        $c->{'obj_db'}->{'logs'} = [] unless $c->{'obj_db'}->{'logs'};
2158        push @{$c->{'obj_db'}->{'logs'}},
2159            sprintf("[config][%s][%s] moved %s '%s' to '%s'",
2160                                    $c->db->get_peer_by_key($c->stash->{'param_backend'})->{'name'},
2161                                    $c->stash->{'remote_user'},
2162                                    $obj->get_type(),
2163                                    $obj->get_name(),
2164                                    $file->{'path'},
2165        );
2166        $c->stash->{'obj_model_changed'} = 1;
2167
2168        return $c->redirect_to('conf.cgi?sub=objects&data.id='.$obj->get_id());
2169    }
2170    elsif($c->stash->{action} eq 'move') {
2171        $c->stash->{'template'} = 'conf_objects_move.tt';
2172    }
2173    return;
2174}
2175
2176##########################################################
2177sub _object_clone {
2178    my($c, $obj) = @_;
2179
2180    my $files_root          = _set_files_stash($c, 1);
2181    $c->stash->{'new_file'} = $obj->{'file'}->{'display'};
2182    $c->stash->{'new_file'} =~ s/^$files_root/\//gmx;
2183    # if cloned from a readonly file, keep new_file empty
2184    if($obj->{'file'}->{'readonly'}) { $c->stash->{'new_file'} = ''; }
2185    my $newobj = Monitoring::Config::Object->new(
2186        type     => $obj->get_type(),
2187        conf     => dclone($obj->{'conf'}),
2188        coretype => $c->{'obj_db'}->{'coretype'},
2189    );
2190    my $name = $newobj->get_name()." clone";
2191    if(scalar @{$c->{'obj_db'}->get_objects_by_name($newobj->get_type(), $name)} > 0) {
2192        for my $i (2..100) {
2193            if(scalar @{$c->{'obj_db'}->get_objects_by_name($newobj->get_type(), $name.' '.$i)} == 0) {
2194                $name = $name.' '.$i;
2195                last;
2196            }
2197        }
2198    }
2199    $newobj->set_name($name);
2200    return $newobj;
2201}
2202
2203
2204##########################################################
2205sub _clone_refs {
2206    my($c, $obj, $cloned_id, $clone_refs) = @_;
2207    return unless $cloned_id;
2208    my $new_name = $obj->get_name();
2209    my $orig     = $c->{'obj_db'}->get_object_by_id($cloned_id);
2210    if(!$orig) {
2211        Thruk::Utils::set_message( $c, 'fail_message', 'Could not find object to clone from.' );
2212        return;
2213    }
2214
2215    my $cloned_name = $orig->get_name();
2216    if($new_name eq $cloned_name) {
2217        Thruk::Utils::set_message( $c, 'fail_message', 'New name must be different' );
2218        return;
2219    }
2220
2221    $c->{'obj_db'}->clone_refs($orig, $obj, $cloned_name, $new_name, $clone_refs);
2222
2223    return;
2224}
2225
2226
2227##########################################################
2228sub _object_new {
2229    my($c) = @_;
2230
2231    _set_files_stash($c, 1);
2232    $c->stash->{'new_file'} = '';
2233    my $standard_keys;
2234    my $default_values = {};
2235    if($c->config->{'Thruk::Plugin::ConfigTool'}->{'default_keys_'.$c->stash->{'type'}}) {
2236        $standard_keys = [split(/\s+/mx, $c->config->{'Thruk::Plugin::ConfigTool'}->{'default_keys_'.$c->stash->{'type'}})];
2237        for my $k (@{$standard_keys}) {
2238            my $v;
2239            ($k,$v) = split(/:/mx, $k, 2);
2240            $default_values->{$k} = $v if defined $v;
2241        }
2242    }
2243    my $obj = Monitoring::Config::Object->new(type     => $c->stash->{'type'},
2244                                              name     => $c->stash->{'data_name'},
2245                                              coretype => $c->{'obj_db'}->{'coretype'},
2246                                              standard => $standard_keys,
2247                                            );
2248
2249    if(!defined $obj) {
2250        Thruk::Utils::set_message( $c, 'fail_message', 'Failed to create object' );
2251        return;
2252    }
2253
2254    $obj->{'conf'} = { %{$obj->{'conf'}}, %{$obj->sanitize_values($default_values)} };
2255
2256    if($c->req->parameters->{'template'}) {
2257        $obj->{'conf'}->{'name'}     = '';
2258        $obj->{'conf'}->{'register'} = 0;
2259        delete $obj->{'conf'}->{'host_name'};
2260        delete $obj->{'conf'}->{'service_description'};
2261        delete $obj->{'conf'}->{'contact_name'};
2262        delete $obj->{'conf'}->{'alias'};
2263    }
2264
2265    # set initial config from cgi parameters
2266    my $initial_conf = $obj->get_data_from_param($c->req->parameters, $obj->{'conf'});
2267    if(scalar keys %{$initial_conf} > 0 && $obj->has_object_changed($initial_conf)) {
2268        $c->{'obj_db'}->update_object($obj, $initial_conf );
2269    }
2270
2271    return $obj;
2272}
2273
2274
2275##########################################################
2276sub _file_delete {
2277    my($c) = @_;
2278
2279    my $path = $c->req->parameters->{'path'} || '';
2280    $path    =~ s/^\#//gmx;
2281
2282    my $files = $c->req->parameters->{'files'};
2283    for my $filename (ref $files eq 'ARRAY' ? @{$files} : ($files) ) {
2284        my $file = $c->{'obj_db'}->get_file_by_path($filename);
2285        if(defined $file) {
2286            $c->{'obj_db'}->file_delete($file);
2287        }
2288    }
2289
2290    Thruk::Utils::set_message( $c, 'success_message', 'File(s) deleted successfully' );
2291    return $c->redirect_to('conf.cgi?sub=objects&action=browser#'.$path);
2292}
2293
2294
2295##########################################################
2296sub _file_undelete {
2297    my($c) = @_;
2298
2299    my $path = $c->req->parameters->{'path'} || '';
2300    $path    =~ s/^\#//gmx;
2301
2302    my $files = $c->req->parameters->{'files'};
2303    for my $filename (ref $files eq 'ARRAY' ? @{$files} : ($files) ) {
2304        my $file = $c->{'obj_db'}->get_file_by_path($filename);
2305        if(defined $file) {
2306            $c->{'obj_db'}->file_undelete($file);
2307        }
2308    }
2309
2310    Thruk::Utils::set_message( $c, 'success_message', 'File(s) recoverd successfully' );
2311    return $c->redirect_to('conf.cgi?sub=objects&action=browser#'.$path);
2312}
2313
2314
2315##########################################################
2316sub _file_save {
2317    my($c) = @_;
2318
2319    my $filename = $c->req->parameters->{'file'}    || '';
2320    my $content  = $c->req->parameters->{'content'} || '';
2321    my $lastline = $c->req->parameters->{'line'};
2322    my $file     = $c->{'obj_db'}->get_file_by_path($filename);
2323    my $lastobj;
2324    if(defined $file) {
2325        $lastobj = $file->update_objects_from_text($content, $lastline);
2326        $c->{'obj_db'}->_rebuild_index();
2327        my $files_root                   = _set_files_stash($c, 1);
2328        $c->{'obj_db'}->{'needs_commit'} = 1;
2329        $c->stash->{'obj_model_changed'} = 1;
2330        $c->stash->{'file_name'}         = $file->{'display'};
2331        $c->stash->{'file_name'}         =~ s/^$files_root//gmx;
2332        if(scalar @{$file->{'errors'}} > 0) {
2333            Thruk::Utils::set_message( $c,
2334                                      'fail_message',
2335                                      'File '.$c->stash->{'file_name'}.' changed with errors',
2336                                      $file->{'errors'},
2337                                    );
2338        } else {
2339            Thruk::Utils::set_message( $c, 'success_message', 'File '.$c->stash->{'file_name'}.' changed successfully' );
2340        }
2341    }
2342    elsif(_is_extra_file($filename, $c->config->{'Thruk::Plugin::ConfigTool'}->{'edit_files'})) {
2343        Thruk::Utils::set_message( $c, 'success_message', 'File '.$filename.' changed successfully' );
2344        Thruk::Utils::IO::write($filename, $content);
2345        if(defined $c->req->parameters->{'backlink'}) {
2346            return $c->redirect_to($c->req->parameters->{'backlink'});
2347        }
2348    } else {
2349        Thruk::Utils::set_message( $c, 'fail_message', 'File does not exist' );
2350    }
2351
2352    if(defined $lastobj) {
2353        return $c->redirect_to('conf.cgi?sub=objects&data.id='.$lastobj->get_id());
2354    }
2355    return $c->redirect_to('conf.cgi?sub=objects&action=browser#'.$file->{'display'});
2356}
2357
2358##########################################################
2359sub _file_editor {
2360    my($c) = @_;
2361
2362    my $files_root  = _set_files_stash($c);
2363    my $filename    = $c->req->parameters->{'file'} || '';
2364    my $file        = $c->{'obj_db'}->get_file_by_path($filename);
2365    if(defined $file) {
2366        $c->stash->{'file'}          = $file;
2367        $c->stash->{'line'}          = $c->req->parameters->{'line'} || 1;
2368        $c->stash->{'back'}          = $c->req->parameters->{'back'} || '';
2369        $c->stash->{'file_link'}     = $file->{'display'};
2370        $c->stash->{'file_name'}     = $file->{'display'};
2371        $c->stash->{'file_name'}     =~ s/^$files_root//gmx;
2372        $c->stash->{'file_content'}  = decode_utf8($file->get_new_file_content());
2373        $c->stash->{'template'}      = $c->config->{'use_feature_editor'} ? 'conf_objects_fancyeditor.tt' : 'conf_objects_fileeditor.tt';
2374    }
2375    elsif(_is_extra_file($filename, $c->config->{'Thruk::Plugin::ConfigTool'}->{'edit_files'})) {
2376        $file = Monitoring::Config::File->new($filename, [], $c->{'obj_db'}->{'coretype'}, 1);
2377        $c->stash->{'file'}          = $file;
2378        $c->stash->{'file_link'}     = $filename;
2379        $c->stash->{'file_name'}     = $filename;
2380        $c->stash->{'line'}          = $c->req->parameters->{'line'} || 1;
2381        $c->stash->{'file_content'}  = '';
2382        if(-f $filename) {
2383            my $content                  = Thruk::Utils::IO::read($filename);
2384            $c->stash->{'file_content'}  = decode_utf8($content);
2385        }
2386        $c->stash->{'template'}      = $c->config->{'use_feature_editor'} ? 'conf_objects_fancyeditor.tt' : 'conf_objects_fileeditor.tt';
2387        $c->stash->{'subtitle'}      = "";
2388    } else {
2389        Thruk::Utils::set_message( $c, 'fail_message', 'File does not exist' );
2390    }
2391    return;
2392}
2393
2394##########################################################
2395sub _file_browser {
2396    my($c) = @_;
2397
2398    _set_files_stash($c);
2399    $c->stash->{'template'} = 'conf_objects_filebrowser.tt';
2400    return;
2401}
2402
2403##########################################################
2404sub _file_history {
2405    my($c) = @_;
2406
2407    return 1 unless $c->stash->{'has_history'};
2408
2409    my $commit     = $c->req->parameters->{'id'};
2410    my $obj_id     = $c->req->parameters->{'data.id'};
2411    my $files_root = $c->{'obj_db'}->get_files_root();
2412    my $dir        = $c->{'obj_db'}->{'config'}->{'git_base_dir'} || $c->config->{'Thruk::Plugin::ConfigTool'}->{'git_base_dir'} || $files_root;
2413
2414    $c->stash->{'template'} = 'conf_objects_filehistory.tt';
2415
2416    if($obj_id) {
2417        return if _file_history_blame_obj($c, $obj_id);
2418    }
2419    if($commit) {
2420        return if _file_history_commit($c, $commit, $dir);
2421    }
2422
2423    my $logs = _get_git_logs($c, $dir);
2424
2425    Thruk::Utils::page_data($c, $logs);
2426    $c->stash->{'logs'} = $logs;
2427    $c->stash->{'dir'}  = $dir;
2428    return;
2429}
2430
2431##########################################################
2432sub _file_history_commit {
2433    my($c, $commit, $dir) = @_;
2434
2435    # verify our commit id
2436    if($commit !~ m/^[a-zA-Z0-9]+$/mx) {
2437        Thruk::Utils::set_message( $c, 'fail_message', 'Not a valid commit id!' );
2438        return;
2439    }
2440
2441    my $data = _get_git_commit($c, $dir, $commit);
2442    if(!$data) {
2443        Thruk::Utils::set_message( $c, 'fail_message', 'Not a valid commit!' );
2444        return;
2445    }
2446
2447    $c->stash->{'previous'} = '';
2448    $c->stash->{'next'}     = '';
2449    my $logs = _get_git_logs($c, $dir);
2450    for my $l (@{$logs}) {
2451        if($l->{'id'} eq $data->{'id'}) {
2452            $c->stash->{'previous'} = _get_git_commit($c, $dir, $l->{'previous'}) if $l->{'previous'};
2453            $c->stash->{'next'}     = _get_git_commit($c, $dir, $l->{'next'})     if $l->{'next'};
2454            last;
2455        }
2456    }
2457
2458    # make new files visible
2459    $data->{'diff'} =~ s/\-\-\-\s+\/dev\/null\n\+\+\+\s+b\/(.*?)$/--- a\/$1/gmxs;
2460    $data->{'diff'} = Thruk::Utils::Filter::escape_html($data->{'diff'});
2461
2462    # changed files
2463    our $diff_link_nr    = 0;
2464    our $diff_link_files = [];
2465    $data->{'diff'} =~ s/^(\-\-\-\s+a\/.*)$/&_diff_link($1)/gemx;
2466    $data->{'diff'} = Thruk::Utils::beautify_diff($data->{'diff'});
2467    $data->{'diff'} =~ s/^\s+//gmx;
2468
2469    $c->stash->{'dir'}      = $dir;
2470    $c->stash->{'data'}     = $data;
2471    $c->stash->{'links'}    = $diff_link_files;
2472    $c->stash->{'template'} = 'conf_objects_filehistory_commit.tt';
2473
2474    return 1;
2475}
2476
2477##########################################################
2478sub _file_history_blame_obj {
2479    my($c, $obj_id) = @_;
2480
2481    my $obj = $c->{'obj_db'}->get_object_by_id($c->stash->{'data_id'});
2482    if(!$obj) {
2483        Thruk::Utils::set_message($c, 'fail_message', 'no such object');
2484        return;
2485    }
2486    if(!$obj->{'file'} || $obj->{'line'} <= 0) {
2487        Thruk::Utils::set_message($c, 'fail_message', 'object has not yet been saved');
2488        return;
2489    }
2490
2491    my $blame = _get_git_blame($c, $obj->{'file'}->{'path'}, $obj->{'line'}, $obj->{'line2'});
2492
2493    $c->stash->{'object'}   = $obj;
2494    $c->stash->{'blame'}    = $blame;
2495    $c->stash->{'template'} = 'conf_objects_filehistory_blame.tt';
2496
2497    return 1;
2498}
2499
2500##########################################################
2501sub _get_git_logs {
2502    my($c, $dir) = @_;
2503    my $cmd = "cd '".$dir."' && git log --pretty='format:".join("\x1f", '%h', '%an', '%ae', '%at', '%s')."\x1e'";
2504    my($rc, $out) = Thruk::Utils::IO::cmd($cmd);
2505    my $logs = [];
2506    my $last;
2507    for my $line (split("\x1e", $out)) {
2508        my @d = split("\x1f", $line);
2509        next if scalar @d < 5;
2510        $d[0] =~ s/^\n//mx;
2511        my $data = {
2512            'id'           => $d[0],
2513            'author_name'  => $d[1],
2514            'author_email' => $d[2],
2515            'date'         => $d[3],
2516            'message'      => $d[4],
2517            'next'         => '',
2518            'previous'     => '',
2519        };
2520        push @{$logs}, $data;
2521        $last->{'previous'} = $data->{'id'} if $last;
2522        $data->{'next'}     = $last->{'id'} if $last;
2523        $last = $data;
2524    }
2525    return $logs;
2526}
2527
2528##########################################################
2529sub _get_git_commit {
2530    my($c, $dir, $commit) = @_;
2531    my $cmd = "cd '".$dir."' && git show --pretty='format:".join("\x1f", '%h', '%an', '%ae', '%at', '%p', '%t', '%s', '%b')."\x1f' ".$commit;
2532    my $output = Thruk::Utils::IO::cmd($cmd);
2533    my @d = split(/\x1f/mx, $output);
2534    return if scalar @d < 4;
2535    my $data = {
2536            'id'           => $d[0],
2537            'author_name'  => $d[1],
2538            'author_email' => $d[2],
2539            'date'         => $d[3],
2540            'parent'       => $d[4],
2541            'tree'         => $d[5],
2542            'message'      => $d[6],
2543            'body'         => $d[7],
2544            'diff'         => $d[8],
2545    };
2546    return $data;
2547}
2548
2549##########################################################
2550sub _get_git_blame {
2551    my($c, $path, $line_start, $line_end) = @_;
2552    my $dir = Thruk::Base::dirname($path);
2553    my $cmd = "cd '".$dir."' && git blame -swp -L $line_start,$line_end '".$path."'";
2554    my $output = Thruk::Utils::IO::cmd($cmd);
2555    my $blame = {lines => [], commits => {}};
2556    my($state, $block, $commit) = (0, {}, {});
2557    for my $line (split/\n/mx, $output) {
2558        # new commit header starts
2559        if($state == 0) {
2560            my($hash, $sourceline, $resultline, $num_lines) = split(/\s+/mx, $line);
2561            $commit = {
2562                hash       => $hash,
2563                sourceline => $sourceline,
2564                resultline => $resultline,
2565                num_lines  => $num_lines,
2566            };
2567            $state = 3;
2568            # new commit hash, parse commit details
2569            if(!$blame->{'commits'}->{$hash}) {
2570                $state = 2;
2571                $blame->{'commits'}->{$hash} = $commit;
2572            }
2573            next;
2574        }
2575
2576        # commit details parser
2577        if($state == 2) {
2578            my($key, $value) = split(/\s+/mx, $line, 2);
2579            $commit->{$key} = $value;
2580            if($key eq 'filename') {
2581                # commit header parsing done
2582                $state = 3;
2583            }
2584            next;
2585        }
2586
2587        # line parser
2588        if($state == 3) {
2589            chomp($line);
2590            $block = {
2591                line       => $line,
2592                hash       => $commit->{'hash'},
2593                sourceline => $commit->{'sourceline'},
2594                resultline => $commit->{'resultline'},
2595            };
2596            push @{$blame->{'lines'}}, $block;
2597            $state = 0;
2598            next;
2599        }
2600    }
2601    return $blame;
2602}
2603
2604##########################################################
2605sub _diff_link {
2606    my($text) = @_;
2607    our $diff_link_nr;
2608    our $diff_link_files;
2609    $diff_link_files->[$diff_link_nr] = $text;
2610    $diff_link_files->[$diff_link_nr] =~ s/^\-\-\-\s+a\///gmx;
2611    $text = "<hr><a name='file".$diff_link_nr."'></a>\n".$text;
2612    $diff_link_nr++;
2613    return $text;
2614}
2615
2616##########################################################
2617sub _object_tree {
2618    my($c) = @_;
2619
2620    # create list of templates
2621    for my $type (qw/host service contact/) {
2622        my $templates = {};
2623        my $objs = $c->{'obj_db'}->get_templates_by_type($type);
2624        for my $o (@{$objs}) {
2625            next if $o->{'disabled'};
2626            if(!defined $o->{'conf'}->{'use'}) {
2627                $templates->{$o->get_template_name()} = $o;
2628            } else {
2629                for my $tname (@{$o->{'conf'}->{'use'}}) {
2630                    my $t = $c->{'obj_db'}->get_template_by_name($type, $tname);
2631                    $t->{'child_templates'} = {} unless defined $t->{'child_templates'};
2632                    $t->{'child_templates'}->{$o->get_template_name()} = $o;
2633                }
2634            }
2635        }
2636        $c->stash->{$type.'templates'} = $templates;
2637    }
2638
2639    $c->stash->{'template'} = 'conf_objects_tree.tt';
2640    return;
2641}
2642
2643##########################################################
2644sub _object_tree_objects {
2645    my($c) = @_;
2646
2647    my $type     = $c->req->parameters->{'type'}     || '';
2648    my $template = $c->req->parameters->{'template'};
2649    my $origin   = $c->req->parameters->{'origin'};
2650    my $dir      = $c->req->parameters->{'dir'};
2651    my $objs = [];
2652    if($dir) {
2653        $objs = $c->{'obj_db'}->get_objects_by_path($dir);
2654        $c->stash->{'objects_type'} = 'all';
2655    }
2656    elsif($type) {
2657        my $filter;
2658        if(defined $template) {
2659            $filter = {};
2660            $filter->{'use'} = $template;
2661        }
2662        $objs = $c->{'obj_db'}->get_objects_by_type($type, $filter, $origin);
2663        $c->stash->{'objects_type'} = $type;
2664    } else {
2665        $objs = $c->{'obj_db'}->get_objects();
2666        $c->stash->{'objects_type'} = 'all';
2667    }
2668
2669    # sort by name
2670    @{$objs} = sort {uc($a->get_name()) cmp uc($b->get_name())} @{$objs};
2671
2672    $c->stash->{'tree_objects_layout'} = 'table';
2673    if($c->cookies('thruk_obj_layout')) {
2674        $c->stash->{'tree_objects_layout'} = $c->cookies('thruk_obj_layout');
2675    }
2676
2677    my $all_files  = $c->{'obj_db'}->get_files();
2678    my $files_tree = _files_to_path($c, $all_files);
2679    my $files_root = $files_tree->{'path'};
2680    $c->stash->{'files_tree'} = $files_tree;
2681    $c->stash->{'files_root'} = $files_root;
2682
2683    $c->stash->{'objects'}  = $objs;
2684    $c->stash->{'template'} = 'conf_objects_tree_objects.tt';
2685    return;
2686}
2687
2688##########################################################
2689sub _host_list_services {
2690    my($c, $obj) = @_;
2691
2692    my $services = $c->{'obj_db'}->get_services_for_host($obj);
2693    $c->stash->{'services'} = $services ;
2694    $c->stash->{'template'} = 'conf_objects_host_list_services.tt';
2695    return;
2696}
2697
2698##########################################################
2699sub _list_references {
2700    my($c, $obj) = @_;
2701    _gather_references($c, $obj, 1);
2702    $c->stash->{'show_incoming'} = 1;
2703    $c->stash->{'show_outgoing'} = 1;
2704    $c->stash->{'show_renamed'}  = 0;
2705    $c->stash->{'template'}      = 'conf_objects_listref.tt';
2706    return;
2707}
2708
2709##########################################################
2710sub _config_check {
2711    my($c) = @_;
2712    $c->stats->profile(begin => "conf::_config_check");
2713    my $obj_check_cmd = $c->stash->{'peer_conftool'}->{'obj_check_cmd'};
2714    $obj_check_cmd = $obj_check_cmd.' 2>&1' if($obj_check_cmd && $obj_check_cmd !~ m|>|mx);
2715    my $rc = 0;
2716    if($c->{'obj_db'}->is_remote()) {
2717        if($c->{'obj_db'}->remote_config_check($c)) {
2718            Thruk::Utils::set_message( $c, 'success_message', 'config check successfull' );
2719            $rc = 1;
2720        } else {
2721            Thruk::Utils::set_message( $c, 'fail_message', 'config check failed!' );
2722        }
2723    } else {
2724        if(_cmd($c, $obj_check_cmd)) {
2725            Thruk::Utils::set_message( $c, 'success_message', 'config check successfull' );
2726            $rc = 1;
2727        } else {
2728            Thruk::Utils::set_message( $c, 'fail_message', 'config check failed!' );
2729        }
2730    }
2731    _nice_check_output($c);
2732
2733    $c->stash->{'needs_commit'}      = $c->{'obj_db'}->{'needs_commit'};
2734    $c->stash->{'last_changed'}      = $c->{'obj_db'}->{'last_changed'};
2735    $c->stats->profile(end => "conf::_config_check");
2736    return($rc, $c->stash->{'original_output'});
2737}
2738
2739##########################################################
2740sub _config_reload {
2741    my($c) = @_;
2742    $c->stats->profile(begin => "conf::_config_reload");
2743
2744    $c->stash->{'original_output'} = "";
2745    my $time = time();
2746    my $name = $c->stash->{'param_backend'};
2747    my $peer = $c->db->get_peer_by_key($name);
2748    my $pkey = $peer->peer_key();
2749    my $wait = 1;
2750
2751    my $last_reload = $c->stash->{'pi_detail'}->{$pkey}->{'program_start'};
2752    if(!$last_reload) {
2753        my $processinfo = $c->db->get_processinfo(backends => $pkey);
2754        $last_reload = ($processinfo->{$pkey} && $processinfo->{$pkey}->{'program_start'}) || (time() - 1);
2755    }
2756
2757    $c->stats->profile(comment => "program_start before reload: ".$last_reload);
2758    if($c->stash->{'peer_conftool'}->{'obj_reload_cmd'}) {
2759        if($c->{'obj_db'}->is_remote() && $c->{'obj_db'}->remote_config_reload($c)) {
2760            Thruk::Utils::set_message( $c, 'success_message', 'config reloaded successfully' );
2761            $c->{'obj_db'}->{'last_changed'} = 0;
2762            $c->{'obj_db'}->{'needs_commit'} = 0;
2763            Thruk::Utils::Conf::store_model_retention($c, $pkey);
2764        }
2765        elsif(!$c->{'obj_db'}->is_remote() && _cmd($c, $c->stash->{'peer_conftool'}->{'obj_reload_cmd'})) {
2766            Thruk::Utils::set_message( $c, 'success_message', 'config reloaded successfully' );
2767            $c->{'obj_db'}->{'last_changed'} = 0;
2768            $c->{'obj_db'}->{'needs_commit'} = 0;
2769            Thruk::Utils::Conf::store_model_retention($c, $pkey);
2770        } else {
2771            Thruk::Utils::set_message( $c, 'fail_message', 'config reload failed!' );
2772            $wait = 0;
2773        }
2774
2775        _nice_check_output($c);
2776    } else {
2777        # restart by livestatus
2778        die("no backend found by name ".$name) unless $peer;
2779        my $options = {
2780            'command' => sprintf("COMMAND [%d] RESTART_PROCESS", $time),
2781            'backend' => [ $pkey ],
2782        };
2783        $c->db->send_command( %{$options} );
2784        $c->stash->{'output'} = 'config reloaded by external command.';
2785    }
2786    $c->stats->profile(comment => "reload command issued: ".time());
2787
2788    # wait until core responds again
2789    if($wait) {
2790        if(!Thruk::Utils::wait_after_reload($c, $pkey, $last_reload)) {
2791            $c->stash->{'original_output'} .= 'Warning: waiting for core reload failed.';
2792            $c->stash->{'output'}          .= "\n<font color='red'>".$c->stash->{'original_output'}."</font>";
2793        }
2794    }
2795
2796    # reload navigation, probably some names have changed
2797    $c->stash->{'reload_nav'} = 1;
2798
2799    $c->stats->profile(end => "conf::_config_reload");
2800    return(1, $c->stash->{'original_output'});
2801}
2802
2803##########################################################
2804sub _nice_check_output {
2805    my($c) = @_;
2806    $c->stash->{'original_output'} = $c->stash->{'output'};
2807    $c->stash->{'output'} =~ s/(Error\s*:.*)$/<b><font color="red">$1<\/font><\/b>/gmx;
2808    $c->stash->{'output'} =~ s/(Warning\s*:.*)$/<b><font color="#FFA500">$1<\/font><\/b>/gmx;
2809    $c->stash->{'output'} =~ s/(CONFIG\s+ERROR.*)$/<b><font color="red">$1<\/font><\/b>/gmx;
2810    $c->stash->{'output'} =~ s/(\(config\s+file\s+'(.*?)',\s+starting\s+on\s+line\s+(\d+)\))/<a href="conf.cgi?sub=objects&amp;file=$2&amp;line=$3">$1<\/a>/gmx;
2811    $c->stash->{'output'} =~ s/\s+in\s+file\s+'(.*?)'\s+on\s+line\s+(\d+)/ in file <a href="conf.cgi?sub=objects&amp;type=file&amp;file=$1&amp;line=$2">'$1' on line $2<\/a>/gmx;
2812    $c->stash->{'output'} =~ s/\s+in\s+(\w+)\s+'(.*?)'/ in $1 '<a href="conf.cgi?sub=objects&amp;type=$1&amp;data.name=$2">$2<\/a>'/gmx;
2813    $c->stash->{'output'} =~ s/Warning:\s+(\w+)\s+'(.*?)'\s+/Warning: $1 '<a href="conf.cgi?sub=objects&amp;type=$1&amp;data.name=$2">$2<\/a>' /gmx;
2814    $c->stash->{'output'} =~ s/Error:\s+(\w+)\s+'(.*?)'\s+/Error: $1 '<a href="conf.cgi?sub=objects&amp;type=$1&amp;data.name=$2">$2<\/a>' /gmx;
2815    $c->stash->{'output'} =~ s/Error\s*:\s*the\s+service\s+([^\s]+)\s+on\s+host\s+'([^']+)'/Error: the service <a href="conf.cgi?sub=objects&amp;type=service&amp;data.name=$1&amp;data.name2=$2">$1<\/a> on host '$2'/gmx;
2816    $c->stash->{'output'} = "<pre>".$c->stash->{'output'}."</pre>";
2817    return;
2818}
2819
2820##########################################################
2821# check for external reloads
2822sub _check_external_reload {
2823    my($c) = @_;
2824
2825    return unless defined $c->{'obj_db'}->{'last_changed'};
2826
2827    if($c->{'obj_db'}->{'last_changed'} > 0) {
2828        my $last_reloaded = $c->stash->{'pi_detail'}->{$c->stash->{'param_backend'}}->{'program_start'} || 0;
2829        if($last_reloaded > $c->{'obj_db'}->{'last_changed'}) {
2830            $c->{'obj_db'}->{'last_changed'} = 0;
2831            $c->stash->{'obj_model_changed'} = 1;
2832        }
2833    }
2834    return;
2835}
2836
2837##########################################################
2838sub _gather_references {
2839    my($c, $obj, $include_outside) = @_;
2840
2841    #&timing_breakpoint('_gather_references');
2842
2843    my($incoming, $outgoing) = $c->{'obj_db'}->gather_references($obj);
2844
2845    $c->stash->{'other_refs'} = {} unless $c->stash->{'other_refs'};
2846    $c->stash->{'other_refs'} = _get_non_config_tool_references($c, $obj) if $include_outside;
2847
2848    # linked from delete object page?
2849    $c->stash->{'force_delete'} = $c->req->parameters->{'show_force'} ? 1 : 0;
2850
2851    $c->stash->{'incoming'} = $incoming;
2852    $c->stash->{'outgoing'} = $outgoing;
2853    $c->stash->{'has_refs'} = 1 if(scalar keys %{$incoming} || scalar keys %{$outgoing} || scalar keys %{$c->stash->{'other_refs'}});
2854
2855    #&timing_breakpoint('_gather_references done');
2856
2857    return({incoming => $incoming, outgoing => $outgoing});
2858}
2859
2860##########################################################
2861sub _is_extra_file {
2862    my($filename, $edit_files) = @_;
2863    $edit_files = Thruk::Base::list($edit_files);
2864    for my $file (@{$edit_files}) {
2865        # direct match
2866        if($file eq $filename) {
2867            return 1;
2868        }
2869        # pattern is a directory and the file is below that folder
2870        elsif($filename =~ m|^\Q$file\E/|mx && -d $file) {
2871            return 1;
2872        }
2873    }
2874    return 0;
2875}
2876
2877##########################################################
2878sub _get_non_config_tool_references {
2879    my($c, $obj) = @_;
2880    my $other_refs = {};
2881    if($obj->get_type() eq 'host') {
2882        Thruk::Utils::References::get_host_matches($c, $c->stash->{'param_backend'}, { $c->stash->{'param_backend'} => 1 }, $other_refs, $obj->get_primary_name() || $obj->get_name());
2883    }
2884    elsif($obj->get_type() eq 'hostgroup') {
2885        Thruk::Utils::References::get_hostgroup_matches($c, $c->stash->{'param_backend'}, { $c->stash->{'param_backend'} => 1 }, $other_refs, $obj->get_primary_name() || $obj->get_name());
2886    }
2887    elsif($obj->get_type() eq 'service') {
2888        # expand hosts and hostgroups and iterate over all of them
2889        my $all_hosts = {};
2890        for my $host_name (@{$obj->{'conf'}->{'host_name'}}) {
2891            $all_hosts->{$host_name} = 1;
2892        }
2893        for my $hostgroup_name (@{$obj->{'conf'}->{'hostgroup_name'}}) {
2894            my $groups = $c->db->get_hostgroups(filter => [{ name => $hostgroup_name }], backend => [$c->stash->{'param_backend'}], columns => [qw/name members/]);
2895            for my $group (@{$groups}) {
2896                for my $host_name (@{$group->{'members'}}) {
2897                    $all_hosts->{$host_name} = 1;
2898                }
2899            }
2900        }
2901        for my $host_name (sort keys %{$all_hosts}) {
2902            Thruk::Utils::References::get_service_matches($c, $c->stash->{'param_backend'}, { $c->stash->{'param_backend'} => 1 }, $other_refs, $host_name, $obj->get_primary_name() || $obj->get_name());
2903        }
2904    }
2905    elsif($obj->get_type() eq 'servicegroup') {
2906        Thruk::Utils::References::get_servicegroup_matches($c, $c->stash->{'param_backend'}, { $c->stash->{'param_backend'} => 1 }, $other_refs, $obj->get_primary_name() || $obj->get_name());
2907    }
2908    elsif($obj->get_type() eq 'contact') {
2909        Thruk::Utils::References::get_contact_matches($c, $c->stash->{'param_backend'}, { $c->stash->{'param_backend'} => 1 }, $other_refs, $obj->get_primary_name() || $obj->get_name());
2910    }
2911    $other_refs = $other_refs->{$c->stash->{'param_backend'}};
2912    delete $other_refs->{'Livestatus'};
2913    delete $other_refs->{'Configuration'};
2914
2915    # remove duplicates
2916    for my $key (sort keys %{$other_refs}) {
2917        my $uniq = {};
2918        my @new = ();
2919        for my $entry (@{$other_refs->{$key}}) {
2920            my $first = 0;
2921            if(!$uniq->{$entry->{'name'}}->{$entry->{'details'}}) {
2922                $uniq->{$entry->{'name'}}->{$entry->{'details'}} = 0;
2923                $first = 1;
2924            }
2925            $uniq->{$entry->{'name'}}->{$entry->{'details'}}++;
2926            push @new, $entry if $first;
2927        }
2928        for my $n (@new) {
2929            if($uniq->{$n->{'name'}}->{$n->{'details'}} > 1) {
2930                $n->{'details'} = $n->{'details'}.' ('.$uniq->{$n->{'name'}}->{$n->{'details'}}.' times)';
2931            }
2932        }
2933        $other_refs->{$key} = \@new;
2934    }
2935
2936    return($other_refs);
2937}
2938
2939##########################################################
2940
29411;