You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

grep.lua 4.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. --[[
  2. Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com>
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ]]--
  13. local argparse = require "argparse"
  14. -- Define command line options
  15. local parser = argparse()
  16. :name "rspamadm grep"
  17. :description "Search for patterns in rspamd logs"
  18. :help_description_margin(30)
  19. parser:mutex(
  20. parser:option "-s --string"
  21. :description('Plain string to search (case-insensitive)')
  22. :argname "<str>",
  23. parser:option "-p --pattern"
  24. :description('Pattern to search for (regex)')
  25. :argname "<re>"
  26. )
  27. parser:flag "-l --lua"
  28. :description('Use Lua patterns in string search')
  29. parser:argument "input":args "*"
  30. :description('Process specified inputs')
  31. :default("stdin")
  32. parser:flag "-S --sensitive"
  33. :description('Enable case-sensitivity in string search')
  34. parser:flag "-o --orphans"
  35. :description('Print orphaned logs')
  36. parser:flag "-P --partial"
  37. :description('Print partial logs')
  38. local function handler(args)
  39. local rspamd_regexp = require 'rspamd_regexp'
  40. local res = parser:parse(args)
  41. if not res['string'] and not res['pattern'] then
  42. parser:error('string or pattern options must be specified')
  43. end
  44. if res['string'] and res['pattern'] then
  45. parser:error('string and pattern options are mutually exclusive')
  46. end
  47. local buffer = {}
  48. local matches = {}
  49. local pattern = res['pattern']
  50. local re
  51. if pattern then
  52. re = rspamd_regexp.create(pattern)
  53. if not re then
  54. io.stderr:write("Couldn't compile regex: " .. pattern .. '\n')
  55. os.exit(1)
  56. end
  57. end
  58. local plainm = true
  59. if res['lua'] then
  60. plainm = false
  61. end
  62. local orphans = res['orphans']
  63. local search_str = res['string']
  64. local sensitive = res['sensitive']
  65. local partial = res['partial']
  66. if search_str and not sensitive then
  67. search_str = string.lower(search_str)
  68. end
  69. local inputs = res['input'] or { 'stdin' }
  70. for _, n in ipairs(inputs) do
  71. local h, err
  72. if string.match(n, '%.xz$') then
  73. h, err = io.popen('xzcat ' .. n, 'r')
  74. elseif string.match(n, '%.bz2$') then
  75. h, err = io.popen('bzcat ' .. n, 'r')
  76. elseif string.match(n, '%.gz$') then
  77. h, err = io.popen('zcat ' .. n, 'r')
  78. elseif string.match(n, '%.zst$') then
  79. h, err = io.popen('zstdcat ' .. n, 'r')
  80. elseif n == 'stdin' then
  81. h = io.input()
  82. else
  83. h, err = io.open(n, 'r')
  84. end
  85. if not h then
  86. if err then
  87. io.stderr:write("Couldn't open file (" .. n .. '): ' .. err .. '\n')
  88. else
  89. io.stderr:write("Couldn't open file (" .. n .. '): no error\n')
  90. end
  91. else
  92. for line in h:lines() do
  93. local hash = string.match(line, '<(%x+)>')
  94. local already_matching = false
  95. if hash then
  96. if matches[hash] then
  97. table.insert(matches[hash], line)
  98. already_matching = true
  99. else
  100. if buffer[hash] then
  101. table.insert(buffer[hash], line)
  102. else
  103. buffer[hash] = { line }
  104. end
  105. end
  106. end
  107. local ismatch = false
  108. if re then
  109. ismatch = re:match(line)
  110. elseif sensitive and search_str then
  111. ismatch = string.find(line, search_str, 1, plainm)
  112. elseif search_str then
  113. local lwr = string.lower(line)
  114. ismatch = string.find(lwr, search_str, 1, plainm)
  115. end
  116. if ismatch then
  117. if not hash then
  118. if orphans then
  119. print('*** orphaned ***')
  120. print(line)
  121. print()
  122. end
  123. elseif not already_matching then
  124. matches[hash] = buffer[hash]
  125. end
  126. end
  127. local is_end = string.match(line, '<%x+>; task; rspamd_protocol_http_reply:')
  128. if is_end then
  129. buffer[hash] = nil
  130. if matches[hash] then
  131. for _, v in ipairs(matches[hash]) do
  132. print(v)
  133. end
  134. print()
  135. matches[hash] = nil
  136. end
  137. end
  138. end
  139. if partial then
  140. for k, v in pairs(matches) do
  141. print('*** partial ***')
  142. for _, vv in ipairs(v) do
  143. print(vv)
  144. end
  145. print()
  146. matches[k] = nil
  147. end
  148. else
  149. matches = {}
  150. end
  151. end
  152. end
  153. end
  154. return {
  155. handler = handler,
  156. description = parser._description,
  157. name = 'grep'
  158. }