database.pm 46 KB

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