miércoles, 23 de diciembre de 2015

Configurando Django para entrar en producción

En realidad estoy probando esta configuración para verificar las posibilidades de Django para entrar en producción. Pues que voy a usarlo de esta forma para desarrollo, aunque el servidor que trae para ese caso pueda ser suficiente. En todo caso vamos:
Estoy tomando en consideración:
  • La instalación de Debian Jessie es la mínima necesaria para que el sistema funcione
  • El usuario mafi es un usuario sin privilegios administrativos. Ni siquiera tiene permisos especiales mediante sudo
  • Lo anterior significa que el proceso uwsgi corre con los permisos y limitaciones de un usuario corriente. Que andar corriendo servicios como root es del diablo
Preparación del sistema base:
Como usuario root:
Instalamos paquetes necesarios
aptitude install python-pip
virtualenv nginx python-dev gcc
Creamos el directorio sobre el cual vamos a crear el entorno con virtualenv
mkdir /var/www/yacto
usermod -G -mafi www-data
chown mafi:www-data /var/www/yacto
Como el usuario sin privilegios (mafi en mi caso) Ahora, como el usuario corriente con el que vamos a trabajar
cd /var/www
virtualenv yacto/
cd yacto/
source bin/activate
Notará que hubo un cambio en la shell, y eso es bueno, de la siguiente forma:
mafi@dev:/var/www/yacto$
(yacto)mafi@dev:/var/www/yacto$
Ahora podemos instalar los paquetes en el entorno virtual
pip install Django uwsgi psycopg2
Inicializamos el proyecto
django-admin startproject yacto
Como usuario postgres (Incluso, en otro servidor dedicado para tal fin)
Si es que acaso es tu primera vez, basta con hacer lo siguiente
createuser -DRSP yacto
Ingrese la contraseña para el nuevo rol:
Ingrésela nuevamente:
createdb -O yacto yacto
Como el usuario sin privilegios (mafi en mi caso)
La configuración por defecto para un proyecto con Django se almacena en yacto/settings.py, así que lo configuramos de la siguiente forma:

Para configurar el uso de la base de datos anteriormente creada, modificamos el diccionario DATABASES de la siguiente forma:
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'yactividades_2',
        'USER': 'yactividades_2',
        'PASSWORD': 'yactividades_2',
        'HOST': '192.168.2.3',
        'POST': '5432',
        'client_encoding': 'UTF8',
        'default_transaction_isolation': 'read committed',
        'timezone': 'UTC'
    }
}
Lo de 'client_encoding', 'default_transaction_isolation' y 'timezone' lo explican acá: Optimizing PostgreSQL’s configuration

Hacia el final del mencionado fichero, encontramos la sección "Static files". Al final de ella, agregamos la constante STATIC_ROOT
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static/")

Como consejo adicional, nunca esta de más configurar "Internationalization", sección que esta precisamente arriba de "Static Files"
# Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/

LANGUAGE_CODE = 'es-SV'

TIME_ZONE = 'America/El_Salvador'

USE_I18N = True

USE_L10N = True

USE_TZ = True
Empezamos a crear los ficheros de configuración:

El directorio referenciado en  para almacenar contenido estático:
mkdir static
aplicacion=$(basename $PWD)
touch $aplicacion.log
Creamos el fichero $aplicacion\_nginx.conf para configuración del sitio web con el siguiente contenido. Las opciones son las básicas funcionales, y basta con cambiar yacto por el nombre de su aplicación, aunque
# $aplicacion\_nginx.conf
# Conexión que Nginx hace con Django
upstream yacto {
    server unix:///var/www/yacto/yacto/yacto.sock;
}

# Configuración del servidor
server {
    # Puerto para servir el sitio
    listen     80;
    # Sustituir por IP o FQDN donde se quiera publicar tal sitio
    server_name yacto.salud.gob.sv;
    charset     utf-8;

    # max upload size
    client_max_body_size 75M;

    # Ficheros estáticos del proyecto, referenciado en STATIC_ROOT del fichero settings.py del proyecto
    location /static {
        alias /var/www/yacto/yacto/static/;
    }

    # Finally, send all non-media requests to the Django server.
    location / {
        uwsgi_pass yacto;
        #include /var/www/yacto/yacto/uwsgi_params;
        include /etc/nginx/uwsgi_params;
    }

}
Ahora creamos el fichero yacto_uwsgi.ini para configurar varias opciones a tomar por parte de uwsgi, las cuales bien pueden ser configuradas al arrancar la aplicación, pero respecto a configuraciones, siempres será más ordenado un archivo de configuración (Obvio, supongo), que guardar en alguna parte las opciones que un comando ha de usar.
# $aplicacion\_uwsgi.ini file
[uwsgi]

# Django-related settings
# the base directory (full path)
chdir           = /var/www/yacto/yacto
# Django's wsgi file
module          = yacto.wsgi
# the virtualenv (full path)
home            = /var/www/yacto

# process-related settings
# master
master          = true
# maximum number of worker processes
processes       = 10
# the socket (use the full path to be safe
socket          = yacto.sock
# Recordar que en Debian los ficheros se crean 755, lo cual hace necesario que esta opción se use, so riesgo de errores relacionados a permisos
chmod-socket    = 660
# clear environment on exit
vacuum          = true
# El proceso corre como demonio y loguea en yacto.log
# Lo mejor es usarlo sólo en conjunto a emperor, es un poco engorroso de usar de lo contrario
# daemonize     = yacto.log

Teniendo todo esto listo, preparamos a Django para irse en línea:
python manage.py makemigrations
No changes detected
python manage.py migrate
Operations to perform:
  Apply all migrations: admin, contenttypes, auth, sessions
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying sessions.0001_initial... OK

python manage.py createsuperuser
Username (leave blank to use 'mafi'):
Email address: mafi@yacto.com
Password:
Password (again):
Superuser created successfully.

python manage.py collectstatic

You have requested to collect static files at the destination
location as specified in your settings:

    /var/www/yacto/yacto/static

This will overwrite existing files!
Are you sure you want to do this?

Type 'yes' to continue, or 'no' to cancel: yes

Copying '/var/www/yacto/local/lib/python2.7/site-packages/django/contrib/admin/static/admin/fonts/LICENSE.txt'
Copying '/var/www/yacto/local/lib/python2.7/site-packages/django/contrib/admin/static/admin/fonts/README.txt'
[... Contenido suprimido ...]
Copying '/var/www/yacto/local/lib/python2.7/site-packages/django/contrib/admin/static/admin/js/vendor/jquery/jquery.min.js'
Copying '/var/www/yacto/local/lib/python2.7/site-packages/django/contrib/admin/static/admin/js/vendor/jquery/LICENSE-JQUERY.txt'

56 static files copied to '/var/www/yacto/yacto/static'.

Y luego nos volvemos root de nuevo y hacemos lo siguiente para "declarar" el sitio en nginx
ln -s /var/www/yacto/yacto/yacto_nginx.conf /etc/nginx/sites-enabled/
systemctl status nginx.service

Fuentes
Pues que nada, esto es la versión más resumida de Setting up Django and your web server with uWSGI and nginx. La referida guía se detiene en bastante detalles que explican bastante el funcionamiento, incluso esta haciendo pruebas continuamente.

Tiene una ligera influencia de Setup Django on Debian 8

Y obviamente las fuentes oficiales: How to install DjangoManaging static files (e.g. images, JavaScript,CSS)

lunes, 30 de noviembre de 2015

Instalando Oysttyer (¡Cliente de twitter desde la terminal!)

Siempre con la intención de no evitar salir de nuestra zona de confort (Algunos le llaman "flujo de trabajo", pero no estoy del todo seguro), resulta que hace tiempo venía usando ttytter como cliente de twitter a usar desde consola.
Tenía varias razones para ello: Usa menos recursos, es bastante sencillo de usar (De hecho puede llegar a automatizarse bastante bien). Y tus compañeros de trabajo creen que estás a punto de compilar algún driver exótico.

Pues resulta que esta muerto desde hace años. La versión 2.1.0 data de Diciembre de 2012. La última vez que lo instalé, el año pasado, pasé por alto el banner a la entrada del sitio, supongo.

La cuestión es que existe un fork oficial llamado oysttyer que cuenta con la bendición del creador de ttytter. Instalar el fork es tan fácil como lo era instalar al padre.

Es posible clonar el repo desde git en el repositorio que puse arriba. O pueden bajar archivo comprimido desde la "página oficial" del proyecto.

Oysttyer, al igual que ttyter, consta básicamente de un sólo fichero perl. Yo lo tiré a /usr/bin, no quisé complicarme demasiado con esas cosas:

Como root:
wget https://github.com/oysttyer/oysttyer/tarball/master -O master.tgz

tar -xzvf master.tgz

cd oysttyer-oysttyer-c6ff39e/

cp oysttyer-oysttyer-*/oysttyer.pl /usr/bin/oystter

Ahora, cada usuario del sistema puede usar oystter, basta con que acceda desde consola para configurarlo de la siguiente manera:
Copiamos la URL y la pegamos EN UNA LÍNEA en el navegador


Autorizamos la aplicación
Eso nos devuelve el pin de autorización por parte de Twitter
Retornamos a la consola y pegamos el pin
La próxima vez que abrimos la aplicación, aparece un pato

domingo, 25 de octubre de 2015

Compilando xscreensaver

Con mi nuevo equipo, una HP ENVY Notebook 15-k212la, resulta que casi todo funcionó a la primera, incluso la tarjeta inalámbrica, que suele ser un verdadero problema para algunos.

Sin embargo, los controles para el brillo de pantalla no estaban funcionando. No es especialmente un problema, pero era sumamente incómoda la situación. Claro que es posible configurar estos valores por consola, pero una laptop es una buena ocasión para introducir a usuarios comunes en el mundo del software libre.

Incluso tomé las recomendaciones de instalar Bumblebee en OpenSuSE, que antes ya lo había probado con Fedora, pero parece que al final ni siquiera funciona como se supone y seguía sin solucionar el problema.

