1
0

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. sub do_sql {
  419. my ($db, $sql, @bind_values) = @_;
  420. return 0 unless $db;
  421. return 0 unless $sql;
  422. unless (reconnect_db(\$db)) {
  423. log_error("No database connection available for SQL: $sql");
  424. return 0;
  425. }
  426. # Логируем не-SELECT-запросы
  427. log_debug( $sql . (@bind_values ? ' | bind: [' . join(', ', map { defined $_ ? $_ : 'undef' } @bind_values) . ']' : '')) unless $sql =~ /^select /i;
  428. # Подготовка запроса
  429. my $sth = $db->prepare($sql) or do {
  430. log_error("Unable to prepare SQL [$sql]: " . $db->errstr);
  431. return 0;
  432. };
  433. # Выполнение запроса
  434. my $rv;
  435. if (@bind_values) {
  436. $rv = $sth->execute(@bind_values) or do {
  437. log_error("Unable to execute SQL [$sql] with bind: [" . join(', ', map { defined $_ ? $_ : 'undef' } @bind_values) . "]: " . $sth->errstr);
  438. return 0;
  439. };
  440. } else {
  441. $rv = $sth->execute() or do {
  442. log_error("Unable to execute SQL [$sql]: " . $sth->errstr);
  443. return 0;
  444. };
  445. }
  446. # Обработка результатов по типу запроса
  447. if ($sql =~ /^insert/i) {
  448. my $id;
  449. if ($config_ref{DBTYPE} and $config_ref{DBTYPE} eq 'mysql') {
  450. $id = $sth->{mysql_insertid};
  451. } else {
  452. ($id) = $db->selectrow_array("SELECT lastval()");
  453. }
  454. $sth->finish();
  455. return $id || 0; # Возвращаем ID или 0 если ID нет
  456. }
  457. elsif ($sql =~ /^select /i) {
  458. my $data = $sth->fetchall_arrayref({});
  459. $sth->finish();
  460. return $data; # возвращаем ссылку на массив
  461. }
  462. else {
  463. # UPDATE, DELETE, CREATE, ALTER и т.д.
  464. $sth->finish();
  465. return 1;
  466. }
  467. }
  468. #---------------------------------------------------------------------------------------------------------------
  469. # Обновленная функция get_option с параметризованными запросами
  470. sub get_option {
  471. my $db = shift;
  472. my $option_id = shift;
  473. return if (!$option_id);
  474. return if (!$db);
  475. my $sql = q{
  476. SELECT
  477. COALESCE(c.value, co.default_value) AS value,
  478. co.option_type
  479. FROM config_options co
  480. LEFT JOIN config c ON c.option_id = co.id
  481. WHERE co.id = ?
  482. };
  483. my $record = get_record_sql($db, $sql, $option_id);
  484. unless ($record) {
  485. log_error("Option ID $option_id not found in config_options table");
  486. return;
  487. }
  488. return $record->{value};
  489. }
  490. #---------------------------------------------------------------------------------------------------------------
  491. # Внутренняя функция для выполнения параметризованных запросов
  492. sub _execute_param {
  493. my ($db, $sql, $params, $options) = @_;
  494. return unless $db && $sql;
  495. # Логируем не-SELECT-запросы
  496. unless ($sql =~ /^\s*SELECT/i) {
  497. log_debug( $sql . ($params ? ' | params: [' . join(', ', map { defined $_ ? $_ : 'undef' } @$params) . ']' : ''));
  498. }
  499. # Переподключение
  500. unless (reconnect_db(\$db)) {
  501. log_error("No database connection available");
  502. return wantarray ? () : undef;
  503. }
  504. my $mode = $options->{mode} || 'execute';
  505. my $sth = $db->prepare($sql) or do {
  506. log_error("Unable to prepare SQL [$sql]: " . $db->errstr);
  507. return wantarray ? () : undef;
  508. };
  509. my $rv = $params ? $sth->execute(@$params) : $sth->execute();
  510. unless ($rv) {
  511. log_error("Unable to execute SQL [$sql]" . ($params ? " with params: [" . join(', ', @$params) . "]" : "") . ": " . $sth->errstr);
  512. $sth->finish();
  513. return wantarray ? () : undef;
  514. }
  515. if ($mode eq 'single') {
  516. my $row = $sth->fetchrow_hashref();
  517. $sth->finish();
  518. return $row;
  519. }
  520. elsif ($mode eq 'array') {
  521. my @rows;
  522. while (my $row = $sth->fetchrow_hashref()) {
  523. push @rows, $row;
  524. }
  525. $sth->finish();
  526. return \@rows;
  527. }
  528. elsif ($mode eq 'arrayref') {
  529. my $rows = $sth->fetchall_arrayref({});
  530. $sth->finish();
  531. return $rows;
  532. }
  533. elsif ($mode eq 'scalar') {
  534. my $row = $sth->fetchrow_arrayref();
  535. $sth->finish();
  536. return $row ? $row->[0] : undef;
  537. }
  538. elsif ($mode eq 'id') {
  539. if ($sql =~ /^\s*INSERT/i) {
  540. my $id;
  541. if ($config_ref{DBTYPE} and $config_ref{DBTYPE} eq 'mysql') {
  542. $id = $sth->{mysql_insertid};
  543. } else {
  544. ($id) = $db->selectrow_array("SELECT lastval()");
  545. }
  546. $sth->finish();
  547. return $id || 0;
  548. }
  549. $sth->finish();
  550. return 1;
  551. }
  552. else {
  553. $sth->finish();
  554. return 1;
  555. }
  556. }
  557. #---------------------------------------------------------------------------------------------------------------
  558. sub get_records_sql {
  559. my ($db, $sql, @params) = @_;
  560. my @result;
  561. return @result if (!$db);
  562. return @result if (!$sql);
  563. unless (reconnect_db(\$db)) {
  564. log_error("No database connection available");
  565. return @result;
  566. }
  567. my $result_ref = _execute_param($db, $sql, \@params, { mode => 'array' });
  568. if (ref($result_ref) eq 'ARRAY') {
  569. @result = @$result_ref;
  570. }
  571. return @result;
  572. }
  573. #---------------------------------------------------------------------------------------------------------------
  574. sub get_record_sql {
  575. my ($db, $sql, @params) = @_;
  576. my @result;
  577. return @result if (!$db);
  578. return @result if (!$sql);
  579. # Добавляем LIMIT только если его еще нет в запросе
  580. if ($sql !~ /\bLIMIT\s+\d+/i && $sql !~ /\bFETCH\s+FIRST\s+\d+/i) {
  581. $sql .= ' LIMIT 1';
  582. }
  583. # Переподключение
  584. unless (reconnect_db(\$db)) {
  585. log_error("No database connection available");
  586. return;
  587. }
  588. return _execute_param($db, $sql, \@params, { mode => 'single' });
  589. }
  590. #---------------------------------------------------------------------------------------------------------------
  591. sub get_count_records {
  592. my ($db, $table, $filter, @params) = @_;
  593. my $result = 0;
  594. return $result if (!$db);
  595. return $result if (!$table);
  596. my $sSQL='SELECT COUNT(*) as rec_cnt FROM '.$table;
  597. if ($filter) { $sSQL=$sSQL." WHERE ".$filter; }
  598. my $record = get_record_sql($db,$sSQL, @params);
  599. if ($record->{rec_cnt}) { $result = $record->{rec_cnt}; }
  600. return $result;
  601. }
  602. #---------------------------------------------------------------------------------------------------------------
  603. sub get_id_record {
  604. my ($db, $table, $filter, @params) = @_;
  605. my $result = 0;
  606. return $result if (!$db);
  607. return $result if (!$table);
  608. my $record = get_record_sql($db,"SELECT id FROM $table WHERE $filter", @params);
  609. if ($record->{id}) { $result = $record->{id}; }
  610. return $result;
  611. }
  612. #---------------------------------------------------------------------------------------------------------------
  613. sub get_diff_rec {
  614. my ($db, $table, $record, $filter_sql, @filter_params) = @_;
  615. return unless $db && $table && $filter_sql;
  616. unless (reconnect_db(\$db)) {
  617. log_error("No database connection available");
  618. return;
  619. }
  620. my $old_record = get_record_sql($db,"SELECT * FROM $table WHERE $filter_sql",@filter_params);
  621. return unless $old_record;
  622. my $result;
  623. foreach my $field (keys %$record) {
  624. if (!$record->{$field}) { $record->{$field}=''; }
  625. if (!$old_record->{$field}) { $old_record->{$field}=''; }
  626. if ($record->{$field}!~/^$old_record->{$field}$/) { $result->{$field} = "$record->{$field} [ old: " . $old_record->{$field} . "]"; }
  627. }
  628. return hast_to_txt($result);
  629. }
  630. #---------------------------------------------------------------------------------------------------------------
  631. sub insert_record {
  632. my ($db, $table, $record) = @_;
  633. return unless $db && $table;
  634. unless (reconnect_db(\$db)) {
  635. log_error("No database connection available");
  636. return;
  637. }
  638. my $dns_changed = 0;
  639. my $rec_id = 0;
  640. if ($table eq "user_auth") {
  641. foreach my $field (keys %$record) {
  642. if (exists $acl_fields{$field}) { $record->{changed}="1"; }
  643. if (exists $dhcp_fields{$field}) { $record->{dhcp_changed}="1"; }
  644. if (exists $dns_fields{$field}) { $dns_changed=1; }
  645. }
  646. }
  647. my @insert_params;
  648. my $fields = '';
  649. my $values = '';
  650. my $new_str = '';
  651. foreach my $field (keys %$record) {
  652. my $val = defined $record->{$field} ? $record->{$field} : undef;
  653. # Экранируем имя поля в зависимости от СУБД
  654. my $quoted_field = $config_ref{DBTYPE} eq 'mysql'
  655. ? '`' . $field . '`'
  656. : '"' . $field . '"';
  657. $fields .= "$quoted_field, ";
  658. $values .= "?, ";
  659. push @insert_params, $val;
  660. # Для лога — безопасное представление
  661. my $log_val = defined $val ? substr($val, 0, 200) : 'NULL';
  662. $log_val =~ s/[^[:print:]]/_/g;
  663. $new_str .= " $field => $log_val,";
  664. }
  665. $fields =~ s/,\s*$//;
  666. $values =~ s/,\s*$//;
  667. my $sSQL = "INSERT INTO $table($fields) VALUES($values)";
  668. my $result = do_sql($db,$sSQL,@insert_params);
  669. if ($result) {
  670. $rec_id = $result if ($table eq "user_auth");
  671. $new_str='id: '.$result.' '.$new_str;
  672. if ($table eq 'user_auth_alias' and $dns_changed) {
  673. if ($record->{'alias'} and $record->{'alias'}!~/\.$/) {
  674. my $add_dns;
  675. $add_dns->{'name_type'}='CNAME';
  676. $add_dns->{'name'}=$record->{'alias'};
  677. $add_dns->{'value'}=get_dns_name($db,$record->{'auth_id'});
  678. $add_dns->{'operation_type'}='add';
  679. $add_dns->{'auth_id'}=$record->{'auth_id'};
  680. insert_record($db,'dns_queue',$add_dns);
  681. }
  682. }
  683. if ($table eq 'user_auth' and $dns_changed) {
  684. if ($record->{'dns_name'} and $record->{'ip'} and !$record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  685. my $add_dns;
  686. $add_dns->{'name_type'}='A';
  687. $add_dns->{'name'}=$record->{'dns_name'};
  688. $add_dns->{'value'}=$record->{'ip'};
  689. $add_dns->{'operation_type'}='add';
  690. $add_dns->{'auth_id'}=$result;
  691. insert_record($db,'dns_queue',$add_dns);
  692. }
  693. if ($record->{'dns_name'} and $record->{'ip'} and $record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  694. my $add_dns;
  695. $add_dns->{'name_type'}='PTR';
  696. $add_dns->{'name'}=$record->{'dns_name'};
  697. $add_dns->{'value'}=$record->{'ip'};
  698. $add_dns->{'operation_type'}='add';
  699. $add_dns->{'auth_id'}=$result;
  700. insert_record($db,'dns_queue',$add_dns);
  701. }
  702. }
  703. }
  704. db_log_debug($db,'Add record to table '.$table.' '.$new_str,$rec_id);
  705. return $result;
  706. }
  707. #---------------------------------------------------------------------------------------------------------------
  708. sub update_record {
  709. my ($db, $table, $record, $filter_sql, @filter_params) = @_;
  710. return unless $db && $table && $filter_sql;
  711. unless (reconnect_db(\$db)) {
  712. log_error("No database connection available");
  713. return;
  714. }
  715. my $select_sql = "SELECT * FROM $table WHERE $filter_sql";
  716. my $old_record = get_record_sql($db, $select_sql, @filter_params);
  717. return unless $old_record;
  718. my @update_params;
  719. my $set_clause = '';
  720. my $dns_changed = 0;
  721. my $rec_id = $old_record->{id} || 0;
  722. if ($table eq "user_auth") {
  723. $rec_id = $old_record->{'id'} if ($old_record->{'id'});
  724. my $cur_ou_id = $old_record->{'ou_id'} if ($old_record->{'ou_id'});
  725. if (exists $record->{ou_id}) { $cur_ou_id = $record->{'ou_id'}; }
  726. #disable update field 'created_by'
  727. if ($old_record->{'created_by'} and exists ($record->{'created_by'})) { delete $record->{'created_by'}; }
  728. foreach my $field (keys %$record) {
  729. if (exists $acl_fields{$field}) { $record->{changed}="1"; }
  730. if (exists $dhcp_fields{$field} and !is_system_ou($db,$cur_ou_id)) { $record->{dhcp_changed}="1"; }
  731. if (exists $dns_fields{$field}) { $dns_changed=1; }
  732. }
  733. }
  734. my $diff = '';
  735. for my $field (keys %$record) {
  736. my $old_val = defined $old_record->{$field} ? $old_record->{$field} : '';
  737. my $new_val = defined $record->{$field} ? $record->{$field} : '';
  738. if ($new_val ne $old_val) {
  739. $diff .= " $field => $new_val (old: $old_val),";
  740. $set_clause .= " $field = ?, ";
  741. push @update_params, $new_val;
  742. }
  743. }
  744. return 1 unless $set_clause;
  745. # Добавляем служебные поля
  746. if ($table eq 'user_auth') {
  747. $set_clause .= "changed_time = ?, ";
  748. push @update_params, GetNowTime();
  749. }
  750. $set_clause =~ s/,\s*$//;
  751. $diff =~ s/,\s*$//;
  752. if ($table eq 'user_auth') {
  753. if ($dns_changed) {
  754. my $del_dns;
  755. if ($old_record->{'dns_name'} and $old_record->{'ip'} and !$old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
  756. $del_dns->{'name_type'}='A';
  757. $del_dns->{'name'}=$old_record->{'dns_name'};
  758. $del_dns->{'value'}=$old_record->{'ip'};
  759. $del_dns->{'operation_type'}='del';
  760. if ($rec_id) { $del_dns->{'auth_id'}=$rec_id; }
  761. insert_record($db,'dns_queue',$del_dns);
  762. }
  763. if ($old_record->{'dns_name'} and $old_record->{'ip'} and $old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
  764. $del_dns->{'name_type'}='PTR';
  765. $del_dns->{'name'}=$old_record->{'dns_name'};
  766. $del_dns->{'value'}=$old_record->{'ip'};
  767. $del_dns->{'operation_type'}='del';
  768. if ($rec_id) { $del_dns->{'auth_id'}=$rec_id; }
  769. insert_record($db,'dns_queue',$del_dns);
  770. }
  771. my $new_dns;
  772. my $dns_rec_ip = $old_record->{ip};
  773. my $dns_rec_name = $old_record->{dns_name};
  774. if ($record->{'dns_name'}) { $dns_rec_name = $record->{'dns_name'}; }
  775. if ($record->{'ip'}) { $dns_rec_ip = $record->{'ip'}; }
  776. if ($dns_rec_name and $dns_rec_ip and !$record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  777. $new_dns->{'name_type'}='A';
  778. $new_dns->{'name'}=$dns_rec_name;
  779. $new_dns->{'value'}=$dns_rec_ip;
  780. $new_dns->{'operation_type'}='add';
  781. if ($rec_id) { $new_dns->{'auth_id'}=$rec_id; }
  782. insert_record($db,'dns_queue',$new_dns);
  783. }
  784. if ($dns_rec_name and $dns_rec_ip and $record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  785. $new_dns->{'name_type'}='PTR';
  786. $new_dns->{'name'}=$dns_rec_name;
  787. $new_dns->{'value'}=$dns_rec_ip;
  788. $new_dns->{'operation_type'}='add';
  789. if ($rec_id) { $new_dns->{'auth_id'}=$rec_id; }
  790. insert_record($db,'dns_queue',$new_dns);
  791. }
  792. }
  793. }
  794. if ($table eq 'user_auth_alias') {
  795. if ($dns_changed) {
  796. my $del_dns;
  797. if ($old_record->{'alias'} and $old_record->{'alias'}!~/\.$/) {
  798. $del_dns->{'name_type'}='CNAME';
  799. $del_dns->{'name'}=$old_record->{'alias'};
  800. $del_dns->{'operation_type'}='del';
  801. $del_dns->{'value'}=get_dns_name($db,$old_record->{auth_id});
  802. $del_dns->{'auth_id'}=$old_record->{auth_id};
  803. insert_record($db,'dns_queue',$del_dns);
  804. }
  805. my $new_dns;
  806. my $dns_rec_name = $old_record->{alias};
  807. if ($record->{'alias'}) { $dns_rec_name = $record->{'alias'}; }
  808. if ($dns_rec_name and $record->{'alias'}!~/\.$/) {
  809. $new_dns->{'name_type'}='CNAME';
  810. $new_dns->{'name'}=$dns_rec_name;
  811. $new_dns->{'operation_type'}='add';
  812. $new_dns->{'value'}=get_dns_name($db,$old_record->{auth_id});
  813. $new_dns->{'auth_id'}=$rec_id;
  814. insert_record($db,'dns_queue',$new_dns);
  815. }
  816. }
  817. }
  818. # Формируем полный список параметров: сначала SET, потом WHERE
  819. my @all_params = (@update_params, @filter_params);
  820. my $update_sql = "UPDATE $table SET $set_clause WHERE $filter_sql";
  821. db_log_debug($db, "Change table $table for $filter_sql set: $diff", $rec_id);
  822. return do_sql($db, $update_sql, @all_params);
  823. }
  824. #---------------------------------------------------------------------------------------------------------------
  825. sub delete_record {
  826. my ($db, $table, $filter_sql, @filter_params) = @_;
  827. return unless $db && $table && $filter_sql;
  828. unless (reconnect_db(\$db)) {
  829. log_error("No database connection available");
  830. return;
  831. }
  832. my $select_sql = "SELECT * FROM $table WHERE $filter_sql";
  833. my $old_record = get_record_sql($db, $select_sql, @filter_params);
  834. return unless $old_record;
  835. my $rec_id = 0;
  836. my $diff='';
  837. foreach my $field (keys %$old_record) {
  838. next if (!$old_record->{$field});
  839. $diff = $diff." $field => $old_record->{$field},";
  840. }
  841. $diff=~s/,\s*$//;
  842. if ($table eq 'user_auth') {
  843. $rec_id = $old_record->{'id'} if ($old_record->{'id'});
  844. }
  845. db_log_debug($db,'Delete record from table '.$table.' value: '.$diff, $rec_id);
  846. #never delete user ip record!
  847. if ($table eq 'user_auth') {
  848. my $sSQL = "UPDATE user_auth SET changed = 1, deleted = 1, changed_time = ? WHERE $filter_sql";
  849. my $ret = do_sql($db, $sSQL, GetNowTime(), @filter_params);
  850. if ($old_record->{'dns_name'} and $old_record->{'ip'} and !$old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
  851. my $del_dns;
  852. $del_dns->{'name_type'}='A';
  853. $del_dns->{'name'}=$old_record->{'dns_name'};
  854. $del_dns->{'value'}=$old_record->{'ip'};
  855. $del_dns->{'operation_type'}='del';
  856. $del_dns->{'auth_id'}=$old_record->{'id'};
  857. insert_record($db,'dns_queue',$del_dns);
  858. }
  859. if ($old_record->{'dns_name'} and $old_record->{'ip'} and $old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
  860. my $del_dns;
  861. $del_dns->{'name_type'}='PTR';
  862. $del_dns->{'name'}=$old_record->{'dns_name'};
  863. $del_dns->{'value'}=$old_record->{'ip'};
  864. $del_dns->{'operation_type'}='del';
  865. $del_dns->{'auth_id'}=$old_record->{'id'};
  866. insert_record($db,'dns_queue',$del_dns);
  867. }
  868. return $ret;
  869. }
  870. if ($table eq 'user_list' and $old_record->{'permanent'}) { return; }
  871. if ($table eq 'user_auth_alias') {
  872. if ($old_record->{'alias'} and $old_record->{'auth_id'} and $old_record->{'alias'}!~/\.$/) {
  873. my $del_dns;
  874. $del_dns->{'name_type'}='CNAME';
  875. $del_dns->{'name'}=$old_record->{'alias'};
  876. $del_dns->{'value'}=get_dns_name($db,$old_record->{'auth_id'});
  877. $del_dns->{'operation_type'}='del';
  878. $del_dns->{'auth_id'}=$old_record->{'auth_id'};
  879. insert_record($db,'dns_queue',$del_dns);
  880. }
  881. }
  882. my $sSQL = "DELETE FROM ".$table." WHERE ".$filter_sql;
  883. return do_sql($db,$sSQL,@filter_params);
  884. }
  885. #---------------------------------------------------------------------------------------------------------------
  886. sub is_system_ou {
  887. my ($db, $ou_id) = @_;
  888. return 0 if !defined $ou_id || $ou_id !~ /^\d+$/ || $ou_id <= 0;
  889. my $sql = "SELECT 1 FROM ou WHERE id = ? AND (default_users = 1 OR default_hotspot = 1)";
  890. my $record = get_record_sql($db, $sql, $ou_id);
  891. return $record ? 1 : 0;
  892. }
  893. #---------------------------------------------------------------------------------------------------------------
  894. sub init_option {
  895. my $db=shift;
  896. $last_refresh_config = time();
  897. $config_ref{version}='';
  898. my $version_record = get_record_sql($db,"SELECT version FROM version WHERE version is NOT NULL");
  899. if ($version_record) { $config_ref{version}=$version_record->{version}; }
  900. $config_ref{self_ip} = '127.0.0.1';
  901. if ($DBHOST ne '127.0.0.1') {
  902. my $ip_route = qx(ip r get $DBHOST 2>&1 | head -1);
  903. if ($? == 0) {
  904. if ($ip_route =~ /src\s+(\d+\.\d+\.\d+\.\d+)/) { $config_ref{self_ip} = $1; }
  905. }
  906. }
  907. $config_ref{dbh}=$db;
  908. $config_ref{save_detail}=get_option($db,23);
  909. $config_ref{add_unknown_user}=get_option($db,22);
  910. $config_ref{dhcp_server}=get_option($db,5);
  911. $config_ref{snmp_default_version}=get_option($db,9);
  912. $config_ref{snmp_default_community}=get_option($db,11);
  913. $config_ref{KB}=get_option($db,1);
  914. if ($config_ref{KB} ==0) { $config_ref{KB}=1000; }
  915. if ($config_ref{KB} ==1) { $config_ref{KB}=1024; }
  916. $config_ref{admin_email}=get_option($db,21);
  917. $config_ref{sender_email}=get_option($db,52);
  918. $config_ref{send_email}=get_option($db,51);
  919. $config_ref{history}=get_option($db,26);
  920. $config_ref{history_dhcp}=get_option($db,27);
  921. $config_ref{router_login}=get_option($db,28);
  922. $config_ref{router_password}=get_option($db,29);
  923. $config_ref{router_port}=get_option($db,30);
  924. $config_ref{org_name}=get_option($db,32);
  925. $config_ref{domain_name}=get_option($db,33);
  926. $config_ref{connections_history}=get_option($db,35);
  927. $config_ref{debug}=get_option($db,34);
  928. $config_ref{log_level} = get_option($db,53);
  929. if ($config_ref{debug}) { $config_ref{log_level} = 255; }
  930. $config_ref{urgent_sync}=get_option($db,50);
  931. $config_ref{ignore_hotspot_dhcp_log} = get_option($db,44);
  932. $config_ref{ignore_update_dhcp_event} = get_option($db,45);
  933. $config_ref{update_hostname_from_dhcp} = get_option($db,46);
  934. $config_ref{history_log_day}=get_option($db,47);
  935. $config_ref{history_syslog_day} = get_option($db,48);
  936. $config_ref{history_trafstat_day} = get_option($db,49);
  937. $config_ref{enable_quotes} = get_option($db,54);
  938. $config_ref{netflow_step} = get_option($db,55);
  939. $config_ref{traffic_ipstat_history} = get_option($db,56);
  940. $config_ref{nagios_url} = get_option($db,57);
  941. $config_ref{cacti_url} = get_option($db,58);
  942. $config_ref{torrus_url} = get_option($db,59);
  943. $config_ref{wiki_url} = get_option($db,60);
  944. $config_ref{stat_url} = get_option($db,62);
  945. $config_ref{wiki_path} = get_option($db,61);
  946. $config_ref{auto_mac_rule} = get_option($db,64);
  947. #network configuration mode
  948. $config_ref{config_mode}=get_option($db,68);
  949. #auto clean old user record
  950. $config_ref{clean_empty_user}=get_option($db,69);
  951. #dns_server_type
  952. $config_ref{dns_server}=get_option($db,3);
  953. $config_ref{dns_server_type}=get_option($db,70);
  954. $config_ref{enable_dns_updates}=get_option($db,71);
  955. #$save_detail = 1; id=23
  956. $save_detail=get_option($db,23);
  957. #$add_unknown_user = 1; id=22
  958. $add_unknown_user=get_option($db,22);
  959. #$dns_server='192.168.2.12'; id=3
  960. $dns_server=get_option($db,3);
  961. #$dhcp_server='192.168.2.12'; id=5
  962. $dhcp_server=get_option($db,5);
  963. #$snmp_default_version='2'; id=9
  964. $snmp_default_version=get_option($db,9);
  965. #$snmp_default_community='public'; id=11
  966. $snmp_default_community=get_option($db,11);
  967. #$KB=1024; id=1
  968. $KB=$config_ref{KB};
  969. #$admin_email; id=21
  970. $admin_email=get_option($db,21);
  971. #sender email
  972. $sender_email=get_option($db,52);
  973. #send email
  974. $send_email=get_option($db,51);
  975. #$history=15; id=26
  976. $history=get_option($db,26);
  977. #$history_dhcp=7; id=27
  978. $history_dhcp=get_option($db,27);
  979. #$router_login="admin"; id=28
  980. $router_login=get_option($db,28);
  981. #$router_password="admin"; id=29
  982. $router_password=get_option($db,29);
  983. #$router_port=23; id=30
  984. $router_port=get_option($db,30);
  985. #32
  986. $org_name=get_option($db,32);
  987. #33
  988. $domain_name=get_option($db,33);
  989. #35
  990. $connections_history=get_option($db,35);
  991. #debug
  992. $debug=get_option($db,34);
  993. #log level
  994. $log_level = get_option($db,53);
  995. if ($debug) { $log_level = 255; }
  996. #urgent sync access
  997. $urgent_sync=get_option($db,50);
  998. $ignore_hotspot_dhcp_log = get_option($db,44);
  999. $ignore_update_dhcp_event = get_option($db,45);
  1000. $update_hostname_from_dhcp = get_option($db,46);
  1001. $history_log_day=get_option($db,47);
  1002. $history_syslog_day = get_option($db,48);
  1003. $history_trafstat_day = get_option($db,49);
  1004. my $ou = get_record_sql($db,"SELECT id FROM ou WHERE default_users = 1");
  1005. if (!$ou) { $default_user_ou_id = 0; } else { $default_user_ou_id = $ou->{'id'}; }
  1006. $ou = get_record_sql($db,"SELECT id FROM ou WHERE default_hotspot = 1 ");
  1007. if (!$ou) { $default_hotspot_ou_id = $default_user_ou_id; } else { $default_hotspot_ou_id = $ou->{'id'}; }
  1008. @subnets=get_records_sql($db,'SELECT * FROM subnets ORDER BY ip_int_start');
  1009. if (defined $office_networks) { undef $office_networks; }
  1010. if (defined $free_networks) { undef $free_networks; }
  1011. if (defined $vpn_networks) { undef $vpn_networks; }
  1012. if (defined $hotspot_networks) { undef $hotspot_networks; }
  1013. if (defined $all_networks) { undef $all_networks; }
  1014. $office_networks = new Net::Patricia;
  1015. $free_networks = new Net::Patricia;
  1016. $vpn_networks = new Net::Patricia;
  1017. $hotspot_networks = new Net::Patricia;
  1018. $all_networks = new Net::Patricia;
  1019. @office_network_list=();
  1020. @free_network_list=();
  1021. @free_network_list=();
  1022. @vpn_network_list=();
  1023. @hotspot_network_list=();
  1024. @all_network_list=();
  1025. foreach my $net (@subnets) {
  1026. next if (!$net->{subnet});
  1027. $subnets_ref{$net->{subnet}}=$net;
  1028. if ($net->{office}) {
  1029. push(@office_network_list,$net->{subnet});
  1030. $office_networks->add_string($net->{subnet},$net);
  1031. }
  1032. if ($net->{free}) {
  1033. push(@free_network_list,$net->{subnet});
  1034. $free_networks->add_string($net->{subnet},$net);
  1035. }
  1036. if ($net->{vpn}) {
  1037. push(@vpn_network_list,$net->{subnet});
  1038. $vpn_networks->add_string($net->{subnet},$net);
  1039. }
  1040. if ($net->{hotspot}) {
  1041. push(@hotspot_network_list,$net->{subnet});
  1042. push(@all_network_list,$net->{subnet});
  1043. $hotspot_networks->add_string($net->{subnet},$net);
  1044. }
  1045. push(@all_network_list,$net->{subnet});
  1046. $all_networks->add_string($net->{subnet},$net);
  1047. }
  1048. }
  1049. #---------------------------------------------------------------------------------------------------------------
  1050. sub Set_Variable {
  1051. my ($db, $name, $value, $timeshift) = @_;
  1052. $name //= $MY_NAME;
  1053. $value //= $$;
  1054. $timeshift //= 60;
  1055. Del_Variable($db, $name);
  1056. my $clean_time = time() + $timeshift;
  1057. my ($sec, $min, $hour, $day, $month, $year) = localtime($clean_time);
  1058. $month++;
  1059. $year += 1900;
  1060. my $clear_time_str = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $year, $month, $day, $hour, $min, $sec;
  1061. my $sql = "INSERT INTO variables (name, value, clear_time) VALUES (?, ?, ?)";
  1062. do_sql($db, $sql, $name, $value, $clear_time_str);
  1063. }
  1064. #---------------------------------------------------------------------------------------------------------------
  1065. sub Get_Variable {
  1066. my $db = shift;
  1067. my $name = shift || $MY_NAME;
  1068. my $variable = get_record_sql($db, 'SELECT value FROM variables WHERE name = ?', $name);
  1069. if ($variable and $variable->{'value'}) { return $variable->{'value'}; }
  1070. return;
  1071. }
  1072. #---------------------------------------------------------------------------------------------------------------
  1073. sub Del_Variable {
  1074. my ($db, $name) = @_;
  1075. $name //= $MY_NAME;
  1076. do_sql($db, "DELETE FROM variables WHERE name = ?", $name);
  1077. }
  1078. #---------------------------------------------------------------------------------------------------------------
  1079. sub clean_variables {
  1080. my ($db) = @_;
  1081. # 1. Clean temporary variables
  1082. my $now = time();
  1083. my ($sec, $min, $hour, $day, $month, $year) = localtime($now);
  1084. $month++;
  1085. $year += 1900;
  1086. my $now_str = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $year, $month, $day, $hour, $min, $sec;
  1087. do_sql($db, "DELETE FROM variables WHERE clear_time <= ?", $now_str);
  1088. # 2. Clean old AD computer cache
  1089. my $yesterday = DateTime->now(time_zone => 'local')->subtract(days => 1);
  1090. my $clean_str = $yesterday->strftime("%Y-%m-%d 00:00:00");
  1091. do_sql($db, "DELETE FROM ad_comp_cache WHERE last_found <= ?", $clean_str);
  1092. }
  1093. #---------------------------------------------------------------------------------------------------------------
  1094. #skip init for upgrade
  1095. if ($MY_NAME!~/upgrade.pl/) {
  1096. $dbh=init_db();
  1097. init_option($dbh);
  1098. clean_variables($dbh);
  1099. Set_Variable($dbh);
  1100. }
  1101. #warn "DBI driver name: ", $dbh->{Driver}->{Name}, "\n";
  1102. #warn "Full dbh class: ", ref($dbh), "\n";
  1103. 1;
  1104. }