Pues si, todavía siguen intentando ataques con diccionario para usuarios y contraseñas...
Así al menos he recordado que me quedaba por hacer una entrada sobre firewalls y otras defensas que poder implementar a nuestro servidor de correo.
sudo apt-get install phpmyadmin apache2 apache2-utils
Aceptamos la creacción de la base de datos necesaria para phpmyadmin y damos la contraseña de superusuario (root) de MySQL que creamos en el primer apartado de la guia. La siguiente pantalla nos requerirá una nueva contraseña para el usuario phpmyadmin y tras la confirmación se nos preguntará que servidor web vamos a utilizar para proceder a la configuración automática, en nuestro caso apache2.sudo a2enconf phpmyadmin
sudo php5enmod mcrypt
sudo /etc/init.d/apache2 restart
sudo nano /etc/phpmyadmin/apache.conf
Alias /<loquesea> /usr/share/phpmyadmin
El siguiente paso será añadir un nivel de autenticación adicional con .htaccess . Para ello vamos a editar el fichero de configuración del sitio en Apache:
sudo nano /etc/phpmyadmin/apache.conf
Donde buscamos la sección:<Directory /usr/share/phpmyadmin>
Options FollowSymLinks
DirectoryIndex index.php
Y añadimos justo debajo la siguiente línea:
AllowOverride All
Cerramos y guardamos el fichero para despues reiniciar Apache.
sudo /etc/init.d/apache2 restart
Vamos a crear el fichero htaccess encargado de la configuración de acceso:
sudo nano /usr/share/phpmyadmin/.htaccess
Donde escribiremos lo siguiente:
AuthType Basic
AuthName "Acceso restringido"
AuthUserFile /etc/phpmyadmin/.htpasswd
Require valid-user
Aquí dejo una guía en profundidad sobre htaccess para aquellos que quieran saber más sobre el tema. Lo siguiente será crear un usuario con htpasswd:
sudo htpasswd -c /etc/phpmyadmin/.htpasswd <TU USUARIO>
Se nos requerirá una contraseña para el nuevo usuario y las credenciales se guardarán cifradas en .htpasswd dentro de la carpeta de phpMyAdmin. Para añadir usuarios adicionales emplearemos el mismo comando pero sin el atributo -c. Ahora, si nos dirigimos con un navegador a phpMyAdmin (Acordarse de que le dimos un nuevo alias) se nos requerirá un usuario y contraseña para poder acceder a este.
Roundcube es un agente de correo web, que permitirá a nuestros usuarios acceder y gestionar su correo por medio de nuestro servidor web. En un principio pensé en llevar a cabo esta tarea por medio de SquirrelMail pero su diseño, mayor soporte y las ganas de probar algo nuevo me han llevado a elegir Roundcube para este fin. Para instalarlo en Ubuntu (Para Debian lo detallo más adelante) basta con:
sudo apt-get install roundcube roundcube-mysql roundcube-plugins
Nos preguntará si queremos configurar el acceso a la base de datos, elegimos que si y posteriormente que queremos hacerlo por MySQL. Nos preguntará por la contraseña de root de nuestra base de datos y luego por la que queramos elegir para el usuario roundcube que creará. mkdir /opt/roundcube
cd /opt/roundcube
wget https://downloads.sourceforge.net/project/roundcubemail/roundcubemail/1.1.3/roundcubemail-1.1.3-complete.tar.gz
tar xfz roundcubemail-1.1.3-complete.tar.gz
mv roundcubemail-1.1.3/* .
mv roundcubemail-1.1.3/.htaccess .
rmdir roundcubemail-1.1.3
rm roundcubemail-1.1.3-complete.tar.gz
chown -R www-data:www-data /opt/roundcube
mysql -u root -pCREATE DATABASE roundcubemail;
GRANT ALL PRIVILEGES ON roundcubemail.* TO roundcube@localhost IDENTIFIED BY 'TU_CONTRASEÑA_AQUI';
flush privileges;
quit;
mysql --defaults-file=/etc/mysql/debian.cnf roundcubemail < /opt/roundcube/SQL/mysql.initial.sql
cd /opt/roundcube/config
cp -pf config.inc.php.sample config.inc.php
nano config.inc.php
$config['db_dsnw'] = 'mysql://roundcube:TU_CONTRASEÑA_AQUI@localhost/roundcubemail';
Buscamos el siguiente parámetro: smtp_server y dejamos la linea así:
$config['smtp_server'] = 'localhost';
Para configurar nuestras conexiones por medio de ssl añadimos lo siguiente:
$config['default_host'] = 'ssl://<NOMBRE_DE_DOMINIO_DE_TU_CRT>';
$config['default_port'] = 993;
$config['imap_auth_type'] = PLAIN;
$config['imap_conn_options'] = array(
'ssl' => array(
'verify_peer' => false,
'verfify_peer_name' => false,
);
nano /etc/apache2/conf-available/roundcube.conf
Alias /roundcube /opt/roundcube
Alias /webmail /opt/roundcube
<Directory /opt/roundcube>
Options +FollowSymLinks
# AddDefaultCharset UTF-8
AddType text/x-component .htc
<IfModule mod_php5.c>
AddType application/x-httpd-php .php
php_flag display_errors Off
php_flag log_errors On
# php_value error_log logs/errors
php_value upload_max_filesize 10M
php_value post_max_size 12M
php_value memory_limit 64M
php_flag zlib.output_compression Off
php_flag magic_quotes_gpc Off
php_flag magic_quotes_runtime Off
php_flag zend.ze1_compatibility_mode Off
php_flag suhosin.session.encrypt Off
#php_value session.cookie_path /
php_flag session.auto_start Off
php_value session.gc_maxlifetime 21600
php_value session.gc_divisor 500
php_value session.gc_probability 1
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^favicon\.ico$ skins/larry/images/favicon.ico
# security rules:
# - deny access to files not containing a dot or starting with a dot
# in all locations except installer directory
RewriteRule ^(?!installer)(\.?[^\.]+)$ - [F]
# - deny access to some locations
RewriteRule ^/?(\.git|\.tx|SQL|bin|config|logs|temp|tests|program\/(include|lib|localization|steps)) - [F]
# - deny access to some documentation files
RewriteRule /?(README\.md|composer\.json-dist|composer\.json|package\.xml)$ - [F]
</IfModule>
<IfModule mod_deflate.c>
SetOutputFilter DEFLATE
</IfModule>
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 1 month"
</IfModule>
FileETag MTime Size
<IfModule mod_autoindex.c>
Options -Indexes
</ifModule>
AllowOverride None
Require all granted
</Directory>
<Directory /opt/roundcube/plugins/enigma/home>
Options -FollowSymLinks
AllowOverride None
Require all denied
</Directory>
<Directory /opt/roundcube/config>
Options -FollowSymLinks
AllowOverride None
Require all denied
</Directory>
<Directory /opt/roundcube/temp>
Options -FollowSymLinks
AllowOverride None
Require all denied
</Directory>
<Directory /opt/roundcube/logs>
Options -FollowSymLinks
AllowOverride None
Require all denied
</Directory>
a2enconf roundcube
service apache2 reload
sudo apt-get install libsasl2-modules libsasl2-modules-sql libgsasl7 libauthen-sasl-cyrus-perl sasl2-bin libpam-mysqlSeguimos con su configuración. Lo primero que haremos es otorgar permisos a Postfix para hacer posible su comunicación con SASL:
sudo adduser postfix saslCreamos un directorio accesible desde el chroot de Postfix, para SASL.
sudo mkdir -p /var/spool/postfix/var/run/saslauthdAhora editamos el archivo de configuración de Postfix para habilitar las características de autenticación con SASL.
sudo nano /etc/postfix/main.cfDonde añadimos las lineas siguientes:
# Habilitamos SASLSin salir del archivo buscamos y comprobamos las siguientes lineas en busca de la autenticación SASL (Ya deberían estar correctamente si se a copiado de anteriores post de esta guia):
smtpd_sasl_auth_enable = yes
# Si tus clientes van a utilizar Outlook Express o más antiguos
# esto necesitará ser cambiado a yes
broken_sasl_auth_clients = no
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain =
# Añadimos permit_sasl_authenticated a las existentesSalimos y guardamos main.cf y nos vamos a configurar en modo de arranque de SASL.
# reglas de smtpd_sender_restrictions
smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks, warn_if_reject reject_non_fqdn_sender, reject_unknown_sender_domain, reject_unauth_pipelining, permit
# Añadimos permit_sasl_authenticated a las existentes
# reglas de smtpd_recipient_restrictions
smtpd_recipient_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unauth_destination, check_policy_service inet:127.0.0.1:10023, permit
sudo nano /etc/default/saslauthdCambiamos lo siguiente:
START=yesLo siguiente es indicar a Postfix como trabajar con SASL
# Indicamos, en la última línea del fichero, el directorio de trabajo
# en el que creamos anteriormente dentro de spool de Postfix y
# añadimos -r para indicar que el domino es parte del nombre de usuario
OPTIONS="-r -c -m /var/spool/postfix/var/run/saslauthd"
sudo nano /etc/postfix/sasl/smtpd.confLo dejamos así:
pwcheck_method: saslauthdVamos a decirle al módulo PAM como autenticar al SMTP usando MySQL.
mech_list: plain login cram-md5 digest-md5
log_level: 7
allow_plaintext: true
auxprop_plugin: sql
sql_engine: mysql
sql_hostnames: 127.0.0.1
sql_user: mail
sql_passwd: mailPASS # Contraseña de tu usuario mail en MySQL
sql_database: maildb
sql_select: select crypt from users where id='%u@%r' and enabled = 1
sudo nano /etc/pam.d/smtp
# El siguiénte código ha de estar en dos líneas y hay que acordarse de sustiruir 'mailPASS' por nuestra contraseña del usuario mail en mySQL.
auth required pam_mysql.so user=mail passwd=mailPASS host=127.0.0.1 db=maildb table=users usercolumn=id passwdcolumn=crypt crypt=1Con esto hemos terminado la configuración de SASL en Postfix. Recomiendo poner a prueba su funcionamiento, mandando y recibiendo correo como se indica en el Anexo I pero estando atento a los registros de /var/log/mail.log , /var/log/mysql.log y /var/log/auth.log
account sufficient pam_mysql.so user=mail passwd=mailPASS host=127.0.0.1 db=maildb table=users usercolumn=id passwdcolumn=crypt crypt=1
IMAP_CAPABILITY="IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA AUTH=CRAM-MD5 AUTH=CRAM-SHA1 IDLE"Una vez realizada la configuración de SASL, reiniciamos los servicios afectados:
sudo /etc/init.d/saslauthd restart
sudo /etc/init.d/postfix restart
cd /etc/postfixEste comando nos servirá para generar una clave PEM encriptada con el algoritmo RSA de 2048 bits con una valided de casi 3 años, suficiente. Nos pedirá una serie de requisitos antes de hacerlo siendo estos; El código de el país (dos letras), estado o provincia, ciudad, compañía, nombre del dominio (FQDN) y e-mail. Lo anterior se puede dejar vacío o con valores por defecto pulsando intro, excepto el nombre del dominio para el que sirve la clave.
sudo openssl req -new -outform PEM -out postfix.cert -newkey rsa:2048 -nodes -keyout postfix.key -keyform PEM -days 999 -x509
sudo nano /etc/postfix/main.cfDonde cambiaremos las rutas y nos aseguraremos de que se use TLS:
smtpd_tls_cert_file=/etc/postfix/postfix.certAhora le toca al fichero master.cf donde buscaremos las líneas "smtps" y "submission" (el cual forzaremos también a usar TLS) asegurándonos que sean como sigue o cambiandolas en su defecto.
smtpd_tls_key_file=/etc/postfix/postfix.key
smtpd_use_tls=yes
submission inet n - n - - smtpdAhora seguimos con el otro certificado necesario; el de Courier. Como hemos hecho antes con Postfix, vamos a la carpeta de este y lo generamos:
-o smtpd_sasl_auth_enable=yes
# Si no quieres forzar a submission a utilizar únicamente TLS
# Añade un comentario a la siguiente línea
-o smtpd_tls_auth_only=yes
# -o smtpd_tls_security_level=encrypt
# -o header_checks=
# -o body_checks=<
-o smtpd_client_restrictions=permit_sasl_authenticated,reject_unauth_destination,reject
-o smtpd_sasl_security_options=noanonymous,noplaintext
-o smtpd_sasl_tls_security_options=noanonymous
# -o milter_macro_daemon_name=ORIGINATING<
smtps inet n - - - - smtpd
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_tls_auth_only=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_sasl_security_options=noanonymous,noplaintext
-o smtpd_sasl_tls_security_options=noanonymous
# -o milter_macro_daemon_name=ORIGINATING
cd /etc/courierY editamos su configuración para forzar TLS/SSL e indicar la ruta del certificado:
sudo openssl req -x509 -newkey rsa:1024 -keyout imapd.pem -out imapd.pem -nodes -days 999
sudo nano /etc/courier/imapd-sslCambiando a lo siguiente:
TLS_CERTFILE=/etc/courier/imapd.pemHasta aquí ya hemos configurado por completo el servidor de correo, quedaría reiniciar los servicios con la nueva configuración y probarlo todo (Anexo 1). No es poca cosa... No obstante esta no va a ser la última entrada de la guía ya que para un correcto funcionamiento y una fácil administración hace falta hablar de PHPmyAdmin y crear usuarios y alias necesarios para el correo local etc. Además quiero incluir el cliente web SquirrelMail para proporcionar acceso IMAP/POP3 a los usuarios por medio de nuestra web.
IMAP_TLS_REQUIRED=1
sudo apt-get install amavisd-newLa configuración por defecto en Ubuntu nos vale perfectamente así que nos limitaremos a leer su documentación para entender cómo funciona y echaremos un vistazo sus opciones predefinidas con un cat:
cd /etc/amavis/conf.d/En este directorio podemos ver archivo por archivo la configuración de Amavis pero en principio, nos valen los valores tal como están. Sólo uno de ellos será modificado:
sudo nano /etc/amavisd/50-userAñadimos antes de las 2 lineas finales:
@local_domains_acl = qw(.);Ahora hemos de permitir a Amavis el acceso a los correos para que pueda chequearlos así que nos vamos a reconfigurar Postfix:
$log_level = 2;
$syslog_priority = 'debug';
$sa_tag_level_deflt = 2.0; # add spam info headers if at, or above that level
# $sa_tag2_level_deflt = 6.31; # add 'spam detected' headers at that level
$sa_kill_level_deflt = 8.0; # triggers spam evasive actions
# $sa_dsn_cutoff_level = 10; # spam level beyond which a DSN is not sent
$final_spam_destiny = D_PASS;
# $final_spam_destiny = D_REJECT; # default
# $final_spam_destiny = D_BOUNCE; # debian default
# $final_spam_destiny = D_DISCARD; # ubuntu default, recommended as sender is usually faked
amavis unix - - - - 2 smtpEn el mismo archivo buscamos el servicio pickup y añadimos dos líneas, quedándose así:
-o smtp_data_done_timeout=1200
-o smtp_send_xforward_command=yes
-o disable_dns_lookups=yes
-o max_use=20
127.0.0.1:10025 inet n - - - - smtpd
-o content_filter=
-o local_recipient_maps=
-o relay_recipient_maps=
-o smtpd_restriction_classes=
-o smtpd_delay_reject=no
-o smtpd_client_restrictions=permit_mynetworks,reject
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o smtpd_data_restrictions=reject_unauth_pipelining
-o smtpd_end_of_data_restrictions=
-o mynetworks=127.0.0.0/8
-o smtpd_error_sleep_time=0
-o smtpd_soft_error_limit=1001
-o smtpd_hard_error_limit=1000
-o smtpd_client_connection_count_limit=0
-o smtpd_client_connection_rate_limit=0
-o receive_override_options=no_header_body_checks,no_unknown_recipient_checks
pickup unix n - - 60 1 pickupEn el otro archivo de configuración de Postfix:
-o content_filter=
-o receive_override_options=no_header_body_checks
sudo nano /etc/postfix/main.cfAñadimos la siguiente línea:
content_filter = amavis:[127.0.0.1]:10024Con esta configuración Amavis debería poder tener acceso al correo. Antes de activar el anti virus y el anti spam deberíamos probar que realmente lo tiene. Primero reiniciamos el servicio con un sudo /etc/init.d/amavis restart Ahora hemos de enviar un correo al servidor y buscar en los logs que amavis cumple su función. Recomiendo seguir el Anexo I, en concreto la prueba para ver si Postfix recibe correo conectándonos por telnet y después comprobar syslog con un:
cat /var/log/syslog | grep 'amavisd-new, port 10024'
sudo nano /etc/amavis/conf.d/15-content_filter_mode
@bypass_virus_checks_maps = (
\%bypass_virus_checks, \@bypass_virus_checks_acl, \$bypass_virus_checks_re);
@bypass_spam_checks_maps = (Si ya hemos hecho la comprobación de funcionamiento de Amavis y todo va como debe, podemos proceder a bajar el nivel de registro que alteramos anteriormente:
\%bypass_spam_checks, \@bypass_spam_checks_acl, \$bypass_spam_checks_re);
sudo nano /etc/amavis/conf.d/50-userEl bloque de configuración que añadimos casi al final cambiará a éste:
@local_domains_acl = qw(.);Hemos terminado con Amavis. Reiniciamos servicio y pasamos a lo siguiente:
$log_level = 1;
$syslog_priority = 'info';
#$sa_tag_level_deflt = 2.0; # add spam info headers if at, or above that level
# $sa_tag2_level_deflt = 6.31; # add 'spam detected' headers at that level
$sa_kill_level_deflt = 8.0; # triggers spam evasive actions
# $sa_dsn_cutoff_level = 10; # spam level beyond which a DSN is not sent
#$final_spam_destiny = D_PASS;
# $final_spam_destiny = D_REJECT; # default
# $final_spam_destiny = D_BOUNCE; # debian default
$final_spam_destiny = D_DISCARD; # ubuntu default, recommended as sender is usually faked
sudo /etc/init.d/amavis restart
sudo nano /etc/default/spamassassinDónde cambiamos ENABLED para dejarlo como sigue:
ENABLED=1
sudo apt-get install clamav clamav-base libclamav6 clamav-daemon clamav-freshclamClamAv tampoco necesitas grandes cambios. Podemos ver su configuración en /etc/clamav/ pero en principio será más por curiosidad. Freshclam en cambio quizá necesite un pequeño ajuste. Es el servicio que se encarga de la actualización de la base de datos de virus y por defecto viene configurado para hacerlo una vez cada hora (24 por día) lo cual es algo excesivo.
sudo dpkg-reconfigure clamav-freshclamEsta línea nos llevará a un asistente de configuración en el que podremos cambiar la tasa a 1 así como elegir un servidor cercano y definir las opciones de conexión.
sudo dpkg-reconfigure clamav-baseVamos a añadir el usuario clamav a Amavis para que pueda escanear los ficheros temporales.
sudo adduser clamav amavisCon esto está todo. Seguimos con Postgrey.
sudo apt-get install postgreyDejamos la configuración por defecto y nos vamos a Postfix para añadir normas de interacción con esta nueva herramienta:
sudo nano /etc/postfix/main.cfY ahí buscamos la linea recipient_restrictions y la dejamos como sigue (Una única línea):
smtpd_recipient_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unauth_destination, check_policy_service inet:127.0.0.1:10023, permitListo.
sudo /etc/init.d/mysql restart
sudo /etc/init.d/postfix restart
sudo /etc/init.d/courier-imap-ssl restart
sudo /etc/init.d/courier-imap restart
sudo /etc/init.d/courier-authdaemon restart
INSERT INTO domains (domain) VALUESPara el usuario 'test' con contraseña 'test1234'
('tudominio.com');
INSERT INTO users (id,name,maildir,crypt) VALUESY para terminar, creamos entradas en la table de aliases.
('test@tudominio.com','test','test/',encrypt('test1234', CONCAT('$5$', MD5(RAND()))) );
INSERT INTO aliases (mail,destination) VALUESCon aliases, en la primera entrada de la tabla, hemos enlazado la dirección pruebas@tudominio.com con la dirección del usuario test. La segunda entrada es para que se dé como final la dirección test@tudominio.com
('pruebas@tudominio.com','test@tudominio.com'),
('test@tudominio.com','test@tudominio.com');
chaosnet@lamula:~$ telnet localhost 25
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 lamula.thedarknode.es ESMTP Postfix (Ubuntu)
# Introducimos el saludo
EHLO lamula.thedarknode.es
250-lamula.thedarknode.es
250-PIPELINING
250-SIZE 10240000
250-ETRN
250-STARTTLS
250-AUTH PLAIN LOGIN CRAM-MD5 DIGEST-MD5
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
# La respuesta anterior del servidor puede variar según lo que
# tengamos configurado hasta ahora.
# Ahora indicamos al servidor que queremos dejar un correo e indicamos
# su dirección de origen. No tiene por qué ser real para las pruebas.
MAIL FROM: <test@dominiodeprueba.com>
250 2.1.0 Ok
# Si todo va bien nos devolverá un Ok y seguiremos con la dirección de destino
RCPT TO: <chaosnet@thedarknode.es>
250 2.1.5 Ok
# Otro Ok nos sirve para pasar al comando para dar el contenido del mensaje.
data
354 End data with <CR><LF>.<CR><LF>
# Escribimos en cuerpo y salimos con una linea de un solo punto
Esto es una prueba de recepción de correo para nuestro servidor Postfix.
Visita http://blog.thedarknode.es !!
.
250 2.0.0 Ok: queued as 2629A581177
# Tras un nuevo Ok indicándonos que el mensaje a sido aceptado y puesto
# en cola, cerramos la conexión con un;Esto, si lo acompañamos en otra terminal de un
quit
221 2.0.0 Bye
Connection closed by foreign host.
chaosnet@lamula:~$
tail -f -n 30 /var/log/mail.logY abrimos una tercera para:
tail -f -n 30 /var/log/mysql/mysql.logNos debería bastar para ver los errores que pudiese haber en nuestra configuración.
chaosnet@lamula:~$ telnet localhost 25
...
Connected to localhost.
Escape character is '^]'.
220 lamula.thedarknode.es ESMTP Postfix (Ubuntu)
EHLO lamula.thedarknode.es
250-lamula.thedarknode.es
250-PIPELINING
...
# Aquí esta vez damos una dirección válida en nuestro servidor
MAIL FROM: <chaosnet@thedarknode.es>
250 2.1.0 Ok
# Y aquí dejamos una dirección en internet a la que tengamos accesoComprobando el registro de /var/log/mail.log y nuestra cuenta de correo a la que enviamos el mensaje, verificaremos si hay errores y cuales son.
RCPT TO: <mail@real.com>
250 2.1.5 Ok
data
354 End data with <CR><LF>.<CR><LF>
Esto es una prueba de envio de correo para nuestro servidor Postfix.
Visita http://blog.thedarknode.es !!
.
250 2.0.0 Ok: queued as 85EE2581189
quit
221 2.0.0 Bye
Connection closed by foreign host.
chaosnet@lamula:~$ telnet localhost 143Si la salida es parecida a esa, el demonio está escuchando de manera correcta, de lo contrario habría que revisar los logs.
Trying ::1...
Connected to localhost.
Escape character is '^]'.
* OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA AUTH=CRAM-MD5 AUTH=CRAM-SHA1 IDLE ACL ACL2=UNION STARTTLS] Courier-IMAP ready. Copyright 1998-2011 Double Precision, Inc. See COPYING for distribution information.