Commit | Line | Data |
---|---|---|
f8beb284 MW |
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("ed,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,"ed)) 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,"ed)) 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,"ed)) 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,"ed)) 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("ed,2 * j + 1)) die_nomem(fatal); | |
364 | quoted.len = mysql_escape_string(quoted.s,comment,j); /* from */ | |
365 | if (!stralloc_cat(&logline,"ed)) 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 | } |