database.pm 63 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866
  1. package eyelib::database;
  2. #
  3. # Copyright (C) Roman Dmitriev, rnd@rajven.ru
  4. #
  5. # commit example
  6. # Начинаем транзакцию вручную
  7. #$db->{AutoCommit} = 0;
  8. #eval {
  9. # for my $row (@rows) {
  10. # insert_record($db, 'user_auth', $row);
  11. # insert_record($db, 'user_auth_alias', $row2);
  12. # }
  13. # $db->commit();
  14. #};
  15. #if ($@) {
  16. # eval { $db->rollback(); };
  17. # die "Migration failed: $@";
  18. #}
  19. #$db->{AutoCommit} = 1;
  20. use utf8;
  21. use open ":encoding(utf8)";
  22. use strict;
  23. use English;
  24. use FindBin '$Bin';
  25. use lib "/opt/Eye/scripts";
  26. use base 'Exporter';
  27. use vars qw(@EXPORT @ISA);
  28. use eyelib::config;
  29. use eyelib::main;
  30. use Net::Patricia;
  31. use eyelib::net_utils;
  32. use Data::Dumper;
  33. use DateTime;
  34. use POSIX qw(mktime ctime strftime);
  35. use File::Temp qw(tempfile);
  36. use DBI;
  37. use DBD::Pg qw(:pg_types);
  38. use Text::CSV;
  39. our @ISA = qw(Exporter);
  40. our @EXPORT = qw(
  41. StrToIp
  42. IpToStr
  43. prepare_audit_message
  44. batch_db_sql_cached
  45. batch_db_sql_csv
  46. reconnect_db
  47. write_db_log
  48. db_log_debug
  49. db_log_error
  50. db_log_info
  51. db_log_verbose
  52. db_log_warning
  53. normalize_value
  54. get_table_columns
  55. init_db
  56. do_sql
  57. _execute_param
  58. do_sql_param
  59. get_option_safe
  60. get_count_records
  61. get_id_record
  62. get_records_sql
  63. get_record_sql
  64. get_diff_rec
  65. update_record
  66. insert_record
  67. delete_record
  68. get_option
  69. init_option
  70. is_system_ou
  71. Set_Variable
  72. Get_Variable
  73. Del_Variable
  74. clean_variables
  75. build_db_schema
  76. $add_rules
  77. $L_WARNING
  78. $L_INFO
  79. $L_DEBUG
  80. $L_ERROR
  81. $L_VERBOSE
  82. %db_schema
  83. );
  84. BEGIN
  85. {
  86. #---------------------------------------------------------------------------------------------------------------
  87. our $add_rules;
  88. our $L_ERROR = 0;
  89. our $L_WARNING = 1;
  90. our $L_INFO = 2;
  91. our $L_VERBOSE = 3;
  92. our $L_DEBUG = 255;
  93. our %acl_fields = (
  94. 'ip' => '1',
  95. 'ip_int' => '1',
  96. 'enabled'=>'1',
  97. 'dhcp'=>'1',
  98. 'filter_group_id'=>'1',
  99. 'deleted'=>'1',
  100. 'dhcp_acl'=>'1',
  101. 'queue_id'=>'1',
  102. 'mac'=>'1',
  103. 'blocked'=>'1'
  104. );
  105. our %dhcp_fields = (
  106. 'ip' => '1',
  107. 'dhcp_acl'=>'1',
  108. 'dhcp_option_set'=>'1',
  109. 'dhcp'=>'1',
  110. 'deleted'=>'1',
  111. 'mac'=>'1',
  112. );
  113. our %dns_fields = (
  114. 'ip' => '1',
  115. 'dns_name'=>'1',
  116. 'dns_ptr_only'=>'1',
  117. 'alias'=>'1',
  118. );
  119. our %db_schema;
  120. #---------------------------------------------------------------------------------------------------------------
  121. sub prepare_audit_message {
  122. my ($dbh, $table, $old_data, $new_data, $record_id, $operation) = @_;
  123. # === 1. Конфигурация отслеживаемых таблиц ===
  124. my %audit_config = (
  125. 'auth_rules' => {
  126. summary => ['rule'],
  127. fields => ['user_id', 'ou_id', 'rule_type', 'rule', 'description']
  128. },
  129. 'building' => {
  130. summary => ['name'],
  131. fields => ['name', 'description']
  132. },
  133. 'customers' => {
  134. summary => ['login'],
  135. fields => ['login', 'description', 'rights']
  136. },
  137. 'devices' => {
  138. summary => ['device_name'],
  139. fields => [
  140. 'device_type', 'device_model_id', 'vendor_id', 'device_name', 'building_id',
  141. 'ip', 'login', 'protocol', 'control_port', 'port_count', 'sn',
  142. 'description', 'snmp_version', 'snmp3_auth_proto', 'snmp3_priv_proto',
  143. 'snmp3_user_rw', 'snmp3_user_ro', 'community', 'rw_community',
  144. 'discovery', 'netflow_save', 'user_acl', 'dhcp', 'nagios',
  145. 'active', 'queue_enabled', 'connected_user_only', 'user_id'
  146. ]
  147. },
  148. 'device_filter_instances' => {
  149. summary => [],
  150. fields => ['instance_id', 'device_id']
  151. },
  152. 'device_l3_interfaces' => {
  153. summary => ['name'],
  154. fields => ['device_id', 'snmpin', 'interface_type', 'name']
  155. },
  156. 'device_models' => {
  157. summary => ['model_name'],
  158. fields => ['model_name', 'vendor_id', 'poe_in', 'poe_out', 'nagios_template']
  159. },
  160. 'device_ports' => {
  161. summary => ['port', 'ifname'],
  162. fields => [
  163. 'device_id', 'snmp_index', 'port', 'ifname', 'port_name', 'description',
  164. 'target_port_id', 'auth_id', 'last_mac_count', 'uplink', 'nagios',
  165. 'skip', 'vlan', 'tagged_vlan', 'untagged_vlan', 'forbidden_vlan'
  166. ]
  167. },
  168. 'filter_instances' => {
  169. summary => ['name'],
  170. fields => ['name', 'description']
  171. },
  172. 'filter_list' => {
  173. summary => ['name'],
  174. fields => ['name', 'description', 'proto', 'dst', 'dstport', 'srcport', 'filter_type']
  175. },
  176. 'gateway_subnets' => {
  177. summary => [],
  178. fields => ['device_id', 'subnet_id']
  179. },
  180. 'group_filters' => {
  181. summary => [],
  182. fields => ['group_id', 'filter_id', 'rule_order', 'action']
  183. },
  184. 'group_list' => {
  185. summary => ['group_name'],
  186. fields => ['instance_id', 'group_name', 'description']
  187. },
  188. 'ou' => {
  189. summary => ['ou_name'],
  190. fields => [
  191. 'ou_name', 'description', 'default_users', 'default_hotspot',
  192. 'nagios_dir', 'nagios_host_use', 'nagios_ping', 'nagios_default_service',
  193. 'enabled', 'filter_group_id', 'queue_id', 'dynamic', 'life_duration', 'parent_id'
  194. ]
  195. },
  196. 'queue_list' => {
  197. summary => ['queue_name'],
  198. fields => ['queue_name', 'download', 'upload']
  199. },
  200. 'subnets' => {
  201. summary => ['subnet'],
  202. fields => [
  203. 'subnet', 'vlan_tag', 'ip_int_start', 'ip_int_stop', 'dhcp_start', 'dhcp_stop',
  204. 'dhcp_lease_time', 'gateway', 'office', 'hotspot', 'vpn', 'free', 'dhcp',
  205. 'static', 'dhcp_update_hostname', 'discovery', 'notify', 'description'
  206. ]
  207. },
  208. 'user_auth' => {
  209. summary => ['ip', 'dns_name'],
  210. fields => [
  211. 'user_id', 'ou_id', 'ip', 'save_traf', 'enabled', 'dhcp', 'filter_group_id',
  212. 'dynamic', 'end_life', 'description', 'dns_name', 'dns_ptr_only', 'wikiname',
  213. 'dhcp_acl', 'queue_id', 'mac', 'dhcp_option_set', 'blocked', 'day_quota',
  214. 'month_quota', 'device_model_id', 'firmware', 'client_id', 'nagios',
  215. 'nagios_handler', 'link_check', 'deleted'
  216. ]
  217. },
  218. 'user_auth_alias' => {
  219. summary => ['alias'],
  220. fields => ['auth_id', 'alias', 'description']
  221. },
  222. 'user_list' => {
  223. summary => ['login'],
  224. fields => [
  225. 'login', 'description', 'enabled', 'blocked', 'deleted', 'ou_id',
  226. 'device_id', 'filter_group_id', 'queue_id', 'day_quota', 'month_quota', 'permanent'
  227. ]
  228. },
  229. 'vendors' => {
  230. summary => ['name'],
  231. fields => ['name']
  232. }
  233. );
  234. return undef unless exists $audit_config{$table};
  235. my $summary_fields = $audit_config{$table}{summary};
  236. my $monitored_fields = $audit_config{$table}{fields};
  237. # === 2. Нормализация данных и определение изменений ===
  238. my %changes;
  239. if ($operation eq 'insert') {
  240. for my $field (@$monitored_fields) {
  241. if (exists $new_data->{$field}) {
  242. $changes{$field} = { old => undef, new => $new_data->{$field} };
  243. }
  244. }
  245. }
  246. elsif ($operation eq 'delete') {
  247. for my $field (@$monitored_fields) {
  248. if (exists $old_data->{$field}) {
  249. $changes{$field} = { old => $old_data->{$field}, new => undef };
  250. }
  251. }
  252. }
  253. elsif ($operation eq 'update') {
  254. $old_data //= {};
  255. $new_data //= {};
  256. for my $field (@$monitored_fields) {
  257. next unless exists $new_data->{$field}; # частичное обновление
  258. my $old_val = exists $old_data->{$field} ? $old_data->{$field} : undef;
  259. my $new_val = $new_data->{$field};
  260. my $old_str = !defined($old_val) ? '' : "$old_val";
  261. my $new_str = !defined($new_val) ? '' : "$new_val";
  262. if ($old_str ne $new_str) {
  263. $changes{$field} = { old => $old_val, new => $new_val };
  264. }
  265. }
  266. }
  267. return undef unless %changes;
  268. # === 3. Краткое описание записи ===
  269. my @summary_parts;
  270. for my $field (@$summary_fields) {
  271. my $val = defined($new_data->{$field}) ? $new_data->{$field}
  272. : (defined($old_data->{$field}) ? $old_data->{$field} : undef);
  273. push @summary_parts, "$val" if defined $val && $val ne '';
  274. }
  275. my $summary_label = @summary_parts
  276. ? '"' . join(' | ', @summary_parts) . '"'
  277. : "ID=$record_id";
  278. # === 4. Расшифровка *_id полей ===
  279. my %resolved_changes;
  280. for my $field (keys %changes) {
  281. my $old_resolved = resolve_reference_value($dbh, $field, $changes{$field}{old});
  282. my $new_resolved = resolve_reference_value($dbh, $field, $changes{$field}{new});
  283. $resolved_changes{$field} = { old => $old_resolved, new => $new_resolved };
  284. }
  285. # === 5. Формирование сообщения ===
  286. my $op_label = 'Updated';
  287. if ($operation eq 'insert') {
  288. $op_label = 'Created';
  289. } elsif ($operation eq 'delete') {
  290. $op_label = 'Deleted';
  291. } else {
  292. $op_label = ucfirst($operation);
  293. }
  294. my $message = sprintf("[%s] %s (%s) in table `%s`:\n",
  295. $op_label,
  296. ucfirst($table),
  297. $summary_label,
  298. $table
  299. );
  300. for my $field (sort keys %resolved_changes) {
  301. my $change = $resolved_changes{$field};
  302. if ($operation eq 'insert') {
  303. if (defined $change->{new}) {
  304. $message .= sprintf(" %s: %s\n", $field, $change->{new});
  305. }
  306. } elsif ($operation eq 'delete') {
  307. if (defined $change->{old}) {
  308. $message .= sprintf(" %s: %s\n", $field, $change->{old});
  309. }
  310. } else { # update
  311. my $old_display = !defined($change->{old}) ? '[NULL]' : $change->{old};
  312. my $new_display = !defined($change->{new}) ? '[NULL]' : $change->{new};
  313. $message .= sprintf(" %s: \"%s\" → \"%s\"\n", $field, $old_display, $new_display);
  314. }
  315. }
  316. chomp $message;
  317. return $message;
  318. }
  319. #---------------------------------------------------------------------------------------------------------------
  320. sub resolve_reference_value {
  321. my ($dbh, $field, $value) = @_;
  322. return undef if !defined $value || $value eq '';
  323. # Проверка на целое число (как в PHP)
  324. if ($value !~ /^[+-]?\d+$/) {
  325. return "$value";
  326. }
  327. my $as_int = int($value);
  328. if ("$as_int" ne "$value") {
  329. return "$value";
  330. }
  331. my $id = $as_int;
  332. if ($field eq 'device_id') {
  333. return get_device_name($dbh, $id) // "Device#$id";
  334. }
  335. elsif ($field eq 'building_id') {
  336. return get_building($dbh, $id) // "Building#$id";
  337. }
  338. elsif ($field eq 'user_id') {
  339. return get_login($dbh, $id) // "User#$id";
  340. }
  341. elsif ($field eq 'ou_id') {
  342. return get_ou($dbh, $id) // "OU#$id";
  343. }
  344. elsif ($field eq 'vendor_id') {
  345. return get_vendor_name($dbh, $id) // "Vendor#$id";
  346. }
  347. elsif ($field eq 'device_model_id') {
  348. return get_device_model_name($dbh, $id) // "Model#$id";
  349. }
  350. elsif ($field eq 'instance_id') {
  351. return get_filter_instance_description($dbh, $id) // "FilterInstance#$id";
  352. }
  353. elsif ($field eq 'subnet_id') {
  354. return get_subnet_description($dbh, $id) // "Subnet#$id";
  355. }
  356. elsif ($field eq 'group_id') {
  357. return get_group($dbh, $id) // "FilterGroup#$id";
  358. }
  359. elsif ($field eq 'filter_id') {
  360. return get_filter($dbh, $id) // "Filter#$id";
  361. }
  362. elsif ($field eq 'filter_group_id') {
  363. return get_group($dbh, $id) // "FilterGroup#$id";
  364. }
  365. elsif ($field eq 'queue_id') {
  366. return get_queue($dbh, $id) // "Queue#$id";
  367. }
  368. elsif ($field eq 'auth_id') {
  369. return 'None' if $id <= 0;
  370. my $sql = "
  371. SELECT
  372. COALESCE(ul.login, CONCAT('User#', ua.user_id)) AS login,
  373. ua.ip,
  374. ua.dns_name
  375. FROM user_auth ua
  376. LEFT JOIN user_list ul ON ul.id = ua.user_id
  377. WHERE ua.id = ?
  378. ";
  379. my $row = get_record_sql($dbh, $sql, $id);
  380. return "Auth#$id" unless $row;
  381. my @parts;
  382. push @parts, "login: $row->{login}" if $row->{login} && $row->{login} ne '';
  383. push @parts, "IP: $row->{ip}" if $row->{ip} && $row->{ip} ne '';
  384. push @parts, "DNS: $row->{dns_name}" if $row->{dns_name} && $row->{dns_name} ne '';
  385. return @parts ? join(', ', @parts) : "Auth#$id";
  386. }
  387. elsif ($field eq 'target_port_id') {
  388. return 'None' if $id == 0;
  389. my $sql = "
  390. SELECT CONCAT(d.device_name, '[', dp.port, ']')
  391. FROM device_ports dp
  392. JOIN devices d ON d.id = dp.device_id
  393. WHERE dp.id = ?
  394. ";
  395. my $name = $dbh->selectrow_array($sql, undef, $id);
  396. return $name // "Port#$id";
  397. }
  398. else {
  399. return "$value";
  400. }
  401. }
  402. #---------------------------------------------------------------------------------------------------------------
  403. sub build_db_schema {
  404. my ($dbh) = @_;
  405. # Определяем тип СУБД
  406. my $db_type = lc($dbh->{Driver}->{Name});
  407. die "Unsupported database driver: $db_type"
  408. unless $db_type eq 'mysql' || $db_type eq 'pg';
  409. # Получаем имя базы данных
  410. my $db_name;
  411. if ($db_type eq 'mysql') {
  412. ($db_name) = $dbh->selectrow_array("SELECT DATABASE()");
  413. } elsif ($db_type eq 'pg') {
  414. ($db_name) = $dbh->selectrow_array("SELECT current_database()");
  415. }
  416. my $db_info;
  417. $db_info->{db_type}=$db_type;
  418. $db_info->{db_name}=$db_name;
  419. return $db_info if (exists $db_schema{$db_type}{$db_name});
  420. # Получаем список таблиц
  421. my @tables;
  422. if ($db_type eq 'mysql') {
  423. my $sth = $dbh->prepare("SHOW TABLES");
  424. $sth->execute();
  425. @tables = map { $_->[0] } @{$sth->fetchall_arrayref()};
  426. } elsif ($db_type eq 'pg') {
  427. my $sql = q{
  428. SELECT tablename
  429. FROM pg_tables
  430. WHERE schemaname = 'public'
  431. };
  432. my $sth = $dbh->prepare($sql);
  433. $sth->execute();
  434. @tables = map { $_->[0] } @{$sth->fetchall_arrayref()};
  435. }
  436. # Собираем схему
  437. for my $table (@tables) {
  438. my $sth = $dbh->column_info(undef, undef, $table, '%');
  439. while (my $col = $sth->fetchrow_hashref) {
  440. my $col_name = lc($col->{COLUMN_NAME});
  441. $db_schema{$db_type}{$db_name}{$table}{$col_name} = {
  442. type => $col->{TYPE_NAME} // '',
  443. nullable => $col->{NULLABLE} // 1,
  444. default => $col->{COLUMN_DEF} // undef,
  445. };
  446. }
  447. }
  448. return $db_info;
  449. }
  450. #---------------------------------------------------------------------------------------------------------------
  451. sub normalize_value {
  452. my ($value, $col_info) = @_;
  453. # Если значение пустое — обрабатываем по правилам колонки
  454. if (!defined $value || $value eq '' || $value =~ /^(?:NULL|\\N)$/i) {
  455. return $col_info->{nullable} ? undef : _default_for_type($col_info);
  456. }
  457. my $type = lc($col_info->{type});
  458. # --- Числовые типы: приводим к числу, если выглядит как число ---
  459. if ($type =~ /^(?:tinyint|smallint|mediumint|int|integer|bigint|serial|bigserial)$/i) {
  460. # Просто конвертируем строку в число (Perl сам обрежет мусор)
  461. # Например: "123abc" → 123, "abc" → 0
  462. return 0 + $value;
  463. }
  464. # --- Булевы: приводим к 0/1 ---
  465. if ($type =~ /^(?:bool|boolean|bit)$/i) {
  466. return $value ? 1 : 0;
  467. }
  468. # --- Временные типы: оставляем как есть, но фильтруем "нулевые" даты MySQL ---
  469. if ($type =~ /^(?:timestamp|datetime|date|time)$/i) {
  470. # Это частая проблема при миграции — '0000-00-00' ломает PostgreSQL
  471. return undef if $value =~ /^0000-00-00/;
  472. return $value;
  473. }
  474. # --- Все остальные типы (строки, inet, json и т.д.) — передаём как есть ---
  475. return $value;
  476. }
  477. # Вспомогательная: безопасное значение по умолчанию
  478. sub _default_for_type {
  479. my ($col) = @_;
  480. # Используем DEFAULT, только если он простой литерал (не выражение)
  481. if (defined $col->{default}) {
  482. my $def = $col->{default};
  483. # Пропускаем выражения: nextval(), CURRENT_TIMESTAMP, NOW(), uuid() и т.п.
  484. if ($def !~ /(nextval|current_timestamp|now|uuid|auto_increment|::)/i) {
  485. # Убираем одинарные кавычки, если строка: 'value' → value
  486. if ($def =~ /^'(.*)'$/) {
  487. return $1;
  488. }
  489. # Если похоже на число — вернём как число
  490. if ($def =~ /^[+-]?\d+$/) {
  491. return 0 + $def;
  492. }
  493. return $def;
  494. }
  495. }
  496. # Фолбэк по типу
  497. my $type = lc($col->{type});
  498. if ($type =~ /^(?:tinyint|smallint|int|integer|bigint)/i) { return 0; }
  499. if ($type =~ /^(?:char|varchar|text)/i) { return ''; }
  500. if ($type =~ /^(?:timestamp|datetime)/i) { return GetNowTime(); }
  501. return undef;
  502. }
  503. #---------------------------------------------------------------------------------------------------------------
  504. sub get_table_columns {
  505. my ($db, $table) = @_;
  506. my %columns;
  507. my $sth = $db->column_info(undef, undef, $table, '%');
  508. while (my $row = $sth->fetchrow_hashref) {
  509. my $name = lc($row->{COLUMN_NAME}); # ← приводим к нижнему регистру сразу!
  510. $columns{$name} = {
  511. type => $row->{TYPE_NAME} // '',
  512. nullable => $row->{NULLABLE} // 1,
  513. default => $row->{COLUMN_DEF} // undef,
  514. };
  515. }
  516. return %columns; # возвращает список: key1, val1, key2, val2...
  517. }
  518. #---------------------------------------------------------------------------------------------------------------
  519. sub StrToIp {
  520. return unpack('N',pack('C4',split(/\./,$_[0])));
  521. }
  522. #---------------------------------------------------------------------------------------------------------------
  523. sub IpToStr {
  524. my $nIP = shift;
  525. my $res = (($nIP>>24) & 255) .".". (($nIP>>16) & 255) .".". (($nIP>>8) & 255) .".". ($nIP & 255);
  526. return $res;
  527. }
  528. #---------------------------------------------------------------------------------------------------------------
  529. sub batch_db_sql_cached {
  530. my ( $sql, $data) = @_;
  531. my $db=init_db();
  532. # Запоминаем исходное состояние AutoCommit
  533. my $original_autocommit = $db->{AutoCommit};
  534. eval {
  535. # Выключаем AutoCommit для транзакции
  536. $db->{AutoCommit} = 0;
  537. my $sth = $db->prepare_cached($sql) or die "Unable to prepare SQL: " . $db->errstr;
  538. for my $params (@$data) {
  539. next unless @$params;
  540. $sth->execute(@$params) or die "Unable to execute with params [" . join(',', @$params) . "]: " . $sth->errstr;
  541. }
  542. $db->commit();
  543. 1;
  544. } or do {
  545. my $err = $@ || 'Unknown error';
  546. eval { $db->rollback() };
  547. warn "batch_sql_cached failed: $err";
  548. # Восстанавливаем AutoCommit даже при ошибке
  549. $db->{AutoCommit} = $original_autocommit;
  550. return 0;
  551. };
  552. # Восстанавливаем исходный режим AutoCommit
  553. $db->{AutoCommit} = $original_autocommit;
  554. $db->disconnect();
  555. return 1;
  556. }
  557. #---------------------------------------------------------------------------------------------------------------
  558. sub batch_db_sql_csv {
  559. my ($table, $data) = @_;
  560. return 0 unless @$data;
  561. # Первая строка — заголовки (имена столбцов)
  562. my $header_row = shift @$data;
  563. unless ($header_row && ref($header_row) eq 'ARRAY' && @$header_row) {
  564. log_error("First row must be column names (array reference)");
  565. return 0;
  566. }
  567. my @columns = @$header_row;
  568. # Теперь @$data содержит только строки данных
  569. my $data_rows = $data;
  570. # Если нет данных — только заголовок
  571. unless (@$data_rows) {
  572. log_debug("No data rows to insert, only header");
  573. return 1;
  574. }
  575. my $db = init_db();
  576. my $original_autocommit = $db->{AutoCommit};
  577. $db->{AutoCommit} = 0;
  578. if (get_db_type($db) eq 'mysql') {
  579. # --- MySQL: попытка LOAD DATA, fallback на INSERT ---
  580. log_debug("Using LOAD DATA LOCAL INFILE for MySQL");
  581. my $fh = File::Temp->new(UNLINK => 1);
  582. my $fname = $fh->filename;
  583. binmode($fh, ':utf8');
  584. my $csv = Text::CSV->new({
  585. binary => 1,
  586. quote_char => '"',
  587. escape_char => '"',
  588. sep_char => ',',
  589. eol => "\r\n",
  590. always_quote => 1,
  591. }) or do {
  592. my $err = "Cannot create Text::CSV: " . Text::CSV->error_diag();
  593. log_error($err);
  594. $db->{AutoCommit} = $original_autocommit;
  595. $db->disconnect();
  596. return 0;
  597. };
  598. # Пишем заголовок
  599. $csv->print($fh, \@columns);
  600. # Пишем данные
  601. for my $row (@$data_rows) {
  602. next unless $row && ref($row) eq 'ARRAY' && @$row == @columns;
  603. my @vals = map { defined($_) ? $_ : 'NULL' } @$row;
  604. $csv->print($fh, \@vals);
  605. }
  606. close $fh;
  607. my $col_list = join(', ', map { $db->quote_identifier($_) } @columns);
  608. my $query = qq{LOAD DATA LOCAL INFILE '$fname' INTO TABLE $table FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\r\n' IGNORE 1 LINES ($col_list)};
  609. my $load_ok = eval { $db->do($query); 1 };
  610. if (!$load_ok) {
  611. my $err = "MySQL LOAD DATA failed: $@";
  612. log_error($err);
  613. log_debug("Falling back to bulk INSERT for MySQL");
  614. goto FALLBACK_INSERT_MYSQL;
  615. }
  616. $db->commit();
  617. $db->{AutoCommit} = $original_autocommit;
  618. $db->disconnect();
  619. return 1;
  620. # ========================
  621. # Fallback для MySQL
  622. # ========================
  623. FALLBACK_INSERT_MYSQL:
  624. {
  625. my $quoted_cols = join(', ', map { $db->quote_identifier($_) } @columns);
  626. my $placeholders = join(',', ('?') x @columns);
  627. my $sql = "INSERT INTO $table ($quoted_cols) VALUES ($placeholders)";
  628. my $sth = $db->prepare($sql);
  629. my $success = eval {
  630. for my $row (@$data_rows) {
  631. next unless $row && ref($row) eq 'ARRAY' && @$row == @columns;
  632. my @vals = map { defined($_) ? $_ : undef } @$row;
  633. $sth->execute(@vals);
  634. }
  635. 1;
  636. };
  637. if ($success) {
  638. $db->commit();
  639. } else {
  640. eval { $db->rollback(); };
  641. my $err = "MySQL bulk INSERT failed: $@";
  642. log_error($err);
  643. $db->{AutoCommit} = $original_autocommit;
  644. $db->disconnect();
  645. return 0;
  646. }
  647. $db->{AutoCommit} = $original_autocommit;
  648. }
  649. } elsif (get_db_type($db) eq 'pg') {
  650. if (!$db->can('pg_putcopydata') || !$db->can('pg_putcopyend')) {
  651. log_debug("pg_putcopydata/pg_putcopyend not available — falling back to bulk INSERT");
  652. goto FALLBACK_INSERT_PG;
  653. }
  654. my $col_list = join(', ', map { $db->quote_identifier($_) } @columns);
  655. my $copy_sql = "COPY $table ($col_list) FROM STDIN WITH (FORMAT CSV, HEADER true)";
  656. my $use_header_as_data;
  657. my $start_ok = eval { $db->do($copy_sql); 1 };
  658. if (!$start_ok) {
  659. log_debug("COPY with HEADER failed: $@ — trying without HEADER");
  660. $copy_sql = "COPY $table ($col_list) FROM STDIN WITH (FORMAT CSV)";
  661. $start_ok = eval { $db->do($copy_sql); 1 };
  662. if (!$start_ok) {
  663. log_debug("COPY failed entirely: $@ — falling back to bulk INSERT");
  664. goto FALLBACK_INSERT_PG;
  665. }
  666. $use_header_as_data = 1;
  667. } else {
  668. $use_header_as_data = 0;
  669. }
  670. log_debug("Using CSV COPY for PostgreSQL");
  671. my $csv = Text::CSV->new({
  672. binary => 1,
  673. quote_char => '"',
  674. escape_char => '"',
  675. sep_char => ',',
  676. eol => "\n",
  677. always_quote => 1,
  678. }) or do {
  679. my $err = "Cannot create Text::CSV: " . Text::CSV->error_diag();
  680. log_error($err);
  681. eval { $db->pg_putcopyend(); };
  682. $db->{AutoCommit} = $original_autocommit;
  683. $db->disconnect();
  684. return 0;
  685. };
  686. my $success = eval {
  687. if ($use_header_as_data) {
  688. $csv->combine(@columns);
  689. $db->pg_putcopydata($csv->string);
  690. }
  691. for my $row (@$data_rows) {
  692. next unless $row && ref($row) eq 'ARRAY' && @$row == @columns;
  693. my @vals = map { defined($_) ? $_ : undef } @$row;
  694. $csv->combine(@vals);
  695. $db->pg_putcopydata($csv->string);
  696. }
  697. $db->pg_putcopyend();
  698. 1;
  699. };
  700. if ($success) {
  701. $db->commit();
  702. } else {
  703. eval { $db->rollback(); };
  704. my $err = "CSV COPY failed: $@";
  705. log_error($err);
  706. eval { $db->pg_putcopyend(); };
  707. goto FALLBACK_INSERT_PG;
  708. }
  709. # ========================
  710. # Fallback для PostgreSQL
  711. # ========================
  712. FALLBACK_INSERT_PG:
  713. {
  714. my $quoted_cols = join(', ', map { $db->quote_identifier($_) } @columns);
  715. my $placeholders = join(',', ('?') x @columns);
  716. my $sql = "INSERT INTO $table ($quoted_cols) VALUES ($placeholders)";
  717. my $sth = $db->prepare($sql);
  718. my $success = eval {
  719. for my $row (@$data_rows) {
  720. next unless $row && ref($row) eq 'ARRAY' && @$row == @columns;
  721. my @vals = map { defined($_) ? $_ : undef } @$row;
  722. $sth->execute(@vals);
  723. }
  724. 1;
  725. };
  726. if ($success) {
  727. $db->commit();
  728. } else {
  729. eval { $db->rollback(); };
  730. my $err = "PostgreSQL bulk INSERT failed: $@";
  731. log_error($err);
  732. $db->{AutoCommit} = $original_autocommit;
  733. $db->disconnect();
  734. return 0;
  735. }
  736. }
  737. } else {
  738. my $err = "Unsupported DBTYPE: ". get_db_type($db);
  739. log_error($err);
  740. $db->{AutoCommit} = $original_autocommit;
  741. $db->disconnect();
  742. return 0;
  743. }
  744. $db->{AutoCommit} = $original_autocommit;
  745. $db->disconnect();
  746. return 1;
  747. }
  748. #---------------------------------------------------------------------------------------------------------------
  749. sub reconnect_db {
  750. my $db_ref = shift;
  751. # Если соединение активно — ничего не делаем
  752. if ($$db_ref && $$db_ref->ping) {
  753. return 1;
  754. }
  755. # Сохраняем AutoCommit из текущего соединения (если есть)
  756. my $original_autocommit = 1;
  757. if ($$db_ref) {
  758. $original_autocommit = $$db_ref->{AutoCommit};
  759. eval { $$db_ref->disconnect; };
  760. $$db_ref = undef;
  761. }
  762. # Пытаемся переподключиться
  763. eval {
  764. $$db_ref = init_db($original_autocommit);
  765. unless ($$db_ref && $$db_ref->ping) {
  766. log_die "Failed to establish database connection";
  767. }
  768. 1;
  769. } or do {
  770. my $error = $@ || 'Unknown error';
  771. $$db_ref = undef;
  772. log_die "Database reconnection failed: $error";
  773. return 0;
  774. };
  775. return 1;
  776. }
  777. #---------------------------------------------------------------------------------------------------------------
  778. sub write_db_log {
  779. my $db=shift;
  780. my $msg=shift;
  781. my $level = shift || $L_VERBOSE;
  782. my $auth_id = shift || 0;
  783. return if (!$db);
  784. return if (!$msg);
  785. $msg=~s/[\'\"]//g;
  786. my $db_log = 0;
  787. # Переподключение
  788. unless (reconnect_db(\$db)) {
  789. log_error("No database connection available");
  790. $db_log = 0;
  791. }
  792. if ($level eq $L_ERROR and $log_level >= $L_ERROR) { log_error($msg); $db_log = 1; }
  793. if ($level eq $L_WARNING and $log_level >= $L_WARNING) { log_warning($msg); $db_log = 1; }
  794. if ($level eq $L_INFO and $log_level >= $L_INFO) { log_info($msg); $db_log = 1; }
  795. if ($level eq $L_VERBOSE and $log_level >= $L_VERBOSE) { log_verbose($msg); $db_log = 1; }
  796. if ($level eq $L_DEBUG and $log_level >= $L_DEBUG) { log_debug($msg); return; }
  797. if ($db_log) {
  798. #my $new_id = do_sql($dbh, 'INSERT INTO user_list (login) VALUES (?)', 'Ivan');
  799. do_sql($db,'INSERT INTO worklog(customer,message,level,auth_id,ip) VALUES( ?, ?, ?, ?, ?)',$MY_NAME,$msg,$level,$auth_id,$config_ref{self_ip});
  800. }
  801. }
  802. #---------------------------------------------------------------------------------------------------------------
  803. sub db_log_debug {
  804. my $db = shift;
  805. my $msg = shift;
  806. my $id = shift;
  807. if ($debug) { log_debug($msg); }
  808. }
  809. #---------------------------------------------------------------------------------------------------------------
  810. sub db_log_error {
  811. my $db = shift;
  812. my $msg = shift;
  813. if ($log_level >= $L_ERROR) {
  814. sendEmail("ERROR! ".get_first_line($msg),$msg,1);
  815. write_db_log($db,$msg,$L_ERROR);
  816. }
  817. }
  818. #---------------------------------------------------------------------------------------------------------------
  819. sub db_log_info {
  820. my $db = shift;
  821. my $msg = shift;
  822. my $id = shift;
  823. if ($log_level >= $L_INFO) { write_db_log($db,$msg,$L_INFO,$id); }
  824. }
  825. #---------------------------------------------------------------------------------------------------------------
  826. sub db_log_verbose {
  827. my $db = shift;
  828. my $msg = shift;
  829. my $id = shift;
  830. if ($log_level >= $L_VERBOSE) { write_db_log($db,$msg,$L_VERBOSE,$id); }
  831. }
  832. #---------------------------------------------------------------------------------------------------------------
  833. sub db_log_warning {
  834. my $db = shift;
  835. my $msg = shift;
  836. my $id = shift;
  837. if ($log_level >= $L_WARNING) { write_db_log($db,$msg,$L_WARNING,$id); }
  838. }
  839. #---------------------------------------------------------------------------------------------------------------
  840. sub init_db {
  841. my $autocommit = shift;
  842. if (!defined $autocommit) { $autocommit = 1; }
  843. my $db;
  844. if ($config_ref{DBTYPE} eq 'mysql') {
  845. $db = DBI->connect(
  846. "dbi:mysql:database=$DBNAME;host=$DBHOST;port=3306;mysql_local_infile=1", $DBUSER, $DBPASS,
  847. { RaiseError => 0, AutoCommit => $autocommit, mysql_enable_utf8 => 1 }
  848. );
  849. if (!defined $db) {
  850. log_die "Cannot connect to MySQL server: $DBI::errstr\n";
  851. }
  852. $db->do('SET NAMES utf8mb4');
  853. } else {
  854. $db = DBI->connect(
  855. "dbi:Pg:dbname=$DBNAME;host=$DBHOST;port=5432", $DBUSER, $DBPASS,
  856. { RaiseError => 0, AutoCommit => $autocommit, pg_enable_utf8 => 1, pg_server_prepare => 0 }
  857. );
  858. if (!defined $db) {
  859. log_die "Cannot connect to PostgreSQL server: $DBI::errstr\n";
  860. }
  861. }
  862. return $db;
  863. }
  864. #---------------------------------------------------------------------------------------------------------------
  865. # Обновленная функция get_option с параметризованными запросами
  866. sub get_option {
  867. my $db = shift;
  868. my $option_id = shift;
  869. return if (!$option_id);
  870. return if (!$db);
  871. my $sql = q{
  872. SELECT
  873. COALESCE(c.value, co.default_value) AS value,
  874. co.option_type
  875. FROM config_options co
  876. LEFT JOIN config c ON c.option_id = co.id
  877. WHERE co.id = ?
  878. };
  879. my $record = get_record_sql($db, $sql, $option_id);
  880. unless ($record) {
  881. log_error("Option ID $option_id not found in config_options table");
  882. return;
  883. }
  884. return $record->{value};
  885. }
  886. #---------------------------------------------------------------------------------------------------------------
  887. sub get_records_sql {
  888. my ($db, $sql, @params) = @_;
  889. my @result;
  890. return @result if (!$db);
  891. return @result if (!$sql);
  892. unless (reconnect_db(\$db)) {
  893. log_error("No database connection available");
  894. return @result;
  895. }
  896. my $result_ref = _execute_param($db, $sql, \@params, { mode => 'array' });
  897. if (ref($result_ref) eq 'ARRAY') {
  898. @result = @$result_ref;
  899. }
  900. return @result;
  901. }
  902. #---------------------------------------------------------------------------------------------------------------
  903. sub get_record_sql {
  904. my ($db, $sql, @params) = @_;
  905. my @result;
  906. return @result if (!$db);
  907. return @result if (!$sql);
  908. # Добавляем LIMIT только если его еще нет в запросе
  909. if ($sql !~ /\bLIMIT\s+\d+/i && $sql !~ /\bFETCH\s+FIRST\s+\d+/i) {
  910. $sql .= ' LIMIT 1';
  911. }
  912. # Переподключение
  913. unless (reconnect_db(\$db)) {
  914. log_error("No database connection available");
  915. return;
  916. }
  917. return _execute_param($db, $sql, \@params, { mode => 'single' });
  918. }
  919. #---------------------------------------------------------------------------------------------------------------
  920. sub get_count_records {
  921. my ($db, $table, $filter, @params) = @_;
  922. my $result = 0;
  923. return $result if (!$db);
  924. return $result if (!$table);
  925. my $sSQL='SELECT COUNT(*) as rec_cnt FROM '.$table;
  926. if ($filter) { $sSQL=$sSQL." WHERE ".$filter; }
  927. my $record = get_record_sql($db,$sSQL, @params);
  928. if ($record->{rec_cnt}) { $result = $record->{rec_cnt}; }
  929. return $result;
  930. }
  931. #---------------------------------------------------------------------------------------------------------------
  932. sub get_id_record {
  933. my ($db, $table, $filter, @params) = @_;
  934. my $result = 0;
  935. return $result if (!$db);
  936. return $result if (!$table);
  937. my $record = get_record_sql($db,"SELECT id FROM $table WHERE $filter", @params);
  938. if ($record->{id}) { $result = $record->{id}; }
  939. return $result;
  940. }
  941. #---------------------------------------------------------------------------------------------------------------
  942. sub get_diff_rec {
  943. my ($db, $table, $record, $filter_sql, @filter_params) = @_;
  944. return unless $db && $table && $filter_sql;
  945. unless (reconnect_db(\$db)) {
  946. log_error("No database connection available");
  947. return;
  948. }
  949. my $old_record = get_record_sql($db,"SELECT * FROM $table WHERE $filter_sql",@filter_params);
  950. return unless $old_record;
  951. my $result;
  952. foreach my $field (keys %$record) {
  953. if (!$record->{$field}) { $record->{$field}=''; }
  954. if (!$old_record->{$field}) { $old_record->{$field}=''; }
  955. if ($record->{$field}!~/^$old_record->{$field}$/) { $result->{$field} = "$record->{$field} [ old: " . $old_record->{$field} . "]"; }
  956. }
  957. return hash_to_text($result);
  958. }
  959. #---------------------------------------------------------------------------------------------------------------
  960. sub get_db_type {
  961. my $db = shift;
  962. return lc($db->{Driver}->{Name});
  963. #'mysql', 'pg'
  964. }
  965. #---------------------------------------------------------------------------------------------------------------
  966. # Внутренняя функция для выполнения параметризованных запросов
  967. sub _execute_param {
  968. my ($db, $sql, $params, $options) = @_;
  969. return unless $db && $sql;
  970. my $mode = $options->{mode} || 'execute';
  971. # --- Автоматическая поддержка RETURNING для PostgreSQL ---
  972. my $was_modified = 0;
  973. my $original_sql = $sql;
  974. if ($mode eq 'id' && $sql =~ /^\s*INSERT\b/i) {
  975. if (get_db_type($db) eq 'pg') {
  976. unless ($sql =~ /\bRETURNING\b/i) {
  977. $sql .= ' RETURNING id';
  978. $was_modified = 1;
  979. $mode = 'scalar';
  980. }
  981. }
  982. }
  983. # Логируем не-SELECT
  984. unless ($original_sql =~ /^\s*SELECT/i) {
  985. log_debug($original_sql . ($params ? ' | params: [' . join(', ', map { defined $_ ? $_ : 'undef' } @$params) . ']' : ''));
  986. }
  987. # === не переподключаемся внутри транзакции ===
  988. my $autocommit_enabled = $db->{AutoCommit};
  989. unless ($autocommit_enabled) {
  990. # В транзакции: нельзя переподключаться!
  991. unless ($db->ping) {
  992. log_error("Database connection lost during transaction");
  993. return wantarray ? () : undef;
  994. }
  995. } else {
  996. # Вне транзакции: можно переподключиться
  997. unless (reconnect_db(\$db)) {
  998. log_error("No database connection available");
  999. return wantarray ? () : undef;
  1000. }
  1001. }
  1002. my $sth = $db->prepare($sql) or do {
  1003. log_error("Unable to prepare SQL [$original_sql]: " . $db->errstr);
  1004. return wantarray ? () : undef;
  1005. };
  1006. my $rv = $params ? $sth->execute(@$params) : $sth->execute();
  1007. unless ($rv) {
  1008. log_error("Unable to execute SQL [$original_sql]" . ($params ? " with params: [" . join(', ', map { defined $_ ? $_ : 'undef' } @$params) . "]" : "") . ": " . $sth->errstr);
  1009. $sth->finish();
  1010. return wantarray ? () : undef;
  1011. }
  1012. # --- Обработка результатов ---
  1013. if ($was_modified && $mode eq 'scalar') {
  1014. my $row = $sth->fetchrow_arrayref();
  1015. $sth->finish();
  1016. my $id = $row ? $row->[0] : 0;
  1017. return $id;
  1018. }
  1019. elsif ($mode eq 'single') {
  1020. my $row = $sth->fetchrow_hashref();
  1021. $sth->finish();
  1022. return $row;
  1023. }
  1024. elsif ($mode eq 'array') {
  1025. my @rows;
  1026. while (my $row = $sth->fetchrow_hashref()) {
  1027. push @rows, $row;
  1028. }
  1029. $sth->finish();
  1030. return \@rows;
  1031. }
  1032. elsif ($mode eq 'arrayref') {
  1033. my $rows = $sth->fetchall_arrayref({});
  1034. $sth->finish();
  1035. return $rows;
  1036. }
  1037. elsif ($mode eq 'scalar') {
  1038. my $row = $sth->fetchrow_arrayref();
  1039. $sth->finish();
  1040. return $row ? $row->[0] : undef;
  1041. }
  1042. elsif ($mode eq 'id') {
  1043. if ($original_sql =~ /^\s*INSERT/i) {
  1044. my $id;
  1045. if (get_db_type($db) eq 'mysql') {
  1046. $id = $sth->{mysql_insertid};
  1047. } else {
  1048. ($id) = $db->selectrow_array("SELECT lastval()");
  1049. }
  1050. $sth->finish();
  1051. return $id || 0;
  1052. }
  1053. $sth->finish();
  1054. return 1;
  1055. }
  1056. else {
  1057. $sth->finish();
  1058. return 1;
  1059. }
  1060. }
  1061. #---------------------------------------------------------------------------------------------------------------
  1062. sub do_sql {
  1063. my ($db, $sql, @bind_values) = @_;
  1064. return unless $db && $sql; # Возвращаем undef при ошибке входных данных
  1065. my $mode;
  1066. if ($sql =~ /^\s*insert\b/i) {
  1067. $mode = 'id';
  1068. } elsif ($sql =~ /^\s*select\b/i) {
  1069. $mode = 'arrayref';
  1070. } else {
  1071. $mode = 'execute';
  1072. }
  1073. my $result = _execute_param($db, $sql, \@bind_values, { mode => $mode });
  1074. # Если _execute_param вернул undef/ложь — это ошибка
  1075. unless (defined $result) {
  1076. return; # Возвращаем undef (лучше, чем 0)
  1077. }
  1078. if ($mode eq 'id') {
  1079. return $result; # число (возможно 0 — допустимо для ID)
  1080. } elsif ($mode eq 'arrayref') {
  1081. # _execute_param всегда возвращает ARRAYREF при успехе
  1082. return $result;
  1083. } else {
  1084. # Для UPDATE/DELETE: возвращаем количество затронутых строк или 1
  1085. return $result ? $result : 1;
  1086. }
  1087. }
  1088. #---------------------------------------------------------------------------------------------------------------
  1089. sub insert_record {
  1090. my ($db, $table, $record) = @_;
  1091. return unless $db && $table && ref($record) eq 'HASH' && %$record;
  1092. # Переподключаемся ТОЛЬКО если не в транзакции
  1093. if ($db->{AutoCommit}) {
  1094. unless (reconnect_db(\$db)) {
  1095. log_error("No database connection available");
  1096. return;
  1097. }
  1098. } else {
  1099. unless ($db->ping) {
  1100. log_error("Database connection lost during transaction");
  1101. return;
  1102. }
  1103. }
  1104. my $db_info= build_db_schema($db);
  1105. my $dns_changed = 0;
  1106. my $rec_id = 0;
  1107. if ($table eq "user_auth") {
  1108. foreach my $field (keys %$record) {
  1109. if (exists $acl_fields{$field}) { $record->{changed}="1"; }
  1110. if (exists $dhcp_fields{$field}) { $record->{dhcp_changed}="1"; }
  1111. if (exists $dns_fields{$field}) { $dns_changed=1; }
  1112. }
  1113. }
  1114. my @insert_params;
  1115. my $fields = '';
  1116. my $values = '';
  1117. foreach my $field (keys %$record) {
  1118. my $val = normalize_value($record->{$field}, $db_schema{$db_info->{db_type}}{$db_info->{db_name}}{$table}{$field});
  1119. # Экранируем имя поля в зависимости от СУБД
  1120. my $quoted_field = get_db_type($db) eq 'mysql'
  1121. ? '`' . $field . '`'
  1122. : '"' . $field . '"';
  1123. $fields .= "$quoted_field, ";
  1124. $values .= "?, ";
  1125. push @insert_params, $val;
  1126. }
  1127. $fields =~ s/,\s*$//;
  1128. $values =~ s/,\s*$//;
  1129. my $sSQL = "INSERT INTO $table($fields) VALUES($values)";
  1130. my $result = do_sql($db,$sSQL,@insert_params);
  1131. if ($result) {
  1132. $rec_id = $result;
  1133. my $changed_msg = prepare_audit_message($db, $table, undef, $record, $rec_id, 'insert');
  1134. if ($table !~ /session/i) {
  1135. if (defined $changed_msg && $changed_msg ne '') {
  1136. if ($table !~ /user/i) {
  1137. db_log_info($db, $changed_msg);
  1138. } else {
  1139. if ($table eq 'user_auth' && defined $record->{ip} && $record->{ip} ne '') {
  1140. if (is_hotspot($db, $record->{ip})) {
  1141. db_log_info($db, $changed_msg, $rec_id);
  1142. } else {
  1143. db_log_warning($db, $changed_msg, $rec_id);
  1144. my $send_alert_create = isNotifyCreate(get_notify_subnet($db, $record->{ip}));
  1145. sendEmail("WARN! " . get_first_line($changed_msg), $changed_msg, 1) if $send_alert_create;
  1146. }
  1147. } else {
  1148. db_log_warning($db, $changed_msg);
  1149. }
  1150. }
  1151. }
  1152. if ($table eq 'user_auth_alias' and $dns_changed) {
  1153. if ($record->{'alias'} and $record->{'alias'}!~/\.$/) {
  1154. my $add_dns;
  1155. $add_dns->{'name_type'}='CNAME';
  1156. $add_dns->{'name'}=$record->{'alias'};
  1157. $add_dns->{'value'}=get_dns_name($db,$record->{'auth_id'});
  1158. $add_dns->{'operation_type'}='add';
  1159. $add_dns->{'auth_id'}=$record->{'auth_id'};
  1160. insert_record($db,'dns_queue',$add_dns);
  1161. }
  1162. }
  1163. if ($table eq 'user_auth' and $dns_changed) {
  1164. if ($record->{'dns_name'} and $record->{'ip'} and !$record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  1165. my $add_dns;
  1166. $add_dns->{'name_type'}='A';
  1167. $add_dns->{'name'}=$record->{'dns_name'};
  1168. $add_dns->{'value'}=$record->{'ip'};
  1169. $add_dns->{'operation_type'}='add';
  1170. $add_dns->{'auth_id'}=$result;
  1171. insert_record($db,'dns_queue',$add_dns);
  1172. }
  1173. if ($record->{'dns_name'} and $record->{'ip'} and $record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  1174. my $add_dns;
  1175. $add_dns->{'name_type'}='PTR';
  1176. $add_dns->{'name'}=$record->{'dns_name'};
  1177. $add_dns->{'value'}=$record->{'ip'};
  1178. $add_dns->{'operation_type'}='add';
  1179. $add_dns->{'auth_id'}=$result;
  1180. insert_record($db,'dns_queue',$add_dns);
  1181. }
  1182. }
  1183. }
  1184. }
  1185. return $result;
  1186. }
  1187. #---------------------------------------------------------------------------------------------------------------
  1188. sub update_record {
  1189. my ($db, $table, $record, $filter_sql, @filter_params) = @_;
  1190. return unless $db && $table && $filter_sql;
  1191. # Переподключаемся ТОЛЬКО если не в транзакции
  1192. if ($db->{AutoCommit}) {
  1193. unless (reconnect_db(\$db)) {
  1194. log_error("No database connection available");
  1195. return;
  1196. }
  1197. } else {
  1198. unless ($db->ping) {
  1199. log_error("Database connection lost during transaction");
  1200. return;
  1201. }
  1202. }
  1203. my $db_info = build_db_schema($db);
  1204. my $select_sql = "SELECT * FROM $table WHERE $filter_sql";
  1205. my $old_record = get_record_sql($db, $select_sql, @filter_params);
  1206. return unless $old_record;
  1207. my @update_params;
  1208. my $set_clause = '';
  1209. my $dns_changed = 0;
  1210. my $rec_id = $old_record->{id} || 0;
  1211. if ($table eq "user_auth") {
  1212. $rec_id = $old_record->{'id'} if ($old_record->{'id'});
  1213. my $cur_ou_id = $old_record->{'ou_id'} if ($old_record->{'ou_id'});
  1214. if (exists $record->{ou_id}) { $cur_ou_id = $record->{'ou_id'}; }
  1215. #disable update field 'created_by'
  1216. #if ($old_record->{'created_by'} and exists ($record->{'created_by'})) { delete $record->{'created_by'}; }
  1217. foreach my $field (keys %$record) {
  1218. if (exists $acl_fields{$field}) { $record->{changed}="1"; }
  1219. if (exists $dhcp_fields{$field} and !is_system_ou($db,$cur_ou_id)) { $record->{dhcp_changed}="1"; }
  1220. if (exists $dns_fields{$field}) { $dns_changed=1; }
  1221. }
  1222. }
  1223. for my $field (keys %$record) {
  1224. my $old_val = defined $old_record->{$field} ? $old_record->{$field} : '';
  1225. my $new_val = normalize_value($record->{$field}, $db_schema{$db_info->{db_type}}{$db_info->{db_name}}{$table}{$field});
  1226. if ($new_val ne $old_val) {
  1227. $set_clause .= " $field = ?, ";
  1228. push @update_params, $new_val;
  1229. }
  1230. }
  1231. return 1 unless $set_clause;
  1232. # Добавляем служебные поля
  1233. if ($table eq 'user_auth') {
  1234. $set_clause .= "changed_time = ?, ";
  1235. push @update_params, GetNowTime();
  1236. }
  1237. $set_clause =~ s/,\s*$//;
  1238. if ($table eq 'user_auth') {
  1239. if ($dns_changed) {
  1240. my $del_dns;
  1241. if ($old_record->{'dns_name'} and $old_record->{'ip'} and !$old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
  1242. $del_dns->{'name_type'}='A';
  1243. $del_dns->{'name'}=$old_record->{'dns_name'};
  1244. $del_dns->{'value'}=$old_record->{'ip'};
  1245. $del_dns->{'operation_type'}='del';
  1246. if ($rec_id) { $del_dns->{'auth_id'}=$rec_id; }
  1247. insert_record($db,'dns_queue',$del_dns);
  1248. }
  1249. if ($old_record->{'dns_name'} and $old_record->{'ip'} and $old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
  1250. $del_dns->{'name_type'}='PTR';
  1251. $del_dns->{'name'}=$old_record->{'dns_name'};
  1252. $del_dns->{'value'}=$old_record->{'ip'};
  1253. $del_dns->{'operation_type'}='del';
  1254. if ($rec_id) { $del_dns->{'auth_id'}=$rec_id; }
  1255. insert_record($db,'dns_queue',$del_dns);
  1256. }
  1257. my $new_dns;
  1258. my $dns_rec_ip = $old_record->{ip};
  1259. my $dns_rec_name = $old_record->{dns_name};
  1260. if ($record->{'dns_name'}) { $dns_rec_name = $record->{'dns_name'}; }
  1261. if ($record->{'ip'}) { $dns_rec_ip = $record->{'ip'}; }
  1262. if ($dns_rec_name and $dns_rec_ip and !$record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  1263. $new_dns->{'name_type'}='A';
  1264. $new_dns->{'name'}=$dns_rec_name;
  1265. $new_dns->{'value'}=$dns_rec_ip;
  1266. $new_dns->{'operation_type'}='add';
  1267. if ($rec_id) { $new_dns->{'auth_id'}=$rec_id; }
  1268. insert_record($db,'dns_queue',$new_dns);
  1269. }
  1270. if ($dns_rec_name and $dns_rec_ip and $record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
  1271. $new_dns->{'name_type'}='PTR';
  1272. $new_dns->{'name'}=$dns_rec_name;
  1273. $new_dns->{'value'}=$dns_rec_ip;
  1274. $new_dns->{'operation_type'}='add';
  1275. if ($rec_id) { $new_dns->{'auth_id'}=$rec_id; }
  1276. insert_record($db,'dns_queue',$new_dns);
  1277. }
  1278. }
  1279. }
  1280. if ($table eq 'user_auth_alias') {
  1281. if ($dns_changed) {
  1282. my $del_dns;
  1283. if ($old_record->{'alias'} and $old_record->{'alias'}!~/\.$/) {
  1284. $del_dns->{'name_type'}='CNAME';
  1285. $del_dns->{'name'}=$old_record->{'alias'};
  1286. $del_dns->{'operation_type'}='del';
  1287. $del_dns->{'value'}=get_dns_name($db,$old_record->{auth_id});
  1288. $del_dns->{'auth_id'}=$old_record->{auth_id};
  1289. insert_record($db,'dns_queue',$del_dns);
  1290. }
  1291. my $new_dns;
  1292. my $dns_rec_name = $old_record->{alias};
  1293. if ($record->{'alias'}) { $dns_rec_name = $record->{'alias'}; }
  1294. if ($dns_rec_name and $record->{'alias'}!~/\.$/) {
  1295. $new_dns->{'name_type'}='CNAME';
  1296. $new_dns->{'name'}=$dns_rec_name;
  1297. $new_dns->{'operation_type'}='add';
  1298. $new_dns->{'value'}=get_dns_name($db,$old_record->{auth_id});
  1299. $new_dns->{'auth_id'}=$rec_id;
  1300. insert_record($db,'dns_queue',$new_dns);
  1301. }
  1302. }
  1303. }
  1304. my @all_params = (@update_params, @filter_params);
  1305. my $update_sql = "UPDATE $table SET $set_clause WHERE $filter_sql";
  1306. my $result = do_sql($db, $update_sql, @all_params);
  1307. if ($result) {
  1308. my $changed_msg = prepare_audit_message($db, $table, $old_record, $record , $rec_id, 'update');
  1309. if ($table !~ /session/i) {
  1310. if (defined $changed_msg && $changed_msg ne '') {
  1311. if ($table !~ /user/i) {
  1312. db_log_info($db, $changed_msg);
  1313. } else {
  1314. if (is_hotspot($db, $old_record->{ip})) {
  1315. db_log_info($db, $changed_msg, $rec_id);
  1316. } else {
  1317. db_log_warning($db, $changed_msg, $rec_id);
  1318. if ($table eq 'user_auth' && defined $old_record->{ip} && $old_record->{ip} ne '') {
  1319. my $send_alert_update = isNotifyUpdate(get_notify_subnet($db, $old_record->{ip}));
  1320. sendEmail("WARN! " . get_first_line($changed_msg), $changed_msg, 1) if $send_alert_update;
  1321. }
  1322. }
  1323. }
  1324. }
  1325. }
  1326. }
  1327. return $result;
  1328. }
  1329. #---------------------------------------------------------------------------------------------------------------
  1330. sub delete_record {
  1331. my ($db, $table, $filter_sql, @filter_params) = @_;
  1332. return unless $db && $table && $filter_sql;
  1333. # Переподключаемся ТОЛЬКО если не в транзакции
  1334. if ($db->{AutoCommit}) {
  1335. unless (reconnect_db(\$db)) {
  1336. log_error("No database connection available");
  1337. return;
  1338. }
  1339. } else {
  1340. unless ($db->ping) {
  1341. log_error("Database connection lost during transaction");
  1342. return;
  1343. }
  1344. }
  1345. my $select_sql = "SELECT * FROM $table WHERE $filter_sql";
  1346. my $old_record = get_record_sql($db, $select_sql, @filter_params);
  1347. return unless $old_record;
  1348. my $rec_id = $old_record->{'id'};
  1349. #never delete user ip record!
  1350. if ($table eq 'user_auth') {
  1351. my $sSQL = "UPDATE user_auth SET changed = 1, deleted = 1, changed_time = ? WHERE $filter_sql";
  1352. my $ret = do_sql($db, $sSQL, GetNowTime(), @filter_params);
  1353. if ($old_record->{'dns_name'} and $old_record->{'ip'} and !$old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
  1354. my $del_dns;
  1355. $del_dns->{'name_type'}='A';
  1356. $del_dns->{'name'}=$old_record->{'dns_name'};
  1357. $del_dns->{'value'}=$old_record->{'ip'};
  1358. $del_dns->{'operation_type'}='del';
  1359. $del_dns->{'auth_id'}=$old_record->{'id'};
  1360. insert_record($db,'dns_queue',$del_dns);
  1361. }
  1362. if ($old_record->{'dns_name'} and $old_record->{'ip'} and $old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
  1363. my $del_dns;
  1364. $del_dns->{'name_type'}='PTR';
  1365. $del_dns->{'name'}=$old_record->{'dns_name'};
  1366. $del_dns->{'value'}=$old_record->{'ip'};
  1367. $del_dns->{'operation_type'}='del';
  1368. $del_dns->{'auth_id'}=$old_record->{'id'};
  1369. insert_record($db,'dns_queue',$del_dns);
  1370. }
  1371. my $changed_msg = prepare_audit_message($db, $table, $old_record, undef , $rec_id, 'delete');
  1372. if ($ret) {
  1373. if (defined $changed_msg && $changed_msg ne '') {
  1374. if (defined $old_record->{ip} && $old_record->{ip} ne '') {
  1375. if (is_hotspot($db, $old_record->{ip})) {
  1376. db_log_info($db, $changed_msg, $rec_id);
  1377. } else {
  1378. db_log_warning($db, $changed_msg, $rec_id);
  1379. my $send_alert_delete = isNotifyDelete(get_notify_subnet($db, $old_record->{ip}));
  1380. sendEmail("WARN! " . get_first_line($changed_msg), $changed_msg, 1) if $send_alert_delete;
  1381. }
  1382. }
  1383. }
  1384. }
  1385. return $ret;
  1386. }
  1387. if ($table eq 'user_list' and $old_record->{'permanent'}) { return; }
  1388. if ($table eq 'user_auth_alias') {
  1389. if ($old_record->{'alias'} and $old_record->{'auth_id'} and $old_record->{'alias'}!~/\.$/) {
  1390. my $del_dns;
  1391. $del_dns->{'name_type'}='CNAME';
  1392. $del_dns->{'name'}=$old_record->{'alias'};
  1393. $del_dns->{'value'}=get_dns_name($db,$old_record->{'auth_id'});
  1394. $del_dns->{'operation_type'}='del';
  1395. $del_dns->{'auth_id'}=$old_record->{'auth_id'};
  1396. insert_record($db,'dns_queue',$del_dns);
  1397. }
  1398. }
  1399. my $sSQL = "DELETE FROM ".$table." WHERE ".$filter_sql;
  1400. my $result = do_sql($db,$sSQL,@filter_params);
  1401. my $changed_msg = prepare_audit_message($db, $table, $old_record, undef , $rec_id, 'delete');
  1402. if ($result && $table !~ /session/i) {
  1403. if (defined $changed_msg && $changed_msg ne '') {
  1404. if ($table !~ /user/i) {
  1405. db_log_info($db, $changed_msg);
  1406. } else {
  1407. db_log_warning($db, $changed_msg);
  1408. }
  1409. }
  1410. }
  1411. return $result;
  1412. }
  1413. #---------------------------------------------------------------------------------------------------------------
  1414. sub is_system_ou {
  1415. my ($db, $ou_id) = @_;
  1416. return 0 if !defined $ou_id || $ou_id !~ /^\d+$/ || $ou_id <= 0;
  1417. my $sql = "SELECT 1 FROM ou WHERE id = ? AND (default_users = 1 OR default_hotspot = 1)";
  1418. my $record = get_record_sql($db, $sql, $ou_id);
  1419. return $record ? 1 : 0;
  1420. }
  1421. #---------------------------------------------------------------------------------------------------------------
  1422. sub init_option {
  1423. my $db=shift;
  1424. $last_refresh_config = time();
  1425. $config_ref{version}='';
  1426. my $version_record = get_record_sql($db,"SELECT version FROM version WHERE version is NOT NULL");
  1427. if ($version_record) { $config_ref{version}=$version_record->{version}; }
  1428. $config_ref{self_ip} = '127.0.0.1';
  1429. if ($DBHOST ne '127.0.0.1') {
  1430. my $ip_route = qx(ip r get $DBHOST 2>&1 | head -1);
  1431. if ($? == 0) {
  1432. if ($ip_route =~ /src\s+(\d+\.\d+\.\d+\.\d+)/) { $config_ref{self_ip} = $1; }
  1433. }
  1434. }
  1435. $config_ref{dbh}=$db;
  1436. $config_ref{save_detail}=get_option($db,23);
  1437. $config_ref{add_unknown_user}=get_option($db,22);
  1438. $config_ref{dhcp_server}=get_option($db,5);
  1439. $config_ref{snmp_default_version}=get_option($db,9);
  1440. $config_ref{snmp_default_community}=get_option($db,11);
  1441. $config_ref{KB}=get_option($db,1);
  1442. if ($config_ref{KB} ==0) { $config_ref{KB}=1000; }
  1443. if ($config_ref{KB} ==1) { $config_ref{KB}=1024; }
  1444. $config_ref{admin_email}=get_option($db,21);
  1445. $config_ref{sender_email}=get_option($db,52);
  1446. $config_ref{send_email}=get_option($db,51);
  1447. $config_ref{history}=get_option($db,26);
  1448. $config_ref{history_dhcp}=get_option($db,27);
  1449. $config_ref{router_login}=get_option($db,28);
  1450. $config_ref{router_password}=get_option($db,29);
  1451. $config_ref{router_port}=get_option($db,30);
  1452. $config_ref{org_name}=get_option($db,32);
  1453. $config_ref{domain_name}=get_option($db,33);
  1454. $config_ref{connections_history}=get_option($db,35);
  1455. $config_ref{debug}=get_option($db,34);
  1456. $config_ref{log_level} = get_option($db,53);
  1457. if ($config_ref{debug}) { $config_ref{log_level} = 255; }
  1458. $config_ref{urgent_sync}=get_option($db,50);
  1459. $config_ref{ignore_hotspot_dhcp_log} = get_option($db,44);
  1460. $config_ref{ignore_update_dhcp_event} = get_option($db,45);
  1461. $config_ref{update_hostname_from_dhcp} = get_option($db,46);
  1462. $config_ref{history_log_day}=get_option($db,47);
  1463. $config_ref{history_syslog_day} = get_option($db,48);
  1464. $config_ref{history_trafstat_day} = get_option($db,49);
  1465. $config_ref{enable_quotes} = get_option($db,54);
  1466. $config_ref{netflow_step} = get_option($db,55);
  1467. $config_ref{traffic_ipstat_history} = get_option($db,56);
  1468. $config_ref{nagios_url} = get_option($db,57);
  1469. $config_ref{cacti_url} = get_option($db,58);
  1470. $config_ref{torrus_url} = get_option($db,59);
  1471. $config_ref{wiki_url} = get_option($db,60);
  1472. $config_ref{stat_url} = get_option($db,62);
  1473. $config_ref{wiki_path} = get_option($db,61);
  1474. $config_ref{auto_mac_rule} = get_option($db,64);
  1475. #network configuration mode
  1476. $config_ref{config_mode}=get_option($db,68);
  1477. #auto clean old user record
  1478. $config_ref{clean_empty_user}=get_option($db,69);
  1479. #dns_server_type
  1480. $config_ref{dns_server}=get_option($db,3);
  1481. $config_ref{dns_server_type}=get_option($db,70);
  1482. $config_ref{enable_dns_updates}=get_option($db,71);
  1483. #$save_detail = 1; id=23
  1484. $save_detail=get_option($db,23);
  1485. #$add_unknown_user = 1; id=22
  1486. $add_unknown_user=get_option($db,22);
  1487. #$dns_server='192.168.2.12'; id=3
  1488. $dns_server=get_option($db,3);
  1489. #$dhcp_server='192.168.2.12'; id=5
  1490. $dhcp_server=get_option($db,5);
  1491. #$snmp_default_version='2'; id=9
  1492. $snmp_default_version=get_option($db,9);
  1493. #$snmp_default_community='public'; id=11
  1494. $snmp_default_community=get_option($db,11);
  1495. #$KB=1024; id=1
  1496. $KB=$config_ref{KB};
  1497. #$admin_email; id=21
  1498. $admin_email=get_option($db,21);
  1499. #sender email
  1500. $sender_email=get_option($db,52);
  1501. #send email
  1502. $send_email=get_option($db,51);
  1503. #$history=15; id=26
  1504. $history=get_option($db,26);
  1505. #$history_dhcp=7; id=27
  1506. $history_dhcp=get_option($db,27);
  1507. #$router_login="admin"; id=28
  1508. $router_login=get_option($db,28);
  1509. #$router_password="admin"; id=29
  1510. $router_password=get_option($db,29);
  1511. #$router_port=23; id=30
  1512. $router_port=get_option($db,30);
  1513. #32
  1514. $org_name=get_option($db,32);
  1515. #33
  1516. $domain_name=get_option($db,33);
  1517. #35
  1518. $connections_history=get_option($db,35);
  1519. #debug
  1520. $debug=get_option($db,34);
  1521. #log level
  1522. $log_level = get_option($db,53);
  1523. if ($debug) { $log_level = 255; }
  1524. #urgent sync access
  1525. $urgent_sync=get_option($db,50);
  1526. $ignore_hotspot_dhcp_log = get_option($db,44);
  1527. $ignore_update_dhcp_event = get_option($db,45);
  1528. $update_hostname_from_dhcp = get_option($db,46);
  1529. $history_log_day=get_option($db,47);
  1530. $history_syslog_day = get_option($db,48);
  1531. $history_trafstat_day = get_option($db,49);
  1532. my $ou = get_record_sql($db,"SELECT id FROM ou WHERE default_users = 1");
  1533. if (!$ou) { $default_user_ou_id = 0; } else { $default_user_ou_id = $ou->{'id'}; }
  1534. $ou = get_record_sql($db,"SELECT id FROM ou WHERE default_hotspot = 1 ");
  1535. if (!$ou) { $default_hotspot_ou_id = $default_user_ou_id; } else { $default_hotspot_ou_id = $ou->{'id'}; }
  1536. @subnets=get_records_sql($db,'SELECT * FROM subnets ORDER BY ip_int_start');
  1537. if (defined $office_networks) { undef $office_networks; }
  1538. if (defined $free_networks) { undef $free_networks; }
  1539. if (defined $vpn_networks) { undef $vpn_networks; }
  1540. if (defined $hotspot_networks) { undef $hotspot_networks; }
  1541. if (defined $all_networks) { undef $all_networks; }
  1542. $office_networks = new Net::Patricia;
  1543. $free_networks = new Net::Patricia;
  1544. $vpn_networks = new Net::Patricia;
  1545. $hotspot_networks = new Net::Patricia;
  1546. $all_networks = new Net::Patricia;
  1547. @office_network_list=();
  1548. @free_network_list=();
  1549. @free_network_list=();
  1550. @vpn_network_list=();
  1551. @hotspot_network_list=();
  1552. @all_network_list=();
  1553. foreach my $net (@subnets) {
  1554. next if (!$net->{subnet});
  1555. $subnets_ref{$net->{subnet}}=$net;
  1556. if ($net->{office}) {
  1557. push(@office_network_list,$net->{subnet});
  1558. $office_networks->add_string($net->{subnet},$net);
  1559. }
  1560. if ($net->{free}) {
  1561. push(@free_network_list,$net->{subnet});
  1562. $free_networks->add_string($net->{subnet},$net);
  1563. }
  1564. if ($net->{vpn}) {
  1565. push(@vpn_network_list,$net->{subnet});
  1566. $vpn_networks->add_string($net->{subnet},$net);
  1567. }
  1568. if ($net->{hotspot}) {
  1569. push(@hotspot_network_list,$net->{subnet});
  1570. push(@all_network_list,$net->{subnet});
  1571. $hotspot_networks->add_string($net->{subnet},$net);
  1572. }
  1573. push(@all_network_list,$net->{subnet});
  1574. $all_networks->add_string($net->{subnet},$net);
  1575. }
  1576. }
  1577. #---------------------------------------------------------------------------------------------------------------
  1578. sub Set_Variable {
  1579. my ($db, $name, $value, $timeshift) = @_;
  1580. $name //= $MY_NAME;
  1581. $value //= $$;
  1582. $timeshift //= 60;
  1583. Del_Variable($db, $name);
  1584. my $clean_time = time() + $timeshift;
  1585. my ($sec, $min, $hour, $day, $month, $year) = localtime($clean_time);
  1586. $month++;
  1587. $year += 1900;
  1588. my $clear_time_str = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $year, $month, $day, $hour, $min, $sec;
  1589. my $sql = "INSERT INTO variables (name, value, clear_time) VALUES (?, ?, ?)";
  1590. do_sql($db, $sql, $name, $value, $clear_time_str);
  1591. }
  1592. #---------------------------------------------------------------------------------------------------------------
  1593. sub Get_Variable {
  1594. my $db = shift;
  1595. my $name = shift || $MY_NAME;
  1596. my $variable = get_record_sql($db, 'SELECT value FROM variables WHERE name = ?', $name);
  1597. if ($variable and $variable->{'value'}) { return $variable->{'value'}; }
  1598. return;
  1599. }
  1600. #---------------------------------------------------------------------------------------------------------------
  1601. sub Del_Variable {
  1602. my ($db, $name) = @_;
  1603. $name //= $MY_NAME;
  1604. do_sql($db, "DELETE FROM variables WHERE name = ?", $name);
  1605. }
  1606. #---------------------------------------------------------------------------------------------------------------
  1607. sub clean_variables {
  1608. my ($db) = @_;
  1609. # 1. Clean temporary variables
  1610. my $now = time();
  1611. my ($sec, $min, $hour, $day, $month, $year) = localtime($now);
  1612. $month++;
  1613. $year += 1900;
  1614. my $now_str = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $year, $month, $day, $hour, $min, $sec;
  1615. do_sql($db, "DELETE FROM variables WHERE clear_time <= ?", $now_str);
  1616. # 2. Clean old AD computer cache
  1617. my $yesterday = DateTime->now(time_zone => 'local')->subtract(days => 1);
  1618. my $clean_str = $yesterday->strftime("%Y-%m-%d 00:00:00");
  1619. do_sql($db, "DELETE FROM ad_comp_cache WHERE last_found <= ?", $clean_str);
  1620. }
  1621. #---------------------------------------------------------------------------------------------------------------
  1622. #skip init for upgrade
  1623. if ($MY_NAME!~/upgrade.pl/) {
  1624. $dbh=init_db();
  1625. init_option($dbh);
  1626. clean_variables($dbh);
  1627. Set_Variable($dbh);
  1628. warn "DBI driver name: ", $dbh->{Driver}->{Name}, "\n" if ($debug);
  1629. warn "Full dbh class: ", ref($dbh), "\n" if ($debug);
  1630. }
  1631. 1;
  1632. }