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