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