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