database.pm 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281
  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 ($config_ref{DBTYPE} 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 ($config_ref{DBTYPE} eq 'postgresql') {
  213. unless ($db->{Driver}->{Name} eq 'Pg') {
  214. my $err = "PostgreSQL expected but connected via " . $db->{Driver}->{Name};
  215. log_error($err);
  216. $db->disconnect();
  217. return 0;
  218. }
  219. if (!$db->can('pg_putcopydata') || !$db->can('pg_putcopyend')) {
  220. log_debug("pg_putcopydata/pg_putcopyend not available — falling back to bulk INSERT");
  221. goto FALLBACK_INSERT_PG;
  222. }
  223. my $col_list = join(', ', map { $db->quote_identifier($_) } @columns);
  224. my $copy_sql = "COPY $table ($col_list) FROM STDIN WITH (FORMAT CSV, HEADER true)";
  225. my $use_header_as_data;
  226. my $start_ok = eval { $db->do($copy_sql); 1 };
  227. if (!$start_ok) {
  228. log_debug("COPY with HEADER failed: $@ — trying without HEADER");
  229. $copy_sql = "COPY $table ($col_list) FROM STDIN WITH (FORMAT CSV)";
  230. $start_ok = eval { $db->do($copy_sql); 1 };
  231. if (!$start_ok) {
  232. log_debug("COPY failed entirely: $@ — falling back to bulk INSERT");
  233. goto FALLBACK_INSERT_PG;
  234. }
  235. $use_header_as_data = 1;
  236. } else {
  237. $use_header_as_data = 0;
  238. }
  239. log_debug("Using CSV COPY for PostgreSQL");
  240. my $csv = Text::CSV->new({
  241. binary => 1,
  242. quote_char => '"',
  243. escape_char => '"',
  244. sep_char => ',',
  245. eol => "\n",
  246. always_quote => 1,
  247. }) or do {
  248. my $err = "Cannot create Text::CSV: " . Text::CSV->error_diag();
  249. log_error($err);
  250. eval { $db->pg_putcopyend(); };
  251. $db->disconnect();
  252. return 0;
  253. };
  254. my $success = eval {
  255. if ($use_header_as_data) {
  256. $csv->combine(@columns);
  257. $db->pg_putcopydata($csv->string);
  258. }
  259. for my $row (@$data_rows) {
  260. next unless $row && ref($row) eq 'ARRAY' && @$row == @columns;
  261. my @vals = map { defined($_) ? $_ : undef } @$row;
  262. $csv->combine(@vals);
  263. $db->pg_putcopydata($csv->string);
  264. }
  265. $db->pg_putcopyend();
  266. 1;
  267. };
  268. if ($success) {
  269. $db->disconnect();
  270. return 1;
  271. } else {
  272. my $err = "CSV COPY failed: $@";
  273. log_error($err);
  274. eval { $db->pg_putcopyend(); };
  275. goto FALLBACK_INSERT_PG;
  276. }
  277. # ========================
  278. # Fallback для PostgreSQL
  279. # ========================
  280. FALLBACK_INSERT_PG:
  281. {
  282. my $quoted_cols = join(', ', map { $db->quote_identifier($_) } @columns);
  283. my $placeholders = join(',', ('?') x @columns);
  284. my $sql = "INSERT INTO $table ($quoted_cols) VALUES ($placeholders)";
  285. my $sth = $db->prepare($sql);
  286. my $success = eval {
  287. for my $row (@$data_rows) {
  288. next unless $row && ref($row) eq 'ARRAY' && @$row == @columns;
  289. my @vals = map { defined($_) ? $_ : undef } @$row;
  290. $sth->execute(@vals);
  291. }
  292. 1;
  293. };
  294. if (!$success) {
  295. my $err = "PostgreSQL bulk INSERT failed: $@";
  296. log_error($err);
  297. $db->disconnect();
  298. return 0;
  299. }
  300. }
  301. } else {
  302. my $err = "Unsupported DBTYPE: '$config_ref{DBTYPE}'";
  303. log_error($err);
  304. $db->disconnect();
  305. return 0;
  306. }
  307. $db->disconnect();
  308. return 1;
  309. }
  310. #---------------------------------------------------------------------------------------------------------------
  311. sub reconnect_db {
  312. my $db_ref = shift;
  313. # Если соединение активно, ничего не делаем
  314. if ($$db_ref && $$db_ref->ping) {
  315. return 1;
  316. }
  317. # Переподключаемся
  318. eval {
  319. # Закрываем старое соединение если есть
  320. if ($$db_ref) {
  321. $$db_ref->disconnect;
  322. $$db_ref = undef;
  323. }
  324. # Создаем новое соединение
  325. $$db_ref = init_db();
  326. # Проверяем что соединение установлено
  327. unless ($$db_ref && $$db_ref->ping) {
  328. die "Failed to establish database connection";
  329. }
  330. 1; # возвращаем истину при успехе
  331. } or do {
  332. my $error = $@ || 'Unknown error';
  333. warn "Database reconnection failed: $error";
  334. $$db_ref = undef;
  335. return 0;
  336. };
  337. return 1;
  338. }
  339. #---------------------------------------------------------------------------------------------------------------
  340. sub write_db_log {
  341. my $db=shift;
  342. my $msg=shift;
  343. my $level = shift || $L_VERBOSE;
  344. my $auth_id = shift || 0;
  345. return if (!$db);
  346. return if (!$msg);
  347. $msg=~s/[\'\"]//g;
  348. my $db_log = 0;
  349. # Переподключение
  350. unless (reconnect_db(\$db)) {
  351. log_error("No database connection available");
  352. $db_log = 0;
  353. }
  354. if ($level eq $L_ERROR and $log_level >= $L_ERROR) { log_error($msg); $db_log = 1; }
  355. if ($level eq $L_WARNING and $log_level >= $L_WARNING) { log_warning($msg); $db_log = 1; }
  356. if ($level eq $L_INFO and $log_level >= $L_INFO) { log_info($msg); $db_log = 1; }
  357. if ($level eq $L_VERBOSE and $log_level >= $L_VERBOSE) { log_verbose($msg); $db_log = 1; }
  358. if ($level eq $L_DEBUG and $log_level >= $L_DEBUG) { log_debug($msg); return; }
  359. if ($db_log) {
  360. #my $new_id = do_sql($dbh, 'INSERT INTO user_list (login) VALUES (?)', 'Ivan');
  361. do_sql($db,'INSERT INTO worklog(customer,message,level,auth_id,ip) VALUES( ?, ?, ?, ?, ?)',$MY_NAME,$msg,$level,$auth_id,$config_ref{self_ip});
  362. }
  363. }
  364. #---------------------------------------------------------------------------------------------------------------
  365. sub db_log_debug {
  366. my $db = shift;
  367. my $msg = shift;
  368. my $id = shift;
  369. if ($debug) { log_debug($msg); }
  370. }
  371. #---------------------------------------------------------------------------------------------------------------
  372. sub db_log_error {
  373. my $db = shift;
  374. my $msg = shift;
  375. if ($log_level >= $L_ERROR) {
  376. sendEmail("ERROR! ".get_first_line($msg),$msg,1);
  377. write_db_log($db,$msg,$L_ERROR);
  378. }
  379. }
  380. #---------------------------------------------------------------------------------------------------------------
  381. sub db_log_info {
  382. my $db = shift;
  383. my $msg = shift;
  384. my $id = shift;
  385. if ($log_level >= $L_INFO) { write_db_log($db,$msg,$L_INFO,$id); }
  386. }
  387. #---------------------------------------------------------------------------------------------------------------
  388. sub db_log_verbose {
  389. my $db = shift;
  390. my $msg = shift;
  391. my $id = shift;
  392. if ($log_level >= $L_VERBOSE) { write_db_log($db,$msg,$L_VERBOSE,$id); }
  393. }
  394. #---------------------------------------------------------------------------------------------------------------
  395. sub db_log_warning {
  396. my $db = shift;
  397. my $msg = shift;
  398. my $id = shift;
  399. if ($log_level >= $L_WARNING) { write_db_log($db,$msg,$L_WARNING,$id); }
  400. }
  401. #---------------------------------------------------------------------------------------------------------------
  402. sub init_db {
  403. # Create new database handle. If we can't connect, die()
  404. my $db;
  405. if ($config_ref{DBTYPE} eq 'mysql') {
  406. $db = DBI->connect("dbi:mysql:database=$DBNAME;host=$DBHOST;mysql_local_infile=1","$DBUSER","$DBPASS",
  407. { RaiseError => 0, AutoCommit => 1, mysql_enable_utf8 => 1 });
  408. if ( !defined $db ) { die "Cannot connect to MySQL server: $DBI::errstr\n"; }
  409. $db->do('SET NAMES utf8mb4');
  410. } else {
  411. $db = DBI->connect("dbi:Pg:dbname=$DBNAME;host=$DBHOST","$DBUSER","$DBPASS",
  412. { RaiseError => 0, AutoCommit => 1, pg_enable_utf8 => 1, pg_server_prepare => 0 });
  413. if ( !defined $db ) { die "Cannot connect to PostgreSQL server: $DBI::errstr\n"; }
  414. }
  415. return $db;
  416. }
  417. #---------------------------------------------------------------------------------------------------------------
  418. # Обновленная функция get_option с параметризованными запросами
  419. sub get_option {
  420. my $db = shift;
  421. my $option_id = shift;
  422. return if (!$option_id);
  423. return if (!$db);
  424. my $sql = q{
  425. SELECT
  426. COALESCE(c.value, co.default_value) AS value,
  427. co.option_type
  428. FROM config_options co
  429. LEFT JOIN config c ON c.option_id = co.id
  430. WHERE co.id = ?
  431. };
  432. my $record = get_record_sql($db, $sql, $option_id);
  433. unless ($record) {
  434. log_error("Option ID $option_id not found in config_options table");
  435. return;
  436. }
  437. return $record->{value};
  438. }
  439. #---------------------------------------------------------------------------------------------------------------
  440. sub do_sql {
  441. my ($db, $sql, @bind_values) = @_;
  442. return 0 unless $db && $sql;
  443. my $mode;
  444. if ($sql =~ /^\s*insert\b/i) {
  445. $mode = 'id';
  446. } elsif ($sql =~ /^\s*select\b/i) {
  447. $mode = 'arrayref';
  448. } else {
  449. $mode = 'execute';
  450. }
  451. my $result = _execute_param($db, $sql, \@bind_values, { mode => $mode });
  452. # Обработка ошибок: если _execute_param вернул undef/ложь — возвращаем 0
  453. unless (defined $result) {
  454. return 0;
  455. }
  456. if ($mode eq 'id') {
  457. return $result; # уже число (или 0)
  458. } elsif ($mode eq 'arrayref') {
  459. return ref($result) eq 'ARRAY' ? $result : 0; # на случай ошибки
  460. } else {
  461. return $result ? 1 : 0;
  462. }
  463. }
  464. #---------------------------------------------------------------------------------------------------------------
  465. # Внутренняя функция для выполнения параметризованных запросов
  466. sub _execute_param {
  467. my ($db, $sql, $params, $options) = @_;
  468. return unless $db && $sql;
  469. my $mode = $options->{mode} || 'execute';
  470. # автоматическая поддержка RETURNING для PostgreSQL ---
  471. my $was_modified = 0;
  472. my $original_sql = $sql;
  473. if ($mode eq 'id' && $sql =~ /^\s*INSERT\b/i) {
  474. if ($config_ref{DBTYPE} && $config_ref{DBTYPE} eq 'Pg') {
  475. # Добавляем RETURNING id, если его ещё нет
  476. unless ($sql =~ /\bRETURNING\b/i) {
  477. $sql .= ' RETURNING id';
  478. $was_modified = 1;
  479. # Теперь нам нужно получить скаляр, а не использовать lastval()
  480. $mode = 'scalar'; # временно меняем режим
  481. }
  482. }
  483. }
  484. # Логируем не-SELECT (но уже с возможным RETURNING)
  485. unless ($original_sql =~ /^\s*SELECT/i) {
  486. log_debug($original_sql . ($params ? ' | params: [' . join(', ', map { defined $_ ? $_ : 'undef' } @$params) . ']' : ''));
  487. }
  488. unless (reconnect_db(\$db)) {
  489. log_error("No database connection available");
  490. return wantarray ? () : undef;
  491. }
  492. my $sth = $db->prepare($sql) or do {
  493. log_error("Unable to prepare SQL [$original_sql]: " . $db->errstr);
  494. return wantarray ? () : undef;
  495. };
  496. my $rv = $params ? $sth->execute(@$params) : $sth->execute();
  497. unless ($rv) {
  498. log_error("Unable to execute SQL [$original_sql]" . ($params ? " with params: [" . join(', ', map { defined $_ ? $_ : 'undef' } @$params) . "]" : "") . ": " . $sth->errstr);
  499. $sth->finish();
  500. return wantarray ? () : undef;
  501. }
  502. # --- Обработка результатов ---
  503. if ($was_modified && $mode eq 'scalar') {
  504. # Это был INSERT + RETURNING id в PostgreSQL
  505. my $row = $sth->fetchrow_arrayref();
  506. $sth->finish();
  507. my $id = $row ? $row->[0] : 0;
  508. return $id;
  509. }
  510. elsif ($mode eq 'single') {
  511. my $row = $sth->fetchrow_hashref();
  512. $sth->finish();
  513. return $row;
  514. }
  515. elsif ($mode eq 'array') {
  516. my @rows;
  517. while (my $row = $sth->fetchrow_hashref()) {
  518. push @rows, $row;
  519. }
  520. $sth->finish();
  521. return \@rows;
  522. }
  523. elsif ($mode eq 'arrayref') {
  524. my $rows = $sth->fetchall_arrayref({});
  525. $sth->finish();
  526. return $rows;
  527. }
  528. elsif ($mode eq 'scalar') {
  529. my $row = $sth->fetchrow_arrayref();
  530. $sth->finish();
  531. return $row ? $row->[0] : undef;
  532. }
  533. elsif ($mode eq 'id') {
  534. # Сюда попадём только если НЕ было модификации (т.е. MySQL или старый Pg-путь)
  535. if ($original_sql =~ /^\s*INSERT/i) {
  536. my $id;
  537. if ($config_ref{DBTYPE} && $config_ref{DBTYPE} eq 'mysql') {
  538. $id = $sth->{mysql_insertid};
  539. } else {
  540. ($id) = $db->selectrow_array("SELECT lastval()");
  541. }
  542. $sth->finish();
  543. return $id || 0;
  544. }
  545. $sth->finish();
  546. return 1;
  547. }
  548. else {
  549. $sth->finish();
  550. return 1;
  551. }
  552. }
  553. #---------------------------------------------------------------------------------------------------------------
  554. sub get_records_sql {
  555. my ($db, $sql, @params) = @_;
  556. my @result;
  557. return @result if (!$db);
  558. return @result if (!$sql);
  559. unless (reconnect_db(\$db)) {
  560. log_error("No database connection available");
  561. return @result;
  562. }
  563. my $result_ref = _execute_param($db, $sql, \@params, { mode => 'array' });
  564. if (ref($result_ref) eq 'ARRAY') {
  565. @result = @$result_ref;
  566. }
  567. return @result;
  568. }
  569. #---------------------------------------------------------------------------------------------------------------
  570. sub get_record_sql {
  571. my ($db, $sql, @params) = @_;
  572. my @result;
  573. return @result if (!$db);
  574. return @result if (!$sql);
  575. # Добавляем LIMIT только если его еще нет в запросе
  576. if ($sql !~ /\bLIMIT\s+\d+/i && $sql !~ /\bFETCH\s+FIRST\s+\d+/i) {
  577. $sql .= ' LIMIT 1';
  578. }
  579. # Переподключение
  580. unless (reconnect_db(\$db)) {
  581. log_error("No database connection available");
  582. return;
  583. }
  584. return _execute_param($db, $sql, \@params, { mode => 'single' });
  585. }
  586. #---------------------------------------------------------------------------------------------------------------
  587. sub get_count_records {
  588. my ($db, $table, $filter, @params) = @_;
  589. my $result = 0;
  590. return $result if (!$db);
  591. return $result if (!$table);
  592. my $sSQL='SELECT COUNT(*) as rec_cnt FROM '.$table;
  593. if ($filter) { $sSQL=$sSQL." WHERE ".$filter; }
  594. my $record = get_record_sql($db,$sSQL, @params);
  595. if ($record->{rec_cnt}) { $result = $record->{rec_cnt}; }
  596. return $result;
  597. }
  598. #---------------------------------------------------------------------------------------------------------------
  599. sub get_id_record {
  600. my ($db, $table, $filter, @params) = @_;
  601. my $result = 0;
  602. return $result if (!$db);
  603. return $result if (!$table);
  604. my $record = get_record_sql($db,"SELECT id FROM $table WHERE $filter", @params);
  605. if ($record->{id}) { $result = $record->{id}; }
  606. return $result;
  607. }
  608. #---------------------------------------------------------------------------------------------------------------
  609. sub get_diff_rec {
  610. my ($db, $table, $record, $filter_sql, @filter_params) = @_;
  611. return unless $db && $table && $filter_sql;
  612. unless (reconnect_db(\$db)) {
  613. log_error("No database connection available");
  614. return;
  615. }
  616. my $old_record = get_record_sql($db,"SELECT * FROM $table WHERE $filter_sql",@filter_params);
  617. return unless $old_record;
  618. my $result;
  619. foreach my $field (keys %$record) {
  620. if (!$record->{$field}) { $record->{$field}=''; }
  621. if (!$old_record->{$field}) { $old_record->{$field}=''; }
  622. if ($record->{$field}!~/^$old_record->{$field}$/) { $result->{$field} = "$record->{$field} [ old: " . $old_record->{$field} . "]"; }
  623. }
  624. return hash_to_text($result);
  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 = $config_ref{DBTYPE} 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. }