simple-gnupg-keyserver 1.3.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,239 @@
1
+
2
+ module SimpleHKP
3
+
4
+ module Keys
5
+
6
+ def saveLastKey(lastKey)
7
+ lastKey['colonData'].gsub!(/\\x3a/,':') if
8
+ lastKey.has_key?('colonData')
9
+ pp lastKey if @debug
10
+ @keyLookupData[lastKey['keyID']] = lastKey unless
11
+ lastKey.empty? || lastKey['keyID'].empty?
12
+ end
13
+
14
+ def loadKeys
15
+ @keyLookupData = Hash.new
16
+ puts "SimpleHKP: loading keys" if @debug
17
+ gpg2cmd = "gpg2 --homedir #{@keyDir} --with-fingerprint --with-colons --list-sigs"
18
+ puts gpg2cmd if @debug
19
+ lastKey = Hash.new
20
+ IO.popen(gpg2cmd, 'r').readlines.each do | aLine |
21
+ next if aLine =~ /^tru/
22
+ if aLine =~ /^pub/ then
23
+ saveLastKey(lastKey)
24
+ lastKey = Hash.new
25
+ keyData = aLine.split(/:/)
26
+ lastKey['userID'] = keyData[9].gsub(/\\x3a/,':') # user ID
27
+ lastKey['keyID'] = keyData[4] # key ID
28
+ lastKey['algorithm'] = keyData[3] # key type (algorithm)
29
+ lastKey['len'] = keyData[2] # key length
30
+ lastKey['created'] = keyData[5] # creation date
31
+ lastKey['expires'] = keyData[6] # expiration date
32
+ lastKey['flags'] = keyData[1] # flags
33
+ lastKey['colonData'] = aLine
34
+ next
35
+ end
36
+ lastKey['userID'] = aLine.split(/:/)[9].gsub(/\\x3a/,':') if
37
+ aLine =~ /^uid/ && lastKey['userID'].empty?
38
+ lastKey['colonData'] << aLine
39
+ end
40
+ saveLastKey(lastKey)
41
+ @keyLookupData.each_pair do | key, value |
42
+ value['humanData'] = `gpg2 --list-sig #{key}`
43
+ end
44
+ pp @keyLookupData if @debug
45
+ puts "SimpleHKP: finished loading keys" if @debug
46
+ end
47
+
48
+ def storeKey(env)
49
+ #
50
+ # decode the post data
51
+ #
52
+ keys = URI.decode_www_form(env['rack.input'].read)
53
+ #
54
+ # ensure we are being sent a key
55
+ #
56
+ return replyBadRequest("No keytext field in post data") unless
57
+ keys[0][0] =~ /keytext/
58
+ pp keys[0][1] if @debug
59
+ #
60
+ # send the key data through gpg2 to import it
61
+ #
62
+ gpgStdErr = ""
63
+ Open3.popen3("gpg2 --homedir #{@keyDir} --import") do | stdin, stdout, stderr, wait_thread |
64
+ stdin.write(keys[0][1])
65
+ stdin.close
66
+ gpgStdErr = stderr.read
67
+ end
68
+ #
69
+ # check to see if the key has already been imported and/or changed
70
+ #
71
+ puts "[[#{gpgStdErr}]]" if @debug
72
+ keyChanged = "changed"
73
+ if gpgStdErr =~ /not\s+changed/ then
74
+ keyChanged = "unchanged"
75
+ else
76
+ # something has changed to re-load the keys
77
+ loadKeys
78
+ end
79
+ #
80
+ # send the key data through gpg2 (again ;-( to extract the keyID
81
+ #
82
+ gpgKeyData = ""
83
+ IO.popen('gpg2 --with-fingerprint --with-colons -', 'r+') do | pipe |
84
+ puts 'gpg2 --with-fingerprint --with-colons -' if @debug
85
+ pipe.write(keys[0][1])
86
+ pipe.close_write
87
+ gpgKeyData = pipe.read
88
+ end
89
+ puts gpgKeyData if @debug
90
+ #
91
+ # look for the keyID of the public key
92
+ #
93
+ keyID = nil
94
+ gpgKeyData.each_line do | aLine |
95
+ keyData = aLine.split(/:/)
96
+ next unless keyData[0] =~ /pub/
97
+ keyID = keyData[4]
98
+ break
99
+ end
100
+ return replyBadRequest("No keyID found in gpg2 result using uploaded keytext data") if keyID.nil?
101
+ #
102
+ # return OK
103
+ #
104
+ @statusCode = 200
105
+ @body << @headerHtml
106
+ @body << "<h1 class=\"simpleHKP-storedKey\">Stored key: [#{keyID}]</h1>"
107
+ @body << "<p>key #{keyChanged}</p>"
108
+ @body << "<h2 class=\"simpleHKP-keyDataKeyID\">Key data for key: [#{keyID}]</h2>"
109
+ @body << '<pre class="simpleHKP-keyData">'+@keyLookupData[keyID]['colonData']+"</pre>\n"
110
+ @body << '<pre class="simpleHKP-keyData">'+@keyLookupData[keyID]['humanData']+"</pre>\n"
111
+ @body << @footer
112
+ end
113
+
114
+ def lookUpKey(queryString)
115
+ return replyBadRequest("No search field in queryString") unless
116
+ queryString.has_key?('search')
117
+ #
118
+ # normalize the search string
119
+ #
120
+ searchString = queryString['search']
121
+ searchString.gsub!(/0x/,'')
122
+ searchRegexp = Regexp.new(searchString,
123
+ Regexp::IGNORECASE | Regexp::MULTILINE)
124
+ puts searchRegexp if @debug
125
+ #
126
+ # (linearly) look through the hash of known keys
127
+ # looking for the FIRST match
128
+ #
129
+ keyID = nil
130
+ @keyLookupData.each_pair do | key, keyData |
131
+ next unless keyData['colonData'] =~ searchRegexp
132
+ puts "FOUND #{key} (#{keyData})" if @debug
133
+ keyID = key
134
+ break
135
+ end
136
+ return replyNotFound if keyID.nil?
137
+
138
+ if queryString.has_key?('options') &&
139
+ queryString['options'] == 'mr' then
140
+ #
141
+ # return the key data in machine readable format
142
+ #
143
+ @header = {
144
+ 'Content-Type' => 'application/pgp-keys; charset=utf-8',
145
+ 'Content-Disposition' =>
146
+ 'attachment; filename=' + keyID
147
+ }
148
+ puts @header if @debug
149
+ @body << `gpg2 --homedir #{@keyDir} --armor --export #{keyID}`
150
+ else
151
+ #
152
+ # return the key data for a human to read
153
+ #
154
+ @body << @headerHtml
155
+ @body << @lookupKeysForm
156
+ @body << '<h1 class="simpleHKP-keyAsckeyId">Key: '+keyID+'</h1>'
157
+ @body << '<h2 class="simpleHKP-keyDataTitle">Key data:</h2>'
158
+ @body << '<pre class="simpleHKP-keyData">'
159
+ @body << @keyLookupData[keyID]['humanData']
160
+ @body << '</pre>'
161
+ @body << '<h2 class="simpleHKP-keyAscTitle">Key contents:</h2>'
162
+ @body << '<pre class="simpleHKP-keyAsc">'
163
+ @body << `gpg2 --homedir #{@keyDir} --armor --export #{keyID}`
164
+ @body << '</pre>'
165
+ @body << @footer
166
+ end
167
+ end
168
+
169
+ def indexKeys(queryString)
170
+ return replyBadRequest("No search field in queryString") unless
171
+ queryString.has_key?('search')
172
+ #
173
+ # normalize the search string
174
+ #
175
+ searchString = queryString['search']
176
+ searchString.gsub!(/0x/,'')
177
+ searchRegexp = Regexp.new(searchString,
178
+ Regexp::IGNORECASE | Regexp::MULTILINE)
179
+ puts searchRegexp if @debug
180
+ #
181
+ # accumulate the keyIDs of any keys that match the serach
182
+ #
183
+ keys = Array.new
184
+ @keyLookupData.each_pair do | key, keyData |
185
+ next unless keyData['colonData'] =~ searchRegexp
186
+ puts "FOUND #{key} (#{keyData})" if @debug
187
+ keys.push(key)
188
+ end
189
+
190
+ if queryString.has_key?('options') &&
191
+ queryString['options'] == 'mr' then
192
+ #
193
+ # return a machine readable list of the keys found
194
+ #
195
+ @header = { 'Content-Type' => 'text/plain' }
196
+ @body << "info:1:#{keys.size}\n"
197
+ keys.each do | aKey |
198
+ next unless @keyLookupData.has_key?(aKey)
199
+ keyData = @keyLookupData[aKey]
200
+ pubData = [ "pub" ]
201
+ pubData << aKey
202
+ pubData << keyData['algorithm']
203
+ pubData << keyData['len']
204
+ pubData << keyData['created']
205
+ pubData << keyData['expires']
206
+ pubData << keyData['flags']
207
+ @body << pubData.join(':')
208
+ @body << "\n"
209
+ uidData = [ "uid" ]
210
+ uidData << keyData['userID'].gsub(/:/, '\x3a')
211
+ uidData << keyData['created']
212
+ uidData << keyData['expires']
213
+ uidData << keyData['flags']
214
+ @body << uidData.join(':')
215
+ @body << "\n"
216
+ end
217
+ else
218
+ #
219
+ # return a (simple) human readable list of the keys found
220
+ #
221
+ @body << @headerHtml
222
+ @body << @lookupKeysForm
223
+ @body << '<h1 class="simpleHKP-searchString">Keys matching: ['+searchString+']</h1><ul class="simpleHKP-searchList">'
224
+ keys.each do | aKey |
225
+ next unless @keyLookupData.has_key?(aKey)
226
+ keyData = @keyLookupData[aKey]
227
+ keyStr = " <li class=\"simpleHKP-searchItem\"><a href=\"lookup?op=get&search=#{aKey}\">#{aKey}:&nbsp;"
228
+ keyStr << keyData['userID']
229
+ keyStr << "</a></li>\n"
230
+ @body << keyStr
231
+ end
232
+ @body << '</ul>'
233
+ @body << @footer
234
+ end
235
+ pp @body if @debug
236
+ end
237
+
238
+ end
239
+ end
@@ -0,0 +1,349 @@
1
+ # Copyright (C) 2015 Stephen Gaito
2
+ #
3
+ # (The MIT License)
4
+ #
5
+ # Copyright (c) 2015 Stephen Gaito
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a
8
+ # copy of this software and associated documentation files (the
9
+ # 'Software'), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be included
16
+ # in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS
19
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ module SimpleHKP
27
+ class Server
28
+ include Keys
29
+ include Identities
30
+
31
+ def loadHtml
32
+ puts "SimpleHKP: loading html partials" if @debug
33
+ Dir.chdir(@htmlDir) do
34
+ @headerHtml = "<html><head>"
35
+ @headerHtml << " <title>#{@title}</title>\n"
36
+ @headerHtml << "</head><body class=\"simpleHKP-body\">\n"
37
+ @headerHtml << "<p><a href=\"/\">#{@title}</a></p>"
38
+ @headerHtml = File.open('header.html','r').read if
39
+ File.exists?('header.html')
40
+ puts @headerHtml if @debug
41
+
42
+ @defaultBody = ""
43
+ @defaultBody << "<h1 class=\"simpleHKP-welcome\">Welcome to SimpleHKP</h1>\n"
44
+ @defaultBody << "<ul class=\"simpleHKP-tasksList\">\n"
45
+ @defaultBody << " <li class=\"simpleHKP-taskItem\"><a href=\"/pks/lookup?op=form\">Search keys</a></li>\n"
46
+ @defaultBody << " <li class=\"simpleHKP-taskItem\"><a href=\"/pks/add\">Upload new key</a></li>\n"
47
+ @defaultBody << " <li class=\"simpleHKP-taskItem\"><a href=\"/pis/lookup?op=form\">Search identities</a></li>\n"
48
+ @defaultBody << " <li class=\"simpleHKP-taskItem\"><a href=\"/pis/add\">Upload new identity</a></li>\n"
49
+ @defaultBody << " <li class=\"simpleHKP-taskItem\"><a href=\"reload\">Reload existing keys and identities</a></li>\n"
50
+ @defaultBody << "</ul>\n"
51
+ @defaultBody = File.open('defaultBody.html','r').read if
52
+ File.exists?('defaultBody.html')
53
+ puts @defaultBody if @debug
54
+
55
+ @lookupKeysForm = ""
56
+ @lookupKeysForm << "<div class=\"simpleHKP-lookupKeysFormDiv\">\n"
57
+ @lookupKeysForm << " <h1 class=\"simpleHKP-lookupKeysFormTitle\">Search GnuPG keys</h1>\n"
58
+ @lookupKeysForm << " <form action=\"/pks/lookup\" method=\"get\" id=\"simpleHKP-lookupKeysForm\">\n"
59
+ @lookupKeysForm << " <input type=\"text\" name=\"search\" size=\"80\" />\n"
60
+ @lookupKeysForm << " <input type=\"hidden\" name=\"op\" value=\"index\" />\n"
61
+ @lookupKeysForm << " <input type=\"submit\" value=\"Search\" />\n"
62
+ @lookupKeysForm << " </form>\n"
63
+ @lookupKeysForm << "</div>\n"
64
+ @lookupKeysForm = File.open('lookupKeysForm.html','r').read if
65
+ File.exists?('lookupKeysForm.html')
66
+ puts @lookupKeysForm if @debug
67
+
68
+ @lookupIdentitiesForm = ""
69
+ @lookupIdentitiesForm << "<div class=\"simpleHKP-lookupIdentitiesFormDiv\">\n"
70
+ @lookupIdentitiesForm << " <h1 class=\"simpleHKP-lookupIdentitiesFormTitle\">Search GnuPG keys</h1>\n"
71
+ @lookupIdentitiesForm << " <form action=\"/pis/lookup\" method=\"get\" id=\"simpleHKP-lookupIdentitiesForm\">\n"
72
+ @lookupIdentitiesForm << " <input type=\"text\" name=\"search\" size=\"80\" />\n"
73
+ @lookupIdentitiesForm << " <input type=\"hidden\" name=\"op\" value=\"index\" />\n"
74
+ @lookupIdentitiesForm << " <input type=\"submit\" value=\"Search\" />\n"
75
+ @lookupIdentitiesForm << " </form>\n"
76
+ @lookupIdentitiesForm << "</div>\n"
77
+ @lookupIdentitiesForm = File.open('lookupIdentitiesForm.html','r').read if
78
+ File.exists?('lookupIdentitiesForm.html')
79
+ puts @lookupIdentitiesForm if @debug
80
+
81
+ @uploadKeyForm = ""
82
+ @uploadKeyForm << "<div class=\"simpleHKP-uploadKeyFormDiv\">\n"
83
+ @uploadKeyForm << " <h1 class=\"simpleHKP-uploadKeyFormTitle\">Paste GnuPG key to be uploaded below</h1>\n"
84
+ @uploadKeyForm << " <form action=\"/pks/add\" method=\"post\" id=\"simpleHKP-uploadKeyForm\">\n"
85
+ @uploadKeyForm << " <textarea name=\"keytext\" form=\"simpleHKP-uploadKeyForm\" rows = \"30\" cols=\"70\" ></textarea>\n"
86
+ @uploadKeyForm << " <input type=\"submit\" value=\"Upload\" />\n"
87
+ @uploadKeyForm << " </form>\n"
88
+ @uploadKeyForm << "</div>\n"
89
+ @uploadKeyForm = File.open('uploadKeyForm.html','r').read if
90
+ File.exists?('uploadKeyForm.html')
91
+ puts @uploadKeyForm if @debug
92
+
93
+ @uploadIdentityForm = ""
94
+ @uploadIdentityForm << "<div class=\"simpleHKP-uploadIdentityFormDiv\">\n"
95
+ @uploadIdentityForm << " <h1 class=\"simpleHKP-uploadIdentityFormTitle\">Paste GnuPG encrypted identity to be uploaded below</h1>\n"
96
+ @uploadIdentityForm << " <form action=\"/pis/add\" method=\"post\" id=\"simpleHKP-uploadIdentityForm\">\n"
97
+ @uploadIdentityForm << " <textarea name=\"keytext\" form=\"simpleHKP-uploadIdentityForm\" rows = \"30\" cols=\"70\" ></textarea>\n"
98
+ @uploadIdentityForm << " <input type=\"submit\" value=\"Upload\" />\n"
99
+ @uploadIdentityForm << " </form>\n"
100
+ @uploadIdentityForm << "</div>\n"
101
+ @uploadIdentityForm = File.open('uploadIdentityForm.html','r').read if
102
+ File.exists?('uploadIdentityForm.html')
103
+ puts @uploadIdentityForm if @debug
104
+
105
+ @footer = "<p style=\"color:grey\">SimpleHKP version: #{SimpleHKP::VERSION}</p></body></html>\n"
106
+ @footer = File.open('footer.html','r').read if
107
+ File.exists?('footer.html')
108
+ puts @footer if @debug
109
+ end
110
+ puts "SimpleHKP: finished loading html partials" if @debug
111
+ end
112
+
113
+ def initialize(options = {})
114
+ #
115
+ # setup the default options
116
+ #
117
+ defaultOptions = {
118
+ 'debug' => false,
119
+ 'title' => 'SimpleHKP',
120
+ 'simpleHKPdir' => 'simpleHKP',
121
+ 'keyDir' => 'keys',
122
+ 'idDir' => 'identities',
123
+ # 'idIndex' => 'identities/idIndex.yaml',
124
+ 'mediaDir' => 'media',
125
+ 'htmlDir' => 'html',
126
+ 'mimeMap' => {
127
+ 'css' => 'text/css',
128
+ 'html' => 'text/html',
129
+ 'js' => 'text/javascript'
130
+ }
131
+ }
132
+ #
133
+ # merge the options mimeMap into the default options mimeMap
134
+ #
135
+ defaultOptions['mimeMap'].merge!(delete(options['mimeMap'])) if
136
+ options.has_key?('mimeMap')
137
+ #
138
+ # merge in the rest of the options into the default options
139
+ #
140
+ @options = defaultOptions.merge(options)
141
+ #
142
+ # setup the required variables
143
+ #
144
+ @debug = @options['debug']
145
+ @title = @options['title']
146
+ @baseDir = @options['simpleHKPdir']
147
+ @keyDir = @baseDir+'/'+@options['keyDir']
148
+ @idDir = @baseDir+'/'+@options['idDir']
149
+ # @idIndex = @baseDir+'/'+@options['idIndex']
150
+ @mediaDir = @baseDir+'/'+@options['mediaDir']
151
+ @htmlDir = @baseDir+'/'+@options['htmlDir']
152
+ @mimeMap = @options['mimeMap']
153
+ if @debug then
154
+ puts "SimpleHKP options:"
155
+ pp @options
156
+ end
157
+ #
158
+ # ensure the required directories all exist
159
+ #
160
+ FileUtils.mkdir_p(@keyDir)
161
+ FileUtils.chmod(0700, @keyDir)
162
+ FileUtils.mkdir_p(@idDir)
163
+ FileUtils.chmod(0700, @idDir)
164
+ FileUtils.mkdir_p(@mediaDir)
165
+ FileUtils.mkdir_p(@htmlDir)
166
+ #
167
+ # load the existing keys and the html partials
168
+ loadKeys
169
+ loadIdentities
170
+ loadHtml
171
+ end
172
+
173
+ def ppEnv(env)
174
+ Dir.pwd+' '+
175
+ # '['+URI.decode_www_form(env["rack.input"].rewind.read).pretty_inspect+']'+
176
+ env.pretty_inspect
177
+ end
178
+
179
+ def replyInternalError(env, exception)
180
+ @statusCode = 500
181
+ @body << @headerHtml
182
+ @body << "<p>Internal server error!</p>\n"
183
+ @body << "<pre>#{exception}</pre>\n" if @debug
184
+ @body << @footer
185
+ if @debug then
186
+ puts "\n\n-----------------------------------------------------------"
187
+ puts exception
188
+ puts "-----------------------------------------------------------"
189
+ puts ppEnv(env)
190
+ puts "-----------------------------------------------------------\n\n"
191
+ end
192
+ end
193
+
194
+ def replyNotImplemented
195
+ @statusCode = 501
196
+ @body << @headerHtml
197
+ @body << '<p>Not implemented!<p>'
198
+ @body << @footer
199
+ end
200
+
201
+ def replyBadRequest(message)
202
+ @statusCode = 400
203
+ @body << @headerHtml
204
+ @body << '<p>Bad request!</p>'
205
+ @body << "<p>#{message}</p>"
206
+ @body << @footer
207
+ end
208
+
209
+ def replyNotFound
210
+ @statusCode = 404
211
+ @body << @headerHtml
212
+ @body << '<p>Not found!</p>'
213
+ @body << @footer
214
+ end
215
+
216
+ def replyDefaultBody
217
+ @body << @headerHtml
218
+ @body << @defaultBody
219
+ @body << @footer
220
+ end
221
+
222
+ def replyKeyLookupForm
223
+ @body << @headerHtml
224
+ @body << @lookupKeysForm
225
+ @body << @footer
226
+ end
227
+
228
+ def replyIdentityLookupForm
229
+ @body << @headerHtml
230
+ @body << @lookupIdentitiesForm
231
+ @body << @footer
232
+ end
233
+
234
+ def replyKeyUploadForm
235
+ @body << @headerHtml
236
+ @body << @uploadKeyForm
237
+ @body << @footer
238
+ end
239
+
240
+ def replyIdentityUploadForm
241
+ @body << @headerHtml
242
+ @body << @uploadIdentityForm
243
+ @body << @footer
244
+ end
245
+
246
+ def replyFile(env)
247
+ fileName = env['REQUEST_PATH'].sub(/^.*\/media\//,'')
248
+ if File.exists?(fileName) then
249
+ fileExt = File.extname(fileName).sub(/^\./,'')
250
+ @header['Content-Type'] = @mimeMap[fileExt] if
251
+ @mimeMap.has_key?(fileExt)
252
+ @body << File.open(fileName,'r').read
253
+ end
254
+ end
255
+
256
+ def decodeQueryString(env)
257
+ queryHash = Hash.new
258
+ URI.decode_www_form(env['QUERY_STRING']).each do | aKeyValue |
259
+ queryHash[aKeyValue[0]] = aKeyValue[1]
260
+ end
261
+ queryHash
262
+ end
263
+
264
+ def call(env)
265
+ #
266
+ # initialize the response parts
267
+ #
268
+ @statusCode = 200
269
+ @header = {"Content-Type" => "text/html; charset=utf-8"}
270
+ @body = Array.new
271
+ #
272
+ # decode the request
273
+ #
274
+ begin
275
+ puts ppEnv(env) if @debug
276
+ case env['REQUEST_METHOD']
277
+ when 'POST'
278
+ case env['REQUEST_PATH']
279
+ when /^\/pks\/add$/i
280
+ storeKey(env)
281
+ when /^\/pis\/add$/i
282
+ storeIdentity(env)
283
+ else
284
+ replyNotImplemented
285
+ end
286
+ when 'GET'
287
+ case env['REQUEST_PATH']
288
+ when /^\/pks\/add$/i
289
+ replyKeyUploadForm
290
+ when /^\/pis\/add$/i
291
+ replyIdentityUploadForm
292
+ when /^\/pks\/lookup$/i
293
+ queryString = decodeQueryString(env)
294
+ if queryString.has_key?('op') then
295
+ case queryString['op']
296
+ when /get/i
297
+ lookUpKey(queryString)
298
+ when /index/i
299
+ indexKeys(queryString)
300
+ else
301
+ replyKeyLookupForm
302
+ end
303
+ else
304
+ replyBadRequest("No op field in queryString")
305
+ end
306
+ when /^\/pis\/lookup$/i
307
+ queryString = decodeQueryString(env)
308
+ if queryString.has_key?('op') then
309
+ case queryString['op']
310
+ when /get/i
311
+ lookUpIdentity(queryString)
312
+ when /index/i
313
+ indexIdentities(queryString)
314
+ else
315
+ replyIdentityLookupForm
316
+ end
317
+ else
318
+ replyBadRequest("No op field in queryString")
319
+ end
320
+ when /media\//i
321
+ replyFile(env)
322
+ when /reload$/i
323
+ loadKeys
324
+ loadIdentities
325
+ loadHtml
326
+ replyDefaultBody
327
+ else
328
+ replyDefaultBody
329
+ end
330
+ else
331
+ replyBadRequest("Unknown request method")
332
+ end
333
+ rescue Exception => exception
334
+ replyInternalError(env, exception)
335
+ end
336
+ #
337
+ # send the response
338
+ #
339
+ if @debug then
340
+ puts "SimpleHKP reply:"
341
+ pp @statusCode
342
+ pp @header
343
+ puts @body.join("\n")
344
+ end
345
+ [ @statusCode, @header, @body.flatten ]
346
+ end
347
+
348
+ end
349
+ end