--- /dev/null
+/*$Id: subscribe.c,v 1.22 1999/11/10 04:08:27 lindberg Exp $*/
+/*$Name: ezmlm-idx-040 $*/
+#include "stralloc.h"
+#include "getln.h"
+#include "readwrite.h"
+#include "substdio.h"
+#include "strerr.h"
+#include "open.h"
+#include "byte.h"
+#include "case.h"
+#include "lock.h"
+#include "error.h"
+#include "subscribe.h"
+#include "uint32.h"
+#include "fmt.h"
+#include "errtxt.h"
+#include "log.h"
+#include "idx.h"
+#include <mysql.h>
+#include <mysqld_error.h>
+
+static void die_nomem(fatal)
+char *fatal;
+{
+ strerr_die2x(111,fatal,ERR_NOMEM);
+}
+
+static stralloc addr = {0};
+static stralloc lcaddr = {0};
+static stralloc line = {0};
+static stralloc domain = {0};
+static stralloc logline = {0};
+static stralloc quoted = {0};
+static stralloc fnnew = {0};
+static stralloc fn = {0};
+static stralloc fnlock = {0};
+static char szh[FMT_ULONG];
+
+void die_read(fatal)
+char *fatal;
+{
+ strerr_die4sys(111,fatal,ERR_READ,fn.s,": ");
+}
+
+void die_write(fatal)
+char *fatal;
+{
+ strerr_die4sys(111,fatal,ERR_WRITE,fnnew.s,": ");
+}
+
+static int fd;
+static substdio ss;
+static char ssbuf[256];
+static int fdnew;
+static substdio ssnew;
+static char ssnewbuf[256];
+
+int subscribe(dbname,userhost,flagadd,comment,event,flagmysql,
+ forcehash,tab,fatal)
+/* add (flagadd=1) or remove (flagadd=0) userhost from the subscr. database */
+/* dbname. Comment is e.g. the subscriber from line or name. It is added to */
+/* the log. Event is the action type, e.g. "probe", "manual", etc. The */
+/* direction (sub/unsub) is inferred from flagadd. Returns 1 on success, 0 */
+/* on failure. If flagmysql is set and the file "sql" is found in the */
+/* directory dbname, it is parsed and a mysql db is assumed. if forcehash is */
+/* >=0 it is used in place of the calculated hash. This makes it possible to */
+/* add addresses with a hash that does not exist. forcehash has to be 0..99. */
+/* for unsubscribes, the address is only removed if forcehash matches the */
+/* actual hash. This way, ezmlm-manage can be prevented from touching certain*/
+/* addresses that can only be removed by ezmlm-unsub. Usually, this would be */
+/* used for sublist addresses (to avoid removal) and sublist aliases (to */
+/* prevent users from subscribing them (although the cookie mechanism would */
+/* prevent the resulting duplicate message from being distributed. */
+
+char *dbname;
+char *userhost;
+int flagadd;
+char *comment;
+char *event;
+int flagmysql;
+int forcehash;
+char *tab;
+char *fatal;
+{
+ int fdlock;
+
+ MYSQL_RES *result;
+ MYSQL_ROW row;
+ char *cp,*cpafter,*cpat;
+ char szhash[3] = "00";
+ char *r = (char *) 0;
+ char *table = (char *) 0;
+ char **ptable = &table;
+
+ unsigned int j;
+ uint32 h,lch;
+ unsigned char ch,lcch;
+ int match;
+ int flagwasthere;
+
+ if (userhost[str_chr(userhost,'\n')])
+ strerr_die2x(100,fatal,ERR_ADDR_NL);
+
+ if (tab) ptable = &tab;
+
+ if (!flagmysql || (r = opensql(dbname,ptable))) {
+ if (r && *r) strerr_die2x(111,fatal,r);
+ /* fallback to local db */
+ if (!stralloc_copys(&addr,"T")) die_nomem(fatal);
+ if (!stralloc_cats(&addr,userhost)) die_nomem(fatal);
+ if (addr.len > 401)
+ strerr_die2x(100,fatal,ERR_ADDR_LONG);
+
+ j = byte_rchr(addr.s,addr.len,'@');
+ if (j == addr.len)
+ strerr_die2x(100,fatal,ERR_ADDR_AT);
+ case_lowerb(addr.s + j + 1,addr.len - j - 1);
+ if (!stralloc_copy(&lcaddr,&addr)) die_nomem(fatal);
+ case_lowerb(lcaddr.s + 1,j - 1); /* make all-lc version of address */
+
+ if (forcehash >= 0 && forcehash <= 52) {
+ ch = lcch = (unsigned char) forcehash;
+ } else {
+ h = 5381;
+ lch = h;
+ for (j = 0;j < addr.len;++j) {
+ h = (h + (h << 5)) ^ (uint32) (unsigned char) addr.s[j];
+ lch = (lch + (lch << 5)) ^ (uint32) (unsigned char) lcaddr.s[j];
+ }
+ lcch = 64 + (lch % 53);
+ ch = 64 + (h % 53);
+ }
+
+ if (!stralloc_0(&addr)) die_nomem(fatal);
+ if (!stralloc_0(&lcaddr)) die_nomem(fatal);
+ if (!stralloc_copys(&fn,dbname)) die_nomem(fatal);
+ if (!stralloc_copys(&fnlock,dbname)) die_nomem(fatal);
+
+ if (!stralloc_cats(&fn,"/subscribers/")) die_nomem(fatal);
+ if (!stralloc_catb(&fn,&lcch,1)) die_nomem(fatal);
+ if (!stralloc_copy(&fnnew,&fn)) die_nomem(fatal);
+ /* code later depends on fnnew = fn + 'n' */
+ if (!stralloc_cats(&fnnew,"n")) die_nomem(fatal);
+ if (!stralloc_cats(&fnlock,"/lock")) die_nomem(fatal);
+ if (!stralloc_0(&fnnew)) die_nomem(fatal);
+ if (!stralloc_0(&fn)) die_nomem(fatal);
+ if (!stralloc_0(&fnlock)) die_nomem(fatal);
+
+ fdlock = open_append(fnlock.s);
+ if (fdlock == -1)
+ strerr_die4sys(111,fatal,ERR_OPEN,fnlock.s,": ");
+ if (lock_ex(fdlock) == -1)
+ strerr_die4sys(111,fatal,ERR_OBTAIN,fnlock.s,": ");
+
+ /* do lower case hashed version first */
+ fdnew = open_trunc(fnnew.s);
+ if (fdnew == -1) die_write(fatal);
+ substdio_fdbuf(&ssnew,write,fdnew,ssnewbuf,sizeof(ssnewbuf));
+
+ flagwasthere = 0;
+
+ fd = open_read(fn.s);
+ if (fd == -1) {
+ if (errno != error_noent) { close(fdnew); die_read(fatal); }
+ }
+ else {
+ substdio_fdbuf(&ss,read,fd,ssbuf,sizeof(ssbuf));
+
+ for (;;) {
+ if (getln(&ss,&line,&match,'\0') == -1) {
+ close(fd); close(fdnew); die_read(fatal);
+ }
+ if (!match) break;
+ if (line.len == addr.len)
+ if (!case_diffb(line.s,line.len,addr.s)) {
+ flagwasthere = 1;
+ if (!flagadd)
+ continue;
+ }
+ if (substdio_bput(&ssnew,line.s,line.len) == -1) {
+ close(fd); close(fdnew); die_write(fatal);
+ }
+ }
+
+ close(fd);
+ }
+
+ if (flagadd && !flagwasthere)
+ if (substdio_bput(&ssnew,addr.s,addr.len) == -1) {
+ close(fdnew); die_write(fatal);
+ }
+
+ if (substdio_flush(&ssnew) == -1) { close(fdnew); die_write(fatal); }
+ if (fsync(fdnew) == -1) { close(fdnew); die_write(fatal); }
+ close(fdnew);
+
+ if (rename(fnnew.s,fn.s) == -1)
+ strerr_die6sys(111,fatal,ERR_MOVE,fnnew.s," to ",fn.s,": ");
+
+ if ((ch == lcch) || flagwasthere) {
+ close(fdlock);
+ if (flagadd ^ flagwasthere) {
+ if (!stralloc_0(&addr)) die_nomem(fatal);
+ log(dbname,event,addr.s+1,comment);
+ return 1;
+ }
+ return 0;
+ }
+
+ /* If unsub and not found and hashed differ, OR */
+ /* sub and not found (so added with new hash) */
+ /* do the 'case-dependent' hash */
+
+ fn.s[fn.len - 2] = ch;
+ fnnew.s[fnnew.len - 3] = ch;
+ fdnew = open_trunc(fnnew.s);
+ if (fdnew == -1) die_write(fatal);
+ substdio_fdbuf(&ssnew,write,fdnew,ssnewbuf,sizeof(ssnewbuf));
+
+ fd = open_read(fn.s);
+ if (fd == -1) {
+ if (errno != error_noent) { close(fdnew); die_read(fatal); }
+ } else {
+ substdio_fdbuf(&ss,read,fd,ssbuf,sizeof(ssbuf));
+
+ for (;;) {
+ if (getln(&ss,&line,&match,'\0') == -1)
+ { close(fd); close(fdnew); die_read(fatal); }
+ if (!match) break;
+ if (line.len == addr.len)
+ if (!case_diffb(line.s,line.len,addr.s)) {
+ flagwasthere = 1;
+ continue; /* always want to remove from case-sensitive hash */
+ }
+ if (substdio_bput(&ssnew,line.s,line.len) == -1)
+ { close(fd); close(fdnew); die_write(fatal); }
+ }
+
+ close(fd);
+ }
+
+ if (substdio_flush(&ssnew) == -1) { close(fdnew); die_write(fatal); }
+ if (fsync(fdnew) == -1) { close(fdnew); die_write(fatal); }
+ close(fdnew);
+
+ if (rename(fnnew.s,fn.s) == -1)
+ strerr_die6sys(111,fatal,ERR_MOVE,fnnew.s," to ",fn.s,": ");
+
+ close(fdlock);
+ if (flagadd ^ flagwasthere) {
+ if (!stralloc_0(&addr)) die_nomem(fatal);
+ log(dbname,event,addr.s+1,comment);
+ return 1;
+ }
+ return 0;
+
+ } else { /* SQL version */
+ domain.len = 0; /* clear domain */
+ /* lowercase and check address */
+ if (!stralloc_copys(&addr,userhost)) die_nomem(fatal);
+ if (addr.len > 255) /* this is 401 in std ezmlm. 255 */
+ /* should be plenty! */
+ strerr_die2x(100,fatal,ERR_ADDR_LONG);
+ j = byte_rchr(addr.s,addr.len,'@');
+ if (j == addr.len)
+ strerr_die2x(100,fatal,ERR_ADDR_AT);
+ cpat = addr.s + j;
+ case_lowerb(cpat + 1,addr.len - j - 1);
+ if (!stralloc_ready("ed,2 * addr.len + 1)) die_nomem(fatal);
+ quoted.len = mysql_escape_string(quoted.s,addr.s,addr.len);
+ /* stored unescaped, so it should be ok if quoted.len is >255, as */
+ /* long as addr.len is not */
+
+ if (forcehash < 0) {
+ if (!stralloc_copy(&lcaddr,&addr)) die_nomem(fatal);
+ case_lowerb(lcaddr.s,j); /* make all-lc version of address */
+ h = 5381;
+ for (j = 0;j < lcaddr.len;++j) {
+ h = (h + (h << 5)) ^ (uint32) (unsigned char) lcaddr.s[j];
+ }
+ ch = (h % 53); /* 0 - 52 */
+ } else
+ ch = (forcehash % 100);
+
+ szhash[0] = '0' + ch / 10; /* hash for sublist split */
+ szhash[1] = '0' + (ch % 10);
+
+ if (flagadd) {
+ if (!stralloc_copys(&line,"LOCK TABLES ")) die_nomem(fatal);
+ if (!stralloc_cats(&line,table)) die_nomem(fatal);
+ if (!stralloc_cats(&line," WRITE")) die_nomem(fatal);
+ if (mysql_real_query((MYSQL *) psql,line.s,line.len))
+ strerr_die2x(111,fatal,mysql_error((MYSQL *) psql));
+ if (!stralloc_copys(&line,"SELECT address FROM ")) die_nomem(fatal);
+ if (!stralloc_cats(&line,table)) die_nomem(fatal);
+ if (!stralloc_cats(&line," WHERE address='")) die_nomem(fatal);
+ if (!stralloc_cat(&line,"ed)) die_nomem(fatal); /* addr */
+ if (!stralloc_cats(&line,"'")) die_nomem(fatal);
+ if (mysql_real_query((MYSQL *) psql,line.s,line.len))
+ strerr_die2x(111,fatal,mysql_error((MYSQL *) psql));
+ if (!(result = mysql_use_result((MYSQL *) psql)))
+ strerr_die2x(111,fatal,mysql_error((MYSQL *) psql));
+ if ((row = mysql_fetch_row(result))) { /* there */
+ while (mysql_fetch_row(result)); /* use'm up */
+ mysql_free_result(result);
+ if (mysql_query((MYSQL *) psql,"UNLOCK TABLES"))
+ strerr_die2x(111,"fatal",mysql_error((MYSQL *) psql));
+ return 0; /* there */
+ } else { /* not there */
+ mysql_free_result(result);
+ if (mysql_errno((MYSQL *) psql)) /* or ERROR */
+ strerr_die2x(111,fatal,mysql_error((MYSQL *) psql));
+ if (!stralloc_copys(&line,"INSERT INTO ")) die_nomem(fatal);
+ if (!stralloc_cats(&line,table)) die_nomem(fatal);
+ if (!stralloc_cats(&line," (address,hash) VALUES ('"))
+ die_nomem(fatal);
+ if (!stralloc_cat(&line,"ed)) die_nomem(fatal); /* addr */
+ if (!stralloc_cats(&line,"',")) die_nomem(fatal);
+ if (!stralloc_cats(&line,szhash)) die_nomem(fatal); /* hash */
+ if (!stralloc_cats(&line,")")) die_nomem(fatal);
+ if (mysql_real_query((MYSQL *) psql,line.s,line.len)) /* INSERT */
+ strerr_die2x(111,fatal,mysql_error((MYSQL *) psql));
+ if (mysql_query((MYSQL *) psql,"UNLOCK TABLES"))
+ strerr_die2x(111,fatal,mysql_error((MYSQL *) psql));
+ }
+ } else { /* unsub */
+ if (!stralloc_copys(&line,"DELETE FROM ")) die_nomem(fatal);
+ if (!stralloc_cats(&line,table)) die_nomem(fatal);
+ if (!stralloc_cats(&line," WHERE address='")) die_nomem(fatal);
+ if (!stralloc_cat(&line,"ed)) die_nomem(fatal); /* addr */
+ if (forcehash >= 0) {
+ if (!stralloc_cats(&line,"' AND hash=")) die_nomem(fatal);
+ if (!stralloc_cats(&line,szhash)) die_nomem(fatal);
+ } else {
+ if (!stralloc_cats(&line,"' AND hash BETWEEN 0 AND 52"))
+ die_nomem(fatal);
+ }
+ if (mysql_real_query((MYSQL *) psql,line.s,line.len))
+ strerr_die2x(111,fatal,mysql_error((MYSQL *) psql));
+ if (mysql_affected_rows((MYSQL *) psql) == 0)
+ return 0; /* address wasn't there*/
+ }
+
+ /* log to subscriber log */
+ /* INSERT INTO t_slog (address,edir,etype,fromline) */
+ /* VALUES('address',{'+'|'-'},'etype','[comment]') */
+
+ if (!stralloc_copys(&logline,"INSERT INTO ")) die_nomem(fatal);
+ if (!stralloc_cats(&logline,table)) die_nomem(fatal);
+ if (!stralloc_cats(&logline,
+ "_slog (address,edir,etype,fromline) VALUES ('")) die_nomem(fatal);
+ if (!stralloc_cat(&logline,"ed)) die_nomem(fatal);
+ if (flagadd) { /* edir */
+ if (!stralloc_cats(&logline,"','+','")) die_nomem(fatal);
+ } else {
+ if (!stralloc_cats(&logline,"','-','")) die_nomem(fatal);
+ }
+ if (*(event + 1)) /* ezmlm-0.53 uses '' for ezmlm-manage's work */
+ if (!stralloc_catb(&logline,event+1,1)) die_nomem(fatal); /* etype */
+ if (!stralloc_cats(&logline,"','")) die_nomem(fatal);
+ if (comment && *comment) {
+ j = str_len(comment);
+ if (!stralloc_ready("ed,2 * j + 1)) die_nomem(fatal);
+ quoted.len = mysql_escape_string(quoted.s,comment,j); /* from */
+ if (!stralloc_cat(&logline,"ed)) die_nomem(fatal);
+ }
+ if (!stralloc_cats(&logline,"')")) die_nomem(fatal);
+
+ if (mysql_real_query((MYSQL *) psql,logline.s,logline.len))
+ ; /* log (ignore errors) */
+ if (!stralloc_0(&addr))
+ ; /* ignore errors */
+ log(dbname,event,addr.s,comment); /* also log to old log */
+ return 1; /* desired effect */
+ }
+}