database.pm 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112
  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. binmode($fh, ':utf8');
  138. for my $row (@$data) {
  139. next unless $row && @$row;
  140. my $line = 'NULL'; # автоинкремент
  141. for my $val (@$row) {
  142. $line .= defined($val) ? ',' . $val : ',NULL';
  143. }
  144. print $fh $line . "\r\n";
  145. }
  146. close $fh;
  147. my $query = "LOAD DATA LOCAL INFILE '" . $fh->filename . "' INTO TABLE `$table` FIELDS TERMINATED BY ',' LINES TERMINATED BY '\r\n'";
  148. $db->do($query);
  149. } else {
  150. # PostgreSQL: используем COPY ... FROM STDIN
  151. my $copy_sql = "COPY $table FROM STDIN WITH (FORMAT CSV, DELIMITER ',', NULL 'NULL')";
  152. $db->do($copy_sql); # Переключает соединение в режим копирования
  153. for my $row (@$data) {
  154. next unless $row && @$row;
  155. my $line = 'NULL'; # автоинкремент
  156. for my $val (@$row) {
  157. $line .= defined($val) ? ',' . $val : ',NULL';
  158. }
  159. $line .= "\n";
  160. $db->pg_put_copy_data($line);
  161. }
  162. $db->pg_put_copy_end(); # Завершаем копирование
  163. }
  164. $db->disconnect();
  165. }
  166. #---------------------------------------------------------------------------------------------------------------
  167. sub reconnect_db {
  168. my $db_ref = shift;
  169. # Если соединение активно, ничего не делаем
  170. if ($$db_ref && $$db_ref->ping) {
  171. return 1;
  172. }
  173. # Переподключаемся
  174. eval {
  175. # Закрываем старое соединение если есть
  176. if ($$db_ref) {
  177. $$db_ref->disconnect;
  178. $$db_ref = undef;
  179. }
  180. # Создаем новое соединение
  181. $$db_ref = init_db();
  182. # Проверяем что соединение установлено
  183. unless ($$db_ref && $$db_ref->ping) {
  184. die "Failed to establish database connection";
  185. }
  186. 1; # возвращаем истину при успехе
  187. } or do {
  188. my $error = $@ || 'Unknown error';
  189. warn "Database reconnection failed: $error";
  190. $$db_ref = undef;
  191. return 0;
  192. };
  193. return 1;
  194. }
  195. #---------------------------------------------------------------------------------------------------------------
  196. sub write_db_log {
  197. my $db=shift;
  198. my $msg=shift;
  199. my $level = shift || $L_VERBOSE;
  200. my $auth_id = shift || 0;
  201. return if (!$db);
  202. return if (!$msg);
  203. $msg=~s/[\'\"]//g;
  204. my $db_log = 0;
  205. # Переподключение
  206. unless (reconnect_db(\$db)) {
  207. log_error("No database connection available");
  208. $db_log = 0;
  209. }
  210. if ($level eq $L_ERROR and $log_level >= $L_ERROR) { log_error($msg); $db_log = 1; }
  211. if ($level eq $L_WARNING and $log_level >= $L_WARNING) { log_warning($msg); $db_log = 1; }
  212. if ($level eq $L_INFO and $log_level >= $L_INFO) { log_info($msg); $db_log = 1; }
  213. if ($level eq $L_VERBOSE and $log_level >= $L_VERBOSE) { log_verbose($msg); $db_log = 1; }
  214. if ($level eq $L_DEBUG and $log_level >= $L_DEBUG) { log_debug($msg); return; }
  215. if ($db_log) {
  216. #my $new_id = do_sql($dbh, 'INSERT INTO user_list (login) VALUES (?)', 'Ivan');
  217. do_sql($db,'INSERT INTO worklog(customer,message,level,auth_id,ip) VALUES( ?, ?, ?, ?, ?)',$MY_NAME,$msg,$level,$auth_id,$config_ref{self_ip});
  218. }
  219. }
  220. #---------------------------------------------------------------------------------------------------------------
  221. sub db_log_debug {
  222. my $db = shift;
  223. my $msg = shift;
  224. my $id = shift;
  225. if ($debug) { log_debug($msg); }
  226. }
  227. #---------------------------------------------------------------------------------------------------------------
  228. sub db_log_error {
  229. my $db = shift;
  230. my $msg = shift;
  231. if ($log_level >= $L_ERROR) {
  232. sendEmail("ERROR! ".get_first_line($msg),$msg,1);
  233. write_db_log($db,$msg,$L_ERROR);
  234. }
  235. }
  236. #---------------------------------------------------------------------------------------------------------------
  237. sub db_log_info {
  238. my $db = shift;
  239. my $msg = shift;
  240. my $id = shift;
  241. if ($log_level >= $L_INFO) { write_db_log($db,$msg,$L_INFO,$id); }
  242. }
  243. #---------------------------------------------------------------------------------------------------------------
  244. sub db_log_verbose {
  245. my $db = shift;
  246. my $msg = shift;
  247. my $id = shift;
  248. if ($log_level >= $L_VERBOSE) { write_db_log($db,$msg,$L_VERBOSE,$id); }
  249. }
  250. #---------------------------------------------------------------------------------------------------------------
  251. sub db_log_warning {
  252. my $db = shift;
  253. my $msg = shift;
  254. my $id = shift;
  255. if ($log_level >= $L_WARNING) { write_db_log($db,$msg,$L_WARNING,$id); }
  256. }
  257. #---------------------------------------------------------------------------------------------------------------
  258. sub init_db {
  259. # Create new database handle. If we can't connect, die()
  260. my $db;
  261. if ($config_ref{DBTYPE} eq 'mysql') {
  262. $db = DBI->connect("dbi:mysql:database=$DBNAME;host=$DBHOST","$DBUSER","$DBPASS",
  263. { RaiseError => 0, AutoCommit => 1, mysql_enable_utf8 => 1 });
  264. if ( !defined $db ) { die "Cannot connect to MySQL server: $DBI::errstr\n"; }
  265. $db->do('SET NAMES utf8mb4');
  266. } else {
  267. $db = DBI->connect("dbi:Pg:dbname=$DBNAME;host=$DBHOST","$DBUSER","$DBPASS",
  268. { RaiseError => 0, AutoCommit => 1, pg_enable_utf8 => 1, pg_server_prepare => 0 });
  269. if ( !defined $db ) { die "Cannot connect to PostgreSQL server: $DBI::errstr\n"; }
  270. }
  271. return $db;
  272. }
  273. #---------------------------------------------------------------------------------------------------------------
  274. sub do_sql {
  275. my ($db, $sql, @bind_values) = @_;
  276. return 0 unless $db;
  277. return 0 unless $sql;
  278. unless (reconnect_db(\$db)) {
  279. log_error("No database connection available for SQL: $sql");
  280. return 0;
  281. }
  282. # Логируем не-SELECT-запросы
  283. log_debug( $sql . (@bind_values ? ' | bind: [' . join(', ', map { defined $_ ? $_ : 'undef' } @bind_values) . ']' : '')) unless $sql =~ /^select /i;
  284. # Подготовка запроса
  285. my $sth = $db->prepare($sql) or do {
  286. log_error("Unable to prepare SQL [$sql]: " . $db->errstr);
  287. return 0;
  288. };
  289. # Выполнение запроса
  290. my $rv;
  291. if (@bind_values) {
  292. $rv = $sth->execute(@bind_values) or do {
  293. log_error("Unable to execute SQL [$sql] with bind: [" . join(', ', map { defined $_ ? $_ : 'undef' } @bind_values) . "]: " . $sth->errstr);
  294. return 0;
  295. };
  296. } else {
  297. $rv = $sth->execute() or do {
  298. log_error("Unable to execute SQL [$sql]: " . $sth->errstr);
  299. return 0;
  300. };
  301. }
  302. # Обработка результатов по типу запроса
  303. if ($sql =~ /^insert/i) {
  304. my $id;
  305. if ($config_ref{DBTYPE} and $config_ref{DBTYPE} eq 'mysql') {
  306. $id = $sth->{mysql_insertid};
  307. } else {
  308. ($id) = $db->selectrow_array("SELECT lastval()");
  309. }
  310. $sth->finish();
  311. return $id || 0; # Возвращаем ID или 0 если ID нет
  312. }
  313. elsif ($sql =~ /^select /i) {
  314. my $data = $sth->fetchall_arrayref({});
  315. $sth->finish();
  316. return $data; # возвращаем ссылку на массив
  317. }
  318. else {
  319. # UPDATE, DELETE, CREATE, ALTER и т.д.
  320. $sth->finish();
  321. return 1;
  322. }
  323. }
  324. #---------------------------------------------------------------------------------------------------------------
  325. # Обновленная функция get_option с параметризованными запросами
  326. sub get_option {
  327. my $db = shift;
  328. my $option_id = shift;
  329. return if (!$option_id);
  330. return if (!$db);
  331. my $sql = q{
  332. SELECT
  333. COALESCE(c.value, co.default_value) AS value,
  334. co.option_type
  335. FROM config_options co
  336. LEFT JOIN config c ON c.option_id = co.id
  337. WHERE co.id = ?
  338. };
  339. my $record = get_record_sql($db, $sql, $option_id);
  340. unless ($record) {
  341. log_error("Option ID $option_id not found in config_options table");
  342. return;
  343. }
  344. return $record->{value};
  345. }
  346. #---------------------------------------------------------------------------------------------------------------
  347. # Внутренняя функция для выполнения параметризованных запросов
  348. sub _execute_param {
  349. my ($db, $sql, $params, $options) = @_;
  350. return unless $db && $sql;
  351. # Логируем не-SELECT-запросы
  352. unless ($sql =~ /^\s*SELECT/i) {
  353. log_debug( $sql . ($params ? ' | params: [' . join(', ', map { defined $_ ? $_ : 'undef' } @$params) . ']' : ''));
  354. }
  355. # Переподключение
  356. unless (reconnect_db(\$db)) {
  357. log_error("No database connection available");
  358. return wantarray ? () : undef;
  359. }
  360. my $mode = $options->{mode} || 'execute';
  361. my $sth = $db->prepare($sql) or do {
  362. log_error("Unable to prepare SQL [$sql]: " . $db->errstr);
  363. return wantarray ? () : undef;
  364. };
  365. my $rv = $params ? $sth->execute(@$params) : $sth->execute();
  366. unless ($rv) {
  367. log_error("Unable to execute SQL [$sql]" . ($params ? " with params: [" . join(', ', @$params) . "]" : "") . ": " . $sth->errstr);
  368. $sth->finish();
  369. return wantarray ? () : undef;
  370. }
  371. if ($mode eq 'single') {
  372. my $row = $sth->fetchrow_hashref();
  373. $sth->finish();
  374. return $row;
  375. }
  376. elsif ($mode eq 'array') {
  377. my @rows;
  378. while (my $row = $sth->fetchrow_hashref()) {
  379. push @rows, $row;
  380. }
  381. $sth->finish();
  382. return \@rows;
  383. }
  384. elsif ($mode eq 'arrayref') {
  385. my $rows = $sth->fetchall_arrayref({});
  386. $sth->finish();
  387. return $rows;
  388. }
  389. elsif ($mode eq 'scalar') {
  390. my $row = $sth->fetchrow_arrayref();
  391. $sth->finish();
  392. return $row ? $row->[0] : undef;
  393. }
  394. elsif ($mode eq 'id') {
  395. if ($sql =~ /^\s*INSERT/i) {
  396. my $id;
  397. if ($config_ref{DBTYPE} and $config_ref{DBTYPE} eq 'mysql') {
  398. $id = $sth->{mysql_insertid};
  399. } else {
  400. ($id) = $db->selectrow_array("SELECT lastval()");
  401. }
  402. $sth->finish();
  403. return $id || 0;
  404. }
  405. $sth->finish();
  406. return 1;
  407. }
  408. else {
  409. $sth->finish();
  410. return 1;
  411. }
  412. }
  413. #---------------------------------------------------------------------------------------------------------------
  414. sub get_records_sql {
  415. my ($db, $sql, @params) = @_;
  416. my @result;
  417. return @result if (!$db);
  418. return @result if (!$sql);
  419. unless (reconnect_db(\$db)) {
  420. log_error("No database connection available");
  421. return @result;
  422. }
  423. my $result_ref = _execute_param($db, $sql, \@params, { mode => 'array' });
  424. if (ref($result_ref) eq 'ARRAY') {
  425. @result = @$result_ref;
  426. }
  427. return @result;
  428. }
  429. #---------------------------------------------------------------------------------------------------------------
  430. sub get_record_sql {
  431. my ($db, $sql, @params) = @_;
  432. my @result;
  433. return @result if (!$db);
  434. return @result if (!$sql);
  435. # Добавляем LIMIT только если его еще нет в запросе
  436. if ($sql !~ /\bLIMIT\s+\d+/i && $sql !~ /\bFETCH\s+FIRST\s+\d+/i) {
  437. $sql .= ' LIMIT 1';
  438. }
  439. # Переподключение
  440. unless (reconnect_db(\$db)) {
  441. log_error("No database connection available");
  442. return;
  443. }
  444. return _execute_param($db, $sql, \@params, { mode => 'single' });
  445. }
  446. #---------------------------------------------------------------------------------------------------------------
  447. sub get_count_records {
  448. my ($db, $table, $filter, @params) = @_;
  449. my $result = 0;
  450. return $result if (!$db);
  451. return $result if (!$table);
  452. my $sSQL='SELECT COUNT(*) as rec_cnt FROM '.$table;
  453. if ($filter) { $sSQL=$sSQL." WHERE ".$filter; }
  454. my $record = get_record_sql($db,$sSQL, @params);
  455. if ($record->{rec_cnt}) { $result = $record->{rec_cnt}; }
  456. return $result;
  457. }
  458. #---------------------------------------------------------------------------------------------------------------
  459. sub get_id_record {
  460. my ($db, $table, $filter, @params) = @_;
  461. my $result = 0;
  462. return $result if (!$db);
  463. return $result if (!$table);
  464. my $record = get_record_sql($db,"SELECT id FROM $table WHERE $filter", @params);
  465. if ($record->{id}) { $result = $record->{id}; }
  466. return $result;
  467. }
  468. #---------------------------------------------------------------------------------------------------------------
  469. sub get_diff_rec {
  470. my ($db, $table, $record, $filter_sql, @filter_params) = @_;
  471. return unless $db && $table && $filter_sql;
  472. unless (reconnect_db(\$db)) {
  473. log_error("No database connection available");
  474. return;
  475. }
  476. my $old_record = get_record_sql($db,"SELECT * FROM $table WHERE $filter_sql",@filter_params);
  477. return unless $old_record;
  478. my $result;
  479. foreach my $field (keys %$record) {
  480. if (!$record->{$field}) { $record->{$field}=''; }
  481. if (!$old_record->{$field}) { $old_record->{$field}=''; }
  482. if ($record->{$field}!~/^$old_record->{$field}$/) { $result->{$field} = "$record->{$field} [ old: " . $old_record->{$field} . "]"; }
  483. }
  484. return hast_to_txt($result);
  485. }
  486. #---------------------------------------------------------------------------------------------------------------
  487. sub insert_record {
  488. my ($db, $table, $record) = @_;
  489. return unless $db && $table;
  490. unless (reconnect_db(\$db)) {
  491. log_error("No database connection available");
  492. return;
  493. }
  494. my $dns_changed = 0;
  495. my $rec_id = 0;
  496. if ($table eq "user_auth") {
  497. foreach my $field (keys %$record) {
  498. if (exists $acl_fields{$field}) { $record->{changed}="1"; }
  499. if (exists $dhcp_fields{$field}) { $record->{dhcp_changed}="1"; }
  500. if (exists $dns_fields{$field}) { $dns_changed=1; }
  501. }
  502. }
  503. my @insert_params;
  504. my $fields = '';
  505. my $values = '';
  506. my $new_str = '';
  507. foreach my $field (keys %$record) {
  508. my $val = defined $record->{$field} ? $record->{$field} : undef;
  509. # Экранируем имя поля в зависимости от СУБД
  510. my $quoted_field = $config_ref{DBTYPE} eq 'mysql'
  511. ? '`' . $field . '`'
  512. : '"' . $field . '"';
  513. $fields .= "$quoted_field, ";
  514. $values .= "?, ";
  515. push @insert_params, $val;
  516. # Для лога — безопасное представление
  517. my $log_val = defined $val ? substr($val, 0, 200) : 'NULL';
  518. $log_val =~ s/[^[:print:]]/_/g;
  519. $new_str .= " $field => $log_val,";
  520. }
  521. $fields =~ s/,\s*$//;
  522. $values =~ s/,\s*$//;
  523. my $sSQL = "INSERT INTO $table($fields) VALUES($values)";
  524. my $result = do_sql($db,$sSQL,@insert_params);
  525. if ($result) {
  526. $rec_id = $result if ($table eq "user_auth");
  527. $new_str='id: '.$result.' '.$new_str;
  528. if ($table eq 'user_auth_alias' and $dns_changed) {
  529. if ($record->{'alias'} and $record->{'alias'}!~/\.$/) {
  530. my $add_dns;
  531. $add_dns->{'name_type'}='CNAME';
  532. $add_dns->{'name'}=$record->{'alias'};
  533. $add_dns->{'value'}=get_dns_name($db,$record->{'auth_id'});
  534. $add_dns->{'operation_type'}='add';
  535. $add_dns->{'auth_id'}=$record->{'auth_id'};
  536. insert_record($db,'dns_queue',$add_dns);
  537. }
  538. }
  539. if ($table eq 'user_auth' and $dns_changed) {
  540. if ($record->{'dns_name'} and $record->{'ip'} and !$record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  541. my $add_dns;
  542. $add_dns->{'name_type'}='A';
  543. $add_dns->{'name'}=$record->{'dns_name'};
  544. $add_dns->{'value'}=$record->{'ip'};
  545. $add_dns->{'operation_type'}='add';
  546. $add_dns->{'auth_id'}=$result;
  547. insert_record($db,'dns_queue',$add_dns);
  548. }
  549. if ($record->{'dns_name'} and $record->{'ip'} and $record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  550. my $add_dns;
  551. $add_dns->{'name_type'}='PTR';
  552. $add_dns->{'name'}=$record->{'dns_name'};
  553. $add_dns->{'value'}=$record->{'ip'};
  554. $add_dns->{'operation_type'}='add';
  555. $add_dns->{'auth_id'}=$result;
  556. insert_record($db,'dns_queue',$add_dns);
  557. }
  558. }
  559. }
  560. db_log_debug($db,'Add record to table '.$table.' '.$new_str,$rec_id);
  561. return $result;
  562. }
  563. #---------------------------------------------------------------------------------------------------------------
  564. sub update_record {
  565. my ($db, $table, $record, $filter_sql, @filter_params) = @_;
  566. return unless $db && $table && $filter_sql;
  567. unless (reconnect_db(\$db)) {
  568. log_error("No database connection available");
  569. return;
  570. }
  571. my $select_sql = "SELECT * FROM $table WHERE $filter_sql";
  572. my $old_record = get_record_sql($db, $select_sql, @filter_params);
  573. return unless $old_record;
  574. my @update_params;
  575. my $set_clause = '';
  576. my $dns_changed = 0;
  577. my $rec_id = $old_record->{id} || 0;
  578. if ($table eq "user_auth") {
  579. $rec_id = $old_record->{'id'} if ($old_record->{'id'});
  580. my $cur_ou_id = $old_record->{'ou_id'} if ($old_record->{'ou_id'});
  581. if (exists $record->{ou_id}) { $cur_ou_id = $record->{'ou_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} and !is_system_ou($db,$cur_ou_id)) { $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. next if (!$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 is_system_ou {
  743. my ($db, $ou_id) = @_;
  744. return 0 if !defined $ou_id || $ou_id !~ /^\d+$/ || $ou_id <= 0;
  745. my $sql = "SELECT 1 FROM ou WHERE id = ? AND (default_users = 1 OR default_hotspot = 1)";
  746. my $record = get_record_sql($db, $sql, [$ou_id]);
  747. return $record ? 1 : 0;
  748. }
  749. #---------------------------------------------------------------------------------------------------------------
  750. sub init_option {
  751. my $db=shift;
  752. $last_refresh_config = time();
  753. $config_ref{version}='';
  754. my $version_record = get_record_sql($db,"SELECT version FROM version WHERE version is NOT NULL");
  755. if ($version_record) { $config_ref{version}=$version_record->{version}; }
  756. $config_ref{self_ip} = '127.0.0.1';
  757. if ($DBHOST ne '127.0.0.1') {
  758. my $ip_route = qx(ip r get $DBHOST 2>&1 | head -1);
  759. if ($? == 0) {
  760. if ($ip_route =~ /src\s+(\d+\.\d+\.\d+\.\d+)/) { $config_ref{self_ip} = $1; }
  761. }
  762. }
  763. $config_ref{dbh}=$db;
  764. $config_ref{save_detail}=get_option($db,23);
  765. $config_ref{add_unknown_user}=get_option($db,22);
  766. $config_ref{dhcp_server}=get_option($db,5);
  767. $config_ref{snmp_default_version}=get_option($db,9);
  768. $config_ref{snmp_default_community}=get_option($db,11);
  769. $config_ref{KB}=get_option($db,1);
  770. if ($config_ref{KB} ==0) { $config_ref{KB}=1000; }
  771. if ($config_ref{KB} ==1) { $config_ref{KB}=1024; }
  772. $config_ref{admin_email}=get_option($db,21);
  773. $config_ref{sender_email}=get_option($db,52);
  774. $config_ref{send_email}=get_option($db,51);
  775. $config_ref{history}=get_option($db,26);
  776. $config_ref{history_dhcp}=get_option($db,27);
  777. $config_ref{router_login}=get_option($db,28);
  778. $config_ref{router_password}=get_option($db,29);
  779. $config_ref{router_port}=get_option($db,30);
  780. $config_ref{org_name}=get_option($db,32);
  781. $config_ref{domain_name}=get_option($db,33);
  782. $config_ref{connections_history}=get_option($db,35);
  783. $config_ref{debug}=get_option($db,34);
  784. $config_ref{log_level} = get_option($db,53);
  785. if ($config_ref{debug}) { $config_ref{log_level} = 255; }
  786. $config_ref{urgent_sync}=get_option($db,50);
  787. $config_ref{ignore_hotspot_dhcp_log} = get_option($db,44);
  788. $config_ref{ignore_update_dhcp_event} = get_option($db,45);
  789. $config_ref{update_hostname_from_dhcp} = get_option($db,46);
  790. $config_ref{history_log_day}=get_option($db,47);
  791. $config_ref{history_syslog_day} = get_option($db,48);
  792. $config_ref{history_trafstat_day} = get_option($db,49);
  793. $config_ref{enable_quotes} = get_option($db,54);
  794. $config_ref{netflow_step} = get_option($db,55);
  795. $config_ref{traffic_ipstat_history} = get_option($db,56);
  796. $config_ref{nagios_url} = get_option($db,57);
  797. $config_ref{cacti_url} = get_option($db,58);
  798. $config_ref{torrus_url} = get_option($db,59);
  799. $config_ref{wiki_url} = get_option($db,60);
  800. $config_ref{stat_url} = get_option($db,62);
  801. $config_ref{wiki_path} = get_option($db,61);
  802. $config_ref{auto_mac_rule} = get_option($db,64);
  803. #network configuration mode
  804. $config_ref{config_mode}=get_option($db,68);
  805. #auto clean old user record
  806. $config_ref{clean_empty_user}=get_option($db,69);
  807. #dns_server_type
  808. $config_ref{dns_server}=get_option($db,3);
  809. $config_ref{dns_server_type}=get_option($db,70);
  810. $config_ref{enable_dns_updates}=get_option($db,71);
  811. #$save_detail = 1; id=23
  812. $save_detail=get_option($db,23);
  813. #$add_unknown_user = 1; id=22
  814. $add_unknown_user=get_option($db,22);
  815. #$dns_server='192.168.2.12'; id=3
  816. $dns_server=get_option($db,3);
  817. #$dhcp_server='192.168.2.12'; id=5
  818. $dhcp_server=get_option($db,5);
  819. #$snmp_default_version='2'; id=9
  820. $snmp_default_version=get_option($db,9);
  821. #$snmp_default_community='public'; id=11
  822. $snmp_default_community=get_option($db,11);
  823. #$KB=1024; id=1
  824. $KB=$config_ref{KB};
  825. #$admin_email; id=21
  826. $admin_email=get_option($db,21);
  827. #sender email
  828. $sender_email=get_option($db,52);
  829. #send email
  830. $send_email=get_option($db,51);
  831. #$history=15; id=26
  832. $history=get_option($db,26);
  833. #$history_dhcp=7; id=27
  834. $history_dhcp=get_option($db,27);
  835. #$router_login="admin"; id=28
  836. $router_login=get_option($db,28);
  837. #$router_password="admin"; id=29
  838. $router_password=get_option($db,29);
  839. #$router_port=23; id=30
  840. $router_port=get_option($db,30);
  841. #32
  842. $org_name=get_option($db,32);
  843. #33
  844. $domain_name=get_option($db,33);
  845. #35
  846. $connections_history=get_option($db,35);
  847. #debug
  848. $debug=get_option($db,34);
  849. #log level
  850. $log_level = get_option($db,53);
  851. if ($debug) { $log_level = 255; }
  852. #urgent sync access
  853. $urgent_sync=get_option($db,50);
  854. $ignore_hotspot_dhcp_log = get_option($db,44);
  855. $ignore_update_dhcp_event = get_option($db,45);
  856. $update_hostname_from_dhcp = get_option($db,46);
  857. $history_log_day=get_option($db,47);
  858. $history_syslog_day = get_option($db,48);
  859. $history_trafstat_day = get_option($db,49);
  860. my $ou = get_record_sql($db,"SELECT id FROM ou WHERE default_users = 1");
  861. if (!$ou) { $default_user_ou_id = 0; } else { $default_user_ou_id = $ou->{'id'}; }
  862. $ou = get_record_sql($db,"SELECT id FROM ou WHERE default_hotspot = 1 ");
  863. if (!$ou) { $default_hotspot_ou_id = $default_user_ou_id; } else { $default_hotspot_ou_id = $ou->{'id'}; }
  864. @subnets=get_records_sql($db,'SELECT * FROM subnets ORDER BY ip_int_start');
  865. if (defined $office_networks) { undef $office_networks; }
  866. if (defined $free_networks) { undef $free_networks; }
  867. if (defined $vpn_networks) { undef $vpn_networks; }
  868. if (defined $hotspot_networks) { undef $hotspot_networks; }
  869. if (defined $all_networks) { undef $all_networks; }
  870. $office_networks = new Net::Patricia;
  871. $free_networks = new Net::Patricia;
  872. $vpn_networks = new Net::Patricia;
  873. $hotspot_networks = new Net::Patricia;
  874. $all_networks = new Net::Patricia;
  875. @office_network_list=();
  876. @free_network_list=();
  877. @free_network_list=();
  878. @vpn_network_list=();
  879. @hotspot_network_list=();
  880. @all_network_list=();
  881. foreach my $net (@subnets) {
  882. next if (!$net->{subnet});
  883. $subnets_ref{$net->{subnet}}=$net;
  884. if ($net->{office}) {
  885. push(@office_network_list,$net->{subnet});
  886. $office_networks->add_string($net->{subnet},$net);
  887. }
  888. if ($net->{free}) {
  889. push(@free_network_list,$net->{subnet});
  890. $free_networks->add_string($net->{subnet},$net);
  891. }
  892. if ($net->{vpn}) {
  893. push(@vpn_network_list,$net->{subnet});
  894. $vpn_networks->add_string($net->{subnet},$net);
  895. }
  896. if ($net->{hotspot}) {
  897. push(@hotspot_network_list,$net->{subnet});
  898. push(@all_network_list,$net->{subnet});
  899. $hotspot_networks->add_string($net->{subnet},$net);
  900. }
  901. push(@all_network_list,$net->{subnet});
  902. $all_networks->add_string($net->{subnet},$net);
  903. }
  904. }
  905. #---------------------------------------------------------------------------------------------------------------
  906. sub Set_Variable {
  907. my ($db, $name, $value, $timeshift) = @_;
  908. $name //= $MY_NAME;
  909. $value //= $$;
  910. $timeshift //= 60;
  911. Del_Variable($db, $name);
  912. my $clean_time = time() + $timeshift;
  913. my ($sec, $min, $hour, $day, $month, $year) = localtime($clean_time);
  914. $month++;
  915. $year += 1900;
  916. my $clear_time_str = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $year, $month, $day, $hour, $min, $sec;
  917. my $sql = "INSERT INTO variables (name, value, clear_time) VALUES (?, ?, ?)";
  918. do_sql($db, $sql, $name, $value, $clear_time_str);
  919. }
  920. #---------------------------------------------------------------------------------------------------------------
  921. sub Get_Variable {
  922. my $db = shift;
  923. my $name = shift || $MY_NAME;
  924. my $variable = get_record_sql($db, 'SELECT value FROM variables WHERE name = ?', $name);
  925. if ($variable and $variable->{'value'}) { return $variable->{'value'}; }
  926. return;
  927. }
  928. #---------------------------------------------------------------------------------------------------------------
  929. sub Del_Variable {
  930. my ($db, $name) = @_;
  931. $name //= $MY_NAME;
  932. do_sql($db, "DELETE FROM variables WHERE name = ?", $name);
  933. }
  934. #---------------------------------------------------------------------------------------------------------------
  935. sub clean_variables {
  936. my ($db) = @_;
  937. # 1. Clean temporary variables
  938. my $now = time();
  939. my ($sec, $min, $hour, $day, $month, $year) = localtime($now);
  940. $month++;
  941. $year += 1900;
  942. my $now_str = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $year, $month, $day, $hour, $min, $sec;
  943. do_sql($db, "DELETE FROM variables WHERE clear_time <= ?", $now_str);
  944. # 2. Clean old AD computer cache
  945. my $yesterday = DateTime->now(time_zone => 'local')->subtract(days => 1);
  946. my $clean_str = $yesterday->strftime("%Y-%m-%d 00:00:00");
  947. do_sql($db, "DELETE FROM ad_comp_cache WHERE last_found <= ?", $clean_str);
  948. }
  949. #---------------------------------------------------------------------------------------------------------------
  950. #skip init for upgrade
  951. if ($MY_NAME!~/upgrade.pl/) {
  952. $dbh=init_db();
  953. init_option($dbh);
  954. clean_variables($dbh);
  955. Set_Variable($dbh);
  956. }
  957. 1;
  958. }