Lecciones aprendidas desarrollando GitHub pages

Crecimiento GitHub Pages Recientemente se ha cumplido un año que estamos escribiendo en este sitio Cuata Etapa. Me viene bien recordarlo porque recientemente leíamos Eight lessons learned hacking on GitHub Pages for six months en el que nos cuentan algunos principios generales aprendidos desarrollando el servicio.

El primero de ellos (previo, más bien) es ser usuario de tu propio servicio para conocerlo bien, conocer sus virtudes, encontrar sus defectos…

Y luego la lista:

  • Test, test, and then test again

Desarrollo basado en pruebas, para estar seguros de que cuando se introducen cambios nada va a fallar.

  • Use public APIs, and when they don’t exist, build them

En la línea del principio ‘previo’ no utilizar componentes secretos o privados y si se detecta la necesidad de algo que no existe, ponerlo a disposición de los usuarios.

  • Let the user make the breaking change

Permitir a los usuarios que controlen los cambios importantes que pueden afectar a sus sitios.

  • In every communication, provide an out

Cuando se comunican los errores se informa al usuario no sólo del problema sino también de dónde tiene que mirar para solucionar el fallo.

  • Optimize for your ideal use case, not the most common

No hay que olvidar que el servicio es para dar a conocer los proyectos. Eso debería ser lo más fácil de hacer, independientemente de lo que la gente esté haciendo.

  • Successful efforts are cross-team efforts

Como tantos proyectos, no son cosa sólo de un equipo que se encarga de ellos, sino que, seguramente, hará falta hablar y trabajar con otros equipos.

  • Match user expectations, then exceed them

Que los usuarios tengan lo que esperaban del proyecto, pero mejor. No hay mucho que añadir aquí.

  • It makes business sense to support open source

Una parte de su producto está basado en Jekyll, que es software libre y que ha ganado su propio espacio como producto independiente, que siguen apoyando y en el que siguen participando.

Condiciones de carrera en algunos sitios famosos

Carrera Cuando explicamos las condiciones de carrera siempre nos quedamos con la duda de si es un tema que ya todo el mundo conoce y también si no sería mejor dedicar ese tiempo a explicar otras cosas. De pronto, una entrada como Race conditions on Facebook, DigitalOcean and others (fixed) nos demuestra que más vale no olvidarlos.

Las condiciones de carrera ocurren en sistemas concurrentes, cuando dos actores acceden a o modifican una determinada información y el tiempo y la forma de acceso son importantes, porque lo que haga uno de ellos afecta a lo que el otro debería poder hacer. Se puede ver una explicación en Practical Race Condition Vulnerabilities in Web Applications y también en Condición de Carrera

En la entrada se comenta sobre varios fallos de sitios de alto nivel (que, al final, es lo que hace que valga la pena comentarlos aquí). Son secuencias de pasos para fallos de Facebook (en este caso aumentar el número de revisiones de una página o crear varios nombres de usuario para una cuenta), DigitalOcean (añadir crédito a nuestra cuenta), y LastPass (con un fallo parecido al de DigitalOcean).

Errores al final

Montaña La mente humana es curiosa. Al principio de un proyecto tomamos algunas partes con entusiasmo, pero como el final está lejos estamos relajados, exploramos, jugamos, modificamos, probamos cosas… Cuando se acerca el final vamos yendo más a lo concreto, tratando de cumplir con los hitos y ajustándonos a lo que hay que hacer. Y cuando llega el momento del cierre es un ‘sálvese quien pueda’ y dejamos algunas partes pilldas con hilos, en espera de evoluciones posteriores y cosas que haremos (tal vez) cuando vengan tiempos mejores y el proyecto esté entregado. Por lo visto también, cuando llegamos al final y todo está yendo más o menos bien perdemos tensión, creemos que todo lo tenemos controlado y nos volvemos perezosos y bajamos la guardia, llegando a ser más descuidados y confiados.

