Import ezmlm-idx 0.40
[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 }