gps - greylist policy service for postfix
Greylisting is a way of reducing spam coming into users' mailboxes on the mail server. gps (it soon will be called greylist) is a fast and secure implementation of a greylist policy service for the postfix mail server.
gps is no longer actively developed. Use the Greylist daemon instead. The greylist daemon is completely compatible to gps. The same database schema is used and any configuration that was possible with gps can be done with the greylist daemon (but is slightly more complicated).
1.007
- Last modified on
- July 10 2009
UPDATE 6/4/2011:There is now a Belorussian translation of this document by Bohdan Zograf. Many thanks!
Sourceforge Page | Introduction | Installation | Configuration | Running | Whitelisting | Database Maintenance | Todo and known bugs | Changelog | Credits | Download | Discussion & Support | The gps Forum | Links | Class hierarchy and source documentation Some of the above links may not work at the moment as gps has moved to a new web site
Introductiongps, firstly, is an implementation of a greylist policy service for postfix. Greylisting is a concept to reduce the amount of UCE ('spam') by technical means. Tests on production systems show that greylisting is hugely effective against spam. Read more about greylisting on http://www.greylisting.org and http://projects.puremagic.com/greylisting/whitepaper.htmlSecondly, gps takes greylisting one step further starting with version 1.0. Based on the experience of using greylisting in a production environment, gps comes with features that hugely reduce the problems of the original greylisting concept. These improvements make gps' greylisting usable for ISPs and big mail system setups. gps' main features are:
Project Status
InstallationTo build gps from source the following packages are required:
To build gps unpack the source tar ball (not if using SVN) and run configure and make. Since gps is under development you may have to do: |
tar xvfz gps-<version>.tar.gz [OR] tar xvfz gps-<version>.tar.gz cd gps-X.X (or cd release-<version>) make -f Makefile.cvs ./configure make make install
g++ -s -o gps configreader.cpp db.cpp main.cpp read.cpp cfg.cpp dbdefs.cpp wlcacheddb.cpp signals.cpp -ldbi -ldl
- Note:
- If you get stuck with installing gps post your problem in the gps Forum
Configuration
First create an empty database for greylisting. How to do this depends on the database backend.Example for mysql (this does not use a password):
# mysql -p
> CREATE DATABASE greylist;
> GRANT ALL ON greylist TO 'greylist' IDENTIFIED BY 'secret';
> BYE
- Note:
- etc/gps.pgsql.conf in the package contains a step by step example on how to do this in postgresql.
mode=init.Add gps to your master.cf and main.cf files as described in the postfix documentation under greylisting (taken from http://www.postfix.org/SMTPD_POLICY_README.html):
/etc/postfix/master.cf:
policy unix - n n - - spawn
user=nobody argv=/usr/local/bin/gps /usr/local/etc/gps.conf
/etc/postfix/main.cf:
smtpd_recipient_restrictions =
...
reject_unauth_destination
check_policy_service unix:private/policy
...
policy_time_limit = 3600
Syntax
gps [-v] configfile- Parameters:
-
-v enables verbose log messages configfile your config file including path
Configuration File
The following options are used in the configuration file.- Note:
- the keys and values are case sensitive.
| Parameter | Possible values or value range (default in bold) |
Description | Depends on | Version |
|---|---|---|---|---|
| mode | normal | init | weak | reverse | Sets the greylisting mode | ||
| weakbytes | 0 - 4 (3) | Number of significant bytes of client IP address | mode=weak(|reverse) | 0.92 |
| dbtype | mysql | sqlite | pgsql | Database type | ||
| db_host | hostname or IP address | Database server | dbtype | |
| db_username | username | Database user name | dbtype | |
| db_password | password | Database password | dbtype | |
| db_dbname | database name | Database name | dbtype | |
| db_port | port number | Database port | dbtype=pgsql | 0.9 |
| db_pgsql_options | Postgres options | Postgres options | dbtype=pgsql | 0.9 |
| db_pgsql_tty | /dev/ttyX (/dev/null) | Postgres logging | dbtype=pgsql | 0.9 |
| db_sqlite_dbdir | path (permissions!) | SQLite Database path | dbtype=sqlite | 0.9 |
| timeout | seconds (3600=1 hour) | Greylisting timeout | (mode=init) | |
| wl_network | off | db | dbcached (off) | Network whitelisting mode | 0.8 | |
| wl_recipient | off | db | dbcached (off) | Recipient whitelisting mode | 0.8 | |
| wl_sender | off | db | dbcached (off) | Sender whitelisting mode | 0.8 | |
| wl_pattern | off | db | dbcached (off) | Pattern matching whitelisting mode | 0.91 | |
mode
mode tells gps in which mode to run. Default is init -
initCreates any database table(s) that dont exist. Will always return "dunno" but add records to the database. It can therefore be used to gather a sufficient number of records for the database before switching over to the reverse, normal or weak mode which actually perform the greylisting. Note that this causes more SQL queries as it checks whether the greylist tables exist. This should only be used for initialising the database and testing gps.- Note:
- This will not stop any mail.
In the 1.x version init mode should only be used for creating the database tables and not to fill the triplet table with data. The triplets that this mode writes to the database are inconsistent with running gps in mode reverse later. This results in certain mail being let through without any checking. If this is the case there are messages about exceptions in the mail log. The solution is to remove all entries that have client addresses beginning with numbers from the triplet table. Something like this will do the job:
> DELETE FROM triplet WHERE client_address RLIKE '1'; > DELETE FROM triplet WHERE client_address RLIKE '2'; ...
-
reverseName based greylisting: instead of checking the client IP address of the triplet this resolves the IP address and uses only a significant part of the host name for checking whether a triplet has already been checked. (E.g. Given the IP address 1.2.3.4 resolves to mail-server255.someisp.com gps checks whether a triplet (someisp.com,sender,recipient) already exists. In normal mode it would check (1.2.3.4,sender,recipient) thus rejecting mail if it came from a different machine on the same relay) This mode is useful for ISPs as it reduces the need to whitelist and therefore complaints from users about not getting mail. At the same time it is still effective in stopping spam. In case the name resolution fails this is logged and weak greylisting based on the IP address is done instead.
- Note:
- This is supported from version 1.0 on and is the recommended mode to use
-
normalNormal greylisting mode: gps checks the triplets (client IP address,sender,recipient) against the database and blocks mail until the timeout is reached for the first mail. Subsequent mails with the same triplet are let through immediately.
-
weak(Starting from version 0.7) Weak greylisting. In brief this means ignore the last byte of client IP addresses. It is useful if you get stuck with sender mail servers that use a block of network addresses on the same subnet to send mail. Usingweakmode results in significantly higher cpu load. It is therefore recommended to use a combination of Whitelisting methods (for version 0.x).
- Note:
- From version 1.0 on gps uses a different way of storing and looking up client IP addresses. This reduces CPU load. A new configuration setting is added:
weakbytes sets the number of significant bytes of the client address in weak greylisting mode. The default is 3.
timeout
timeout The greylisting whitepaper suggests a timeout of 3600 seconds (1 hour) before a new triplet of sender, recipient, client address should be allowed through the greylisting system. Reducing the timeout will keep users happy and is still very effective (e.g. 60 seconds). Default is 3600 dbtype
dbtype sets the database type to use. This must be set to the same name libdbi expects. Currently, libdi-drivers support mysql, pgsql (version 0.9+), sqlite (version 0.9+), msql (?), oracle (?). gps will exit and log a list of available drivers if the specified driver is not installed (comes in handy for checking libdbi installation). Default value is mysql db_<db parameter>
db_<db parameter> This is the list of paramters to be passed on to libdbi to make the database connection. The db parameters depend on the driver. See the example configuration file below and the included gps.conf, gps.sqlite.conf and gps.pgsql.conf for examples of how to use the different database backends. Example db parameters for dbtype=mysql: db_host=localhost db_username=gps db_password=secret db_dbname=greylist
Whitelisting
A good greylisting implementation should include several ways of whitelisting. Many mail systems do not conform to the SMTP specification, some big ISPs use multiple mail servers on the same subnet. In the worst case mails get bounced back. Weak greylisting (sometimes called light greylisting) is one way to attack this problem, whitelisting is better in CPU load and results in better spam reduction. In the 1.x series gps uses a better approach to this problem. Using the modereverse solves the issues with mail relays and thus reduces the need to whitelist.The following whitelisting options are provided by gps. The can be used in any combination. If you try to optimise your configuration bear in mind that the whitelisting tables get processed before the triplets table.
Whitelisting Database Modes (version 0.8+)
All whitelisting modules can use different ways of storing data and looking up entries. The mode to use is set in the configuration file.wl_<module>=<mode>
- Parameters:
-
off Is the default. This whitelisting module is not used. db (version 0.7b+) The whitelisting data is stored in a table with the name of the whitelisting module. If gps is run in mode=initit will check if the table exists and create it if necessary. Setting a module todbmakes gps check evry triplet against the whitelisting module's table before checking the main triplets table. Therefore, for every whitelisting modules enabled one more SQL query is generated.dbcached (version 0.8+) When gps is started it reads the module's whitelisting table and creates a memory cache of it which it uses to do subsequent lookups. This uses more memory than dband results in longer startup times, but means fewer SQL queries, and is - once initialised - much faster thandb.
wl_network
wl_network (version 0.7b+) This sets the network whitelisting mode. If it is set to wl_network=db it will check the table network prior to everything else whether the client address network block has been whitelisted. In order to turn it off use wl_network=off. Default off Example of adding a whitelisting entry in mysql
> use greylist;
> insert into network values ('192.168.0.','my home network');
> bye (or CTRL+D)
- Note:
- The last dot of the netblock is mandatory!
wl_recipient, wl_sender
wl_recipient (version 0.8+) This sets the recipient (or sender) whitelisting mode. If it is set to wl_recipient=db it will check the table recipient prior to everything else whether the recipient address has been whitelisted. In order to turn it off use wl_recipient=off. Default off Example of adding a whitelisting entry in mysql
> use greylist;
> insert into recipient values ('bla@mydomain.com','this user wants his spam');
> bye (or CTRL+D)
wl_pattern
wl_pattern (version 0.91+) allows whitelisting based on regular expression matching.The regular expressions in wl_pattern can, theoretically, be used to replace any of the other whitelisting modules. Furthermore, it can be used to implement complex whitelisting rules combining several conditions. Nevertheless, it should only be used if none of the other modules suit the task. It is much slower by itself and also because all its patterns will be tested against any incoming triplet. The other modules use database or string map based lookups. If wl_pattern has to be used this should be done by setting it to wl_pattern=dbcached thus reducing the number of database queries.
gps builds a text that expressions can be matched against for advanced whitelisting solutions. The format of the gps internal representation is:
s=someuser@yahoo.com r=someuser@mydomain.org c=216.145.54.171 h=mrout1.yahoo.com
In the above example the IP address resolves to one of Yahoo's servers. This pattern uses reverse name lookup and matches the example:
> insert into pattern values(".+^h=.*yahoo\.com.+$","yahoo");
> insert into pattern values(".+^r=.*@someorg\.org.+$","someorg want all spam");
^s=user.+^r=myuser@mydomain.+^c=210
^s=user.+^r=myuser@mydomain\.org.+^c=210
- Note:
- the
.+ after theorgin the example is still required!
s=user is at the beginning do not use the leading .+ before the anchor ^ ^s=sender@example\.com.+$
weakbytes
weakbytes sets the number of significant bytes of the client address in weak greylisting mode. The default is 3.Example configuration file:
mode=reverse dbtype=mysql db_host=localhost db_username=gps db_password=secret db_dbname=greylist timeout=60 wl_recipient=dbcached wl_network=db wl_sender=off wl_pattern=dbcached
To test gps and your configuration use the following command. Configuration errors will be logged to syslogd (facility mail). Also see Running.
./src/gps -v etc/gps.conf < tests/testinput4.txt
Now wait for the number of seconds specified in timeout and run the same line again. It should return "action=dunno" lines. If it does gps is ready.
- Note:
- If you plan to use gps in reverse mode (strongly recommended) then you must now clear out the triplet table. E.g. Again, check the log and post in the Forum if something goes wrong.
> TRUNCATE TABLE `triplet`;
Example configuration files for postgres and SQLite are in the etc/ folder after unpacking gps. The gps.pgsql.conf contains step by step instruction on how to install and configure postgres on debian and how to create the greylist database and user.
mode=init. Nevertheless, it is useable after this.
- Note:
- If you get stuck with configuring gps post your problem in the gps Forum
Running
For running gps the requirements are:- dbd drivers for the chosen database backend from http://libdbi-drivers.sf.net (or libdbd-(driver) package)
gps logs its actions to the syslog mail facility. The output from a testrun is shown below:
- Note:
- Running gps in verbose mode (-v switch) generates a lot of log output and is only recommended for initialising and troubleshooting.
mail gps[2225]: started (ver.: 0.8 built: Sep 14 2004 18:35:14) mail gps[2225]: reading config: /etc/gps.conf mail gps[2225]: config: prefix: key: mode value=normal mail gps[2225]: config: prefix: db key: host value=localhost mail gps[2225]: config: prefix: db key: username value=greylist mail gps[2225]: config: prefix: db key: password value= mail gps[2225]: config: prefix: db key: dbname value=greylist mail gps[2225]: config: prefix: key: timeout value=60 mail gps[2225]: connecting to DB, using driver mysql mail gps[2225]: setting DB option: dbname to: greylist mail gps[2225]: setting DB option: host to: localhost mail gps[2225]: setting DB option: password to: mail gps[2225]: setting DB option: username to: root mail gps[2225]: connected to DB mail gps[2225]: ok: 'foobar.tld' -> 'barfoo.tld', '1.2.3.4' (3, 152 secs) mail gps[2225]: action=dunno mail gps[2225]: new: 'foo@blabla.org' -> 'blabla@foo.org', '192.168.0.1' mail gps[2225]: action=defer_if_permit Service is unavailable mail gps[2225]: wait: 'foo@blabla.org' -> 'blabla@foo.org', '192.168.0.1' (0, 34 secs) mail gps[2225]: action=defer_if_permit Service is unavailable mail gps[2225]: disconnecting from DB
- new: sender -> recipient, client_address|client_name
- wait: sender -> recipient, client_address|client_name (count, time_difference first seen)
- ok: sender -> recipient, client_address|client_name (count, time_difference last seen)
- Parameters:
-
sender the sender's address recipient the recipient's address client_address the client's address client_name the significant part of the resolved client name when run in mode reversecount number of times triplet has been passed from postfix time_difference interval between now and the record's time
mail gps[18838]: wl recipient: 'foobar.tld' -> 'bla@mydomain.com', '192.168.0.254': this user wants his spam
mail gps[18452]: wl network: 'foobar.tld' -> 'bla@someorg.org', '192.168.0.254': my home network
- Note:
- If you have questions about running gps post in the gps Forum
Database Maintenance
The greylisting approach requires a level of database maintenance. This implementation uses an example perl script for database maintenance. This can be run from cron.gps-maintain.pl [-v] [-delete] -eq|-lt count -age seconds configfile
/usr/local/bin/gps-maintain.pl -delete -eq 0 -age 18000 /usr/local/etc/gps.conf
/usr/local/bin/gps-maintain.pl -delete -age 3110400 /usr/local/etc/gps.conf
Todo and known bugs
- A couple of know SQLite and postgresql problems especially on table creation. Workaround at the moment: create them manually, check the forums for details
- A problem with reverse DNS: a couple of companies have decided to use more than one cannonical name for their servers. Reverse greylisting has (non-critical) problems with that
- Fix Solaris+SQLite problems: one user reports that gps reports many exceptions and spam is going through his machine as a result (help needed)
- Addition to gps-maintain.pl for whitelisting and gathering statistics
- Build Debian package (help needed)
- Add a two-servers-one-database configuration howto
- Fix postgresql errors on create index. Postgres thinks sender(15),recipient(15) is a call to a function.
- Note:
- If you think you found a bug or if you have improved gps post in the gps Forum
Credits
- Thanks to Cedric for suggesting the new reverse mode.
- Thanks to Marek Tichy for proposing this project and helping implement it and thanks to Cedric Knight for all the feedback.
- Thanks to the person whose name I have to look up in my E-mail for feedback on the SQL implementation and testing it on Postgres.
- Credits to Michael Hubbard for porting gps to FreeBSD and rewriting gps-maintain.pl
Changelog
- 10/07/2009 -- version 1.007, r41, bugfixes, upgrade recommended (Cedric)
- fixes: use weak, not 'unknown' rDNS
- fix redundant gethostbyaddr() calls
- fix triplet table creation syntax
- 3/04/2007-24/04/2007 -- version 1.005, bugfix, upgrade recommended
- Moved project to Sourceforge and SVN Sourceforge Page
- Fix for bug when two machines run at different times and timestamps are in the past
- Bugfixes for policy service timeout errors
- Support for clean shutdown on signals
- use reverse dns provided by postfix instead of resolving - still resolves if necessary
- 2/03/2005 -- version 1.004, bugfix, upgrade recommended
- Fix for bug with email addresses, client names, etc. that contain %s or any other printf/syslog/dbi_conquery-type parsed expression that resulted in gps segfaulting and mail being let through without checking (Bug reported by Jamie L. Penman-Smithson)
- New gps-maintain.pl script from Michael Hubbard
- 15/02/2005 -- version 1.003, bugfixes
- Patches from Michael Hubbard to compile gps on FreeBSD
- Fixed problem in triplet table definition (thanks to David Dakdo)
- Fixed REPLACE MySQLism (thanks to Jared)
- Fixed gps-maintain.pl compatibility problems with sqlite (thanks to mhubbard)
- 28/01/2005 -- version 1.002, bugfixes for 1.001
- Note:
- before upgrading to this version read the notes for version 1.001
- Fixed name resolution algorithm in gps and gps-db-update.pl, should stop getting invalid host strings for valid host names
- The host name gets calculated by using removing everything up to and including the first dot
- If the host name does not contain a dot the whole string is taken (I have noticed some mailhosts that resolve to 'localhost', seriously)
- The host name gets calculated by using removing everything up to and including the first dot
- Fixed a problem with quoting ' in E-mail addresses when the old dbi library is used
- Reenabled whitelisting network module
- Fixed a bug in INSERT command, the order of sender, recipient was wrong
- 25/01/2005 -- version 1.001, major changes
- Changed the database structure. It is not compatible with the 0.x series.
- Added new greylisting mode
reverse:see description under mode - IP addresses are now stored numerically (see notes version 0.92)
- weakbytes now functional but better use mode
reverseand fallover toweakif necessary - Renamed the
Tripletstable totriplet(this should make it easier to track version change problems) - Added a PERL script for database upgrade gps-db-update.pl -- read comments in the file carefully before using
- 13/01/2005 -- version 0.93, small bugfixes (last stable in 0.x series)
- fixed a bug that prevented creating the whitelisting tables in mode init
- fixed the problem with empty comments in whitelisting tables
- fixed a bug that prevented creating the whitelisting tables in mode init
- 20/11/2004 -- version 0.92
- changed the behaviour when something goes wrong during the init phase: gps used to exit and report the error. This resulted in REJECT for the message. Typically, this happened when the db is down. Now gps will continue and let all messages through without performing any checks. If this happens the logfile will contain gps entries saying NO GREYLISTING WILL BE DONE
- Fixed the regex pattern support so it works with multiple conditions. E.g. now a pattern that expresses "sender=user@domain.org AND recipient=myuser@mydomain.org" can be used. For details see wl_pattern
- Experimental Support for a new db scheme that stores client addresses as numerics instead of strings
- This should speed up weak greylisting
- Added weakbytes to the config options -- not functional yet
- Note that this is disabled by default, comment out this line in defs.h to test it: There is also a db conversion script gps-db-update.pl(in PERL) but note that the conversion procedure and table names might change
#define OLDWEAK (true) - Added 4 new columns to the table. To upgrade an existing db use alter table before installing this version.
- This is what the table should look like (the order of fields is significant):
mysql> explain Triplets; +----------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +----------------+--------------+------+-----+---------+-------+ | client_address | varchar(40) | | PRI | | | | recipient | varchar(160) | | PRI | | | | sender | varchar(160) | | PRI | | | | ip64 | decimal(4,0) | | PRI | 0 | | | ip32 | decimal(4,0) | | PRI | 0 | | | ip16 | decimal(4,0) | | PRI | 0 | | | ip8 | decimal(4,0) | | PRI | 0 | | | count | int(11) | | | 0 | | | uts | int(11) | | | 0 | | +----------------+--------------+------+-----+---------+-------+
- 18/10/2004 -- version 0.91 pattern based whitelisting
- added support for whitelisting based on regular expressions: See wl_pattern for details of this powerful feature.
- 18/09/2004 -- version 0.9 bugfixes
- fixed SQL incompatibilties with SQLite and postgresql
- fixed some bugs with empty / non-existing whitelisting database tables
- included example configuration files for SQLite and postgresql
- fixed a typo in gps-maintain.pl thanks to a post in the gps Forum
- 14/09/2004 -- version 0.8 whitelisting revisited
- changed to a wl modules based system: wl_sender, wl_recipient
- wl modules now offer dbcached mode: reads database at start and keeps it in memory instead of querying db
- optimised indices for triplets table
- 13/07/2004 -- version 0.7b small bugfixes
- added configure option --with-syslog-facility={facility} default: LOG_MAIL
- fixed bug in CREATE TABLE network
- fixed detection and support for new libdbi dbi_conn_queryf (libdbi version >= 0.7.2)
- 07/07/2004 -- version 0.7b
- Added whitelisting support: wl_network
- 06/07/2004 -- version 0.7
- Changed support for weak greylisting. Use mode=weak
- Changed sql statements to work with pgsql
- Changed Triplets table index
- 09/06/2004 -- version 0.6: Added support for weak greylisting: use ignorelastbyte=true in the config file (see included example) to allow any address within the same netblock
Download
Since version 1.005 gps source code is hosted on sourceforge.To checkout the current stable version use subversion (svn):
svn co https://greylist.svn.sourceforge.net/svnroot/greylist/trunk greylist cd greylist make -f Makefile.cvs ./configure make make install
Source code package from the Sourceforge Page. Follow the link for a list of releases.- Using SVN:
svn co https://greylist.svn.sourceforge.net/svnroot/greylist/tags/release-1.005 greylist-1.005 New: Unofficial experimental Debian package. This is untested. Please leave your bugreport or feedback on the forum
The current development version is available from subversion on sourceforge
- Using SVN:
svn co https://greylist.svn.sourceforge.net/svnroot/greylist greylist
Older versions are not available fom this web site anymore.
Older versions will be available from this site but mainly for archiving reasons. If you are using one of them consider upgrading to the most recent stable version.
- Note:
- Upgrading from 0.x versions to 1.x versions requires upgrading the database to the new database format. The distribution includes an upgrade script gps-db-update.pl It contains more information on how to perform the upgrade.
- Stable Version in 0.x series http://mimo.gn.apc.org/gps/gps-0.93.tar.gz (last in 0.x series)
Older Releases
- Version 1.x
- Version 1.004 http://mimo.gn.apc.org/gps/gps-1.004.tar.gz
- Version 1.003 (15/02/2002) http://mimo.gn.apc.org/gps/gps-1.003.tar.gz
- Version 1.002 (28/01/2005) http://mimo.gn.apc.org/gps/gps-1.002.tar.gz (buggy)
- Version 0.x
- Version 0.92 (20/11/04) http://mimo.gn.apc.org/gps/gps-0.92.tar.gz
- Version 0.91 (18/10/04) http://mimo.gn.apc.org/gps/gps-0.91.tar.gz
- Version 0.9 (18/9/04) http://mimo.gn.apc.org/gps/gps-0.9.tar.gz (stable)
- Version 0.8 (14/9/04) http://mimo.gn.apc.org/gps/gps-0.8.tar.gz (buggy)
- Version 0.7b (13/7/04) http://mimo.gn.apc.org/gps/gps-0.7b.tar.gz (stable)
- Version 0.6 (9/7/04) http://mimo.gn.apc.org/gps/gps-0.6.tar.gz
Discussion & Support
I have set up a publicly accessible forum where you can post bug reports, questions, and answers (preferably) around gps.Links
Let me know if you have a link to add here.- Everything to know about greylisting: http://www.greylisting.org
- Greylisting whitepaper by Evan Harris: http://projects.puremagic.com/greylisting/whitepaper.html
- BSD port by Xin LI: http://resources.delphij.net/postfix-gps
- libdbi - Database Independent Abstraction Layer for C: http://libdbi.sourceforge.net/
- Database Drivers for libdbi: http://libdbi-drivers.sourceforge.net/
- Other implementations for Postfix: http://greylisting.org/implementations/postfix.shtml
- Postfix SMTP Access Policy Delegation: http://www.postfix.org/SMTPD_POLICY_README.html
- Need some distraction? Try http://xarch.tu-graz.ac.at/home/mimo/
Recent comments