| #include "byte.h" |
| #include "case.h" |
| #include "cookie.h" |
| #include "date822fmt.h" |
| #include "datetime.h" |
| #include "die.h" |
| #include "fmt.h" |
| #include "log.h" |
| #include "messages.h" |
| #include "scan.h" |
| #include "stralloc.h" |
| #include "strerr.h" |
| #include "sub_sql.h" |
| #include "subdb.h" |
| #include "subhash.h" |
| |
| static stralloc addr; |
| static stralloc name; |
| static stralloc query; |
| static stralloc params[4]; |
| static stralloc lcaddr; |
| |
| static void die_write(void) |
| { |
| strerr_die2sys(111,FATAL,MSG(ERR_WRITE_STDOUT)); |
| } |
| |
| static void make_name(struct subdbinfo *info, |
| const char *suffix1, |
| const char *suffix2, |
| int terminate) |
| { |
| if (!stralloc_copys(&name,info->base_table)) die_nomem(); |
| if (suffix1 && !stralloc_cats(&name,suffix1)) die_nomem(); |
| if (suffix2 && !stralloc_cats(&name,suffix2)) die_nomem(); |
| if (terminate && !stralloc_0(&name)) die_nomem(); |
| } |
| |
| /* Checks the hash against the cookie table. If it matches, returns NULL, |
| * else returns "". If error, returns error string. */ |
| const char *sub_sql_checktag (struct subdbinfo *info, |
| unsigned long num, /* message number */ |
| unsigned long listno, /* bottom of range => slave */ |
| const char *action, |
| const char *seed, |
| const char *hash) /* cookie */ |
| { |
| void *result; |
| char strnum[FMT_ULONG]; |
| |
| /* SELECT msgnum FROM table_cookie WHERE msgnum=num and cookie='hash' */ |
| /* succeeds only is everything correct. */ |
| if (listno) { /* only for slaves */ |
| if (!stralloc_copyb(¶ms[0],strnum,fmt_ulong(strnum,listno))) die_nomem(); |
| if (!stralloc_copyb(¶ms[1],strnum,fmt_ulong(strnum,num))) die_nomem(); |
| if (!stralloc_copys(&query,"SELECT listno FROM ")) die_nomem(); |
| if (!stralloc_cats(&query,info->base_table)) die_nomem(); |
| if (!stralloc_cats(&query,"_mlog WHERE ")) die_nomem(); |
| if (!stralloc_cats(&query,sql_checktag_listno_where_defn)) die_nomem(); |
| |
| result = sql_select(info,&query,2,params); |
| if (sql_fetch_row(info,result,1,params)) { |
| sql_free_result(info,result); |
| return ""; /* already done */ |
| } |
| /* no result */ |
| sql_free_result(info,result); |
| } |
| |
| if (!stralloc_copyb(¶ms[0],strnum,fmt_ulong(strnum,num))) die_nomem(); |
| if (!stralloc_copyb(¶ms[1],hash,COOKIE)) die_nomem(); |
| |
| if (!stralloc_copys(&query,"SELECT msgnum FROM ")) die_nomem(); |
| if (!stralloc_cats(&query,info->base_table)) die_nomem(); |
| if (!stralloc_cats(&query,"_cookie WHERE ")) die_nomem(); |
| if (!stralloc_cats(&query,sql_checktag_msgnum_where_defn)) die_nomem(); |
| |
| result = sql_select(info,&query,2,params); |
| if (!sql_fetch_row(info,result,1,params)) { |
| sql_free_result(info,result); |
| return ""; /* not parent => perm error */ |
| } |
| sql_free_result(info,result); /* success! cookie matches */ |
| return (char *)0; |
| (void)action; |
| (void)seed; |
| } |
| |
| int sub_sql_issub(struct subdbinfo *info, |
| const char *table, |
| const char *userhost, |
| stralloc *recorded) |
| { |
| unsigned int j; |
| void *result; |
| int ret; |
| |
| /* SELECT address FROM list WHERE address = 'userhost' AND hash */ |
| /* BETWEEN 0 AND 52. Without the hash restriction, we'd make it */ |
| /* even easier to defeat. Just faking sender to the list name would*/ |
| /* work. Since sender checks for posts are bogus anyway, I don't */ |
| /* know if it's worth the cost of the "WHERE ...". */ |
| |
| make_name(info,table?"_":0,table,0); |
| |
| /* Lower-case the domain portion */ |
| if (!stralloc_copys(&addr,userhost)) die_nomem(); |
| j = byte_rchr(addr.s,addr.len,'@'); |
| if (j == addr.len) |
| return 0; |
| case_lowerb(addr.s + j + 1,addr.len - j - 1); |
| |
| if (!stralloc_copys(&query,"SELECT address FROM ")) die_nomem(); |
| if (!stralloc_cat(&query,&name)) die_nomem(); |
| if (!stralloc_cats(&query," WHERE ")) die_nomem(); |
| if (!stralloc_cats(&query,sql_issub_where_defn)) die_nomem(); |
| |
| result = sql_select(info,&query,1,&addr); |
| |
| if (!sql_fetch_row(info,result,1,&addr)) |
| ret = 0; |
| else { |
| /* we need to return the actual address as other dbs may accept |
| * user-*@host, but we still want to make sure to send to e.g the |
| * correct moderator address. */ |
| if (recorded != 0) { |
| if (!stralloc_copy(recorded,&addr)) die_nomem(); |
| if (!stralloc_0(recorded)) die_nomem(); |
| } |
| ret = 1; |
| } |
| sql_free_result(info,result); |
| return ret; |
| } |
| |
| /* Creates an entry for message num and the list listno and code "done". |
| * Returns NULL on success, and the error string on error. */ |
| const char *sub_sql_logmsg(struct subdbinfo *info, |
| unsigned long num, |
| unsigned long listno, |
| unsigned long subs, |
| int done) |
| { |
| char *s; |
| char strnum[FMT_ULONG]; |
| |
| if (!stralloc_copys(&query,"INSERT INTO ")) die_nomem(); |
| if (!stralloc_cats(&query,info->base_table)) die_nomem(); |
| if (!stralloc_cats(&query,"_mlog (msgnum,listno,subs,done) VALUES ")) |
| die_nomem(); |
| if (!stralloc_cats(&query,sql_logmsg_values_defn)) die_nomem(); |
| if (!stralloc_copyb(¶ms[0],strnum,fmt_ulong(strnum,num))) die_nomem(); |
| if (!stralloc_copyb(¶ms[1],strnum,fmt_ulong(strnum,listno))) |
| die_nomem(); |
| if (!stralloc_copyb(¶ms[2],strnum,fmt_ulong(strnum,subs))) die_nomem(); |
| s = strnum; |
| if (done < 0) { |
| done = - done; |
| *s++ = '-'; |
| } |
| s[fmt_uint(s,done)] = 0; |
| if (!stralloc_copys(¶ms[3],s)) die_nomem(); |
| |
| sql_exec(info,&query,4,params); /* ignore dups */ |
| return 0; |
| } |
| |
| /* Outputs all addresses in the table through subwrite. subwrite must be |
| * a function returning >=0 on success, -1 on error, and taking |
| * arguments (char* string, unsigned int length). It will be called once |
| * per address and should take care of newline or whatever needed for |
| * the output form. */ |
| unsigned long sub_sql_putsubs(struct subdbinfo *info, |
| const char *table, |
| unsigned long hash_lo, |
| unsigned long hash_hi, |
| int subwrite()) /* write function. */ |
| { |
| void *result; |
| unsigned long no = 0L; |
| char strnum[FMT_ULONG]; |
| |
| if (!stralloc_copyb(¶ms[0],strnum,fmt_ulong(strnum,hash_lo))) die_nomem(); |
| if (!stralloc_copyb(¶ms[1],strnum,fmt_ulong(strnum,hash_hi))) die_nomem(); |
| make_name(info,table?"_":0,table,0); |
| |
| /* main query */ |
| if (!stralloc_copys(&query,"SELECT address FROM ")) |
| die_nomem(); |
| if (!stralloc_cat(&query,&name)) die_nomem(); |
| if (!stralloc_cats(&query," WHERE ")) die_nomem(); |
| if (!stralloc_cats(&query,sql_putsubs_where_defn)) die_nomem(); |
| |
| result = sql_select(info,&query,2,params); |
| |
| no = 0; |
| while (sql_fetch_row(info,result,1,&addr)) { |
| if (subwrite(addr.s,addr.len) == -1) die_write(); |
| no++; /* count for list-list fxn */ |
| } |
| sql_free_result(info,result); |
| return no; |
| } |
| |
| /* Searches the subscriber log and outputs via subwrite(s,len) any entry |
| * that matches search. A '_' is search is a wildcard. Any other |
| * non-alphanum/'.' char is replaced by a '_'. */ |
| void sub_sql_searchlog(struct subdbinfo *info, |
| const char *table, |
| char *search, /* search string */ |
| int subwrite()) /* output fxn */ |
| { |
| void *result; |
| datetime_sec when; |
| struct datetime dt; |
| char date[DATE822FMT]; |
| int nparams; |
| char strnum[FMT_ULONG]; |
| |
| make_name(info,table?"_":0,table,0); |
| /* SELECT (*) FROM list_slog WHERE fromline LIKE '%search%' OR address */ |
| /* LIKE '%search%' ORDER BY tai; */ |
| /* The '*' is formatted to look like the output of the non-mysql version */ |
| /* This requires reading the entire table, since search fields are not */ |
| /* indexed, but this is a rare query and time is not of the essence. */ |
| |
| if (!stralloc_copys(&query,"SELECT ")) die_nomem(); |
| if (!stralloc_cats(&query,sql_searchlog_select_defn)) die_nomem(); |
| if (!stralloc_cats(&query," FROM ")) die_nomem(); |
| if (!stralloc_cat(&query,&name)) die_nomem(); |
| if (!stralloc_cats(&query,"_slog")) die_nomem(); |
| if (*search) { /* We can afford to wait for LIKE '%xx%' */ |
| if (!stralloc_copys(¶ms[0],search)) die_nomem(); |
| if (!stralloc_copys(¶ms[1],search)) die_nomem(); |
| nparams = 2; |
| if (!stralloc_cats(&query," WHERE ")) die_nomem(); |
| if (!stralloc_cats(&query,sql_searchlog_where_defn)) die_nomem(); |
| } |
| else |
| nparams = 0; |
| /* ordering by tai which is an index */ |
| if (!stralloc_cats(&query," ORDER by tai")) die_nomem(); |
| |
| result = sql_select(info,&query,nparams,params); |
| while (sql_fetch_row(info,result,2,params)) { |
| if (!stralloc_0(¶ms[0])) die_nomem(); |
| (void)scan_ulong(params[0].s,&when); |
| datetime_tai(&dt,when); |
| if (!stralloc_copyb(¶ms[0],date,date822fmt(date,&dt)-1)) die_nomem(); |
| if (!stralloc_cats(¶ms[0],": ")) die_nomem(); |
| if (!stralloc_catb(¶ms[0],strnum,fmt_ulong(strnum,when))) die_nomem(); |
| if (!stralloc_cats(¶ms[0]," ")) die_nomem(); |
| if (!stralloc_cat(¶ms[0],¶ms[1])) die_nomem(); |
| if (subwrite(params[0].s,params[0].len) == -1) die_write(); |
| } |
| sql_free_result(info,result); |
| } |
| |
| /* Add (flagadd=1) or remove (flagadd=0) userhost from the subscriber |
| * database table. 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 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. */ |
| int sub_sql_subscribe(struct subdbinfo *info, |
| const char *table, |
| const char *userhost, |
| int flagadd, |
| const char *comment, |
| const char *event, |
| int forcehash) |
| { |
| void *result; |
| char *cpat; |
| char szhash[3] = "00"; |
| |
| unsigned int j; |
| unsigned char ch; |
| int nparams; |
| |
| make_name(info,table?"_":0,table,0); |
| |
| /* lowercase and check address */ |
| if (!stralloc_copys(&addr,userhost)) die_nomem(); |
| if (addr.len > 255) /* this is 401 in std ezmlm. 255 */ |
| /* should be plenty! */ |
| strerr_die2x(100,FATAL,MSG(ERR_ADDR_LONG)); |
| j = byte_rchr(addr.s,addr.len,'@'); |
| if (j == addr.len) |
| strerr_die2x(100,FATAL,MSG(ERR_ADDR_AT)); |
| cpat = addr.s + j; |
| case_lowerb(cpat + 1,addr.len - j - 1); |
| |
| if (forcehash < 0) { |
| if (!stralloc_copy(&lcaddr,&addr)) die_nomem(); |
| case_lowerb(lcaddr.s,j); /* make all-lc version of address */ |
| ch = subhashsa(&lcaddr); |
| } else |
| ch = (forcehash % 100); |
| |
| szhash[0] = '0' + ch / 10; /* hash for sublist split */ |
| szhash[1] = '0' + (ch % 10); |
| |
| if (flagadd) { |
| /* FIXME: LOCK TABLES name WRITE */ |
| if (!stralloc_copys(&query,"SELECT address FROM ")) die_nomem(); |
| if (!stralloc_cat(&query,&name)) die_nomem(); |
| if (!stralloc_cats(&query," WHERE ")) die_nomem(); |
| if (!stralloc_cats(&query,sql_subscribe_select_where_defn)) die_nomem(); |
| if (!stralloc_copy(¶ms[0],&addr)) die_nomem(); |
| result = sql_select(info,&query,1,params); |
| if (sql_fetch_row(info,result,1,params)) { |
| sql_free_result(info,result); |
| /* FIXME: UNLOCK TABLES */ |
| return 0; /* already subscribed */ |
| } else { /* not there */ |
| sql_free_result(info,result); |
| |
| if (!stralloc_copys(&query,"INSERT INTO ")) die_nomem(); |
| if (!stralloc_cat(&query,&name)) die_nomem(); |
| if (!stralloc_cats(&query," (address,hash) VALUES ")) die_nomem(); |
| if (!stralloc_cats(&query,sql_subscribe_list_values_defn)) die_nomem(); |
| if (!stralloc_copy(¶ms[0],&addr)) die_nomem(); |
| if (!stralloc_copys(¶ms[1],szhash)) die_nomem(); |
| sql_exec(info,&query,2,params); |
| /* FIXME: UNLOCK TABLES */ |
| } |
| } else { /* unsub */ |
| if (!stralloc_copys(&query,"DELETE FROM ")) die_nomem(); |
| if (!stralloc_cat(&query,&name)) die_nomem(); |
| if (!stralloc_cats(&query," WHERE ")) die_nomem(); |
| if (!stralloc_copy(¶ms[0],&addr)) die_nomem(); |
| if (forcehash >= 0) { |
| if (!stralloc_cats(&query,sql_subscribe_delete2_where_defn)) die_nomem(); |
| if (!stralloc_copys(¶ms[1],szhash)) die_nomem(); |
| nparams = 2; |
| } |
| else { |
| if (!stralloc_cats(&query,sql_subscribe_delete1_where_defn)) die_nomem(); |
| nparams = 1; |
| } |
| if (sql_exec(info,&query,1,params) == 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(&query,"INSERT INTO ")) die_nomem(); |
| if (!stralloc_cat(&query,&name)) die_nomem(); |
| if (!stralloc_cats(&query,"_slog (address,edir,etype,fromline) VALUES ")) die_nomem(); |
| if (!stralloc_cats(&query,sql_subscribe_slog_values_defn)) die_nomem(); |
| if (!stralloc_copy(¶ms[0],&addr)) die_nomem(); |
| if (!stralloc_copys(¶ms[1],flagadd?"+":"-")) die_nomem(); /* edir */ |
| if (!stralloc_copyb(¶ms[2],event+1,!!*(event+1))) die_nomem(); /* etype */ |
| if (!stralloc_copys(¶ms[3],comment && *comment ? comment : "")) die_nomem(); /* from */ |
| |
| sql_exec(info,&query,4,params); /* log (ignore errors) */ |
| if (stralloc_0(&addr)) |
| logaddr(table,event,addr.s,comment); /* also log to old log */ |
| return 1; /* desired effect */ |
| } |
| |
| /* This routine inserts the cookie into table_cookie. We log arrival of |
| * the message (done=0). */ |
| void sub_sql_tagmsg(struct subdbinfo *info, |
| unsigned long msgnum, /* number of this message */ |
| const char *hashout, /* previously calculated hash */ |
| unsigned long bodysize, |
| unsigned long chunk) |
| { |
| const char *ret; |
| char strnum[FMT_ULONG]; |
| |
| if (chunk >= 53L) chunk = 0L; /* sanity */ |
| |
| /* INSERT INTO table_cookie (msgnum,tai,cookie,bodysize,chunk) VALUES (...) */ |
| /* (we may have tried message before, but failed to complete, so */ |
| /* ER_DUP_ENTRY is ok) */ |
| if (!stralloc_copys(&query,"INSERT INTO ")) die_nomem(); |
| if (!stralloc_cats(&query,info->base_table)) die_nomem(); |
| if (!stralloc_cats(&query,"_cookie (msgnum,tai,cookie,bodysize,chunk) VALUES ")) |
| die_nomem(); |
| if (!stralloc_cats(&query,sql_tagmsg_values_defn)) die_nomem(); |
| if (!stralloc_copyb(¶ms[0],strnum,fmt_ulong(strnum,msgnum))) die_nomem(); |
| if (!stralloc_copyb(¶ms[1],hashout,COOKIE)) die_nomem(); |
| if (!stralloc_copyb(¶ms[2],strnum,fmt_ulong(strnum,bodysize))) die_nomem(); |
| if (!stralloc_copyb(¶ms[3],strnum,fmt_ulong(strnum,chunk))) die_nomem(); |
| |
| sql_exec(info,&query,4,params); /* ignore dups */ |
| |
| if (! (ret = logmsg(msgnum,0L,0L,1))) |
| return; /* log done=1*/ |
| if (*ret) strerr_die2x(111,FATAL,ret); |
| } |
| |
| static const char *create_table(struct subdbinfo *info, |
| const char *suffix1, |
| const char *suffix2, |
| const char *definition) |
| { |
| make_name(info,suffix1,suffix2,1); |
| if (sql_table_exists(info,name.s) > 0) |
| return 0; |
| if (!stralloc_copys(&query,"CREATE TABLE ")) die_nomem(); |
| if (!stralloc_cats(&query,name.s)) die_nomem(); |
| if (!stralloc_cats(&query," (")) die_nomem(); |
| if (!stralloc_cats(&query,definition)) die_nomem(); |
| if (!stralloc_cats(&query,")")) die_nomem(); |
| if (!stralloc_0(&query)) die_nomem(); |
| return sql_create_table(info,query.s); |
| } |
| |
| static const char *create_table_set(struct subdbinfo *info, |
| const char *suffix, |
| int do_mlog) |
| { |
| const char *r; |
| |
| /* Address table */ |
| if ((r = create_table(info,suffix,"",sql_sub_table_defn)) != 0) |
| return r; |
| /* Subscription log table. */ |
| if ((r = create_table(info,suffix,"_slog",sql_slog_table_defn)) != 0) |
| return r; |
| |
| if (do_mlog) { |
| /* main list inserts a cookie here. Sublists check it */ |
| if ((r = create_table(info,suffix,"_cookie",sql_cookie_table_defn)) != 0) |
| return r; |
| |
| /* main and sublist log here when the message is done done=0 for |
| * arrived, done=4 for sent, 5 for receit. */ |
| if ((r = create_table(info,suffix,"_mlog",sql_mlog_table_defn)) != 0) |
| return r; |
| } |
| |
| return 0; |
| } |
| |
| const char *sub_sql_mktab(struct subdbinfo *info) |
| { |
| const char *r; |
| |
| if ((r = create_table_set(info,"",1)) != 0) |
| return r; |
| if ((r = create_table_set(info,"_allow",0)) != 0) |
| return r; |
| if ((r = create_table_set(info,"_deny",0)) != 0) |
| return r; |
| if ((r = create_table_set(info,"_digest",1)) != 0) |
| return r; |
| if ((r = create_table_set(info,"_mod",0)) != 0) |
| return r; |
| return 0; |
| } |
| |
| static const char *remove_table(struct subdbinfo *info, |
| const char *suffix1, |
| const char *suffix2) |
| { |
| make_name(info,suffix1,suffix2,1); |
| if (sql_table_exists(info,name.s) == 0) |
| return 0; |
| return sql_drop_table(info,name.s); |
| } |
| |
| static const char *remove_table_set(struct subdbinfo *info, |
| const char *suffix) |
| { |
| const char *r; |
| if ((r = remove_table(info,suffix,"_mlog")) != 0 |
| || (r = remove_table(info,suffix,"_cookie")) != 0 |
| || (r = remove_table(info,suffix,"_slog")) != 0 |
| || (r = remove_table(info,suffix,"")) != 0) |
| return r; |
| return 0; |
| } |
| |
| const char *sub_sql_rmtab(struct subdbinfo *info) |
| { |
| const char *r; |
| if ((r = remove_table_set(info,"")) != 0 |
| || (r = remove_table_set(info,"_allow")) != 0 |
| || (r = remove_table_set(info,"_deny")) != 0 |
| || (r = remove_table_set(info,"_digest")) != 0 |
| || (r = remove_table_set(info,"_mod")) != 0) |
| return r; |
| return 0; |
| } |