database.pm 34 KB

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