::namespace eval ::qw::ntp { /* { ntp - Network Time Protocol */ } } variable ::qw::ntp::service_list ""; ::set ::qw::ntp::service_list { time4.nrc.ca time.chu.nrc.ca ntp2a.mcc.ac.uk ptbtime1.ptb.de salmon.maths.tcd.ie time.ien.it time.chu.nrc.ca ntp.nblug.org openbrick.kicks-ass.net ntp0.cornell.edu ntp.glorb.com } ::proc ::qw::ntp::ntp_clock_get {args} { ::if {!$::qw::control(ntp_system_clock_check_is_enabled)} { ::return ""; } /* { Returns the ntp clock in unix epoch or empty if can't get or not enabled. */ } ::qw::s_args_marshal; ::package require time; ::set NTPClock ""; ::foreach Hostname $::qw::ntp::service_list { ::set Token [::time::gettime $Hostname]; ::if {[::time::status $Token] ne "ok"} { /* { When a computer is not connected to the net we get the error: "couldn't open socket: invalid argument". We get it instantly and the turnaround for attempting multiple urls is negligable. So when the computer is not connected we in effect just let the user into the database. This means our checking is not fool-proof. */ } ::time::cleanup $Token; ::continue; } ::set NTPClock [::time::unixtime $Token]; ::time::cleanup $Token; ::break; } ::if {$NTPClock eq ""} { /* { Couldn't get the ntp time. Probably not connected. */ } ::qw::warning 314120110929080354 "Could not retrieve NTP (Network Time Protocol) time."; } ::return $NTPClock; } ::proc ::qw::ntp::system_clock_is_ok {args} { ::qw::s_args_marshal; ::set NTPClock [ntp_clock_get $s_args]; ::if {$NTPClock eq ""} { /* { Couldn't get the ntp time. Probably not connected. We're out of here and we let the user into the database. */ } ::return 1; } ::set Tolerance [::sargs::integer_get $s_args .tolerance_seconds]; ::if {$Tolerance==0} { /* { If not called with a tolerance, use the default (20 minutes). */ } ::set Tolerance $::qw::control(ntp_system_clock_check_tolerance_seconds) } ::set SystemClock [::clock seconds]; ::set Difference [::expr {int(abs($NTPClock-$SystemClock))}]; ::if {$Difference<$Tolerance} { /* { The system clock is close enough to the network time protocol clock. We're out of here. */ } ::return 1; } ::return 0; } ::proc ::qw::ntp::system_clock_check {args} { ::qw::s_args_marshal; ::set NTPClock [ntp_clock_get $s_args]; ::if {$NTPClock eq ""} { /* { Couldn't get the ntp time. Probably not connected. We're out of here and we let the user into the database. */ } ::return; } ::set Tolerance [::sargs::integer_get $s_args .tolerance_seconds]; ::if {$Tolerance==0} { /* { If not called with a tolerance, use the default (20 minutes). */ } ::set Tolerance $::qw::control(ntp_system_clock_check_tolerance_seconds) } ::set SystemClock [::clock seconds]; ::set Difference [::expr {int(abs($NTPClock-$SystemClock))}]; ::if {$Difference<$Tolerance} { /* { The system clock is close enough to the network time protocol clock. We're out of here. */ } ::return; } /* { ::qw::dialog::confirm and ::qw::dialog::error take their s_args that are passed on to the help page at different levels - a serious problem that should be resolved in nv3. Here we re-use the help page for both an error and a confirm. We put the s_args in the inside level for the confirm. They are also at the dialog level for the error. */ } ::set Hostname [::string tolower [::info hostname]]; ::set HelpPage [::subst -nocommands { .id 314120111002122201 .clock_seconds $SystemClock .ntp_seconds $NTPClock .difference_seconds $Difference .hostname {$Hostname} .body { [p { The computer system clock is out by more than 20 minutes. Maybe it was changed by a user, or perhaps there was some kind of computer failure, or it could be a deliberate attempt to manipulate the system clock for unknown purposes. NewViews requires that the computer system clock be kept reasonably accurate, say within 20 minutes of the [qw_quoted real] time. }] [p { To detect this discrepancy, NewViews checked with an NTP ([bold "Network Time Protocol"]) clock server and compared it to the system clock of the computer identified as [qw_field_value [qw_s_args .hostname]]. }] [p { The [qw_s_args .hostname] computer system clock was: [qw_field_value [::clock format [qw_s_args .clock_seconds] -format {%I:%M:%S %p %a %b %d %Y}]]. }] [p { The NTP (correct) time was: [qw_field_value [::clock format [qw_s_args .ntp_seconds] -format {%I:%M:%S %p %a %b %d %Y}]]. }] [p { The difference was [::qw::clockutil::runtime_format_hms .seconds [qw_s_args .difference_seconds]] (hh:mm:ss). }] [h2 {Why does the system clock need to be reasonably accurate?}] [p { First note that we only require the system clock to be within 20 minutes of the correct time, which is generally considered to be reasonable. }] [p { There are several places in the database where time-stamped records are kept in strict chronological order. Session and audit records are two examples. Any attempt to manipulate the system clock by setting it to an inaccurate value could compromise the chronological order. }] [p { Suppose you inadvertently change the system clock, moving it ahead by a year and then you perform regular accounting activities which also creates more time-stamped audit records. These audit records will be time-stamped a year in the future because the system clock is used for the time-stamps. }] [p { You cannot change the system clock back to the current (correct) time and then simply resume accounting activity because the next audit record's time-stamp would not be in chronological order. You would have these alternatives: }] [ol { [li { [p { Send the database to NewViews customer support for repair. }] [p { Customer support can repair the database but it takes time and effort and there is a charge. }] }] [li { [p { Wait a year before resuming accounting activity. }] [p { This might be viable if you only changed the system clock by a day or so in the future but otherwise it's generally out of the question. }] }] [li { [p { Set the system clock in the future (not recommended). }] [p { If you set your system clock in the future, i.e. to the future date that originally caused the problem, you can actually open the database. We are not recommending that you do this in order to continue accounting because that will just create additional session and audit records that are time-stamped in the future, making the situation even worse. However, if you need read access to the database before it can be repaired, make a temporary copy of the database, set the system date in the future, and access the temporary database copy. After you close the database remember to immediately reset the system date to the present and delete the temporary copy of the database. DO NOT OPEN any other databases while the system clock is set in the future or you will corrupt them as well. }] }] }] [p { None of the alternatives above are very attractive and keeping the system clock accurate to within 20 minutes seems like a reasonable restriction. Before this check was put in place (version 2.21) it actually did happen that users changed their system clocks and repairs were necessary. This didn't happen often but it wasn't all that rare either. The time check and corresponding error are designed to avoid the rather painful solutions above altogether. }] } }]; ::if {$::qw::control(serial_is_internal)} { ::if {[::sargs::get $s_args .caller] eq "database_open"} { ::if {[::sargs::get $s_args .tcp.command] ne "call"} { /* { Customer support - put up a confirm dialog. If customer support is opening a local database then we put up a confirm dialog instead of throwing an error. We let the customer support user enter the database (it better be a copy of the original). For regular users they are prevented from opening the database until they correct the system clock. Also, we don't let the customer support open a remote database because the prompt would appear on the server whereas an error message crosses the net. */ } ::sargs::var::set HelpPage .title "System Clock Problem"; ::set Text "*********** WARNING: SYSTEM CLOCK PROBLEM ***********"; ::append Text "\n"; ::append Text "\nComputer system clock: [::clock format $SystemClock -format {%I:%M:%S %p %a %b %d %Y}]"; ::append Text "\nNetwork Protocol Time: [::clock format $NTPClock -format {%I:%M:%S %p %a %b %d %Y}]"; ::append Text "\nDifference: [::qw::clockutil::runtime_format_hms .seconds $Difference] (hh:mm:ss)"; ::append Text "\n"; ::append Text "\nA user would normally get an error here but because you are customer support"; ::append Text "\nyou are being granted access to the database. Keep in mind that opening the "; ::append Text "\ndatabase will cause serious time-stamp related problems such as time-stamping"; ::append Text "\nsession and audit records in the future. So only enter the database if this is a"; ::append Text "\ncopy of the original and it's ok to damage it."; ::append Text "\n"; ::append Text "\nClick for the same help that the user would see on their error message."; ::set Result [::qw::dialog::confirm \ .title "*** WARNING: SYSTEM CLOCK PROBLEM ***" \ .text $Text \ /button/ok.text "Enter Database Anyway" \ .clock_seconds $SystemClock \ .ntp_seconds $NTPClock \ .difference_seconds $Difference \ .help_page $HelpPage \ ]; ::if {!$Result} { ::qw::throw \ .text "Customer support user aborted database open." \ .priority ignore \ ; } ::return; } } } ::qw::throw \ .text "The computer system clock is inaccurate: [::clock format $NTPClock -format {%I:%M:%S %p %a %b %d %Y}]" \ .tags {error} \ .clock_seconds $SystemClock \ .ntp_seconds $NTPClock \ .difference_seconds $Difference \ .help_page $HelpPage \ ; }