Remove trailing whitespace.
[dockapps.git] / wmbiff / scripts / security.debian.rb
blob11d2362ef945553537a4ba5d4392f005f3a25ac7
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/http'
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   [ pkgfile.gsub(/.*#{Server}/, '').tr('_','/'), # 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 # we'll open a persistent session, but only if we need it.
78 session = nil
80 # update the user's cache if necessary.
81 packagelists.each { |urlpath, sc, uc|
82   sctime = File.stat(sc).mtime
83   cached_time =
84     if(test(?e, uc)) then
85       uctime = File.stat(uc).mtime
86       if ( uctime < sctime ) then
87         # we have a user cache, but it is older than the system cache
88         File.unlink(uc)  # delete the obsolete user cache.
89         sctime
90       else
91         uctime
92       end
93     else
94       # the user cache doesn't exist, but we might have
95       # talked to the server recently.
96       if(test(?e, uc + '.stamp')) then
97         File.stat(uc + '.stamp').mtime
98       else
99         sctime
100       end
101     end
102   if(Time.now > cached_time + Refetch_Interval_Sec) then
103     debugmsg "fetching #{urlpath} %s > %s + %d" % [Time.now, cached_time, Refetch_Interval_Sec]
104     begin
105       if(session == nil) then
106         session = Net::HTTP.new(Server)
107         # session.set_pipe($stderr);
108       end
109       begin
110         # the warning with ruby1.8 on the following line
111         # has to do with the resp, data bit, which should
112         # eventually be replaced with (copied from the
113         # docs with the 1.8 net/http.rb)
114         #         response = http.get('/index.html')
115         #         puts response.body
116         resp, data = session.get(urlpath,
117                                  { 'If-Modified-Since' =>
118                                    cached_time.strftime( "%a, %d %b %Y %H:%M:%S GMT" ) })
119       rescue SocketError => e
120         # if the net is down, we'll get this error; avoid printing a stack trace.
121         puts "XX old"
122         puts e
123         exit 1;
124       rescue Timeout::Error => e
125         # if the net is down, we might get this error instead.
126         # but there is no good reason to print the specific exception. (execution expired)
127         puts "XX old"
128         exit 1;
129       end
130       test(?e, Cachedir) or Dir.mkdir(Cachedir)
131       File.open(uc, 'w') { |o| o.puts data }
132       test(?e, uc + '.stamp') and File.unlink(uc + '.stamp')  # we have a copy, don't need the stamp.
133       debugmsg "urlpath updated"
134     rescue Net::ProtoRetriableError => detail
135       head = detail.data
136       if head.code != "304"
137         raise "unexpected error occurred: " + detail
138       end
139       test(?e, Cachedir) or Dir.mkdir(Cachedir)
140       if(test(?e, uc)) then
141         touch(uc)
142       else
143         # we didn't get an update, but we don't have a cached
144         # copy in the user directory.
145         touch(uc + '.stamp')
146       end
147     end
148   else
149     debugmsg "skipping #{urlpath}"
150   end
153 available = Hash.new
154 package = nil
155 packagelists.each { |url, sc, uc|
156   File.open( (test(?e, uc)) ? uc : sc, 'r').each { |ln|
157     if(m = /^Package: (.*)/.match(ln)) then
158       package = m[1]
159     elsif(m = /^Version: (.*)/.match(ln)) then
160       available[package] = m[1]
161     end
162   }
165 installed = Hash.new
166 package = nil
167 isinstalled = false
168 File.open('/var/lib/dpkg/status').each { |ln|
169   if(m = /^Package: (.*)$/.match(ln)) then
170     package = m[1]
171     isinstalled = false # reset
172   elsif(m = /^Status: install ok installed/.match(ln)) then
173     isinstalled = true
174   elsif(m = /^Version: (.*)$/.match(ln)) then
175     isinstalled && installed[package] = m[1]
176   end
179 debugmsg "%d installed, %d available" % [ installed.length, available.length ]
181 updatedcount = 0
182 updated = Array.new
183 ( installed.keys & available.keys ).each { |pkg|
184   if(version_a_gt_b(available[pkg], installed[pkg])) then
185     updatedcount += 1
186     updated.push(pkg + ": #{available[pkg]} > #{installed[pkg]}")
187   end
190 # we're done.  output a count in the format expected by wmbiff.
191 if(updatedcount > 0) then
192   puts "%d new" % [ updatedcount ]
193 else
194   puts "%d old" % [ installed.length ]
197 puts updated.join("\n")