require 'socket' require 'sqlite3' class NntpClient def initialize host @host = host @curgroup = nil reconnect end def reconnect @sock = TCPSocket.new @host, 'nntp' puts ' [+] connected' s = @sock.gets puts s if $VERBOSE group @curgroup if @curgroup end def method_missing(m, *a) if m == :group @curgroup = a.first end cmd = m.to_s << a.map { |s| ' ' << s.to_s }.join puts "> #{cmd}" if $VERBOSE begin @sock.puts cmd rescue return if m == :quit reconnect @sock.puts cmd end str = @sock.gets.chomp! or raise 'partial read' puts str if $VERBOSE # no yield if response != 2xx if block_given? and str[0] == ?2 loop do str = @sock.gets.chomp! or raise 'partial read' return if str == '.' str = str[1..-1] if str[0] == ?. yield str end end str end end class NntpDB attr_reader :db def initialize name @created = true if not File.exist? name @db = SQLite3::Database.new name if @created @db.execute_batch <= min and count < max end } grouplist = db.execute 'select id, name from groups' puts " [+] number of groups : #{grouplist.length}" step = 100000 grpcnt = 0 grouplist.sort_by { |id, name| name }.each { |groupid, groupname| next if ARGV[0] and groupname !~ /#{ARGV[0]}/ groupid = groupid.to_i puts " [+] group #{grpcnt += 1}/#{grouplist.length} : #{groupname}" low, high = db.get_first_row('select low, high from groups where id=?', groupid).map { |i| i.to_i } next if highest_seen[groupid] == high ans = s.group(groupname) resp, count, low, high = ans.split(' ').map { |i| i.to_i } if resp != 211 puts " [-] response #{ans} - deleting" db.transaction { db.execute 'delete from articles where groupid=?', groupid db.execute 'delete from groups where id=?', groupid } next end db.transaction { db.execute 'delete from articles where groupid=? and id high # nil for unchecked groups low = (highest_seen[groupid] or low-1).to_i + 1 while high > low nextlow = low + step nextlow = high if nextlow > high - step/3 retried = 0 puts " [+] xover #{low}-#{nextlow}#{" (#{(nextlow-min)*100/(high-min)}%)" if nextlow != high}" # vim sux " begin db.transaction stmt = db.prepare 'insert into articles (groupid, id, subject, poster, date, msgid, bytes, lines) values (?, ?, ?, ?, ?, ?, ?, ?)' s.xover("#{low}-#{nextlow}") { |e| id, subject, from, date, mid, ref, bytes, lines = e.split "\t" stmt.execute groupid, id.to_i, subject, from, date, mid, bytes.to_i, lines.to_i } db.execute 'update groups set high=? where id=?', nextlow, groupid low = nextlow + 1 rescue stmt.close db.rollback puts " [-] Exception: #{$!.class} : #{$!.message rescue nil}" raise if retried > 4 step /= 2 step = 5000 if step < 5000 retried += 1 s.reconnect retry rescue Object stmt.close db.rollback raise else stmt.close db.commit step *= 2 if retried == 0 and step < 100000 and nextlow != high end end } puts "total number of articles indexed : #{db.get_first_value 'select count(*) from articles'}" ensure db.close s.quit rescue nil end end