Así que al volver sobre mis pasos, me encuentro con que la respuesta era más sencilla de lo que había pensado: Intel graphics de Arch Wiki y [SOLVED] Backlight control not working in X/KDE me estaban gritando que la solución era tan sencilla como realizar el procedimiento que se estipula en [SOLVED] Brightness problem in Ubuntu 12.04 Precise Pangolin.

Nota: Una tontería. Configurada de esta forma, en
/sys/class/backlight/
sólo queda el directorio que corresponde al vendor. Así que es el valor en
/sys/class/backlight/intel_backlight/actual_brightness
el que cambia con las teclas para control de brillo

Además, parece que las entradas en el registro del sistema respecto a
[ 2273.719866] atkbd serio0: Unknown key pressed (translated set 2, code 0xab on isa0060/serio0).
[ 2273.719870] atkbd serio0: Use 'setkeycodes e02b ' to make it known.
[ 2273.773690] atkbd serio0: Unknown key released (translated set 2, code 0xab on isa0060/serio0).
[ 2273.773694] atkbd serio0: Use 'setkeycodes e02b ' to make it known.

eran sólo una pista falsa.

Así que en Fedora, bastará con agregar acpi_backlight=vendor" a la línea GRUB_CMDLINE_LINUX del fichero /etc/default/grub:
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="rd.lvm.lv=fedora_ilaria/root rd.lvm.lv=fedora_ilaria/swap rhgb quiet acpi_backlight=vendor"
GRUB_DISABLE_RECOVERY="true"
Ahora ejecutamos para que vuelva a crear el fichero de configuración de grub (En este caso, para un equipo que usa UEFI) con el siguiente comando:
grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg
Y al reiniciar el equipo, las teclas correspondientes en el teclado será capaces de configurar el brillo de pantalla

lunes, 19 de octubre de 2015

Primeros pasos con Mustache

Mustache es un sistema de plantillas sin lógica, (Traducción literal de la descripción que hacen de si mismos en el sitio web), lo que significa que a diferencia de otros sistemas de plantillas para JavaScript este no define estructura de control, sino que trabaja definiendo etiquetas.

Es un gran adelanto usar un sistema de plantillas. Separar la "lógica de negocios" de la presentación siempre es un gusto, pese a los inconvenientes que pueda presentar en cuánto a rendimiento. Por otra parte, no es díficil aprender a usar Mustache, y su sencillez su no es problema para las capacidades que presenta:

Su uso puede resumirse de la siguiente forma:

  • Se crea una plantilla a usar sobre como han de tratarse los datos, tomando como referencia un objeto JSON. En mi caso, mi JSON es un array cuyos datos son los que necesito mostrar:

<tbody id="respuesta">
    <script id="respuesta-template" type="text/x-custom-template">
        {% verbatim %}
            {{#datos}}
                {{#.}}
                    <tr id="{{uid}}"><td>
                        <p class="col-md-12"><b>{{ cn }}</b></p>
                        <p class="col-md-12 col-sm-12 small">{{ #title }} {{ title }} en {{ /title }}{{ #ou }}{{ ou }} de {{ /ou }}{{ establecimiento }}</p>
                        <p class="col-md-6 col-sm-12 small">{{ mail }}</p>
                        <p class="col-md-6 col-sm-12 small">{{ telephoneNumber }</p>
                    </td></tr>
                {{/.}}
            {{/datos}}
        {% endverbatim %}
    </script>
</tbody>

  • El objeto JSON va definido, datos más, datos menos, de la siguiente forma:

{
    "datos": [
        {
            "cn": "AdaCruz",
            "mail": "acruz@salud.gob.sv",
            "uid": "acruz"
        },
        {
            "cn": "Adalberto Chavez",
            "mail": "achavez@salud.gob.sv",
            "uid": "achavez"
        }
    ],
    "mensajeError": null
}
La idea es que la etiqueta {{#datos}} permite acceder no a los datos de la clave en el objeto JSON, sino más bien a la funcionalidad por defecto que siendo un array es la de recorrerlo. Lo de {{#.}} es un extraño workaround ya que parece que el objeto JSON es recibido seccionado.
Por otra parte {{#.}}, podría ser útil para recorrer un objeto JSON que consista en una seria de array sin índice alguno.

Lo demás, es que cuando estemos recorriendo cada objeto json que compone el array tendremos el dato que cada índice contiene.

Luego, vale la pena mencionar el uso que se le ha vuelto a dar a la etiqueta de tipo {{ #etiqueta }} en el caso del segundo bloque:
<p class="col-md-12 col-sm-12 small">{{ #title }} {{ title }} en {{ /title }}{{ #ou }}{{ ou }} de {{ /ou }}{{ establecimiento }}</p>
Las etiquetas de este modo funcionan como un condicionante. Lo que este dentro de ellas no será mostrado a menos que haya contenido que mostrar. En este caso particular, lo he usado para construir la frase según haya uno u otro contenido, pero supongo que será posible enmarcar toda una etiqueta HTML si se antoja necesario.
  • Por último, el código que realiza todo el trabajo de presentación va de la siguiente forma:
var template = $('#respuesta-template').html();
Mustache.parse(template);
var contenido = Mustache.render(template, respuesta);
pmostrarError(respuesta);
pmostrarMensaje(respuesta);
$("#respuesta tr").remove();
$("#respuesta").append(contenido);

El resultado viene quedando de la siguiente forma:
Los íconos son parte del proyecto Material Icons de Google  y omití las etiquetas de imagen para hacer legible al ejemplo 

Tener en cuenta que este intento de manual ha obviado muchas cosas: El que este usando Bootstrap y JQuery por ejemplo.

Fuentes:
Defining a HTML "template" to append using JQuery
mustache.js - Logic-less {{mustache}} templates with JavaScript
Mustache.js + jQuery: what is the minimal working example ?
How to use Mustache with JS / jQuery – a working example

jueves, 17 de septiembre de 2015

Algunos apuntes sobre como sobrevivir a una implementación de ZoneMinder

Instalación:

Se describe en la wiki de ZoneMinder la instalación para muchas distribuciones, entre ellas en Debian: Debian 8.1 64-bit with Zoneminder 1.28.1 the Easy Way. Esta es la forma en que debe hacerse. No entiendo las razones (Y no tengo la disposición por ahora de descubrirlas) pero tuve un fallo en instalando en un servidor de prueba donde había un servidor apache previamente instalado. Los problemas obtenidos fueron miles, como por ejemplo:

01/07/12 19:49:32.342530 zmpkg[2016].ERR [Unable to run "/usr/bin/zmdc.pl startup", output is "Starting server"] 
Y no tuve más que virtualizar otro servidor de prueba, y empezar la instalación desde cero siguiente exactamente los pasos descritos en la wiki. Alguien en esta lista de usuarios Debian (Que a su vez referencia a un lista de usuarios de Ubuntu, atentos a #5 y #8) habla como si fuera un problema de orden en los pasos a seguir. Nunca a este nivel, pienso, pero no he podido ahondar más en las causas reales.

Configuración inicial: Siguen los problemas

Hacer el primer intento por agregar cámaras hizo que los siguientes problemas aparecieran:
Sep 17 03:42:58 zoneminder zmc_m2[28085]: INF [Starting Capture]
Sep 17 03:42:59 zoneminder web_php[20362]: ERR [socket_sendto( /var/run/zm/zms-933322s.sock ) failed: No such file or directory]
Sep 17 03:42:59 zoneminder web_php[20362]: ERR [getStreamCmdResponse stream error: socket_sendto( /var/run/zm/zms-933322s.sock ) failed: No such file or directory - checkStreamForErrors()]
Sep 17 03:43:01 zoneminder zmc_m1[26920]: ERR [Unable to open input rtsp://admin:12345@192.0.2.1//Streaming/Channels/1 due to: Operation now in progress]
Sep 17 03:43:03 zoneminder zmc_m2[28085]: ERR [RTP timed out]
Sep 17 03:43:06 zoneminder web_php[21144]: ERR [socket_sendto( /var/run/zm/zms-298700s.sock ) failed: No such file or directory]
Sep 17 03:43:06 zoneminder web_php[21144]: ERR [getStreamCmdResponse stream error: socket_sendto( /var/run/zm/zms-298700s.sock ) failed: No such file or directory - checkStreamForErrors()]
Sep 17 03:43:11 zoneminder zmc_m1[26920]: ERR [Unable to open input rtsp://admin:12345@192.0.2.1//Streaming/Channels/1 due to: Operation now in progress]
Sep 17 03:43:12 zoneminder zmc_m2[28085]: ERR [Failed to pre-capture monitor 2 (0/1)]
Sep 17 03:43:12 zoneminder zmc_m2[28085]: INF [Terminating Logger]
No entiendo si en realidad se debía a que las cámaras pudieran estar apagadas en ese momento (Algo que es posible debido al escaso control que tengo sobre ellas), o al error que pude solucionar en Apache, que solucioné de la forma más chapucera: En el fichero /etc/apache2/conf-enabled/serve-cgi-bin.conf
<ifmodule mod_alias.c="">
        <ifmodule mod_cgi.c="">
                Define ENABLE_USR_LIB_CGI_BIN
        </ifmodule>

        <ifmodule mod_cgid.c="">
                Define ENABLE_USR_LIB_CGI_BIN
        </ifmodule>

        <ifdefine enable_usr_lib_cgi_bin="">
        ScriptAlias /cgi-bin "/usr/lib/zoneminder/cgi-bin"
        <directory cgi-bin="" lib="" usr="" zoneminder="">
            Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
            AllowOverride All
            Require all granted
        </directory>
        </ifdefine>
</ifmodule>
es necesario borrar la configuración de los alias por defecto para cgi, lo cual puede ser un problema si acaso se usan otros en el mismo sistema, para que quede de la siguiente forma:
<ifmodule mod_alias.c="">
        <ifmodule mod_cgi.c="">
                Define ENABLE_USR_LIB_CGI_BIN
        </ifmodule>

        <ifmodule mod_cgid.c="">
                Define ENABLE_USR_LIB_CGI_BIN
        </ifmodule>
</ifmodule>
Con lo cual la única configuración al respecto será la contenida en el fichero /etc/apache2/conf-available/zoneminder.conf que debe cargarse en el momento justo en que la guía de configuración que referí arriba lo requiera

Configurando cámaras.

Aunque pensé que sería un dolor de cabeza, lo cierto es que soportan bastante hardware (Lo de basarse en protocolos abiertos, supongo). Mi equipo es un DS-2CD2132-I que alguién ya se había descrito en la wiki. Aunque es posible usar directamente RTSP en lugar de RTSP mediante ffmpeg, resulta que la opción directa presenta problemas de rendimiento brutales. Con ffmpeg tiene un nivel bastante aceptable, sobre todo en lo referente a la alarma por zonas.

martes, 28 de julio de 2015

TTY inaccesibles en Fedora > 21

Hace tiempo había tenido problemas graves respecto al entorno gráfico en OpenSuse y Fedora, que resolví configurando nomodeset.

Luego, desde que estoy usando el driver privativo de mi tarjeta de vídeo NVIDIA no había podido acceder a tty alguna en mi Fedora, pero era algo que no me importaba (Al menos hasta que tuve tiempo para abordar el problema) Además, cuando el sistema iniciaba no podía ver el los indicadores de booteo (Otra cosa que poco importaba en realidad). La conjunción de ambas cosas fue lo que me llevó a pensar que el problema se relacionaba con el sistema gráfico, por lo que mi primera sospecha fue de nuevo nomodeset. Y la sospecha fue cierta.

Estos problemas se solucionan configurando nomodeset, al parecer las tarjetas nvidia también se aprovechan de las características que tal característica ofrece, por lo tanto la solución pasa por indicar en el grub que el arranque se haga con la opción nomodeset activada:

Abrimos el fichero de configuración por defecto para grub, la forma más segura de configurar grub
/etc/default/grub

Y agregamos/modificamos la línea de configuración GRUB_CMDLINE_LINUX_DEFAULT
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"

Actualizamos el fichero de configuración:
grub2-mkconfig -o /boot/grub2/grub.cfg

Y reiniciamos el equipo. Ahora ya es posible acceder a las tty, aprovechando las escasas ventajas que ello confiere en un sistema pensando para escritorio. Pero ya esta solucionado, y eso es lo importante del asunto.

(1) Fuente
(2) Fuente

martes, 9 de junio de 2015

Cbpolicyd en Zimbra

Un tux-cartero-censor es la mejor idea para un logo
 Parafraseando la descripción que ellos mismos dicen acerca de su desarrollo:
PolicyD v2 (Nombre en clave “cluebringer”, de cuya contracción queda cbpolicyd), es un servidor de políticas multiplataforma para los servidores MTA más populares. Este servicio de políticas está diseñado principalmente para entornos de hosting de correo a gran escala. El principal objetivo es implementar tantas características de combate al spam y de sanitización de correo como sea posible, mientras al mismo tiempo se mantiene la portabilidad, estabilidad y rendimiento requerido para la crítica misión de hosting de correo. La mayoría de ideas y métodos implementados en cbpolicyd derivan de Policyd v1, así como de la larga participación de los autores en la industria del hosting de correo a gran escala.
Si bien pueden complicarse las reglas con el fin de usar todas las características posibles (Verificación de SPF y HELO, por mencionar algunas), es necesario entender que la comprobación de cbpolicyd ocurre dentro de los procesos de postfix, por lo que puede reducir el tiempo de respuesta frente al usuario.
Las demás características de cbpolicyd pueden implementarse dentro de la configuración anti-spam posterior, siendo de hecho un poco menos restrictivas y más flexibles.

Por ahora, usaremos cbpolicyd para definir políticas de volumen de correo por remitente, esto es, cuántos correos puede un mismo remitente enviar por unidad de tiempo, una característica que le dará un perfil bastante profesional a nuestro servicio de correo electrónico.

La configuración por defecto de zimbra (Bastante sencilla de poner en marcha, dicho sea de paso) tiene un problema de rendimiento inherente al hecho de usar Sqlite como backend, así que en su momento hemos de cambiar la configuración para que use una base de datos que hemos de configurar en el servidor mysql que zimbra ha instalado por defecto.
No sé de otros casos, pero un servidor mediano con más de 5,000 cuentas de correo se verá seriamente afectado en algunas horas del día si no se hace este cambio.

Comprobamos que sqlite esté instalado en el servidor, caso contrario, tendremos un pequeño error al arrancar el servicio por primera vez:

rpm -qa | grep sqlite
sqlite-3.6.20-1.el6.x86_64

Los siguientes son los script para creación de base de datos y de nuestras reglas, que hemos de usar más adelante:

~/backup/cbpolicyd/cbpolicyd.mysql.sql
-- -----------------------------------------------------
-- Schema cbpolicyd
-- -----------------------------------------------------
CREATE SCHEMA IF NOT EXISTS `cbpolicyd` DEFAULT CHARACTER SET latin1 ;
USE `cbpolicyd` ;

-- -----------------------------------------------------
-- Table `cbpolicyd`.`policies`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`policies` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`policies` (
  `ID` INT(11) NOT NULL AUTO_INCREMENT,
  `Name` VARCHAR(255) NOT NULL,
  `Priority` SMALLINT(6) NOT NULL,
  `Description` TEXT NULL DEFAULT NULL,
  `Disabled` SMALLINT(6) NOT NULL DEFAULT '0',
  PRIMARY KEY (`ID`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`access_control`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`access_control` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`access_control` (
  `ID` INT(11) NOT NULL AUTO_INCREMENT,
  `PolicyID` INT(11) NULL DEFAULT NULL,
  `Name` VARCHAR(255) NOT NULL,
  `Verdict` VARCHAR(255) NULL DEFAULT NULL,
  `Data` TEXT NULL DEFAULT NULL,
  `Comment` VARCHAR(1024) NULL DEFAULT NULL,
  `Disabled` SMALLINT(6) NOT NULL DEFAULT '0',
  PRIMARY KEY (`ID`),
  INDEX `PolicyID` (`PolicyID` ASC),
  CONSTRAINT `access_control_ibfk_1`
    FOREIGN KEY (`PolicyID`)
    REFERENCES `cbpolicyd`.`policies` (`ID`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`checkhelo`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`checkhelo` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`checkhelo` (
  `ID` INT(11) NOT NULL AUTO_INCREMENT,
  `PolicyID` INT(11) NULL DEFAULT NULL,
  `Name` VARCHAR(255) NOT NULL,
  `UseBlacklist` SMALLINT(6) NULL DEFAULT NULL,
  `BlacklistPeriod` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
  `UseHRP` SMALLINT(6) NULL DEFAULT NULL,
  `HRPPeriod` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
  `HRPLimit` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
  `RejectInvalid` SMALLINT(6) NULL DEFAULT NULL,
  `RejectIP` SMALLINT(6) NULL DEFAULT NULL,
  `RejectUnresolvable` SMALLINT(6) NULL DEFAULT NULL,
  `Comment` VARCHAR(1024) NULL DEFAULT NULL,
  `Disabled` SMALLINT(6) NOT NULL DEFAULT '0',
  PRIMARY KEY (`ID`),
  INDEX `PolicyID` (`PolicyID` ASC),
  CONSTRAINT `checkhelo_ibfk_1`
    FOREIGN KEY (`PolicyID`)
    REFERENCES `cbpolicyd`.`policies` (`ID`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`checkhelo_blacklist`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`checkhelo_blacklist` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`checkhelo_blacklist` (
  `ID` INT(11) NOT NULL AUTO_INCREMENT,
  `Helo` VARCHAR(255) NOT NULL,
  `Comment` VARCHAR(1024) NULL DEFAULT NULL,
  `Disabled` SMALLINT(6) NOT NULL DEFAULT '0',
  PRIMARY KEY (`ID`),
  UNIQUE INDEX `Helo` (`Helo` ASC))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`checkhelo_tracking`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`checkhelo_tracking` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`checkhelo_tracking` (
  `Address` VARCHAR(64) NOT NULL,
  `Helo` VARCHAR(255) NOT NULL,
  `LastUpdate` BIGINT(20) UNSIGNED NOT NULL,
  UNIQUE INDEX `Address` (`Address` ASC, `Helo` ASC),
  INDEX `checkhelo_tracking_idx1` (`LastUpdate` ASC))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`checkhelo_whitelist`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`checkhelo_whitelist` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`checkhelo_whitelist` (
  `ID` INT(11) NOT NULL AUTO_INCREMENT,
  `Source` VARCHAR(512) NOT NULL,
  `Comment` VARCHAR(1024) NULL DEFAULT NULL,
  `Disabled` SMALLINT(6) NOT NULL DEFAULT '0',
  PRIMARY KEY (`ID`),
  UNIQUE INDEX `Source` (`Source` ASC))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`checkspf`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`checkspf` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`checkspf` (
  `ID` INT(11) NOT NULL AUTO_INCREMENT,
  `PolicyID` INT(11) NULL DEFAULT NULL,
  `Name` VARCHAR(255) NOT NULL,
  `UseSPF` SMALLINT(6) NULL DEFAULT NULL,
  `RejectFailedSPF` SMALLINT(6) NULL DEFAULT NULL,
  `AddSPFHeader` SMALLINT(6) NULL DEFAULT NULL,
  `Comment` VARCHAR(1024) NULL DEFAULT NULL,
  `Disabled` SMALLINT(6) NOT NULL DEFAULT '0',
  PRIMARY KEY (`ID`),
  INDEX `PolicyID` (`PolicyID` ASC),
  CONSTRAINT `checkspf_ibfk_1`
    FOREIGN KEY (`PolicyID`)
    REFERENCES `cbpolicyd`.`policies` (`ID`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`greylisting`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`greylisting` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`greylisting` (
  `ID` INT(11) NOT NULL AUTO_INCREMENT,
  `PolicyID` INT(11) NULL DEFAULT NULL,
  `Name` VARCHAR(255) NOT NULL,
  `UseGreylisting` SMALLINT(6) NULL DEFAULT NULL,
  `GreylistPeriod` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
  `Track` VARCHAR(255) NOT NULL,
  `GreylistAuthValidity` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
  `GreylistUnAuthValidity` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
  `UseAutoWhitelist` SMALLINT(6) NULL DEFAULT NULL,
  `AutoWhitelistPeriod` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
  `AutoWhitelistCount` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
  `AutoWhitelistPercentage` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
  `UseAutoBlacklist` SMALLINT(6) NULL DEFAULT NULL,
  `AutoBlacklistPeriod` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
  `AutoBlacklistCount` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
  `AutoBlacklistPercentage` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
  `Comment` VARCHAR(1024) NULL DEFAULT NULL,
  `Disabled` SMALLINT(6) NOT NULL DEFAULT '0',
  PRIMARY KEY (`ID`),
  INDEX `PolicyID` (`PolicyID` ASC),
  CONSTRAINT `greylisting_ibfk_1`
    FOREIGN KEY (`PolicyID`)
    REFERENCES `cbpolicyd`.`policies` (`ID`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`greylisting_autoblacklist`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`greylisting_autoblacklist` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`greylisting_autoblacklist` (
  `ID` INT(11) NOT NULL AUTO_INCREMENT,
  `TrackKey` VARCHAR(512) NOT NULL,
  `Added` BIGINT(20) UNSIGNED NOT NULL,
  `Comment` VARCHAR(1024) NULL DEFAULT NULL,
  PRIMARY KEY (`ID`),
  UNIQUE INDEX `TrackKey` (`TrackKey` ASC))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`greylisting_autowhitelist`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`greylisting_autowhitelist` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`greylisting_autowhitelist` (
  `ID` INT(11) NOT NULL AUTO_INCREMENT,
  `TrackKey` VARCHAR(512) NOT NULL,
  `Added` BIGINT(20) UNSIGNED NOT NULL,
  `LastSeen` BIGINT(20) UNSIGNED NOT NULL,
  `Comment` VARCHAR(1024) NULL DEFAULT NULL,
  PRIMARY KEY (`ID`),
  UNIQUE INDEX `TrackKey` (`TrackKey` ASC))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`greylisting_tracking`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`greylisting_tracking` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`greylisting_tracking` (
  `TrackKey` VARCHAR(512) NOT NULL,
  `Sender` VARCHAR(255) NOT NULL,
  `Recipient` VARCHAR(255) NOT NULL,
  `FirstSeen` BIGINT(20) UNSIGNED NOT NULL,
  `LastUpdate` BIGINT(20) UNSIGNED NOT NULL,
  `Tries` BIGINT(20) UNSIGNED NOT NULL,
  `Count` BIGINT(20) UNSIGNED NOT NULL,
  UNIQUE INDEX `TrackKey` (`TrackKey` ASC, `Sender` ASC, `Recipient` ASC),
  INDEX `greylisting_tracking_idx1` (`LastUpdate` ASC, `Count` ASC))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`greylisting_whitelist`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`greylisting_whitelist` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`greylisting_whitelist` (
  `ID` INT(11) NOT NULL AUTO_INCREMENT,
  `Source` VARCHAR(255) NOT NULL,
  `Comment` VARCHAR(1024) NULL DEFAULT NULL,
  `Disabled` SMALLINT(6) NOT NULL DEFAULT '0',
  PRIMARY KEY (`ID`),
  UNIQUE INDEX `Source` (`Source` ASC))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`policy_groups`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`policy_groups` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`policy_groups` (
  `ID` INT(11) NOT NULL AUTO_INCREMENT,
  `Name` VARCHAR(255) NOT NULL,
  `Disabled` SMALLINT(6) NOT NULL DEFAULT '0',
  `Comment` VARCHAR(1024) NULL DEFAULT NULL,
  PRIMARY KEY (`ID`),
  UNIQUE INDEX `Name` (`Name` ASC))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`policy_group_members`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`policy_group_members` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`policy_group_members` (
  `ID` INT(11) NOT NULL AUTO_INCREMENT,
  `PolicyGroupID` INT(11) NULL DEFAULT NULL,
  `Member` VARCHAR(255) NOT NULL,
  `Disabled` SMALLINT(6) NOT NULL DEFAULT '0',
  `Comment` VARCHAR(1024) NULL DEFAULT NULL,
  PRIMARY KEY (`ID`),
  INDEX `PolicyGroupID` (`PolicyGroupID` ASC),
  CONSTRAINT `policy_group_members_ibfk_1`
    FOREIGN KEY (`PolicyGroupID`)
    REFERENCES `cbpolicyd`.`policy_groups` (`ID`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`policy_members`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`policy_members` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`policy_members` (
  `ID` INT(11) NOT NULL AUTO_INCREMENT,
  `PolicyID` INT(11) NULL DEFAULT NULL,
  `Source` TEXT NULL DEFAULT NULL,
  `Destination` TEXT NULL DEFAULT NULL,
  `Comment` VARCHAR(1024) NULL DEFAULT NULL,
  `Disabled` SMALLINT(6) NOT NULL DEFAULT '0',
  PRIMARY KEY (`ID`),
  INDEX `PolicyID` (`PolicyID` ASC),
  CONSTRAINT `policy_members_ibfk_1`
    FOREIGN KEY (`PolicyID`)
    REFERENCES `cbpolicyd`.`policies` (`ID`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`quotas`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`quotas` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`quotas` (
  `ID` INT(11) NOT NULL AUTO_INCREMENT,
  `PolicyID` INT(11) NULL DEFAULT NULL,
  `Name` VARCHAR(255) NOT NULL,
  `Track` VARCHAR(255) NOT NULL,
  `Period` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
  `Verdict` VARCHAR(255) NULL DEFAULT NULL,
  `Data` TEXT NULL DEFAULT NULL,
  `LastQuota` SMALLINT(6) NOT NULL DEFAULT '0',
  `Comment` VARCHAR(1024) NULL DEFAULT NULL,
  `Disabled` SMALLINT(6) NOT NULL DEFAULT '0',
  PRIMARY KEY (`ID`),
  INDEX `PolicyID` (`PolicyID` ASC),
  CONSTRAINT `quotas_ibfk_1`
    FOREIGN KEY (`PolicyID`)
    REFERENCES `cbpolicyd`.`policies` (`ID`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`quotas_limits`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`quotas_limits` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`quotas_limits` (
  `ID` INT(11) NOT NULL AUTO_INCREMENT,
  `QuotasID` INT(11) NULL DEFAULT NULL,
  `Type` VARCHAR(255) NULL DEFAULT NULL,
  `CounterLimit` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
  `Comment` VARCHAR(1024) NULL DEFAULT NULL,
  `Disabled` SMALLINT(6) NOT NULL DEFAULT '0',
  PRIMARY KEY (`ID`),
  INDEX `QuotasID` (`QuotasID` ASC),
  CONSTRAINT `quotas_limits_ibfk_1`
    FOREIGN KEY (`QuotasID`)
    REFERENCES `cbpolicyd`.`quotas` (`ID`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`quotas_tracking`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`quotas_tracking` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`quotas_tracking` (
  `QuotasLimitsID` INT(11) NULL DEFAULT NULL,
  `TrackKey` VARCHAR(512) NULL DEFAULT NULL,
  `LastUpdate` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
  `Counter` DECIMAL(10,4) NULL DEFAULT NULL,
  UNIQUE INDEX `QuotasLimitsID` (`QuotasLimitsID` ASC, `TrackKey` ASC),
  INDEX `quotas_tracking_idx1` (`LastUpdate` ASC),
  CONSTRAINT `quotas_tracking_ibfk_1`
    FOREIGN KEY (`QuotasLimitsID`)
    REFERENCES `cbpolicyd`.`quotas_limits` (`ID`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `cbpolicyd`.`session_tracking`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `cbpolicyd`.`session_tracking` ;

CREATE TABLE IF NOT EXISTS `cbpolicyd`.`session_tracking` (
  `Instance` VARCHAR(255) NULL DEFAULT NULL,
  `QueueID` VARCHAR(255) NULL DEFAULT NULL,
  `UnixTimestamp` BIGINT(20) NOT NULL,
  `ClientAddress` VARCHAR(64) NULL DEFAULT NULL,
  `ClientName` VARCHAR(255) NULL DEFAULT NULL,
  `ClientReverseName` VARCHAR(255) NULL DEFAULT NULL,
  `Protocol` VARCHAR(255) NULL DEFAULT NULL,
  `EncryptionProtocol` VARCHAR(255) NULL DEFAULT NULL,
  `EncryptionCipher` VARCHAR(255) NULL DEFAULT NULL,
  `EncryptionKeySize` VARCHAR(255) NULL DEFAULT NULL,
  `SASLMethod` VARCHAR(255) NULL DEFAULT NULL,
  `SASLSender` VARCHAR(255) NULL DEFAULT NULL,
  `SASLUsername` VARCHAR(255) NULL DEFAULT NULL,
  `Helo` VARCHAR(255) NULL DEFAULT NULL,
  `Sender` VARCHAR(255) NULL DEFAULT NULL,
  `Size` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
  `RecipientData` TEXT NULL DEFAULT NULL,
  UNIQUE INDEX `Instance` (`Instance` ASC),
  INDEX `session_tracking_idx1` (`QueueID` ASC, `ClientAddress` ASC, `Sender` ASC),
  INDEX `session_tracking_idx2` (`UnixTimestamp` ASC))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;

El siguiente ejemplo esta recortado a próposito, pero supongo que podría ser bastante ilustrativo sobre la forma de crear las reglas. En todo caso, encontrar una la forma correcta de hacer estas reglas parece ser más arte que ciencia, en cuanto la documentación disponible me ha parecido extrañamente escasa

~/backup/cbpolicyd/dominio.mysql.sql

use cbpolicyd;

/* Creamos los grupos que hemos de usar posteriormente en la tabla policy_members */
INSERT INTO policy_groups (Name) values('MAILSERVER');
/* Asignamos algunos miembros a los grupos */
INSERT INTO policy_group_members (PolicyGroupID,Member, Comment) VALUES (1,'10.10.20.2/24', 'MailServer');
INSERT INTO policy_group_members (PolicyGroupID,Member, Comment) VALUES (1,'190.86.170.10/24', 'IP Pública');

INSERT INTO policy_groups (Name) values('MIRED');
INSERT INTO policy_group_members (PolicyGroupID,Member, Comment) VALUES (2,'192.168.20.0/24', 'Redes LAN');
INSERT INTO policy_group_members (PolicyGroupID,Member, Comment) VALUES (2,'10.10.20.0/24', 'DMZ');

INSERT INTO policy_groups (Name) values("DOMINIOS");
INSERT INTO policy_group_members (PolicyGroupID,Member, Comment) VALUES (3,'@dominio.com', 'Dominio Principal');

/* Creamos las politicas en tabla policies. Cada politica debe tener al menos un miembro definido en policy_members */
INSERT INTO policies (Name,Priority,Description) VALUES ('PoliticaMailServer',0,'Primer politica con Mail Server');
/* Asignamos miembros haciendo politicas que definan origen y destino  */
INSERT INTO policy_members (PolicyID,Source,Destination) VALUES(1, '%MAILSERVER', 'any');

INSERT INTO policies (Name,Priority,Description) VALUES ('PoliticaLANInterna',1,'Primer politica con LAN');
INSERT INTO policy_members (PolicyID,Source,Destination) VALUES(2, '%MIRED,!%MAILSERVER', '%DOMINIOS');

INSERT INTO policies (Name,Priority,Description) VALUES ('PoliticaLANExterna',2,'Primer politica con LAN');
INSERT INTO policy_members (PolicyID,Source,Destination) VALUES(3, '%MIRED,!%MAILSERVER', '!%DOMINIOS');

INSERT INTO policies (Name,Priority,Description) VALUES ('Salida',3,'Politica para usuarios externos');
INSERT INTO policy_members (PolicyID,Source,Destination) VALUES(4, '!%DOMINIOS', '%DOMINIOS');

/* Asignamos una quota a cada politica, que define entre otras cosas la unidad de tiempo en la cual se define */
/* Este tiene onda, es casi exactamente uno menos de lo que se especifica en este */
INSERT INTO quotas (PolicyID,Name,Track,Period,Verdict,Data) VALUES (1, 'Min_PoliticaMailServer','Sender:user@domain', 60, 'DEFER', 'Se difieren los mensajes fuera de cuota');
/* Y ahora asignamos a la cuota, propiamente un volumen permitido */
INSERT INTO quotas_limits (QuotasID,Type,CounterLimit) VALUES(1, 'MessageCount', 125);
/* Este es otro par quota - quota_limits que pertenece a la misma política */
INSERT INTO quotas (PolicyID,Name,Track,Period,Verdict,Data) VALUES (1, 'Hor_PoliticaMailServer','Sender:user@domain', 3600, 'REJECT', 'Se rechazan los mensajes fuera de cuota');
INSERT INTO quotas_limits (QuotasID,Type,CounterLimit) VALUES(2, 'MessageCount', 480);

/* Desde Red LAN hacia cuentas de nuestros dominios */
INSERT INTO quotas (PolicyID,Name,Track,Period,Verdict,Data) VALUES (2, 'Min_PoliticaLANInterna','Sender:user@domain', 60, 'DEFER', 'Se difieren los mensajes fuera de cuota');
INSERT INTO quotas_limits (QuotasID,Type,CounterLimit) VALUES(3, 'MessageCount', 60);
INSERT INTO quotas (PolicyID,Name,Track,Period,Verdict,Data) VALUES (2, 'Hor_PoliticaLANInterna','Sender:user@domain', 3600, 'REJECT', 'Se rechazan los mensajes fuera de cuota');
INSERT INTO quotas_limits (QuotasID,Type,CounterLimit) VALUES(4, 'MessageCount', 250);

/* Desde Red LAN hacia fuera del dominio */
INSERT INTO quotas (PolicyID,Name,Track,Period,Verdict,Data) VALUES (3, 'Min_PoliticaLANExterna','Sender:user@domain', 60, 'DEFER', 'Se difieren los mensajes fuera de cuota');
INSERT INTO quotas_limits (QuotasID,Type,CounterLimit) VALUES(5, 'MessageCount', 74);
INSERT INTO quotas (PolicyID,Name,Track,Period,Verdict,Data) VALUES (3, 'Hor_PoliticaLANExterna','Sender:user@domain', 3600, 'REJECT', 'Se rechazan los mensajes fuera de cuota');
INSERT INTO quotas_limits (QuotasID,Type,CounterLimit) VALUES(6, 'MessageCount', 400);

/* Gente externa que quiere enviar a nuestro dominio */
INSERT INTO quotas (PolicyID,Name,Track,Period,Verdict,Data) VALUES (4, 'Min_Salida','Sender:user@domain', 60, 'DEFER', 'Se difieren los mensajes fuera de cuota');
INSERT INTO quotas_limits (QuotasID,Type,CounterLimit) VALUES(7, 'MessageCount', 95);
INSERT INTO quotas (PolicyID,Name,Track,Period,Verdict,Data) VALUES (4, 'Hor_Salida','Sender:user@domain', 3600, 'REJECT', 'Se rechazan los mensajes fuera de cuota');
INSERT INTO quotas_limits (QuotasID,Type,CounterLimit) VALUES(8, 'MessageCount', 460);

Creamos la base de datos en el servidor mysql de zimbra: Creamos un directorio en ~/backup (O en cualquier directorio que como usuario zimbra tengamos permiso suficiente) para trabajar con nuestros ficheros.
zimbra@mail:~ $ mkdir ~/backup/cbpolicyd
zimbra@mail:~ $ cd ~/backup/cbpolicyd

Creamos los ficheros que con los script para creación de base de datos y para insertar nuestras reglas. Por el tamaño de ambos, creo que separarlos es una idea recomendable en vista a que sean tan sencillos como sean posibles.
zimbra@mail:~/backup/cbpolicyd $ vim -p cbpolicyd.mysql.sql minsal.mysql.sql
[... En donde colocamos el contenido de los ficheros antes expuestos ...]

Ejecutamos el cliente de mysql como usuario zimbra en el servidor, que es por mucho la forma más sencilla de acceder a él.
zimbra@mail:~/backup/cbpolicyd $ mysql
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 126452
Server version: 5.5.32-log Source distribution

Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

Crearemos la base de datos cbpolicyd, y le asignaremos permisos sobre ella a un usuario cbpolicyd con contraseña “Password”, valores todos que pueden cambiarse como se desee pero que en este momento son bastante descriptivos.
mysql> create database cbpolicyd;
Query OK, 1 row affected (0.00 sec)
mysql> grant all privileges on cbpolicyd.* to 'cbpolicyd'@'%' identified by 'Password';
Query OK, 0 rows affected (0.00 sec)

Ejecutamos los script para creación de base de datos y para nuestras reglas personalizadas
mysql> source cbpolicyd.mysql.sql;
Query OK, 1 row affected, 1 warning (0.00 sec)
Database changed
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 0 rows affected (0.24 sec)
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 0 rows affected (0.31 sec)
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 0 rows affected (1.70 sec)
[...]

mysql> source dominio.mysql.sql
Database changed
Query OK, 1 row affected (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Query OK, 1 row affected (0.00 sec)
[...]

Por último, en un intento de normalización, movemos los script que hayamos creado a la locación por defecto para datos de cbpolicyd en ~/data/cbpolicyd/db/, donde hallaremos entre otras cosas el fichero sqlite que usaría por defecto:
mysql> quit
zimbra@mail:~ $ mv *sql ~/data/cbpolicyd/db/

Ahora podemos habilitar cbpolicyd en nuestro servidor zimbra:
zmprov ms `zmhostname` +zimbraServiceEnabled cbpolicyd

Uno o dos minutos después, zmconfigd se encargará de poner todo en marcha por primera vez. Configuramos el fichero /opt/zimbra/conf/cbpolicyd.conf.in, desde donde se crea la configuración de cbpolicyd en /opt/zimbra/conf/cbpolicyd.conf con cada reinicio de zmconfigd, de manera que el DSN por defecto quede comentarizado y hallamos agregado uno que apunte hacia nuestra base de datos en el servidor mysql:
[...]
#DSN=DBI:SQLite:dbname=@@cbpolicyd_db_file@@
DSN=DBI:mysql:database=cbpolicyd;host=127.0.0.1;port=7306
Username=cbpolicyd
Password=Password
[...]

Reiniciamos el servicio, y en cuanto esté arriba todas las reglas empezarán a surgir efecto:
zmcbpolicydctl restart

lunes, 25 de mayo de 2015

Comprobar la delegación de DNS inversa

El nombre más exacto sería Delegación IN-ADDR.ARPA sin clase, pero el que tiene por ahora tiene como más mejor posicionamente en buscadores, y es la forma en que la mayoria del mundo lo conoce.

La idea es que la resolución inversa DNS suele estar fuertemente asociada a los ISP que administran las IP que se han de resolver. Sin embargo, en ocasiones puede ser bastante engorroso tener que consultar con ellos los cambios en nuestras entradas DNS, sobre todo cuando son demasiadas.

Después de las gestiones administrativas necesarias, es posible que nos delegue la resolución inversa para una red sin clase, básicamente, las direcciones IP públicas que nos han sido proporcionadas.

Las comprobaciones pueden ser un poco tediosas, pero son sumamente necesarias para garantizar que todo esté funcionando como es debido. Si bien no hay una especie de fórmula mágica para este caso, es posible establecer una especie de pasos a seguir para comprobar que todo esté funcionando perfectamente

  • Puede obtener los servidores DNS de su proveedor, usualmente preguntandole, sino, puede intentar a buscar los servidores dados para su dominio, en ese caso, proveedor.com
    Una comprobación exhaustiva requiere que se revise cada uno de los servidores DNS que aparecen como respuesta. Un DNS de su proveedor que no tenga los registros actualizados hará que los registros que se propagan por internet tengan errores.
dig NS proveedor.com +noall +additional
; <<>> DiG 9.9.6-P1-RedHat-9.9.6-8.P1.fc21 <<>> NS proveedor.com +noall +additional
;; global options: +cmd
ns.proveedor.net.         35430    IN    A    200.1.2.3
ns.proveedor9.net.        35027    IN    A    200.1.2.4  

  • Luego, hay que comprobar que los servidores DNS del proveedor no pueda resolver la entrada PTR para nuestras IP, como normalmente haría, sino que frente a las consultas DNS devuelva un CNAME que ha de enviar a la autoridad para esas IP, que ya debe estar señalada como nuestros servidores DNS. Usaremos una ip 200.1.2.130, que debe estar dentro del rango de IP públicas de las que el proveedor nos transfiere la autoridad.
dig @200.1.2.3 -x 200.1.2.130 +norecurse +noall +answer +authority +comment
; <<>> DiG 9.9.6-P1-RedHat-9.9.6-8.P1.fc21 <<>> @200.1.2.3 -x 200.1.2.130 +norecurse +noall +answer +authority +comment
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16232
;; flags: qr aa ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 3

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; ANSWER SECTION:
130.2.1.200.in-addr.arpa. 86400 IN    CNAME    130.128-254.2.1.200.in-addr.arpa.

;; AUTHORITY SECTION:
128-254.2.1.200.in-addr.arpa. 86400 IN NS    ns1.institucion.com.
128-254.2.1.200.in-addr.arpa. 86400 IN NS    ns2.institucion.com.


Podemos obtener la forma actual del registro PTR viendo el CNAME que el servidor de nuestro proveedor devuelve en la sección ANSWER en la respuesta de la consulta anterior, pero bien puede verlo más claro con el siguiente comando
dig @200.1.2.3 -x 200.1.2.130 +norecurse +short
130.128-254.2.1.200.in-addr.arpa.
Dado que nuestros servidores DNS resuelven la reversa en base a la forma en que el servidor de su proveedor se las tranfiere, (Vea el CNAME que el servidor DNS de su proveedor ha devuelto), tenga en cuenta que su servidor ya no puede resolver ninguna de las siguientes consultas
dig @ns1.institucion.com PTR 130.2.1.200.in-addr.arpa

dig @ns1.institucion.com -x 200.1.2.130
que son con las que normalmente haría una comprobación de este tipo, pero tome en cuenta que cualquier otro servidor debe ser capaz de obtener respuestas de las mismas
dig @8.8.8.8 -x 200.1.2.130 +short
130.128-254.2.1.200.in-addr.arpa.
mail.salud.gob.sv.

dig @8.8.8.8 PTR 130.2.1.200.in-addr.arpa +short
130.128-254.2.1.200.in-addr.arpa.
mail.salud.gob.sv.
Para resolver correctamente en sus servidores DNS, debe hacer la consulta PTR igual al CNAME devuelto por los DNS de su proveedor. Lo que significa que usted ha configurado según parámetros del proveedor.
dig @ns1.institucion.com 130.128-254.2.1.200.in-addr.arpa
correo.institucion.com.

  • Una de las pruebas más exhaustivas para empezar a revisar el problema desde un principio es hacer un dig +trace, ya que el resultado especifica el final de cada bloque, en la línea que empieza por Received x bytes… el servidor que responde por dicho bloque, así que puede rastrear si algún servidor están enviando resultados erróneos.
dig @8.8.8.8 -x 200.31.169.130 +trace +nodnssec

; <<>> DiG 9.9.6-P1-RedHat-9.9.6-8.P1.fc21 <<>> @8.8.8.8 -x 200.31.169.130 +trace +nodnssec
; (1 server found)
;; global options: +cmd
.            19272    IN    NS    a.root-servers.net.
.            19272    IN    NS    l.root-servers.net.
.            19272    IN    NS    m.root-servers.net.
.            19272    IN    NS    j.root-servers.net.
.            19272    IN    NS    f.root-servers.net.
.            19272    IN    NS    i.root-servers.net.
.            19272    IN    NS    e.root-servers.net.
.            19272    IN    NS    g.root-servers.net.
.            19272    IN    NS    h.root-servers.net.
.            19272    IN    NS    d.root-servers.net.
.            19272    IN    NS    c.root-servers.net.
.            19272    IN    NS    k.root-servers.net.
.            19272    IN    NS    b.root-servers.net.
;; Received 239 bytes from 8.8.8.8#53(8.8.8.8) in 31 ms

in-addr.arpa.        172800    IN    NS    e.in-addr-servers.arpa.
in-addr.arpa.        172800    IN    NS    a.in-addr-servers.arpa.
in-addr.arpa.        172800    IN    NS    c.in-addr-servers.arpa.
in-addr.arpa.        172800    IN    NS    d.in-addr-servers.arpa.
in-addr.arpa.        172800    IN    NS    f.in-addr-servers.arpa.
in-addr.arpa.        172800    IN    NS    b.in-addr-servers.arpa.
;; Received 432 bytes from 192.33.4.12#53(c.root-servers.net) in 63 ms

200.in-addr.arpa.    86400    IN    NS    a.arpa.dns.br.
200.in-addr.arpa.    86400    IN    NS    ns.lacnic.net.
200.in-addr.arpa.    86400    IN    NS    ns2.lacnic.net.
200.in-addr.arpa.    86400    IN    NS    ns3.afrinic.net.
200.in-addr.arpa.    86400    IN    NS    sec1.authdns.ripe.net.
200.in-addr.arpa.    86400    IN    NS    sec3.apnic.net.
200.in-addr.arpa.    86400    IN    NS    tinnie.arin.net.
200.in-addr.arpa.    86400    IN    NS    ns-lacnic.nic.mx.
;; Received 267 bytes from 199.253.183.183#53(b.in-addr-servers.arpa) in 174 ms

2.1.200.in-addr.arpa. 86400    IN    NS    NS.PROVEEDOR.NET.
;; Received 83 bytes from 204.61.215.62#53(ns3.afrinic.net) in 120 ms

130.2.1.200.in-addr.arpa. 86400 IN    CNAME    130.128-254.2.1.200.in-addr.arpa.
128-254.2.1.200.in-addr.arpa. 86400 IN NS    ns1.institucion.com.
128-254.2.1.200.in-addr.arpa. 86400 IN NS    ns2.institucion.com.
;; Received 162 bytes from 200.1.2.3#53(NS.PROVEEDOR.NET) in 2 ms

(1) Fuente
(2) Fuente

viernes, 10 de abril de 2015

Notas sobre la forma de hacer nateo respecto a rutas

Este post será dentro de poco algo obsoleto, en cuanto el nateo será una técnica inútil cuando IPv6 termine su despliegue, sin embargo, aprovecho a subir estas notas porque en ningún lugar hacen bien excepto aquí
De este esquema hablo
¿Podría este esquema acceder a un FTP en Sucursal?
Totalmente, siempre y cuando entre el Firewall y y Sucursal no se este nateando el tráfico desde LAN, ya que estamos nateando el tráfico de LAN en Firewall, lo que haría un segundo nateo (Nadie en sus cabales lo haría, pero yo no estaba en mis cabales el día que jugué con una configuración de ese tipo )

Rutas
En una configuración de primer o segundo caso completas, es necesario configurar rutas para especificar cual es el gateway que deben usar para llegar a tal ruta, y en consecuencia, que interfaz física han de buscar para salir. (Si bien se haya configurado la ip en una interfaz virtual, lo importante es la física sobre la que esa interfaz virtual trabaja)

Las interfaces virtuales son un chiste dentro de la configuración de iptables
Es bastante común que se configure la interfaz hacia DMZ (Central + Sucursal) como una interfaz virtual respecto a la interfaz hacia internet.

En ese caso, iptables toma en cuenta la interfaz fisica debido a que es lo que la configuración de rutas toma en cuenta.

Para ejemplo: Habiendo configurado como rutas adicionales
route add -net  192.168.10.0 gw 172.16.2.1
route add -net  192.168.83.0 gw 172.16.2.1
route add -net  192.168.85.0 gw 172.16.2.1
route add -net  192.168.87.0 gw 172.16.2.1
Y estando configurada 172.16.2.1 sobre una interfaz virtual eth0:1, las rutas para el sistema deberían aparecer de la siguiente forma
root@firewall:~# route -n

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.10.25   0.0.0.0         UG    0      0        0 eth0
10.10.20.0      172.16.2.1      255.255.255.0   UG    0      0        0 eth0
10.20.20.0      0.0.0.0         255.255.255.0   U     0      0        0 eth1
10.30.20.0      0.0.0.0         255.255.255.0   U     0      0        0 eth2
172.16.2.0      0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.10.0    0.0.0.0         255.255.255.224 U     0      0        0 eth0
192.168.83.0    172.16.2.1      255.255.255.0   UG    0      0        0 eth0
192.168.85.0    172.16.2.1      255.255.255.0   UG    0      0        0 eth0
192.168.87.0    172.16.2.1      255.255.255.0   UG    0      0        0 eth0
Hice un experimento para demostrarme lo anterior, y no es del todo complicado una vez que se entiende lo hasta ahora expuesto, pero como yo no lo entendía casi me mata
 Imagínemos que hemos configurado firewall, y que tenemos acceso a nebula, que es un equipo que se encuentra entre Firewall y DMZ (Central + Sucursal). En la vida real, este es un proveedor de enlaces, pero el experimento va de configurar un equipo cualquiera como nebula con apenas forwarding de paquetes y un par de reglas en la cadena POSTROUTING en la tabla nat de iptables

Cuando Firewall muestra esto:
root@firewall:~# iptables -t nat -nvL POSTROUTING
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination        
    3   189 ACCEPT     all  --  *      eth0    10.20.20.0/24        10.10.20.0/24        /* Vamos hacia DMZ */
    3   180 MASQUERADE  all  --  *      eth0    10.20.20.0/24        0.0.0.0/0            match-set rwa dst /* Vamos hacia RWA */
    0     0 MASQUERADE  all  --  *      eth0    10.20.20.0/24        0.0.0.0/0            /* Vamos hacia Internet */
Nebula nebula dice estar nateando de esta manera:
root@nebula:~# iptables -t nat -nvL POSTROUTING
Chain POSTROUTING (policy ACCEPT 5 packets, 1058 bytes)
 pkts bytes target     prot opt in     out     source               destination        
    3   189 MASQUERADE  all  --  *      br0     10.20.20.0/24        0.0.0.0/0          
    3   180 MASQUERADE  all  --  *      br0     172.16.2.0/24        0.0.0.0/0  
Cuando el destino desde la red $LAN era 10.10.20.0, #firewall no lo natea en realidad, por tanto llega a #nebula con su ip original como se puede ver, y hace coincidencia en la regla esperada para tal fin, en el ejemplo la primera
Luego, cuando el destino eran los grupos en rwa, (que de hecho incluyen a 10.10.20.0/24, pero al anteponer la regla anterior en el orden de iptables nunca llegará a este punto) #firewall nateo con salida en la interfaz eth0, pero hay que fijarse en eth0
root@firewall:~# ip addr show eth0
2: eth0:  mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 52:54:00:de:78:a8 brd ff:ff:ff:ff:ff:ff
    inet 172.16.2.15/24 brd 172.16.2.255 scope global eth0:1
    inet 192.168.10.15/27 brd 192.168.10.31 scope global eth0
    inet6 fe80::5054:ff:fede:78a8/64 scope link
       valid_lft forever preferred_lft forever 
Si bien se especifica la interfaz eth0 en su totalidad, iptables en #firewall natea la salida con la ip 172.16.2.15/24, (Puede observarse que pertenece a eth0:1 porque fue creada con ifconfig). La prueba de dicho nateo esta en que en #nebula se ha hecho coincidencia en la regla para nateo de 172.16.2.0/24, no de 192.168.10.0/27 (Regla que de hecho no existe en #nebula), porque en la tabla de ruteo mostrada arriba se especifica que hacia las redes en el grupo ipset #rwa es a traves de  172.16.2.1

Es obvio que llegando a este punto mi explicación podría ser insuficiente para entender, confió en que las tablas que he colocado sean de hecho más explicativas por si mismas

Para arrancar un nebula como el que use para revisar todo la teoría

Nebula debe conocer esas redes que natea, para que cuando le devuelvan las peticiones el sepa a donde putas debe enviarlas:
root@nebula:~# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.10.1    0.0.0.0         UG    0      0        0 br0
10.20.20.0      172.16.2.15     255.255.255.0   UG    0      0        0 br1
10.30.20.0      0.0.0.0         255.255.255.0   U     0      0        0 virbr2
172.16.2.0      0.0.0.0         255.255.255.224 U     0      0        0 br1
172.16.3.0      0.0.0.0         255.255.255.0   U     0      0        0 br1
192.168.10.0    0.0.0.0         255.255.255.224 U     0      0        0 br0
Nateamos
iptables -t nat -D POSTROUTING -o br0 -s 192.168.10.0/24 -j MASQUERADE
iptables -t nat -A POSTROUTING -o br0 -s 172.16.2.0/24 -j MASQUERADE
iptables -t nat -A POSTROUTING -o br0 -s 172.16.3.0/24 -j MASQUERADE

jueves, 22 de enero de 2015

"Instalar" Java: update-alternatives en Fedora 21

Si bien tengo ya una entrada sobre como configurar Java (Tomando como base una instalación de OpenSuSE), creí conveniente actualizarla un poco.

Lo recomendable es usar el RPM, tal como lo indica esta guía. Como sea, parece ser que el RPM no configura la alternativa, y lo que es más,  la instalación de OpenOffice ya había instalado la openJDK como dependencia, así que será necesario usar update-alternatives para no hacer un lío (Aunque tuve que hacer un lío para evitarlo).

Básicamente, los comandos a usar son estos:
update-alternatives --install /bin/java java /usr/java/default/bin/java 200 --slave /usr/share/man/man1/java.1.gz java.1.gz /usr/java/default/man/man1/java.1.gz \
--slave /bin/javaws javaws /usr/java/default/bin/javaws --slave /usr/share/man/man1/javaws.1.gz javaws.1.gz /usr/java/default/man/man1/javaws.1.gz \
--slave /bin/jcontrol jcontrol /usr/java/default/bin/jcontrol --slave /usr/share/man/man1/jcontrol.1.gz jcontrol.1.gz /usr/java/default/man/man1/jcontrol.1.gz \
--slave /bin/jjs jjs /usr/java/default/bin/jjs --slave /usr/share/man/man1/jjs.1.gz jjs.1.gz /usr/java/default/man/man1/jjs.1.gz \
--slave /bin/keytool keytool /usr/java/default/bin/keytool --slave /usr/share/man/man1/keytool.1.gz keytool.1.gz /usr/java/default/man/man1/keytool.1.gz \
--slave /bin/orbd orbd /usr/java/default/bin/orbd --slave /usr/share/man/man1/orbd.1.gz orbd.1.gz /usr/java/default/man/man1/orbd.1.gz \
--slave /bin/pack200 pack200 /usr/java/default/bin/pack200 --slave /usr/share/man/man1/pack200.1.gz pack200.1.gz /usr/java/default/man/man1/pack200.1.gz \
--slave /bin/policytool policytool /usr/java/default/bin/policytool --slave /usr/share/man/man1/policytool.1.gz policytool.1.gz /usr/java/default/man/man1/policytool.1.gz \
--slave /bin/rmid rmid /usr/java/default/bin/rmid --slave /usr/share/man/man1/rmid.1.gz rmid.1.gz /usr/java/default/man/man1/rmid.1.gz \
--slave /bin/rmiregistry rmiregistry /usr/java/default/bin/rmiregistry --slave /usr/share/man/man1/rmiregistry.1.gz rmiregistry.1.gz /usr/java/default/man/man1/rmiregistry.1.gz \
--slave /bin/servertool servertool /usr/java/default/bin/servertool --slave /usr/share/man/man1/servertool.1.gz servertool.1.gz /usr/java/default/man/man1/servertool.1.gz \
--slave /bin/tnameserv tnameserv /usr/java/default/bin/tnameserv --slave /usr/share/man/man1/tnameserv.1.gz tnameserv.1.gz /usr/java/default/man/man1/tnameserv.1.gz \
--slave /bin/unpack200 unpack200 /usr/java/default/bin/unpack200 --slave /usr/share/man/man1/unpack200.1.gz unpack200.1.gz /usr/java/default/man/man1/unpack200.1.gz

update-alternatives --install /usr/bin/javac javac /usr/java/default/bin/javac 200 --slave /usr/share/man/man1/javac.1.gz javac.1.gz /usr/java/default/man/man1/javac.1.gz \
--slave /usr/bin/appletviewer appletviewer /usr/java/default/bin/appletviewer --slave /usr/share/man/man1/appletviewer.1.gz appletviewer.1.gz /usr/java/default/man/man1/appletviewer.1.gz \
--slave /usr/bin/extcheck extcheck /usr/java/default/bin/extcheck --slave /usr/share/man/man1/extcheck.1.gz extcheck.1.gz /usr/java/default/man/man1/extcheck.1.gz \
--slave /usr/bin/idlj idlj /usr/java/default/bin/idlj --slave /usr/share/man/man1/idlj.1.gz idlj.1.gz /usr/java/default/man/man1/idlj.1.gz \
--slave /usr/bin/jar jar /usr/java/default/bin/jar --slave /usr/share/man/man1/jar.1.gz jar.1.gz /usr/java/default/man/man1/jar.1.gz \
--slave /usr/bin/jarsigner jarsigner /usr/java/default/bin/jarsigner --slave /usr/share/man/man1/jarsigner.1.gz jarsigner.1.gz /usr/java/default/man/man1/jarsigner.1.gz \
--slave /usr/bin/java-rmi.cgi java-rmi.cgi /usr/java/default/bin/java-rmi.cgi --slave /usr/share/man/man1/java-rmi.cgi.1.gz java-rmi.cgi.1.gz /usr/java/default/man/man1/java-rmi.cgi.1.gz \
--slave /usr/bin/javadoc javadoc /usr/java/default/bin/javadoc --slave /usr/share/man/man1/javadoc.1.gz javadoc.1.gz /usr/java/default/man/man1/javadoc.1.gz \
--slave /usr/bin/javafxpackager javafxpackager /usr/java/default/bin/javafxpackager --slave /usr/share/man/man1/javafxpackager.1.gz javafxpackager.1.gz /usr/java/default/man/man1/javafxpackager.1.gz \
--slave /usr/bin/javah javah /usr/java/default/bin/javah --slave /usr/share/man/man1/javah.1.gz javah.1.gz /usr/java/default/man/man1/javah.1.gz \
--slave /usr/bin/javap javap /usr/java/default/bin/javap --slave /usr/share/man/man1/javap.1.gz javap.1.gz /usr/java/default/man/man1/javap.1.gz \
--slave /usr/bin/javapackager javapackager /usr/java/default/bin/javapackager --slave /usr/share/man/man1/javapackager.1.gz javapackager.1.gz /usr/java/default/man/man1/javapackager.1.gz \
--slave /usr/bin/jcmd jcmd /usr/java/default/bin/jcmd --slave /usr/share/man/man1/jcmd.1.gz jcmd.1.gz /usr/java/default/man/man1/jcmd.1.gz \
--slave /usr/bin/jconsole jconsole /usr/java/default/bin/jconsole --slave /usr/share/man/man1/jconsole.1.gz jconsole.1.gz /usr/java/default/man/man1/jconsole.1.gz \
--slave /usr/bin/jdb jdb /usr/java/default/bin/jdb --slave /usr/share/man/man1/jdb.1.gz jdb.1.gz /usr/java/default/man/man1/jdb.1.gz \
--slave /usr/bin/jdeps jdeps /usr/java/default/bin/jdeps --slave /usr/share/man/man1/jdeps.1.gz jdeps.1.gz /usr/java/default/man/man1/jdeps.1.gz \
--slave /usr/bin/jhat jhat /usr/java/default/bin/jhat --slave /usr/share/man/man1/jhat.1.gz jhat.1.gz /usr/java/default/man/man1/jhat.1.gz \
--slave /usr/bin/jinfo jinfo /usr/java/default/bin/jinfo --slave /usr/share/man/man1/jinfo.1.gz jinfo.1.gz /usr/java/default/man/man1/jinfo.1.gz \
--slave /usr/bin/jmap jmap /usr/java/default/bin/jmap --slave /usr/share/man/man1/jmap.1.gz jmap.1.gz /usr/java/default/man/man1/jmap.1.gz \
--slave /usr/bin/jmc jmc /usr/java/default/bin/jmc --slave /usr/share/man/man1/jmc.1.gz jmc.1.gz /usr/java/default/man/man1/jmc.1.gz \
--slave /usr/bin/jmc.ini jmc.ini /usr/java/default/bin/jmc.ini --slave /usr/share/man/man1/jmc.ini.1.gz jmc.ini.1.gz /usr/java/default/man/man1/jmc.ini.1.gz \
--slave /usr/bin/jps jps /usr/java/default/bin/jps --slave /usr/share/man/man1/jps.1.gz jps.1.gz /usr/java/default/man/man1/jps.1.gz \
--slave /usr/bin/jrunscript jrunscript /usr/java/default/bin/jrunscript --slave /usr/share/man/man1/jrunscript.1.gz jrunscript.1.gz /usr/java/default/man/man1/jrunscript.1.gz \
--slave /usr/bin/jsadebugd jsadebugd /usr/java/default/bin/jsadebugd --slave /usr/share/man/man1/jsadebugd.1.gz jsadebugd.1.gz /usr/java/default/man/man1/jsadebugd.1.gz \
--slave /usr/bin/jstack jstack /usr/java/default/bin/jstack --slave /usr/share/man/man1/jstack.1.gz jstack.1.gz /usr/java/default/man/man1/jstack.1.gz \
--slave /usr/bin/jstat jstat /usr/java/default/bin/jstat --slave /usr/share/man/man1/jstat.1.gz jstat.1.gz /usr/java/default/man/man1/jstat.1.gz \
--slave /usr/bin/jstatd jstatd /usr/java/default/bin/jstatd --slave /usr/share/man/man1/jstatd.1.gz jstatd.1.gz /usr/java/default/man/man1/jstatd.1.gz \
--slave /usr/bin/jvisualvm jvisualvm /usr/java/default/bin/jvisualvm --slave /usr/share/man/man1/jvisualvm.1.gz jvisualvm.1.gz /usr/java/default/man/man1/jvisualvm.1.gz \
--slave /usr/bin/native2ascii native2ascii /usr/java/default/bin/native2ascii --slave /usr/share/man/man1/native2ascii.1.gz native2ascii.1.gz /usr/java/default/man/man1/native2ascii.1.gz \
--slave /usr/bin/rmic rmic /usr/java/default/bin/rmic --slave /usr/share/man/man1/rmic.1.gz rmic.1.gz /usr/java/default/man/man1/rmic.1.gz \
--slave /usr/bin/schemagen schemagen /usr/java/default/bin/schemagen --slave /usr/share/man/man1/schemagen.1.gz schemagen.1.gz /usr/java/default/man/man1/schemagen.1.gz \
--slave /usr/bin/serialver serialver /usr/java/default/bin/serialver --slave /usr/share/man/man1/serialver.1.gz serialver.1.gz /usr/java/default/man/man1/serialver.1.gz \
--slave /usr/bin/wsgen wsgen /usr/java/default/bin/wsgen --slave /usr/share/man/man1/wsgen.1.gz wsgen.1.gz /usr/java/default/man/man1/wsgen.1.gz \
--slave /usr/bin/wsimport wsimport /usr/java/default/bin/wsimport --slave /usr/share/man/man1/wsimport.1.gz wsimport.1.gz /usr/java/default/man/man1/wsimport.1.gz \
--slave /usr/bin/xjc xjc /usr/java/default/bin/xjc --slave /usr/share/man/man1/xjc.1.gz xjc.1.gz /usr/java/default/man/man1/xjc.1.gz  
Pero como el proceso fue tan educativo, vale la pena explicarlo un poco
  • Los script dentro del RPM de Java alojan su contenido en /usr/java/. Crea dos enlaces simbolicos: default y latest que apuntan hacia el directorio jdk<version>. 
  • Suponemos que cuando actualicemos con otro RPM, actualizará dichos enlaces simbólicos.
  • Dado lo anterior, apuntamos la dirección en el comando update-alternatives a /usr/java/default, para no tener que realizar todo este proceso con cada actualización de JDK
  •  Antes de empezar a configurar las alternativas, empaquetamos los manuales relacionados con los paquetes java.
for i in `find /usr/java/jdk1.8.0_31/man/man1/ -mindepth 1 -exec readlink -f {} +`; do gzip $i; done
  •  Para el primer comando update-alternatives, buscamos todo software que se relacione con java (JRE): se alojan en /usr/java/default/jre/bin/, pero los apuntaremos a /usr/java/default/bin/.
lista=""
for i in `find /usr/java/default/jre/bin/ -type f`; do lista="$lista `basename $i`"; done

for i in ${lista[*]}; do 
  if [[ $i = java ]]; then
    echo -e update-alternatives --install /usr/bin/java java  /usr/java/default/bin/java 200 --slave /usr/share/man/man1/java.1.gz java.1.gz /usr/java/default/man/man1/java.1.gz "\\"
  else
    echo -e --slave /usr/bin/$i $i /usr/java/default/bin/$i --slave /usr/share/man/man1/$i.1.gz $i.1.gz /usr/java/default/man/man1/$i.1.gz "\\"; 
  fi
done
Luego, buscamos en /usr/java/default/bin/ todo el software que no está en $lista, es decir, aquellos que no configuramos como esclavo de java, y que he querido suponer que son todas herramientas de desarrollo:
for j in `find /usr/java/default/bin/ -type f `; do
  i=`basename $j`
  if [[ $lista != *`basename $i`* ]]; then 
    if [[ `basename $i` = javac ]]; then
      echo -e update-alternatives --install /usr/bin/javac javac  /usr/java/default/bin/javac 200 --slave /usr/share/man/man1/javac.1.gz javac.1.gz /usr/java/default/man/man1/javac.1.gz "\\"
    else
      echo -e --slave /usr/bin/$i $i /usr/java/default/bin/$i --slave /usr/share/man/man1/$i.1.gz $i.1.gz /usr/java/default/man/man1/$i.1.gz "\\"; 
    fi
  fi
done
Casi terminado, basta con configurar la versión de java a usar:
update-alternatives --config java
Como no había otra alternativa para javac, se ha configurado automáticamente.

(1) Fuente
(2) Fuente
(3) Fuente

viernes, 16 de enero de 2015

Compilando xscreensaver

Para muchos "compilar" no debería ser una tarea a realizar por un administrador linux, no lo discuto: Hay razones de peso al respecto.

Pero con xscreensaver, esta vez tengo una mejor: Resulta que en CentOS 7 (Con XFCE como Entorno Gráfico), a la fecha, no he podido encontrarlo en los repositorio extra como CentOSPlus, EPEL o incluso RPMForge (El que por cierto esta desaconsejado porque ya no se actualiza).

Así, el procedimiento no ha sido un dolor de cabeza como lo han sido otros, básicamente:

Una vez obtenido el paquete desde la página más oficial posible:
wget http://www.jwz.org/xscreensaver/xscreensaver-5.32.tar.gz
(Pudiendo encontrar en http://www.jwz.org/xscreensaver/download.html la versión más reciente) Y desempacarlo en un luga apropiado:
cd /usr/local/src/
tar -xzvf xscreensaver-5.32.tar.gz
Basta con entrar al directorio
cd xscreensaver-5.32
Configurarlo, compilar e instalar
./configure --with-shadow
make
make install
En cuanto a dependencias, basta con instalar los siguientes paquetes:
yum install gcc xorg-x11-server-devel.x86_64 libXt-devel libXpm-devel motif-devel bc intltool gtk3-devel gtk2-devel libxml2-devel libglade2-devel pam-devel

Si no lo habías hecho antes, el compilador
yum install gcc
Lo que en suma pueda parecer mucho, (Y eso que no han visto el espacio usado) pero la opción es dejar de usar un salvapantallas, o tener que usar Gnome o KDE. O dejar de usar una distribución orientada a servidores como estación de trabajo.

No, parece ser que no se activa openGL, lo que de todos modos no es el fin del mundo. Pero si quieres activarlo no te olvides de revisar si tu sistema lo soporta e instalar las cabeceras:
yum install freeglut-devel.
Sólo es necesario instalar pam-devel si se usa algún módulo PAM para la autenticación de usuarios. En ese caso, configure el paquete de la siguiente forma
./configure --with-pam --with-shadow
Si no lo hace, al iniciar xscreensaver se lanzará un mensaje así:
xscreensaver: 12:19:57: couldn't get password of "alortiz"
xscreensaver: 12:19:57: locking is disabled (error getting password).
xscreensaver: 12:19:57: does xscreensaver need to be setuid?  consult the manual.
Por último, especial mención a uno de los mensajes de error que encontré mientras buscaba las dependencias del sistema:
configure: error: Your system doesn't have "bc", which has been a standard
                  part of Unix since the 1970s.  Come back when your vendor
                  has grown a clue.
El cual fácilmente arranca una sonrisa.

(1) Fuente
(2) Fuente

Otros apuntes interesantes

Otros apuntes interesantes