En The Last Line Effect hablan de algo que está ligeramente relacionado, y por eso lo llaman el efecto de la última línea:

I have studied numbers of errors caused by using the Copy-Paste method and can assure you that programmers most often tend to make mistakes in the last fragment of a homogeneous code block.

En este caso el autor estudia el efecto asociado a copiar bloques de instrucciones y encontrar que la probabilidad de cometer un error en el último bloque de código copiado es cuatro veces mayor que en cualquier otro bloque:

The probability of making a mistake in the last pasted block of code is 4 times higher than in any other block.

La idea es que los programadores copian y pegan framgentos de código que hacen cosas parecidas, y luego los modifican:

When writing program code, programmers often have to write a series of similar constructs. Typing the same code several times is boring and inefficient. That’s why they use the Copy-Paste method: a code fragment is copied and pasted several times with further editing. Everyone knows what is bad about this method: you risk easily forgetting to change something in the pasted lines and thus giving birth to errors. Unfortunately, there is often no better alternative to be found.

En este caso el problema sería parecido al del final de proyecto al que aludíamos en la introducción: copio, pego, modifico, ya tengo el problema casi resuelto, y de pronto estoy menos atento, así que me olvido de finalizar la cosa.

Por lo visto, es algo que también les pasa a los montañeros que, por lo visto, también es más probable que tengan un accidente cuando están finalizando la ascensión.

Añadiendo filtros de correo a mi sistema con sievelib

Laberinto Otra tarea importante que necesitamos en el correo es el filtrado: tener en nuestra carpeta principal sólo los mensajes que pueden ser importantes y llevar a otras carpetas secundarias otros avisos, como mensajes de listas de correo y cosas así. A veces es bueno apartar algunos mensajes importantes que no podemos atender cuando estamos fuera de la oficina: cuando volvamos a lo mejor ya no los vemos como nuevos y no les dedicaremos la atención necesaria.

Como decíamos el otro día el sistema se basa en Zentyal, que incorpora un sistema de filtrado de correo (mediante el webmail y los filtros basados en el protocolo ManageSieve). El problema (para mi) es que no soy usuario habitual del correo web porque prefiero otros clientes y no me resultaba cómodo tener que lanzarlo para añadir un filtro, así que anduve investigando para averiguar si se podría utilizar alguna alternativa. Mi opción preferida es un programa de línea de instrucciones.

Encontré sievelib y estuve haciendo algunas pruebas. No estoy seguro de haber manejado adecuadamente la biblioteca: yo utilizo básicamente dos tipos de reglas, las de almacenar en alguna carpeta concreta, y las de redigir a otra cuenta de correo (por ejemplo ‘newsletters’ en html que se leen mejor en GMail -del que también soy usuario). Por este motivo, sólo me he concentrado en estas reglas y ni siquiera estoy seguro de haberlo hecho en toda su generalidad. El resultado está en addToSieve.py en el estado de la versión que se comenta aquí.

El servidor de correo almacena las reglas en ficheros y hace una pequeña gestión que le permite saber cuál de ellos está activo, así que hay que conectarse e identificarse y autentificarse. Habremos leído los datos de configuración de manera análoga a cómo hacíamos con el servidor IMAP como veíamos en Jugando con IMAP en Python y seleccionar el ‘script’ que queremos modificar y analizarlo:

from sievelib.managesieve import Client

...

        c = Client(SERVER)
        c.connect(USER,PASSWORD, starttls=True, authmech="PLAIN")

...

        script = c.getscript('sogo')
        p = Parser()
        p.parse(script)

Para crear la regla vamos a basarnos en el mensaje que queremos filtrar. Mostramos los 15 mensajes más recientes y el usuario elije uno de ellos:

