1
0

stat-sync.pl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. #!/usr/bin/perl
  2. #
  3. # Copyright (C) Roman Dmitriev, rnd@rajven.ru
  4. #
  5. use utf8;
  6. use warnings;
  7. use Encode;
  8. use open qw(:std :encoding(UTF-8));
  9. no warnings 'utf8';
  10. use English;
  11. use base;
  12. use FindBin '$Bin';
  13. use lib "/opt/Eye/scripts";
  14. use Data::Dumper;
  15. use eyelib::config;
  16. use eyelib::main;
  17. use eyelib::database;
  18. use eyelib::common;
  19. use eyelib::logconfig;
  20. use eyelib::net_utils;
  21. use strict;
  22. use Cwd;
  23. use Net::Netmask;
  24. use DateTime;
  25. my $mute_time=300;
  26. ### Analyze DHCP requests
  27. # === 1. Получение событий из очереди DHCP ===
  28. sub _fetch_dhcp_queue {
  29. my ($dbh) = @_;
  30. my @events = get_records_sql($dbh, "SELECT * FROM dhcp_queue");
  31. log_debug("Fetched " . scalar(@events) . " DHCP event(s) from queue") if @events;
  32. return @events;
  33. }
  34. # === 2. Проверка, нужно ли подавлять событие (mute logic) ===
  35. sub _should_mute_dhcp_event {
  36. my ($ip, $new_action, $leases_ref, $mute_time) = @_;
  37. return 0 unless exists $leases_ref->{$ip};
  38. my $last_event = $leases_ref->{$ip};
  39. my $time_diff = time() - ($last_event->{last_time} // 0);
  40. # Подавляем, если:
  41. # - действие не отличается от предыдущего
  42. # - и прошло меньше $mute_time секунд
  43. if ($last_event->{action} eq $new_action && $time_diff <= $mute_time) {
  44. log_debug("Muting DHCP event for IP $ip: same as recent opposite action (diff: ${time_diff}s)");
  45. return 1;
  46. }
  47. return 0;
  48. }
  49. # === 3. Обработка одного DHCP-события ===
  50. sub _process_single_dhcp_event {
  51. my ($dbh, $event, $leases_ref, $mute_time) = @_;
  52. my $ip = $event->{ip};
  53. my $action = $event->{action};
  54. # Проверка на подавление
  55. if (_should_mute_dhcp_event($ip, $action, $leases_ref, $mute_time)) {
  56. # Всё равно обновляем last_time, чтобы не накапливать старые записи
  57. $leases_ref->{$ip} = $event;
  58. $leases_ref->{$ip}->{last_time} //= time();
  59. return;
  60. }
  61. # Обработка запроса
  62. log_debug("Processing DHCP event: action=$action, ip=$ip, mac=$event->{mac}");
  63. my $dhcp_record = process_dhcp_request(
  64. $dbh,
  65. $action,
  66. $event->{mac},
  67. $ip,
  68. $event->{dhcp_hostname},
  69. '', '', ''
  70. );
  71. # Удаляем из очереди
  72. my $rows = do_sql($dbh, "DELETE FROM dhcp_queue WHERE id = ?", $event->{id});
  73. log_debug("Deleted DHCP event ID $event->{id} from queue (affected rows: $rows)");
  74. # Проверяем, что запись создана/обновлена
  75. if (!$dhcp_record or !$dhcp_record->{auth_id} ){
  76. wrlog($W_ERROR,"User ip auth record not created by DHCP request for ip: $ip mac: $event->{mac}!");
  77. return;
  78. }
  79. # Обновляем кэш последних событий
  80. $leases_ref->{$ip} = $event;
  81. $leases_ref->{$ip}->{last_time} //= time();
  82. }
  83. # === 4. Основная функция обработки очереди DHCP ===
  84. sub process_dhcp_queue {
  85. my ($hdb, $leases_ref, $mute_time) = @_;
  86. # Получаем все события
  87. my @dhcp_events = _fetch_dhcp_queue($hdb);
  88. return unless @dhcp_events;
  89. wrlog($W_INFO,"Processing " . scalar(@dhcp_events) . " DHCP event(s) from queue");
  90. # Обрабатываем каждое событие
  91. foreach my $dhcp (@dhcp_events) {
  92. eval {
  93. _process_single_dhcp_event($hdb, $dhcp, $leases_ref, $mute_time);
  94. };
  95. if ($@) {
  96. wrlog($W_ERROR,"Failed to process DHCP event ID $dhcp->{id}: $@");
  97. # Не прерываем остальные события
  98. }
  99. }
  100. wrlog($W_INFO,"DHCP queue processing completed");
  101. }
  102. ### UPDATE user state
  103. # === 1. Обнуление флагов changed для динамических/хостспот-пользователей ===
  104. sub _reset_changed_flags_for_default_ous {
  105. my ($dbh, $default_user_ou_id, $default_hotspot_ou_id) = @_;
  106. if (!defined $default_user_ou_id || !defined $default_hotspot_ou_id) {
  107. log_warning("Skipping reset of changed flags: default OU IDs not set");
  108. return;
  109. }
  110. my $rows1 = do_sql($dbh, "UPDATE user_auth SET changed = 0 WHERE ou_id = ? OR ou_id = ?", $default_user_ou_id, $default_hotspot_ou_id);
  111. my $rows2 = do_sql($dbh, "UPDATE user_auth SET dhcp_changed = 0 WHERE ou_id = ? OR ou_id = ?", $default_user_ou_id, $default_hotspot_ou_id);
  112. log_debug("Reset 'changed' flags for $rows1 records, 'dhcp_changed' for $rows2 records (default OUs)");
  113. }
  114. # === 2. Сброс флагов changed для IP вне офисных сетей ===
  115. sub _clear_changed_flags_for_non_office_ips {
  116. my ($dbh, $office_networks) = @_;
  117. my @all_changed = get_records_sql($dbh, "SELECT id, ip FROM user_auth WHERE changed = 1 OR dhcp_changed = 1");
  118. return unless @all_changed;
  119. my $cleared = 0;
  120. for my $row (@all_changed) {
  121. next if !$row->{ip};
  122. next if $office_networks->match_string($row->{ip}); # IP в офисной сети — оставляем
  123. my $rows = do_sql($dbh, "UPDATE user_auth SET changed = 0, dhcp_changed = 0 WHERE id = ?", $row->{id});
  124. $cleared += $rows;
  125. }
  126. if ($cleared) {
  127. wrlog($W_INFO,"Cleared 'changed' flags for $cleared records with non-office IPs");
  128. }
  129. }
  130. # === 3. Обработка DHCP-изменений ===
  131. sub _process_dhcp_changes {
  132. my ($dbh) = @_;
  133. my $changed = get_record_sql($dbh, "SELECT COUNT(*) AS c_count FROM user_auth WHERE dhcp_changed = 1");
  134. my $count = $changed ? ($changed->{c_count} // 0) : 0;
  135. return if $count == 0;
  136. wrlog($W_INFO,"Found $count record(s) with dhcp_changed=1");
  137. # Сбрасываем флаги
  138. do_sql($dbh, "UPDATE user_auth SET dhcp_changed = 0");
  139. # Запускаем внешний скрипт
  140. my $dhcp_exec = get_option($dbh, 38);
  141. if (!$dhcp_exec) {
  142. log_warning("DHCP sync script (opt 38) not configured");
  143. return;
  144. }
  145. my %result = do_exec_ref("/usr/bin/sudo $dhcp_exec");
  146. if ($result{status} != 0) {
  147. wrlog($W_ERROR,"DHCP config sync failed: " . ($result{stderr} // 'no error output'));
  148. } else {
  149. wrlog($W_INFO,"DHCP config synced successfully");
  150. }
  151. }
  152. # === 4. Обработка ACL-изменений ===
  153. sub _process_acl_changes {
  154. my ($dbh) = @_;
  155. my $changed = get_record_sql($dbh, "SELECT COUNT(*) AS c_count FROM user_auth WHERE changed = 1");
  156. my $count = $changed ? ($changed->{c_count} // 0) : 0;
  157. return if $count == 0;
  158. wrlog($W_INFO,"Found $count record(s) with changed=1 (ACL/DHCP)");
  159. my $acl_exec = get_option($dbh, 37);
  160. if (!$acl_exec) {
  161. log_warning("ACL sync script (opt 37) not configured");
  162. return;
  163. }
  164. my %result = do_exec_ref("$acl_exec --changes-only");
  165. if ($result{status} != 0) {
  166. wrlog($W_ERROR,"Gateway ACL sync failed: " . ($result{stderr} // 'no error output'));
  167. } else {
  168. wrlog($W_INFO,"Gateway ACL synced successfully");
  169. }
  170. }
  171. # === 5. Обработка DNS-очереди ===
  172. sub _process_dns_queue {
  173. my ($dbh) = @_;
  174. my @dns_changed = get_records_sql($dbh, "SELECT DISTINCT auth_id FROM dns_queue");
  175. return unless @dns_changed;
  176. wrlog($W_INFO,"Processing DNS queue for " . scalar(@dns_changed) . " auth_id(s)");
  177. for my $auth (@dns_changed) {
  178. my $auth_id = $auth->{auth_id};
  179. eval {
  180. update_dns_record($dbh, $auth_id);
  181. do_sql($dbh, "DELETE FROM dns_queue WHERE auth_id = ?", $auth_id);
  182. wrlog($W_INFO,"DNS processed and cleared for auth_id: $auth_id");
  183. };
  184. if ($@) {
  185. wrlog($W_ERROR,"Failed to process DNS for auth_id=$auth_id: $@");
  186. }
  187. }
  188. }
  189. # === 6. Очистка временных записей user_auth ===
  190. sub _cleanup_expired_dynamic_users {
  191. my ($dbh) = @_;
  192. # Используем параметризованный запрос вместо quote()
  193. my $now_str = DateTime->now(time_zone => 'local')->strftime('%Y-%m-%d %H:%M:%S');
  194. my @users_auth = get_records_sql($dbh,
  195. "SELECT id, user_id, end_life FROM user_auth WHERE deleted = 0 AND dynamic = 1 AND end_life <= ?",
  196. $now_str
  197. );
  198. return unless @users_auth;
  199. wrlog($W_INFO,"Cleaning up " . scalar(@users_auth) . " expired dynamic user_auth records");
  200. for my $row (@users_auth) {
  201. eval {
  202. delete_user_auth($dbh, $row->{id});
  203. db_wrlog($W_INFO,$dbh, "Removed dynamic user auth record for auth_id: $row->{id} by end_life time: $row->{end_life}", $row->{id});
  204. # Удаляем пользователя, если больше нет активных auth-записей
  205. my $u_count = get_count_records($dbh, 'user_auth', 'deleted = 0 AND user_id = ?', $row->{user_id});
  206. if ($u_count == 0) {
  207. delete_user($dbh, $row->{user_id});
  208. wrlog($W_INFO,"Deleted orphaned user_id: $row->{user_id}");
  209. }
  210. };
  211. if ($@) {
  212. wrlog($W_ERROR,"Error cleaning up auth_id $row->{id}: $@");
  213. }
  214. }
  215. }
  216. # === 7. Основная функция обновления конфигурации ===
  217. sub refresh_config_if_needed {
  218. my ($hdb, $last_refresh_ref, $default_user_ou_id, $default_hotspot_ou_id, $office_networks) = @_;
  219. return if time() - $$last_refresh_ref < 60;
  220. log_debug("Starting config refresh cycle");
  221. # Обновляем опции
  222. init_option($hdb);
  223. my $urgent_sync = get_option($hdb, 50);
  224. if ($urgent_sync) {
  225. wrlog($W_INFO,"Urgent sync triggered (option 50)");
  226. _reset_changed_flags_for_default_ous($hdb, $default_user_ou_id, $default_hotspot_ou_id);
  227. _clear_changed_flags_for_non_office_ips($hdb, $office_networks);
  228. _process_dhcp_changes($hdb);
  229. _process_acl_changes($hdb);
  230. }
  231. _process_dns_queue($hdb);
  232. _cleanup_expired_dynamic_users($hdb);
  233. $$last_refresh_ref = time();
  234. log_debug("Config refresh cycle completed");
  235. }
  236. wrlog($W_INFO,"Starting...");
  237. setpriority(0,0,19);
  238. my %leases;
  239. while (1) {
  240. eval {
  241. # Create new database handle. If we can't connect, die()
  242. my $hdb = init_db();
  243. # Process DHCP queue every 10 seconds
  244. process_dhcp_queue($hdb, \%leases, $mute_time);
  245. # Update state every 60 seconds
  246. refresh_config_if_needed(
  247. $hdb,
  248. \$last_refresh_config,
  249. $default_user_ou_id,
  250. $default_hotspot_ou_id,
  251. $office_networks
  252. );
  253. sleep(10);
  254. };
  255. if ($@) { wrlog($W_ERROR,"Exception found: $@"); sleep(300); }
  256. }
  257. wrlog($W_INFO,"Process stopped.");
  258. exit 0;