snipsnap script macro and python



This article is also posted at snipsnap.org.

It’s a {script}, {script} world

I recently tried out SnipSnap’s {script} macro, which runs Python code inside SnipSnap. It uses Jython as an embedded, plug-and-play Python interpreter. It works great, and promised to turn SnipSnap into a lightweight webapp development paradise. Unfortunately, I can’t use it…but maybe you can succeed where I failed!

Can’t it do anything?

Recently, a friend asked me if I could write a simple webapp on snarfed.org, my SnipSnap-based site. Unfortunately, I hesitated, and for good reason. I’ve written my fair share of SnipSnap macros, and I like the macro architecture, but it’s not designed for interactive webapps. I considered whipping up some JSPs and serving them through Jetty, but that’s far from lightweight, and I’m far from a JSP expert. I have friends who are wizards with AJAX, but JavaScript can’t get very far without access to a backing store.

Flip the switch

Fortunately, I remembered my job desscription as SnipSnap admin and thought of SnipSnap’s {script} macro. I first tried the canonical “Hello world” script:

{script}
print 'Hello world!'
{script}

I quickly noticed that the {script} macro isn’t enabled by default. I looked inside WEB-INF/lib/snipsnap-servlets.jar and found that the ScriptMacro line in META-INF/services/org.radeox.macro.Macro was commented out:

#org.snipsnap.render.macro.ScriptMacro

You can uncomment this line and rebuild snipsnap-servlets.jar, but it’s probably easier to just download my script.jar, which just has ScriptMacro.class and an uncommented org.radeox.macro.Macro file, put it in your WEB-INF/lib directory, and restart SnipSnap.

After I did that, the hello world script actually worked. Holy executable code, Batman!

Down the rabbit hole

I quickly tested the waters. First, file system access. Any webapp worth its salt needs a backing store, and while databases are de rigeur, they’re also overkill in many cases. Flat files are often more than good enough. So:

{script}
f = open('foo.bar', 'w')
f.write('qwert')
f.close()

f = open('foo.bar')
print f.read()
f.close()
{script}

Great! It could talk to the server file system. The script output qwert, as expected. Next, I tried network connectivity.

{script}
from urllib import urlopen

page = urlopen('http://asdf.com/')
print page.read()
{script}

Sweet! It printed the (surprisingly small) HTML for the asdf.com home page.

I know where you live

At this point, the {script} environment was still fairly isolated from SnipSnap. My next task was to access basic SnipSnap site information, starting with the snip that contains the macro. I poked around in the source and noticed this telltale snippet in ScriptMacro.java:

PythonInterpreter interp = new PythonInterpreter();

interp.setOut(writer);
interp.set("snip",
           params.getSnipRenderContext().getSnip());

Aha! Sure enough, this code snippet prints out the name of the outer snip:

{script}
print snip
{script}

On its own, though, the snip name isn’t too useful. The Jython interpreter is running in the same JVM as SnipSnap, though, so maybe we can sneak in the back door…

{script}
import sys
sys.add_package('org.snipsnap.snip')
from org.snipsnap.snip import SnipSpaceFactory

factory = SnipSpaceFactory.getInstance()
ats = factory.load(str(snip)).getAttachments().getAll()
at_names = ', '.join([a.getName() for a in ats])

print """
   This snip is called **%s**.
   Its attachments are **%s**.
""" % (snip, at_names)
{script}

This script prints the snip name and the file names of each attachment. Ah, now we’re getting somewhere!

Wait a minute…

At this point, something had been nagging at the back of my mind for a while. I’d ignored it for a while, but by the time I enabled {script} on my live server, it pushed into the front of my mind. Hmm, {script} lets people run arbitrary code on my server, with filesystem and network access. Worse, since it needs to bind to port 80, SnipSnap runs as root, so their code would too. Yow! That’s a gaping security hole to open on a production server.

I quickly turned {script} off, ran chkrootkit, and checked the web server logs to see if any script kiddies had noticed. SnipSnap isn’t exactly a common target, and {script} is probably disabled on all other SnipSnap sites…but still, I wasn’t taking any chances.

But…! but…!

This is where I am now. I’d love to be able to use {script} on my site. It turns SnipSnap into a lightweight webapp development platform, in the same vein as PHP, .NET, J2EE, and Rails. However, it has the notable advantage that you can develop an entire webapp, from start to finish, on the web site itself, with nothing more than your browser. I don’t know of any other webapp platforms that can claim that!

Of course, it does have drawbacks. Other than pen and paper, a text form in a web page is just about the worst development environment I can imagine. Sure, you can code in your favorite text editor and transfer to SnipSnap for testing, but that’s a recipe for version skew madness.

Also, it may be lightweight, but it’s not really designed for building webapps. You’ll have to build even the most basic functionality yourself, including forms, templates, and user input. And don’t even think about commonly used features like sessions, databases, and testing.

And, of course, there’s the aforementioned security…or more accurately, lack of security. Still, I’ve drunk the SnipSnap kool-aid, and I’d love an easy way to build webapps on top of it. Until I learn how to lock down and sandbox it, though, {script} isn’t the answer. BeanShell might be worth a look, but at first glance, I’m guessing it’s similarly wide open.

Got any ideas? If so, please let me know!

Leave a Reply

Your email address will not be published. Required fields are marked *