chiark / gitweb /
Debianization and various other fixes.
[ezmlm] / sub_mysql / subscribe.c
1 /*$Id: subscribe.c,v 1.22 1999/11/10 04:08:27 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
3 #include "stralloc.h"
4 #include "getln.h"
5 #include "readwrite.h"
6 #include "substdio.h"
7 #include "strerr.h"
8 #include "open.h"
9 #include "byte.h"
10 #include "case.h"
11 #include "lock.h"
12 #include "error.h"
13 #include "subscribe.h"
14 #include "uint32.h"
15 #include "fmt.h"
16 #include "errtxt.h"
17 #include "log.h"
18 #include "idx.h"
19 #include <mysql.h>
20 #include <mysqld_error.h>
21
22 static void die_nomem(fatal)
23 char *fatal;
24 {
25   strerr_die2x(111,fatal,ERR_NOMEM);
26 }
27
28 static stralloc addr = {0};
29 static stralloc lcaddr = {0};
30 static stralloc line = {0};
31 static stralloc domain = {0};
32 static stralloc logline = {0};
33 static stralloc quoted = {0};
34 static stralloc fnnew = {0};
35 static stralloc fn = {0};
36 static stralloc fnlock = {0};
37 static char szh[FMT_ULONG];
38
39 void die_read(fatal)
40 char *fatal;
41 {
42   strerr_die4sys(111,fatal,ERR_READ,fn.s,": ");
43 }
44
45 void die_write(fatal)
46 char *fatal;
47 {
48   strerr_die4sys(111,fatal,ERR_WRITE,fnnew.s,": ");
49 }
50
51 static int fd;
52 static substdio ss;
53 static char ssbuf[256];
54 static int fdnew;
55 static substdio ssnew;
56 static char ssnewbuf[256];
57
58 int subscribe(dbname,userhost,flagadd,comment,event,flagmysql,
59         forcehash,tab,fatal)
60 /* add (flagadd=1) or remove (flagadd=0) userhost from the subscr. database  */
61 /* dbname. Comment is e.g. the subscriber from line or name. It is added to  */
62 /* the log. Event is the action type, e.g. "probe", "manual", etc. The       */
63 /* direction (sub/unsub) is inferred from flagadd. Returns 1 on success, 0   */
64 /* on failure. If flagmysql is set and the file "sql" is found in the        */
65 /* directory dbname, it is parsed and a mysql db is assumed. if forcehash is */
66 /* >=0 it is used in place of the calculated hash. This makes it possible to */
67 /* add addresses with a hash that does not exist. forcehash has to be 0..99. */
68 /* for unsubscribes, the address is only removed if forcehash matches the    */
69 /* actual hash. This way, ezmlm-manage can be prevented from touching certain*/
70 /* addresses that can only be removed by ezmlm-unsub. Usually, this would be */
71 /* used for sublist addresses (to avoid removal) and sublist aliases (to     */
72 /* prevent users from subscribing them (although the cookie mechanism would  */
73 /* prevent the resulting duplicate message from being distributed. */
74
75 char *dbname;
76 char *userhost;
77 int flagadd;
78 char *comment;
79 char *event;
80 int flagmysql;
81 int forcehash;
82 char *tab;
83 char *fatal;
84 {
85   int fdlock;
86
87   MYSQL_RES *result;
88   MYSQL_ROW row;
89   char *cp,*cpafter,*cpat;
90   char szhash[3] = "00";
91   char *r = (char *) 0;
92   char *table = (char *) 0;
93   char **ptable = &table;
94
95   unsigned int j;
96   uint32 h,lch;
97   unsigned char ch,lcch;
98   int match;
99   int flagwasthere;
100
101   if (userhost[str_chr(userhost,'\n')])
102     strerr_die2x(100,fatal,ERR_ADDR_NL);
103
104   if (tab) ptable = &tab;
105
106   if (!flagmysql || (r = opensql(dbname,ptable))) {
107     if (r && *r) strerr_die2x(111,fatal,r);
108                                                 /* fallback to local db */
109     if (!stralloc_copys(&addr,"T")) die_nomem(fatal);
110     if (!stralloc_cats(&addr,userhost)) die_nomem(fatal);
111     if (addr.len > 401)
112       strerr_die2x(100,fatal,ERR_ADDR_LONG);
113
114     j = byte_rchr(addr.s,addr.len,'@');
115     if (j == addr.len)
116       strerr_die2x(100,fatal,ERR_ADDR_AT);
117     case_lowerb(addr.s + j + 1,addr.len - j - 1);
118     if (!stralloc_copy(&lcaddr,&addr)) die_nomem(fatal);
119     case_lowerb(lcaddr.s + 1,j - 1);    /* make all-lc version of address */
120
121     if (forcehash >= 0 && forcehash <= 52) {
122       ch = lcch = (unsigned char) forcehash;
123     } else {
124       h = 5381;
125       lch = h;
126       for (j = 0;j < addr.len;++j) {
127         h = (h + (h << 5)) ^ (uint32) (unsigned char) addr.s[j];
128         lch = (lch + (lch << 5)) ^ (uint32) (unsigned char) lcaddr.s[j];
129       }
130       lcch = 64 + (lch % 53);
131       ch = 64 + (h % 53);
132     }
133
134     if (!stralloc_0(&addr)) die_nomem(fatal);
135     if (!stralloc_0(&lcaddr)) die_nomem(fatal);
136     if (!stralloc_copys(&fn,dbname)) die_nomem(fatal);
137     if (!stralloc_copys(&fnlock,dbname)) die_nomem(fatal);
138
139     if (!stralloc_cats(&fn,"/subscribers/")) die_nomem(fatal);
140     if (!stralloc_catb(&fn,&lcch,1)) die_nomem(fatal);
141     if (!stralloc_copy(&fnnew,&fn)) die_nomem(fatal);
142         /* code later depends on fnnew = fn + 'n' */
143     if (!stralloc_cats(&fnnew,"n")) die_nomem(fatal);
144     if (!stralloc_cats(&fnlock,"/lock")) die_nomem(fatal);
145     if (!stralloc_0(&fnnew)) die_nomem(fatal);
146     if (!stralloc_0(&fn)) die_nomem(fatal);
147     if (!stralloc_0(&fnlock)) die_nomem(fatal);
148
149     fdlock = open_append(fnlock.s);
150     if (fdlock == -1)
151       strerr_die4sys(111,fatal,ERR_OPEN,fnlock.s,": ");
152     if (lock_ex(fdlock) == -1)
153       strerr_die4sys(111,fatal,ERR_OBTAIN,fnlock.s,": ");
154
155                                 /* do lower case hashed version first */
156     fdnew = open_trunc(fnnew.s);
157     if (fdnew == -1) die_write(fatal);
158     substdio_fdbuf(&ssnew,write,fdnew,ssnewbuf,sizeof(ssnewbuf));
159
160     flagwasthere = 0;
161
162     fd = open_read(fn.s);
163     if (fd == -1) {
164       if (errno != error_noent) { close(fdnew); die_read(fatal); }
165     }
166     else {
167       substdio_fdbuf(&ss,read,fd,ssbuf,sizeof(ssbuf));
168
169       for (;;) {
170         if (getln(&ss,&line,&match,'\0') == -1) {
171           close(fd); close(fdnew); die_read(fatal);
172         }
173         if (!match) break;
174         if (line.len == addr.len)
175           if (!case_diffb(line.s,line.len,addr.s)) {
176             flagwasthere = 1;
177             if (!flagadd)
178               continue;
179           }
180         if (substdio_bput(&ssnew,line.s,line.len) == -1) {
181           close(fd); close(fdnew); die_write(fatal);
182         }
183       }
184
185       close(fd);
186     }
187
188     if (flagadd && !flagwasthere)
189       if (substdio_bput(&ssnew,addr.s,addr.len) == -1) {
190         close(fdnew); die_write(fatal);
191       }
192
193     if (substdio_flush(&ssnew) == -1) { close(fdnew); die_write(fatal); }
194     if (fsync(fdnew) == -1) { close(fdnew); die_write(fatal); }
195     close(fdnew);
196
197     if (rename(fnnew.s,fn.s) == -1)
198       strerr_die6sys(111,fatal,ERR_MOVE,fnnew.s," to ",fn.s,": ");
199
200     if ((ch == lcch) || flagwasthere) {
201       close(fdlock);
202       if (flagadd ^ flagwasthere) {
203         if (!stralloc_0(&addr)) die_nomem(fatal);
204         log(dbname,event,addr.s+1,comment);
205         return 1;
206       }
207       return 0;
208     }
209
210                         /* If unsub and not found and hashed differ, OR */
211                         /* sub and not found (so added with new hash) */
212                         /* do the 'case-dependent' hash */
213
214     fn.s[fn.len - 2] = ch;
215     fnnew.s[fnnew.len - 3] = ch;
216     fdnew = open_trunc(fnnew.s);
217     if (fdnew == -1) die_write(fatal);
218     substdio_fdbuf(&ssnew,write,fdnew,ssnewbuf,sizeof(ssnewbuf));
219
220     fd = open_read(fn.s);
221     if (fd == -1) {
222       if (errno != error_noent) { close(fdnew); die_read(fatal); }
223     } else {
224       substdio_fdbuf(&ss,read,fd,ssbuf,sizeof(ssbuf));
225
226       for (;;) {
227         if (getln(&ss,&line,&match,'\0') == -1)
228           { close(fd); close(fdnew); die_read(fatal); }
229         if (!match) break;
230         if (line.len == addr.len)
231           if (!case_diffb(line.s,line.len,addr.s)) {
232             flagwasthere = 1;
233             continue;   /* always want to remove from case-sensitive hash */
234           }
235         if (substdio_bput(&ssnew,line.s,line.len) == -1)
236           { close(fd); close(fdnew); die_write(fatal); }
237       }
238
239       close(fd);
240     }
241
242     if (substdio_flush(&ssnew) == -1) { close(fdnew); die_write(fatal); }
243     if (fsync(fdnew) == -1) { close(fdnew); die_write(fatal); }
244     close(fdnew);
245
246     if (rename(fnnew.s,fn.s) == -1)
247       strerr_die6sys(111,fatal,ERR_MOVE,fnnew.s," to ",fn.s,": ");
248
249     close(fdlock);
250     if (flagadd ^ flagwasthere) {
251       if (!stralloc_0(&addr)) die_nomem(fatal);
252       log(dbname,event,addr.s+1,comment);
253       return 1;
254     }
255     return 0;
256
257   } else {                              /* SQL version */
258     domain.len = 0;                     /* clear domain */
259                                         /* lowercase and check address */
260     if (!stralloc_copys(&addr,userhost)) die_nomem(fatal);
261     if (addr.len > 255)                 /* this is 401 in std ezmlm. 255 */
262                                         /* should be plenty! */
263       strerr_die2x(100,fatal,ERR_ADDR_LONG);
264     j = byte_rchr(addr.s,addr.len,'@');
265     if (j == addr.len)
266       strerr_die2x(100,fatal,ERR_ADDR_AT);
267     cpat = addr.s + j;
268     case_lowerb(cpat + 1,addr.len - j - 1);
269     if (!stralloc_ready(&quoted,2 * addr.len + 1)) die_nomem(fatal);
270     quoted.len = mysql_escape_string(quoted.s,addr.s,addr.len);
271         /* stored unescaped, so it should be ok if quoted.len is >255, as */
272         /* long as addr.len is not */
273
274     if (forcehash < 0) {
275       if (!stralloc_copy(&lcaddr,&addr)) die_nomem(fatal);
276       case_lowerb(lcaddr.s,j);          /* make all-lc version of address */
277       h = 5381;
278       for (j = 0;j < lcaddr.len;++j) {
279         h = (h + (h << 5)) ^ (uint32) (unsigned char) lcaddr.s[j];
280       }
281       ch = (h % 53);                    /* 0 - 52 */
282     } else
283       ch = (forcehash % 100);
284
285     szhash[0] = '0' + ch / 10;          /* hash for sublist split */
286     szhash[1] = '0' + (ch % 10);
287
288     if (flagadd) {
289       if (!stralloc_copys(&line,"LOCK TABLES ")) die_nomem(fatal);
290       if (!stralloc_cats(&line,table)) die_nomem(fatal);
291       if (!stralloc_cats(&line," WRITE")) die_nomem(fatal);
292       if (mysql_real_query((MYSQL *) psql,line.s,line.len))
293         strerr_die2x(111,fatal,mysql_error((MYSQL *) psql));
294       if (!stralloc_copys(&line,"SELECT address FROM ")) die_nomem(fatal);
295       if (!stralloc_cats(&line,table)) die_nomem(fatal);
296       if (!stralloc_cats(&line," WHERE address='")) die_nomem(fatal);
297       if (!stralloc_cat(&line,&quoted)) die_nomem(fatal);       /* addr */
298       if (!stralloc_cats(&line,"'")) die_nomem(fatal);
299       if (mysql_real_query((MYSQL *) psql,line.s,line.len))
300         strerr_die2x(111,fatal,mysql_error((MYSQL *) psql));
301       if (!(result = mysql_use_result((MYSQL *) psql)))
302         strerr_die2x(111,fatal,mysql_error((MYSQL *) psql));
303       if ((row = mysql_fetch_row(result))) {                    /* there */
304         while (mysql_fetch_row(result));                        /* use'm up */
305         mysql_free_result(result);
306         if (mysql_query((MYSQL *) psql,"UNLOCK TABLES"))
307           strerr_die2x(111,"fatal",mysql_error((MYSQL *) psql));
308         return 0;                                               /* there */
309       } else {                                                  /* not there */
310         mysql_free_result(result);
311         if (mysql_errno((MYSQL *) psql))                        /* or ERROR */
312           strerr_die2x(111,fatal,mysql_error((MYSQL *) psql));
313         if (!stralloc_copys(&line,"INSERT INTO ")) die_nomem(fatal);
314         if (!stralloc_cats(&line,table)) die_nomem(fatal);
315         if (!stralloc_cats(&line," (address,hash) VALUES ('"))
316                 die_nomem(fatal);
317         if (!stralloc_cat(&line,&quoted)) die_nomem(fatal);     /* addr */
318         if (!stralloc_cats(&line,"',")) die_nomem(fatal);
319         if (!stralloc_cats(&line,szhash)) die_nomem(fatal);     /* hash */
320         if (!stralloc_cats(&line,")")) die_nomem(fatal);
321         if (mysql_real_query((MYSQL *) psql,line.s,line.len))   /* INSERT */
322           strerr_die2x(111,fatal,mysql_error((MYSQL *) psql));
323         if (mysql_query((MYSQL *) psql,"UNLOCK TABLES"))
324           strerr_die2x(111,fatal,mysql_error((MYSQL *) psql));
325       }
326     } else {                                                    /* unsub */
327       if (!stralloc_copys(&line,"DELETE FROM ")) die_nomem(fatal);
328       if (!stralloc_cats(&line,table)) die_nomem(fatal);
329       if (!stralloc_cats(&line," WHERE address='")) die_nomem(fatal);
330       if (!stralloc_cat(&line,&quoted)) die_nomem(fatal);       /* addr */
331       if (forcehash >= 0) {
332         if (!stralloc_cats(&line,"' AND hash=")) die_nomem(fatal);
333         if (!stralloc_cats(&line,szhash)) die_nomem(fatal);
334       } else {
335         if (!stralloc_cats(&line,"' AND hash BETWEEN 0 AND 52"))
336                 die_nomem(fatal);
337       }
338       if (mysql_real_query((MYSQL *) psql,line.s,line.len))
339           strerr_die2x(111,fatal,mysql_error((MYSQL *) psql));
340       if (mysql_affected_rows((MYSQL *) psql) == 0)
341         return 0;                               /* address wasn't there*/
342     }
343
344                 /* log to subscriber log */
345                 /* INSERT INTO t_slog (address,edir,etype,fromline) */
346                 /* VALUES('address',{'+'|'-'},'etype','[comment]') */
347
348     if (!stralloc_copys(&logline,"INSERT INTO ")) die_nomem(fatal);
349     if (!stralloc_cats(&logline,table)) die_nomem(fatal);
350     if (!stralloc_cats(&logline,
351         "_slog (address,edir,etype,fromline) VALUES ('")) die_nomem(fatal);
352     if (!stralloc_cat(&logline,&quoted)) die_nomem(fatal);
353     if (flagadd) {                                              /* edir */
354       if (!stralloc_cats(&logline,"','+','")) die_nomem(fatal);
355     } else {
356       if (!stralloc_cats(&logline,"','-','")) die_nomem(fatal);
357     }
358     if (*(event + 1))   /* ezmlm-0.53 uses '' for ezmlm-manage's work */
359       if (!stralloc_catb(&logline,event+1,1)) die_nomem(fatal); /* etype */
360     if (!stralloc_cats(&logline,"','")) die_nomem(fatal);
361     if (comment && *comment) {
362         j = str_len(comment);
363         if (!stralloc_ready(&quoted,2 * j + 1)) die_nomem(fatal);
364         quoted.len = mysql_escape_string(quoted.s,comment,j);   /* from */
365         if (!stralloc_cat(&logline,&quoted)) die_nomem(fatal);
366     }
367     if (!stralloc_cats(&logline,"')")) die_nomem(fatal);
368
369     if (mysql_real_query((MYSQL *) psql,logline.s,logline.len))
370                 ;                               /* log (ignore errors) */
371     if (!stralloc_0(&addr))
372                 ;                               /* ignore errors */
373     log(dbname,event,addr.s,comment);           /* also log to old log */
374     return 1;                                   /* desired effect */
375   }
376 }