]>
git.ipfire.org Git - ipfire.org.git/blob - fireinfo/fireinfod
75aadce9eb2c1b76cad16f37a064ea9ad436f629
9 import tornado
.database
10 import tornado
.httpserver
12 import tornado
.options
17 DATABASE_HOST
= ["irma.ipfire.org", "madeye.ipfire.org"]
18 DATABASE_NAME
= "stasy"
20 DEFAULT_HOST
= "www.ipfire.org"
22 MIN_PROFILE_VERSION
= 0
23 MAX_PROFILE_VERSION
= 0
26 def __getattr__(self
, key
):
30 raise AttributeError, key
32 def __setattr__(self
, key
, val
):
36 class Fireinfod(tornado
.web
.Application
):
37 def __init__(self
, **kwargs
):
40 default_host
= DEFAULT_HOST
,
43 settings
.update(kwargs
)
45 tornado
.web
.Application
.__init
__(self
, **settings
)
47 # Establish database connection
48 self
.connection
= pymongo
.Connection(DATABASE_HOST
)
49 self
.db
= self
.connection
[DATABASE_NAME
]
50 logging
.info("Successfully connected to database: %s:%s" % \
51 (self
.connection
.host
, self
.connection
.port
))
53 self
.add_handlers(r
"fireinfo.ipfire.org", [
54 (r
"/", tornado
.web
.RedirectHandler
, { "url" : "http://www.ipfire.org/" }),
55 (r
"/send/([a-z0-9]+)", ProfileSendHandler
),
56 (r
"/debug", DebugHandler
),
60 # this should not be neccessary (see default_host) but some versions
61 # of tornado have a bug.
62 self
.add_handlers(r
".*", [
63 (r
".*", tornado
.web
.RedirectHandler
, { "url" : "http://" + DEFAULT_HOST
+ "/" })
67 logging
.debug("Disconnecting from database")
68 self
.connection
.disconnect()
72 return tornado
.ioloop
.IOLoop
.instance()
74 def start(self
, port
=9001):
75 logging
.info("Starting application")
77 http_server
= tornado
.httpserver
.HTTPServer(self
, xheaders
=True)
78 http_server
.listen(port
)
80 # Register automatic cleanup for old profiles, etc.
81 automatic_cleanup
= tornado
.ioloop
.PeriodicCallback(
82 self
.automatic_cleanup
, 60*60*1000)
83 automatic_cleanup
.start()
88 logging
.info("Stopping application")
91 def db_get_collection(self
, name
):
92 return pymongo
.collection
.Collection(self
.db
, name
)
96 return self
.db_get_collection("profiles")
100 return self
.db_get_collection("archives")
102 def automatic_cleanup(self
):
103 logging
.info("Starting automatic cleanup...")
105 # Remove all profiles that were not updated since 4 weeks.
106 not_updated_since
= datetime
.datetime
.utcnow() - \
107 datetime
.timedelta(weeks
=4)
109 self
.move_profiles({ "updated" : { "$lt" : not_updated_since
}})
111 def move_profiles(self
, find
):
113 Move all profiles by the "find" criteria.
115 for p
in self
.profiles
.find(find
):
116 self
.archives
.save(p
)
117 self
.profiles
.remove(find
)
120 class BaseHandler(tornado
.web
.RequestHandler
):
123 return backend
.GeoIP()
127 return self
.application
.db
129 def db_get_collection(self
, name
):
130 return self
.application
.db_get_collection(name
)
133 def db_collections(self
):
134 return [self
.db_get_collection(c
) for c
in self
.db
.collection_names()]
138 Database information:
139 Host: %(db_host)s:%(db_port)s
141 All nodes: %(db_nodes)s
147 DEBUG_COLLECTION_STR
= """
149 Total documents: %(count)d
152 class DebugHandler(BaseHandler
):
154 # This handler is only available in debugging mode.
155 if not self
.application
.settings
["debug"]:
156 return tornado
.web
.HTTPError(404)
158 self
.set_header("Content-type", "text/plain")
160 conn
, db
= (self
.application
.connection
, self
.db
)
165 db_nodes
= list(conn
.nodes
),
169 for collection
in self
.db_collections
:
170 collections
.append(DEBUG_COLLECTION_STR
% {
171 "name" : collection
.name
, "count" : collection
.count(),
173 debug_info
["collections"] = "".join(collections
)
175 self
.write(DEBUG_STR
% debug_info
)
179 class ProfileSendHandler(BaseHandler
):
182 return self
.application
.archives
186 return self
.application
.profiles
189 # Create an empty profile.
190 self
.profile
= Profile()
192 def __check_attributes(self
, profile
):
194 Check for attributes that must be provided,
203 for attr
in attributes
:
204 if not profile
.has_key(attr
):
205 raise tornado
.web
.HTTPError(400, "Profile lacks '%s' attribute: %s" % (attr
, profile
))
207 def __check_valid_ids(self
, profile
):
209 Check if IDs contain valid data.
212 for id in ("public_id", "private_id"):
213 if re
.match(r
"^([a-f0-9]{40})$", "%s" % profile
[id]) is None:
214 raise tornado
.web
.HTTPError(400, "ID '%s' has wrong format: %s" % (id, profile
))
216 def __check_equal_ids(self
, profile
):
218 Check if public_id and private_id are equal.
221 if profile
.public_id
== profile
.private_id
:
222 raise tornado
.web
.HTTPError(400, "Public and private IDs are equal: %s" % profile
)
224 def __check_matching_ids(self
, profile
):
226 Check if a profile with the given public_id is already in the
227 database. If so we need to check if the private_id matches.
229 p
= self
.profiles
.find_one({ "public_id" : profile
["public_id"]})
234 if p
.private_id
!= profile
.private_id
:
235 raise tornado
.web
.HTTPError(400, "Mismatch of private_id: %s" % profile
)
237 def __check_profile_version(self
, profile
):
239 Check if this version of the server software does support the
242 version
= profile
.profile_version
244 if version
< MIN_PROFILE_VERSION
or version
> MAX_PROFILE_VERSION
:
245 raise tornado
.web
.HTTPError(400,
246 "Profile version is not supported: %s" % version
)
248 def check_profile(self
):
250 This method checks if the blob is sane.
254 self
.__check
_attributes
,
255 self
.__check
_valid
_ids
,
256 self
.__check
_equal
_ids
,
257 self
.__check
_profile
_version
,
258 # These checks require at least one database query and should be done
260 self
.__check
_matching
_ids
,
266 # If we got here, everything is okay and we can go on...
268 def move_profiles(self
, find
):
269 self
.application
.move_profiles(find
)
271 # The GET method is only allowed in debugging mode.
272 def get(self
, public_id
):
273 if not self
.application
.settings
["debug"]:
274 return tornado
.web
.HTTPError(405)
276 return self
.post(public_id
)
278 def post(self
, public_id
):
279 profile
= self
.get_argument("profile", None)
281 # Send "400 bad request" if no profile was provided
283 raise tornado
.web
.HTTPError(400, "No profile received.")
285 # Try to decode the profile.
287 self
.profile
.update(simplejson
.loads(profile
))
288 except simplejson
.decoder
.JSONDecodeError
, e
:
289 raise tornado
.web
.HTTPError(400, "Profile could not be decoded: %s" % e
)
291 # Create a shortcut and overwrite public_id from query string
292 profile
= self
.profile
293 profile
.public_id
= public_id
295 # Add timestamp to the profile
296 profile
.updated
= datetime
.datetime
.utcnow()
298 # Check if profile contains proper data.
301 # Get GeoIP information if address is not defined in rfc1918
302 addr
= ipaddr
.IPAddress(self
.request
.remote_ip
)
303 if not addr
.is_private
:
304 profile
.geoip
= self
.geoip
.get_all(self
.request
.remote_ip
)
306 # Move previous profiles to archive and keep only the latest one
307 # in profiles. This will make full table lookups faster.
308 self
.move_profiles({ "public_id" : profile
.public_id
})
310 # Write profile to database
311 id = self
.profiles
.save(profile
)
313 self
.write("Your profile was successfully saved to the database.")
316 logging
.debug("Saved profile: %s" % profile
)
319 if __name__
== "__main__":