database.pm 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103
  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. # Внутренняя функция для выполнения параметризованных запросов
  325. sub _execute_param {
  326. my ($db, $sql, $params, $options) = @_;
  327. return unless $db && $sql;
  328. # Логируем не-SELECT-запросы
  329. unless ($sql =~ /^\s*SELECT/i) {
  330. log_debug( $sql . ($params ? ' | params: [' . join(', ', map { defined $_ ? $_ : 'undef' } @$params) . ']' : ''));
  331. }
  332. # Переподключение
  333. unless (reconnect_db(\$db)) {
  334. log_error("No database connection available");
  335. return wantarray ? () : undef;
  336. }
  337. my $mode = $options->{mode} || 'execute';
  338. eval {
  339. my $sth = $db->prepare($sql) or die "Unable to prepare SQL [$sql]: " . $db->errstr;
  340. my $rv = $params ? $sth->execute(@$params) : $sth->execute();
  341. unless ($rv) {
  342. die "Unable to execute SQL [$sql]" . ($params ? " with params: [" . join(', ', @$params) . "]" : "") . ": " . $sth->errstr;
  343. }
  344. if ($mode eq 'single') {
  345. my $row = $sth->fetchrow_hashref();
  346. $sth->finish();
  347. return $row;
  348. }
  349. elsif ($mode eq 'array') {
  350. my @rows;
  351. while (my $row = $sth->fetchrow_hashref()) {
  352. push @rows, $row;
  353. }
  354. $sth->finish();
  355. return @rows;
  356. }
  357. elsif ($mode eq 'arrayref') {
  358. my $rows = $sth->fetchall_arrayref({});
  359. $sth->finish();
  360. return $rows;
  361. }
  362. elsif ($mode eq 'scalar') {
  363. my $row = $sth->fetchrow_arrayref();
  364. $sth->finish();
  365. return $row ? $row->[0] : undef;
  366. }
  367. elsif ($mode eq 'id') {
  368. if ($sql =~ /^\s*INSERT/i) {
  369. my $id;
  370. if ($config_ref{DBTYPE} and $config_ref{DBTYPE} eq 'mysql') {
  371. $id = $sth->{mysql_insertid};
  372. } else {
  373. ($id) = $db->selectrow_array("SELECT lastval()");
  374. }
  375. $sth->finish();
  376. return $id || 0;
  377. }
  378. $sth->finish();
  379. return 1;
  380. }
  381. else {
  382. $sth->finish();
  383. return 1;
  384. }
  385. };
  386. if ($@) {
  387. log_error("Error executing SQL [$sql]: " . $@);
  388. return wantarray ? () : undef;
  389. }
  390. }
  391. #---------------------------------------------------------------------------------------------------------------
  392. # Обновленная функция get_option с параметризованными запросами
  393. sub get_option {
  394. my $db = shift;
  395. my $option_id = shift;
  396. return if (!$option_id);
  397. return if (!$db);
  398. my $sql = q{
  399. SELECT
  400. COALESCE(c.value, co.default_value) as value,
  401. co.option_type
  402. FROM config_options co
  403. LEFT JOIN config c ON c.option_id = co.id AND c.option_id = ?
  404. WHERE co.id = ?
  405. LIMIT 1
  406. };
  407. my $record = get_record_sql($db, $sql, $option_id, $option_id);
  408. unless ($record) {
  409. log_error("Option ID $option_id not found in config_options table");
  410. return;
  411. }
  412. my $result = $record->{value};
  413. # Приводим к правильному типу
  414. if ($record->{option_type} =~ /^(int|bool)/i) {
  415. $result = $result * 1;
  416. }
  417. return $result;
  418. }
  419. #---------------------------------------------------------------------------------------------------------------
  420. sub get_count_records {
  421. my ($db, $table, $filter, @params) = @_;
  422. my $result = 0;
  423. return $result if (!$db);
  424. return $result if (!$table);
  425. my $sSQL='SELECT COUNT(*) as rec_cnt FROM '.$table;
  426. if ($filter) { $sSQL=$sSQL." WHERE ".$filter; }
  427. my $record = get_record_sql($db,$sSQL, @params);
  428. if ($record->{rec_cnt}) { $result = $record->{rec_cnt}; }
  429. return $result;
  430. }
  431. #---------------------------------------------------------------------------------------------------------------
  432. sub get_id_record {
  433. my ($db, $table, $filter, @params) = @_;
  434. my $result = 0;
  435. return $result if (!$db);
  436. return $result if (!$table);
  437. my $record = get_record_sql($db,"SELECT id FROM $table WHERE $filter", @params);
  438. if ($record->{id}) { $result = $record->{id}; }
  439. return $result;
  440. }
  441. #---------------------------------------------------------------------------------------------------------------
  442. sub get_records_sql {
  443. my ($db, $sql, @params) = @_;
  444. my @result;
  445. return @result if (!$db);
  446. return @result if (!$sql);
  447. unless (reconnect_db(\$db)) {
  448. log_error("No database connection available");
  449. return @result;
  450. }
  451. return _execute_param($db, $sql, \@params, { mode => 'array' });
  452. }
  453. #---------------------------------------------------------------------------------------------------------------
  454. sub get_record_sql {
  455. my ($db, $sql, @params) = @_;
  456. my @result;
  457. return @result if (!$db);
  458. return @result if (!$sql);
  459. # Добавляем LIMIT только если его еще нет в запросе
  460. if ($sql !~ /\bLIMIT\s+\d+/i && $sql !~ /\bFETCH\s+FIRST\s+\d+/i) {
  461. $sql .= ' LIMIT 1';
  462. }
  463. # Переподключение
  464. unless (reconnect_db(\$db)) {
  465. log_error("No database connection available");
  466. return;
  467. }
  468. return _execute_param($db, $sql, \@params, { mode => 'single' });
  469. }
  470. #---------------------------------------------------------------------------------------------------------------
  471. sub get_diff_rec {
  472. my ($db, $table, $record, $filter_sql, @filter_params) = @_;
  473. return unless $db && $table && $filter_sql;
  474. unless (reconnect_db(\$db)) {
  475. log_error("No database connection available");
  476. return;
  477. }
  478. my $old_record = get_record_sql($db,"SELECT * FROM $table WHERE $filter_sql",@filter_params);
  479. return unless $old_record;
  480. my $result;
  481. foreach my $field (keys %$record) {
  482. if (!$record->{$field}) { $record->{$field}=''; }
  483. if (!$old_record->{$field}) { $old_record->{$field}=''; }
  484. if ($record->{$field}!~/^$old_record->{$field}$/) { $result->{$field} = "$record->{$field} [ old: " . $old_record->{$field} . "]"; }
  485. }
  486. return hast_to_txt($result);
  487. }
  488. #---------------------------------------------------------------------------------------------------------------
  489. sub insert_record {
  490. my ($db, $table, $record) = @_;
  491. return unless $db && $table;
  492. unless (reconnect_db(\$db)) {
  493. log_error("No database connection available");
  494. return;
  495. }
  496. my $dns_changed = 0;
  497. my $rec_id = 0;
  498. if ($table eq "user_auth") {
  499. foreach my $field (keys %$record) {
  500. if (exists $acl_fields{$field}) { $record->{changed}="1"; }
  501. if (exists $dhcp_fields{$field}) { $record->{dhcp_changed}="1"; }
  502. if (exists $dns_fields{$field}) { $dns_changed=1; }
  503. }
  504. }
  505. my @insert_params;
  506. my $fields = '';
  507. my $values = '';
  508. my $new_str = '';
  509. foreach my $field (keys %$record) {
  510. my $val = defined $record->{$field} ? $record->{$field} : undef;
  511. # Экранируем имя поля в зависимости от СУБД
  512. my $quoted_field = $config_ref{DBTYPE} eq 'mysql'
  513. ? '`' . $field . '`'
  514. : '"' . $field . '"';
  515. $fields .= "$quoted_field, ";
  516. $values .= "?, ";
  517. push @insert_params, $val;
  518. # Для лога — безопасное представление
  519. my $log_val = defined $val ? substr($val, 0, 200) : 'NULL';
  520. $log_val =~ s/[^[:print:]]/_/g;
  521. $new_str .= " $field => $log_val,";
  522. }
  523. $fields =~ s/,\s*$//;
  524. $values =~ s/,\s*$//;
  525. my $sSQL = "INSERT INTO $table($fields) VALUES($values)";
  526. my $result = do_sql($db,$sSQL,@insert_params);
  527. if ($result) {
  528. $rec_id = $result if ($table eq "user_auth");
  529. $new_str='id: '.$result.' '.$new_str;
  530. if ($table eq 'user_auth_alias' and $dns_changed) {
  531. if ($record->{'alias'} and $record->{'alias'}!~/\.$/) {
  532. my $add_dns;
  533. $add_dns->{'name_type'}='CNAME';
  534. $add_dns->{'name'}=$record->{'alias'};
  535. $add_dns->{'value'}=get_dns_name($db,$record->{'auth_id'});
  536. $add_dns->{'operation_type'}='add';
  537. $add_dns->{'auth_id'}=$record->{'auth_id'};
  538. insert_record($db,'dns_queue',$add_dns);
  539. }
  540. }
  541. if ($table eq 'user_auth' and $dns_changed) {
  542. if ($record->{'dns_name'} and $record->{'ip'} and !$record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  543. my $add_dns;
  544. $add_dns->{'name_type'}='A';
  545. $add_dns->{'name'}=$record->{'dns_name'};
  546. $add_dns->{'value'}=$record->{'ip'};
  547. $add_dns->{'operation_type'}='add';
  548. $add_dns->{'auth_id'}=$result;
  549. insert_record($db,'dns_queue',$add_dns);
  550. }
  551. if ($record->{'dns_name'} and $record->{'ip'} and $record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  552. my $add_dns;
  553. $add_dns->{'name_type'}='PTR';
  554. $add_dns->{'name'}=$record->{'dns_name'};
  555. $add_dns->{'value'}=$record->{'ip'};
  556. $add_dns->{'operation_type'}='add';
  557. $add_dns->{'auth_id'}=$result;
  558. insert_record($db,'dns_queue',$add_dns);
  559. }
  560. }
  561. }
  562. db_log_debug($db,'Add record to table '.$table.' '.$new_str,$rec_id);
  563. return $result;
  564. }
  565. #---------------------------------------------------------------------------------------------------------------
  566. sub update_record {
  567. my ($db, $table, $record, $filter_sql, @filter_params) = @_;
  568. return unless $db && $table && $filter_sql;
  569. unless (reconnect_db(\$db)) {
  570. log_error("No database connection available");
  571. return;
  572. }
  573. my $select_sql = "SELECT * FROM $table WHERE $filter_sql";
  574. my $old_record = get_record_sql($db, $select_sql, @filter_params);
  575. return unless $old_record;
  576. my @update_params;
  577. my $set_clause = '';
  578. my $dns_changed = 0;
  579. my $rec_id = $old_record->{id} || 0;
  580. if ($table eq "user_auth") {
  581. $rec_id = $old_record->{'id'} if ($old_record->{'id'});
  582. #disable update field 'created_by'
  583. if ($old_record->{'created_by'} and exists ($record->{'created_by'})) { delete $record->{'created_by'}; }
  584. foreach my $field (keys %$record) {
  585. if (exists $acl_fields{$field}) { $record->{changed}="1"; }
  586. if (exists $dhcp_fields{$field}) { $record->{dhcp_changed}="1"; }
  587. if (exists $dns_fields{$field}) { $dns_changed=1; }
  588. }
  589. }
  590. my $diff = '';
  591. for my $field (keys %$record) {
  592. my $old_val = defined $old_record->{$field} ? $old_record->{$field} : '';
  593. my $new_val = defined $record->{$field} ? $record->{$field} : '';
  594. if ($new_val ne $old_val) {
  595. $diff .= " $field => $new_val (old: $old_val),";
  596. $set_clause .= " $field = ?, ";
  597. push @update_params, $new_val;
  598. }
  599. }
  600. return 1 unless $set_clause;
  601. # Добавляем служебные поля
  602. if ($table eq 'user_auth') {
  603. $set_clause .= "changed_time = ?, ";
  604. push @update_params, GetNowTime();
  605. }
  606. $set_clause =~ s/,\s*$//;
  607. $diff =~ s/,\s*$//;
  608. if ($table eq 'user_auth') {
  609. if ($dns_changed) {
  610. my $del_dns;
  611. if ($old_record->{'dns_name'} and $old_record->{'ip'} and !$old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
  612. $del_dns->{'name_type'}='A';
  613. $del_dns->{'name'}=$old_record->{'dns_name'};
  614. $del_dns->{'value'}=$old_record->{'ip'};
  615. $del_dns->{'operation_type'}='del';
  616. if ($rec_id) { $del_dns->{'auth_id'}=$rec_id; }
  617. insert_record($db,'dns_queue',$del_dns);
  618. }
  619. if ($old_record->{'dns_name'} and $old_record->{'ip'} and $old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
  620. $del_dns->{'name_type'}='PTR';
  621. $del_dns->{'name'}=$old_record->{'dns_name'};
  622. $del_dns->{'value'}=$old_record->{'ip'};
  623. $del_dns->{'operation_type'}='del';
  624. if ($rec_id) { $del_dns->{'auth_id'}=$rec_id; }
  625. insert_record($db,'dns_queue',$del_dns);
  626. }
  627. my $new_dns;
  628. my $dns_rec_ip = $old_record->{ip};
  629. my $dns_rec_name = $old_record->{dns_name};
  630. if ($record->{'dns_name'}) { $dns_rec_name = $record->{'dns_name'}; }
  631. if ($record->{'ip'}) { $dns_rec_ip = $record->{'ip'}; }
  632. if ($dns_rec_name and $dns_rec_ip and !$record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  633. $new_dns->{'name_type'}='A';
  634. $new_dns->{'name'}=$dns_rec_name;
  635. $new_dns->{'value'}=$dns_rec_ip;
  636. $new_dns->{'operation_type'}='add';
  637. if ($rec_id) { $new_dns->{'auth_id'}=$rec_id; }
  638. insert_record($db,'dns_queue',$new_dns);
  639. }
  640. if ($dns_rec_name and $dns_rec_ip and $record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  641. $new_dns->{'name_type'}='PTR';
  642. $new_dns->{'name'}=$dns_rec_name;
  643. $new_dns->{'value'}=$dns_rec_ip;
  644. $new_dns->{'operation_type'}='add';
  645. if ($rec_id) { $new_dns->{'auth_id'}=$rec_id; }
  646. insert_record($db,'dns_queue',$new_dns);
  647. }
  648. }
  649. }
  650. if ($table eq 'user_auth_alias') {
  651. if ($dns_changed) {
  652. my $del_dns;
  653. if ($old_record->{'alias'} and $old_record->{'alias'}!~/\.$/) {
  654. $del_dns->{'name_type'}='CNAME';
  655. $del_dns->{'name'}=$old_record->{'alias'};
  656. $del_dns->{'operation_type'}='del';
  657. $del_dns->{'value'}=get_dns_name($db,$old_record->{auth_id});
  658. $del_dns->{'auth_id'}=$old_record->{auth_id};
  659. insert_record($db,'dns_queue',$del_dns);
  660. }
  661. my $new_dns;
  662. my $dns_rec_name = $old_record->{alias};
  663. if ($record->{'alias'}) { $dns_rec_name = $record->{'alias'}; }
  664. if ($dns_rec_name and $record->{'alias'}!~/\.$/) {
  665. $new_dns->{'name_type'}='CNAME';
  666. $new_dns->{'name'}=$dns_rec_name;
  667. $new_dns->{'operation_type'}='add';
  668. $new_dns->{'value'}=get_dns_name($db,$old_record->{auth_id});
  669. $new_dns->{'auth_id'}=$rec_id;
  670. insert_record($db,'dns_queue',$new_dns);
  671. }
  672. }
  673. }
  674. # Формируем полный список параметров: сначала SET, потом WHERE
  675. my @all_params = (@update_params, @filter_params);
  676. my $update_sql = "UPDATE $table SET $set_clause WHERE $filter_sql";
  677. db_log_debug($db, "Change table $table for $filter_sql set: $diff", $rec_id);
  678. return do_sql($db, $update_sql, @all_params);
  679. }
  680. #---------------------------------------------------------------------------------------------------------------
  681. sub delete_record {
  682. my ($db, $table, $filter_sql, @filter_params) = @_;
  683. return unless $db && $table && $filter_sql;
  684. unless (reconnect_db(\$db)) {
  685. log_error("No database connection available");
  686. return;
  687. }
  688. my $select_sql = "SELECT * FROM $table WHERE $filter_sql";
  689. my $old_record = get_record_sql($db, $select_sql, @filter_params);
  690. return unless $old_record;
  691. my $rec_id = 0;
  692. my $diff='';
  693. foreach my $field (keys %$old_record) {
  694. if (!$old_record->{$field}) { $old_record->{$field}=''; }
  695. $diff = $diff." $field => $old_record->{$field},";
  696. }
  697. $diff=~s/,\s*$//;
  698. if ($table eq 'user_auth') {
  699. $rec_id = $old_record->{'id'} if ($old_record->{'id'});
  700. }
  701. db_log_debug($db,'Delete record from table '.$table.' value: '.$diff, $rec_id);
  702. #never delete user ip record!
  703. if ($table eq 'user_auth') {
  704. my $sSQL = "UPDATE user_auth SET changed = 1, deleted = 1, changed_time = ? WHERE $filter_sql";
  705. my $ret = do_sql($db, $sSQL, GetNowTime(), @filter_params);
  706. if ($old_record->{'dns_name'} and $old_record->{'ip'} and !$old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
  707. my $del_dns;
  708. $del_dns->{'name_type'}='A';
  709. $del_dns->{'name'}=$old_record->{'dns_name'};
  710. $del_dns->{'value'}=$old_record->{'ip'};
  711. $del_dns->{'operation_type'}='del';
  712. $del_dns->{'auth_id'}=$old_record->{'id'};
  713. insert_record($db,'dns_queue',$del_dns);
  714. }
  715. if ($old_record->{'dns_name'} and $old_record->{'ip'} and $old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
  716. my $del_dns;
  717. $del_dns->{'name_type'}='PTR';
  718. $del_dns->{'name'}=$old_record->{'dns_name'};
  719. $del_dns->{'value'}=$old_record->{'ip'};
  720. $del_dns->{'operation_type'}='del';
  721. $del_dns->{'auth_id'}=$old_record->{'id'};
  722. insert_record($db,'dns_queue',$del_dns);
  723. }
  724. return $ret;
  725. }
  726. if ($table eq 'user_list' and $old_record->{'permanent'}) { return; }
  727. if ($table eq 'user_auth_alias') {
  728. if ($old_record->{'alias'} and $old_record->{'auth_id'} and $old_record->{'alias'}!~/\.$/) {
  729. my $del_dns;
  730. $del_dns->{'name_type'}='CNAME';
  731. $del_dns->{'name'}=$old_record->{'alias'};
  732. $del_dns->{'value'}=get_dns_name($db,$old_record->{'auth_id'});
  733. $del_dns->{'operation_type'}='del';
  734. $del_dns->{'auth_id'}=$old_record->{'auth_id'};
  735. insert_record($db,'dns_queue',$del_dns);
  736. }
  737. }
  738. my $sSQL = "DELETE FROM ".$table." WHERE ".$filter_sql;
  739. return do_sql($db,$sSQL,@filter_params);
  740. }
  741. #---------------------------------------------------------------------------------------------------------------
  742. sub init_option {
  743. my $db=shift;
  744. $last_refresh_config = time();
  745. $config_ref{version}='';
  746. my $version_record = get_record_sql($db,"SELECT version FROM version WHERE version is NOT NULL");
  747. if ($version_record) { $config_ref{version}=$version_record->{version}; }
  748. $config_ref{self_ip} = '127.0.0.1';
  749. if ($DBHOST ne '127.0.0.1') {
  750. my $ip_route = qx(ip r get $DBHOST 2>&1 | head -1);
  751. if ($? == 0) {
  752. if ($ip_route =~ /src\s+(\d+\.\d+\.\d+\.\d+)/) { $config_ref{self_ip} = $1; }
  753. }
  754. }
  755. $config_ref{dbh}=$db;
  756. $config_ref{save_detail}=get_option($db,23);
  757. $config_ref{add_unknown_user}=get_option($db,22);
  758. $config_ref{dhcp_server}=get_option($db,5);
  759. $config_ref{snmp_default_version}=get_option($db,9);
  760. $config_ref{snmp_default_community}=get_option($db,11);
  761. $config_ref{KB}=get_option($db,1);
  762. if ($config_ref{KB} ==0) { $config_ref{KB}=1000; }
  763. if ($config_ref{KB} ==1) { $config_ref{KB}=1024; }
  764. $config_ref{admin_email}=get_option($db,21);
  765. $config_ref{sender_email}=get_option($db,52);
  766. $config_ref{send_email}=get_option($db,51);
  767. $config_ref{history}=get_option($db,26);
  768. $config_ref{history_dhcp}=get_option($db,27);
  769. $config_ref{router_login}=get_option($db,28);
  770. $config_ref{router_password}=get_option($db,29);
  771. $config_ref{router_port}=get_option($db,30);
  772. $config_ref{org_name}=get_option($db,32);
  773. $config_ref{domain_name}=get_option($db,33);
  774. $config_ref{connections_history}=get_option($db,35);
  775. $config_ref{debug}=get_option($db,34);
  776. $config_ref{log_level} = get_option($db,53);
  777. if ($config_ref{debug}) { $config_ref{log_level} = 255; }
  778. $config_ref{urgent_sync}=get_option($db,50);
  779. $config_ref{ignore_hotspot_dhcp_log} = get_option($db,44);
  780. $config_ref{ignore_update_dhcp_event} = get_option($db,45);
  781. $config_ref{update_hostname_from_dhcp} = get_option($db,46);
  782. $config_ref{history_log_day}=get_option($db,47);
  783. $config_ref{history_syslog_day} = get_option($db,48);
  784. $config_ref{history_trafstat_day} = get_option($db,49);
  785. $config_ref{enable_quotes} = get_option($db,54);
  786. $config_ref{netflow_step} = get_option($db,55);
  787. $config_ref{traffic_ipstat_history} = get_option($db,56);
  788. $config_ref{nagios_url} = get_option($db,57);
  789. $config_ref{cacti_url} = get_option($db,58);
  790. $config_ref{torrus_url} = get_option($db,59);
  791. $config_ref{wiki_url} = get_option($db,60);
  792. $config_ref{stat_url} = get_option($db,62);
  793. $config_ref{wiki_path} = get_option($db,61);
  794. $config_ref{auto_mac_rule} = get_option($db,64);
  795. #network configuration mode
  796. $config_ref{config_mode}=get_option($db,68);
  797. #auto clean old user record
  798. $config_ref{clean_empty_user}=get_option($db,69);
  799. #dns_server_type
  800. $config_ref{dns_server}=get_option($db,3);
  801. $config_ref{dns_server_type}=get_option($db,70);
  802. $config_ref{enable_dns_updates}=get_option($db,71);
  803. #$save_detail = 1; id=23
  804. $save_detail=get_option($db,23);
  805. #$add_unknown_user = 1; id=22
  806. $add_unknown_user=get_option($db,22);
  807. #$dns_server='192.168.2.12'; id=3
  808. $dns_server=get_option($db,3);
  809. #$dhcp_server='192.168.2.12'; id=5
  810. $dhcp_server=get_option($db,5);
  811. #$snmp_default_version='2'; id=9
  812. $snmp_default_version=get_option($db,9);
  813. #$snmp_default_community='public'; id=11
  814. $snmp_default_community=get_option($db,11);
  815. #$KB=1024; id=1
  816. $KB=$config_ref{KB};
  817. #$admin_email; id=21
  818. $admin_email=get_option($db,21);
  819. #sender email
  820. $sender_email=get_option($db,52);
  821. #send email
  822. $send_email=get_option($db,51);
  823. #$history=15; id=26
  824. $history=get_option($db,26);
  825. #$history_dhcp=7; id=27
  826. $history_dhcp=get_option($db,27);
  827. #$router_login="admin"; id=28
  828. $router_login=get_option($db,28);
  829. #$router_password="admin"; id=29
  830. $router_password=get_option($db,29);
  831. #$router_port=23; id=30
  832. $router_port=get_option($db,30);
  833. #32
  834. $org_name=get_option($db,32);
  835. #33
  836. $domain_name=get_option($db,33);
  837. #35
  838. $connections_history=get_option($db,35);
  839. #debug
  840. $debug=get_option($db,34);
  841. #log level
  842. $log_level = get_option($db,53);
  843. if ($debug) { $log_level = 255; }
  844. #urgent sync access
  845. $urgent_sync=get_option($db,50);
  846. $ignore_hotspot_dhcp_log = get_option($db,44);
  847. $ignore_update_dhcp_event = get_option($db,45);
  848. $update_hostname_from_dhcp = get_option($db,46);
  849. $history_log_day=get_option($db,47);
  850. $history_syslog_day = get_option($db,48);
  851. $history_trafstat_day = get_option($db,49);
  852. my $ou = get_record_sql($db,"SELECT id FROM ou WHERE default_users = 1");
  853. if (!$ou) { $default_user_ou_id = 0; } else { $default_user_ou_id = $ou->{'id'}; }
  854. $ou = get_record_sql($db,"SELECT id FROM ou WHERE default_hotspot = 1 ");
  855. if (!$ou) { $default_hotspot_ou_id = $default_user_ou_id; } else { $default_hotspot_ou_id = $ou->{'id'}; }
  856. @subnets=get_records_sql($db,'SELECT * FROM subnets ORDER BY ip_int_start');
  857. if (defined $office_networks) { undef $office_networks; }
  858. if (defined $free_networks) { undef $free_networks; }
  859. if (defined $vpn_networks) { undef $vpn_networks; }
  860. if (defined $hotspot_networks) { undef $hotspot_networks; }
  861. if (defined $all_networks) { undef $all_networks; }
  862. $office_networks = new Net::Patricia;
  863. $free_networks = new Net::Patricia;
  864. $vpn_networks = new Net::Patricia;
  865. $hotspot_networks = new Net::Patricia;
  866. $all_networks = new Net::Patricia;
  867. @office_network_list=();
  868. @free_network_list=();
  869. @free_network_list=();
  870. @vpn_network_list=();
  871. @hotspot_network_list=();
  872. @all_network_list=();
  873. foreach my $net (@subnets) {
  874. next if (!$net->{subnet});
  875. $subnets_ref{$net->{subnet}}=$net;
  876. if ($net->{office}) {
  877. push(@office_network_list,$net->{subnet});
  878. $office_networks->add_string($net->{subnet},$net);
  879. }
  880. if ($net->{free}) {
  881. push(@free_network_list,$net->{subnet});
  882. $free_networks->add_string($net->{subnet},$net);
  883. }
  884. if ($net->{vpn}) {
  885. push(@vpn_network_list,$net->{subnet});
  886. $vpn_networks->add_string($net->{subnet},$net);
  887. }
  888. if ($net->{hotspot}) {
  889. push(@hotspot_network_list,$net->{subnet});
  890. push(@all_network_list,$net->{subnet});
  891. $hotspot_networks->add_string($net->{subnet},$net);
  892. }
  893. push(@all_network_list,$net->{subnet});
  894. $all_networks->add_string($net->{subnet},$net);
  895. }
  896. }
  897. #---------------------------------------------------------------------------------------------------------------
  898. sub Set_Variable {
  899. my ($db, $name, $value, $timeshift) = @_;
  900. $name //= $MY_NAME;
  901. $value //= $$;
  902. $timeshift //= 60;
  903. Del_Variable($db, $name);
  904. my $clean_time = time() + $timeshift;
  905. my ($sec, $min, $hour, $day, $month, $year) = localtime($clean_time);
  906. $month++;
  907. $year += 1900;
  908. my $clear_time_str = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $year, $month, $day, $hour, $min, $sec;
  909. my $sql = "INSERT INTO variables (name, value, clear_time) VALUES (?, ?, ?)";
  910. do_sql($db, $sql, $name, $value, $clear_time_str);
  911. }
  912. #---------------------------------------------------------------------------------------------------------------
  913. sub Get_Variable {
  914. my $db = shift;
  915. my $name = shift || $MY_NAME;
  916. my $variable = get_record_sql($db, 'SELECT value FROM variables WHERE name = ?', $name);
  917. if ($variable and $variable->{'value'}) { return $variable->{'value'}; }
  918. return;
  919. }
  920. #---------------------------------------------------------------------------------------------------------------
  921. sub Del_Variable {
  922. my ($db, $name) = @_;
  923. $name //= $MY_NAME;
  924. do_sql($db, "DELETE FROM variables WHERE name = ?", $name);
  925. }
  926. #---------------------------------------------------------------------------------------------------------------
  927. sub clean_variables {
  928. my ($db) = @_;
  929. # 1. Clean temporary variables
  930. my $now = time();
  931. my ($sec, $min, $hour, $day, $month, $year) = localtime($now);
  932. $month++;
  933. $year += 1900;
  934. my $now_str = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $year, $month, $day, $hour, $min, $sec;
  935. do_sql($db, "DELETE FROM variables WHERE clear_time <= ?", $now_str);
  936. # 2. Clean old AD computer cache
  937. my $yesterday = DateTime->now(time_zone => 'local')->subtract(days => 1);
  938. my $clean_str = $yesterday->strftime("%Y-%m-%d 00:00:00");
  939. do_sql($db, "DELETE FROM ad_comp_cache WHERE last_found <= ?", $clean_str);
  940. }
  941. #---------------------------------------------------------------------------------------------------------------
  942. #skip init for upgrade
  943. if ($MY_NAME!~/upgrade.pl/) {
  944. $dbh=init_db();
  945. init_option($dbh);
  946. clean_variables($dbh);
  947. Set_Variable($dbh);
  948. }
  949. 1;
  950. }