1 /*$Id: subscribe.c,v 1.22 1999/11/10 04:08:27 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
13 #include "subscribe.h"
20 #include <mysqld_error.h>
22 static void die_nomem(fatal
)
25 strerr_die2x(111,fatal
,ERR_NOMEM
);
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
];
42 strerr_die4sys(111,fatal
,ERR_READ
,fn
.s
,": ");
48 strerr_die4sys(111,fatal
,ERR_WRITE
,fnnew
.s
,": ");
53 static char ssbuf
[256];
55 static substdio ssnew
;
56 static char ssnewbuf
[256];
58 int subscribe(dbname
,userhost
,flagadd
,comment
,event
,flagmysql
,
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. */
89 char *cp
,*cpafter
,*cpat
;
90 char szhash
[3] = "00";
92 char *table
= (char *) 0;
93 char **ptable
= &table
;
97 unsigned char ch
,lcch
;
101 if (userhost
[str_chr(userhost
,'\n')])
102 strerr_die2x(100,fatal
,ERR_ADDR_NL
);
104 if (tab
) ptable
= &tab
;
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
);
112 strerr_die2x(100,fatal
,ERR_ADDR_LONG
);
114 j
= byte_rchr(addr
.s
,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 */
121 if (forcehash
>= 0 && forcehash
<= 52) {
122 ch
= lcch
= (unsigned char) forcehash
;
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
];
130 lcch
= 64 + (lch
% 53);
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
);
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
);
149 fdlock
= open_append(fnlock
.s
);
151 strerr_die4sys(111,fatal
,ERR_OPEN
,fnlock
.s
,": ");
152 if (lock_ex(fdlock
) == -1)
153 strerr_die4sys(111,fatal
,ERR_OBTAIN
,fnlock
.s
,": ");
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
));
162 fd
= open_read(fn
.s
);
164 if (errno
!= error_noent
) { close(fdnew
); die_read(fatal
); }
167 substdio_fdbuf(&ss
,read
,fd
,ssbuf
,sizeof(ssbuf
));
170 if (getln(&ss
,&line
,&match
,'\0') == -1) {
171 close(fd
); close(fdnew
); die_read(fatal
);
174 if (line
.len
== addr
.len
)
175 if (!case_diffb(line
.s
,line
.len
,addr
.s
)) {
180 if (substdio_bput(&ssnew
,line
.s
,line
.len
) == -1) {
181 close(fd
); close(fdnew
); die_write(fatal
);
188 if (flagadd
&& !flagwasthere
)
189 if (substdio_bput(&ssnew
,addr
.s
,addr
.len
) == -1) {
190 close(fdnew
); die_write(fatal
);
193 if (substdio_flush(&ssnew
) == -1) { close(fdnew
); die_write(fatal
); }
194 if (fsync(fdnew
) == -1) { close(fdnew
); die_write(fatal
); }
197 if (rename(fnnew
.s
,fn
.s
) == -1)
198 strerr_die6sys(111,fatal
,ERR_MOVE
,fnnew
.s
," to ",fn
.s
,": ");
200 if ((ch
== lcch
) || flagwasthere
) {
202 if (flagadd
^ flagwasthere
) {
203 if (!stralloc_0(&addr
)) die_nomem(fatal
);
204 log(dbname
,event
,addr
.s
+1,comment
);
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 */
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
));
220 fd
= open_read(fn
.s
);
222 if (errno
!= error_noent
) { close(fdnew
); die_read(fatal
); }
224 substdio_fdbuf(&ss
,read
,fd
,ssbuf
,sizeof(ssbuf
));
227 if (getln(&ss
,&line
,&match
,'\0') == -1)
228 { close(fd
); close(fdnew
); die_read(fatal
); }
230 if (line
.len
== addr
.len
)
231 if (!case_diffb(line
.s
,line
.len
,addr
.s
)) {
233 continue; /* always want to remove from case-sensitive hash */
235 if (substdio_bput(&ssnew
,line
.s
,line
.len
) == -1)
236 { close(fd
); close(fdnew
); die_write(fatal
); }
242 if (substdio_flush(&ssnew
) == -1) { close(fdnew
); die_write(fatal
); }
243 if (fsync(fdnew
) == -1) { close(fdnew
); die_write(fatal
); }
246 if (rename(fnnew
.s
,fn
.s
) == -1)
247 strerr_die6sys(111,fatal
,ERR_MOVE
,fnnew
.s
," to ",fn
.s
,": ");
250 if (flagadd
^ flagwasthere
) {
251 if (!stralloc_0(&addr
)) die_nomem(fatal
);
252 log(dbname
,event
,addr
.s
+1,comment
);
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
,'@');
266 strerr_die2x(100,fatal
,ERR_ADDR_AT
);
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 */
275 if (!stralloc_copy(&lcaddr
,&addr
)) die_nomem(fatal
);
276 case_lowerb(lcaddr
.s
,j
); /* make all-lc version of address */
278 for (j
= 0;j
< lcaddr
.len
;++j
) {
279 h
= (h
+ (h
<< 5)) ^ (uint32
) (unsigned char) lcaddr
.s
[j
];
281 ch
= (h
% 53); /* 0 - 52 */
283 ch
= (forcehash
% 100);
285 szhash
[0] = '0' + ch
/ 10; /* hash for sublist split */
286 szhash
[1] = '0' + (ch
% 10);
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 ('"))
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
));
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
);
335 if (!stralloc_cats(&line
,"' AND hash BETWEEN 0 AND 52"))
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*/
344 /* log to subscriber log */
345 /* INSERT INTO t_slog (address,edir,etype,fromline) */
346 /* VALUES('address',{'+'|'-'},'etype','[comment]') */
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
);
356 if (!stralloc_cats(&logline
,"','-','")) die_nomem(fatal
);
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
);
367 if (!stralloc_cats(&logline
,"')")) die_nomem(fatal
);
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 */