def selectMessage(M):
	M.select()
	data=M.sort('ARRIVAL', 'UTF-8', 'ALL')
	if (data[0]=='OK'):
		j=0
		msg_data=[]
		messages=data[1][0].split(' ')
		for i in messages[-15:]:
			typ, msg_data_fetch = M.fetch(i, '(BODY.PEEK[HEADER.FIELDS (From Sender To Subject List-Id)])')
			for response_part in msg_data_fetch:
				if isinstance(response_part, tuple):
					msg = email.message_from_string(response_part[1])
					msg_data.append(msg)
					print "%2d) %4s %20s %40s" %(j,i,msg['From'][:20],msg['Subject'][:40])
					j=j+1
		msg_number = raw_input("Which message? ")
		return msg_data[int(msg_number)] #messages[-10+int(msg_number)-1]
	else:	
		return 0

Creo que no hay mucho que comentar en este código, simplemente ordenamos los mensajes por orden de llegada y mostramos algunos campos de los 15 últimos (utilizamos BODY.PEEK para que no se marquen como leídos los mensajes que no lo estuvieran). Para extraer las cabeceras relevantes se utiliza email.message_from_string. Si todo va bien, devolvemos el mensaje return msg_data[int(msg_number)].

Ahora que sabemos qué mensaje vamos a utilizar para el filtrado elegimos qué cabecera es la que utilizaremos para filtrar

	(keyword, textHeader) = selectHeaderAuto(M, msg)

Nos interesan las siguientes cabeceras (al menos por ahora, esto puede ir evolucionando):

msgHeaders=['List-Id', 'From', 'Sender','Subject','To', 'X-Original-To']

Además prefiero filtrar por List-Id siempre que sea posible, así que en selectHeaderAuto tenemos:

	
if msg.has_key('List-Id'): 
		return ('List-Id', msg['List-Id'][msg['List-Id'].find('<')+1:-1])

Notar que hemos ‘limpiado’ el contenido para eliminar todo lo que hay antes y después de los ángulos. En caso de que no tenga esta cabecera, mostramos las otras con este código, que tiene en cuenta que algunas de ellas pueden no estar presentes:

	
		for header in msgHeaders:
			if msg.has_key(header):
				print i," ) ", header, msg[header]
			i = i + 1
		header_num=raw_input("Select header: ")
		

Tomamos la cabecera y el texto correspondiente:

	
		header=msgHeaders[int(header_num)-1]
		textHeader=msg[msgHeaders[int(header_num)-1]]

Y ahora lo limpiamos (si es una dirección de correo contendrá ángulos, y si es un asunto ‘Subject’ a veces contiene corchetes, que también nos pueden venir bien para filtrar).

	
		pos = textHeader.find('<')
		if (pos>=0):
			textHeader=textHeader[pos+1:textHeader.find('>',pos+1)]
		else:
			pos = textHeader.find('[')
			textHeader=textHeader[pos+1:textHeader.find(']',pos+1)]

		pos = textHeader.find('<')
		if (pos>=0):
			textHeader=textHeader[pos+1:textHeader.find('>',pos+1)]
		else:
			pos = textHeader.find('[')
			textHeader=textHeader[pos+1:textHeader.find(']',pos+1)]
		return (header, textHeader)

Con estas cabeceras ahora tenemos que decidir la acción que realizaremos con los mensajes que coindidan con las cabeceras seleccionadas. De esto se ocupa la función selectAction. Esta función nos ofrece como acciones posibles las que ya tengamos en los filtros (carpetas que ya hemos utilizado, sievelib.commands.FileintoCommand, y redirecciones existentes, sievelib.commands.RedirectCommand), mostrando sus valores relevantes:

	
	i = 1
	for r in p.result:
		if r.children:
			if (type(r.children[0]) == sievelib.commands.FileintoCommand):
				print i, ") Folder   ", r.children[0]['mailbox']
			elif (type(r.children[0]) == sievelib.commands.RedirectCommand):
				print i, ") Redirect ", r.children[0]['address']
			else:
				print i, ") Not implented ", type(r.children[0])
		else:
			print  i, ") Not implented ", type(r)
	

