- config and host editing pretty much done
[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 2.1.0beta2pl1, released 30 May 2004.
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             $content .= <<EOF;
1191 <input type="text" name="v_z_$varName" size="$size" maxlength="256" value="${EscHTML($varValue)}"$onChange>
1192 EOF
1193         } elsif ( $type->{type} eq "boolean" ) {
1194             # checkbox
1195             my $checked = "checked" if ( $varValue );
1196             $content .= <<EOF;
1197 <input type="checkbox" name="v_z_$varName" $checked value="1"$onChange>
1198 EOF
1199         } elsif ( $type->{type} eq "select" ) {
1200             $content .= <<EOF;
1201 <select name="v_z_$varName"$onChange>
1202 EOF
1203             foreach my $option ( @{$type->{values}} ) {
1204                 my $sel = " selected" if ( $varValue eq $option );
1205                 $content .= "<option$sel>$option</option>\n";
1206             }
1207             $content .= "</select>\n";
1208         } else {
1209             # multi-line text area - count number of lines
1210             my $rowCnt = $varValue =~ tr/\n//;
1211             $rowCnt = 1 if ( $rowCnt < 1 );
1212             $content .= <<EOF;
1213 <textarea name="v_z_$varName" cols="$size" rows="$rowCnt"$onChange>${EscHTML($varValue)}</textarea>
1214 EOF
1215         }
1216         $content .= "</td>\n";
1217     }
1218     return $content;
1219 }
1220
1221 sub errorCheck
1222 {
1223     my $errors = {};
1224
1225     foreach my $param ( keys(%ConfigMeta) ) {
1226         fieldErrorCheck($ConfigMeta{$param}, $param, $errors);
1227     }
1228     return $errors;
1229 }
1230
1231 sub fieldErrorCheck
1232 {
1233     my($type, $varName, $errors) = @_;
1234
1235     $type = { type => $type } if ( ref($type) ne "HASH" );
1236
1237     if ( $type->{type} eq "list" ) {
1238         for ( my $i = 0 ; ; $i++ ) {
1239             last if ( fieldErrorCheck($type->{child}, "${varName}_z_$i", $errors) );
1240         }
1241     } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
1242         my(@order, $childType);
1243         my $ret;
1244
1245         if ( defined($type->{order}) ) {
1246             @order = @{$type->{order}};
1247         } elsif ( defined($type->{child}) ) {
1248             @order = sort(keys(%{$type->{child}}));
1249         } else {
1250             @order = split(/\0/, $In{"vflds.$varName"});
1251         }
1252         foreach my $fld ( @order ) {
1253             if ( defined($type->{child}) ) {
1254                 $childType = $type->{child}{$fld};
1255             } else {
1256                 $childType = $type->{childType};
1257             }
1258             $ret ||= fieldErrorCheck($childType, "${varName}_z_$fld", $errors);
1259         }
1260         return $ret;
1261     } else {
1262         $In{"v_z_$varName"} = "0" if ( $type->{type} eq "boolean"
1263                                         && $In{"v_z_$varName"} eq "" );
1264
1265         return 1 if ( !exists($In{"v_z_$varName"}) );
1266
1267         (my $var = $varName) =~ s/_z_/./g;
1268
1269         if ( $type->{type} eq "integer"
1270                 || $type->{type} eq "boolean" ) {
1271             if ( $In{"v_z_$varName"} !~ /^-?\d+\s*$/s
1272                             && $In{"v_z_$varName"} ne "" ) {
1273                 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_an_integer}}");
1274             }
1275         } elsif ( $type->{type} eq "float" ) {
1276             if ( $In{"v_z_$varName"} !~ /^-?\d*(\.\d*)?\s*$/s
1277                             && $In{"v_z_$varName"} ne "" ) {
1278                 $errors->{$varName}
1279                         = eval("qq{$Lang->{CfgEdit_Error__must_be_real_valued_number}}");
1280             }
1281         } elsif ( $type->{type} eq "shortlist" ) {
1282             my @vals = split(/[,\s]+/, $In{"v_z_$varName"});
1283             for ( my $i = 0 ; $i < @vals ; $i++ ) {
1284                 if ( $type->{child} eq "integer"
1285                         && $vals[$i] !~ /^-?\d+\s*$/s
1286                         && $vals[$i] ne "" ) {
1287                     my $k = $i + 1;
1288                     $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__entry__must_be_an_integer}}");
1289                 } elsif ( $type->{child} eq "float"
1290                         && $vals[$i] !~ /^-?\d*(\.\d*)?\s*$/s
1291                         && $vals[$i] ne "" ) {
1292                     my $k = $i + 1;
1293                     $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__entry__must_be_real_valued_number}}");
1294                 }
1295             }
1296         } elsif ( $type->{type} eq "select" ) {
1297             my $match = 0;
1298             foreach my $option ( @{$type->{values}} ) {
1299                 if ( $In{"v_z_$varName"} eq $option ) {
1300                     $match = 1;
1301                     last;
1302                 }
1303             }
1304             $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_valid_option}}")
1305                             if ( !$match );
1306         } elsif ( $type->{type} eq "execPath" ) {
1307             if ( $In{"v_z_$varName"} ne "" && !-x $In{"v_z_$varName"} ) {
1308                 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_executable_program}}");
1309             }
1310         } else {
1311             #
1312             # $type->{type} eq "string": no error checking
1313             #
1314         }
1315     }
1316     return 0;
1317 }
1318
1319 sub inputParse
1320 {
1321     my($bpc, $userHost) = @_;
1322     my $conf     = {};
1323     my $override = {};
1324
1325     foreach my $param ( keys(%ConfigMeta) ) {
1326         my $value;
1327         next if ( $userHost
1328                       && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
1329                          || (!$PrivAdmin
1330                             && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
1331         fieldInputParse($ConfigMeta{$param}, $param, \$value);
1332         $conf->{$param}     = $value;
1333         $override->{$param} = $In{"override_$param"};
1334     }
1335     return ($conf, $override);
1336 }
1337
1338 sub fieldInputParse
1339 {
1340     my($type, $varName, $value) = @_;
1341
1342     $type = { type => $type } if ( ref($type) ne "HASH" );
1343
1344     if ( $type->{type} eq "list" ) {
1345         $$value = [];
1346         for ( my $i = 0 ; ; $i++ ) {
1347             my $val;
1348             last if ( fieldInputParse($type->{child}, "${varName}_z_$i", \$val) );
1349             push(@$$value, $val);
1350         }
1351         $$value = undef if ( $type->{undefIfEmpty} && @$$value == 0 );
1352     } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
1353         my(@order, $childType);
1354         my $ret;
1355         $$value = {};
1356
1357         if ( defined($type->{order}) ) {
1358             @order = @{$type->{order}};
1359         } elsif ( defined($type->{child}) ) {
1360             @order = sort(keys(%{$type->{child}}));
1361         } else {
1362             @order = split(/\0/, $In{"vflds.$varName"});
1363         }
1364
1365         foreach my $fld ( @order ) {
1366             my $val;
1367             if ( defined($type->{child}) ) {
1368                 $childType = $type->{child}{$fld};
1369             } else {
1370                 $childType = $type->{childType};
1371             }
1372             $ret ||= fieldInputParse($childType, "${varName}_z_$fld", \$val);
1373             last if ( $ret );
1374             $$value->{$fld} = $val;
1375         }
1376         return $ret;
1377     } else {
1378         if ( $type->{type} eq "boolean" ) {
1379             $$value = 0 + $In{"v_z_$varName"};
1380         } elsif ( !exists($In{"v_z_$varName"}) ) {
1381             return 1;
1382         }
1383
1384         if ( $type->{type} eq "integer" ) {
1385             $$value = 0 + $In{"v_z_$varName"};
1386         } elsif ( $type->{type} eq "float" ) {
1387             $$value = 0 + $In{"v_z_$varName"};
1388         } elsif ( $type->{type} eq "shortlist" ) {
1389             $$value = [split(/[,\s]+/, $In{"v_z_$varName"})];
1390             if ( $type->{child} eq "float"
1391                     || $type->{child} eq "integer"
1392                     || $type->{child} eq "boolean" ) {
1393                 foreach ( @$$value ) {
1394                     $_ += 0;
1395                 }
1396             }
1397         } else {
1398             $$value = $In{"v_z_$varName"};
1399             $$value =~ s/\r\n/\n/g;
1400         }
1401         $$value = undef if ( $type->{undefIfEmpty} && $$value eq "" );
1402     }
1403     return 0;
1404 }
1405
1406 sub configDiffMesg
1407 {
1408     my($host, $oldConf, $newConf) = @_;
1409     my $mesg;
1410     my $conf;
1411
1412     if ( $host ne "" ) {
1413         $conf = "host $host config";
1414     } else {
1415         $conf = "main config";
1416     }
1417
1418     foreach my $p ( keys(%ConfigMeta) ) {
1419         if ( !exists($oldConf->{$p}) && !exists($newConf->{$p}) ) {
1420             next;
1421         } elsif ( exists($oldConf->{$p}) && !exists($newConf->{$p}) ) {
1422             $mesg .= eval("qq($Lang->{CfgEdit_Log_Delete_param})");
1423         } elsif ( !exists($oldConf->{$p}) && exists($newConf->{$p}) ) {
1424             my $dump = Data::Dumper->new([$newConf->{$p}]);
1425             $dump->Indent(0);
1426             $dump->Sortkeys(1);
1427             $dump->Terse(1);
1428             my $value = $dump->Dump;
1429             $value =~ s/\n/\\n/g;
1430             $value =~ s/\r/\\r/g;
1431             $mesg .= eval("qq($Lang->{CfgEdit_Log_Add_param_value})");
1432         } else {
1433             my $dump = Data::Dumper->new([$newConf->{$p}]);
1434             $dump->Indent(0);
1435             $dump->Sortkeys(1);
1436             $dump->Terse(1);
1437             my $valueNew = $dump->Dump;
1438
1439             my $v = $oldConf->{$p};
1440             if ( ref($newConf->{$p}) eq "ARRAY" && ref($v) eq "" ) {
1441                 $v = [$v];
1442             }
1443             $dump = Data::Dumper->new([$v]);
1444             $dump->Indent(0);
1445             $dump->Sortkeys(1);
1446             $dump->Terse(1);
1447             my $valueOld = $dump->Dump;
1448
1449             (my $valueNew2 = $valueNew) =~ s/['\n\r]//g;
1450             (my $valueOld2 = $valueOld) =~ s/['\n\r]//g;
1451             $valueNew =~ s/\n/\\n/g;
1452             $valueOld =~ s/\n/\\n/g;
1453             $valueNew =~ s/\r/\\r/g;
1454             $valueOld =~ s/\r/\\r/g;
1455             $mesg .= eval("qq($Lang->{CfgEdit_Log_Change_param_value})")
1456                                     if ( $valueOld2 ne $valueNew2 );
1457         }
1458     }
1459     return $mesg;
1460 }
1461
1462 sub hostsDiffMesg
1463 {
1464     my($hostsNew) = @_;
1465     my $hostsOld = $bpc->HostInfoRead();
1466     my($mesg, $hostChange);
1467
1468     foreach my $host ( keys(%$hostsOld) ) {
1469         if ( !defined($hostsNew->{$host}) ) {
1470             $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Delete})");
1471             $hostChange++;
1472             next;
1473         }
1474         foreach my $key ( keys(%{$hostsNew->{$host}}) ) {
1475             next if ( $hostsNew->{$host}{$key} eq $hostsOld->{$host}{$key} );
1476             my $valueOld = $hostsOld->{$host}{$key};
1477             my $valueNew = $hostsNew->{$host}{$key};
1478             $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Change})");
1479             $hostChange++;
1480         }
1481     }
1482
1483     foreach my $host ( keys(%$hostsNew) ) {
1484         next if ( defined($hostsOld->{$host}) );
1485         my $dump = Data::Dumper->new([$hostsNew->{$host}]);
1486         $dump->Indent(0);
1487         $dump->Sortkeys(1);
1488         $dump->Terse(1);
1489         my $value = $dump->Dump;
1490         $value =~ s/\n/\\n/g;
1491         $value =~ s/\r/\\r/g;
1492         $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Add})");
1493         $hostChange++;
1494     }
1495     return ($mesg, $hostChange);
1496 }
1497
1498 1;