c0c327d358d932989d8bfbfbfb9641386be5fdd7
[BackupPC.git] / lib / BackupPC / CGI / EditConfig.pm
1 #============================================================= -*-perl-*-
2 #
3 # BackupPC::CGI::EditConfig package
4 #
5 # DESCRIPTION
6 #
7 #   This module implements the EditConfig action for the CGI interface.
8 #
9 # AUTHOR
10 #   Craig Barratt  <cbarratt@users.sourceforge.net>
11 #
12 # COPYRIGHT
13 #   Copyright (C) 2004  Craig Barratt
14 #
15 #   This program is free software; you can redistribute it and/or modify
16 #   it under the terms of the GNU General Public License as published by
17 #   the Free Software Foundation; either version 2 of the License, or
18 #   (at your option) any later version.
19 #
20 #   This program is distributed in the hope that it will be useful,
21 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
22 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 #   GNU General Public License for more details.
24 #
25 #   You should have received a copy of the GNU General Public License
26 #   along with this program; if not, write to the Free Software
27 #   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
28 #
29 #========================================================================
30 #
31 # Version 3.0.0alpha, released 23 Jan 2006.
32 #
33 # See http://backuppc.sourceforge.net.
34 #
35 #========================================================================
36
37 package BackupPC::CGI::EditConfig;
38
39 use strict;
40 use BackupPC::CGI::Lib qw(:all);
41 use BackupPC::Config::Meta qw(:all);
42 use BackupPC::Storage;
43 use Data::Dumper;
44
45 our %ConfigMenu = (
46     server => {
47         text  => "CfgEdit_Title_Server",
48         param => [
49             {text => "CfgEdit_Title_General_Parameters"},
50             {name => "ServerHost"},
51             {name => "BackupPCUser"},
52             {name => "BackupPCUserVerify"},
53             {name => "MaxOldLogFiles"},
54             {name => "TrashCleanSleepSec"},
55
56             {text => "CfgEdit_Title_Wakeup_Schedule"},
57             {name => "WakeupSchedule"},
58
59             {text => "CfgEdit_Title_Concurrent_Jobs"},
60             {name => "MaxBackups"},
61             {name => "MaxUserBackups"},
62             {name => "MaxPendingCmds"},
63             {name => "MaxBackupPCNightlyJobs"},
64             {name => "BackupPCNightlyPeriod"},
65
66             {text => "CfgEdit_Title_Pool_Filesystem_Limits"},
67             {name => "DfCmd"},
68             {name => "DfMaxUsagePct"},
69             {name => "HardLinkMax"},
70
71             {text => "CfgEdit_Title_Other_Parameters"},
72             {name => "UmaskMode"},
73             {name => "MyPath"},
74             {name => "DHCPAddressRanges"},
75             {name => "PerlModuleLoad"},
76             {name => "ServerInitdPath"},
77             {name => "ServerInitdStartCmd"},
78
79             {text => "CfgEdit_Title_Remote_Apache_Settings"},
80             {name => "ServerPort"},
81             {name => "ServerMesgSecret"},
82
83             {text => "CfgEdit_Title_Program_Paths"},
84             {name => "SshPath"},
85             {name => "NmbLookupPath"},
86             {name => "PingPath"},
87             {name => "DfPath"},
88             {name => "SplitPath"},
89             {name => "ParPath"},
90             {name => "CatPath"},
91             {name => "GzipPath"},
92             {name => "Bzip2Path"},
93
94             {text => "CfgEdit_Title_Install_Paths"},
95             {name => "TopDir"},
96             {name => "ConfDir"},
97             {name => "LogDir"},
98             {name => "CgiDir"},
99             {name => "InstallDir"},
100         ],
101     },
102     email => {
103         text  => "CfgEdit_Title_Email",
104         param => [
105             {text => "CfgEdit_Title_Email_settings"},
106             {name => "SendmailPath"},
107             {name => "EMailNotifyMinDays"},
108             {name => "EMailFromUserName"},
109             {name => "EMailAdminUserName"},
110             {name => "EMailUserDestDomain"},
111
112             {text => "CfgEdit_Title_Email_User_Messages"},
113             {name => "EMailNoBackupEverSubj"},
114             {name => "EMailNoBackupEverMesg"},
115             {name => "EMailNotifyOldBackupDays"},
116             {name => "EMailNoBackupRecentSubj"},
117             {name => "EMailNoBackupRecentMesg"},
118             {name => "EMailNotifyOldOutlookDays"},
119             {name => "EMailOutlookBackupSubj"},
120             {name => "EMailOutlookBackupMesg"},
121             {name => "EMailHeaders"},
122         ],
123     },
124     cgi => {
125         text => "CfgEdit_Title_CGI",
126         param => [
127             {text => "CfgEdit_Title_Admin_Privileges"},
128             {name => "CgiAdminUserGroup"},
129             {name => "CgiAdminUsers"},
130
131             {text => "CfgEdit_Title_Page_Rendering"},
132             {name => "Language"},
133             {name => "CgiNavBarAdminAllHosts"},
134             {name => "CgiSearchBoxEnable"},
135             {name => "CgiNavBarLinks"},
136             {name => "CgiStatusHilightColor"},
137             {name => "CgiDateFormatMMDD"},
138             {name => "CgiHeaders"},
139             {name => "CgiExt2ContentType"},
140             {name => "CgiCSSFile"},
141
142             {text => "CfgEdit_Title_Paths"},
143             {name => "CgiURL"},
144             {name => "CgiImageDir"},
145             {name => "CgiImageDirURL"},
146
147             {text => "CfgEdit_Title_User_URLs"},
148             {name => "CgiUserHomePageCheck"},
149             {name => "CgiUserUrlCreate"},
150
151             {text => "CfgEdit_Title_User_Config_Editing"},
152             {name => "CgiUserConfigEditEnable"},
153             {name => "CgiUserConfigEdit"},
154         ],
155     },
156     xfer => {
157         text => "CfgEdit_Title_Xfer",
158         param => [
159             {text => "CfgEdit_Title_Xfer_Settings"},
160             {name => "XferMethod", onchangeSubmit => 1},
161             {name => "XferLogLevel"},
162             {name => "ClientCharset"},
163
164             {text => "CfgEdit_Title_Smb_Settings",
165                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
166             {name => "SmbShareName",
167                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
168             {name => "SmbShareUserName",
169                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
170             {name => "SmbSharePasswd",
171                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
172
173             {text => "CfgEdit_Title_Tar_Settings",
174                 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
175             {name => "TarShareName",
176                 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
177
178             {text => "CfgEdit_Title_Rsync_Settings",
179                 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
180             {text => "CfgEdit_Title_Rsyncd_Settings",
181                 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
182             {name => "RsyncShareName",
183                 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
184             {name => "RsyncdUserName",
185                 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
186             {name => "RsyncdPasswd",
187                 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
188             {name => "RsyncdAuthRequired",
189                 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
190             {name => "RsyncCsumCacheVerifyProb",
191                 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
192
193             {text => "CfgEdit_Title_BackupPCd_Settings",
194                 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
195             {name => "BackupPCdShareName",
196                 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
197             {name => "BackupPCdPath",
198                 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
199             {name => "BackupPCdCmd",
200                 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
201             {name => "BackupPCdRestoreCmd",
202                 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
203
204             {text => "CfgEdit_Title_Archive_Settings",
205                 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
206             {name => "ArchiveDest",
207                 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
208             {name => "ArchiveComp",
209                 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
210             {name => "ArchivePar",
211                 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
212             {name => "ArchiveSplit",
213                 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
214
215             {text => "CfgEdit_Title_Include_Exclude",
216                 visible => sub { return $_[0]->{XferMethod} ne "archive"; } },
217             {name => "BackupFilesOnly",
218                 visible => sub { return $_[0]->{XferMethod} ne "archive"; } },
219             {name => "BackupFilesExclude",
220                 visible => sub { return $_[0]->{XferMethod} ne "archive"; } },
221
222             {text => "CfgEdit_Title_Smb_Paths_Commands",
223                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
224             {name => "SmbClientPath",
225                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
226             {name => "SmbClientFullCmd",
227                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
228             {name => "SmbClientIncrCmd",
229                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
230             {name => "SmbClientRestoreCmd",
231                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
232
233             {text => "CfgEdit_Title_Tar_Paths_Commands",
234                 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
235             {name => "TarClientPath",
236                 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
237             {name => "TarClientCmd",
238                 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
239             {name => "TarFullArgs",
240                 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
241             {name => "TarIncrArgs",
242                 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
243             {name => "TarClientRestoreCmd",
244                 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
245
246             {text => "CfgEdit_Title_Rsync_Paths_Commands_Args",
247                 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
248             {text => "CfgEdit_Title_Rsyncd_Port_Args",
249                 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
250             {name => "RsyncClientPath",
251                 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
252             {name => "RsyncClientCmd",
253                 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
254             {name => "RsyncClientRestoreCmd",
255                 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
256             {name => "RsyncdClientPort",
257                 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
258             {name => "RsyncArgs",
259                 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
260             {name => "RsyncRestoreArgs",
261                 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
262
263             {text => "CfgEdit_Title_Archive_Paths_Commands",
264                 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
265             {name => "ArchiveClientCmd",
266                 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
267
268         ],
269     },
270     schedule => {
271         text => "CfgEdit_Title_Schedule",
272         param => [
273             {text => "CfgEdit_Title_Full_Backups"},
274             {name => "FullPeriod"},
275             {name => "FullKeepCnt"},
276             {name => "FullKeepCntMin"},
277             {name => "FullAgeMax"},
278
279             {text => "CfgEdit_Title_Incremental_Backups"},
280             {name => "IncrPeriod"},
281             {name => "IncrKeepCnt"},
282             {name => "IncrKeepCntMin"},
283             {name => "IncrAgeMax"},
284             {name => "IncrFill"},
285
286             {text => "CfgEdit_Title_Blackouts"},
287             {name => "BlackoutBadPingLimit"},
288             {name => "BlackoutGoodCnt"},
289             {name => "BlackoutPeriods"},
290
291             {text => "CfgEdit_Title_Other"},
292             {name => "PartialAgeMax"},
293             {name => "RestoreInfoKeepCnt"},
294             {name => "ArchiveInfoKeepCnt"},
295             {name => "BackupZeroFilesIsFatal"},
296         ],
297     },
298     backup => {
299         text => "CfgEdit_Title_Backup_Settings",
300         param => [
301             {text => "CfgEdit_Title_Client_Lookup"},
302             {name => "ClientNameAlias"},
303             {name => "NmbLookupCmd"},
304             {name => "NmbLookupFindHostCmd"},
305             {name => "FixedIPNetBiosNameCheck"},
306             {name => "PingCmd"},
307             {name => "PingMaxMsec"},
308             
309             {text => "CfgEdit_Title_Other"},
310             {name => "ClientTimeout"},
311             {name => "MaxOldPerPCLogFiles"},
312             {name => "CompressLevel"},
313
314             {text => "CfgEdit_Title_User_Commands"},
315             {name => "DumpPreUserCmd"},
316             {name => "DumpPostUserCmd"},
317             {name => "DumpPreShareCmd"},
318             {name => "DumpPostShareCmd"},
319             {name => "RestorePreUserCmd"},
320             {name => "RestorePostUserCmd"},
321             {name => "ArchivePreUserCmd"},
322             {name => "ArchivePostUserCmd"},
323         ],
324     },
325     hosts => {
326         text => "CfgEdit_Title_Hosts",
327         param => [
328             {text    => "CfgEdit_Title_Hosts"},
329             {name    => "Hosts",
330              comment => "CfgEdit_Hosts_Comment"},
331         ],
332     },
333 );
334
335 sub action
336 {
337     my $pc_dir = "$TopDir/pc";
338     my($content, $contentHidden, $newConf, $override, $mainConf, $hostConf);
339     my $errors = {};
340
341     my $host = $In{host};
342     my $menu = $In{menu} || "server";
343     my $hosts_path = $Hosts;
344     my $config_path = $host eq "" ? "$TopDir/conf/config.pl"
345                                   : "$TopDir/pc/$host/config.pl";
346
347     my $Privileged = CheckPermission($host)
348                        && ($PrivAdmin || $Conf{CgiUserConfigEditEnable});
349     my $userHost = 1 if ( defined($host) );
350     my $debugText;
351
352     if ( !$Privileged ) {
353         ErrorExit(eval("qq{$Lang->{Only_privileged_users_can_edit_config_files}}"));
354     }
355
356     if ( defined($In{menu}) || $In{editAction} eq $Lang->{CfgEdit_Button_Save} ) {
357         $errors = errorCheck();
358         if ( %$errors ) {
359             #
360             # If there are errors, then go back to the same menu
361             #
362             $In{editAction} = "";
363             $In{newMenu} = "";
364         }
365         if ( (my $var = $In{overrideUncheck}) ne "" ) {
366             #
367             # a compound variable was unchecked; delete extra
368             # variables to make the shape the same.
369             #
370             #print STDERR Dumper(\%In);
371             foreach my $v ( keys(%In) ) {
372                 next if ( $v !~ /^v_z_(\Q$var\E(_z_.*|$))/ );
373                 delete($In{$v}) if ( !defined($In{"orig_z_$1"}) );
374             }
375             delete($In{"vflds.$var"});
376         }
377
378         ($newConf, $override) = inputParse($bpc, $userHost);
379         $override = undef if ( $host eq "" );
380
381     } else {
382         #
383         # First time: pick up the current config settings
384         #
385         $mainConf = $bpc->ConfigDataRead();
386         if ( $host ne "" ) {
387             $hostConf = $bpc->ConfigDataRead($host);
388             $override = {};
389             foreach my $param ( keys(%$hostConf) ) {
390                 $override->{$param} = 1;
391             }
392         } else {
393             my $hostInfo = $bpc->HostInfoRead();
394             $hostConf = {};
395             $mainConf->{Hosts} = [map($hostInfo->{$_}, sort(keys(%$hostInfo)))];
396         }
397         $newConf = { %$mainConf, %$hostConf };
398     }
399
400     if ( $In{editAction} ne $Lang->{CfgEdit_Button_Save} && $In{newMenu} ne ""
401                     && defined($ConfigMenu{$In{newMenu}}) ) {
402         $menu = $In{newMenu};
403     }
404
405     my %menuDisable;
406     if ( $userHost ) {
407         #
408         # For a non-admin user editing the host config, we need to
409         # figure out which subsets of the menu tree will be visible,
410         # based on what is enabled.  Admin users can edit all the
411         # available per-host settings.
412         #
413         foreach my $m ( keys(%ConfigMenu) ) {
414             my $enabled = 0;
415             my $text = -1;
416             my $n = 0;
417             my @mask = ();
418
419             foreach my $paramInfo ( @{$ConfigMenu{$m}{param}} ) {
420                 my $param = $paramInfo->{name};
421                 if ( defined($paramInfo->{text}) ) {
422                     $text = $n;
423                     $mask[$text] = 1;
424                 } else {
425                     if ( $bpc->{Conf}{CgiUserConfigEdit}{$param}
426                           || (defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
427                                 && $PrivAdmin) ) {
428                         $mask[$text] = 0 if ( $text >= 0 );
429                         $mask[$n] = 0;
430                         $enabled ||= 1;
431                     } else {
432                         $mask[$n] = 1;
433                     }
434                 }
435                 $n++;
436             }
437             $menuDisable{$m}{mask} = \@mask;
438             $menuDisable{$m}{top}  = !$enabled;
439         }
440         if ( $menuDisable{$menu}{top} ) {
441             #
442             # Find an enabled menu if the current menu is empty
443             #
444             foreach my $m ( sort(keys(%menuDisable)) ) {
445                 if ( !$menuDisable{$m}{top} ) {
446                     $menu = $m;
447                     last;
448                 }
449             }
450         }
451     }
452
453     my $groupText;
454     foreach my $m ( keys(%ConfigMenu) ) {
455         next if ( $menuDisable{$m}{top} );
456         my $text = eval("qq($Lang->{$ConfigMenu{$m}{text}})");
457         if ( $m eq $menu ) {
458             $groupText .= <<EOF;
459 <td class="editTabSel"><a href="javascript:menuSubmit('$m')"><b>$text</b></a></td>
460 EOF
461         } else {
462             $groupText .= <<EOF;
463 <td class="editTabNoSel"><a href="javascript:menuSubmit('$m')">$text</a></td>
464 EOF
465         }
466     }
467
468     if ( $host eq "" ) {
469         $content .= eval("qq($Lang->{CfgEdit_Header_Main})");
470     } else {
471         $content .= eval("qq($Lang->{CfgEdit_Header_Host})");
472     }
473
474     my $saveDisplay = "block";
475     $saveDisplay = "none" if ( !$In{modified}
476                           || $In{editAction} eq $Lang->{CfgEdit_Button_Save} );
477     #
478     # Add action and host to the URL so the nav bar link is
479     # highlighted
480     #
481     my $url = "$MyURL?action=editConfig";
482     $url .= "&host=$host" if ( $host ne "" );
483     $content .= <<EOF;
484 <table border="0" cellpadding="2">
485 <tr>$groupText</tr>
486 <tr>
487 <form method="post" name="form1" action="$url">
488 <input type="hidden" name="host" value="$host">
489 <input type="hidden" name="menu" value="$menu">
490 <input type="hidden" name="newMenu" value="">
491 <input type="hidden" name="modified" value="$In{modified}">
492 <input type="hidden" name="deleteVar" value="">
493 <input type="hidden" name="insertVar" value="">
494 <input type="hidden" name="overrideUncheck" value="">
495 <input type="hidden" name="addVar" value="">
496 <input type="hidden" name="action" value="editConfig">
497 <input type="submit" class="editSaveButton" style="display: $saveDisplay" name="editAction" value="${EscHTML($Lang->{CfgEdit_Button_Save})}">
498
499 <script language="javascript" type="text/javascript">
500 <!--
501
502     function deleteSubmit(varName)
503     {
504         document.form1.deleteVar.value = varName;
505         document.form1.modified.value = 1;
506         document.form1.submit();
507         return;
508     }
509
510     function insertSubmit(varName)
511     {
512         document.form1.insertVar.value = varName;
513         document.form1.modified.value = 1;
514         document.form1.submit();
515         return;
516     }
517
518     function addSubmit(varName, checkKey)
519     {
520         if ( checkKey
521             && eval("document.form1.addVarKey_" + varName + ".value") == "" ) {
522             alert("New key must be non-empty");
523             return;
524         }
525         document.form1.addVar.value = varName;
526         document.form1.modified.value = 1;
527         document.form1.submit();
528         return;
529     }
530
531     function menuSubmit(menuName)
532     {
533         document.form1.newMenu.value = menuName;
534         document.form1.submit();
535     }
536
537     function varChange(varName)
538     {
539         document.form1.editAction.style.display = "block";
540         document.form1.modified.value = 1;
541     }
542
543     function checkboxChange(varName)
544     {
545         document.form1.editAction.style.display = "block";
546         document.form1.modified.value = 1;
547         // Do nothing if the checkbox is now set
548         if ( eval("document.form1.override_" + varName + ".checked") ) {
549             return false;
550         }
551         var allVars = {};
552         var varRE  = new RegExp("^v_z_(" + varName + ".*)");
553         var origRE = new RegExp("^orig_z_(" + varName + ".*)");
554         for ( var i = 0 ; i < document.form1.elements.length ; i++ ) {
555             var e = document.form1.elements[i];
556             var re;
557             if ( (re = varRE.exec(e.name)) != null ) {
558                 if ( allVars[re[1]] == null ) {
559                     allVars[re[1]] = 0;
560                 }
561                 allVars[re[1]]++;
562                 //debugMsg("found v_z_ match with " + re[1]);
563                 //debugMsg("allVars[" + re[1] + "] = " + allVars[re[1]]);
564             } else if ( (re = origRE.exec(e.name)) != null ) {
565                 if ( allVars[re[1]] == null ) {
566                     allVars[re[1]] = 0;
567                 }
568                 allVars[re[1]]--;
569                 //debugMsg("allVars[" + re[1] + "] = " + allVars[re[1]]);
570             }
571         }
572         var sameShape = 1;
573         for ( v in allVars ) {
574             if ( allVars[v] != 0 ) {
575                 //debugMsg("Not the same shape because of " + v);
576                 sameShape = 0;
577             } else {
578                 // copy the original variable values
579                 //debugMsg("setting " + v);
580                 eval("document.form1.v_z_" + v + ".value = document.form1.orig_z_" + v + ".value");
581             }
582         }
583         if ( sameShape ) {
584             return true;
585         } else {
586             // need to rebuild the form since the compound variable
587             // has changed shape
588             document.form1.overrideUncheck.value = varName;
589             document.form1.submit();
590             return false;
591         }
592     }
593
594     function checkboxSet(varName)
595     {
596         document.form1.editAction.style.display = "block";
597         document.form1.modified.value = 1;
598         eval("document.form1.override_" + varName + ".checked = 1;");
599         return false;
600     }
601
602     var debugCounter = 0;
603     function debugMsg(msg)
604     {
605         debugCounter++;
606         var t = document.createTextNode(debugCounter + ": " + msg);
607         var br = document.createElement("br");
608         var debug = document.getElementById("debug");
609         debug.appendChild(t);
610         debug.appendChild(br);
611     }
612
613     function displayHelp(varName)
614     {
615         var help = document.getElementById("id_" + varName);
616         help.style.display = help.style.display == "block" ? "none" : "block";
617     }
618
619 //-->
620 </script>
621
622 <span id="debug">$debugText</span>
623
624 EOF
625
626     $content .= <<EOF;
627 <table border="1" cellspacing="0">
628 EOF
629
630     my $doneParam = {};
631
632     #
633     # There is a special case of the user deleting just the field
634     # that has the error(s).  So if the delete variable is a match
635     # or parent to all the errors then ignore the errors.
636     #
637     if ( $In{deleteVar} ne "" && %$errors > 0 ) {
638         my $matchAll = 1;
639         foreach my $v ( keys(%$errors) ) {
640             if ( $v ne $In{deleteVar} && $v !~ /^\Q$In{deleteVar}_z_/ ) {
641                 $matchAll = 0;
642                 last;
643             }
644         }
645         $errors = {} if ( $matchAll );
646     }
647
648     my $isError = %$errors;
649
650     if ( !$isError && $In{editAction} eq $Lang->{CfgEdit_Button_Save} ) {
651         my($mesg, $err);
652         if ( $host ne "" ) {
653             $hostConf = $bpc->ConfigDataRead($host) if ( !defined($hostConf) );
654             my %hostConf2 = %$hostConf;
655             foreach my $param ( keys(%$newConf) ) {
656                 if ( $override->{$param} ) {
657                     $hostConf->{$param} = $newConf->{$param}
658                 } else {
659                     delete($hostConf->{$param});
660                 }
661             }
662             $mesg = configDiffMesg($host, \%hostConf2, $hostConf);
663             $err .= $bpc->ConfigDataWrite($host, $hostConf);
664         } else {
665             $mainConf = $bpc->ConfigDataRead() if ( !defined($mainConf) );
666
667             my $hostsSave = [];
668             my($hostsNew, $allHosts, $copyConf);
669             foreach my $entry ( @{$newConf->{Hosts}} ) {
670                 next if ( $entry->{host} eq "" );
671                 $allHosts->{$entry->{host}} = 1;
672                 $allHosts->{$1} = 1 if ( $entry->{host} =~ /(.+?)\s*=/ );
673             }
674             foreach my $entry ( @{$newConf->{Hosts}} ) {
675                 next if ( $entry->{host} eq ""
676                            || defined($hostsNew->{$entry->{host}}) );
677                 if ( $entry->{host} =~ /(.+?)\s*=\s*(.+)/ ) {
678                     if ( defined($allHosts->{$2}) ) {
679                         $entry->{host} = $1;
680                         $copyConf->{$1} = $2;
681                     } else {
682                         my $fullHost = $entry->{host};
683                         my $copyHost = $2;
684                         $err .= eval("qq($Lang->{CfgEdit_Error_Copy_host_does_not_exist})");
685                     }
686                 }
687                 push(@$hostsSave, $entry);
688                 $hostsNew->{$entry->{host}} = $entry;
689             }
690             ($mesg, my $hostChange) = hostsDiffMesg($hostsNew);
691             $bpc->HostInfoWrite($hostsNew) if ( $hostChange );
692             foreach my $host ( keys(%$copyConf) ) {
693                 my $confData = $bpc->ConfigDataRead($copyConf->{$host});
694                 my $fromHost = $copyConf->{$host};
695                 $err  .= $bpc->ConfigDataWrite($host, $confData);
696                 $mesg .= eval("qq($Lang->{CfgEdit_Log_Copy_host_config})");
697             }
698
699             delete($newConf->{Hosts});
700             $mesg .= configDiffMesg(undef, $mainConf, $newConf);
701             $mainConf = { %$mainConf, %$newConf };
702             $err .= $bpc->ConfigDataWrite(undef, $mainConf);
703             $newConf->{Hosts} = $hostsSave;
704         }
705         if ( defined($err) ) {
706             $content .= <<EOF;
707 <tr><td colspan="2" class="border"><span class="editError">$err</span></td></tr>
708 EOF
709         }
710         $bpc->ServerConnect();
711         if ( $mesg ne "" ) {
712             (my $mesgBR = $mesg) =~ s/\n/<br>\n/g;
713             $content .= <<EOF;
714 <tr><td colspan="2" class="border"><span class="editComment">$mesgBR</span></td></tr>
715 EOF
716             foreach my $str ( split(/\n/, $mesg) ) {
717                 $bpc->ServerMesg("log $str") if ( $str ne "" );
718             }
719         }
720         #
721         # Tell the server to reload, unless we only changed
722         # a client config
723         #
724         $bpc->ServerMesg("server reload") if ( $host eq "" );
725     }
726
727     my @mask = @{$menuDisable{$menu}{mask} || []};
728
729     foreach my $paramInfo ( @{$ConfigMenu{$menu}{param}} ) {
730
731         my $param    = $paramInfo->{name};
732         my $disabled = shift(@mask);
733
734         next if ( $disabled || $menuDisable{$menu}{top} );
735         if ( ref($paramInfo->{visible}) eq "CODE"
736                         && !&{$paramInfo->{visible}}($newConf) ) {
737             next;
738         }
739
740         if ( defined($paramInfo->{text}) ) {
741             my $text = eval("qq($Lang->{$paramInfo->{text}})");
742             $content .= <<EOF;
743 <tr><td colspan="2" class="editHeader">$text</td></tr>
744 EOF
745             next;
746         }
747
748         #
749         # TODO: get parameter documentation
750         #
751         my $comment = "";
752         #$comment =~ s/\'//g;
753         #$comment =~ s/\"//g;
754         #$comment =~ s/\n/ /g;
755
756         $doneParam->{$param} = 1;
757
758         $content .= fieldEditBuild($ConfigMeta{$param},
759                               $param,
760                               $newConf->{$param},
761                               $errors,
762                               0,
763                               $comment,
764                               $isError,
765                               $paramInfo->{onchangeSubmit},
766                               defined($override) ? $param : undef,
767                               defined($override) ? $override->{$param} : undef
768                         );
769         if ( defined($paramInfo->{comment}) ) {
770             my $topDir = $bpc->TopDir;
771             my $text = eval("qq($Lang->{$paramInfo->{comment}})");
772             $content .= <<EOF;
773 <tr><td colspan="2" class="editComment">$text</td></tr>
774 EOF
775         }
776     }
777
778     #
779     # Emit any remaining errors - should not happen
780     #
781     foreach my $param ( sort(keys(%$errors)) ) {
782         $content .= <<EOF;
783 <tr><td colspan="2" class="border"><span class="editError">$errors->{$param}</span></td></tr>
784 EOF
785         delete($errors->{$param});
786     }
787
788     $content .= <<EOF;
789 </table>
790 EOF
791
792     #
793     # Emit all the remaining editable config settings as hidden values
794     #
795     foreach my $param ( keys(%ConfigMeta) ) {
796         next if ( $doneParam->{$param} );
797         next if ( $userHost
798                       && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
799                          || (!$PrivAdmin
800                              && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
801         $content .= fieldHiddenBuild($ConfigMeta{$param},
802                             $param,
803                             $newConf->{$param},
804                             "v"
805                         );
806         if ( defined($override) ) {
807             $content .= <<EOF;
808 <input type="hidden" name="override_$param" value="$override->{$param}">
809 EOF
810         }
811         $doneParam->{$param} = 1;
812     }
813
814     if ( defined($In{menu}) || $In{editAction} eq $Lang->{CfgEdit_Button_Save} ) {
815         if ( $In{editAction} eq $Lang->{CfgEdit_Button_Save}
816                 && !$userHost ) {
817             #
818             # Emit the new settings as orig_z_ parameters
819             #
820             $doneParam = {};
821             foreach my $param ( keys(%ConfigMeta) ) {
822                 next if ( $doneParam->{$param} );
823                 next if ( $userHost
824                           && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
825                              || (!$PrivAdmin
826                                 && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
827                 $contentHidden .= fieldHiddenBuild($ConfigMeta{$param},
828                                         $param,
829                                         $newConf->{$param},
830                                         "orig",
831                                     );
832                 $doneParam->{$param} = 1;
833                 $In{modified} = 0;
834             }
835         } else {
836             #
837             # Just switching menus: copy all the orig_z_ input parameters
838             #
839             foreach my $var ( keys(%In) ) {
840                 next if ( $var !~ /^orig_z_/ );
841                 $contentHidden .= <<EOF;
842 <input type="hidden" name="$var" value="${EscHTML($In{$var})}">
843 EOF
844             }
845         }
846     } else {
847         #
848         # First time: emit all the original config settings
849         #
850         $doneParam = {};
851         foreach my $param ( keys(%ConfigMeta) ) {
852             next if ( $doneParam->{$param} );
853             next if ( $userHost
854                           && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
855                              || (!$PrivAdmin
856                                 && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
857             $contentHidden .= fieldHiddenBuild($ConfigMeta{$param},
858                                     $param,
859                                     $mainConf->{$param},
860                                     "orig",
861                                 );
862             $doneParam->{$param} = 1;
863         }
864     }
865
866     $content .= <<EOF;
867 $contentHidden
868 </form>
869 </tr>
870 </table>
871 EOF
872
873     Header("Config Edit", $content);
874     Trailer();
875 }
876
877 sub fieldHiddenBuild
878 {
879     my($type, $varName, $varValue, $prefix) = @_;
880     my $content;
881
882     $type = { type => $type } if ( ref($type) ne "HASH" );
883
884     if ( $type->{type} eq "list" ) {
885         $varValue = [] if ( !defined($varValue) );
886         $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
887
888         for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
889             $content .= fieldHiddenBuild($type->{child}, "${varName}_z_$i",
890                                          $varValue->[$i], $prefix);
891         }
892     } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
893         $varValue = {} if ( ref($varValue) ne "HASH" );
894         my(@order, $childType);
895
896         if ( defined($type->{order}) ) {
897             @order = @{$type->{order}};
898         } elsif ( defined($type->{child}) ) {
899             @order = sort(keys(%{$type->{child}}));
900         } else {
901             @order = sort(keys(%$varValue));
902         }
903
904         foreach my $fld ( @order ) {
905             if ( defined($type->{child}) ) {
906                 $childType = $type->{child}{$fld};
907             } else {
908                 $childType = $type->{childType};
909                 #
910                 # emit list of fields since they are user-defined
911                 # rather than hard-coded
912                 #
913                 $content .= <<EOF;
914 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
915 EOF
916             }
917             $content .= fieldHiddenBuild($childType, "${varName}_z_$fld",
918                                          $varValue->{$fld}, $prefix);
919         }
920     } elsif ( $type->{type} eq "shortlist" ) {
921         $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
922         $varValue = join(", ", @$varValue);
923         $content .= <<EOF;
924 <input type="hidden" name="${prefix}_z_$varName" value="${EscHTML($varValue)}">
925 EOF
926     } else {
927         $content .= <<EOF;
928 <input type="hidden" name="${prefix}_z_$varName" value="${EscHTML($varValue)}">
929 EOF
930     }
931     return $content;
932 }
933
934 sub fieldEditBuild
935 {
936     my($type, $varName, $varValue, $errors, $level, $comment, $isError,
937        $onchangeSubmit, $overrideVar, $overrideSet) = @_;
938
939     my $content;
940     my $size = 50 - 10 * $level;
941     $type = { type => $type } if ( ref($type) ne "HASH" );
942
943     $size = $type->{size} if ( defined($type->{size}) );
944
945     #
946     # These fragments allow inline conent to be turned on and off
947     #
948     # <tr><td colspan="2"><span id="id_$varName" style="display: none" class="editComment">$comment</span></td></tr>
949     # <tr><td class="border"><a href="javascript: displayHelp('$varName')">$varName</a>
950     #
951
952     if ( $level == 0 ) {
953         my $lcVarName = lc($varName);
954         $content .= <<EOF;
955 <tr><td class="border"><a href="?action=view&type=docs#item_%24conf%7b$lcVarName%7d">$varName</a>
956 EOF
957         if ( defined($overrideVar) ) {
958             my $override_checked = "";
959             if ( !$isError && $In{deleteVar}      =~ /^\Q${varName}_z_/
960                    || !$isError && $In{insertVar} =~ /^\Q${varName}\E(_z_|$)/
961                    || !$isError && $In{addVar}    =~ /^\Q${varName}\E(_z_|$)/ ) {
962                 $overrideSet = 1;
963             }
964             if ( $overrideSet ) {
965                 $override_checked = "checked";
966             }
967             $content .= <<EOF;
968 <br><input type="checkbox" name="override_$varName" $override_checked value="1" onClick="checkboxChange('$varName')">\&nbsp;${EscHTML($Lang->{CfgEdit_Button_Override})}
969 EOF
970         }
971         $content .= "</td>\n";
972     }
973
974     if ( $type->{type} eq "list" ) {
975         $content .= "<td class=\"border\">\n";
976         $varValue = [] if ( !defined($varValue) );
977         $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
978         if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_z_\E(\d+)$/
979                 && $1 < @$varValue ) {
980             #
981             # User deleted entry in this array
982             #
983             splice(@$varValue, $1, 1) if ( @$varValue > 1 || $type->{emptyOk} );
984             $In{deleteVar} = "";
985         }
986         if ( !$isError && $In{insertVar} =~ /^\Q${varName}_z_\E(\d+)$/
987                 && $1 < @$varValue ) {
988             #
989             # User inserted entry in this array
990             #
991             splice(@$varValue, $1, 0, "")
992                         if ( @$varValue > 1 || $type->{emptyOk} );
993             $In{insertVar} = "";
994         }
995         if ( !$isError && $In{addVar} eq $varName ) {
996             #
997             # User added entry to this array
998             #
999             push(@$varValue, undef);
1000             $In{addVar} = "";
1001         }
1002         $content .= "<table border=\"1\" cellspacing=\"0\">\n";
1003
1004         if ( ref($type) eq "HASH" && ref($type->{child}) eq "HASH"
1005                     && $type->{child}{type} eq "horizHash" ) {
1006             my @order;
1007             if ( defined($type->{child}{order}) ) {
1008                 @order = @{$type->{child}{order}};
1009             } else {
1010                 @order = sort(keys(%{$type->{child}{child}}));
1011             }
1012             $content .= "<tr><td class=\"border\"></td>\n";
1013             for ( my $i = 0 ; $i < @order ; $i++ ) {
1014                 $content .= "<td>$order[$i]</td>\n";
1015             }
1016             $content .= "</tr>\n";
1017             for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
1018                 if ( @$varValue > 1 || $type->{emptyOk} ) {
1019                     $content .= <<EOF;
1020 <td class="border">
1021 <input type="button" name="del_${varName}_z_$i" value="${EscHTML($Lang->{CfgEdit_Button_Delete})}"
1022     onClick="deleteSubmit('${varName}_z_$i')">
1023 </td>
1024 EOF
1025                 }
1026                 $content .= fieldEditBuild($type->{child}, "${varName}_z_$i",
1027                                   $varValue->[$i], $errors, $level + 1, undef,
1028                                   $isError, $onchangeSubmit,
1029                                   $overrideVar, $overrideSet);
1030                 $content .= "</tr>\n";
1031             }
1032         } else {
1033             for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
1034                 $content .= <<EOF;
1035 <tr><td class="border">
1036 <input type="button" name="ins_${varName}_z_$i" value="${EscHTML($Lang->{CfgEdit_Button_Insert})}"
1037     onClick="insertSubmit('${varName}_z_$i')">
1038 EOF
1039                 if ( @$varValue > 1 || $type->{emptyOk} ) {
1040                     $content .= <<EOF;
1041 <input type="button" name="del_${varName}_z_$i" value="${EscHTML($Lang->{CfgEdit_Button_Delete})}"
1042     onClick="deleteSubmit('${varName}_z_$i')">
1043 EOF
1044                 }
1045                 $content .= "</td>\n";
1046                 $content .= fieldEditBuild($type->{child}, "${varName}_z_$i",
1047                                     $varValue->[$i], $errors, $level + 1, undef,
1048                                     $isError, $onchangeSubmit,
1049                                     $overrideVar, $overrideSet);
1050                 $content .= "</tr>\n";
1051             }
1052         }
1053         $content .= <<EOF;
1054 <tr><td class="border"><input type="button" name="add_$varName" value="${EscHTML($Lang->{CfgEdit_Button_Add})}"
1055     onClick="addSubmit('$varName')"></td></tr>
1056 </table>
1057 EOF
1058         $content .= "</td>\n";
1059     } elsif ( $type->{type} eq "hash" ) {
1060         $content .= "<td class=\"border\">\n";
1061         $content .= "<table border=\"1\" cellspacing=\"0\">\n";
1062         $varValue = {} if ( ref($varValue) ne "HASH" );
1063
1064         if ( !$isError && !$type->{noKeyEdit}
1065                         && $In{deleteVar} !~ /^\Q${varName}_z_\E.*_z_/
1066                         && $In{deleteVar} =~ /^\Q${varName}_z_\E(\w+)$/ ) {
1067             #
1068             # User deleted entry in this hash
1069             #
1070             delete($varValue->{$1}) if ( keys(%$varValue) > 1
1071                                             || $type->{emptyOk} );
1072             $In{deleteVar} = "";
1073         }
1074         if ( !$isError && !defined($type->{child})
1075                         && $In{addVar} eq $varName ) {
1076             #
1077             # User added entry to this array
1078             #
1079             $varValue->{$In{"addVarKey_$varName"}} = ""
1080                         if ( !defined($varValue->{$In{"addVarKey_$varName"}}) );
1081             $In{addVar} = "";
1082         }
1083         my(@order, $childType);
1084
1085         if ( defined($type->{order}) ) {
1086             @order = @{$type->{order}};
1087         } elsif ( defined($type->{child}) ) {
1088             @order = sort(keys(%{$type->{child}}));
1089         } else {
1090             @order = sort(keys(%$varValue));
1091         }
1092
1093         foreach my $fld ( @order ) {
1094             $content .= <<EOF;
1095 <tr><td class="border">$fld
1096 EOF
1097             if ( !$type->{noKeyEdit}
1098                     && (keys(%$varValue) > 1 || $type->{emptyOk}) ) {
1099                 $content .= <<EOF;
1100 <input type="submit" name="del_${varName}_z_$fld" value="${EscHTML($Lang->{CfgEdit_Button_Delete})}"
1101         onClick="deleteSubmit('${varName}_z_$fld')">
1102 EOF
1103             }
1104             if ( defined($type->{child}) ) {
1105                 $childType = $type->{child}{$fld};
1106             } else {
1107                 $childType = $type->{childType};
1108                 #
1109                 # emit list of fields since they are user-defined
1110                 # rather than hard-coded
1111                 #
1112                 $content .= <<EOF;
1113 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
1114 EOF
1115             }
1116             $content .= "</td>\n";
1117             $content .= fieldEditBuild($childType, "${varName}_z_$fld",
1118                             $varValue->{$fld}, $errors, $level + 1, undef,
1119                             $isError, $onchangeSubmit,
1120                             $overrideVar, $overrideSet);
1121             $content .= "</tr>\n";
1122         }
1123
1124         if ( !$type->{noKeyEdit} ) {
1125             $content .= <<EOF;
1126 <tr><td class="border" colspan="2">
1127 New key: <input type="text" name="addVarKey_$varName" size="20" maxlength="256" value="">
1128 <input type="button" name="add_$varName" value="${EscHTML($Lang->{CfgEdit_Button_Add})}" onClick="addSubmit('$varName', 1)">
1129 </td></tr>
1130 EOF
1131         }
1132         $content .= "</table>\n";
1133         $content .= "</td>\n";
1134     } elsif ( $type->{type} eq "horizHash" ) {
1135         $varValue = {} if ( ref($varValue) ne "HASH" );
1136         my(@order, $childType);
1137
1138         if ( defined($type->{order}) ) {
1139             @order = @{$type->{order}};
1140         } elsif ( defined($type->{child}) ) {
1141             @order = sort(keys(%{$type->{child}}));
1142         } else {
1143             @order = sort(keys(%$varValue));
1144         }
1145
1146         foreach my $fld ( @order ) {
1147             if ( defined($type->{child}) ) {
1148                 $childType = $type->{child}{$fld};
1149             } else {
1150                 $childType = $type->{childType};
1151                 #
1152                 # emit list of fields since they are user-defined
1153                 # rather than hard-coded
1154                 #
1155                 $content .= <<EOF;
1156 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
1157 EOF
1158             }
1159             $content .= fieldEditBuild($childType, "${varName}_z_$fld",
1160                             $varValue->{$fld}, $errors, $level + 1, undef,
1161                             $isError, $onchangeSubmit,
1162                             $overrideVar, $overrideSet);
1163         }
1164     } else {
1165         $content .= "<td class=\"border\">\n";
1166         if ( $isError ) {
1167             #
1168             # If there was an error, we use the original post values
1169             # in %In, rather than the parsed values in $varValue.
1170             # This is so that the user's erroneous input is preserved.
1171             #
1172             $varValue = $In{"v_z_$varName"} if ( defined($In{"v_z_$varName"}) );
1173         }
1174         if ( defined($errors->{$varName}) ) {
1175             $content .= <<EOF;
1176 <span class="editError">$errors->{$varName}</span><br>
1177 EOF
1178             delete($errors->{$varName});
1179         }
1180         my $onChange;
1181         if ( defined($overrideVar) ) {
1182             $onChange .= "checkboxSet('$overrideVar');";
1183         } else {
1184             $onChange .= "varChange('$overrideVar');";
1185         }
1186         if ( $onchangeSubmit ) {
1187             $onChange .= "document.form1.submit();";
1188         }
1189         if ( $onChange ne "" ) {
1190             $onChange = " onChange=\"$onChange\"";
1191         }
1192         if ( $varValue !~ /\n/ &&
1193                 ($type->{type} eq "integer"
1194                     || $type->{type} eq "string"
1195                     || $type->{type} eq "execPath"
1196                     || $type->{type} eq "shortlist"
1197                     || $type->{type} eq "float") ) {
1198             # simple input box
1199             if ( $type->{type} eq "shortlist" ) {
1200                 $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
1201                 $varValue = join(", ", @$varValue);
1202             }
1203             my $textType = ($varName =~ /Passwd/) ? "password" : "text";
1204             $content .= <<EOF;
1205 <input type="$textType" name="v_z_$varName" size="$size" maxlength="256" value="${EscHTML($varValue)}"$onChange>
1206 EOF
1207         } elsif ( $type->{type} eq "boolean" ) {
1208             # checkbox
1209             my $checked = "checked" if ( $varValue );
1210             $content .= <<EOF;
1211 <input type="checkbox" name="v_z_$varName" $checked value="1"$onChange>
1212 EOF
1213         } elsif ( $type->{type} eq "select" ) {
1214             $content .= <<EOF;
1215 <select name="v_z_$varName"$onChange>
1216 EOF
1217             foreach my $option ( @{$type->{values}} ) {
1218                 my $sel = " selected" if ( $varValue eq $option );
1219                 $content .= "<option$sel>$option</option>\n";
1220             }
1221             $content .= "</select>\n";
1222         } else {
1223             # multi-line text area - count number of lines
1224             my $rowCnt = $varValue =~ tr/\n//;
1225             $rowCnt = 1 if ( $rowCnt < 1 );
1226             $content .= <<EOF;
1227 <textarea name="v_z_$varName" cols="$size" rows="$rowCnt"$onChange>${EscHTML($varValue)}</textarea>
1228 EOF
1229         }
1230         $content .= "</td>\n";
1231     }
1232     return $content;
1233 }
1234
1235 sub errorCheck
1236 {
1237     my $errors = {};
1238
1239     foreach my $param ( keys(%ConfigMeta) ) {
1240         fieldErrorCheck($ConfigMeta{$param}, $param, $errors);
1241     }
1242     return $errors;
1243 }
1244
1245 sub fieldErrorCheck
1246 {
1247     my($type, $varName, $errors) = @_;
1248
1249     $type = { type => $type } if ( ref($type) ne "HASH" );
1250
1251     if ( $type->{type} eq "list" ) {
1252         for ( my $i = 0 ; ; $i++ ) {
1253             last if ( fieldErrorCheck($type->{child}, "${varName}_z_$i", $errors) );
1254         }
1255     } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
1256         my(@order, $childType);
1257         my $ret;
1258
1259         if ( defined($type->{order}) ) {
1260             @order = @{$type->{order}};
1261         } elsif ( defined($type->{child}) ) {
1262             @order = sort(keys(%{$type->{child}}));
1263         } else {
1264             @order = split(/\0/, $In{"vflds.$varName"});
1265         }
1266         foreach my $fld ( @order ) {
1267             if ( defined($type->{child}) ) {
1268                 $childType = $type->{child}{$fld};
1269             } else {
1270                 $childType = $type->{childType};
1271             }
1272             $ret ||= fieldErrorCheck($childType, "${varName}_z_$fld", $errors);
1273         }
1274         return $ret;
1275     } else {
1276         $In{"v_z_$varName"} = "0" if ( $type->{type} eq "boolean"
1277                                         && $In{"v_z_$varName"} eq "" );
1278
1279         return 1 if ( !exists($In{"v_z_$varName"}) );
1280
1281         (my $var = $varName) =~ s/_z_/./g;
1282
1283         if ( $type->{type} eq "integer"
1284                 || $type->{type} eq "boolean" ) {
1285             if ( $In{"v_z_$varName"} !~ /^-?\d+\s*$/s
1286                             && $In{"v_z_$varName"} ne "" ) {
1287                 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_an_integer}}");
1288             }
1289         } elsif ( $type->{type} eq "float" ) {
1290             if ( $In{"v_z_$varName"} !~ /^-?\d*(\.\d*)?\s*$/s
1291                             && $In{"v_z_$varName"} ne "" ) {
1292                 $errors->{$varName}
1293                         = eval("qq{$Lang->{CfgEdit_Error__must_be_real_valued_number}}");
1294             }
1295         } elsif ( $type->{type} eq "shortlist" ) {
1296             my @vals = split(/[,\s]+/, $In{"v_z_$varName"});
1297             for ( my $i = 0 ; $i < @vals ; $i++ ) {
1298                 if ( $type->{child} eq "integer"
1299                         && $vals[$i] !~ /^-?\d+\s*$/s
1300                         && $vals[$i] ne "" ) {
1301                     my $k = $i + 1;
1302                     $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__entry__must_be_an_integer}}");
1303                 } elsif ( $type->{child} eq "float"
1304                         && $vals[$i] !~ /^-?\d*(\.\d*)?\s*$/s
1305                         && $vals[$i] ne "" ) {
1306                     my $k = $i + 1;
1307                     $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__entry__must_be_real_valued_number}}");
1308                 }
1309             }
1310         } elsif ( $type->{type} eq "select" ) {
1311             my $match = 0;
1312             foreach my $option ( @{$type->{values}} ) {
1313                 if ( $In{"v_z_$varName"} eq $option ) {
1314                     $match = 1;
1315                     last;
1316                 }
1317             }
1318             $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_valid_option}}")
1319                             if ( !$match );
1320         } elsif ( $type->{type} eq "execPath" ) {
1321             if ( $In{"v_z_$varName"} ne "" && !-x $In{"v_z_$varName"} ) {
1322                 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_executable_program}}");
1323             }
1324         } else {
1325             #
1326             # $type->{type} eq "string": no error checking
1327             #
1328         }
1329     }
1330     return 0;
1331 }
1332
1333 sub inputParse
1334 {
1335     my($bpc, $userHost) = @_;
1336     my $conf     = {};
1337     my $override = {};
1338
1339     foreach my $param ( keys(%ConfigMeta) ) {
1340         my $value;
1341         next if ( $userHost
1342                       && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
1343                          || (!$PrivAdmin
1344                             && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
1345         fieldInputParse($ConfigMeta{$param}, $param, \$value);
1346         $conf->{$param}     = $value;
1347         $override->{$param} = $In{"override_$param"};
1348     }
1349     return ($conf, $override);
1350 }
1351
1352 sub fieldInputParse
1353 {
1354     my($type, $varName, $value) = @_;
1355
1356     $type = { type => $type } if ( ref($type) ne "HASH" );
1357
1358     if ( $type->{type} eq "list" ) {
1359         $$value = [];
1360         for ( my $i = 0 ; ; $i++ ) {
1361             my $val;
1362             last if ( fieldInputParse($type->{child}, "${varName}_z_$i", \$val) );
1363             push(@$$value, $val);
1364         }
1365         $$value = undef if ( $type->{undefIfEmpty} && @$$value == 0 );
1366     } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
1367         my(@order, $childType);
1368         my $ret;
1369         $$value = {};
1370
1371         if ( defined($type->{order}) ) {
1372             @order = @{$type->{order}};
1373         } elsif ( defined($type->{child}) ) {
1374             @order = sort(keys(%{$type->{child}}));
1375         } else {
1376             @order = split(/\0/, $In{"vflds.$varName"});
1377         }
1378
1379         foreach my $fld ( @order ) {
1380             my $val;
1381             if ( defined($type->{child}) ) {
1382                 $childType = $type->{child}{$fld};
1383             } else {
1384                 $childType = $type->{childType};
1385             }
1386             $ret ||= fieldInputParse($childType, "${varName}_z_$fld", \$val);
1387             last if ( $ret );
1388             $$value->{$fld} = $val;
1389         }
1390         return $ret;
1391     } else {
1392         if ( $type->{type} eq "boolean" ) {
1393             $$value = 0 + $In{"v_z_$varName"};
1394         } elsif ( !exists($In{"v_z_$varName"}) ) {
1395             return 1;
1396         }
1397
1398         if ( $type->{type} eq "integer" ) {
1399             $$value = 0 + $In{"v_z_$varName"};
1400         } elsif ( $type->{type} eq "float" ) {
1401             $$value = 0 + $In{"v_z_$varName"};
1402         } elsif ( $type->{type} eq "shortlist" ) {
1403             $$value = [split(/[,\s]+/, $In{"v_z_$varName"})];
1404             if ( $type->{child} eq "float"
1405                     || $type->{child} eq "integer"
1406                     || $type->{child} eq "boolean" ) {
1407                 foreach ( @$$value ) {
1408                     $_ += 0;
1409                 }
1410             }
1411         } else {
1412             $$value = $In{"v_z_$varName"};
1413             $$value =~ s/\r\n/\n/g;
1414         }
1415         $$value = undef if ( $type->{undefIfEmpty} && $$value eq "" );
1416     }
1417     return 0;
1418 }
1419
1420 sub configDiffMesg
1421 {
1422     my($host, $oldConf, $newConf) = @_;
1423     my $mesg;
1424     my $conf;
1425
1426     if ( $host ne "" ) {
1427         $conf = "host $host config";
1428     } else {
1429         $conf = "main config";
1430     }
1431
1432     foreach my $p ( keys(%ConfigMeta) ) {
1433         if ( !exists($oldConf->{$p}) && !exists($newConf->{$p}) ) {
1434             next;
1435         } elsif ( exists($oldConf->{$p}) && !exists($newConf->{$p}) ) {
1436             $mesg .= eval("qq($Lang->{CfgEdit_Log_Delete_param})");
1437         } elsif ( !exists($oldConf->{$p}) && exists($newConf->{$p}) ) {
1438             my $dump = Data::Dumper->new([$newConf->{$p}]);
1439             $dump->Indent(0);
1440             $dump->Sortkeys(1);
1441             $dump->Terse(1);
1442             my $value = $dump->Dump;
1443             $value =~ s/\n/\\n/g;
1444             $value =~ s/\r/\\r/g;
1445             $mesg .= eval("qq($Lang->{CfgEdit_Log_Add_param_value})");
1446         } else {
1447             my $dump = Data::Dumper->new([$newConf->{$p}]);
1448             $dump->Indent(0);
1449             $dump->Sortkeys(1);
1450             $dump->Terse(1);
1451             my $valueNew = $dump->Dump;
1452
1453             my $v = $oldConf->{$p};
1454             if ( ref($newConf->{$p}) eq "ARRAY" && ref($v) eq "" ) {
1455                 $v = [$v];
1456             }
1457             $dump = Data::Dumper->new([$v]);
1458             $dump->Indent(0);
1459             $dump->Sortkeys(1);
1460             $dump->Terse(1);
1461             my $valueOld = $dump->Dump;
1462
1463             (my $valueNew2 = $valueNew) =~ s/['\n\r]//g;
1464             (my $valueOld2 = $valueOld) =~ s/['\n\r]//g;
1465             $valueNew =~ s/\n/\\n/g;
1466             $valueOld =~ s/\n/\\n/g;
1467             $valueNew =~ s/\r/\\r/g;
1468             $valueOld =~ s/\r/\\r/g;
1469             $mesg .= eval("qq($Lang->{CfgEdit_Log_Change_param_value})")
1470                                     if ( $valueOld2 ne $valueNew2 );
1471         }
1472     }
1473     return $mesg;
1474 }
1475
1476 sub hostsDiffMesg
1477 {
1478     my($hostsNew) = @_;
1479     my $hostsOld = $bpc->HostInfoRead();
1480     my($mesg, $hostChange);
1481
1482     foreach my $host ( keys(%$hostsOld) ) {
1483         if ( !defined($hostsNew->{$host}) ) {
1484             $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Delete})");
1485             $hostChange++;
1486             next;
1487         }
1488         foreach my $key ( keys(%{$hostsNew->{$host}}) ) {
1489             next if ( $hostsNew->{$host}{$key} eq $hostsOld->{$host}{$key} );
1490             my $valueOld = $hostsOld->{$host}{$key};
1491             my $valueNew = $hostsNew->{$host}{$key};
1492             $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Change})");
1493             $hostChange++;
1494         }
1495     }
1496
1497     foreach my $host ( keys(%$hostsNew) ) {
1498         next if ( defined($hostsOld->{$host}) );
1499         my $dump = Data::Dumper->new([$hostsNew->{$host}]);
1500         $dump->Indent(0);
1501         $dump->Sortkeys(1);
1502         $dump->Terse(1);
1503         my $value = $dump->Dump;
1504         $value =~ s/\n/\\n/g;
1505         $value =~ s/\r/\\r/g;
1506         $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Add})");
1507         $hostChange++;
1508     }
1509     return ($mesg, $hostChange);
1510 }
1511
1512 1;