Cada resultado es una regla, que tiene como ‘hijo’ la acción y la carpeta o la dirección donde se reenvía (en ambos casos puede haber varias acciones, como veremos luego). También indica las reglas que no han sido contempladas por el programa y que, por lo tanto no tenemos posibilidad de añadir (abriendo la puerta a futuras mejoras). Finalmente, se ofrecen las opciones de añadir carpetas o redirecciones nuevas:

	
	print i, ") New folder "
	print i+1, ") New redirection"

Si reutilizamos alguna acción, simplemente copiamos lo que ya teníamos, añdiendo las carpetas o las redirecciones que haya y terminando con el stop que hace que no se analicen más reglas:

	
		action=p.result[int(option)-1].children

		for i in action:
			print i.arguments
			if i.arguments.has_key('mailbox'):
				actions.append(("fileinto",i.arguments['mailbox']))
			elif i.arguments.has_key('address'):
				actions.append(("redirect",i.arguments['address']))
			else:	
				actions.append(("stop",))
	

En el caso de que queramos utilizar una carpeta diferente, solicitamos el nombrey comprobamos que la carpeta existe (podríamos querer una carpeta que no existe, pero casi nunca es el caso, así que no contemplamos esa opción; esto nos permitirá evitar errores al teclear). Para esto es necesario estar autentificado con el servidor IMAP:

	
		folder= raw_input("Name of the folder: ")
		print "Name ", folder
		if (doFolderExist(folder,M)[0]!='OK'):

La carpeta (sólo una) se añade así:

	
			actions.append(("fileinto", folder))
			actions.append(("stop",))

En el caso de redirecciones la verificación se hace mediante una pregunta explícita:

	
		redir= raw_input("Redirection to: ")
		print "Name ", redir
		itsOK= raw_input("It's ok? (y/n)")
		if (itsOK!='y'):

Y en caso de que la validemos, añadimos la acción correspondiente:

	
			actions.append(("redirect", redir))
			actions.append(("stop",))

Aunque hemos seleccionado una cabecera para filtrar y la hemos limpiado es posible que no queramos utilizar la cabecera completa (ejemplo típico: en lugar de una dirección de correo, un dominio o una parte del usuario de correo) así que ofrecemos al usuario la posibilidad de escribir lo que considere oportuno:

	print "Filter: (header) ", keyword,", (text) ", textHeader
	filterCond = raw_input("Text for selection (empty for all): ")

	if not filterCond:
		filterCond = textHeader

Ahora construimos la regla:

	conditions=[]
	conditions.append((keyword, ":contains", filterCond))

Y creamos un filtro con ella (lo ideal sería añadirla al correspondiente si ya existía pero eso será objeto de una entrada posterior, tal vez) y lo almacenamos en un fichero (seguro que esta parte podría evitarse, pero no tengo claro cómo hacerlo):

	fs = FiltersSet("test")
	fs.addfilter("",conditions,actions)
	fs.tosieve(open('/tmp/kkSieve','w'))

Lo abrimos para analizarlo igual que el original:

	p2=Parser()
	p2.parse(open('/tmp/kkSieve','r').read())
	lenP2 = len(p2.result)

Y añadimos las reglas a las que teníamos, para finalizar escribiéndolas en un fichero:

	p.result.append(p2.result[lenP2-1])	

	fSieve=open('/tmp/kkSieve','w')
	for r in p.result:
		r.tosieve(0,fSieve)
	fSieve.close()

Guardamos el fichero original para tener un histórico y también como copia de seguridad por si algo va mal:

	name = time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime())
	c.putscript(name+'sogo',script)

Finalmente, ponemos el nuevo fichero como fichero de reglas de nuestra criba (‘sieve’):

	fSieve=open('/tmp/kkSieve','r')
	if not c.putscript('sogo',fSieve.read()):
		print "fail!"

