database.pm 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283
  1. package eyelib::database;
  2. #
  3. # Copyright (C) Roman Dmitriev, rnd@rajven.ru
  4. #
  5. use utf8;
  6. use open ":encoding(utf8)";
  7. use strict;
  8. use English;
  9. use FindBin '$Bin';
  10. use lib "/opt/Eye/scripts";
  11. use base 'Exporter';
  12. use vars qw(@EXPORT @ISA);
  13. use eyelib::config;
  14. use eyelib::main;
  15. use Net::Patricia;
  16. use eyelib::net_utils;
  17. use Data::Dumper;
  18. use DateTime;
  19. use POSIX qw(mktime ctime strftime);
  20. use File::Temp qw(tempfile);
  21. use DBI;
  22. use DBD::Pg qw(:pg_types);
  23. use Text::CSV;
  24. our @ISA = qw(Exporter);
  25. our @EXPORT = qw(
  26. StrToIp
  27. IpToStr
  28. batch_db_sql_cached
  29. batch_db_sql_csv
  30. reconnect_db
  31. write_db_log
  32. db_log_debug
  33. db_log_error
  34. db_log_info
  35. db_log_verbose
  36. db_log_warning
  37. init_db
  38. do_sql
  39. _execute_param
  40. do_sql_param
  41. get_option_safe
  42. get_count_records
  43. get_id_record
  44. get_records_sql
  45. get_record_sql
  46. get_diff_rec
  47. update_record
  48. insert_record
  49. delete_record
  50. get_option
  51. init_option
  52. is_system_ou
  53. Set_Variable
  54. Get_Variable
  55. Del_Variable
  56. clean_variables
  57. $add_rules
  58. $L_WARNING
  59. $L_INFO
  60. $L_DEBUG
  61. $L_ERROR
  62. $L_VERBOSE
  63. );
  64. BEGIN
  65. {
  66. #---------------------------------------------------------------------------------------------------------------
  67. our $add_rules;
  68. our $L_ERROR = 0;
  69. our $L_WARNING = 1;
  70. our $L_INFO = 2;
  71. our $L_VERBOSE = 3;
  72. our $L_DEBUG = 255;
  73. our %acl_fields = (
  74. 'ip' => '1',
  75. 'ip_int' => '1',
  76. 'enabled'=>'1',
  77. 'dhcp'=>'1',
  78. 'filter_group_id'=>'1',
  79. 'deleted'=>'1',
  80. 'dhcp_acl'=>'1',
  81. 'queue_id'=>'1',
  82. 'mac'=>'1',
  83. 'blocked'=>'1'
  84. );
  85. our %dhcp_fields = (
  86. 'ip' => '1',
  87. 'dhcp_acl'=>'1',
  88. 'dhcp_option_set'=>'1',
  89. 'dhcp'=>'1',
  90. 'deleted'=>'1',
  91. 'mac'=>'1',
  92. );
  93. our %dns_fields = (
  94. 'ip' => '1',
  95. 'dns_name'=>'1',
  96. 'dns_ptr_only'=>'1',
  97. 'alias'=>'1',
  98. );
  99. #---------------------------------------------------------------------------------------------------------------
  100. sub StrToIp {
  101. return unpack('N',pack('C4',split(/\./,$_[0])));
  102. }
  103. #---------------------------------------------------------------------------------------------------------------
  104. sub IpToStr {
  105. my $nIP = shift;
  106. my $res = (($nIP>>24) & 255) .".". (($nIP>>16) & 255) .".". (($nIP>>8) & 255) .".". ($nIP & 255);
  107. return $res;
  108. }
  109. #---------------------------------------------------------------------------------------------------------------
  110. sub batch_db_sql_cached {
  111. my ($sql, $data) = @_;
  112. my $db=init_db();
  113. eval {
  114. my $sth = $db->prepare_cached($sql) or die "Unable to prepare SQL: " . $db->errstr;
  115. for my $params (@$data) {
  116. next unless @$params;
  117. $sth->execute(@$params) or die "Unable to execute with params [" . join(',', @$params) . "]: " . $sth->errstr;
  118. }
  119. $db->commit() if (!$db->{AutoCommit});
  120. 1;
  121. } or do {
  122. my $err = $@ || 'Unknown error';
  123. eval { $db->rollback() };
  124. $db->disconnect();
  125. die "batch_db_sql_cached failed: $err";
  126. };
  127. $db->disconnect();
  128. return 1;
  129. }
  130. #---------------------------------------------------------------------------------------------------------------
  131. sub batch_db_sql_csv {
  132. my ($table, $data) = @_;
  133. return 0 unless @$data;
  134. # Первая строка — заголовки (имена столбцов)
  135. my $header_row = shift @$data;
  136. unless ($header_row && ref($header_row) eq 'ARRAY' && @$header_row) {
  137. log_error("First row must be column names (array reference)");
  138. return 0;
  139. }
  140. my @columns = @$header_row;
  141. # Теперь @$data содержит только строки данных
  142. my $data_rows = $data;
  143. # Если нет данных — только заголовок
  144. unless (@$data_rows) {
  145. log_debug("No data rows to insert, only header");
  146. return 1;
  147. }
  148. my $db = init_db();
  149. if (get_db_type($db) eq 'mysql') {
  150. # --- MySQL: попытка LOAD DATA, fallback на INSERT ---
  151. log_debug("Using LOAD DATA LOCAL INFILE for MySQL");
  152. my $fh = File::Temp->new(UNLINK => 1);
  153. my $fname = $fh->filename;
  154. binmode($fh, ':utf8');
  155. my $csv = Text::CSV->new({
  156. binary => 1,
  157. quote_char => '"',
  158. escape_char => '"',
  159. sep_char => ',',
  160. eol => "\r\n",
  161. always_quote => 1,
  162. }) or do {
  163. my $err = "Cannot create Text::CSV: " . Text::CSV->error_diag();
  164. log_error($err);
  165. $db->disconnect();
  166. return 0;
  167. };
  168. # Пишем заголовок
  169. $csv->print($fh, \@columns);
  170. # Пишем данные
  171. for my $row (@$data_rows) {
  172. next unless $row && ref($row) eq 'ARRAY' && @$row == @columns;
  173. my @vals = map { defined($_) ? $_ : 'NULL' } @$row;
  174. $csv->print($fh, \@vals);
  175. }
  176. close $fh;
  177. my $col_list = join(', ', map { $db->quote_identifier($_) } @columns);
  178. my $query = qq{LOAD DATA LOCAL INFILE '$fname' INTO TABLE $table FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\r\n' IGNORE 1 LINES ($col_list)};
  179. my $load_ok = eval { $db->do($query); 1 };
  180. if (!$load_ok) {
  181. my $err = "MySQL LOAD DATA failed: $@";
  182. log_error($err);
  183. log_debug("Falling back to bulk INSERT for MySQL");
  184. goto FALLBACK_INSERT_MYSQL;
  185. }
  186. $db->disconnect();
  187. return 1;
  188. # ========================
  189. # Fallback для MySQL
  190. # ========================
  191. FALLBACK_INSERT_MYSQL:
  192. {
  193. my $quoted_cols = join(', ', map { $db->quote_identifier($_) } @columns);
  194. my $placeholders = join(',', ('?') x @columns);
  195. my $sql = "INSERT INTO $table ($quoted_cols) VALUES ($placeholders)";
  196. my $sth = $db->prepare($sql);
  197. my $success = eval {
  198. for my $row (@$data_rows) {
  199. next unless $row && ref($row) eq 'ARRAY' && @$row == @columns;
  200. my @vals = map { defined($_) ? $_ : undef } @$row;
  201. $sth->execute(@vals);
  202. }
  203. 1;
  204. };
  205. if (!$success) {
  206. my $err = "MySQL bulk INSERT failed: $@";
  207. log_error($err);
  208. $db->disconnect();
  209. return 0;
  210. }
  211. }
  212. } elsif (get_db_type($db) eq 'pg') {
  213. if (!$db->can('pg_putcopydata') || !$db->can('pg_putcopyend')) {
  214. log_debug("pg_putcopydata/pg_putcopyend not available — falling back to bulk INSERT");
  215. goto FALLBACK_INSERT_PG;
  216. }
  217. my $col_list = join(', ', map { $db->quote_identifier($_) } @columns);
  218. my $copy_sql = "COPY $table ($col_list) FROM STDIN WITH (FORMAT CSV, HEADER true)";
  219. my $use_header_as_data;
  220. my $start_ok = eval { $db->do($copy_sql); 1 };
  221. if (!$start_ok) {
  222. log_debug("COPY with HEADER failed: $@ — trying without HEADER");
  223. $copy_sql = "COPY $table ($col_list) FROM STDIN WITH (FORMAT CSV)";
  224. $start_ok = eval { $db->do($copy_sql); 1 };
  225. if (!$start_ok) {
  226. log_debug("COPY failed entirely: $@ — falling back to bulk INSERT");
  227. goto FALLBACK_INSERT_PG;
  228. }
  229. $use_header_as_data = 1;
  230. } else {
  231. $use_header_as_data = 0;
  232. }
  233. log_debug("Using CSV COPY for PostgreSQL");
  234. my $csv = Text::CSV->new({
  235. binary => 1,
  236. quote_char => '"',
  237. escape_char => '"',
  238. sep_char => ',',
  239. eol => "\n",
  240. always_quote => 1,
  241. }) or do {
  242. my $err = "Cannot create Text::CSV: " . Text::CSV->error_diag();
  243. log_error($err);
  244. eval { $db->pg_putcopyend(); };
  245. $db->disconnect();
  246. return 0;
  247. };
  248. my $success = eval {
  249. if ($use_header_as_data) {
  250. $csv->combine(@columns);
  251. $db->pg_putcopydata($csv->string);
  252. }
  253. for my $row (@$data_rows) {
  254. next unless $row && ref($row) eq 'ARRAY' && @$row == @columns;
  255. my @vals = map { defined($_) ? $_ : undef } @$row;
  256. $csv->combine(@vals);
  257. $db->pg_putcopydata($csv->string);
  258. }
  259. $db->pg_putcopyend();
  260. 1;
  261. };
  262. if ($success) {
  263. $db->disconnect();
  264. return 1;
  265. } else {
  266. my $err = "CSV COPY failed: $@";
  267. log_error($err);
  268. eval { $db->pg_putcopyend(); };
  269. goto FALLBACK_INSERT_PG;
  270. }
  271. # ========================
  272. # Fallback для PostgreSQL
  273. # ========================
  274. FALLBACK_INSERT_PG:
  275. {
  276. my $quoted_cols = join(', ', map { $db->quote_identifier($_) } @columns);
  277. my $placeholders = join(',', ('?') x @columns);
  278. my $sql = "INSERT INTO $table ($quoted_cols) VALUES ($placeholders)";
  279. my $sth = $db->prepare($sql);
  280. my $success = eval {
  281. for my $row (@$data_rows) {
  282. next unless $row && ref($row) eq 'ARRAY' && @$row == @columns;
  283. my @vals = map { defined($_) ? $_ : undef } @$row;
  284. $sth->execute(@vals);
  285. }
  286. 1;
  287. };
  288. if (!$success) {
  289. my $err = "PostgreSQL bulk INSERT failed: $@";
  290. log_error($err);
  291. $db->disconnect();
  292. return 0;
  293. }
  294. }
  295. } else {
  296. my $err = "Unsupported DBTYPE: ". get_db_type($db);
  297. log_error($err);
  298. $db->disconnect();
  299. return 0;
  300. }
  301. $db->disconnect();
  302. return 1;
  303. }
  304. #---------------------------------------------------------------------------------------------------------------
  305. sub reconnect_db {
  306. my $db_ref = shift;
  307. # Если соединение активно, ничего не делаем
  308. if ($$db_ref && $$db_ref->ping) {
  309. return 1;
  310. }
  311. # Переподключаемся
  312. eval {
  313. # Закрываем старое соединение если есть
  314. if ($$db_ref) {
  315. $$db_ref->disconnect;
  316. $$db_ref = undef;
  317. }
  318. # Создаем новое соединение
  319. $$db_ref = init_db();
  320. # Проверяем что соединение установлено
  321. unless ($$db_ref && $$db_ref->ping) {
  322. die "Failed to establish database connection";
  323. }
  324. 1; # возвращаем истину при успехе
  325. } or do {
  326. my $error = $@ || 'Unknown error';
  327. warn "Database reconnection failed: $error";
  328. $$db_ref = undef;
  329. return 0;
  330. };
  331. return 1;
  332. }
  333. #---------------------------------------------------------------------------------------------------------------
  334. sub write_db_log {
  335. my $db=shift;
  336. my $msg=shift;
  337. my $level = shift || $L_VERBOSE;
  338. my $auth_id = shift || 0;
  339. return if (!$db);
  340. return if (!$msg);
  341. $msg=~s/[\'\"]//g;
  342. my $db_log = 0;
  343. # Переподключение
  344. unless (reconnect_db(\$db)) {
  345. log_error("No database connection available");
  346. $db_log = 0;
  347. }
  348. if ($level eq $L_ERROR and $log_level >= $L_ERROR) { log_error($msg); $db_log = 1; }
  349. if ($level eq $L_WARNING and $log_level >= $L_WARNING) { log_warning($msg); $db_log = 1; }
  350. if ($level eq $L_INFO and $log_level >= $L_INFO) { log_info($msg); $db_log = 1; }
  351. if ($level eq $L_VERBOSE and $log_level >= $L_VERBOSE) { log_verbose($msg); $db_log = 1; }
  352. if ($level eq $L_DEBUG and $log_level >= $L_DEBUG) { log_debug($msg); return; }
  353. if ($db_log) {
  354. #my $new_id = do_sql($dbh, 'INSERT INTO user_list (login) VALUES (?)', 'Ivan');
  355. do_sql($db,'INSERT INTO worklog(customer,message,level,auth_id,ip) VALUES( ?, ?, ?, ?, ?)',$MY_NAME,$msg,$level,$auth_id,$config_ref{self_ip});
  356. }
  357. }
  358. #---------------------------------------------------------------------------------------------------------------
  359. sub db_log_debug {
  360. my $db = shift;
  361. my $msg = shift;
  362. my $id = shift;
  363. if ($debug) { log_debug($msg); }
  364. }
  365. #---------------------------------------------------------------------------------------------------------------
  366. sub db_log_error {
  367. my $db = shift;
  368. my $msg = shift;
  369. if ($log_level >= $L_ERROR) {
  370. sendEmail("ERROR! ".get_first_line($msg),$msg,1);
  371. write_db_log($db,$msg,$L_ERROR);
  372. }
  373. }
  374. #---------------------------------------------------------------------------------------------------------------
  375. sub db_log_info {
  376. my $db = shift;
  377. my $msg = shift;
  378. my $id = shift;
  379. if ($log_level >= $L_INFO) { write_db_log($db,$msg,$L_INFO,$id); }
  380. }
  381. #---------------------------------------------------------------------------------------------------------------
  382. sub db_log_verbose {
  383. my $db = shift;
  384. my $msg = shift;
  385. my $id = shift;
  386. if ($log_level >= $L_VERBOSE) { write_db_log($db,$msg,$L_VERBOSE,$id); }
  387. }
  388. #---------------------------------------------------------------------------------------------------------------
  389. sub db_log_warning {
  390. my $db = shift;
  391. my $msg = shift;
  392. my $id = shift;
  393. if ($log_level >= $L_WARNING) { write_db_log($db,$msg,$L_WARNING,$id); }
  394. }
  395. #---------------------------------------------------------------------------------------------------------------
  396. sub init_db {
  397. # Create new database handle. If we can't connect, die()
  398. my $db;
  399. if ($config_ref{DBTYPE} eq 'mysql') {
  400. $db = DBI->connect("dbi:mysql:database=$DBNAME;host=$DBHOST;mysql_local_infile=1","$DBUSER","$DBPASS",
  401. { RaiseError => 0, AutoCommit => 1, mysql_enable_utf8 => 1 });
  402. if ( !defined $db ) { die "Cannot connect to MySQL server: $DBI::errstr\n"; }
  403. $db->do('SET NAMES utf8mb4');
  404. } else {
  405. $db = DBI->connect("dbi:Pg:dbname=$DBNAME;host=$DBHOST","$DBUSER","$DBPASS",
  406. { RaiseError => 0, AutoCommit => 1, pg_enable_utf8 => 1, pg_server_prepare => 0 });
  407. if ( !defined $db ) { die "Cannot connect to PostgreSQL server: $DBI::errstr\n"; }
  408. }
  409. return $db;
  410. }
  411. #---------------------------------------------------------------------------------------------------------------
  412. # Обновленная функция get_option с параметризованными запросами
  413. sub get_option {
  414. my $db = shift;
  415. my $option_id = shift;
  416. return if (!$option_id);
  417. return if (!$db);
  418. my $sql = q{
  419. SELECT
  420. COALESCE(c.value, co.default_value) AS value,
  421. co.option_type
  422. FROM config_options co
  423. LEFT JOIN config c ON c.option_id = co.id
  424. WHERE co.id = ?
  425. };
  426. my $record = get_record_sql($db, $sql, $option_id);
  427. unless ($record) {
  428. log_error("Option ID $option_id not found in config_options table");
  429. return;
  430. }
  431. return $record->{value};
  432. }
  433. #---------------------------------------------------------------------------------------------------------------
  434. sub do_sql {
  435. my ($db, $sql, @bind_values) = @_;
  436. return 0 unless $db && $sql;
  437. my $mode;
  438. if ($sql =~ /^\s*insert\b/i) {
  439. $mode = 'id';
  440. } elsif ($sql =~ /^\s*select\b/i) {
  441. $mode = 'arrayref';
  442. } else {
  443. $mode = 'execute';
  444. }
  445. my $result = _execute_param($db, $sql, \@bind_values, { mode => $mode });
  446. # Обработка ошибок: если _execute_param вернул undef/ложь — возвращаем 0
  447. unless (defined $result) {
  448. return 0;
  449. }
  450. if ($mode eq 'id') {
  451. return $result; # уже число (или 0)
  452. } elsif ($mode eq 'arrayref') {
  453. return ref($result) eq 'ARRAY' ? $result : 0; # на случай ошибки
  454. } else {
  455. return $result ? 1 : 0;
  456. }
  457. }
  458. #---------------------------------------------------------------------------------------------------------------
  459. # Внутренняя функция для выполнения параметризованных запросов
  460. sub _execute_param {
  461. my ($db, $sql, $params, $options) = @_;
  462. return unless $db && $sql;
  463. my $mode = $options->{mode} || 'execute';
  464. # автоматическая поддержка RETURNING для PostgreSQL ---
  465. my $was_modified = 0;
  466. my $original_sql = $sql;
  467. if ($mode eq 'id' && $sql =~ /^\s*INSERT\b/i) {
  468. if (get_db_type($db) eq 'pg') {
  469. # Добавляем RETURNING id, если его ещё нет
  470. unless ($sql =~ /\bRETURNING\b/i) {
  471. $sql .= ' RETURNING id';
  472. $was_modified = 1;
  473. # Теперь нам нужно получить скаляр, а не использовать lastval()
  474. $mode = 'scalar'; # временно меняем режим
  475. }
  476. }
  477. }
  478. # Логируем не-SELECT (но уже с возможным RETURNING)
  479. unless ($original_sql =~ /^\s*SELECT/i) {
  480. log_debug($original_sql . ($params ? ' | params: [' . join(', ', map { defined $_ ? $_ : 'undef' } @$params) . ']' : ''));
  481. }
  482. unless (reconnect_db(\$db)) {
  483. log_error("No database connection available");
  484. return wantarray ? () : undef;
  485. }
  486. my $sth = $db->prepare($sql) or do {
  487. log_error("Unable to prepare SQL [$original_sql]: " . $db->errstr);
  488. return wantarray ? () : undef;
  489. };
  490. my $rv = $params ? $sth->execute(@$params) : $sth->execute();
  491. unless ($rv) {
  492. log_error("Unable to execute SQL [$original_sql]" . ($params ? " with params: [" . join(', ', map { defined $_ ? $_ : 'undef' } @$params) . "]" : "") . ": " . $sth->errstr);
  493. $sth->finish();
  494. return wantarray ? () : undef;
  495. }
  496. # --- Обработка результатов ---
  497. if ($was_modified && $mode eq 'scalar') {
  498. # Это был INSERT + RETURNING id в PostgreSQL
  499. my $row = $sth->fetchrow_arrayref();
  500. $sth->finish();
  501. my $id = $row ? $row->[0] : 0;
  502. return $id;
  503. }
  504. elsif ($mode eq 'single') {
  505. my $row = $sth->fetchrow_hashref();
  506. $sth->finish();
  507. return $row;
  508. }
  509. elsif ($mode eq 'array') {
  510. my @rows;
  511. while (my $row = $sth->fetchrow_hashref()) {
  512. push @rows, $row;
  513. }
  514. $sth->finish();
  515. return \@rows;
  516. }
  517. elsif ($mode eq 'arrayref') {
  518. my $rows = $sth->fetchall_arrayref({});
  519. $sth->finish();
  520. return $rows;
  521. }
  522. elsif ($mode eq 'scalar') {
  523. my $row = $sth->fetchrow_arrayref();
  524. $sth->finish();
  525. return $row ? $row->[0] : undef;
  526. }
  527. elsif ($mode eq 'id') {
  528. # Сюда попадём только если НЕ было модификации (т.е. MySQL или старый Pg-путь)
  529. if ($original_sql =~ /^\s*INSERT/i) {
  530. my $id;
  531. if (get_db_type($db) eq 'mysql') {
  532. $id = $sth->{mysql_insertid};
  533. } else {
  534. ($id) = $db->selectrow_array("SELECT lastval()");
  535. }
  536. $sth->finish();
  537. return $id || 0;
  538. }
  539. $sth->finish();
  540. return 1;
  541. }
  542. else {
  543. $sth->finish();
  544. return 1;
  545. }
  546. }
  547. #---------------------------------------------------------------------------------------------------------------
  548. sub get_records_sql {
  549. my ($db, $sql, @params) = @_;
  550. my @result;
  551. return @result if (!$db);
  552. return @result if (!$sql);
  553. unless (reconnect_db(\$db)) {
  554. log_error("No database connection available");
  555. return @result;
  556. }
  557. my $result_ref = _execute_param($db, $sql, \@params, { mode => 'array' });
  558. if (ref($result_ref) eq 'ARRAY') {
  559. @result = @$result_ref;
  560. }
  561. return @result;
  562. }
  563. #---------------------------------------------------------------------------------------------------------------
  564. sub get_record_sql {
  565. my ($db, $sql, @params) = @_;
  566. my @result;
  567. return @result if (!$db);
  568. return @result if (!$sql);
  569. # Добавляем LIMIT только если его еще нет в запросе
  570. if ($sql !~ /\bLIMIT\s+\d+/i && $sql !~ /\bFETCH\s+FIRST\s+\d+/i) {
  571. $sql .= ' LIMIT 1';
  572. }
  573. # Переподключение
  574. unless (reconnect_db(\$db)) {
  575. log_error("No database connection available");
  576. return;
  577. }
  578. return _execute_param($db, $sql, \@params, { mode => 'single' });
  579. }
  580. #---------------------------------------------------------------------------------------------------------------
  581. sub get_count_records {
  582. my ($db, $table, $filter, @params) = @_;
  583. my $result = 0;
  584. return $result if (!$db);
  585. return $result if (!$table);
  586. my $sSQL='SELECT COUNT(*) as rec_cnt FROM '.$table;
  587. if ($filter) { $sSQL=$sSQL." WHERE ".$filter; }
  588. my $record = get_record_sql($db,$sSQL, @params);
  589. if ($record->{rec_cnt}) { $result = $record->{rec_cnt}; }
  590. return $result;
  591. }
  592. #---------------------------------------------------------------------------------------------------------------
  593. sub get_id_record {
  594. my ($db, $table, $filter, @params) = @_;
  595. my $result = 0;
  596. return $result if (!$db);
  597. return $result if (!$table);
  598. my $record = get_record_sql($db,"SELECT id FROM $table WHERE $filter", @params);
  599. if ($record->{id}) { $result = $record->{id}; }
  600. return $result;
  601. }
  602. #---------------------------------------------------------------------------------------------------------------
  603. sub get_diff_rec {
  604. my ($db, $table, $record, $filter_sql, @filter_params) = @_;
  605. return unless $db && $table && $filter_sql;
  606. unless (reconnect_db(\$db)) {
  607. log_error("No database connection available");
  608. return;
  609. }
  610. my $old_record = get_record_sql($db,"SELECT * FROM $table WHERE $filter_sql",@filter_params);
  611. return unless $old_record;
  612. my $result;
  613. foreach my $field (keys %$record) {
  614. if (!$record->{$field}) { $record->{$field}=''; }
  615. if (!$old_record->{$field}) { $old_record->{$field}=''; }
  616. if ($record->{$field}!~/^$old_record->{$field}$/) { $result->{$field} = "$record->{$field} [ old: " . $old_record->{$field} . "]"; }
  617. }
  618. return hash_to_text($result);
  619. }
  620. #---------------------------------------------------------------------------------------------------------------
  621. sub get_db_type {
  622. my $db = shift;
  623. return lc($db->{Driver}->{Name});
  624. #'mysql', 'pg'
  625. }
  626. #---------------------------------------------------------------------------------------------------------------
  627. sub insert_record {
  628. my ($db, $table, $record) = @_;
  629. return unless $db && $table;
  630. unless (reconnect_db(\$db)) {
  631. log_error("No database connection available");
  632. return;
  633. }
  634. my $dns_changed = 0;
  635. my $rec_id = 0;
  636. if ($table eq "user_auth") {
  637. foreach my $field (keys %$record) {
  638. if (exists $acl_fields{$field}) { $record->{changed}="1"; }
  639. if (exists $dhcp_fields{$field}) { $record->{dhcp_changed}="1"; }
  640. if (exists $dns_fields{$field}) { $dns_changed=1; }
  641. }
  642. }
  643. my @insert_params;
  644. my $fields = '';
  645. my $values = '';
  646. my $new_str = '';
  647. foreach my $field (keys %$record) {
  648. my $val = defined $record->{$field} ? $record->{$field} : undef;
  649. # Экранируем имя поля в зависимости от СУБД
  650. my $quoted_field = get_db_type($db) eq 'mysql'
  651. ? '`' . $field . '`'
  652. : '"' . $field . '"';
  653. $fields .= "$quoted_field, ";
  654. $values .= "?, ";
  655. push @insert_params, $val;
  656. # Для лога — безопасное представление
  657. my $log_val = defined $val ? substr($val, 0, 200) : 'NULL';
  658. $log_val =~ s/[^[:print:]]/_/g;
  659. $new_str .= " $field => $log_val,";
  660. }
  661. $fields =~ s/,\s*$//;
  662. $values =~ s/,\s*$//;
  663. my $sSQL = "INSERT INTO $table($fields) VALUES($values)";
  664. my $result = do_sql($db,$sSQL,@insert_params);
  665. if ($result) {
  666. $rec_id = $result if ($table eq "user_auth");
  667. $new_str='id: '.$result.' '.$new_str;
  668. if ($table eq 'user_auth_alias' and $dns_changed) {
  669. if ($record->{'alias'} and $record->{'alias'}!~/\.$/) {
  670. my $add_dns;
  671. $add_dns->{'name_type'}='CNAME';
  672. $add_dns->{'name'}=$record->{'alias'};
  673. $add_dns->{'value'}=get_dns_name($db,$record->{'auth_id'});
  674. $add_dns->{'operation_type'}='add';
  675. $add_dns->{'auth_id'}=$record->{'auth_id'};
  676. insert_record($db,'dns_queue',$add_dns);
  677. }
  678. }
  679. if ($table eq 'user_auth' and $dns_changed) {
  680. if ($record->{'dns_name'} and $record->{'ip'} and !$record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  681. my $add_dns;
  682. $add_dns->{'name_type'}='A';
  683. $add_dns->{'name'}=$record->{'dns_name'};
  684. $add_dns->{'value'}=$record->{'ip'};
  685. $add_dns->{'operation_type'}='add';
  686. $add_dns->{'auth_id'}=$result;
  687. insert_record($db,'dns_queue',$add_dns);
  688. }
  689. if ($record->{'dns_name'} and $record->{'ip'} and $record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  690. my $add_dns;
  691. $add_dns->{'name_type'}='PTR';
  692. $add_dns->{'name'}=$record->{'dns_name'};
  693. $add_dns->{'value'}=$record->{'ip'};
  694. $add_dns->{'operation_type'}='add';
  695. $add_dns->{'auth_id'}=$result;
  696. insert_record($db,'dns_queue',$add_dns);
  697. }
  698. }
  699. }
  700. db_log_debug($db,'Add record to table '.$table.' '.$new_str,$rec_id);
  701. return $result;
  702. }
  703. #---------------------------------------------------------------------------------------------------------------
  704. sub update_record {
  705. my ($db, $table, $record, $filter_sql, @filter_params) = @_;
  706. return unless $db && $table && $filter_sql;
  707. unless (reconnect_db(\$db)) {
  708. log_error("No database connection available");
  709. return;
  710. }
  711. my $select_sql = "SELECT * FROM $table WHERE $filter_sql";
  712. my $old_record = get_record_sql($db, $select_sql, @filter_params);
  713. return unless $old_record;
  714. my @update_params;
  715. my $set_clause = '';
  716. my $dns_changed = 0;
  717. my $rec_id = $old_record->{id} || 0;
  718. if ($table eq "user_auth") {
  719. $rec_id = $old_record->{'id'} if ($old_record->{'id'});
  720. my $cur_ou_id = $old_record->{'ou_id'} if ($old_record->{'ou_id'});
  721. if (exists $record->{ou_id}) { $cur_ou_id = $record->{'ou_id'}; }
  722. #disable update field 'created_by'
  723. #if ($old_record->{'created_by'} and exists ($record->{'created_by'})) { delete $record->{'created_by'}; }
  724. foreach my $field (keys %$record) {
  725. if (exists $acl_fields{$field}) { $record->{changed}="1"; }
  726. if (exists $dhcp_fields{$field} and !is_system_ou($db,$cur_ou_id)) { $record->{dhcp_changed}="1"; }
  727. if (exists $dns_fields{$field}) { $dns_changed=1; }
  728. }
  729. }
  730. my $diff = '';
  731. for my $field (keys %$record) {
  732. my $old_val = defined $old_record->{$field} ? $old_record->{$field} : '';
  733. my $new_val = defined $record->{$field} ? $record->{$field} : '';
  734. if ($new_val ne $old_val) {
  735. $diff .= " $field => $new_val (old: $old_val),";
  736. $set_clause .= " $field = ?, ";
  737. push @update_params, $new_val;
  738. }
  739. }
  740. return 1 unless $set_clause;
  741. # Добавляем служебные поля
  742. if ($table eq 'user_auth') {
  743. $set_clause .= "changed_time = ?, ";
  744. push @update_params, GetNowTime();
  745. }
  746. $set_clause =~ s/,\s*$//;
  747. $diff =~ s/,\s*$//;
  748. if ($table eq 'user_auth') {
  749. if ($dns_changed) {
  750. my $del_dns;
  751. if ($old_record->{'dns_name'} and $old_record->{'ip'} and !$old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
  752. $del_dns->{'name_type'}='A';
  753. $del_dns->{'name'}=$old_record->{'dns_name'};
  754. $del_dns->{'value'}=$old_record->{'ip'};
  755. $del_dns->{'operation_type'}='del';
  756. if ($rec_id) { $del_dns->{'auth_id'}=$rec_id; }
  757. insert_record($db,'dns_queue',$del_dns);
  758. }
  759. if ($old_record->{'dns_name'} and $old_record->{'ip'} and $old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
  760. $del_dns->{'name_type'}='PTR';
  761. $del_dns->{'name'}=$old_record->{'dns_name'};
  762. $del_dns->{'value'}=$old_record->{'ip'};
  763. $del_dns->{'operation_type'}='del';
  764. if ($rec_id) { $del_dns->{'auth_id'}=$rec_id; }
  765. insert_record($db,'dns_queue',$del_dns);
  766. }
  767. my $new_dns;
  768. my $dns_rec_ip = $old_record->{ip};
  769. my $dns_rec_name = $old_record->{dns_name};
  770. if ($record->{'dns_name'}) { $dns_rec_name = $record->{'dns_name'}; }
  771. if ($record->{'ip'}) { $dns_rec_ip = $record->{'ip'}; }
  772. if ($dns_rec_name and $dns_rec_ip and !$record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  773. $new_dns->{'name_type'}='A';
  774. $new_dns->{'name'}=$dns_rec_name;
  775. $new_dns->{'value'}=$dns_rec_ip;
  776. $new_dns->{'operation_type'}='add';
  777. if ($rec_id) { $new_dns->{'auth_id'}=$rec_id; }
  778. insert_record($db,'dns_queue',$new_dns);
  779. }
  780. if ($dns_rec_name and $dns_rec_ip and $record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  781. $new_dns->{'name_type'}='PTR';
  782. $new_dns->{'name'}=$dns_rec_name;
  783. $new_dns->{'value'}=$dns_rec_ip;
  784. $new_dns->{'operation_type'}='add';
  785. if ($rec_id) { $new_dns->{'auth_id'}=$rec_id; }
  786. insert_record($db,'dns_queue',$new_dns);
  787. }
  788. }
  789. }
  790. if ($table eq 'user_auth_alias') {
  791. if ($dns_changed) {
  792. my $del_dns;
  793. if ($old_record->{'alias'} and $old_record->{'alias'}!~/\.$/) {
  794. $del_dns->{'name_type'}='CNAME';
  795. $del_dns->{'name'}=$old_record->{'alias'};
  796. $del_dns->{'operation_type'}='del';
  797. $del_dns->{'value'}=get_dns_name($db,$old_record->{auth_id});
  798. $del_dns->{'auth_id'}=$old_record->{auth_id};
  799. insert_record($db,'dns_queue',$del_dns);
  800. }
  801. my $new_dns;
  802. my $dns_rec_name = $old_record->{alias};
  803. if ($record->{'alias'}) { $dns_rec_name = $record->{'alias'}; }
  804. if ($dns_rec_name and $record->{'alias'}!~/\.$/) {
  805. $new_dns->{'name_type'}='CNAME';
  806. $new_dns->{'name'}=$dns_rec_name;
  807. $new_dns->{'operation_type'}='add';
  808. $new_dns->{'value'}=get_dns_name($db,$old_record->{auth_id});
  809. $new_dns->{'auth_id'}=$rec_id;
  810. insert_record($db,'dns_queue',$new_dns);
  811. }
  812. }
  813. }
  814. # Формируем полный список параметров: сначала SET, потом WHERE
  815. my @all_params = (@update_params, @filter_params);
  816. my $update_sql = "UPDATE $table SET $set_clause WHERE $filter_sql";
  817. db_log_debug($db, "Change table $table for $filter_sql set: $diff", $rec_id);
  818. return do_sql($db, $update_sql, @all_params);
  819. }
  820. #---------------------------------------------------------------------------------------------------------------
  821. sub delete_record {
  822. my ($db, $table, $filter_sql, @filter_params) = @_;
  823. return unless $db && $table && $filter_sql;
  824. unless (reconnect_db(\$db)) {
  825. log_error("No database connection available");
  826. return;
  827. }
  828. my $select_sql = "SELECT * FROM $table WHERE $filter_sql";
  829. my $old_record = get_record_sql($db, $select_sql, @filter_params);
  830. return unless $old_record;
  831. my $rec_id = 0;
  832. my $diff='';
  833. foreach my $field (keys %$old_record) {
  834. next if (!$old_record->{$field});
  835. $diff = $diff." $field => $old_record->{$field},";
  836. }
  837. $diff=~s/,\s*$//;
  838. if ($table eq 'user_auth') {
  839. $rec_id = $old_record->{'id'} if ($old_record->{'id'});
  840. }
  841. db_log_debug($db,'Delete record from table '.$table.' value: '.$diff, $rec_id);
  842. #never delete user ip record!
  843. if ($table eq 'user_auth') {
  844. my $sSQL = "UPDATE user_auth SET changed = 1, deleted = 1, changed_time = ? WHERE $filter_sql";
  845. my $ret = do_sql($db, $sSQL, GetNowTime(), @filter_params);
  846. if ($old_record->{'dns_name'} and $old_record->{'ip'} and !$old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
  847. my $del_dns;
  848. $del_dns->{'name_type'}='A';
  849. $del_dns->{'name'}=$old_record->{'dns_name'};
  850. $del_dns->{'value'}=$old_record->{'ip'};
  851. $del_dns->{'operation_type'}='del';
  852. $del_dns->{'auth_id'}=$old_record->{'id'};
  853. insert_record($db,'dns_queue',$del_dns);
  854. }
  855. if ($old_record->{'dns_name'} and $old_record->{'ip'} and $old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
  856. my $del_dns;
  857. $del_dns->{'name_type'}='PTR';
  858. $del_dns->{'name'}=$old_record->{'dns_name'};
  859. $del_dns->{'value'}=$old_record->{'ip'};
  860. $del_dns->{'operation_type'}='del';
  861. $del_dns->{'auth_id'}=$old_record->{'id'};
  862. insert_record($db,'dns_queue',$del_dns);
  863. }
  864. return $ret;
  865. }
  866. if ($table eq 'user_list' and $old_record->{'permanent'}) { return; }
  867. if ($table eq 'user_auth_alias') {
  868. if ($old_record->{'alias'} and $old_record->{'auth_id'} and $old_record->{'alias'}!~/\.$/) {
  869. my $del_dns;
  870. $del_dns->{'name_type'}='CNAME';
  871. $del_dns->{'name'}=$old_record->{'alias'};
  872. $del_dns->{'value'}=get_dns_name($db,$old_record->{'auth_id'});
  873. $del_dns->{'operation_type'}='del';
  874. $del_dns->{'auth_id'}=$old_record->{'auth_id'};
  875. insert_record($db,'dns_queue',$del_dns);
  876. }
  877. }
  878. my $sSQL = "DELETE FROM ".$table." WHERE ".$filter_sql;
  879. return do_sql($db,$sSQL,@filter_params);
  880. }
  881. #---------------------------------------------------------------------------------------------------------------
  882. sub is_system_ou {
  883. my ($db, $ou_id) = @_;
  884. return 0 if !defined $ou_id || $ou_id !~ /^\d+$/ || $ou_id <= 0;
  885. my $sql = "SELECT 1 FROM ou WHERE id = ? AND (default_users = 1 OR default_hotspot = 1)";
  886. my $record = get_record_sql($db, $sql, $ou_id);
  887. return $record ? 1 : 0;
  888. }
  889. #---------------------------------------------------------------------------------------------------------------
  890. sub init_option {
  891. my $db=shift;
  892. $last_refresh_config = time();
  893. $config_ref{version}='';
  894. my $version_record = get_record_sql($db,"SELECT version FROM version WHERE version is NOT NULL");
  895. if ($version_record) { $config_ref{version}=$version_record->{version}; }
  896. $config_ref{self_ip} = '127.0.0.1';
  897. if ($DBHOST ne '127.0.0.1') {
  898. my $ip_route = qx(ip r get $DBHOST 2>&1 | head -1);
  899. if ($? == 0) {
  900. if ($ip_route =~ /src\s+(\d+\.\d+\.\d+\.\d+)/) { $config_ref{self_ip} = $1; }
  901. }
  902. }
  903. $config_ref{dbh}=$db;
  904. $config_ref{save_detail}=get_option($db,23);
  905. $config_ref{add_unknown_user}=get_option($db,22);
  906. $config_ref{dhcp_server}=get_option($db,5);
  907. $config_ref{snmp_default_version}=get_option($db,9);
  908. $config_ref{snmp_default_community}=get_option($db,11);
  909. $config_ref{KB}=get_option($db,1);
  910. if ($config_ref{KB} ==0) { $config_ref{KB}=1000; }
  911. if ($config_ref{KB} ==1) { $config_ref{KB}=1024; }
  912. $config_ref{admin_email}=get_option($db,21);
  913. $config_ref{sender_email}=get_option($db,52);
  914. $config_ref{send_email}=get_option($db,51);
  915. $config_ref{history}=get_option($db,26);
  916. $config_ref{history_dhcp}=get_option($db,27);
  917. $config_ref{router_login}=get_option($db,28);
  918. $config_ref{router_password}=get_option($db,29);
  919. $config_ref{router_port}=get_option($db,30);
  920. $config_ref{org_name}=get_option($db,32);
  921. $config_ref{domain_name}=get_option($db,33);
  922. $config_ref{connections_history}=get_option($db,35);
  923. $config_ref{debug}=get_option($db,34);
  924. $config_ref{log_level} = get_option($db,53);
  925. if ($config_ref{debug}) { $config_ref{log_level} = 255; }
  926. $config_ref{urgent_sync}=get_option($db,50);
  927. $config_ref{ignore_hotspot_dhcp_log} = get_option($db,44);
  928. $config_ref{ignore_update_dhcp_event} = get_option($db,45);
  929. $config_ref{update_hostname_from_dhcp} = get_option($db,46);
  930. $config_ref{history_log_day}=get_option($db,47);
  931. $config_ref{history_syslog_day} = get_option($db,48);
  932. $config_ref{history_trafstat_day} = get_option($db,49);
  933. $config_ref{enable_quotes} = get_option($db,54);
  934. $config_ref{netflow_step} = get_option($db,55);
  935. $config_ref{traffic_ipstat_history} = get_option($db,56);
  936. $config_ref{nagios_url} = get_option($db,57);
  937. $config_ref{cacti_url} = get_option($db,58);
  938. $config_ref{torrus_url} = get_option($db,59);
  939. $config_ref{wiki_url} = get_option($db,60);
  940. $config_ref{stat_url} = get_option($db,62);
  941. $config_ref{wiki_path} = get_option($db,61);
  942. $config_ref{auto_mac_rule} = get_option($db,64);
  943. #network configuration mode
  944. $config_ref{config_mode}=get_option($db,68);
  945. #auto clean old user record
  946. $config_ref{clean_empty_user}=get_option($db,69);
  947. #dns_server_type
  948. $config_ref{dns_server}=get_option($db,3);
  949. $config_ref{dns_server_type}=get_option($db,70);
  950. $config_ref{enable_dns_updates}=get_option($db,71);
  951. #$save_detail = 1; id=23
  952. $save_detail=get_option($db,23);
  953. #$add_unknown_user = 1; id=22
  954. $add_unknown_user=get_option($db,22);
  955. #$dns_server='192.168.2.12'; id=3
  956. $dns_server=get_option($db,3);
  957. #$dhcp_server='192.168.2.12'; id=5
  958. $dhcp_server=get_option($db,5);
  959. #$snmp_default_version='2'; id=9
  960. $snmp_default_version=get_option($db,9);
  961. #$snmp_default_community='public'; id=11
  962. $snmp_default_community=get_option($db,11);
  963. #$KB=1024; id=1
  964. $KB=$config_ref{KB};
  965. #$admin_email; id=21
  966. $admin_email=get_option($db,21);
  967. #sender email
  968. $sender_email=get_option($db,52);
  969. #send email
  970. $send_email=get_option($db,51);
  971. #$history=15; id=26
  972. $history=get_option($db,26);
  973. #$history_dhcp=7; id=27
  974. $history_dhcp=get_option($db,27);
  975. #$router_login="admin"; id=28
  976. $router_login=get_option($db,28);
  977. #$router_password="admin"; id=29
  978. $router_password=get_option($db,29);
  979. #$router_port=23; id=30
  980. $router_port=get_option($db,30);
  981. #32
  982. $org_name=get_option($db,32);
  983. #33
  984. $domain_name=get_option($db,33);
  985. #35
  986. $connections_history=get_option($db,35);
  987. #debug
  988. $debug=get_option($db,34);
  989. #log level
  990. $log_level = get_option($db,53);
  991. if ($debug) { $log_level = 255; }
  992. #urgent sync access
  993. $urgent_sync=get_option($db,50);
  994. $ignore_hotspot_dhcp_log = get_option($db,44);
  995. $ignore_update_dhcp_event = get_option($db,45);
  996. $update_hostname_from_dhcp = get_option($db,46);
  997. $history_log_day=get_option($db,47);
  998. $history_syslog_day = get_option($db,48);
  999. $history_trafstat_day = get_option($db,49);
  1000. my $ou = get_record_sql($db,"SELECT id FROM ou WHERE default_users = 1");
  1001. if (!$ou) { $default_user_ou_id = 0; } else { $default_user_ou_id = $ou->{'id'}; }
  1002. $ou = get_record_sql($db,"SELECT id FROM ou WHERE default_hotspot = 1 ");
  1003. if (!$ou) { $default_hotspot_ou_id = $default_user_ou_id; } else { $default_hotspot_ou_id = $ou->{'id'}; }
  1004. @subnets=get_records_sql($db,'SELECT * FROM subnets ORDER BY ip_int_start');
  1005. if (defined $office_networks) { undef $office_networks; }
  1006. if (defined $free_networks) { undef $free_networks; }
  1007. if (defined $vpn_networks) { undef $vpn_networks; }
  1008. if (defined $hotspot_networks) { undef $hotspot_networks; }
  1009. if (defined $all_networks) { undef $all_networks; }
  1010. $office_networks = new Net::Patricia;
  1011. $free_networks = new Net::Patricia;
  1012. $vpn_networks = new Net::Patricia;
  1013. $hotspot_networks = new Net::Patricia;
  1014. $all_networks = new Net::Patricia;
  1015. @office_network_list=();
  1016. @free_network_list=();
  1017. @free_network_list=();
  1018. @vpn_network_list=();
  1019. @hotspot_network_list=();
  1020. @all_network_list=();
  1021. foreach my $net (@subnets) {
  1022. next if (!$net->{subnet});
  1023. $subnets_ref{$net->{subnet}}=$net;
  1024. if ($net->{office}) {
  1025. push(@office_network_list,$net->{subnet});
  1026. $office_networks->add_string($net->{subnet},$net);
  1027. }
  1028. if ($net->{free}) {
  1029. push(@free_network_list,$net->{subnet});
  1030. $free_networks->add_string($net->{subnet},$net);
  1031. }
  1032. if ($net->{vpn}) {
  1033. push(@vpn_network_list,$net->{subnet});
  1034. $vpn_networks->add_string($net->{subnet},$net);
  1035. }
  1036. if ($net->{hotspot}) {
  1037. push(@hotspot_network_list,$net->{subnet});
  1038. push(@all_network_list,$net->{subnet});
  1039. $hotspot_networks->add_string($net->{subnet},$net);
  1040. }
  1041. push(@all_network_list,$net->{subnet});
  1042. $all_networks->add_string($net->{subnet},$net);
  1043. }
  1044. }
  1045. #---------------------------------------------------------------------------------------------------------------
  1046. sub Set_Variable {
  1047. my ($db, $name, $value, $timeshift) = @_;
  1048. $name //= $MY_NAME;
  1049. $value //= $$;
  1050. $timeshift //= 60;
  1051. Del_Variable($db, $name);
  1052. my $clean_time = time() + $timeshift;
  1053. my ($sec, $min, $hour, $day, $month, $year) = localtime($clean_time);
  1054. $month++;
  1055. $year += 1900;
  1056. my $clear_time_str = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $year, $month, $day, $hour, $min, $sec;
  1057. my $sql = "INSERT INTO variables (name, value, clear_time) VALUES (?, ?, ?)";
  1058. do_sql($db, $sql, $name, $value, $clear_time_str);
  1059. }
  1060. #---------------------------------------------------------------------------------------------------------------
  1061. sub Get_Variable {
  1062. my $db = shift;
  1063. my $name = shift || $MY_NAME;
  1064. my $variable = get_record_sql($db, 'SELECT value FROM variables WHERE name = ?', $name);
  1065. if ($variable and $variable->{'value'}) { return $variable->{'value'}; }
  1066. return;
  1067. }
  1068. #---------------------------------------------------------------------------------------------------------------
  1069. sub Del_Variable {
  1070. my ($db, $name) = @_;
  1071. $name //= $MY_NAME;
  1072. do_sql($db, "DELETE FROM variables WHERE name = ?", $name);
  1073. }
  1074. #---------------------------------------------------------------------------------------------------------------
  1075. sub clean_variables {
  1076. my ($db) = @_;
  1077. # 1. Clean temporary variables
  1078. my $now = time();
  1079. my ($sec, $min, $hour, $day, $month, $year) = localtime($now);
  1080. $month++;
  1081. $year += 1900;
  1082. my $now_str = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $year, $month, $day, $hour, $min, $sec;
  1083. do_sql($db, "DELETE FROM variables WHERE clear_time <= ?", $now_str);
  1084. # 2. Clean old AD computer cache
  1085. my $yesterday = DateTime->now(time_zone => 'local')->subtract(days => 1);
  1086. my $clean_str = $yesterday->strftime("%Y-%m-%d 00:00:00");
  1087. do_sql($db, "DELETE FROM ad_comp_cache WHERE last_found <= ?", $clean_str);
  1088. }
  1089. #---------------------------------------------------------------------------------------------------------------
  1090. #skip init for upgrade
  1091. if ($MY_NAME!~/upgrade.pl/) {
  1092. $dbh=init_db();
  1093. init_option($dbh);
  1094. clean_variables($dbh);
  1095. Set_Variable($dbh);
  1096. }
  1097. #warn "DBI driver name: ", $dbh->{Driver}->{Name}, "\n";
  1098. #warn "Full dbh class: ", ref($dbh), "\n";
  1099. 1;
  1100. }