joe di castrohttp://joedicastro.com2011-06-22T02:10:00+02:00De Drupal a Pelican2011-06-22T02:10:00+02:00joe di castrohttp://joedicastro.com/de-drupal-a-pelican.html<p>Este blog no está realizado con ningún <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr>, ni siquiera utiliza <abbr title="Base de datos">BDD</abbr> alguna, es simplemente HTML + CSS y nada más. Es decir, es contenido estático, no dinámico. Hasta hace 3 días estaba funcionando con el mejor <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> <a href="http://es.wikipedia.org/wiki/PHP">PHP</a> que conozco, <a href="http://drupal.org">Drupal</a>. Pero persiguiendo el camino hacia el minimalismo y la productividad (fiel al espíritu <a href="http://es.wikipedia.org/wiki/Principio_KISS"><abbr title="Keep It Simple, Stupid (en español, &quot;Mantenlo simple, estúpido&quot;). Ver enlace"><abbr title="Keep It Simple, Stupid (en español, &quot;Mantenlo simple, estúpido&quot;). Ver enlace">KISS</abbr></abbr></a>) que ya inicie cuando <a href="http://joedicastro.com/markdown-la-mejor-opcion-para-crear-contenidos-web.html">comencé a escribir todos mis artículos en Drupal con Markdown</a>, el siguiente paso era evidente. La pregunta era muy sencilla, si un blog consta de contenidos que rara vez cambian (exceptuando los comentarios) ¿para que necesito un gestor de contenidos dinámicos?</p> <p>La respuesta es fácil, para nada. Actualmente, gracias a servicios como los de <a href="http://disqus.com">Disqus</a>, <a href="http://livefyre.com/">Livefyre</a>, <a href="http://intensedebate.com/">IntenseDebate</a> ó <a href="http://www.aboutecho.com/commenting">Echo</a> es posible externalizar el único contenido dinámico básico de un blog, los comentarios. Todo lo demás puede ser contenido puramente estático, solo HTML y CSS, sin renunciar a prácticamente nada de lo que nos ofrece un blog basado en un <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> como Wordpress o Drupal. Se pueden emplear scripts externos en javascript si se desea, o insertarlos dentro del HTML. Lo que nos permite implementar lo mismo que en un blog normal. Además se puede disponer también de feeds RSS y Atom.<br /> </p> <h2 id="elegir_un_generador_de_contenido_est+tico">Elegir un generador de contenido estático</h2> <p>Evidentemente la idea no es crear las paginas HTML a mano, ni de broma, lo lógico era seguir empleando la misma estrategia que ya había iniciado con Drupal, emplear solo ficheros de texto en formato Markdown que nos generarán el HTML necesario de forma automática. Entonces lo que tenía que encontrar era un software que me permitiera hacer lo mismo que Drupal, pero sin toda la parafernalia que rodea a un <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr>. Un generador de sitios web estáticos (a partir de markdown) y que a ser posible estuviera escrito en <strong>Python</strong>, mi lenguaje favorito. Como ya adelante en el <a href="http://joedicastro.com/markdown-la-mejor-opcion-para-crear-contenidos-web.html">artículo sobre Markdown</a>, existen varias opciones:</p> <ul> <li><a href="http://docs.notmyidea.org/alexis/pelican/">Pelican</a> de Alexis Métaireau, que emplea en su propio <a href="http://blog.notmyidea.org/">blog</a></li> <li><a href="http://www.blogofile.com/">Blogofile</a> de Ryan McGuire que también lo usa en su <a href="http://www.enigmacurry.com/">blog</a></li> <li><a href="http://hyde.github.com/">Hyde</a> de Lakshmi Vyas. Su <a href="http://ringce.com/blog/">blog</a> con Hyde también.</li> <li><a href="https://github.com/mitsuhiko/rstblog">rstblog</a> de Armin Ronacher. Solo permite reStructuredText, con él crea su <a href="http://lucumr.pocoo.org/">blog</a>, un ejemplo de elegancia y calidad.</li> </ul> <p>Bueno, tenía varias posibilidades, solo tenía que elegir una que se adaptara mejor a mis necesidades. De entrada descarté <strong>rstblog</strong> porque no permitía el empleo de markdown, cuando los otros permitían tanto .rst como .md como formatos de entrada. Solo me quedaban 3 candidatos. Así que lo primero que hice antes de nada, fue buscar blogs creados con cada uno de ellos, para ver que posibilidades reales ofrecían. Encontré ejemplos de blogs de mucha calidad de todos ellos. Aunque enseguida me di cuenta de una cosa, en dos de ellos los mejores blogs lo eran porque tenían una elevada personalización detrás (artículos de sus autores contándolo). Y curiosamente con el tercero, casi todos preferían quedarse con la configuración estándar, sin tocar prácticamente nada, y la verdad es que el resultado era bastante decente. Luego miré que cargaba cada uno de ellos en la página de entrada, y volvía a repetirse la misma tendencia. En los dos primeros vi demasiadas hojas de estilo, imágenes y demasiados scripts javascript, en el tercero, nuevamente se cargaban menos elementos. Finalmente comparé características, modo de funcionamiento y le eché un vistazo rápido al código. La impresión era otra vez la misma, dos de ellos, <strong>Hyde</strong> y <strong>Blogofile</strong> aunque aparentemente potentes, los veía innecesariamente complejos, en cambió <strong>Pelican</strong> era bastante más sencillo. Otra forma de determinar su repercusión era contar el número de descargas de cada una de las aplicaciones desde PyPi. Los números son los siguientes (a 27 de Junio de 2011), obtenidos con <a href="https://github.com/aclark4life/vanity">Vanity</a> o <a href="http://pythonpackages.com/">pythonpackages.com</a>:</p> <table> <thead> <tr> <th>Paquete</th> <th>Descargas</th> <th>Descargas (2-12-2011)</th> <th>Descargas (7-4-2012)</th> </tr> </thead> <tbody> <tr> <td>Blogofile</td> <td>2.419</td> <td>3.854</td> <td>5.276</td> </tr> <tr> <td>Hyde</td> <td>1.945</td> <td>4.518</td> <td>7.644</td> </tr> <tr> <td>Pelican</td> <td>3.919</td> <td>6.138</td> <td>10.126</td> </tr> </tbody> </table> <p>La elección final era Pelican y no me arrepiento en absoluto, la prueba es que esté blog está funcionando gracias a él (Gracias Alexis!). Aunque las otras dos son también muy buenas opciones, y seguramente serían la primera opción para más de uno. Y siempre podría cambiar fácilmente, porque el contenido seguiría estando guardado en ficheros de texto con marcado markdown. </p> <blockquote> <p><strong>Actualización</strong> (2-12-2011): </p> <p>La estructura de Pelican es tan sencilla y eficaz, que <a href="http://www.solberg.is/">Jökull Sólberg</a> ha creado a partir de una versión hospedada del mismo (y modificada) una de las plataformas de blogs más simples de utilizar que existen, <a href="http://calepin.co/">calepin.co</a>. Publicar articulos es tán fácil como crear un archivo markdown y guardarlo en tu cuenta de <a href="http://www.dropbox.com/">Dropbox</a>. Así de sencillo.</p> </blockquote> <p>No entraré en detalles ahora de como instalar y emplear <strong>Pelican</strong>, eso lo dejo para otro próximo articulo, <a href="http://joedicastro.com/pelican-introduccion-e-instalacion.html">Pelican</a>. Pero si voy a hacer un repaso de los pros y los contras de emplear Pelican frente a un <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> como Drupal para crear un blog.</p> <h2 id="ventajas_de_pelican_vs_cms">Ventajas de Pelican vs <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr></h2> <h3 id="solo_ficheros_de_texto_no_bdd">Solo ficheros de texto, No <abbr title="Base de datos">BDD</abbr></h3> <p>Simplemente te tienes que preocupar de eso, ficheros de texto, es donde guardas el contenido que creas. Todo lo demás lo genera Pelican por ti. Nada de crear y gestiónar bases de datos, ni copias de seguridad de la misma y un montón de espacio y recursos desaprovechado solamente para generar dinámicamente el mismo contenido que te genera Pelican.</p> <h3 id="mejor_rendimiento_carga_de_p+gina_m+s_r+pida">Mejor rendimiento, carga de página más rápida</h3> <p>Generar contenido dinámico es más caro en recursos y es más lento (consultas a la <abbr title="Base de datos">BDD</abbr>). Sobre todo a medida que llenas tu <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> de personalizaciones y plugins. ¿Que hacen prácticamente todos los sistemas de caché?, generar contenido estático para luego servirlo más rápidamente. ¿No es un poco estúpido crear contenido que apenas cambia en el tiempo, en un sistema dinámico que genera ese contenido cada vez y que para mejorar su rendimiento lo convierte en estático? Y ya no hablemos de las múltiples hojas CSS, scripts javascript y enlaces a contenido externo que cargan la mayoría de los <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> por defecto. Cada plugin que añadimos pone su granito de arena y optimizar todo esto requiere dedicación y esfuerzo (o seguir sumando aún más plugins en el mejor de los casos). Con Pelican ya tienes directamente el contenido estático y menos recursos que descargar. En este blog, sin contar con los ficheros javascript de Disqus y Piwik, lo único que se descarga es un fichero HTML, una hoja CSS y las imágenes que se incluyen en los artículos (cuando las hay). Es decir sirves el mismo contenido pero generando menos tráfico desde tu servidor. </p> <h3 id="soporta_mejor_el_tr+fico">Soporta mejor el tráfico</h3> <p>Cuando un sitio web soporta mucho tráfico, emplear un <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> requiere de mucha optimización y generalmente de mucha maquina o complejas instalaciones. Y la base principal siempre es un sistema de caché que sirva contenido lo más estático posible. Se cachea todo lo que se puede, y si es en memoria mejor. Las <abbr title="Base de datos">BDD</abbr> son un problema aparte, desde soluciones NoSQL a clusters o <abbr title="Base de datos">BDD</abbr> distribuidas. Con contenido estático no te tienes que preocupar de optimizar los accesos a la <abbr title="Base de datos">BDD</abbr>, solo de tener un buen servidor web y si quieres, cachear en memoria o ampliar máquina. Pero poco más.</p> <h3 id="seguridad">Seguridad</h3> <p>Olvídate de problemas de seguridad, los únicos agujeros de seguridad de un sitio con contenido estático están del lado del servidor web, de todo lo demás, te olvidas. Establece bien los permisos en el sistema de ficheros y punto. El único contenido dinámico del sitio (javascript) ni siquiera es algo que deba preocuparte, es algo externo que le concierne a <strong>Disqus</strong> o al sistema de analíticas web que elijas (Google Analytics o Piwik).</p> <h3 id="olvidarse_de_gestionar_un_cms_mantenimiento_mucho_m+s_sencillo_nulo">Olvidarse de gestionar un <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr>. Mantenimiento mucho más sencillo (nulo)</h3> <p>Instalar el <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr>, crear la <abbr title="Base de datos">BDD</abbr>, encontrar, instalar y probar los plugins que necesitas, actualizaciones, actualizaciones de seguridad, personalizaciones, temas... Todo lo que rodea a cualquier <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr>. Y ya no digamos si hablamos de un <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> potente y complejo como Drupal, con cientos de posibilidades. Y sin olvidar toda la basura que se va acumulando en las <abbr title="Base de datos">BDD</abbr> tras varias actualizaciones y múltiples pruebas de plugins, con Pelican siempre tienes un sistema limpio. Todo eso lo olvidas con Pelican, lo instalas, personalizas y automatizas una sola vez, luego te olvidas de todo lo que no sea escribir (si quieres, nada te impide seguir cambiándolo y mejorándolo). Emplea tú tiempo en crear contenido.</p> <h3 id="backups_m+s_sencillos">Backups más sencillos</h3> <p>Con un <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> <abbr title="No hacerlas es una decisión nefasta">deberías hacer Backups</abbr> del servidor web tanto del sistema de ficheros como de la <abbr title="Base de datos">BDD</abbr>. Y sería aconsejable tener un servidor web local montado para probar los cambios que vayas a hacer en el <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> sin miedo a romper nada. Con Pelican ni siquiera necesitas hacer Backups del servidor ni del contenido web. Todo lo que necesitas para generarlo ya está en tu ordenador en esos ficheros de texto. Incluso si empleas un tema propio, también está en tu equipo. Así que las copias de seguridad de tu sitio web no son distintas a las que <abbr title="No me digas que aún no las haces, ¿estas de broma?">regularmente ya haces</abbr> de tu ordenador personal.</p> <h3 id="hosting_en_cualquier_sitio">Hosting en cualquier sitio</h3> <p>Solo tienes que alojar contenido estático, no necesitas <abbr title="Base de datos">BDD</abbr> ni soporte para ningún lenguaje o librería en particular. Puedes hasta utilizar recursos gratuitos como las páginas de <a href="https://github.com/">GitHub</a> o <a href="http://bitbucket.org/">BitBucket</a> o un sistema de ficheros en la nube económico como <a href="http://aws.amazon.com/es/s3/">Amazon S3</a> (o <a href="http://aws.amazon.com/es/cloudfront/">Amazon CloudFront</a>). Solo necesitas eso, servir ficheros, nada más. Hasta el hosting más económico te sirve. </p> <h3 id="emplear_un_cvs_para_gestionarlo">Emplear un <abbr title="Control Version System (en español, &quot;Sistema de Control de Versiones&quot;)">CVS</abbr> para gestionarlo</h3> <p>Poder emplear Git o Mercurial o cualquier otro <abbr title="Control Version System (en español, &quot;Sistema de Control de Versiones&quot;)">CVS</abbr> para gestionar los cambios del blog no tiene precio. Ningún sistema de revisiones de <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> es tan potente. Además tienes la posibilidad de crear un <em>hook</em> para que al enviar un commit después de crear un articulo (o realizar un cambio) se suba el contenido automáticamente al servidor. Con esto realizar cualquier cambio o revertir un error es algo trivial. Además te permite subir una copia a un sitio como GitHub o BitBucket y tenerlo siempre disponible en cualquier sitio con conexión a la red. Como maravillosa opción, esto permite que el contenido de un blog, incluso de un mismo articulo, sea editado por más de una persona de manera bastante más sencilla, potente y menos propensa a errores que con un <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr>. </p> <h3 id="crear_los_articulos_off-line">Crear los articulos off-line</h3> <p>Eso te permite ir creando los artículos al ritmo que te de la gana, cuando quieras y en cualquier sitio con un portátil. No necesitas estar conectado a la red. Esto también puede hacerse con un <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr>, pero suele ser más complejo (exceptuando emplear cortar y pegar) e inseguro (si se habilita el envío remoto de artículos). Yo lo había logrado en Drupal empleando markdown, pero seguía necesitando un segundo paso on-line para personalizar las etiquetas. </p> <h3 id="edici+n_de_art+culos_m+s_c+moda">Edición de artículos más cómoda</h3> <p>Puede parecer que un <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> con su editor WYSIWYG es más cómodo, pero todo lo contrario. Ya lo comentaba en el <a href="http://joedicastro.com/markdown-la-mejor-opcion-para-crear-contenidos-web.html">artículo sobre markdown</a>. Pero es que además me proporciona una mejor experiencia de edición y más potente. Explico como redacto yo los artículos para que se entienda mejor. Divido la pantalla en dos mitades, a la izquierda el editor de textos y a la derecha el navegador. Como editor de textos empleo Gedit, que tiene resaltado de texto para markdown y un corrector ortográfico (por esto no uso vim para esto) bastante mejor que el de Firefox (que solo examina el texto hasta cierto número de casos dudosos). Además Pelican tiene una maravillosa opción, <code>autoreload</code> que lo hace correr en segundo plano y cuando detecta un cambio en uno de los ficheros, vuelve a generar el contenido. Entonces en gedit le digo que autoguarde el contenido cada 3 minutos (o a voluntad, manualmente) y cuando Pelican lo detecta, automáticamente regenera los ficheros HTML. Como navegador empleo Firefox y tengo, abierto en una pestaña, el fichero <code>index.html</code> que genera Pelican y empleando la extensión <a href="https://addons.mozilla.org/es-ES/firefox/addon/auto-reload/?src=api">Auto Reload</a> el contenido de la página (en local) se actualiza automáticamente al detectar un cambio en el fichero. Es decir, como en la primera página se puede ver el contenido completo del último articulo, lo que estoy viendo es una previsualización automática del contenido en la página cada 3 minutos. Y todo esto en off-line, sin estar conectado a internet. Esto si es un verdadero editor WYSIWYG, y no los otros. Además, que demonios, los navegadores no se diseñaron para crear texto, cualquier editor de texto es más potente.</p> <h3 id="control_del_spam">Control del Spam</h3> <p>El Spam, esa lacra que azota toda la web. En Pelican, ese problema, lo tiene que gestionar Disqus, no tú. Tú solo tienes que gestionar el poco que se le escape. Pero el buscar un plugin, configurarlo y que funcione bien, es algo de lo que no tienes que preocuparte. En Drupal tenía este asunto solucionado, pero fue cosa de probar varios plugins, hasta que al final <a href="http://joedicastro.com/combatir-el-spam-en-drupal.html">di con uno que me lo solucionaba de verdad</a>. </p> <h3 id="recursos_de_cpu_y_ram">Recursos de CPU y RAM</h3> <p>El contenido dinámico consume mucha más memoria RAM y CPU en el servidor que servir contenido estático. Al fin y al cabo, en el caso del contenido estático, es poco más complejo que servir ficheros. Si tienes que compartir el servidor con más proyectos, agradecerás no tener que emplear un <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> para servir el blog.</p> <h3 id="resaltado_de_sintaxis_incorporada_con_pygments">Resaltado de Sintaxis incorporada con Pygments</h3> <p>Mientras en la mayoría de <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> necesitas un plugin para habilitar el resaltado de sintaxis para código fuente, en Pelican esto viene por defecto empleando el excelente <a href="http://pygments.org/">Pygments</a></p> <h3 id="cumplimiento_de_est+ndares_web">Cumplimiento de Estándares Web</h3> <p>Con Pelican es relativamente sencillo configurar el tema para que cumpla los estándares web y genere contenido valido. Y una vez que lo haces, es para siempre, a no ser que modifiques algo en el tema, todo el contenido que generes cumplirá con los estándares (a no ser que incluyas HTML dentro que no lo sea). De este modo, este sitio valida HTML5, CSS3 y genera feeds RSS y Atom validos. Conseguir esto con un <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> y empleando editores WYSIWYG es bastante más complejo y doloroso. Aunque yo lo había conseguido con Drupal y markdown, tuve que modificar un tema casi por completo, casi como crearlo desde cero. </p> <h2 id="inconvenientes_de_pelican_vs_cms">Inconvenientes de Pelican vs <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr></h2> <h3 id="comentarios_sin_resaltado_de_sintaxis">Comentarios sin resaltado de sintaxis</h3> <p>Algo que me permitía Drupal y no me permite Disqus (por ahora) era emplear markdown en los comentarios y resaltado de sintaxis para el código fuente. Es el mayor inconveniente que he encontrado hasta ahora. Pero bueno, tampoco es algo imprescindible y esperemos que Disqus lo soporte en un futuro.</p> <h3 id="sitemap">Sitemap</h3> <p>Tampoco Pelican genera sitemaps en xml para los buscadores. Aunque tampoco es algo imprescindible y Drupal tampoco lo soporta por defecto, si no a través de un módulo. El autor lo tiene como tarea pendiente, y si tarda mucho, a lo mejor me animo y lo creo yo mismo.</p> <h3 id="personalizaci+n_m+s_sencilla_para_non_geeks">Personalización más sencilla para non geeks</h3> <p>Esta es la parte que menos me afecta, pero es el gran inconveniente para la gran mayoría sin conocimientos avanzados. Aunque Pelican no es difícil de instalar y configurar, si queremos personalizarlo bastante, la cosa cambia. Los <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> son mucho más sencillos en ese sentido, pero el coste a pagar por otro lado no me compensa. </p> <h3 id="no_tiene_b+squeda_incorporada">No tiene búsqueda incorporada</h3> <p>Es otro pequeño inconveniente que puede suplirse empleando la de Google AdSense en el sitio, por ejemplo. Personalmente no me importa demasiado, teniendo disponibles en el sitio recursos como el archivo de todos los artículos publicados o la nube de etiquetas.</p> <h3 id="no_puedes_personalizar_el_contenido_din+micamente">No puedes personalizar el contenido dinámicamente</h3> <p>Con un <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> puedes hacer cosas como mostrar un contenido o un tema distinto según el perfil del usuario, o según la carga del servidor, etc. Con contenido estático lógicamente no puedes hacer esto. A mi me da igual, no lo necesito, es solo un blog.</p> <br /> <p>Llevo varios años empleando Drupal en varios sitios y me sigue pareciendo un <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> excelente y una buenísima opción para generar contenido dinámico para no desarrollladores (de otro modo prefiero un framework como Django). Pero actualmente, para crear blogs, si se tienen conocimientos suficientes, emplear un <abbr title="Content Management System (en español, &quot;Sistema de gestión de contenidos&quot;)">CMS</abbr> me parece una decisión poco acertada, es matar moscas a cañonazos. Hoy en día hay soluciones como Pelican y las mencionadas arriba (y otras alternativas en otros lenguajes) que te permiten crear blogs con facilidad, centrándote únicamente en crear los artículos y automatizar todo lo demás. ¿Acaso esa no es la razón principal del grandisimo éxito de <a href="http://twitter.com/">twitter</a> o <a href="http://www.tumblr.com/">tumblr</a>? La inmediatez de los resultados y la delegación de la gestión a terceros, tú solo escribes. Pelican te permite lo mismo, solo requiere la personalización inicial y listo, con la ventaja añadida de que puedes personalizarlo a tu gusto y hasta donde te dé la gana o seas capaz.</p>Combatir el spam en Drupal2010-10-14T01:42:00+02:00joe di castrohttp://joedicastro.com/combatir-el-spam-en-drupal.html<h3 id="articulo_publicado_originalmente_en_el_antiguo_sitio_deaparatoscom">Articulo publicado originalmente en el antiguo sitio deaparatos.com</h3> <p style="text-align: center;"><img src="pictures/spam_stats.png" alt="deaparatos spam statistiscs" title="estadísticas de spam en deaparatos" height="550" width="593" /></p> <p>En esta gráfica se puede observar la <strong>disminución a lo largo del tiempo de los ataques de spam a este sitio</strong>, <strong>deaparatos.com</strong>, que funciona sobre <strong>Drupal</strong>. Esto se ha conseguido gracias a una doble estrategia:</p> <ul> <li>emplear uno de los mejores módulos antispam existentes para Drupal, <strong>Mollom</strong></li> <li>emplear un script en python de elaboración propia, <strong>ban_drupal_spammers.py</strong></li> </ul> <p>Esta doble estrategia no solo ha conseguido una más que <strong>notable reducción</strong> de la incidencia <strong>del molesto spam</strong> en este sitio, <strong>de casi un 70%</strong>, si no que además ha conseguido <strong>una más que notable reducción del ancho de banda consumido por los spammers</strong>, como se puede observar en la siguiente tabla:</p> <p>Estadísticas de Trafico generado por ataques de spam en deaparatos.com</p> <table> <thead> <tr> <th>Estrategia</th> <th>Días</th> <th>Ataques</th> <th>Trafico (GB)</th> <th>Media pagina (KB)</th> <th>Trafico mes (MB)</th> </tr> </thead> <tbody> <tr> <td>Mollom</td> <td><strong>359</strong></td> <td>48741</td> <td>7,116</td> <td>146,000</td> <td>602,927</td> </tr> <tr> <td>Mollom + script</td> <td><strong>359</strong></td> <td>358666</td> <td>0,016</td> <td>0,046</td> <td>1,389</td> </tr> <tr> <td><strong>Total</strong></td> <td><strong>359</strong></td> <td><strong>407407</strong></td> <td><strong>7,133</strong></td> <td><strong>17,507</strong></td> <td><strong>604,316</strong></td> </tr> </tbody> </table> <p>Si solo hubiera empleado el modulo Mollom, sin emplear mi script</p> <table> <thead> <tr> <th>Trafico spam</th> <th>Calculo</th> <th>Trafico (GB)</th> <th>Ahorro (GB)</th> </tr> </thead> <tbody> <tr> <td><strong>Trafico total</strong></td> <td>(358666 * (146,000 – 0,046) KB) + 7,133 GB =</td> <td>59,481</td> <td><strong>52,349</strong></td> </tr> <tr> <td><strong>Trafico mensual</strong></td> <td>((59,465 GB * 365) / 359) / 12) MB =</td> <td>5,040</td> <td><strong>4,435</strong></td> </tr> </tbody> </table> <p>Como podemos ver en las cifras mostradas de esta tabla, <strong>se ha conseguido en</strong> un periodo de aproximadamente <strong>un año reducir el ancho de banda consumido por los ataques de spam en más de 52 Gigabytes!</strong>, una autentica barbaridad de tráfico que de otro modo se hubiera malgastado. Estamos hablando de <strong>un ahorro de</strong> consumo de tráfico <strong>de casi 4,5 Gigabytes al mes!!!</strong> Un ahorro de ancho de banda mensual que en un hosting compartido puede tranquilamente suponer el cambio de un plan de hosting a otro, simplemente basta con que los molestos spammers pongan tu sitio web en su punto de mira. Y <strong>ha de tenerse en cuenta</strong> una cosa, <strong>que este trafico mensual hubiera sido muy superior si</strong> esta doble estrategia <strong>no hubiera conseguido reducir el numero de spammers en un 69.25%</strong>, no quiero ni pensar en las cifras que hubieran resultado...</p> <p>Para que nos hagamos una idea del ahorro de ancho de banda que ha supuesto el emplear mi script <strong>python</strong>, en el siguiente gráfico podemos ver la diferencia entre emplear solo <strong>Mollom</strong> y emplear <strong>Mollom</strong> combinado con <strong>ban_drupal_spammers.py</strong></p> <p style="text-align: center;"><img src="pictures/Ahorros.png" alt="Eficacia del script" title="Eficacia del script" height="292" width="600" /></p> <p>El gráfico es meridianamente claro, como podemos ver, <strong>por cada 1% de ataques que son rechazados por ban_drupal_spammers.py</strong> y no por Mollom, <strong>ahorramos un 1% de ancho de banda</strong>, tanto en el peso por página como en el tráfico total. Como podemos ver, <strong>hemos ahorrado un total de un 88% de ancho de banda del trafico que sería generado por los ataques de spam en deaparatos.com</strong></p> <p>Después de comprobar la eficacia de esta doble estrategia durante más de un año (las estadísticas se interrumpen antes por el cambio de hosting) voy a explicaros el porqué y el como he llegado a ella, a continuación. También se puede ver el script que ha marcado la diferencia de tráfico.</p> <h2 id="el_spam_en_internet">El spam en internet</h2> <p>El <a href="http://es.wikipedia.org/wiki/Spam">spam</a> es una de las lacras más tediosas y difíciles de combatir en Internet, por no mencionar las tareas delictivas que se apoyan en él. Después de 15 años combatiendo el spam en el correo electrónico, el problema aún está lejos de solucionarse, si bien es cierto que con una adecuada configuración de las herramientas de correo, se ha convertido en una molestia trivial para el usuario final. Pero para los servidores de correo y el tráfico de internet sigue siendo un problema de dimensiones colosales, la lucha contra el mismo se ha convertido una tarea titánica en la que se invierten ingentes sumas de dinero todos los años. De hecho gran parte del tráfico de toda internet se debe al spam (hay quien arroja cifras del 80%, e incluso superiores al 90%), lo que ha acarreado un costosísimo sobredimensionamiento en el equipamiento de proveedores de internet y servidores.</p> <p>Como antes comentaba, lejos de una solución definitiva (en gran medida depende de un tipo muy común de usuario final con escasa cultura informática), esta lacra se expandió hace unos años a hilos en foros, a comentarios en blogs, redes sociales, irc,... es decir, se ha expandido por toda la red. La explosión de la llamada web 2.0 no ha incrementado si no este problema, multiplicándolo. Y he aquí como un problema que afectaba a los usuarios de email y a los proveedores de internet, se ha convertido también en un gran problema para los webmasters. Todo aquel que gestione un sitio web, ha tenido que enfrentarse antes o después con este maldito problema. Un problema que no solo se traduce en cientos o miles de detestables mensajes de spam, que se han de combatir de uno u de otro modo (algunos o bien se rinden o bien tienen abandonados sus sitios y se convierten en auténticos cementerios de spam), si no que además se traduce en un serio problema para el trafico de una web. El número de solicitudes que producen los ataques de spam puede llegar a ser tan elevado, que congestione totalmente ya no solo la página, si no el servidor cuando se trate de un hosting compartido, convirtiéndose casi de facto en un <a href="http://es.wikipedia.org/wiki/DoS">ataque DoS</a> en toda regla. Aún sin llegar a este indeseable extremo, el incremento del tráfico en el sitio debido al spam puede llegar a suponer un porcentaje muy importante del ancho de banda contratado (incluso más del 50% con contramedidas ineficientes), con los consiguientes perjuicios económicos que suponen al webmaster. Los spammers siempre han ido por delante de las contra-medidas, y la actual situación, con extensas <a href="http://es.wikipedia.org/wiki/Botnet">botnets</a> a su disposición y con el <a href="http://es.wikipedia.org/wiki/Cloud_computing">cloud computing</a> (se ha detectado el año pasado la primera <a href="http://www.idg.es/pcworldtech/Los-hackers-controlan-una-botnet-desde-Amazon-EC2/doc88089-actualidad.htm">botnet que empleaba los servicios de Amazon EC2</a>) , nos ha llevado a un combate continuo en las que tienen todas las de ganar a medio plazo... y observo esto con cierta tristeza, por que entiendo que la solución final pasa necesariamente por la educación del usuario final, haciéndole inmune a los -en gran medida patéticos, infantiles, ridículos y chapuceros- reclamos del spam. Y esto último desgraciadamente dista mucho de acercarse a una situación ideal. También cabe mencionar que <a href="http://googlewebmaster-es.blogspot.com/2009/12/comentarios-spam-la-dura-realidad.html?utm_source=feedburner&amp;utm_medium=feed&amp;utm_campaign=Feed%3A+ElBlogParaWebmasters+%28El+Blog+para+Webmasters%29">el spam también perjudica al posicionamiento de una web,</a> a su prestigio, a su funcionalidad, a su aspecto, etc.</p> <h2 id="deaparatoscom_y_el_spam">deaparatos.com y el spam</h2> <p>Y <strong>deaparatos</strong> no está exento de esta amenaza, de hecho se había convertido en un serio problema en el 2009. Este sitio está gestionado con <a href="http://drupal.org/">Drupal</a>, y después de probar con distintos módulos y métodos, unos más frustrantes que otros, ninguno solucionaba por completo el problema, ni me satisfacía como solución. Al final, combinando el módulo más idóneo para combatir esta plaga (idóneo por resultados y por comportamiento) con un script de factura propia en <a href="http://drupal.org/project/Modules">Python</a>, he logrado, no acabar con todo el spam (se me antoja tarea cuasi imposible), pero si minimizar sus efectos a un nivel muchísimo más que aceptable. Y minimizar los efectos tanto a la hora de impedir/eliminar los comentarios spam, como de reducir el abultado tráfico que estos ataques consumían. ¿Por qué un script en <strong>Python</strong>? bueno buscaba algo rápido, un prototipo para probar la solución que tenia en la cabeza y porque estoy "enamorado" de este lenguaje de programación. Quizás si veo que merece la pena, me plantee migrarlo a PHP y convertirlo en un módulo de <strong>Drupal</strong>, o bien modifique el modulo oficial que estoy empleando para mitigar el spam y le incorpore el código que empleo ahora. Bueno, veamos como he llevado a cabo esta solución y porqué.</p> <p>Generalmente los métodos para combatir el spam se centran en:</p> <ul> <li>medidas activas: análisis <a href="http://es.wikipedia.org/wiki/Heuristica">heurísticos</a>, <a href="http://es.wikipedia.org/wiki/Inferencia_estad%C3%ADstica">filtros estadísticos</a> (<a href="http://en.wikipedia.org/wiki/Bayesian_spam_filtering">bayesianos</a>), diferenciación de <a href="http://es.wikipedia.org/wiki/Bot">bots</a>/humanos (<a href="http://es.wikipedia.org/wiki/Captcha">captchas</a>), filtros por <a href="http://es.wikipedia.org/wiki/Host">host</a>/email, mediante <a href="http://es.wikipedia.org/wiki/Cookie">cookies</a>, <a href="http://es.wikipedia.org/wiki/Timestamp">timestamps</a>, filtros por <a href="http://es.wikipedia.org/wiki/User_agent">user-agent</a>, ...</li> <li>medidas pasivas: <a href="http://es.wikipedia.org/wiki/Ofuscaci%C3%B3n">ofuscar</a> direcciones de correo, moderación de comentarios, <a href="http://es.wikipedia.org/wiki/Nofollow">enlaces nofollow</a>, permisos de publicación, políticas de contraseñas, campos ocultos mediante <a href="http://es.wikipedia.org/wiki/Css">css</a>/<a href="http://es.wikipedia.org/wiki/Javascript">javascript</a>, cerrar los comentarios de un post pasado un tiempo, ...</li> </ul> <p>De entrada tenía, y tengo, algo muy claro, no voy a emplear ninguno de estos tres métodos habituales: <em>captchas</em>, <em>moderación de comentarios</em> y <em>requerir registro</em> para enviar comentarios. Y es una decisión inamovible, no pienso claudicar de ningún modo en este sentido. Son métodos que o bien me harían perder un tiempo del que ni dispongo, ni estoy dispuesto a perder, o bien suponen un incordio que me personalmente me incomodan mucho cuando me los encuentro en otros sitios y por los que no quiero hacer pasar a mis lectores. Esto evidentemente deja fuera algunas de las medidas más efectivas para combatir el spam, pero son medidas en las que el usuario o el webmaster siempre pierden, de un modo u otro, y no estoy dispuesto a permitir que los spammers condicionen en ningún modo el compartimiento de este sitio. Aunque suene contradictorio con lo que acabo de decir, el método que voy a comentar aquí, y que empleo actualmente, emplea en alguna medida el uso de captchas, aunque de modo tan limitado, que afecta a menos del 0,5% de los comentarios enviados. Digamos que lo acepto como una razonable excepción a la regla. Si empleo en cierta medida algunos de los otros métodos.</p> <p style="text-align: center;"><img src="pictures/ammap.png" alt="Eficacia del script" title="Eficacia del script" height="419" width="629" /></p> <p>En este mapa podemos ver el país de origen de los ataques de spam contra deaparatos.com</p> <h2 id="drupal_y_el_spam">Drupal y el spam</h2> <p>Con que armas contamos en <strong>Drupal</strong> para combatir el spam? Por un lado tenemos el clásico modulo <a href="http://drupal.org/project/spam">Spam</a>, que emplee en este mismo sitio durante más de dos años, y que su mayor ventaja es contar con un <a href="http://es.wikipedia.org/wiki/Clasificador_bayesiano_ingenuo">filtro Bayesiano</a>. Este módulo es usado actualmente en unos 4.893 sitios <sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup> con <strong>Drupal</strong>. Durante mucho tiempo funcionó perfectamente, de vez en cuando se colaba algún comentario spam, pero era cuestión de reportarselo al modulo y el iba aprendiendo, así como podíamos crear nuestros propios filtros personalizados. El problema comenzó cuando los que se colaban ya eran unos 20 spam diarios y aumentando, y entonces ya no era viable, ni cómodo, perder tanto tiempo para que el filtro bayesiano aprendiera a combatir unos ataques que eran cada vez más sofisticados. Así que tocaba mirar otra alternativa de entre alguna de las disponibles:</p> <ul> <li><a href="http://drupal.org/project/captcha">Captcha</a>, uno de los más usados en <strong>Drupal</strong> (<strong><em>80.286 sitios</em></strong>), y uno de los métodos más empleados en la red. Es la clásica opción donde mediante una pregunta al usuario se intenta diferenciar entre humano y maquina. Los captchas pueden ser de varios tipos, desde cálculos matemáticos sencillos hasta gráficos donde se encuentran unos caracteres ofuscados y que el usuario debe introducir. Hay varios módulos que lo complementan, aportando distintos tipos de captchas, donde <a href="http://drupal.org/project/recaptcha">reCaptcha</a> es uno de los más empleados (16.684). </li> <li><a href="http://drupal.org/project/akismet">Akismet</a>, todo un clásico, basado en el servicio homónimo, <a href="http://wordpress.org/">Akismet</a>, que creara en su día <a href="http://wordpress.org/">Wordpress</a> y que hoy es sostenido por <a href="http://automattic.com/">Automattic</a>, empresa donde trabajan la mayoría de los desarrolladores oficiales de <strong>Wordpress</strong>. Es uno de los métodos más difundidos en la red, en parte por venir de la mano de la empresa más emblemática de los blogs. Esta implementación del servicio <strong>akismet</strong> es ya un tanto antigua (ya no tiene soporte) y hay un modulo más reciente que lo supera y aporta más funcionalidades, <strong>Antispam</strong>, también en esta lista. Es usado actualmente por solo <strong><em>947 sitios</em></strong>.</li> <li><a href="http://drupal.org/project/spamicide">Spamicide</a>, se basa en la premisa de que la mayoría de los ataques spam se producen con bots que acceden a la página con navegadores en modo texto (<a href="http://es.wikipedia.org/wiki/Script_%28inform%C3%A1tica%29">scripts</a> en realidad), por lo tanto ni hacen uso de <strong>css</strong>, ni de <strong>javascript</strong>. Aprovechando esta circunstancia, crean un campo de formulario que es ocultado con css, con lo cual el usuario normal no lo ve, pero si el bot, que si lo rellena con texto, queda descartado. Pero los spammers aprenden muy rapido, así que la medida no es muy efectiva. Además últimamente empiezan a aparecer personas dedicadas a introducir comentarios spam a mano (de ahí vienen muchos de esos comentarios <a href="http://www.frikipedia.es/friki/HOYGAN">hoygan</a> absurdos que no parecen tener mucho sentido) y que cobraran una miseria en países subdesarrollados, en parte para saltarse los captcha. Por eso su efectividad es muy dudosa. Aunque si puede ser usado combinado con otros módulos spam, para reforzar su eficacia. Es muy poco usado, apenas <strong><em>377 sitios</em></strong> lo emplean. </li> <li><a href="http://drupal.org/project/antispam">Antispam</a>, uno de los mejores módulos antispam para drupal. Con el se puede usar algunos de los mejores servicios antispam externos que hay en la red: <a href="http://akismet.com/">Akismet</a>, <a href="http://antispam.typepad.com/">Typepad</a> y <a href="http://defensio.com/">Defensio</a>. Con él podemos abrir una cuenta en uno de estos servicios y configurar el módulo para emplearlo. Su eficacia es muy elevada, ya que son algunas de los mejores armas disponibles contra el spam. El funcionamiento básico es consultar la base de datos de alguno de estos servicios, muy completas, para comprobar si el comentario u el posteador son probable spam, y bloquearlo en caso de que la probabilidad sea muy elevada. En caso de duda, aparecerá un captcha para descartar bots. Tiene unas gráficas estadísticas muy útiles para comprobar la evolución del problema en nuestro sitio. No es demasiado empleado, estando instalado en unos <strong><em>1.718 sitios</em></strong>. </li> <li><a href="http://drupal.org/project/badbehavior%20">Bad Behavior</a>, otro viejo conocido de las medidas antispam. Este se basa en parte en un análisis heurístico de las peticiones HTTP del bot y comparándolo con las bases de datos que poseen de spambots conocidos. Este hace uso también de las base de datos del <strong>Proyecto Honey Pot</strong>, para reforzar la identificación de spammers. Es bastante eficiente, pero el problema está en que van por detrás siempre de los spammers y a veces se les cuela algún que otro comentario spam. Básicamente porque se basa en que uno reporte los spammers que aún no están en sus bases de datos, y hasta que alguien reporta a un spammer, este puede habernos colado unos cuantos mensajes. Es empleado en unos <strong><em>918 sitios</em></strong> drupal.</li> <li><a href="http://drupal.org/project/httpbl">http:BL</a>, ese se basa enteramente en el <a href="http://www.projecthoneypot.org/">Proyecto Honey Pot</a>. Usa sus bases de datos (<a href="http://es.wikipedia.org/wiki/Lista_negra">DNS blacklist</a>) para prevenir comentarios spam y recolectores de direcciones email. Es eficiente en la misma medida que el anterior, depende de su base de datos, que no es tan completa como las de los servicios que soporta el modulo <strong>Antispam</strong>. Una de las virtudes de este modulo es que bloquea las solicitudes de pagina de aquellas Ips que están en su lista negra, con el consiguiente beneficio que esto reporta para el trafico de nuestro sitio. Permite también el uso de <a href="http://en.wikipedia.org/wiki/Whitelist">whitelists</a> y <a href="http://en.wikipedia.org/wiki/Greylist">greylists</a>. Podría ser uno de los mejores módulos antispam para Drupal si no se colaran más comentarios spam de lo deseable. Actualmente solo<strong><em>443 sitios</em></strong> emplean este modulo.</li> <li><a href="http://drupal.org/project/phpids">PHPIDS</a>, esta emplea una aproximación al problema diferente. Emplea un sistema de detección de intrusos desarrollado y mantenido por <a href="http://php-ids.org/">PHPIDS</a>. Este no solo detecta ataques de spam, si no que también otro tipo de ataques maliciosos al sitio, como <a href="http://es.wikipedia.org/wiki/XSS">XSS (cross site scripting)</a>, <a href="http://es.wikipedia.org/wiki/Inyeccion_SQL">inyecciones sql</a>, DoS, etc. El problema es que arroja demasiados falsos positivos y hemos de ir afinando la detección poco a poco, lo cual puede llegar a ser bastante tedioso. Se puede usar conjuntamente con otros módulos antispam, pero normalmente este bloqueará el ataque antes de que el otro se percate. Lo malo, claro, es que hasta que no esté completamente afinado, a los usuarios les puede dar mucho la lata ante comentarios completamente inocuos. También puede llegar a generar unos logs muy extensos que pueden incrementar bastante nuestra base de datos. Puede ser muy útil para aquellos sitios en los que los ataques van más allá del simple spam. Tampoco es muy empleado, solo <strong><em>361 sitios</em></strong> lo tienen implantado.</li> <li><a href="http://drupal.org/project/mollom">Mollom</a>, uno de los últimos en llegar, pero lo ha hecho arrasando, en dos años ha conseguido que ya sea empleado en <strong>23.983 sitios</strong> drupal. Esto se debe en parte a que uno de los co-autores es el creador de <strong>Drupal</strong>, <a href="http://buytaert.net/">Dries Buytaert</a>. <a href="http://mollom.com/">Mollom</a> es un servicio web en la misma linea que <strong>Askimet</strong> o <strong>Defensio</strong>, con una base de datos de usuarios en la que aparte de spammers, se registran reputaciones de usuario en función de parámetros como comentarios ofensivos, comentarios de "baja-calidad" (hoygans), comentarios off-topic, etc según como nosotros lo reportemos a <strong>Mollom</strong>. Es decir que nos ayuda también a mejorar la calidad de nuestro sitio filtrando también a usuarios con baja reputación en función de los parametros que nosotros marquemos. Esto desde luego es un punto a favor del servicio, que nos permite matar dos pajaros de un tiro. El servicio analiza el texto del mensaje, y si es spam, lo bloquea y en caso de dudas mostrara un captcha como el de la imagen (menos del 2% de las ocasiones). Además todo el código es opensource, tanto el del modulo como el de la API de <strong>Mollom</strong> y hay disponibles módulos para otros gestores de contenidos como <strong>Wordpress</strong>, <a href="http://www.joomla.org/">Joomla</a> o <a href="http://radiantcms.org/">Radiant</a> y librerías para múltiples lenguajes (Java, PHP, Ruby, Python, Perl, .Net, ...).</li> </ul> <p>Después de analizar las posibilidades y probar unos cuantos módulos (<strong>Antispam</strong>, <strong>Bad Behavior</strong>, <strong>http:BL</strong>, <strong>PHPIDS</strong> y <strong>Mollom</strong>) llegué a valorar que las dos mejores soluciones en mi caso eran <strong>Antispam</strong> Y <strong>Mollom</strong>. Aunque <strong>PHPIDS</strong> y <strong>http:BL</strong> tenían algunas características únicas que echaba de menos en ellos. Después de probar durante unas semanas tanto <strong>Antispam</strong> como <strong>Mollom</strong>, observe que el indice de fallos de <strong>Mollom</strong> era mucho menor y además era más transparente al usuario, mostrando el captcha en menos ocasiones. Si, <strong>Captcha</strong> es la opción más socorrida por la mayoría de los usuarios de <strong>Drupal</strong>, en cuanto que es la que menos molesta al webmaster, claro, pero le traspasa la molestia al usuario. Yo odio directamente los captcha, no los soporto, y he pasado de utilizar alguna web por ellos. <strong>Mollom</strong> tiene la ventaja de reducir esta molestia a la minima expresión, por lo que el 98% de los usuarios de la web ni siquiera se darán cuenta de que en ella funciona un sistema antispam, que es lo que buscaba desde el principio, un servicio efectivo y transparente.</p> <p><strong>Mollom</strong> era pues, la opción elegida y la que está funcionando en este sitio desde entonces.</p> <p style="text-align: center;"><img src="pictures/captcha.png" alt="Ejemplo de captcha de Mollom" title="Ejemplo de captcha de Mollom" height="114" width="600" /></p> <p>Ejemplo de captcha generado por Mollom</p> <h2 id="la_soluci+n_definitiva_mollom__ban_drupal_spammerspy">La solución definitiva, Mollom + ban_drupal_spammers.py</h2> <p><strong>Aunque Mollom funciona de manera muy efectiva, bloqueando aprox. el 99,98% (en deaparatos.com) de los mensajes spam</strong>, esto no impide que los atacantes sigan intentando una y otra vez colar su spam en el sitio. Esto nos lleva a que las páginas se cargan una y otra vez, consumiendo ancho de banda, ya que <strong>Mollom</strong> actúa a posteriori, cuando se envía el comentario, no antes de cargar la página, lo que es el funcionamiento normal de estos sistemas antispam. Y además los spammers tienen cierta inclinación a intentar introducir el spam en las paginas más populares, las que suelen tener más comentarios y por lo tanto de mayor peso por lo general. Basta con decir que el ancho de banda medio generado por cada uno de estos ataques en este sitio ha sido de 146Kb.<br /> </p> <p>Y es en este aspecto donde echaba de menos una de las características de <strong>http:BL</strong>, bloquear el acceso al sitio a los que están en su lista negra. Empecé entonces a darle vueltas a la manera de implementar esta característica en mi sitio, pero pronto me di cuenta de dos cosas:</p> <ul> <li>No quería hacer una consulta a projecthoneypot.org cada vez que alguien accediera al sitio, por evidentes mermas en el rendimiento del sitio.</li> <li>No quería tampoco tener una lista negra local que se alimentara periódicamente de projecthoneypot.org, porque no quería tener que comprobar miles de ips que probablemente nunca accederían a mi sitio.</li> </ul> <p>La solución entonces pasaba por bloquear solo a los que ya hubieran ejecutado un ataque de spam contra el sitio y que hubieran sido bloqueados al menos una vez por <strong>Mollom</strong>, de modo que en los sucesivos ataques fueran rechazados antes siquiera de cargar la página.</p> <p>Hay una forma de hacer esto de forma manual en <strong>Drupal</strong>, simplemente hay que añadir las ips de los spammers a través de las <em>reglas de acceso</em> en el menú de <em>Administración</em>. Claro que el método es evidentemente tedioso y aparatoso, comprobar las ips atacantes e ir añadiéndolas una por una a través del formulario. Tenía que hacerse de una manera automatizada.</p> <p>La primera idea y más evidente era modificar el modulo <strong>Mollom</strong> para lograr esto, pero no me gusta PHP y procuro evitarlo, además quería un prototipo rápido para evaluar la eficacia de la solución y su repercusión en el ancho de banda, así que todo empezó con un sencillo script en <strong>python</strong>. Pronto me di cuenta de que <strong>Mollom</strong> registraba las ips de todos los atacantes que bloqueaba en el registro de eventos de <strong>Drupal</strong> (la tabla <em>watchdog</em> del modulo opcional <em>Database logging</em>), y que alguna de ellas tenía hasta 30 entradas diferentes en el registro. Y como <strong>Drupal</strong> incorpora el método que citaba antes para banear IPs, lo único necesario era añadir estas IPs a la tabla <em>access</em>.</p> <p style="text-align: center;"><img src="pictures/banned.png" alt="Ejemplo de pagina de ip bloqueada por Drupal" title="Ejemplo de pagina de ip bloqueada por Drupal" height="30" width="233" /></p> <p>Este es un ejemplo de la pàgina que se encontraria un atacante de spam bloqueado a través de la tabla <em>access</em> en Drupal</p> <p>Ahora bien, si añadimos automáticamente estas IPs, llegara un momento en que tendremos varias miles de ellas, y el rendimiento de la página se vera afectado, al tener que comprobar todas estas ips cada vez que alguien accede a la página. Además hay que tener en cuenta que algunas de estas IPs tendrán como origen a un usuario que teniendo el ordenador o router infectado por un <a href="http://es.wikipedia.org/wiki/Rootkit">rootkit</a>/<a href="http://es.wikipedia.org/wiki/Troyano_%28inform%C3%A1tica%29">troyano</a>, pertenezca a una <a href="http://es.wikipedia.org/wiki/Botnet">Botnet</a> sin saberlo. Es posible que estos usuarios acaben limpiando de <a href="http://es.wikipedia.org/wiki/Malware">malware</a> su equipo y en un momento determinado quieran acceder legítimamente al sitio, por lo que no deberían estar bloqueados de por vida. Esto lo solucioné en el script rotando las IPs al llegar a umbral determinado, marcado por el número máximo de las IPs que deseemos almacenar en esta tabla. Al llegar a este número máximo, se borra un porcentaje de IPs, eligiendo siempre a las más antiguas. En estos momentos, en función del rendimiento y tiempo que quiero que permanezcan en la tabla, tengo este valor establecido en unas 2000 IPs. Para controlar la fecha en que fueron introducidas cada una de las ips en la tabla, modifico la tabla access, añadiéndole un campo <em>timestamp</em>.</p> <p>Como se pudo ver al principio del articulo, la efectividad del script es muy elevada y a día de hoy sigo con este método, con un script que ha evolucionado varias veces desde entonces y que se adapta perfectamente a mis necesidades. Los picos que se pueden ver en el primer gráfico del spam bloqueado por el modulo <strong>Mollom</strong>, se deben precisamente a los breves periodos de tiempo en los que por una u otra razón el script no estaba funcionando.</p> <p>El porqué del fantástico ahorro de ancho de banda se puede explicar con la anterior imagen, que es un ejemplo de la página que se encontraría un atacante bloqueado por <strong>ban_drupal_spammers.py</strong>. <strong>Esta página tiene un peso ridículo de entre 33 y 39 bytes, del orden de unas 4000 veces menos que el peso medio de 146 Kilobytes por página del trafico generado por los spammers</strong>.</p> <p>Este script se puede ejecutar en remoto, para hostings compartidos que no pueden correr scripts en <strong>python</strong> pero si permiten acceso remoto a la base de datos en MySQL, como mi anterior <a href="http://www.hostsuar.com/">hosting</a> (quede muy satisfecho). Pero también puede ser ejecutado de manera local, en hosting compartidos (que soporten python), en <a href="http://es.wikipedia.org/wiki/Servidor_virtual_privado">VPS</a> y en servidores dedicados. No muchos hostings compartidos permiten la ejecución de scripts en python, ni siquiera ssh o acceso remoto a la BDD. Afortunadamente, mi hosting actual, <strong><a href="http://www.webfaction.com/?affiliate=joedicastro">Webfaction</a></strong>, me permite todas esas posibilidades y no es ningún un problema. De hecho es el mejor hosting compartido que haya probado nunca y uno de los mejores del mercado, porque su manera de trabajar es única y es lo más parecido a un VPS, pero con una facilidad para administrar las tareas más cotidianas apabullante. Eso si, es distinto a todos los demás y necesita uno adaptarse a su manera de hacer las cosas, pero luego ya no quieres saber nada de otros hosting compartidos. Si además quieres trabajar con ruby o python, pocos puede competir con su flexibilidad, lo que me hizo decidirme por él.</p> <h2 id="el_script_ban_drupal_spammerspy">El script, ban_drupal_spammers.py</h2> <p>El script (siempre la versión más actualizada), los ficheros auxiliares y las instrucciones de como emplearlos, pueden ser encontrados en mi repositorio que se encuentra alojado en <a href="http://github.com/joedicastro/ban-drupal-spammers">github</a>.Y donde tambien se puede encontrar el script python que empleo para recoger los datos que se muestran en el mapa de este árticulo.</p> <p>El código de <strong>ban_drupal_spammers.py</strong> es el siguiente:</p> <div class="codehilite"><pre><span class="c">#!/usr/bin/env python</span> <span class="c"># -*- coding: utf8 -*-</span> <span class="sd">&quot;&quot;&quot;</span> <span class="sd"> ban drupal spammers.py: ban spammers in Drupal with Mollom&#39;s aid</span> <span class="sd">&quot;&quot;&quot;</span> <span class="c">#===============================================================================</span> <span class="c"># This Script uses the Mollom reports in Drupal for ban spammers&#39; ips and</span> <span class="c"># reduce the bandwith usage in the website.</span> <span class="c">#===============================================================================</span> <span class="c">#===============================================================================</span> <span class="c"># Copyright 2010 joe di castro &lt;joe@joedicastro.com&gt;</span> <span class="c">#</span> <span class="c"># This program is free software: you can redistribute it and/or modify</span> <span class="c"># it under the terms of the GNU General Public License as published by</span> <span class="c"># the Free Software Foundation, either version 3 of the License, or</span> <span class="c"># (at your option) any later version.</span> <span class="c">#</span> <span class="c"># This program is distributed in the hope that it will be useful,</span> <span class="c"># but WITHOUT ANY WARRANTY; without even the implied warranty of</span> <span class="c"># MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the</span> <span class="c"># GNU General Public License for more details.</span> <span class="c">#</span> <span class="c"># You should have received a copy of the GNU General Public License</span> <span class="c"># along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</span> <span class="c">#</span> <span class="c">#===============================================================================</span> <span class="n">__author__</span> <span class="o">=</span> <span class="s">&quot;joe di castro - joe@joedicastro.com&quot;</span> <span class="n">__license__</span> <span class="o">=</span> <span class="s">&quot;GNU General Public License version 3&quot;</span> <span class="n">__date__</span> <span class="o">=</span> <span class="s">&quot;15/05/2010&quot;</span> <span class="n">__version__</span> <span class="o">=</span> <span class="s">&quot;0.52&quot;</span> <span class="k">try</span><span class="p">:</span> <span class="kn">import</span> <span class="nn">sys</span> <span class="kn">import</span> <span class="nn">os</span> <span class="kn">import</span> <span class="nn">time</span> <span class="kn">import</span> <span class="nn">base64</span> <span class="kn">import</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="nn">MySQLdb</span> <span class="kn">import</span> <span class="nn">pygeoip</span> <span class="kn">import</span> <span class="nn">logger</span> <span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span> <span class="c"># Checks the installation of the necessary python modules</span> <span class="k">print</span><span class="p">((</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="s">&quot;An error found importing one module:&quot;</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">exc_info</span><span class="p">()[</span><span class="mi">1</span><span class="p">]),</span> <span class="s">&quot;You need to install it&quot;</span><span class="p">,</span> <span class="s">&quot;Exit...&quot;</span><span class="p">]))</span> <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">)</span> <span class="k">def</span> <span class="nf">connect_db</span><span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="n">user</span><span class="p">,</span> <span class="n">pass_</span><span class="p">,</span> <span class="n">db</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">3306</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Connect to MySQL database.&quot;&quot;&quot;</span> <span class="k">try</span><span class="p">:</span> <span class="n">data_base</span> <span class="o">=</span> <span class="n">MySQLdb</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="n">host</span><span class="p">,</span> <span class="n">user</span><span class="o">=</span><span class="n">user</span><span class="p">,</span> <span class="n">passwd</span><span class="o">=</span><span class="n">pass_</span><span class="p">,</span> <span class="n">db</span><span class="o">=</span><span class="n">db</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="n">port</span><span class="p">,</span> <span class="n">client_flag</span><span class="o">=</span><span class="mi">65536</span><span class="p">)</span> <span class="c"># flag 65536 is to allow multiple statements in a single string, equals</span> <span class="c"># to CLIENT_MULTI_STATEMENTS</span> <span class="k">except</span> <span class="n">MySQLdb</span><span class="o">.</span><span class="n">OperationalError</span><span class="p">:</span> <span class="k">print</span><span class="p">(</span><span class="s">&quot;Database connection fails, check that you gave the right &quot;</span> <span class="s">&quot;credentials to access the database{0}Exit...&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">))</span> <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">)</span> <span class="k">return</span> <span class="n">data_base</span> <span class="k">def</span> <span class="nf">select</span><span class="p">(</span><span class="n">curs</span><span class="p">,</span> <span class="n">sql</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Runs a SQL SELECT query and returns a tuple as output.&quot;&quot;&quot;</span> <span class="n">curs</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="n">sql</span><span class="p">)</span> <span class="k">return</span> <span class="n">curs</span><span class="o">.</span><span class="n">fetchall</span><span class="p">()</span> <span class="k">def</span> <span class="nf">alter_table</span><span class="p">(</span><span class="n">curs</span><span class="p">,</span> <span class="n">db_table</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Create the aux field in the table if no exists, else do nothing.&quot;&quot;&quot;</span> <span class="n">database_string</span> <span class="o">=</span> <span class="s">&quot;&quot;&quot;</span> <span class="s"> ALTER TABLE {0}</span> <span class="s"> ADD timestamp INT(11) NOT NULL DEFAULT &#39;0&#39;;</span> <span class="s"> &quot;&quot;&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">db_table</span><span class="p">)</span> <span class="k">try</span><span class="p">:</span> <span class="n">curs</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="n">database_string</span><span class="p">)</span> <span class="k">return</span> <span class="s">&quot;Aux Field &#39;timestamp&#39; in table &#39;{0}&#39; created.&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">db_table</span><span class="p">)</span> <span class="k">except</span> <span class="n">MySQLdb</span><span class="o">.</span><span class="n">OperationalError</span><span class="p">:</span> <span class="k">print</span> <span class="p">(</span><span class="s">&quot;Can&#39;t create the aux field, seems this exists previously.&quot;</span><span class="p">)</span> <span class="c"># This output is not reported in the log, it will be repetitive.</span> <span class="k">def</span> <span class="nf">ins_qstr</span><span class="p">(</span><span class="n">q_mask</span><span class="p">,</span> <span class="n">q_timestamp</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Create a SQL INSERT query string for the given ip.&quot;&quot;&quot;</span> <span class="n">iqstr</span> <span class="o">=</span> <span class="s">&quot;&quot;&quot;</span> <span class="s"> INSERT INTO `access`</span> <span class="s"> (mask, type, status, timestamp)</span> <span class="s"> VALUES (&#39;{0}&#39;, &#39;host&#39;, &#39;0&#39;, {1});{2}</span> <span class="s"> &quot;&quot;&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">q_mask</span><span class="p">,</span> <span class="n">q_timestamp</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">)</span> <span class="k">return</span> <span class="n">iqstr</span> <span class="k">def</span> <span class="nf">del_qstr</span><span class="p">(</span><span class="n">q_timestamp</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Create a DELETE query string for the given timestamp.&quot;&quot;&quot;</span> <span class="n">dqstr</span> <span class="o">=</span> <span class="s">&quot;&quot;&quot;</span> <span class="s"> DELETE FROM access</span> <span class="s"> WHERE timestamp=&#39;{0}&#39;;{1}</span> <span class="s"> &quot;&quot;&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">q_timestamp</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">)</span> <span class="k">return</span> <span class="n">dqstr</span> <span class="k">def</span> <span class="nf">ip_and_country</span><span class="p">(</span><span class="n">l_ips</span><span class="p">,</span> <span class="n">geo</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Create the log lines about the ips and their countries.&quot;&quot;&quot;</span> <span class="n">output</span> <span class="o">=</span> <span class="bp">None</span> <span class="k">if</span> <span class="n">l_ips</span><span class="p">:</span> <span class="n">total</span> <span class="o">=</span> <span class="s">&quot;{0} IPs&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">l_ips</span><span class="p">))</span> <span class="n">ips_and_countries</span> <span class="o">=</span> <span class="p">[(</span><span class="n">geo</span><span class="o">.</span><span class="n">country_name_by_addr</span><span class="p">(</span><span class="n">l</span><span class="p">),</span> <span class="n">l</span><span class="p">)</span> <span class="k">for</span> <span class="n">l</span> <span class="ow">in</span> <span class="n">l_ips</span><span class="p">]</span> <span class="n">ips</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="s">&#39;{0:16} {1}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">i</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">i</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">ips_and_countries</span><span class="p">)])</span> <span class="n">output</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="n">total</span><span class="p">,</span> <span class="s">&#39;&#39;</span><span class="p">,</span> <span class="n">ips</span><span class="p">])</span> <span class="k">return</span> <span class="n">output</span> <span class="k">def</span> <span class="nf">renew_geoip</span><span class="p">(</span><span class="n">gip_path</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Check if the geoip data file is too old.&quot;&quot;&quot;</span> <span class="n">out_str</span> <span class="o">=</span> <span class="s">&#39;&#39;</span> <span class="n">gz_file</span> <span class="o">=</span> <span class="p">(</span><span class="s">&quot;http://geolite.maxmind.com/download/geoip/database/&quot;</span> <span class="s">&quot;GeoLiteCountry/GeoIP.dat.gz&quot;</span><span class="p">)</span> <span class="n">web_url</span> <span class="o">=</span> <span class="s">&quot;http://www.maxmind.com/app/geolitecountry&quot;</span> <span class="n">geoip_file_date</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">getmtime</span><span class="p">(</span><span class="n">gip_path</span><span class="p">)</span> <span class="k">if</span> <span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">geoip_file_date</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">2592000</span><span class="p">:</span> <span class="c"># 2592000s = 30 days</span> <span class="n">out_str</span> <span class="o">+=</span> <span class="p">(</span><span class="s">&quot;Your GeoIP data file* is older than 30 days!{0}{0}&quot;</span> <span class="s">&quot;You can look for a new version in:{0}{1}{0}or{0}{2}{0}{0}&quot;</span> <span class="s">&quot; *{3}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">linesep</span><span class="p">,</span> <span class="n">gz_file</span><span class="p">,</span> <span class="n">web_url</span><span class="p">,</span> <span class="n">gip_path</span><span class="p">))</span> <span class="k">return</span> <span class="n">out_str</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;main section&quot;&quot;&quot;</span> <span class="c">#===============================================================================</span> <span class="c"># SCRIPT PARAMATERS</span> <span class="c">#===============================================================================</span> <span class="c"># database host, name or ip (&#39;localhost&#39; by default)</span> <span class="n">host</span> <span class="o">=</span> <span class="s">&#39;localhost&#39;</span> <span class="c"># database user name (&#39;root&#39; by default)</span> <span class="n">user</span> <span class="o">=</span> <span class="s">&#39;root&#39;</span> <span class="c"># database password, with a minimum security measure, encoded by base64</span> <span class="c"># (&#39;password&#39; by default)</span> <span class="n">password</span> <span class="o">=</span> <span class="n">base64</span><span class="o">.</span><span class="n">b64decode</span><span class="p">(</span><span class="s">&#39;cGFzc3dvcmQ=&#39;</span><span class="p">)</span> <span class="c"># database name (&#39;database&#39; by default)</span> <span class="n">database</span> <span class="o">=</span> <span class="s">&#39;database&#39;</span> <span class="c"># path to geolocation data file GeoIP.dat</span> <span class="n">geoip_path</span> <span class="o">=</span> <span class="s">&#39;/your/path/to/file/GeoIP.dat&#39;</span> <span class="c"># mail server, smtp protocol, to send the log (&#39;localhost&#39; by default)</span> <span class="n">smtp_server</span> <span class="o">=</span> <span class="s">&#39;localhost&#39;</span> <span class="c"># sender&#39;s email address (&#39;&#39; by default)</span> <span class="n">from_addr</span> <span class="o">=</span> <span class="s">&#39;&#39;</span> <span class="c"># a list of receiver(s)&#39; email addresses ([&#39;&#39;] by default)</span> <span class="n">to_addrs</span> <span class="o">=</span> <span class="p">[</span><span class="s">&#39;&#39;</span><span class="p">]</span> <span class="c"># smtp server user (&#39;&#39; by default)</span> <span class="n">smtp_user</span> <span class="o">=</span> <span class="s">&#39;&#39;</span> <span class="c"># smtp server password, with a minimum security measure, encoded by base64</span> <span class="c"># (&#39;password&#39; by default)</span> <span class="n">smtp_pass</span> <span class="o">=</span> <span class="n">base64</span><span class="o">.</span><span class="n">b64decode</span><span class="p">(</span><span class="s">&#39;cGFzc3dvcmQ=&#39;</span><span class="p">)</span> <span class="c"># set the perfomace threshold (number of banned ips) for you site</span> <span class="n">threshold</span> <span class="o">=</span> <span class="mi">2000</span> <span class="c">#===============================================================================</span> <span class="c"># END PARAMETERS</span> <span class="c">#===============================================================================</span> <span class="c"># Initialize the log</span> <span class="n">log</span> <span class="o">=</span> <span class="n">logger</span><span class="o">.</span><span class="n">Logger</span><span class="p">()</span> <span class="c"># log the header</span> <span class="n">url</span> <span class="o">=</span> <span class="s">&#39;http://joedicastro.com&#39;</span> <span class="n">connected</span> <span class="o">=</span> <span class="s">&#39;Connected to {0} in {1} as {2}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">database</span><span class="p">,</span> <span class="n">host</span><span class="p">,</span> <span class="n">user</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">header</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">connected</span><span class="p">)</span> <span class="c"># log the start time</span> <span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">&#39;Start Time&#39;</span><span class="p">)</span> <span class="c"># log the warning about old geolocation data file</span> <span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&#39;The GeoIp.dat file is old&#39;</span><span class="p">,</span> <span class="n">renew_geoip</span><span class="p">(</span><span class="n">geoip_path</span><span class="p">))</span> <span class="c"># connect to database, create the cursors &amp; initialize the geolocation info</span> <span class="n">mysql_db</span> <span class="o">=</span> <span class="n">connect_db</span><span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="n">user</span><span class="p">,</span> <span class="n">password</span><span class="p">,</span> <span class="n">database</span><span class="p">)</span> <span class="n">cursor</span> <span class="o">=</span> <span class="n">mysql_db</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span> <span class="n">dict_cursor</span> <span class="o">=</span> <span class="n">mysql_db</span><span class="o">.</span><span class="n">cursor</span><span class="p">(</span><span class="n">MySQLdb</span><span class="o">.</span><span class="n">cursors</span><span class="o">.</span><span class="n">DictCursor</span><span class="p">)</span> <span class="n">gip</span> <span class="o">=</span> <span class="n">pygeoip</span><span class="o">.</span><span class="n">GeoIP</span><span class="p">(</span><span class="n">geoip_path</span><span class="p">)</span> <span class="c"># optimize the database (instead a cron task in the server)</span> <span class="n">all_tables</span> <span class="o">=</span> <span class="p">[</span><span class="n">tabl</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="k">for</span> <span class="n">tabl</span> <span class="ow">in</span> <span class="n">select</span><span class="p">(</span><span class="n">cursor</span><span class="p">,</span> <span class="s">&quot;SHOW TABLES&quot;</span><span class="p">)]</span> <span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s">&#39;OPTIMIZE TABLE {0}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="s">&#39;, &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">all_tables</span><span class="p">)))</span> <span class="c"># Adds the timestamp field to the &#39;access&#39; table if no exists</span> <span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&#39;New aux table field created&#39;</span><span class="p">,</span> <span class="n">alter_table</span><span class="p">(</span><span class="n">cursor</span><span class="p">,</span> <span class="s">&#39;access&#39;</span><span class="p">))</span> <span class="c"># Query the database and obtain the result. We collect the &#39;access&#39; table</span> <span class="c"># ips and ips from spammers reported by Mollom in &#39;watchdog&#39; table</span> <span class="c"># access = ({&#39;timestamp&#39;:timestamp, &#39;mask&#39;: &#39;ip&#39;}, ...)</span> <span class="c"># mollom = ({&#39;timestamp&#39;:timestamp, &#39;mask&#39;: &#39;ip&#39;}, ...)</span> <span class="n">access</span> <span class="o">=</span> <span class="n">select</span><span class="p">(</span><span class="n">dict_cursor</span><span class="p">,</span> <span class="s">&quot;&quot;&quot;SELECT mask, timestamp FROM access&quot;&quot;&quot;</span><span class="p">)</span> <span class="n">mollom</span> <span class="o">=</span> <span class="n">select</span><span class="p">(</span><span class="n">dict_cursor</span><span class="p">,</span> <span class="s">&quot;&quot;&quot;SELECT hostname as mask, timestamp</span> <span class="s"> FROM `watchdog`</span> <span class="s"> WHERE `type` LIKE &#39;%mollom%&#39;</span> <span class="s"> AND `message` LIKE &#39;</span><span class="si">%s</span><span class="s">pam:%&#39;&quot;&quot;&quot;</span><span class="p">)</span> <span class="c"># From the &#39;access&#39; ips, select the ips blocked by this script from Mollom,</span> <span class="c"># discarding those introduced through the Drupal administration interface</span> <span class="c"># from_access = {&#39;ip&#39;:timestamp, ...}</span> <span class="n">from_access</span> <span class="o">=</span> <span class="p">{}</span> <span class="k">for</span> <span class="n">a_row</span> <span class="ow">in</span> <span class="n">access</span><span class="p">:</span> <span class="k">if</span> <span class="nb">int</span><span class="p">(</span><span class="n">a_row</span><span class="p">[</span><span class="s">&#39;timestamp&#39;</span><span class="p">]):</span> <span class="n">from_access</span><span class="p">[</span><span class="n">a_row</span><span class="p">[</span><span class="s">&#39;mask&#39;</span><span class="p">]]</span> <span class="o">=</span> <span class="n">a_row</span><span class="p">[</span><span class="s">&#39;timestamp&#39;</span><span class="p">]</span> <span class="c"># Here we select the ips that Mollom reported, if there are multiple</span> <span class="c"># occurrences of the same ip, we always choose the most recent</span> <span class="c"># from_mollom = {&#39;ip&#39;:timestamp, ...}</span> <span class="n">from_mollom</span> <span class="o">=</span> <span class="p">{}</span> <span class="k">for</span> <span class="n">m_row</span> <span class="ow">in</span> <span class="n">mollom</span><span class="p">:</span> <span class="k">if</span> <span class="n">m_row</span><span class="p">[</span><span class="s">&#39;mask&#39;</span><span class="p">]</span> <span class="ow">in</span> <span class="n">from_mollom</span><span class="o">.</span><span class="n">keys</span><span class="p">():</span> <span class="k">if</span> <span class="nb">int</span><span class="p">(</span><span class="n">from_mollom</span><span class="p">[</span><span class="n">m_row</span><span class="p">[</span><span class="s">&#39;mask&#39;</span><span class="p">]])</span> <span class="o">&lt;</span> <span class="nb">int</span><span class="p">(</span><span class="n">m_row</span><span class="p">[</span><span class="s">&#39;timestamp&#39;</span><span class="p">]):</span> <span class="n">from_mollom</span><span class="p">[</span><span class="n">m_row</span><span class="p">[</span><span class="s">&#39;mask&#39;</span><span class="p">]]</span> <span class="o">=</span> <span class="n">m_row</span><span class="p">[</span><span class="s">&#39;timestamp&#39;</span><span class="p">]</span> <span class="k">else</span><span class="p">:</span> <span class="n">from_mollom</span><span class="p">[</span><span class="n">m_row</span><span class="p">[</span><span class="s">&#39;mask&#39;</span><span class="p">]]</span> <span class="o">=</span> <span class="n">m_row</span><span class="p">[</span><span class="s">&#39;timestamp&#39;</span><span class="p">]</span> <span class="c"># Now, from these ips, select the IPs of spammers that were not already</span> <span class="c"># banned and generate queries to insert into the &#39;access&#39; table. It&#39;s</span> <span class="c"># necessary to check if some of ips reported through Mollom didn&#39;t be</span> <span class="c"># already banned, because of how the Drupal&#39;s event log works. The optional</span> <span class="c"># core module &quot;Database logging&quot; (which must be enabled to run his script)</span> <span class="c"># is deleting records by the tail (into the &#39;watchdog&#39; table) on each cron</span> <span class="c"># run, according to a maximum limit set in the admin menu. This limit may be</span> <span class="c"># 100, 1000, 10000, 100000, 1000000 records, as determined in the &quot;Loggin</span> <span class="c"># and alerts -&gt; Database logging&quot; menu. Then depending on the record limit</span> <span class="c"># set in the &#39;watchdog&#39; table, the frequency with which you run the cron job</span> <span class="c"># and how often you run this script, it&#39;s very likely that in the previous</span> <span class="c"># query we have returned a number of ips that have not yet eliminated from</span> <span class="c"># the log (&#39;watchdog&#39;), but we have already added to the table of bannedd</span> <span class="c"># ips (&#39;access&#39;). This will avoid duplicate ips on table &#39;access&#39;</span> <span class="c"># ins_ips = [&#39;ip0&#39;, &#39;ip1&#39;, ...]</span> <span class="n">ins_ips</span> <span class="o">=</span> <span class="p">[</span><span class="n">f_ip</span> <span class="k">for</span> <span class="n">f_ip</span> <span class="ow">in</span> <span class="n">from_mollom</span><span class="o">.</span><span class="n">keys</span><span class="p">()</span> <span class="k">if</span> <span class="n">f_ip</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">from_access</span><span class="p">]</span> <span class="n">query_str</span> <span class="o">=</span> <span class="s">&#39;&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">ins_qstr</span><span class="p">(</span><span class="n">i_ip</span><span class="p">,</span> <span class="n">from_mollom</span><span class="p">[</span><span class="n">i_ip</span><span class="p">])</span> <span class="k">for</span> <span class="n">i_ip</span> <span class="ow">in</span> <span class="n">ins_ips</span><span class="p">)</span> <span class="c"># number of banned ips through this script</span> <span class="n">banned_ips</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">from_access</span><span class="p">)</span> <span class="o">+</span> <span class="nb">len</span><span class="p">(</span><span class="n">ins_ips</span><span class="p">)</span> <span class="c"># number of banned ips through Drupal administration interface</span> <span class="n">drupal_banned_ips</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">access</span><span class="p">)</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">from_access</span><span class="p">)</span> <span class="c"># After a certain number of records in the table &#39;access&#39;, the website&#39;s</span> <span class="c"># perfomance deteriorates and from an even larger number, the behavior of</span> <span class="c"># Drupal just become erratic. In the case of the site on which to run this</span> <span class="c"># script, we see a clear loss of performance from the 3000 records and</span> <span class="c"># becomes erratic over 5000. To avoid this unpleasant side effect, and</span> <span class="c"># that cure don&#39;t be worse than the disease, I set a performance threshold</span> <span class="c"># in 2000 records, from which records were removed from the table. If the</span> <span class="c"># number of rows is greater than the performance threshold, we proceed to</span> <span class="c"># calculate the ips to remove, selecting the oldest. The number of ips to</span> <span class="c"># delete will be at least the 30% of &quot;from_access&quot;. Just delete records</span> <span class="c"># inserted through this script, never the inserted via Drupal admin</span> <span class="c"># interface</span> <span class="n">trigger</span> <span class="o">=</span> <span class="nb">bool</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">access</span><span class="p">)</span> <span class="o">&gt;</span> <span class="n">threshold</span><span class="p">)</span> <span class="c"># perfomance threshold</span> <span class="n">del_ips</span><span class="p">,</span> <span class="n">latest</span> <span class="o">=</span> <span class="p">[],</span> <span class="mi">0</span> <span class="c"># ips to delete (if trigger) &amp; latest ip&#39;s date</span> <span class="k">if</span> <span class="n">trigger</span><span class="p">:</span> <span class="c"># Now we&#39;ll group the ips by date. Use the object collections.defauldict</span> <span class="c"># to group the ips in a dictionary of lists (values) of ips by date</span> <span class="c"># (keys)</span> <span class="c"># ips_by_time = {timestamp:[&#39;ip0&#39;, ..], ...}</span> <span class="n">ips_by_time</span> <span class="o">=</span> <span class="n">collections</span><span class="o">.</span><span class="n">defaultdict</span><span class="p">(</span><span class="nb">list</span><span class="p">)</span> <span class="k">for</span> <span class="n">fa_ip</span> <span class="ow">in</span> <span class="n">from_access</span><span class="p">:</span> <span class="n">ips_by_time</span><span class="p">[</span><span class="n">from_access</span><span class="p">[</span><span class="n">fa_ip</span><span class="p">]]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">fa_ip</span><span class="p">)</span> <span class="c"># We selected the oldest ips to have a number of them greater than or</span> <span class="c"># equal to 30% of blocked by this script</span> <span class="k">for</span> <span class="n">ips_date</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">ips_by_time</span><span class="o">.</span><span class="n">keys</span><span class="p">()):</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">del_ips</span><span class="p">)</span> <span class="o">&lt;</span> <span class="p">((</span><span class="nb">len</span><span class="p">(</span><span class="n">from_access</span><span class="p">)</span> <span class="o">*</span> <span class="mi">30</span><span class="p">)</span> <span class="o">/</span> <span class="mi">100</span><span class="p">):</span> <span class="n">query_str</span> <span class="o">+=</span> <span class="n">del_qstr</span><span class="p">(</span><span class="n">ips_date</span><span class="p">)</span> <span class="c"># delete by date, less queries</span> <span class="k">for</span> <span class="n">d_ip</span> <span class="ow">in</span> <span class="n">ips_by_time</span><span class="p">[</span><span class="n">ips_date</span><span class="p">]:</span> <span class="n">del_ips</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">d_ip</span><span class="p">)</span> <span class="n">banned_ips</span> <span class="o">-=</span> <span class="mi">1</span> <span class="k">if</span> <span class="nb">int</span><span class="p">(</span><span class="n">ips_date</span><span class="p">)</span> <span class="o">&gt;</span> <span class="n">latest</span><span class="p">:</span> <span class="n">latest</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">ips_date</span><span class="p">)</span> <span class="n">latest</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s">&#39;%A </span><span class="si">%x</span><span class="s">&#39;</span><span class="p">,</span> <span class="n">time</span><span class="o">.</span><span class="n">localtime</span><span class="p">(</span><span class="n">latest</span><span class="p">))</span> <span class="c"># log spammers&#39; ips deleted from the table</span> <span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&quot;Spammers&#39; Ips deleted&quot;</span><span class="p">,</span> <span class="n">ip_and_country</span><span class="p">(</span><span class="n">del_ips</span><span class="p">,</span> <span class="n">gip</span><span class="p">))</span> <span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&quot;Newest date of deleted IPs&quot;</span><span class="p">,</span> <span class="s">&quot;Date: {0}&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">latest</span><span class="p">))</span> <span class="c"># runs the database query</span> <span class="k">if</span> <span class="n">query_str</span><span class="p">:</span> <span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="n">query_str</span><span class="p">)</span> <span class="c"># close database cursors</span> <span class="n">cursor</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> <span class="n">dict_cursor</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> <span class="c"># log spammers&#39; ips inserted into the table</span> <span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&quot;Spammers&#39; IPs inserted&quot;</span><span class="p">,</span> <span class="n">ip_and_country</span><span class="p">(</span><span class="n">ins_ips</span><span class="p">,</span> <span class="n">gip</span><span class="p">))</span> <span class="c"># log total banned ips by origin</span> <span class="n">log</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="s">&#39;Banned IPs&#39;</span><span class="p">,</span> <span class="p">[</span><span class="s">&#39;Mollom: </span><span class="si">%d</span><span class="s"> IPs&#39;</span> <span class="o">%</span> <span class="n">banned_ips</span><span class="p">,</span> <span class="s">&#39;Drupal: </span><span class="si">%d</span><span class="s"> IPs&#39;</span> <span class="o">%</span> <span class="n">drupal_banned_ips</span><span class="p">])</span> <span class="c"># log the end time</span> <span class="n">log</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="s">&#39;End Time&#39;</span><span class="p">)</span> <span class="c"># send the log by email</span> <span class="n">log</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s">&#39;Ban Drupal Spammers. Ins: {0} Del: {1}&#39;</span><span class="o">.</span> <span class="n">format</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">ins_ips</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">del_ips</span><span class="p">)),</span> <span class="n">send_from</span><span class="o">=</span><span class="n">from_addr</span><span class="p">,</span> <span class="n">dest_to</span><span class="o">=</span><span class="n">to_addrs</span><span class="p">,</span> <span class="n">mail_server</span><span class="o">=</span><span class="n">smtp_server</span><span class="p">,</span> <span class="n">server_user</span><span class="o">=</span><span class="n">smtp_user</span><span class="p">,</span> <span class="n">server_pass</span><span class="o">=</span><span class="n">smtp_pass</span><span class="p">)</span> <span class="c"># write the log to a file</span> <span class="n">log</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span> <span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&quot;__main__&quot;</span><span class="p">:</span> <span class="n">main</span><span class="p">()</span> </pre></div> <br /> <hr /> <h2 id="comentarios_realizados_anteriormente_en_drupal">Comentarios realizados anteriormente en Drupal</h2> <div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;"> <a href="http://inseguridad.org/"><img src="pictures/avtr_jbone.png" height=28 width=28 alt="avatar" title="avatar de bjone"/></a></div> <h3 id="muy_interesante">Muy interesante</h3> <p>por <a href="http://inseguridad.org/">bjone</a> el Jue, 14/10/2010 - 11:40</p> <p>muy interesante... voy a probar el mollom... gracias por la información.</p> <hr /> <div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;"> <img src="pictures/avtr_anonimo.png" height=28 width=28 alt="avatar" title="avatar de anónimo"/></div> <h3 id="preguntita">Preguntita</h3> <p>por Anónimo el Mié, 27/10/2010 - 14:00</p> <p>Estoy armando mi sitio, que poseerá foro y tendré lo que entendí tu llamas hosting compartido. Es decir, me alquilaran espacio de hosting. La pregunta es: ¿las precauciones contra el spam en mi sitio deberán correr exclusivamente por mi cuenta o parte de la pelea la lleva el administrador de hospedaje?</p> <p>Gracias.</p> <hr /> <div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;"> <img src="pictures/avtr_anonimo.png" height=28 width=28 alt="avatar" title="avatar de anónimo"/></div> <h3 id="gracias_termin+_de_leer_el">Gracias, terminé de leer el</h3> <p>por Anónimo el Mié, 27/10/2010 - 14:03</p> <p>Gracias, terminé de leer el artículo y me respondí solo :)</p> <p>Si, parece que deberé hacerme cargo activamente :(.</p> <hr /> <div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;"> <a href="http://joedicastro.com"><img src="pictures/avtr_joedicastro.png" height=28 width=28 alt="avatar" title="avatar de joedicastro"/></a></div> <h3 id="si_efectivamente_as+_es">Si, efectivamente así es,</h3> <p>por <a href="http://joedicastro.com">joe di castro</a> el Mié, 27/10/2010 - 20:04</p> <p>Si, efectivamente así es, pero además es así también en los servidores administrados y en los servidores dedicados.</p> <p>A lo sumo se dedican a administrar el hard, el sistema operativo y el sistema base para la web (Apache, Mysql/PostgreSQL, NGINX, PHP, ...). Pero en cuanto a la aplicación web en si misma y todo lo que a ella atañe, es la parte que te toca. Luego dependiendo de según donde acabes teniendo el hosting, en una situación determinada -de un ataque a la web por ejemplo- pueden desde echarte una mano de buena fe hasta exigirte que lo arregles o te cierran la cuenta (si no te la cierran directamente). Depende de con quien des y de la circunstancia que se dé, en la mayoría de hostings compartidos no se paran mucho a dar soporte a este tipo de situaciones, y si a penalizar a los que no gestionan correctamente sus sitios.</p> <p>De todos modos, si eliges un buen sistema de foros y aplicas un buen sistema antispam, no deberías tener excesivos problemas y acabaras aprendiendo mucho por el camino. La mayoría de los problemas vienen por la desidia y la poca preocupación de los administradores de webs por estos temas.</p> <p>Saludos y suerte con el foro.</p> <hr /> <div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;"> <a href="http://sigt.net/"><img src="pictures/avtr_armonth.png" height=28 width=28 alt="avatar" title="avatar de armonth"/></a></div> <h3 id="sobre_el_spamicide">Sobre el Spamicide</h3> <p>por <a href="http://sigt.net/archivo/sistema-antispam-del-campo-oculto-para-wordpress.xhtml">Armonth</a> el Sáb, 30/10/2010 - 19:59 </p> <p>Buenas, yo ese sistema lo conocía por el de "campo oculto" y lo comenté hace ya más de 3 años en SigT (te he enlazado mi nombre al artículo) con la implementación.</p> <p>Un detalle que cabe mencionar es que es mucho más efectivo poner como campo "te echo atrás por spammer" el campo correspondiente a "nombre" originalmente en la implementación de WordPress (author) y poner un nuevo author que no poner un campo nuevo a ver si lo rellenan.</p> <p>La mayoría de scripts para spamear no están hechos para rellenar todos los campos, están hechos para rellenar el nombre (author), email, url y comment... ignorando otros campos...</p> <hr /> <div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;"> <a href="http://joedicastro.com"><img src="pictures/avtr_joedicastro.png" height=28 width=28 alt="avatar" title="avatar de joedicastro"/></a></div> <h3 id="si_desde_luego_es_bastante">Si, desde luego es bastante</h3> <p>por <a href="http://joedicastro.com">joe di castro</a> el Sáb, 30/10/2010 - 22:05</p> <p>Si, desde luego es bastante más lógico hacerlo de esa manera, engañando doblemente a los spammers. De todos modos el modulo Spamicide te deja renombrar el campo como quieras y se puede hacer pasar por uno de esos campos sin problemas, por lo que se puede hacer lo que comentas.</p> <p>Ya conocía el articulo que enlazas, hace mucho tiempo que te sigo :), aunque los dos estamos muy inactivos últimamente.</p> <p>Saludos</p> <hr /> <div style="float:right; padding:2px; border: 1px solid #ccc; height:28px;"> <a href="http://sigt.net/"><img src="pictures/avtr_armonth.png" height=28 width=28 alt="avatar" title="avatar de armonth"/></a></div> <h3 id="bueno">Bueno</h3> <p>por <a href="http://sigt.net/">Armonth</a> el Sáb, 30/10/2010 - 22:57 </p> <p>Bueno, yo estoy "inactivo" de sigt que no de otro proyecto aún no revelado y que dejé los MMO ;P</p> <div class="footnote"> <hr /> <ol> <li id="fn:1"> <p>Las estadísticas de uso emplean los datos de drupal.org a 12 de Octubre de 2010&#160;<a href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">&#8617;</a></p> </li> </ol> </div>