wmbiff: `regulo_match` modifies `instructions[i].destination`, so remove `const`.
[dockapps.git] / wmbiff / scripts / security.debian.rb
blobbf403cb8318539a28e5af5c406e7f8cd07046e55
1 #! /usr/bin/ruby
3 # Copyright 2002 Neil Spring <nspring@cs.washington.edu>
4 # GPL
5 # report bugs to wmbiff-devel@lists.sourceforge.net
6 # or (preferred) use the debian BTS via 'reportbug'
8 # Based on security-update-check.py by Rob Bradford
10 require 'net/ftp'
12 #require 'profile'
14 # re-fetch interval - only bug the server once every hour.
15 # allows wmbiff to ask us often how many packages have been
16 # updated so that the number goes back to cyan (old) from
17 # yellow (new) quickly on upgrade.
19 # this still doesn't mean we grab the whole file.  we get
20 # if-modified-since.  it just means we don't connect to the
21 # server more often than this.
22 # 6 hours * 60 min/hour * 60 sec/min
23 Refetch_Interval_Sec = 6 * 60 * 60
25 # as an ordinary user, we store Packages in the home directory.
26 Cachedir = ENV['HOME'] + '/.wmbiff-sdr'
28 # look for updates from this server.  This script is designed around
29 # (and simplified greatly by) using just a single server.
30 Server = 'security.debian.org'
32 # extend the Array class with a max method.
33 class Array
34   def inject(n)
35     each { |value| n = yield(n, value) }
36     n
37   end
38   def max
39     inject(0) { |n, value| ((n > value) ? n : value) }
40   end
41 end
43 def debugmsg(str)
44   $stderr.puts str if($VERBOSE)
45 end
47 # to be reimplemented without execing touch.
48 def touch(filename)
49   debugmsg "touching #{filename}"
50   Kernel.system('/usr/bin/touch ' + filename)
51 end
53 # to be reimplemented without execing dpkg, though running
54 # dpkg excessively doesn't seem to be a bottleneck.
55 def version_a_gt_b(a, b)
56   cmd = "/usr/bin/dpkg --compare-versions %s le %s" % [ a, b ]
57   # $stderr.puts cmd
58   return (!Kernel.system(cmd))
59 end
61 # figure out which lists to check
62 # there can be many implementations of
63 # this behavior, this seemed simplest.
66 # we're going to make an array of arrays, for each package
67 # file, the url, the system's cache of the file, and a
68 # per-user cache of the file.
69 packagelists = Dir.glob("/var/lib/apt/lists/#{Server}*Packages").map { |pkgfile|
70   [ '/debian-security' + pkgfile.gsub(/.*#{Server}/, '').tr('_','/').gsub(/Packages/, ''), # the url path
71     pkgfile,  # the system cache of the packages file.  probably up-to-date.
72     # and finally, a user's cache of the page, if needed.
73     "%s/%s" % [ Cachedir, pkgfile.gsub(/.*#{Server}_/,'') ]
74   ]
77 # update the user's cache if necessary.
78 packagelists.each { |urlpath, sc, uc|
79   sctime = File.stat(sc).mtime
80   cached_time =
81     if(test(?e, uc)) then
82       uctime = File.stat(uc).mtime
83       if ( uctime < sctime ) then
84         # we have a user cache, but it is older than the system cache
85         File.unlink(uc)  # delete the obsolete user cache.
86         sctime
87       else
88         uctime
89       end
90     else
91       sctime
92     end
93   if(Time.now > cached_time + Refetch_Interval_Sec) then
94     debugmsg "fetching #{urlpath} %s > %s + %d" % [Time.now, cached_time, Refetch_Interval_Sec]
95     begin
96       test(?e, Cachedir) or Dir.mkdir(Cachedir)
98       ftp = Net::FTP.new(Server)
99       ftp.login
100       ftp.chdir(urlpath)
101       ftp.getbinaryfile('Packages.gz', uc + '.gz', 1024)
102       ftp.close
104       # need to unzip Packages.gz
105       cmd_gunzip = "gzip -df %s" % [ uc + '.gz' ]
106       Kernel.system(cmd_gunzip)
108       rescue SocketError => e
109         # if the net is down, we'll get this error; avoid printing a stack trace.
110         puts "XX old"
111         puts e
112         exit 1;
113       rescue Timeout::Error => e
114         # if the net is down, we might get this error instead.
115         # but there is no good reason to print the specific exception. (execution expired)
116         puts "XX old"
117         exit 1;
118       end
119       debugmsg "urlpath updated"
120   else
121     debugmsg "skipping #{urlpath}"
122   end
125 available = Hash.new
126 package = nil
127 packagelists.each { |url, sc, uc|
128   File.open( (test(?e, uc)) ? uc : sc, 'r').each { |ln|
129     if(m = /^Package: (.*)/.match(ln)) then
130       package = m[1]
131     elsif(m = /^Version: (.*)/.match(ln)) then
132       available[package] = m[1]
133     end
134   }
137 installed = Hash.new
138 package = nil
139 isinstalled = false
140 File.open('/var/lib/dpkg/status').each { |ln|
141   if(m = /^Package: (.*)$/.match(ln)) then
142     package = m[1]
143     isinstalled = false # reset
144   elsif(m = /^Status: install ok installed/.match(ln)) then
145     isinstalled = true
146   elsif(m = /^Version: (.*)$/.match(ln)) then
147     isinstalled && installed[package] = m[1]
148   end
151 debugmsg "%d installed, %d available" % [ installed.length, available.length ]
153 updatedcount = 0
154 updated = Array.new
155 ( installed.keys & available.keys ).each { |pkg|
156   if(version_a_gt_b(available[pkg], installed[pkg])) then
157     updatedcount += 1
158     updated.push(pkg + ": #{available[pkg]} > #{installed[pkg]}")
159   end
162 # we're done.  output a count in the format expected by wmbiff.
163 if(updatedcount > 0) then
164   puts "%d new" % [ updatedcount ]
165 else
166   puts "%d old" % [ installed.length ]
169 puts updated.join("\n")