diff --git a/package/gluon-cron/Makefile b/package/gluon-cron/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..70321181359aa8342ae55bea465e44e020888a79
--- /dev/null
+++ b/package/gluon-cron/Makefile
@@ -0,0 +1,40 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-cron
+PKG_VERSION:=0.3.2.99
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-cron
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Cron support
+  DEPENDS:=+gluon-core
+endef
+
+define Package/gluon-cron/description
+	Gluon community wifi mesh firmware framework: cron support
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+	$(CP) ./src/* $(PKG_BUILD_DIR)/
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	CFLAGS="$(TARGET_CFLAGS)" CPPFLAGS="$(TARGET_CPPFLAGS)" $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)
+endef
+
+define Package/gluon-cron/install
+	$(CP) ./files/* $(1)/
+	$(INSTALL_DIR) $(1)/usr/sbin
+	$(INSTALL_BIN) $(PKG_BUILD_DIR)/gluon-crond $(1)/usr/sbin/
+endef
+
+$(eval $(call BuildPackage,gluon-cron))
diff --git a/package/gluon-cron/files/etc/init.d/gluon-cron b/package/gluon-cron/files/etc/init.d/gluon-cron
new file mode 100755
index 0000000000000000000000000000000000000000..27a05e1d62125725433b622fc2505b7fa604cdc8
--- /dev/null
+++ b/package/gluon-cron/files/etc/init.d/gluon-cron
@@ -0,0 +1,18 @@
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2013 Project Gluon
+
+START=50
+
+SERVICE_USE_PID=1
+SERVICE_WRITE_PID=1
+SERVICE_DAEMONIZE=1
+
+CRONDIR=/lib/gluon/cron
+
+start () {
+	service_start /usr/sbin/gluon-crond "$CRONDIR"
+}
+
+stop() {
+	service_stop /usr/sbin/gluon-crond
+}
diff --git a/package/gluon-cron/files/lib/gluon/cron/.keep b/package/gluon-cron/files/lib/gluon/cron/.keep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/package/gluon-cron/src/Makefile b/package/gluon-cron/src/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..3f4c7a5012a0757c98f0a529feac6e02e7df8c87
--- /dev/null
+++ b/package/gluon-cron/src/Makefile
@@ -0,0 +1,3 @@
+all: gluon-crond
+
+gluon-crond: gluon-crond.c
diff --git a/package/gluon-cron/src/gluon-crond.c b/package/gluon-cron/src/gluon-crond.c
new file mode 100644
index 0000000000000000000000000000000000000000..52ca2afd70b3292c6a4dc8d9c550482da20fcebf
--- /dev/null
+++ b/package/gluon-cron/src/gluon-crond.c
@@ -0,0 +1,316 @@
+/*
+  Copyright (c) 2013, Matthias Schiffer <mschiffer@universe-factory.net>
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice,
+       this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <dirent.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+
+typedef struct job {
+	struct job *next;
+
+	uint64_t minutes;
+	uint32_t hours;
+	uint32_t doms;
+	uint16_t months;
+	uint8_t dows;
+
+	char *command;
+} job_t;
+
+
+static const char const *const MONTHS[12] = {
+	"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"
+};
+
+static const char const *const WEEKDAYS[7] = {
+	"sun", "mon", "tue", "wed", "thu", "fri", "sat"
+};
+
+
+static const char *crondir;
+
+static job_t *jobs = NULL;
+
+
+static void usage(void) {
+	fprintf(stderr, "Usage: gluon-crond <crondir>\n");
+}
+
+
+static inline uint64_t bit(unsigned b) {
+	return ((uint64_t)1) << b;
+}
+
+static int strict_atoi(const char *s) {
+	char *end;
+	int ret = strtol(s, &end, 10);
+
+	if (*end)
+		return -1;
+	else
+		return ret;
+}
+
+static uint64_t parse_strings(const char *input, const char *const *strings, size_t n) {
+	size_t i;
+	for (i = 0; i < n; i++) {
+		if (strcasecmp(input, strings[i]) == 0)
+			return bit(i);
+	}
+
+	return 0;
+}
+
+static uint64_t parse_times(char *input, unsigned min, unsigned n) {
+	uint64_t ret = 0;
+	int step = 1;
+
+	char *comma = strchr(input, ',');
+	if (comma) {
+		*comma = 0;
+		ret = parse_times(comma+1, min, n);
+
+		if (!ret)
+			return 0;
+	}
+
+	char *slash = strchr(input, '/');
+	if (slash) {
+		*slash = 0;
+		step = strict_atoi(slash+1);
+
+		if (step <= 0)
+			return 0;
+	}
+
+	int begin, end;
+	char *minus = strchr(input, '-');
+	if (minus) {
+		*minus = 0;
+		begin = strict_atoi(input);
+		end = strict_atoi(minus+1);
+	}
+	else if (strcmp(input, "*") == 0) {
+		begin = min;
+		end = min+n-1;
+	}
+	else {
+		begin = end = strict_atoi(input);
+	}
+
+	if (begin < min || end < min)
+		return 0;
+
+	int i;
+	for (i = begin-min; i <= end-min; i += step)
+		ret |= bit(i % n);
+
+	return ret;
+}
+
+static int handle_line(const char *line) {
+	job_t job = {};
+	int ret = -1;
+	char *columns[5];
+	int i;
+	int len;
+
+	int matches = sscanf(line, "%ms %ms %ms %ms %ms %n", &columns[0], &columns[1], &columns[2], &columns[3], &columns[4], &len);
+	if (matches != 5 && matches != 6) {
+		if (matches <= 0)
+			ret = 0;
+
+		goto end;
+	}
+
+	job.minutes = parse_times(columns[0], 0, 60);
+	if (!job.minutes)
+		goto end;
+
+	job.hours = parse_times(columns[1], 0, 24);
+	if (!job.hours)
+		goto end;
+
+	job.doms = parse_times(columns[2], 1, 31);
+	if (!job.doms)
+		goto end;
+
+
+	job.months = parse_strings(columns[3], MONTHS, 12);
+
+	if (!job.months)
+		job.months = parse_times(columns[3], 1, 12);
+	if (!job.months)
+		goto end;
+
+	job.dows = parse_strings(columns[4], WEEKDAYS, 7);
+	if (!job.dows)
+		job.dows = parse_times(columns[4], 0, 7);
+	if (!job.dows)
+		goto end;
+
+	job.command = strdup(line+len);
+
+	job_t *jobp = malloc(sizeof(job_t));
+	*jobp = job;
+
+	jobp->next = jobs;
+	jobs = jobp;
+
+	ret = 0;
+
+  end:
+	for (i = 0; i < matches; i++)
+		free(columns[i]);
+
+	return ret;
+}
+
+
+static void read_crontab(const char *name) {
+	FILE *file = fopen(name, "r");
+	if (!file) {
+		syslog(LOG_WARNING, "unable to read crontab `%s'", name);
+		return;
+	}
+
+	char line[16384];
+	unsigned lineno = 0;
+
+	while (fgets(line, sizeof(line), file)) {
+		lineno++;
+
+		char *comment = strchr(line, '#');
+		if (comment)
+			*comment = 0;
+
+		if (handle_line(line))
+			syslog(LOG_WARNING, "syntax error in `%s', line %u", name, lineno);
+	}
+
+	fclose(file);
+}
+
+
+static void read_crondir(void) {
+	DIR *dir;
+
+	if (chdir(crondir) || ((dir = opendir(".")) == NULL)) {
+		fprintf(stderr, "Unable to read crondir `%s'\n", crondir);
+		usage();
+		exit(1);
+	}
+
+	struct dirent *ent;
+	while ((ent = readdir(dir)) != NULL) {
+		if (ent->d_name[0] == '.')
+			continue;
+
+		read_crontab(ent->d_name);
+	}
+
+	closedir(dir);
+}
+
+
+static void run_job(const job_t *job) {
+	pid_t pid = fork();
+	if (pid == 0) {
+		execl("/bin/sh", "/bin/sh", "-c", job->command, (char*)NULL);
+		syslog(LOG_ERR, "unable to run job: exec failed");
+		_exit(1);
+	}
+	else if (pid < 0) {
+		syslog(LOG_ERR, "unable to run job: fork failed");
+	}
+}
+
+
+static void check_job(const job_t *job, const struct tm *tm) {
+	if (!(job->minutes & bit(tm->tm_min)))
+		return;
+
+	if (!(job->hours & bit(tm->tm_hour)))
+		return;
+
+	if (!(job->doms & bit(tm->tm_mday-1)))
+		return;
+
+	if (!(job->months & bit(tm->tm_mon)))
+		return;
+
+	if (!(job->dows & bit(tm->tm_wday)))
+		return;
+
+	run_job(job);
+}
+
+
+int main(int argc, char *argv[]) {
+	if (argc != 2) {
+		usage();
+
+		exit(argc < 2 ? 0 : 1);
+	}
+
+	crondir = argv[1];
+
+	signal(SIGCHLD, SIG_IGN);
+
+	read_crondir();
+
+	time_t t = time(NULL);
+	struct tm *tm = localtime(&t);
+	int minute = tm->tm_min;
+
+	while (1) {
+		sleep(60 - t%60);
+
+		t = time(NULL);
+		tm = localtime(&t);
+
+		minute = (minute+1)%60;
+		if (tm->tm_min != minute) {
+			/* clock has moved, don't execute jobs */
+			minute = tm->tm_min;
+			continue;
+		}
+
+		job_t *job;
+		for (job = jobs; job; job = job->next)
+			check_job(job, tm);
+	}
+}