Planes para el futuro:

  • Agrupar reglas para que las que corresponden a las mismas acciones estén juntas.
  • Modificar este programa para que la adición de reglas se haga añadiéndolas en el lugar que les corresponda, en vez de al final.
  • Gestionar las copias de seguridad de los viejos filtros. Está bien tener algunas por si algo va mal, pero no vale la pena guardar toda la historia.
  • Añadir un modo completamente manual (para el que ya hay código, pero no está integrado)
  • Automatizar todavía más la extracción de reglas (por ejemplo, intentar ‘adivinar’ cuál es la acción que podríamos elegir (?).

¿Ideas? ¿Comentarios? Estaremos escuchando en @mbpfernand0 o en @fernand0.

El valor de tu clave

Robo de credenciales Un error frecuente de muchos usuarios es pensar que no son objetivo de ningún atante (¿a quién podría interesar mi cuenta?). Lo cierto es que es posible que no seamos nada interesantes pero aún así nuestras cuentas puedan ser utilizadas como vía de acceso a otros recursos o personas y por eso hay que mentalizarse y concienciar a los usuarios.

El dato viene de 21% of users think their passwords are of no value to criminals y nos recuerda que las contraseñas son el paso necesario para obtener nuestros datos, información privada e incluso nuestro dinero. Muchas veces toda esta información está disponible gracias a las alertas y avisos que llegan a nuestra cuenta de correo. Incluso aunque no seamos famosos, un atacante puede estar interesado en alguna de nuestras propiedades (virtuales o físicas). También puede utilizarnos como puente para acceder a otros.

En este sentido puede ser una buena lectura el artículo que puede descargarse en Handcrafted Fraud and Extortion: Manual Account Hijacking in the Wild donde nos cuentan el proceso de un atacante cuando consigue las credenciales de una cuenta: formas de ataque, explotación, …

El trabajo se hizo con datos de Google:

We observe an average of 9 incidents per million Google users per day.

y habla de cómo se roban credenciales, cómo se monetiza ese robo y también (aunque para este caso es menos interesante) lo que hacen en la empresa para devolver el control al usuario.

Las técnicas de robo son las que podemos imaginar:

This can occur in a multitude of ways: phishing a user’s credentials; installing malware on the victim’s machine to steal credentials; or guessing a victim’s password. Overall we find corroborating evidence throughout our measurements that phishing is likely to be the main way hijackers compromise user accounts.

Una vez obtenidas estas credenciales se pasa al prefilado del usuario: ver qué hay interesante en la cuenta.

The account profiling part where the hijacker decides on a concrete exploitation plan and the exploitation itself.
The existence of this profiling phase is one of the most surprising insights we gained by fighting manual hijackers. Instead of blindly exploiting every account, hijackers take on average 3 minutes to assess the value of the account before deciding to proceed.

El resultado puede ser que nuestra cuenta termine siendo considerada poco valiosa (y abandonada), que encuentren algún objetivo de interés, o que intenten exteorsionarnos (pidiéndonos un rescate).

El phishing normalmente llegaría a través de mensajes de correo electrónico.

Finalmente habla de una cierta profesionalización de estos ataques: los atacantes parecen tener sus horarios, protocolos de actuación e incluso utilizan herramientas comunes.

Jugando con IMAP en Python

Hilos Hace no mucho parecía que POP iba a ser el protocolo preferido para el correo. Sin embargo, con la movilidad permanente y la necesidad de consultar nuestro correo en cualquier parte parece que todo ha cambiado y las soluciones preferidas son los sistemas de correo basados en proveedores con sus aplicaciones (y típicamente su reflejo en webmail para el escritorio. Si nuestro proveedor no tiene aplicación, parece que la solución más adecuada es el IMAP. POP estaba orientado a la descarga, e IMAP estaba pensando para que el correo permaneciera en el servidor y se accediera a él mediante conexión.

A mi me gusta seguir viviendo la ficción de que una parte de mi correo es más o menos privada y eso pasa por usar los servidores del trabajo (o de un proveedor privado) con algunos inconvenientes: por un lado, malas herramientas de filtrado y gestión del correo; por otra parte, limitaciones en el espacio (todavía hay otra, y es la capacidad de buscar que pude solucionar -más o menos- con mairix).

Estas limitaciones me llevaron a montar un servidorcillo de correo (lo que viene llamándose en los últimos tiempos hacer un Hillary Clinton). Empecé montando los servicios por mi cuenta hasta que probé Zentyal que no es exactamente lo que necesito (tiene muchas más cosas y está pensada para algo más grande, pero me libera de algunas tareas de gestión). Miro con simpatía iniciativas como mailinabox o el fallido -por ahora- ownmailbox. Zentyal me proporciona un sistema con SOGo, Dovecot, filtrado (pronto, espero, otra entrada específica sobre el tema), antispam (no va demasiado bien, al menos en mi experiencia) y algunas herramientas más.

De todas formas, lo que quiero presentar hoy aquí puede ser de utilidad para cualquier cuenta de correo. Se trata de borrar algunos mensajes de una carpeta IMAP basado en reglas sencillas: en nuestro caso, recibimos mensajes de diversas tareas de cron que repetitivamente (sobre todo cuando todo va bien) llegan a nuestro correo. Lo que haremos será recorrer las carpetas donde se reciben las copias de seguridad y borrarlas.

La primera tarea es buscar un poco para ver si existe algún proyecto que nos ayude en la tarea de programación. Python incluye la imaplib que permite hacer las tareas necesarias. El proceso será:

  1. Leer la configuración de las cuentas que vamos a examinar
  2. Solicitar las contraseñas
  3. Seleccionar los mensajes y borrarlos

Además, para añadirle un poco de gracia a la cosa hemos añadido un uso muy básico de threading de manera que se hace el borrado de manera concurrente en las cuentas que haya configuradas. El código de la versión actual del programita se puede encontrar en deleteCronMesgs.py.

Comenzamos leyendo la configuración

config = ConfigParser.ConfigParser()
config.read([os.path.expanduser('~/IMAP.cfg')])

En este fichero está la información de las cuentas de correo, tantas como sea necesario:

[IMAP1]
server:imap.server.com
user:myAccount@server.com
[IMAP2]
server:imap.otherServer.com
user:myUsername

Ahora recorremos las cuentas configuradas y pedimos la contraseña (se podrían poner también en el fichero de configuración pero no termino de sentirme confortable con eso)

for section in config.sections():
	SERVER = config.get(section, 'server')
	USER   = config.get(section, 'user')

	print SERVER
	PASSWORD = getpass.getpass()

El código para borrar los mensajes se encuentra en la función mailFolder y para ello realiza los siguientes pasos:

Primero nos conectamos al servidor, con los datos leídos del fichero de configuración y del teclado.

	M = imaplib.IMAP4_SSL(SERVER)
	M.login(USER , password)

Seleccionamos la carpeta sobre la que vamos a trabajar (en este caso el INBOX) y en ella buscamos los mensajes que nos interesan (los que contienen la cadena ‘Cron Daemon’ en el campo ‘FROM’):

	M.select()
	typ,data = M.search(None,'FROM', 'Cron Daemon')

En search se devuelven dos valores, el estado (¿ha ido todo bien?) y una lista que contiene una cadena de caracteres con los números que identifican a los mensajes que cumplen las condiciones de búsqueda.

Ahora, para borrarlos recorremos la lista y actualizamos la marca (flag) para que refleje la condición de borrado:

	if data[0]: 
		for num in data[0].split():
			M.store(num, '+FLAGS', '\\Deleted')

Además hemos incluido código para ir contando cuántos mensajes borramos y mostrar una actualización cada diez (que no mostramos aquí). Finalmente, cerramos la conexión y terminamos:

	M.close()
	M.logout()

La parte concurrente es muy sencilla, consta de tres pasos.

Para cada cuenta de correo se crea un hilo:

	t = threading.Thread(target=mailFolder, args=(SERVER, USER, PASSWORD,space*i))

En target está la función que se ejecutará cuando se active el hilo y en args los parámetros de esa función.

Ahora acivamos los hilos (hemos separado la creación de la activación para que no haya interferencias de la salida que refleja la actividad con la entrada; podríamos prescindir de la realmientación y lanzarlas sobre la marcha):

for t in threads:
	t.start()

Finalmente, necesitamos esperar a que terminen los hilos, con un par de instrucciones como:

for t in threads:
	t.join()

Siguientes pasos: no tengo claro que el código de los hilos vaya a sobrevivir. Estuvo bien recordar un poquito (¡muy poquito!) de concurrencia pero en este caso no tiene demasiado interés. Tendría sentido poder incluir más reglas de borrado para otros mensajes e incluirlo en la configuración en lugar de en el propio código (por ejemplo, una lista de cademas para buscar o incluso algo más sofisticado).

También me gustaría explorar sistemas alternativos de autentificación (GMail permite utilizar OAuth, si no me equivoco). Como mínimo, poder tener la contraseña cifrada en el fichero de configuración y tener sólo una contraseña manestra.

¿Ideas? ¿Comentarios? Estaremos escuchando en @mbpfernand0 o en @fernand0.

Actualización (2015-12-07) quise terminar esta entrada rápido y olvidé otro ejemplo que tengo a mano. En este caso se trataría de borrar basado en el contenido. Recibimos ciertos mensajes repetidos que querríamos eliminar de nuestras coias de seguridad. Para examinar el contenido de los mensajes utilizaríamos:

	typ, data = M.fetch(num, '(BODY.PEEK[TEXT])')

Para determinar si el mensaje en cuestión es repetido, calculamos el hash (que tenemos precalculado) y lo comparamos:

	m = hashlib.md5()
	m.update(data[0][1])	
	if (binascii.hexlify(m.digest())==HASH):
		M.store(num, '+FLAGS', '\\Deleted')
		i = i + 1

Eliminar la aleatoriedad en un juego

Juegos

En Deterministic Doom hace la prueba de eliminar la aleatoriedad del conocido juego Doom. Para ello, primero observa que el juego no utiliza ninguna fuente de aleatoriedad externa, sino que incluye una tabla de valores de donde se toman los valores para diversas acciones en el juego:

Rather than consume system randomness, Doom has a fixed 256-value random number table from which numbers are pulled by aspects of the game logic

Por otra parte, los efectos son los que uno podría imaginar (y algunos otros): desde el comportamiento de los atacantes a diversos efectos de imagen, pasando por las armas que se utilizan.

Prueba con distintos valores, por ejemplo:

With 0x00, monsters never make their idle noises (breathing etc.) On the other hand, with 0xFF, they always do: so often, that each sample collides with the previous one, and you just get a sort-of monster drone. This is quite overwhelming with even a small pack of monsters.

Curioso.

Más fallos con una larga historia

Bicho

Es un tema recurrente hablar sobre el mito de los miles de ojos mirando. Ni en código abierto Bugs con una larga historia. El caso de LZO ni, por supuesto, en código propietario (supuestamente profesionalmente controlado).

Traemos esta vez un fallo con casi 20 años, 18-year-old bug can be exploited to steal credentials of Windows users en el que no entraremos en los detalles técnicos:

A new technique for exploiting an 18-year-old bug in Windows Server Message Block (SMB), which would allow attackers to intercept user credentials, had been uncovered by Cylance researcher Brian Wallace.

Pero que muestra que a veces los fallos son muy resistentes al paso del tiempo. Y algún día se descubren.

Para los interesandos, más detalles técnicos en SPEAR: Redirect to SMB.

Firmar el código que se ejecuta en el cliente o en el servidor

Firmas Desde hace tiempo tengo una idea un poco loca: en estos tiempos en los que tanto software se ejecuta directamente en un servidor y no tenemos ninguna forma de verificar lo que sucede sería interesante poder ‘certificar’ de alguna forma que el código que se ejecuta es el debido. Por ejemplo, si alguien está usando el servidor web Apache, tal vez debería ser posible en algunos casos poder verificar que efectivamente se ejecuta el código sin modificar, etc. Ya que no podemos auditar el programa que utilizamos, sería una forma de poder ver el código (que tal vez estaría disponible en algún repositorio) y estar seguros de que lo que se ejecuta es justamente lo que vemos.

Por eso me llamó la atención la propuesta/pregunta de Propuesta: ¿Un plugin para autorizar JS en cifrados online? donde se limita el problema a la ejecución de código en JavaScript:

En todos estos casos se emplea código javascript (JS) que se descarga y ejecuta en el navegador del usuario, de manera que el servidor nunca llega a conocer su pasword, desarrollándose todo el proceso de cifrado y descifrado siempre del lado del cliente.

El punto débil de este criptosistema ocurre cuando la web ve comprometida su seguridad y con ella la de su código JS, ya sea por la intervención de un programador deshonesto de la misma organización, ya sea por la intervención de terceros que pueden introducir fragmentos de código malicioso con objeto, por ejemplo, de abrir un socket durante el proceso de cifrado para capturar y enviar la clave del usuario a un determinado sitio.

No se si la propuesta ha avanzado en alguna dirección pero a lo mejor valía la pena probar.

Motivos por los que debería gustarnos Java

Libro: De Euclides a Java Nunca he sido un gran fan de Java. Tampoco he hecho programas de tamaño suficinte como para poder criticarlo o alabarlo con criterio, la verdad. El otro día leía una ‘queja’ que ahora no soy capaz de encontrar donde alguien decía que Oracle no le estaba prestando suficiente atención. Y también otra ¿Se ha convertido Java en un problema para el acceso a la administración electrónica? aunque en este caso no termino de tener clara la conclusión porque me resisto a creer que nadie haya sido capaz de superar el problema de los certificados utilizando algo que no sea el navegador y Java.

A pesar de todo lo dicho, me gustó leer una serie de diez entradas que se pueden leer a partir de 10 Reasons Why Java Now Rocks More Than Ever: Part 1 – The Java Compiler donde se van desgranando diversos motivos por los que debería gustarnos la plataforma.

Para perezosos y personas interesadas sólo en alguna de las partes (o como anzuelo para, a partir de ahí, leerlas todas), el índice:

  1. The Java Compiler
  2. The Core API
  3. Open-Source
  4. The Java Memory Model
  5. High-Performance JVM
  6. Bytecode
  7. Intelligent IDEs
  8. Profiling Tools
  9. Backwards Compatibility
  10. Maturity With Innovation

Lo de la compatibilidad hacia atrás me ha recordado una historia vieja de Joel Spolsky How Microsoft Lost the API War. Lo enlazámabos en su día en la bitácora de BarraPunto, que allí sigue: Joel, la API de Windows y el desarrollo web. Pocas veces nos acordamos cuando empezamos un proyecto nuevo que su vida debería larga y que eso tiene consecuencias cuando apostamos por tecnologías muy novedosas.

Actualización (2015-11-25): El amigo @vrruiz me señala Even If Oracle Is Losing Interest In Java, Should You Worry? como una posible fuente acerca de lo que comentaba arriba de la pérdida de interés en Java por parte de Oracle. Allí se enlaza a esta nota Insider: Oracle has lost interest in Java que creo que es la que yo vi en su momento. Quede aquí referenciada por completitud.