--- /dev/null
+Common subdirectories: ../jabberd-2.0s6.orig/sm/.deps and sm/.deps
+Only in ../jabberd-2.0s6.orig/sm/: .libs
+diff -u ../jabberd-2.0s6.orig/sm/mod_offline.c sm/mod_offline.c
+--- ../jabberd-2.0s6.orig/sm/mod_offline.c Thu Dec 30 14:59:02 2004
++++ sm/mod_offline.c Thu Dec 30 16:39:50 2004
+@@ -27,6 +27,12 @@
+ * $Revision$
+ */
+
++typedef struct _mod_offline_st {
++ int dropmessages;
++ int dropsubscriptions;
++ int userquota;
++} *mod_offline_t;
++
+ static mod_ret_t _offline_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) {
+ st_ret_t ret;
+ os_t os;
+@@ -95,10 +101,13 @@
+ }
+
+ static mod_ret_t _offline_pkt_user(mod_instance_t mi, user_t user, pkt_t pkt) {
++ mod_offline_t offline = (mod_offline_t) mi->mod->private;
+ int ns, elem, attr;
+ os_t os;
+ os_object_t o;
+ pkt_t event;
++ st_ret_t ret;
++ int queuesize;
+
+ /* send messages and s10ns to the top session */
+ if(user->top != NULL && (pkt->type & pkt_MESSAGE || pkt->type & pkt_S10N)) {
+@@ -106,8 +115,20 @@
+ return mod_HANDLED;
+ }
+
++ /* if user quotas are enabled, count the number of offline messages this user has in the queue */
++ if(offline->userquota > 0) {
++ ret = storage_count(user->sm->st, "queue", jid_user(user->jid), NULL, &queuesize);
++
++ log_debug(ZONE, "storage_count ret is %i queue size is %i", ret, queuesize);
++
++ /* if the user's quota is exceeded, return an error */
++ if (ret == st_SUCCESS && (pkt->type & pkt_MESSAGE) && queuesize >= offline->userquota)
++ return -stanza_err_SERVICE_UNAVAILABLE;
++ }
++
+ /* save messages and s10ns for later */
+- if(pkt->type & pkt_MESSAGE || pkt->type & pkt_S10N) {
++ if((pkt->type & pkt_MESSAGE && !offline->dropmessages) ||
++ (pkt->type & pkt_S10N && !offline->dropsubscriptions)) {
+ log_debug(ZONE, "saving message for later");
+
+ pkt_delay(pkt, time(NULL), user->sm->id);
+@@ -222,14 +243,38 @@
+ storage_delete(mi->sm->st, "queue", jid_user(jid), NULL);
+ }
+
++static void _offline_free(module_t mod) {
++ mod_offline_t offline = (mod_offline_t) mod->private;
++
++ free(offline);
++}
++
+ int offline_init(mod_instance_t mi, char *arg) {
+ module_t mod = mi->mod;
++ char *configval;
++ mod_offline_t offline;
+
+ if(mod->init) return 0;
+
++ offline = (mod_offline_t) malloc(sizeof(struct _mod_offline_st));
++ memset(offline, 0, sizeof(struct _mod_offline_st));
++
++ configval = config_get_one(mod->mm->sm->config, "offline.dropmessages", 0);
++ if (configval != NULL)
++ offline->dropmessages = 1;
++
++ configval = config_get_one(mod->mm->sm->config, "offline.dropsubscriptions", 0);
++ if (configval != NULL)
++ offline->dropsubscriptions = 1;
++
++ offline->userquota = j_atoi(config_get_one(mod->mm->sm->config, "offline.userquota", 0), 0);
++
++ mod->private = offline;
++
+ mod->in_sess = _offline_in_sess;
+ mod->pkt_user = _offline_pkt_user;
+ mod->user_delete = _offline_user_delete;
++ mod->free = _offline_free;
+
+ return 0;
+ }
+Only in ../jabberd-2.0s6.orig/sm/: sm
+diff -u ../jabberd-2.0s6.orig/sm/sm.h sm/sm.h
+--- ../jabberd-2.0s6.orig/sm/sm.h Thu Dec 30 14:59:02 2004
++++ sm/sm.h Thu Dec 30 15:06:59 2004
+@@ -584,6 +584,8 @@
+ st_ret_t (*put)(st_driver_t drv, const char *type, const char *owner, os_t os);
+ /** get handler */
+ st_ret_t (*get)(st_driver_t drv, const char *type, const char *owner, const char *filter, os_t *os);
++ /** count handler */
++ st_ret_t (*count)(st_driver_t drv, const char *type, const char *owner, const char *filter, int *count);
+ /** delete handler */
+ st_ret_t (*delete)(st_driver_t drv, const char *type, const char *owner, const char *filter);
+ /** replace handler */
+@@ -605,6 +607,8 @@
+ st_ret_t storage_put(storage_t st, const char *type, const char *owner, os_t os);
+ /** get objects matching this filter */
+ st_ret_t storage_get(storage_t st, const char *type, const char *owner, const char *filter, os_t *os);
++/** count objects matching this filter */
++st_ret_t storage_count(storage_t st, const char *type, const char *owner, const char *filter, int *count);
+ /** delete objects matching this filter */
+ st_ret_t storage_delete(storage_t st, const char *type, const char *owner, const char *filter);
+ /** replace objects matching this filter with objects in this set (atomic delete + get) */
+Only in sm: smlog
+diff -u ../jabberd-2.0s6.orig/sm/storage.c sm/storage.c
+--- ../jabberd-2.0s6.orig/sm/storage.c Thu Dec 30 14:59:02 2004
++++ sm/storage.c Thu Dec 30 16:42:34 2004
+@@ -270,6 +270,32 @@
+ return (drv->get)(drv, type, owner, filter, os);
+ }
+
++st_ret_t storage_count(storage_t st, const char *type, const char *owner, const char *filter, int *count) {
++ st_driver_t drv;
++ st_ret_t ret;
++
++ log_debug(ZONE, "storage_count: type=%s owner=%s filter=%s", type, owner, filter);
++
++ /* find the handler for this type */
++ drv = xhash_get(st->types, type);
++ if(drv == NULL) {
++ /* never seen it before, so it goes to the default driver */
++ drv = st->default_drv;
++ if(drv == NULL) {
++ log_debug(ZONE, "no driver associated with type, and no default driver");
++ return st_NOTIMPL;
++ }
++
++ /* register the type */
++ ret = storage_add_type(st, drv->name, type);
++ if(ret != st_SUCCESS)
++ return ret;
++ }
++
++ return (drv->count != NULL) ? (drv->count)(drv, type, owner, filter, count) : st_NOTIMPL;
++}
++
++
+ st_ret_t storage_delete(storage_t st, const char *type, const char *owner, const char *filter) {
+ st_driver_t drv;
+ st_ret_t ret;
+diff -u ../jabberd-2.0s6.orig/sm/storage_mysql.c sm/storage_mysql.c
+--- ../jabberd-2.0s6.orig/sm/storage_mysql.c Thu Dec 30 14:59:02 2004
++++ sm/storage_mysql.c Thu Dec 30 16:44:37 2004
+@@ -438,6 +438,74 @@
+ return st_SUCCESS;
+ }
+
++static st_ret_t _st_mysql_count(st_driver_t drv, const char *type, const char *owner, const char *filter, int *count) {
++ drvdata_t data = (drvdata_t) drv->private;
++ char *cond, *buf = NULL;
++ int buflen = 0;
++ MYSQL_RES *res;
++ int ntuples, nfields;
++ MYSQL_ROW tuple;
++ char tbuf[128];
++
++ if(mysql_ping(data->conn) != 0) {
++ log_write(drv->st->sm->log, LOG_ERR, "mysql: connection to database lost");
++ return st_FAILED;
++ }
++
++ if(data->prefix != NULL) {
++ snprintf(tbuf, sizeof(tbuf), "%s%s", data->prefix, type);
++ type = tbuf;
++ }
++
++ cond = _st_mysql_convert_filter(drv, owner, filter);
++ log_debug(ZONE, "generated filter: %s", cond);
++
++ MYSQL_SAFE(buf, strlen(type) + strlen(cond) + 31, buflen);
++ sprintf(buf, "SELECT COUNT(*) FROM `%s` WHERE %s", type, cond);
++ free(cond);
++
++ log_debug(ZONE, "prepared sql: %s", buf);
++
++ if(mysql_query(data->conn, buf) != 0) {
++ log_write(drv->st->sm->log, LOG_ERR, "mysql: sql select failed: %s", mysql_error(data->conn));
++ free(buf);
++ return st_FAILED;
++ }
++ free(buf);
++
++ res = mysql_store_result(data->conn);
++ if(res == NULL) {
++ log_write(drv->st->sm->log, LOG_ERR, "mysql: sql result retrieval failed: %s", mysql_error(data->conn));
++ return st_FAILED;
++ }
++
++ ntuples = mysql_num_rows(res);
++ if(ntuples == 0) {
++ mysql_free_result(res);
++ return st_NOTFOUND;
++ }
++
++ log_debug(ZONE, "%d tuples returned", ntuples);
++
++ nfields = mysql_num_fields(res);
++
++ if(nfields == 0) {
++ log_debug(ZONE, "weird, tuples were returned but no fields *shrug*");
++ mysql_free_result(res);
++ return st_NOTFOUND;
++ }
++
++ if((tuple = mysql_fetch_row(res)) == NULL)
++ return st_NOTFOUND;
++
++ if (count!=NULL)
++ *count = atoi(tuple[0]);
++
++ mysql_free_result(res);
++
++ return st_SUCCESS;
++}
++
+ static st_ret_t _st_mysql_delete(st_driver_t drv, const char *type, const char *owner, const char *filter) {
+ drvdata_t data = (drvdata_t) drv->private;
+ char *cond, *buf = NULL;
+@@ -571,6 +639,7 @@
+
+ drv->add_type = _st_mysql_add_type;
+ drv->put = _st_mysql_put;
++ drv->count = _st_mysql_count;
+ drv->get = _st_mysql_get;
+ drv->delete = _st_mysql_delete;
+ drv->replace = _st_mysql_replace;