|
fc5c7d2…
|
drh
|
1 |
/* |
|
fc5c7d2…
|
drh
|
2 |
** Copyright (c) 2018 D. Richard Hipp |
|
fc5c7d2…
|
drh
|
3 |
** |
|
fc5c7d2…
|
drh
|
4 |
** This program is free software; you can redistribute it and/or |
|
fc5c7d2…
|
drh
|
5 |
** modify it under the terms of the Simplified BSD License (also |
|
fc5c7d2…
|
drh
|
6 |
** known as the "2-Clause License" or "FreeBSD License".) |
|
fc5c7d2…
|
drh
|
7 |
** |
|
fc5c7d2…
|
drh
|
8 |
** This program is distributed in the hope that it will be useful, |
|
fc5c7d2…
|
drh
|
9 |
** but without any warranty; without even the implied warranty of |
|
fc5c7d2…
|
drh
|
10 |
** merchantability or fitness for a particular purpose. |
|
fc5c7d2…
|
drh
|
11 |
** |
|
fc5c7d2…
|
drh
|
12 |
** Author contact information: |
|
fc5c7d2…
|
drh
|
13 |
** [email protected] |
|
fc5c7d2…
|
drh
|
14 |
** http://www.hwaci.com/drh/ |
|
fc5c7d2…
|
drh
|
15 |
** |
|
fc5c7d2…
|
drh
|
16 |
******************************************************************************* |
|
fc5c7d2…
|
drh
|
17 |
** |
|
5d28db4…
|
drh
|
18 |
** Logic for email notification, also known as "alerts" or "subscriptions". |
|
fc5c7d2…
|
drh
|
19 |
** |
|
fc5c7d2…
|
drh
|
20 |
** Are you looking for the code that reads and writes the internet |
|
fc5c7d2…
|
drh
|
21 |
** email protocol? That is not here. See the "smtp.c" file instead. |
|
fc5c7d2…
|
drh
|
22 |
** Yes, the choice of source code filenames is not the greatest, but |
|
fc5c7d2…
|
drh
|
23 |
** it is not so bad that changing them seems justified. |
|
e7a5b98…
|
stephan
|
24 |
*/ |
|
fc5c7d2…
|
drh
|
25 |
#include "config.h" |
|
fc5c7d2…
|
drh
|
26 |
#include "alerts.h" |
|
fc5c7d2…
|
drh
|
27 |
#include <assert.h> |
|
fc5c7d2…
|
drh
|
28 |
#include <time.h> |
|
fc5c7d2…
|
drh
|
29 |
|
|
fc5c7d2…
|
drh
|
30 |
/* |
|
fc5c7d2…
|
drh
|
31 |
** Maximum size of the subscriberCode blob, in bytes |
|
fc5c7d2…
|
drh
|
32 |
*/ |
|
fc5c7d2…
|
drh
|
33 |
#define SUBSCRIBER_CODE_SZ 32 |
|
fc5c7d2…
|
drh
|
34 |
|
|
fc5c7d2…
|
drh
|
35 |
/* |
|
fc5c7d2…
|
drh
|
36 |
** SQL code to implement the tables needed by the email notification |
|
fc5c7d2…
|
drh
|
37 |
** system. |
|
fc5c7d2…
|
drh
|
38 |
*/ |
|
fc5c7d2…
|
drh
|
39 |
static const char zAlertInit[] = |
|
fc5c7d2…
|
drh
|
40 |
@ DROP TABLE IF EXISTS repository.subscriber; |
|
fc5c7d2…
|
drh
|
41 |
@ -- Subscribers are distinct from users. A person can have a log-in in |
|
fc5c7d2…
|
drh
|
42 |
@ -- the USER table without being a subscriber. Or a person can be a |
|
fc5c7d2…
|
drh
|
43 |
@ -- subscriber without having a USER table entry. Or they can have both. |
|
fc5c7d2…
|
drh
|
44 |
@ -- In the last case the suname column points from the subscriber entry |
|
fc5c7d2…
|
drh
|
45 |
@ -- to the USER entry. |
|
fc5c7d2…
|
drh
|
46 |
@ -- |
|
fc5c7d2…
|
drh
|
47 |
@ -- The ssub field is a string where each character indicates a particular |
|
fc5c7d2…
|
drh
|
48 |
@ -- type of event to subscribe to. Choices: |
|
fc5c7d2…
|
drh
|
49 |
@ -- a - Announcements |
|
fc5c7d2…
|
drh
|
50 |
@ -- c - Check-ins |
|
60d40d5…
|
drh
|
51 |
@ -- f - Forum posts |
|
bca95cb…
|
drh
|
52 |
@ -- k - ** Special: Unsubscribed using /oneclickunsub |
|
d4361f6…
|
drh
|
53 |
@ -- n - New forum threads |
|
d4361f6…
|
drh
|
54 |
@ -- r - Replies to my own forum posts |
|
fc5c7d2…
|
drh
|
55 |
@ -- t - Ticket changes |
|
36f72c0…
|
stephan
|
56 |
@ -- u - Changes of users' permissions (admins only) |
|
fc5c7d2…
|
drh
|
57 |
@ -- w - Wiki changes |
|
d4361f6…
|
drh
|
58 |
@ -- x - Edits to forum posts |
|
fc5c7d2…
|
drh
|
59 |
@ -- Probably different codes will be added in the future. In the future |
|
fc5c7d2…
|
drh
|
60 |
@ -- we might also add a separate table that allows subscribing to email |
|
fc5c7d2…
|
drh
|
61 |
@ -- notifications for specific branches or tags or tickets. |
|
fc5c7d2…
|
drh
|
62 |
@ -- |
|
fc5c7d2…
|
drh
|
63 |
@ CREATE TABLE repository.subscriber( |
|
fc5c7d2…
|
drh
|
64 |
@ subscriberId INTEGER PRIMARY KEY, -- numeric subscriber ID. Internal use |
|
fc5c7d2…
|
drh
|
65 |
@ subscriberCode BLOB DEFAULT (randomblob(32)) UNIQUE, -- UUID for subscriber |
|
fc5c7d2…
|
drh
|
66 |
@ semail TEXT UNIQUE COLLATE nocase,-- email address |
|
fc5c7d2…
|
drh
|
67 |
@ suname TEXT, -- corresponding USER entry |
|
fc5c7d2…
|
drh
|
68 |
@ sverified BOOLEAN DEFAULT true, -- email address verified |
|
e7a5b98…
|
stephan
|
69 |
@ sdonotcall BOOLEAN, -- true for Do Not Call |
|
fc5c7d2…
|
drh
|
70 |
@ sdigest BOOLEAN, -- true for daily digests only |
|
fc5c7d2…
|
drh
|
71 |
@ ssub TEXT, -- baseline subscriptions |
|
fc5c7d2…
|
drh
|
72 |
@ sctime INTDATE, -- When this entry was created. unixtime |
|
fc5c7d2…
|
drh
|
73 |
@ mtime INTDATE, -- Last change. unixtime |
|
d7e10ce…
|
drh
|
74 |
@ smip TEXT, -- IP address of last change |
|
d7e10ce…
|
drh
|
75 |
@ lastContact INT -- Last contact. days since 1970 |
|
fc5c7d2…
|
drh
|
76 |
@ ); |
|
fc5c7d2…
|
drh
|
77 |
@ CREATE INDEX repository.subscriberUname |
|
fc5c7d2…
|
drh
|
78 |
@ ON subscriber(suname) WHERE suname IS NOT NULL; |
|
e7a5b98…
|
stephan
|
79 |
@ |
|
fc5c7d2…
|
drh
|
80 |
@ DROP TABLE IF EXISTS repository.pending_alert; |
|
fc5c7d2…
|
drh
|
81 |
@ -- Email notifications that need to be sent. |
|
fc5c7d2…
|
drh
|
82 |
@ -- |
|
fc5c7d2…
|
drh
|
83 |
@ -- The first character of the eventid determines the event type. |
|
fc5c7d2…
|
drh
|
84 |
@ -- Remaining characters determine the specific event. For example, |
|
fc5c7d2…
|
drh
|
85 |
@ -- 'c4413' means check-in with rid=4413. |
|
fc5c7d2…
|
drh
|
86 |
@ -- |
|
fc5c7d2…
|
drh
|
87 |
@ CREATE TABLE repository.pending_alert( |
|
fc5c7d2…
|
drh
|
88 |
@ eventid TEXT PRIMARY KEY, -- Object that changed |
|
fc5c7d2…
|
drh
|
89 |
@ sentSep BOOLEAN DEFAULT false, -- individual alert sent |
|
fc5c7d2…
|
drh
|
90 |
@ sentDigest BOOLEAN DEFAULT false, -- digest alert sent |
|
fc5c7d2…
|
drh
|
91 |
@ sentMod BOOLEAN DEFAULT false -- pending moderation alert sent |
|
fc5c7d2…
|
drh
|
92 |
@ ) WITHOUT ROWID; |
|
e0576ea…
|
stephan
|
93 |
@ |
|
d7e10ce…
|
drh
|
94 |
@ -- Obsolete table. No longer used. |
|
fc5c7d2…
|
drh
|
95 |
@ DROP TABLE IF EXISTS repository.alert_bounce; |
|
fc5c7d2…
|
drh
|
96 |
; |
|
fc5c7d2…
|
drh
|
97 |
|
|
fc5c7d2…
|
drh
|
98 |
/* |
|
fc5c7d2…
|
drh
|
99 |
** Return true if the email notification tables exist. |
|
fc5c7d2…
|
drh
|
100 |
*/ |
|
fc5c7d2…
|
drh
|
101 |
int alert_tables_exist(void){ |
|
fc5c7d2…
|
drh
|
102 |
return db_table_exists("repository", "subscriber"); |
|
d7e10ce…
|
drh
|
103 |
} |
|
d7e10ce…
|
drh
|
104 |
|
|
d7e10ce…
|
drh
|
105 |
/* |
|
d7e10ce…
|
drh
|
106 |
** Record the fact that user zUser has made contact with the repository. |
|
d7e10ce…
|
drh
|
107 |
** This resets the subscription timeout on that user. |
|
d7e10ce…
|
drh
|
108 |
*/ |
|
d7e10ce…
|
drh
|
109 |
void alert_user_contact(const char *zUser){ |
|
d7e10ce…
|
drh
|
110 |
if( db_table_has_column("repository","subscriber","lastContact") ){ |
|
db16262…
|
drh
|
111 |
db_unprotect(PROTECT_READONLY); |
|
d7e10ce…
|
drh
|
112 |
db_multi_exec( |
|
d7e10ce…
|
drh
|
113 |
"UPDATE subscriber SET lastContact=now()/86400 WHERE suname=%Q", |
|
d7e10ce…
|
drh
|
114 |
zUser |
|
d7e10ce…
|
drh
|
115 |
); |
|
db16262…
|
drh
|
116 |
db_protect_pop(); |
|
d7e10ce…
|
drh
|
117 |
} |
|
fc5c7d2…
|
drh
|
118 |
} |
|
fc5c7d2…
|
drh
|
119 |
|
|
fc5c7d2…
|
drh
|
120 |
/* |
|
fc5c7d2…
|
drh
|
121 |
** Make sure the table needed for email notification exist in the repository. |
|
fc5c7d2…
|
drh
|
122 |
** |
|
fc5c7d2…
|
drh
|
123 |
** If the bOnlyIfEnabled option is true, then tables are only created |
|
fc5c7d2…
|
drh
|
124 |
** if the email-send-method is something other than "off". |
|
fc5c7d2…
|
drh
|
125 |
*/ |
|
fc5c7d2…
|
drh
|
126 |
void alert_schema(int bOnlyIfEnabled){ |
|
fc5c7d2…
|
drh
|
127 |
if( !alert_tables_exist() ){ |
|
fc5c7d2…
|
drh
|
128 |
if( bOnlyIfEnabled |
|
eb804dc…
|
drh
|
129 |
&& fossil_strcmp(db_get("email-send-method",0),"off")==0 |
|
fc5c7d2…
|
drh
|
130 |
){ |
|
fc5c7d2…
|
drh
|
131 |
return; /* Don't create table for disabled email */ |
|
fc5c7d2…
|
drh
|
132 |
} |
|
0ea56bb…
|
drh
|
133 |
db_exec_sql(zAlertInit); |
|
d7e10ce…
|
drh
|
134 |
return; |
|
0ea56bb…
|
drh
|
135 |
} |
|
d7e10ce…
|
drh
|
136 |
if( db_table_has_column("repository","subscriber","lastContact") ){ |
|
d7e10ce…
|
drh
|
137 |
return; |
|
d7e10ce…
|
drh
|
138 |
} |
|
f33976f…
|
drh
|
139 |
db_unprotect(PROTECT_READONLY); |
|
d7e10ce…
|
drh
|
140 |
db_multi_exec( |
|
1e5dc32…
|
mistachkin
|
141 |
"DROP TABLE IF EXISTS repository.alert_bounce;\n" |
|
d7e10ce…
|
drh
|
142 |
"ALTER TABLE repository.subscriber ADD COLUMN lastContact INT;\n" |
|
d7e10ce…
|
drh
|
143 |
"UPDATE subscriber SET lastContact=mtime/86400;" |
|
d7e10ce…
|
drh
|
144 |
); |
|
f33976f…
|
drh
|
145 |
db_protect_pop(); |
|
d7e10ce…
|
drh
|
146 |
if( db_table_has_column("repository","pending_alert","sentMod") ){ |
|
d7e10ce…
|
drh
|
147 |
return; |
|
d7e10ce…
|
drh
|
148 |
} |
|
d7e10ce…
|
drh
|
149 |
db_multi_exec( |
|
d7e10ce…
|
drh
|
150 |
"ALTER TABLE repository.pending_alert" |
|
d7e10ce…
|
drh
|
151 |
" ADD COLUMN sentMod BOOLEAN DEFAULT false;" |
|
d7e10ce…
|
drh
|
152 |
); |
|
974cf36…
|
drh
|
153 |
} |
|
974cf36…
|
drh
|
154 |
|
|
974cf36…
|
drh
|
155 |
/* |
|
c3ed243…
|
drh
|
156 |
** Process deferred alert events. Return the number of errors. |
|
974cf36…
|
drh
|
157 |
*/ |
|
974cf36…
|
drh
|
158 |
static int alert_process_deferred_triggers(void){ |
|
974cf36…
|
drh
|
159 |
if( db_table_exists("temp","deferred_chat_events") |
|
974cf36…
|
drh
|
160 |
&& db_table_exists("repository","chat") |
|
974cf36…
|
drh
|
161 |
){ |
|
974cf36…
|
drh
|
162 |
const char *zChatUser = db_get("chat-timeline-user", 0); |
|
974cf36…
|
drh
|
163 |
if( zChatUser && zChatUser[0] ){ |
|
614b3f7…
|
drh
|
164 |
chat_create_tables(); /* Make sure TEMP TRIGGERs for FTS exist */ |
|
974cf36…
|
drh
|
165 |
db_multi_exec( |
|
e1ad499…
|
stephan
|
166 |
"INSERT INTO chat(mtime,lmtime,xfrom,xmsg)" |
|
e1ad499…
|
stephan
|
167 |
" SELECT julianday(), " |
|
e1ad499…
|
stephan
|
168 |
" strftime('%%Y-%%m-%%dT%%H:%%M:%%S','now','localtime')," |
|
e1ad499…
|
stephan
|
169 |
" %Q," |
|
974cf36…
|
drh
|
170 |
" chat_msg_from_event(type, objid, user, comment)\n" |
|
974cf36…
|
drh
|
171 |
" FROM deferred_chat_events;\n", |
|
974cf36…
|
drh
|
172 |
zChatUser |
|
974cf36…
|
drh
|
173 |
); |
|
974cf36…
|
drh
|
174 |
} |
|
974cf36…
|
drh
|
175 |
} |
|
974cf36…
|
drh
|
176 |
return 0; |
|
e9d7cf3…
|
drh
|
177 |
} |
|
e9d7cf3…
|
drh
|
178 |
|
|
e9d7cf3…
|
drh
|
179 |
/* |
|
fc5c7d2…
|
drh
|
180 |
** Enable triggers that automatically populate the pending_alert |
|
e9d7cf3…
|
drh
|
181 |
** table. (Later:) Also add triggers that automatically relay timeline |
|
e9d7cf3…
|
drh
|
182 |
** events to chat, if chat is configured for that. |
|
fc5c7d2…
|
drh
|
183 |
*/ |
|
169ba8d…
|
drh
|
184 |
void alert_create_trigger(void){ |
|
e9d7cf3…
|
drh
|
185 |
if( db_table_exists("repository","pending_alert") ){ |
|
e9d7cf3…
|
drh
|
186 |
db_multi_exec( |
|
e9d7cf3…
|
drh
|
187 |
"DROP TRIGGER IF EXISTS repository.alert_trigger1;\n" /* Purge legacy */ |
|
e9d7cf3…
|
drh
|
188 |
"CREATE TRIGGER temp.alert_trigger1\n" |
|
e9d7cf3…
|
drh
|
189 |
"AFTER INSERT ON repository.event BEGIN\n" |
|
e9d7cf3…
|
drh
|
190 |
" INSERT INTO pending_alert(eventid)\n" |
|
e9d7cf3…
|
drh
|
191 |
" SELECT printf('%%.1c%%d',new.type,new.objid) WHERE true\n" |
|
e9d7cf3…
|
drh
|
192 |
" ON CONFLICT(eventId) DO NOTHING;\n" |
|
e9d7cf3…
|
drh
|
193 |
"END;" |
|
e9d7cf3…
|
drh
|
194 |
); |
|
e9d7cf3…
|
drh
|
195 |
} |
|
974cf36…
|
drh
|
196 |
if( db_table_exists("repository","chat") |
|
3f6aa94…
|
stephan
|
197 |
&& db_get("chat-timeline-user", "")[0]!=0 |
|
974cf36…
|
drh
|
198 |
){ |
|
974cf36…
|
drh
|
199 |
/* Record events that will be relayed to chat, but do not relay |
|
974cf36…
|
drh
|
200 |
** them immediately, as the chat_msg_from_event() function requires |
|
974cf36…
|
drh
|
201 |
** that TAGXREF be up-to-date, and that has not happened yet when |
|
c3ed243…
|
drh
|
202 |
** the insert into the EVENT table occurs. Make arrangements to |
|
c3ed243…
|
drh
|
203 |
** invoke alert_process_deferred_triggers() when the transaction |
|
c3ed243…
|
drh
|
204 |
** commits. The TAGXREF table will be ready by then. */ |
|
974cf36…
|
drh
|
205 |
db_multi_exec( |
|
974cf36…
|
drh
|
206 |
"CREATE TABLE temp.deferred_chat_events(\n" |
|
974cf36…
|
drh
|
207 |
" type TEXT,\n" |
|
974cf36…
|
drh
|
208 |
" objid INT,\n" |
|
974cf36…
|
drh
|
209 |
" user TEXT,\n" |
|
974cf36…
|
drh
|
210 |
" comment TEXT\n" |
|
974cf36…
|
drh
|
211 |
");\n" |
|
974cf36…
|
drh
|
212 |
"CREATE TRIGGER temp.chat_trigger1\n" |
|
974cf36…
|
drh
|
213 |
"AFTER INSERT ON repository.event BEGIN\n" |
|
974cf36…
|
drh
|
214 |
" INSERT INTO deferred_chat_events" |
|
974cf36…
|
drh
|
215 |
" VALUES(new.type,new.objid,new.user,new.comment);\n" |
|
974cf36…
|
drh
|
216 |
"END;\n" |
|
974cf36…
|
drh
|
217 |
); |
|
974cf36…
|
drh
|
218 |
db_commit_hook(alert_process_deferred_triggers, 1); |
|
e9d7cf3…
|
drh
|
219 |
} |
|
fc5c7d2…
|
drh
|
220 |
} |
|
fc5c7d2…
|
drh
|
221 |
|
|
fc5c7d2…
|
drh
|
222 |
/* |
|
e9d7cf3…
|
drh
|
223 |
** Disable triggers the event_pending and chat triggers. |
|
fc5c7d2…
|
drh
|
224 |
** |
|
fc5c7d2…
|
drh
|
225 |
** This must be called before rebuilding the EVENT table, for example |
|
fc5c7d2…
|
drh
|
226 |
** via the "fossil rebuild" command. |
|
fc5c7d2…
|
drh
|
227 |
*/ |
|
169ba8d…
|
drh
|
228 |
void alert_drop_trigger(void){ |
|
fc5c7d2…
|
drh
|
229 |
db_multi_exec( |
|
169ba8d…
|
drh
|
230 |
"DROP TRIGGER IF EXISTS temp.alert_trigger1;\n" |
|
169ba8d…
|
drh
|
231 |
"DROP TRIGGER IF EXISTS repository.alert_trigger1;\n" /* Purge legacy */ |
|
e9d7cf3…
|
drh
|
232 |
"DROP TRIGGER IF EXISTS temp.chat_trigger1;\n" |
|
fc5c7d2…
|
drh
|
233 |
); |
|
fc5c7d2…
|
drh
|
234 |
} |
|
fc5c7d2…
|
drh
|
235 |
|
|
fc5c7d2…
|
drh
|
236 |
/* |
|
fc5c7d2…
|
drh
|
237 |
** Return true if email alerts are active. |
|
fc5c7d2…
|
drh
|
238 |
*/ |
|
fc5c7d2…
|
drh
|
239 |
int alert_enabled(void){ |
|
fc5c7d2…
|
drh
|
240 |
if( !alert_tables_exist() ) return 0; |
|
eb804dc…
|
drh
|
241 |
if( fossil_strcmp(db_get("email-send-method",0),"off")==0 ) return 0; |
|
fc5c7d2…
|
drh
|
242 |
return 1; |
|
974cf36…
|
drh
|
243 |
} |
|
974cf36…
|
drh
|
244 |
|
|
974cf36…
|
drh
|
245 |
/* |
|
3f6aa94…
|
stephan
|
246 |
** If alerts are enabled, removes the pending_alert entry which |
|
3f6aa94…
|
stephan
|
247 |
** matches (eventType || rid). Note that pending_alert entries are |
|
3f6aa94…
|
stephan
|
248 |
** added via the manifest crosslinking process, so this has no effect |
|
3f6aa94…
|
stephan
|
249 |
** if called before crosslinking is performed. Because alerts are sent |
|
3f6aa94…
|
stephan
|
250 |
** asynchronously, unqueuing needs to be performed as part of the |
|
3f6aa94…
|
stephan
|
251 |
** transaction in which crosslinking is performed in order to avoid a |
|
3f6aa94…
|
stephan
|
252 |
** race condition. |
|
3f6aa94…
|
stephan
|
253 |
*/ |
|
3f6aa94…
|
stephan
|
254 |
void alert_unqueue(char eventType, int rid){ |
|
3f6aa94…
|
stephan
|
255 |
if( alert_enabled() ){ |
|
3f6aa94…
|
stephan
|
256 |
db_multi_exec("DELETE FROM pending_alert WHERE eventid='%c%d'", |
|
3f6aa94…
|
stephan
|
257 |
eventType, rid); |
|
3f6aa94…
|
stephan
|
258 |
} |
|
3f6aa94…
|
stephan
|
259 |
} |
|
3f6aa94…
|
stephan
|
260 |
|
|
3f6aa94…
|
stephan
|
261 |
/* |
|
fc5c7d2…
|
drh
|
262 |
** If the subscriber table does not exist, then paint an error message |
|
fc5c7d2…
|
drh
|
263 |
** web page and return true. |
|
fc5c7d2…
|
drh
|
264 |
** |
|
fc5c7d2…
|
drh
|
265 |
** If the subscriber table does exist, return 0 without doing anything. |
|
fc5c7d2…
|
drh
|
266 |
*/ |
|
fc5c7d2…
|
drh
|
267 |
static int alert_webpages_disabled(void){ |
|
fc5c7d2…
|
drh
|
268 |
if( alert_tables_exist() ) return 0; |
|
112c713…
|
drh
|
269 |
style_set_current_feature("alerts"); |
|
fc5c7d2…
|
drh
|
270 |
style_header("Email Alerts Are Disabled"); |
|
fc5c7d2…
|
drh
|
271 |
@ <p>Email alerts are disabled on this server</p> |
|
112c713…
|
drh
|
272 |
style_finish_page(); |
|
fc5c7d2…
|
drh
|
273 |
return 1; |
|
fc5c7d2…
|
drh
|
274 |
} |
|
fc5c7d2…
|
drh
|
275 |
|
|
fc5c7d2…
|
drh
|
276 |
/* |
|
fc5c7d2…
|
drh
|
277 |
** Insert a "Subscriber List" submenu link if the current user |
|
fc5c7d2…
|
drh
|
278 |
** is an administrator. |
|
fc5c7d2…
|
drh
|
279 |
*/ |
|
fc5c7d2…
|
drh
|
280 |
void alert_submenu_common(void){ |
|
fc5c7d2…
|
drh
|
281 |
if( g.perm.Admin ){ |
|
fc5c7d2…
|
drh
|
282 |
if( fossil_strcmp(g.zPath,"subscribers") ){ |
|
54a6f09…
|
drh
|
283 |
style_submenu_element("Subscribers","%R/subscribers"); |
|
fc5c7d2…
|
drh
|
284 |
} |
|
fc5c7d2…
|
drh
|
285 |
if( fossil_strcmp(g.zPath,"subscribe") ){ |
|
fc5c7d2…
|
drh
|
286 |
style_submenu_element("Add New Subscriber","%R/subscribe"); |
|
54a6f09…
|
drh
|
287 |
} |
|
4859a91…
|
drh
|
288 |
if( fossil_strcmp(g.zPath,"setup_notification") ){ |
|
4859a91…
|
drh
|
289 |
style_submenu_element("Notification Setup","%R/setup_notification"); |
|
4859a91…
|
drh
|
290 |
} |
|
fc5c7d2…
|
drh
|
291 |
} |
|
fc5c7d2…
|
drh
|
292 |
} |
|
fc5c7d2…
|
drh
|
293 |
|
|
fc5c7d2…
|
drh
|
294 |
|
|
fc5c7d2…
|
drh
|
295 |
/* |
|
fc5c7d2…
|
drh
|
296 |
** WEBPAGE: setup_notification |
|
fc5c7d2…
|
drh
|
297 |
** |
|
fc5c7d2…
|
drh
|
298 |
** Administrative page for configuring and controlling email notification. |
|
fc5c7d2…
|
drh
|
299 |
** Normally accessible via the /Admin/Notification menu. |
|
fc5c7d2…
|
drh
|
300 |
*/ |
|
fc5c7d2…
|
drh
|
301 |
void setup_notification(void){ |
|
fc5c7d2…
|
drh
|
302 |
static const char *const azSendMethods[] = { |
|
fc5c7d2…
|
drh
|
303 |
"off", "Disabled", |
|
cad72b7…
|
drh
|
304 |
"relay", "SMTP relay", |
|
fc5c7d2…
|
drh
|
305 |
"db", "Store in a database", |
|
fc5c7d2…
|
drh
|
306 |
"dir", "Store in a directory", |
|
cad72b7…
|
drh
|
307 |
"pipe", "Pipe to a command", |
|
fc5c7d2…
|
drh
|
308 |
}; |
|
fc5c7d2…
|
drh
|
309 |
login_check_credentials(); |
|
fc5c7d2…
|
drh
|
310 |
if( !g.perm.Setup ){ |
|
fc5c7d2…
|
drh
|
311 |
login_needed(0); |
|
fc5c7d2…
|
drh
|
312 |
return; |
|
fc5c7d2…
|
drh
|
313 |
} |
|
e1962ef…
|
drh
|
314 |
db_begin_transaction(); |
|
fc5c7d2…
|
drh
|
315 |
|
|
fc5c7d2…
|
drh
|
316 |
alert_submenu_common(); |
|
fc5c7d2…
|
drh
|
317 |
style_submenu_element("Send Announcement","%R/announce"); |
|
112c713…
|
drh
|
318 |
style_set_current_feature("alerts"); |
|
fc5c7d2…
|
drh
|
319 |
style_header("Email Notification Setup"); |
|
cad72b7…
|
drh
|
320 |
@ <form action="%R/setup_notification" method="post"><div> |
|
cad72b7…
|
drh
|
321 |
@ <h1>Status   <input type="submit" name="submit" value="Refresh"></h1> |
|
cad72b7…
|
drh
|
322 |
@ </form> |
|
fc5c7d2…
|
drh
|
323 |
@ <table class="label-value"> |
|
fc5c7d2…
|
drh
|
324 |
if( alert_enabled() ){ |
|
fc5c7d2…
|
drh
|
325 |
stats_for_email(); |
|
fc5c7d2…
|
drh
|
326 |
}else{ |
|
fc5c7d2…
|
drh
|
327 |
@ <th>Disabled</th> |
|
fc5c7d2…
|
drh
|
328 |
} |
|
fc5c7d2…
|
drh
|
329 |
@ </table> |
|
fc5c7d2…
|
drh
|
330 |
@ <hr> |
|
fc5c7d2…
|
drh
|
331 |
@ <form action="%R/setup_notification" method="post"><div> |
|
cad72b7…
|
drh
|
332 |
@ <h1> Configuration </h1> |
|
cad72b7…
|
drh
|
333 |
@ <p><input type="submit" name="submit" value="Apply Changes"></p> |
|
cad72b7…
|
drh
|
334 |
@ <hr> |
|
fc5c7d2…
|
drh
|
335 |
login_insert_csrf_secret(); |
|
fc5c7d2…
|
drh
|
336 |
|
|
fc5c7d2…
|
drh
|
337 |
entry_attribute("Canonical Server URL", 40, "email-url", |
|
fc5c7d2…
|
drh
|
338 |
"eurl", "", 0); |
|
fc5c7d2…
|
drh
|
339 |
@ <p><b>Required.</b> |
|
fc85382…
|
stephan
|
340 |
@ This URL is used as the basename for hyperlinks included in |
|
fc5c7d2…
|
drh
|
341 |
@ email alert text. Omit the trailing "/". |
|
fc5c7d2…
|
drh
|
342 |
@ Suggested value: "%h(g.zBaseURL)" |
|
fc5c7d2…
|
drh
|
343 |
@ (Property: "email-url")</p> |
|
60d40d5…
|
drh
|
344 |
@ <hr> |
|
60d40d5…
|
drh
|
345 |
|
|
60d40d5…
|
drh
|
346 |
entry_attribute("Administrator email address", 40, "email-admin", |
|
60d40d5…
|
drh
|
347 |
"eadmin", "", 0); |
|
60d40d5…
|
drh
|
348 |
@ <p>This is the email for the human administrator for the system. |
|
60d40d5…
|
drh
|
349 |
@ Abuse and trouble reports and password reset requests are send here. |
|
60d40d5…
|
drh
|
350 |
@ (Property: "email-admin")</p> |
|
fc5c7d2…
|
drh
|
351 |
@ <hr> |
|
fc5c7d2…
|
drh
|
352 |
|
|
fc5c7d2…
|
drh
|
353 |
entry_attribute("\"Return-Path\" email address", 20, "email-self", |
|
fc5c7d2…
|
drh
|
354 |
"eself", "", 0); |
|
fc5c7d2…
|
drh
|
355 |
@ <p><b>Required.</b> |
|
fc5c7d2…
|
drh
|
356 |
@ This is the email to which email notification bounces should be sent. |
|
fc5c7d2…
|
drh
|
357 |
@ In cases where the email notification does not align with a specific |
|
fc5c7d2…
|
drh
|
358 |
@ Fossil login account (for example, digest messages), this is also |
|
fc5c7d2…
|
drh
|
359 |
@ the "From:" address of the email notification. |
|
fc5c7d2…
|
drh
|
360 |
@ The system administrator should arrange for emails sent to this address |
|
fc5c7d2…
|
drh
|
361 |
@ to be handed off to the "fossil email incoming" command so that Fossil |
|
fc5c7d2…
|
drh
|
362 |
@ can handle bounces. (Property: "email-self")</p> |
|
fc5c7d2…
|
drh
|
363 |
@ <hr> |
|
fc5c7d2…
|
drh
|
364 |
|
|
e50c362…
|
drh
|
365 |
entry_attribute("List-ID", 40, "email-listid", |
|
e50c362…
|
drh
|
366 |
"elistid", "", 0); |
|
e50c362…
|
drh
|
367 |
@ <p> |
|
e50c362…
|
drh
|
368 |
@ If this is not an empty string, then it becomes the argument to |
|
0151018…
|
stephan
|
369 |
@ a "List-ID:" header on all out-bound notification emails. A list ID |
|
0151018…
|
stephan
|
370 |
@ is required for the generation of unsubscribe links in notifications. |
|
e50c362…
|
drh
|
371 |
@ (Property: "email-listid")</p> |
|
e50c362…
|
drh
|
372 |
@ <hr> |
|
e50c362…
|
drh
|
373 |
|
|
fc5c7d2…
|
drh
|
374 |
entry_attribute("Repository Nickname", 16, "email-subname", |
|
fc5c7d2…
|
drh
|
375 |
"enn", "", 0); |
|
fc5c7d2…
|
drh
|
376 |
@ <p><b>Required.</b> |
|
fc5c7d2…
|
drh
|
377 |
@ This is short name used to identifies the repository in the |
|
fc5c7d2…
|
drh
|
378 |
@ Subject: line of email alerts. Traditionally this name is |
|
fc5c7d2…
|
drh
|
379 |
@ included in square brackets. Examples: "[fossil-src]", "[sqlite-src]". |
|
fc5c7d2…
|
drh
|
380 |
@ (Property: "email-subname")</p> |
|
fc5c7d2…
|
drh
|
381 |
@ <hr> |
|
fc5c7d2…
|
drh
|
382 |
|
|
7b8be20…
|
drh
|
383 |
entry_attribute("Subscription Renewal Interval In Days", 8, |
|
7b8be20…
|
drh
|
384 |
"email-renew-interval", "eri", "", 0); |
|
7b8be20…
|
drh
|
385 |
@ <p> |
|
99a319b…
|
wyoung
|
386 |
@ If this value is an integer N greater than or equal to 14, then email |
|
34d45c5…
|
drh
|
387 |
@ notification subscriptions will be suspended N days after the last known |
|
7b8be20…
|
drh
|
388 |
@ interaction with the user. This prevents sending notifications |
|
34d45c5…
|
drh
|
389 |
@ to abandoned accounts. If a subscription comes within 7 days of expiring, |
|
7b8be20…
|
drh
|
390 |
@ a separate email goes out with the daily digest that prompts the |
|
7b8be20…
|
drh
|
391 |
@ subscriber to click on a link to the "/renew" webpage in order to |
|
34d45c5…
|
drh
|
392 |
@ extend their subscription. Subscriptions never expire if this setting |
|
34d45c5…
|
drh
|
393 |
@ is less than 14 or is an empty string. |
|
7b8be20…
|
drh
|
394 |
@ (Property: "email-renew-interval")</p> |
|
7b8be20…
|
drh
|
395 |
@ <hr> |
|
7b8be20…
|
drh
|
396 |
|
|
fc5c7d2…
|
drh
|
397 |
multiple_choice_attribute("Email Send Method", "email-send-method", "esm", |
|
fc5c7d2…
|
drh
|
398 |
"off", count(azSendMethods)/2, azSendMethods); |
|
fc5c7d2…
|
drh
|
399 |
@ <p>How to send email. Requires auxiliary information from the fields |
|
fc5c7d2…
|
drh
|
400 |
@ that follow. Hint: Use the <a href="%R/announce">/announce</a> page |
|
fc5c7d2…
|
drh
|
401 |
@ to send test message to debug this setting. |
|
fc5c7d2…
|
drh
|
402 |
@ (Property: "email-send-method")</p> |
|
fc5c7d2…
|
drh
|
403 |
alert_schema(1); |
|
cad72b7…
|
drh
|
404 |
entry_attribute("SMTP Relay Host", 60, "email-send-relayhost", |
|
6c7066a…
|
drh
|
405 |
"esrh", "localhost", 0); |
|
cad72b7…
|
drh
|
406 |
@ <p>When the send method is "SMTP relay", each email message is |
|
cad72b7…
|
drh
|
407 |
@ transmitted via the SMTP protocol (rfc5321) to a "Mail Submission |
|
cad72b7…
|
drh
|
408 |
@ Agent" or "MSA" (rfc4409) at the hostname shown here. Optionally |
|
cad72b7…
|
drh
|
409 |
@ append a colon and TCP port number (ex: smtp.example.com:587). |
|
cad72b7…
|
drh
|
410 |
@ The default TCP port number is 25. |
|
8266b5b…
|
drh
|
411 |
@ Usage Hint: If Fossil is running inside of a chroot jail, then it might |
|
8266b5b…
|
drh
|
412 |
@ not be able to resolve hostnames. Work around this by using a raw IP |
|
8266b5b…
|
drh
|
413 |
@ address or create a "/etc/hosts" file inside the chroot jail. |
|
cad72b7…
|
drh
|
414 |
@ (Property: "email-send-relayhost")</p> |
|
8266b5b…
|
drh
|
415 |
@ |
|
cad72b7…
|
drh
|
416 |
entry_attribute("Store Emails In This Database", 60, "email-send-db", |
|
cad72b7…
|
drh
|
417 |
"esdb", "", 0); |
|
cad72b7…
|
drh
|
418 |
@ <p>When the send method is "store in a database", each email message is |
|
cad72b7…
|
drh
|
419 |
@ stored in an SQLite database file with the name given here. |
|
cad72b7…
|
drh
|
420 |
@ (Property: "email-send-db")</p> |
|
9993c43…
|
danshearer
|
421 |
entry_attribute("Pipe Email Text Into This Command", 60, "email-send-command", |
|
9993c43…
|
danshearer
|
422 |
"ecmd", "sendmail -ti", 0); |
|
9993c43…
|
danshearer
|
423 |
@ <p>When the send method is "pipe to a command", this is the command |
|
9993c43…
|
danshearer
|
424 |
@ that is run. Email messages are piped into the standard input of this |
|
9993c43…
|
danshearer
|
425 |
@ command. The command is expected to extract the sender address, |
|
9993c43…
|
danshearer
|
426 |
@ recipient addresses, and subject from the header of the piped email |
|
9993c43…
|
danshearer
|
427 |
@ text. (Property: "email-send-command")</p> |
|
fc5c7d2…
|
drh
|
428 |
entry_attribute("Store Emails In This Directory", 60, "email-send-dir", |
|
fc5c7d2…
|
drh
|
429 |
"esdir", "", 0); |
|
fc5c7d2…
|
drh
|
430 |
@ <p>When the send method is "store in a directory", each email message is |
|
fc5c7d2…
|
drh
|
431 |
@ stored as a separate file in the directory shown here. |
|
fc5c7d2…
|
drh
|
432 |
@ (Property: "email-send-dir")</p> |
|
fc5c7d2…
|
drh
|
433 |
|
|
60d40d5…
|
drh
|
434 |
@ <hr> |
|
60d40d5…
|
drh
|
435 |
|
|
f5482a0…
|
wyoung
|
436 |
@ <p><input type="submit" name="submit" value="Apply Changes"></p> |
|
60d40d5…
|
drh
|
437 |
@ </div></form> |
|
60d40d5…
|
drh
|
438 |
db_end_transaction(0); |
|
112c713…
|
drh
|
439 |
style_finish_page(); |
|
fc5c7d2…
|
drh
|
440 |
} |
|
fc5c7d2…
|
drh
|
441 |
|
|
fc5c7d2…
|
drh
|
442 |
#if 0 |
|
fc5c7d2…
|
drh
|
443 |
/* |
|
fc5c7d2…
|
drh
|
444 |
** Encode pMsg as MIME base64 and append it to pOut |
|
fc5c7d2…
|
drh
|
445 |
*/ |
|
fc5c7d2…
|
drh
|
446 |
static void append_base64(Blob *pOut, Blob *pMsg){ |
|
fc5c7d2…
|
drh
|
447 |
int n, i, k; |
|
fc5c7d2…
|
drh
|
448 |
char zBuf[100]; |
|
fc5c7d2…
|
drh
|
449 |
n = blob_size(pMsg); |
|
fc5c7d2…
|
drh
|
450 |
for(i=0; i<n; i+=54){ |
|
fc5c7d2…
|
drh
|
451 |
k = translateBase64(blob_buffer(pMsg)+i, i+54<n ? 54 : n-i, zBuf); |
|
fc5c7d2…
|
drh
|
452 |
blob_append(pOut, zBuf, k); |
|
fc5c7d2…
|
drh
|
453 |
blob_append(pOut, "\r\n", 2); |
|
fc5c7d2…
|
drh
|
454 |
} |
|
fc5c7d2…
|
drh
|
455 |
} |
|
fc5c7d2…
|
drh
|
456 |
#endif |
|
fc5c7d2…
|
drh
|
457 |
|
|
fc5c7d2…
|
drh
|
458 |
/* |
|
fc5c7d2…
|
drh
|
459 |
** Encode pMsg using the quoted-printable email encoding and |
|
fc5c7d2…
|
drh
|
460 |
** append it onto pOut |
|
fc5c7d2…
|
drh
|
461 |
*/ |
|
fc5c7d2…
|
drh
|
462 |
static void append_quoted(Blob *pOut, Blob *pMsg){ |
|
fc5c7d2…
|
drh
|
463 |
char *zIn = blob_str(pMsg); |
|
fc5c7d2…
|
drh
|
464 |
char c; |
|
fc5c7d2…
|
drh
|
465 |
int iCol = 0; |
|
fc5c7d2…
|
drh
|
466 |
while( (c = *(zIn++))!=0 ){ |
|
fc5c7d2…
|
drh
|
467 |
if( (c>='!' && c<='~' && c!='=' && c!=':') |
|
fc5c7d2…
|
drh
|
468 |
|| (c==' ' && zIn[0]!='\r' && zIn[0]!='\n') |
|
fc5c7d2…
|
drh
|
469 |
){ |
|
fc5c7d2…
|
drh
|
470 |
blob_append_char(pOut, c); |
|
fc5c7d2…
|
drh
|
471 |
iCol++; |
|
fc5c7d2…
|
drh
|
472 |
if( iCol>=70 ){ |
|
fc5c7d2…
|
drh
|
473 |
blob_append(pOut, "=\r\n", 3); |
|
fc5c7d2…
|
drh
|
474 |
iCol = 0; |
|
fc5c7d2…
|
drh
|
475 |
} |
|
fc5c7d2…
|
drh
|
476 |
}else if( c=='\r' && zIn[0]=='\n' ){ |
|
fc5c7d2…
|
drh
|
477 |
zIn++; |
|
fc5c7d2…
|
drh
|
478 |
blob_append(pOut, "\r\n", 2); |
|
fc5c7d2…
|
drh
|
479 |
iCol = 0; |
|
fc5c7d2…
|
drh
|
480 |
}else if( c=='\n' ){ |
|
fc5c7d2…
|
drh
|
481 |
blob_append(pOut, "\r\n", 2); |
|
fc5c7d2…
|
drh
|
482 |
iCol = 0; |
|
fc5c7d2…
|
drh
|
483 |
}else{ |
|
fc5c7d2…
|
drh
|
484 |
char x[3]; |
|
fc5c7d2…
|
drh
|
485 |
x[0] = '='; |
|
fc5c7d2…
|
drh
|
486 |
x[1] = "0123456789ABCDEF"[(c>>4)&0xf]; |
|
fc5c7d2…
|
drh
|
487 |
x[2] = "0123456789ABCDEF"[c&0xf]; |
|
fc5c7d2…
|
drh
|
488 |
blob_append(pOut, x, 3); |
|
fc5c7d2…
|
drh
|
489 |
iCol += 3; |
|
fc5c7d2…
|
drh
|
490 |
} |
|
fc5c7d2…
|
drh
|
491 |
} |
|
fc5c7d2…
|
drh
|
492 |
} |
|
fc5c7d2…
|
drh
|
493 |
|
|
fc5c7d2…
|
drh
|
494 |
#if INTERFACE |
|
fc5c7d2…
|
drh
|
495 |
/* |
|
fc5c7d2…
|
drh
|
496 |
** An instance of the following object is used to send emails. |
|
fc5c7d2…
|
drh
|
497 |
*/ |
|
fc5c7d2…
|
drh
|
498 |
struct AlertSender { |
|
fc5c7d2…
|
drh
|
499 |
sqlite3 *db; /* Database emails are sent to */ |
|
fc5c7d2…
|
drh
|
500 |
sqlite3_stmt *pStmt; /* Stmt to insert into the database */ |
|
fc5c7d2…
|
drh
|
501 |
const char *zDest; /* How to send email. */ |
|
fc5c7d2…
|
drh
|
502 |
const char *zDb; /* Name of database file */ |
|
fc5c7d2…
|
drh
|
503 |
const char *zDir; /* Directory in which to store as email files */ |
|
fc5c7d2…
|
drh
|
504 |
const char *zCmd; /* Command to run for each email */ |
|
fc5c7d2…
|
drh
|
505 |
const char *zFrom; /* Emails come from here */ |
|
e50c362…
|
drh
|
506 |
const char *zListId; /* Argument to List-ID header */ |
|
fc5c7d2…
|
drh
|
507 |
SmtpSession *pSmtp; /* SMTP relay connection */ |
|
fc5c7d2…
|
drh
|
508 |
Blob out; /* For zDest=="blob" */ |
|
fc5c7d2…
|
drh
|
509 |
char *zErr; /* Error message */ |
|
fc5c7d2…
|
drh
|
510 |
u32 mFlags; /* Flags */ |
|
fc5c7d2…
|
drh
|
511 |
int bImmediateFail; /* On any error, call fossil_fatal() */ |
|
fc5c7d2…
|
drh
|
512 |
}; |
|
fc5c7d2…
|
drh
|
513 |
|
|
fc5c7d2…
|
drh
|
514 |
/* Allowed values for mFlags to alert_sender_new(). |
|
fc5c7d2…
|
drh
|
515 |
*/ |
|
fc5c7d2…
|
drh
|
516 |
#define ALERT_IMMEDIATE_FAIL 0x0001 /* Call fossil_fatal() on any error */ |
|
fc5c7d2…
|
drh
|
517 |
#define ALERT_TRACE 0x0002 /* Log sending process on console */ |
|
fc5c7d2…
|
drh
|
518 |
|
|
fc5c7d2…
|
drh
|
519 |
#endif /* INTERFACE */ |
|
fc5c7d2…
|
drh
|
520 |
|
|
fc5c7d2…
|
drh
|
521 |
/* |
|
fc5c7d2…
|
drh
|
522 |
** Shutdown an emailer. Clear all information other than the error message. |
|
fc5c7d2…
|
drh
|
523 |
*/ |
|
fc5c7d2…
|
drh
|
524 |
static void emailerShutdown(AlertSender *p){ |
|
fc5c7d2…
|
drh
|
525 |
sqlite3_finalize(p->pStmt); |
|
fc5c7d2…
|
drh
|
526 |
p->pStmt = 0; |
|
fc5c7d2…
|
drh
|
527 |
sqlite3_close(p->db); |
|
fc5c7d2…
|
drh
|
528 |
p->db = 0; |
|
fc5c7d2…
|
drh
|
529 |
p->zDb = 0; |
|
fc5c7d2…
|
drh
|
530 |
p->zDir = 0; |
|
fc5c7d2…
|
drh
|
531 |
p->zCmd = 0; |
|
e50c362…
|
drh
|
532 |
p->zListId = 0; |
|
fc5c7d2…
|
drh
|
533 |
if( p->pSmtp ){ |
|
fc5c7d2…
|
drh
|
534 |
smtp_client_quit(p->pSmtp); |
|
fc5c7d2…
|
drh
|
535 |
smtp_session_free(p->pSmtp); |
|
fc5c7d2…
|
drh
|
536 |
p->pSmtp = 0; |
|
fc5c7d2…
|
drh
|
537 |
} |
|
fc5c7d2…
|
drh
|
538 |
blob_reset(&p->out); |
|
fc5c7d2…
|
drh
|
539 |
} |
|
fc5c7d2…
|
drh
|
540 |
|
|
fc5c7d2…
|
drh
|
541 |
/* |
|
fc5c7d2…
|
drh
|
542 |
** Put the AlertSender into an error state. |
|
fc5c7d2…
|
drh
|
543 |
*/ |
|
fc5c7d2…
|
drh
|
544 |
static void emailerError(AlertSender *p, const char *zFormat, ...){ |
|
fc5c7d2…
|
drh
|
545 |
va_list ap; |
|
fc5c7d2…
|
drh
|
546 |
fossil_free(p->zErr); |
|
fc5c7d2…
|
drh
|
547 |
va_start(ap, zFormat); |
|
fc5c7d2…
|
drh
|
548 |
p->zErr = vmprintf(zFormat, ap); |
|
fc5c7d2…
|
drh
|
549 |
va_end(ap); |
|
fc5c7d2…
|
drh
|
550 |
emailerShutdown(p); |
|
fc5c7d2…
|
drh
|
551 |
if( p->mFlags & ALERT_IMMEDIATE_FAIL ){ |
|
fc5c7d2…
|
drh
|
552 |
fossil_fatal("%s", p->zErr); |
|
fc5c7d2…
|
drh
|
553 |
} |
|
fc5c7d2…
|
drh
|
554 |
} |
|
fc5c7d2…
|
drh
|
555 |
|
|
fc5c7d2…
|
drh
|
556 |
/* |
|
fc5c7d2…
|
drh
|
557 |
** Free an email sender object |
|
fc5c7d2…
|
drh
|
558 |
*/ |
|
fc5c7d2…
|
drh
|
559 |
void alert_sender_free(AlertSender *p){ |
|
fc5c7d2…
|
drh
|
560 |
if( p ){ |
|
fc5c7d2…
|
drh
|
561 |
emailerShutdown(p); |
|
fc5c7d2…
|
drh
|
562 |
fossil_free(p->zErr); |
|
fc5c7d2…
|
drh
|
563 |
fossil_free(p); |
|
fc5c7d2…
|
drh
|
564 |
} |
|
fc5c7d2…
|
drh
|
565 |
} |
|
fc5c7d2…
|
drh
|
566 |
|
|
fc5c7d2…
|
drh
|
567 |
/* |
|
fc5c7d2…
|
drh
|
568 |
** Get an email setting value. Report an error if not configured. |
|
fc5c7d2…
|
drh
|
569 |
** Return 0 on success and one if there is an error. |
|
fc5c7d2…
|
drh
|
570 |
*/ |
|
fc5c7d2…
|
drh
|
571 |
static int emailerGetSetting( |
|
fc5c7d2…
|
drh
|
572 |
AlertSender *p, /* Where to report the error */ |
|
fc5c7d2…
|
drh
|
573 |
const char **pzVal, /* Write the setting value here */ |
|
fc5c7d2…
|
drh
|
574 |
const char *zName /* Name of the setting */ |
|
fc5c7d2…
|
drh
|
575 |
){ |
|
fc5c7d2…
|
drh
|
576 |
const char *z = db_get(zName, 0); |
|
fc5c7d2…
|
drh
|
577 |
int rc = 0; |
|
fc5c7d2…
|
drh
|
578 |
if( z==0 || z[0]==0 ){ |
|
fc5c7d2…
|
drh
|
579 |
emailerError(p, "missing \"%s\" setting", zName); |
|
fc5c7d2…
|
drh
|
580 |
rc = 1; |
|
fc5c7d2…
|
drh
|
581 |
}else{ |
|
fc5c7d2…
|
drh
|
582 |
*pzVal = z; |
|
fc5c7d2…
|
drh
|
583 |
} |
|
fc5c7d2…
|
drh
|
584 |
return rc; |
|
fc5c7d2…
|
drh
|
585 |
} |
|
fc5c7d2…
|
drh
|
586 |
|
|
fc5c7d2…
|
drh
|
587 |
/* |
|
fc5c7d2…
|
drh
|
588 |
** Create a new AlertSender object. |
|
fc5c7d2…
|
drh
|
589 |
** |
|
fc5c7d2…
|
drh
|
590 |
** The method used for sending email is determined by various email-* |
|
fc5c7d2…
|
drh
|
591 |
** settings, and especially email-send-method. The repository |
|
fc5c7d2…
|
drh
|
592 |
** email-send-method can be overridden by the zAltDest argument to |
|
fc5c7d2…
|
drh
|
593 |
** cause a different sending mechanism to be used. Pass "stdout" to |
|
fc5c7d2…
|
drh
|
594 |
** zAltDest to cause all emails to be printed to the console for |
|
fc5c7d2…
|
drh
|
595 |
** debugging purposes. |
|
fc5c7d2…
|
drh
|
596 |
** |
|
fc5c7d2…
|
drh
|
597 |
** The AlertSender object returned must be freed using alert_sender_free(). |
|
fc5c7d2…
|
drh
|
598 |
*/ |
|
fc5c7d2…
|
drh
|
599 |
AlertSender *alert_sender_new(const char *zAltDest, u32 mFlags){ |
|
fc5c7d2…
|
drh
|
600 |
AlertSender *p; |
|
fc5c7d2…
|
drh
|
601 |
|
|
fc5c7d2…
|
drh
|
602 |
p = fossil_malloc(sizeof(*p)); |
|
fc5c7d2…
|
drh
|
603 |
memset(p, 0, sizeof(*p)); |
|
fc5c7d2…
|
drh
|
604 |
blob_init(&p->out, 0, 0); |
|
fc5c7d2…
|
drh
|
605 |
p->mFlags = mFlags; |
|
fc5c7d2…
|
drh
|
606 |
if( zAltDest ){ |
|
fc5c7d2…
|
drh
|
607 |
p->zDest = zAltDest; |
|
fc5c7d2…
|
drh
|
608 |
}else{ |
|
eb804dc…
|
drh
|
609 |
p->zDest = db_get("email-send-method",0); |
|
fc5c7d2…
|
drh
|
610 |
} |
|
fc5c7d2…
|
drh
|
611 |
if( fossil_strcmp(p->zDest,"off")==0 ) return p; |
|
fc5c7d2…
|
drh
|
612 |
if( emailerGetSetting(p, &p->zFrom, "email-self") ) return p; |
|
e50c362…
|
drh
|
613 |
p->zListId = db_get("email-listid", 0); |
|
fc5c7d2…
|
drh
|
614 |
if( fossil_strcmp(p->zDest,"db")==0 ){ |
|
fc5c7d2…
|
drh
|
615 |
char *zErr; |
|
fc5c7d2…
|
drh
|
616 |
int rc; |
|
fc5c7d2…
|
drh
|
617 |
if( emailerGetSetting(p, &p->zDb, "email-send-db") ) return p; |
|
fc5c7d2…
|
drh
|
618 |
rc = sqlite3_open(p->zDb, &p->db); |
|
fc5c7d2…
|
drh
|
619 |
if( rc ){ |
|
fc5c7d2…
|
drh
|
620 |
emailerError(p, "unable to open output database file \"%s\": %s", |
|
fc5c7d2…
|
drh
|
621 |
p->zDb, sqlite3_errmsg(p->db)); |
|
fc5c7d2…
|
drh
|
622 |
return p; |
|
fc5c7d2…
|
drh
|
623 |
} |
|
fc5c7d2…
|
drh
|
624 |
rc = sqlite3_exec(p->db, "CREATE TABLE IF NOT EXISTS email(\n" |
|
fc5c7d2…
|
drh
|
625 |
" emailid INTEGER PRIMARY KEY,\n" |
|
fc5c7d2…
|
drh
|
626 |
" msg TEXT\n);", 0, 0, &zErr); |
|
fc5c7d2…
|
drh
|
627 |
if( zErr ){ |
|
fc5c7d2…
|
drh
|
628 |
emailerError(p, "CREATE TABLE failed with \"%s\"", zErr); |
|
fc5c7d2…
|
drh
|
629 |
sqlite3_free(zErr); |
|
fc5c7d2…
|
drh
|
630 |
return p; |
|
fc5c7d2…
|
drh
|
631 |
} |
|
fc5c7d2…
|
drh
|
632 |
rc = sqlite3_prepare_v2(p->db, "INSERT INTO email(msg) VALUES(?1)", -1, |
|
fc5c7d2…
|
drh
|
633 |
&p->pStmt, 0); |
|
fc5c7d2…
|
drh
|
634 |
if( rc ){ |
|
fc5c7d2…
|
drh
|
635 |
emailerError(p, "cannot prepare INSERT statement: %s", |
|
fc5c7d2…
|
drh
|
636 |
sqlite3_errmsg(p->db)); |
|
fc5c7d2…
|
drh
|
637 |
return p; |
|
fc5c7d2…
|
drh
|
638 |
} |
|
fc5c7d2…
|
drh
|
639 |
}else if( fossil_strcmp(p->zDest, "pipe")==0 ){ |
|
fc5c7d2…
|
drh
|
640 |
emailerGetSetting(p, &p->zCmd, "email-send-command"); |
|
fc5c7d2…
|
drh
|
641 |
}else if( fossil_strcmp(p->zDest, "dir")==0 ){ |
|
fc5c7d2…
|
drh
|
642 |
emailerGetSetting(p, &p->zDir, "email-send-dir"); |
|
fc5c7d2…
|
drh
|
643 |
}else if( fossil_strcmp(p->zDest, "blob")==0 ){ |
|
fc5c7d2…
|
drh
|
644 |
blob_init(&p->out, 0, 0); |
|
bbfca4c…
|
drh
|
645 |
}else if( fossil_strcmp(p->zDest, "relay")==0 |
|
bbfca4c…
|
drh
|
646 |
|| fossil_strcmp(p->zDest, "debug-relay")==0 |
|
bbfca4c…
|
drh
|
647 |
){ |
|
fc5c7d2…
|
drh
|
648 |
const char *zRelay = 0; |
|
fc5c7d2…
|
drh
|
649 |
emailerGetSetting(p, &zRelay, "email-send-relayhost"); |
|
fc5c7d2…
|
drh
|
650 |
if( zRelay ){ |
|
fc5c7d2…
|
drh
|
651 |
u32 smtpFlags = SMTP_DIRECT; |
|
fc5c7d2…
|
drh
|
652 |
if( mFlags & ALERT_TRACE ) smtpFlags |= SMTP_TRACE_STDOUT; |
|
bbfca4c…
|
drh
|
653 |
blob_init(&p->out, 0, 0); |
|
e7a5b98…
|
stephan
|
654 |
p->pSmtp = smtp_session_new(domain_of_addr(p->zFrom), zRelay, |
|
3fc9b90…
|
drh
|
655 |
smtpFlags, 0); |
|
056c83d…
|
drh
|
656 |
if( p->pSmtp==0 || p->pSmtp->zErr ){ |
|
056c83d…
|
drh
|
657 |
emailerError(p, "Could not start SMTP session: %s", |
|
056c83d…
|
drh
|
658 |
p->pSmtp ? p->pSmtp->zErr : "reason unknown"); |
|
056c83d…
|
drh
|
659 |
}else if( p->zDest[0]=='d' ){ |
|
bbfca4c…
|
drh
|
660 |
smtp_session_config(p->pSmtp, SMTP_TRACE_BLOB, &p->out); |
|
bbfca4c…
|
drh
|
661 |
} |
|
fc5c7d2…
|
drh
|
662 |
} |
|
fc5c7d2…
|
drh
|
663 |
} |
|
fc5c7d2…
|
drh
|
664 |
return p; |
|
fc5c7d2…
|
drh
|
665 |
} |
|
fc5c7d2…
|
drh
|
666 |
|
|
fc5c7d2…
|
drh
|
667 |
/* |
|
fc5c7d2…
|
drh
|
668 |
** Scan the header of the email message in pMsg looking for the |
|
e2bdc10…
|
danield
|
669 |
** (first) occurrence of zField. Fill pValue with the content of |
|
fc5c7d2…
|
drh
|
670 |
** that field. |
|
fc5c7d2…
|
drh
|
671 |
** |
|
fc5c7d2…
|
drh
|
672 |
** This routine initializes pValue. Any prior content of pValue is |
|
fc5c7d2…
|
drh
|
673 |
** discarded (leaked). |
|
fc5c7d2…
|
drh
|
674 |
** |
|
fc5c7d2…
|
drh
|
675 |
** Return non-zero on success. Return 0 if no instance of the header |
|
fc5c7d2…
|
drh
|
676 |
** is found. |
|
fc5c7d2…
|
drh
|
677 |
*/ |
|
fc5c7d2…
|
drh
|
678 |
int email_header_value(Blob *pMsg, const char *zField, Blob *pValue){ |
|
fc5c7d2…
|
drh
|
679 |
int nField = (int)strlen(zField); |
|
fc5c7d2…
|
drh
|
680 |
Blob line; |
|
fc5c7d2…
|
drh
|
681 |
blob_rewind(pMsg); |
|
fc5c7d2…
|
drh
|
682 |
blob_init(pValue,0,0); |
|
fc5c7d2…
|
drh
|
683 |
while( blob_line(pMsg, &line) ){ |
|
fc5c7d2…
|
drh
|
684 |
int n, i; |
|
fc5c7d2…
|
drh
|
685 |
char *z; |
|
fc5c7d2…
|
drh
|
686 |
blob_trim(&line); |
|
fc5c7d2…
|
drh
|
687 |
n = blob_size(&line); |
|
fc5c7d2…
|
drh
|
688 |
if( n==0 ) return 0; |
|
fc5c7d2…
|
drh
|
689 |
if( n<nField+1 ) continue; |
|
fc5c7d2…
|
drh
|
690 |
z = blob_buffer(&line); |
|
fc5c7d2…
|
drh
|
691 |
if( sqlite3_strnicmp(z, zField, nField)==0 && z[nField]==':' ){ |
|
fc5c7d2…
|
drh
|
692 |
for(i=nField+1; i<n && fossil_isspace(z[i]); i++){} |
|
fc5c7d2…
|
drh
|
693 |
blob_init(pValue, z+i, n-i); |
|
fc5c7d2…
|
drh
|
694 |
while( blob_line(pMsg, &line) ){ |
|
fc5c7d2…
|
drh
|
695 |
blob_trim(&line); |
|
fc5c7d2…
|
drh
|
696 |
n = blob_size(&line); |
|
fc5c7d2…
|
drh
|
697 |
if( n==0 ) break; |
|
fc5c7d2…
|
drh
|
698 |
z = blob_buffer(&line); |
|
fc5c7d2…
|
drh
|
699 |
if( !fossil_isspace(z[0]) ) break; |
|
fc5c7d2…
|
drh
|
700 |
for(i=1; i<n && fossil_isspace(z[i]); i++){} |
|
fc5c7d2…
|
drh
|
701 |
blob_append(pValue, " ", 1); |
|
fc5c7d2…
|
drh
|
702 |
blob_append(pValue, z+i, n-i); |
|
fc5c7d2…
|
drh
|
703 |
} |
|
fc5c7d2…
|
drh
|
704 |
return 1; |
|
fc5c7d2…
|
drh
|
705 |
} |
|
fc5c7d2…
|
drh
|
706 |
} |
|
fc5c7d2…
|
drh
|
707 |
return 0; |
|
fc5c7d2…
|
drh
|
708 |
} |
|
fc5c7d2…
|
drh
|
709 |
|
|
fc5c7d2…
|
drh
|
710 |
/* |
|
32a8d11…
|
drh
|
711 |
** Determine whether or not the input string is a valid email address. |
|
32a8d11…
|
drh
|
712 |
** Only look at character up to but not including the first \000 or |
|
32a8d11…
|
drh
|
713 |
** the first cTerm character, whichever comes first. |
|
32a8d11…
|
drh
|
714 |
** |
|
e2bdc10…
|
danield
|
715 |
** Return the length of the email address string in bytes if the email |
|
32a8d11…
|
drh
|
716 |
** address is valid. If the email address is misformed, return 0. |
|
32a8d11…
|
drh
|
717 |
*/ |
|
32a8d11…
|
drh
|
718 |
int email_address_is_valid(const char *z, char cTerm){ |
|
fc5c7d2…
|
drh
|
719 |
int i; |
|
fc5c7d2…
|
drh
|
720 |
int nAt = 0; |
|
fc5c7d2…
|
drh
|
721 |
int nDot = 0; |
|
fc5c7d2…
|
drh
|
722 |
char c; |
|
fc5c7d2…
|
drh
|
723 |
if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */ |
|
fc5c7d2…
|
drh
|
724 |
for(i=0; (c = z[i])!=0 && c!=cTerm; i++){ |
|
fc5c7d2…
|
drh
|
725 |
if( fossil_isalnum(c) ){ |
|
fc5c7d2…
|
drh
|
726 |
/* Alphanumerics are always ok */ |
|
fc5c7d2…
|
drh
|
727 |
}else if( c=='@' ){ |
|
fc5c7d2…
|
drh
|
728 |
if( nAt ) return 0; /* Only a single "@" allowed */ |
|
fc5c7d2…
|
drh
|
729 |
if( i>64 ) return 0; /* Local part too big */ |
|
fc5c7d2…
|
drh
|
730 |
nAt = 1; |
|
fc5c7d2…
|
drh
|
731 |
nDot = 0; |
|
fc5c7d2…
|
drh
|
732 |
if( i==0 ) return 0; /* Disallow empty local part */ |
|
fc5c7d2…
|
drh
|
733 |
if( z[i-1]=='.' ) return 0; /* Last char of local cannot be "." */ |
|
fc5c7d2…
|
drh
|
734 |
if( z[i+1]=='.' || z[i+1]=='-' ){ |
|
fc5c7d2…
|
drh
|
735 |
return 0; /* Domain cannot begin with "." or "-" */ |
|
fc5c7d2…
|
drh
|
736 |
} |
|
fc5c7d2…
|
drh
|
737 |
}else if( c=='-' ){ |
|
fc5c7d2…
|
drh
|
738 |
if( z[i+1]==cTerm ) return 0; /* Last character cannot be "-" */ |
|
fc5c7d2…
|
drh
|
739 |
}else if( c=='.' ){ |
|
fc5c7d2…
|
drh
|
740 |
if( z[i+1]=='.' ) return 0; /* Do not allow ".." */ |
|
fc5c7d2…
|
drh
|
741 |
if( z[i+1]==cTerm ) return 0; /* Domain may not end with . */ |
|
fc5c7d2…
|
drh
|
742 |
nDot++; |
|
fc5c7d2…
|
drh
|
743 |
}else if( (c=='_' || c=='+') && nAt==0 ){ |
|
fc5c7d2…
|
drh
|
744 |
/* _ and + are ok in the local part */ |
|
fc5c7d2…
|
drh
|
745 |
}else{ |
|
fc5c7d2…
|
drh
|
746 |
return 0; /* Anything else is an error */ |
|
fc5c7d2…
|
drh
|
747 |
} |
|
fc5c7d2…
|
drh
|
748 |
} |
|
fc5c7d2…
|
drh
|
749 |
if( c!=cTerm ) return 0; /* Missing terminator */ |
|
fc5c7d2…
|
drh
|
750 |
if( nAt==0 ) return 0; /* No "@" found anywhere */ |
|
fc5c7d2…
|
drh
|
751 |
if( nDot==0 ) return 0; /* No "." in the domain */ |
|
32a8d11…
|
drh
|
752 |
return i; |
|
32a8d11…
|
drh
|
753 |
} |
|
fc5c7d2…
|
drh
|
754 |
|
|
32a8d11…
|
drh
|
755 |
/* |
|
32a8d11…
|
drh
|
756 |
** Make a copy of the input string up to but not including the |
|
32a8d11…
|
drh
|
757 |
** first cTerm character. |
|
32a8d11…
|
drh
|
758 |
** |
|
f974583…
|
mark
|
759 |
** Verify that the string to be copied really is a valid |
|
f974583…
|
mark
|
760 |
** email address. If it is not, then return NULL. |
|
32a8d11…
|
drh
|
761 |
** |
|
32a8d11…
|
drh
|
762 |
** This routine is more restrictive than necessary. It does not |
|
32a8d11…
|
drh
|
763 |
** allow comments, IP address, quoted strings, or certain uncommon |
|
32a8d11…
|
drh
|
764 |
** characters. The only non-alphanumerics allowed in the local |
|
32a8d11…
|
drh
|
765 |
** part are "_", "+", "-" and "+". |
|
32a8d11…
|
drh
|
766 |
*/ |
|
32a8d11…
|
drh
|
767 |
char *email_copy_addr(const char *z, char cTerm ){ |
|
32a8d11…
|
drh
|
768 |
int i = email_address_is_valid(z, cTerm); |
|
32a8d11…
|
drh
|
769 |
return i==0 ? 0 : mprintf("%.*s", i, z); |
|
fc5c7d2…
|
drh
|
770 |
} |
|
fc5c7d2…
|
drh
|
771 |
|
|
fc5c7d2…
|
drh
|
772 |
/* |
|
f974583…
|
mark
|
773 |
** Scan the input string for a valid email address that may be |
|
f974583…
|
mark
|
774 |
** enclosed in <...>, or delimited by ',' or ':' or '=' or ' '. |
|
fc5c7d2…
|
drh
|
775 |
** If the string contains one or more email addresses, extract the first |
|
fc5c7d2…
|
drh
|
776 |
** one into memory obtained from mprintf() and return a pointer to it. |
|
fc5c7d2…
|
drh
|
777 |
** If no valid email address can be found, return NULL. |
|
fc5c7d2…
|
drh
|
778 |
*/ |
|
fc5c7d2…
|
drh
|
779 |
char *alert_find_emailaddr(const char *zIn){ |
|
fc5c7d2…
|
drh
|
780 |
char *zOut = 0; |
|
f974583…
|
mark
|
781 |
do{ |
|
f974583…
|
mark
|
782 |
zOut = email_copy_addr(zIn, zIn[strcspn(zIn, ">,:= ")]); |
|
f974583…
|
mark
|
783 |
if( zOut!=0 ) break; |
|
f974583…
|
mark
|
784 |
zIn = (const char *)strpbrk(zIn, "<,:= "); |
|
f974583…
|
mark
|
785 |
if( zIn==0 ) break; |
|
f974583…
|
mark
|
786 |
zIn++; |
|
f974583…
|
mark
|
787 |
}while( zIn!=0 ); |
|
fc5c7d2…
|
drh
|
788 |
return zOut; |
|
fc5c7d2…
|
drh
|
789 |
} |
|
fc5c7d2…
|
drh
|
790 |
|
|
fc5c7d2…
|
drh
|
791 |
/* |
|
fc5c7d2…
|
drh
|
792 |
** SQL function: find_emailaddr(X) |
|
fc5c7d2…
|
drh
|
793 |
** |
|
fc5c7d2…
|
drh
|
794 |
** Return the first valid email address of the form <...> in input string |
|
fc5c7d2…
|
drh
|
795 |
** X. Or return NULL if not found. |
|
fc5c7d2…
|
drh
|
796 |
*/ |
|
fc5c7d2…
|
drh
|
797 |
void alert_find_emailaddr_func( |
|
fc5c7d2…
|
drh
|
798 |
sqlite3_context *context, |
|
fc5c7d2…
|
drh
|
799 |
int argc, |
|
fc5c7d2…
|
drh
|
800 |
sqlite3_value **argv |
|
fc5c7d2…
|
drh
|
801 |
){ |
|
fc5c7d2…
|
drh
|
802 |
const char *zIn = (const char*)sqlite3_value_text(argv[0]); |
|
fc5c7d2…
|
drh
|
803 |
char *zOut = alert_find_emailaddr(zIn); |
|
fc5c7d2…
|
drh
|
804 |
if( zOut ){ |
|
fc5c7d2…
|
drh
|
805 |
sqlite3_result_text(context, zOut, -1, fossil_free); |
|
2e71dc2…
|
drh
|
806 |
} |
|
2e71dc2…
|
drh
|
807 |
} |
|
2e71dc2…
|
drh
|
808 |
|
|
2e71dc2…
|
drh
|
809 |
/* |
|
2e71dc2…
|
drh
|
810 |
** SQL function: display_name(X) |
|
2e71dc2…
|
drh
|
811 |
** |
|
2e71dc2…
|
drh
|
812 |
** If X is a string, search for a user name at the beginning of that |
|
2e71dc2…
|
drh
|
813 |
** string. The user name must be followed by an email address. If |
|
2e71dc2…
|
drh
|
814 |
** found, return the user name. If not found, return NULL. |
|
2e71dc2…
|
drh
|
815 |
** |
|
2e71dc2…
|
drh
|
816 |
** This routine is used to extract the display name from the USER.INFO |
|
2e71dc2…
|
drh
|
817 |
** field. |
|
2e71dc2…
|
drh
|
818 |
*/ |
|
2e71dc2…
|
drh
|
819 |
void alert_display_name_func( |
|
2e71dc2…
|
drh
|
820 |
sqlite3_context *context, |
|
2e71dc2…
|
drh
|
821 |
int argc, |
|
2e71dc2…
|
drh
|
822 |
sqlite3_value **argv |
|
2e71dc2…
|
drh
|
823 |
){ |
|
2e71dc2…
|
drh
|
824 |
const char *zIn = (const char*)sqlite3_value_text(argv[0]); |
|
2e71dc2…
|
drh
|
825 |
int i; |
|
2e71dc2…
|
drh
|
826 |
if( zIn==0 ) return; |
|
2e71dc2…
|
drh
|
827 |
while( fossil_isspace(zIn[0]) ) zIn++; |
|
2e71dc2…
|
drh
|
828 |
for(i=0; zIn[i] && zIn[i]!='<' && zIn[i]!='\n'; i++){} |
|
2e71dc2…
|
drh
|
829 |
if( zIn[i]=='<' ){ |
|
2e71dc2…
|
drh
|
830 |
while( i>0 && fossil_isspace(zIn[i-1]) ){ i--; } |
|
2e71dc2…
|
drh
|
831 |
if( i>0 ){ |
|
2e71dc2…
|
drh
|
832 |
sqlite3_result_text(context, zIn, i, SQLITE_TRANSIENT); |
|
2e71dc2…
|
drh
|
833 |
} |
|
fc5c7d2…
|
drh
|
834 |
} |
|
fc5c7d2…
|
drh
|
835 |
} |
|
fc5c7d2…
|
drh
|
836 |
|
|
fc5c7d2…
|
drh
|
837 |
/* |
|
fc5c7d2…
|
drh
|
838 |
** Return the hostname portion of an email address - the part following |
|
fc5c7d2…
|
drh
|
839 |
** the @ |
|
fc5c7d2…
|
drh
|
840 |
*/ |
|
fc5c7d2…
|
drh
|
841 |
char *alert_hostname(const char *zAddr){ |
|
fc5c7d2…
|
drh
|
842 |
char *z = strchr(zAddr, '@'); |
|
fc5c7d2…
|
drh
|
843 |
if( z ){ |
|
fc5c7d2…
|
drh
|
844 |
z++; |
|
fc5c7d2…
|
drh
|
845 |
}else{ |
|
fc5c7d2…
|
drh
|
846 |
z = (char*)zAddr; |
|
fc5c7d2…
|
drh
|
847 |
} |
|
fc5c7d2…
|
drh
|
848 |
return z; |
|
fc5c7d2…
|
drh
|
849 |
} |
|
fc5c7d2…
|
drh
|
850 |
|
|
fc5c7d2…
|
drh
|
851 |
/* |
|
fc5c7d2…
|
drh
|
852 |
** Return a pointer to a fake email mailbox name that corresponds |
|
fc5c7d2…
|
drh
|
853 |
** to human-readable name zFromName. The fake mailbox name is based |
|
fc5c7d2…
|
drh
|
854 |
** on a hash. No huge problems arise if there is a hash collisions, |
|
fc5c7d2…
|
drh
|
855 |
** but it is still better if collisions can be avoided. |
|
fc5c7d2…
|
drh
|
856 |
** |
|
fc5c7d2…
|
drh
|
857 |
** The returned string is held in a static buffer and is overwritten |
|
fc5c7d2…
|
drh
|
858 |
** by each subsequent call to this routine. |
|
fc5c7d2…
|
drh
|
859 |
*/ |
|
fc5c7d2…
|
drh
|
860 |
static char *alert_mailbox_name(const char *zFromName){ |
|
fc5c7d2…
|
drh
|
861 |
static char zHash[20]; |
|
fc5c7d2…
|
drh
|
862 |
unsigned int x = 0; |
|
fc5c7d2…
|
drh
|
863 |
int n = 0; |
|
fc5c7d2…
|
drh
|
864 |
while( zFromName[0] ){ |
|
fc5c7d2…
|
drh
|
865 |
n++; |
|
fc5c7d2…
|
drh
|
866 |
x = x*1103515245 + 12345 + ((unsigned char*)zFromName)[0]; |
|
fc5c7d2…
|
drh
|
867 |
zFromName++; |
|
fc5c7d2…
|
drh
|
868 |
} |
|
fc5c7d2…
|
drh
|
869 |
sqlite3_snprintf(sizeof(zHash), zHash, |
|
fc5c7d2…
|
drh
|
870 |
"noreply%x%08x", n, x); |
|
fc5c7d2…
|
drh
|
871 |
return zHash; |
|
fc5c7d2…
|
drh
|
872 |
} |
|
fc5c7d2…
|
drh
|
873 |
|
|
fc5c7d2…
|
drh
|
874 |
/* |
|
fc5c7d2…
|
drh
|
875 |
** COMMAND: test-mailbox-hashname |
|
fc5c7d2…
|
drh
|
876 |
** |
|
fc5c7d2…
|
drh
|
877 |
** Usage: %fossil test-mailbox-hashname HUMAN-NAME ... |
|
fc5c7d2…
|
drh
|
878 |
** |
|
fc5c7d2…
|
drh
|
879 |
** Return the mailbox hash name corresponding to each human-readable |
|
fc5c7d2…
|
drh
|
880 |
** name on the command line. This is a test interface for the |
|
fc5c7d2…
|
drh
|
881 |
** alert_mailbox_name() function. |
|
fc5c7d2…
|
drh
|
882 |
*/ |
|
fc5c7d2…
|
drh
|
883 |
void alert_test_mailbox_hashname(void){ |
|
fc5c7d2…
|
drh
|
884 |
int i; |
|
fc5c7d2…
|
drh
|
885 |
for(i=2; i<g.argc; i++){ |
|
fc5c7d2…
|
drh
|
886 |
fossil_print("%30s: %s\n", g.argv[i], alert_mailbox_name(g.argv[i])); |
|
fc5c7d2…
|
drh
|
887 |
} |
|
fc5c7d2…
|
drh
|
888 |
} |
|
fc5c7d2…
|
drh
|
889 |
|
|
fc5c7d2…
|
drh
|
890 |
/* |
|
fc5c7d2…
|
drh
|
891 |
** Extract all To: header values from the email header supplied. |
|
fc5c7d2…
|
drh
|
892 |
** Store them in the array list. |
|
fc5c7d2…
|
drh
|
893 |
*/ |
|
fc5c7d2…
|
drh
|
894 |
void email_header_to(Blob *pMsg, int *pnTo, char ***pazTo){ |
|
fc5c7d2…
|
drh
|
895 |
int nTo = 0; |
|
fc5c7d2…
|
drh
|
896 |
char **azTo = 0; |
|
fc5c7d2…
|
drh
|
897 |
Blob v; |
|
fc5c7d2…
|
drh
|
898 |
char *z, *zAddr; |
|
fc5c7d2…
|
drh
|
899 |
int i; |
|
e0576ea…
|
stephan
|
900 |
|
|
fc5c7d2…
|
drh
|
901 |
email_header_value(pMsg, "to", &v); |
|
fc5c7d2…
|
drh
|
902 |
z = blob_str(&v); |
|
fc5c7d2…
|
drh
|
903 |
for(i=0; z[i]; i++){ |
|
fc5c7d2…
|
drh
|
904 |
if( z[i]=='<' && (zAddr = email_copy_addr(&z[i+1],'>'))!=0 ){ |
|
fc5c7d2…
|
drh
|
905 |
azTo = fossil_realloc(azTo, sizeof(azTo[0])*(nTo+1) ); |
|
fc5c7d2…
|
drh
|
906 |
azTo[nTo++] = zAddr; |
|
fc5c7d2…
|
drh
|
907 |
} |
|
fc5c7d2…
|
drh
|
908 |
} |
|
fc5c7d2…
|
drh
|
909 |
*pnTo = nTo; |
|
fc5c7d2…
|
drh
|
910 |
*pazTo = azTo; |
|
fc5c7d2…
|
drh
|
911 |
} |
|
fc5c7d2…
|
drh
|
912 |
|
|
fc5c7d2…
|
drh
|
913 |
/* |
|
e0576ea…
|
stephan
|
914 |
** Free a list of To addresses obtained from a prior call to |
|
fc5c7d2…
|
drh
|
915 |
** email_header_to() |
|
fc5c7d2…
|
drh
|
916 |
*/ |
|
fc5c7d2…
|
drh
|
917 |
void email_header_to_free(int nTo, char **azTo){ |
|
fc5c7d2…
|
drh
|
918 |
int i; |
|
fc5c7d2…
|
drh
|
919 |
for(i=0; i<nTo; i++) fossil_free(azTo[i]); |
|
fc5c7d2…
|
drh
|
920 |
fossil_free(azTo); |
|
fc5c7d2…
|
drh
|
921 |
} |
|
fc5c7d2…
|
drh
|
922 |
|
|
fc5c7d2…
|
drh
|
923 |
/* |
|
fc5c7d2…
|
drh
|
924 |
** Send a single email message. |
|
fc5c7d2…
|
drh
|
925 |
** |
|
9993c43…
|
danshearer
|
926 |
** The recipient(s) must be specified using "To:" or "Cc:" or "Bcc:" fields |
|
fc5c7d2…
|
drh
|
927 |
** in the header. Likewise, the header must contains a "Subject:" line. |
|
fc5c7d2…
|
drh
|
928 |
** The header might also include fields like "Message-Id:" or |
|
fc5c7d2…
|
drh
|
929 |
** "In-Reply-To:". |
|
fc5c7d2…
|
drh
|
930 |
** |
|
fc5c7d2…
|
drh
|
931 |
** This routine will add fields to the header as follows: |
|
fc5c7d2…
|
drh
|
932 |
** |
|
fc5c7d2…
|
drh
|
933 |
** From: |
|
fc5c7d2…
|
drh
|
934 |
** Date: |
|
fc5c7d2…
|
drh
|
935 |
** Message-Id: |
|
fc5c7d2…
|
drh
|
936 |
** Content-Type: |
|
fc5c7d2…
|
drh
|
937 |
** Content-Transfer-Encoding: |
|
fc5c7d2…
|
drh
|
938 |
** MIME-Version: |
|
b4dcf8e…
|
drh
|
939 |
** Sender: |
|
e0576ea…
|
stephan
|
940 |
** |
|
fc5c7d2…
|
drh
|
941 |
** The caller maintains ownership of the input Blobs. This routine will |
|
fc5c7d2…
|
drh
|
942 |
** read the Blobs and send them onward to the email system, but it will |
|
fc5c7d2…
|
drh
|
943 |
** not free them. |
|
fc5c7d2…
|
drh
|
944 |
** |
|
fc5c7d2…
|
drh
|
945 |
** The Message-Id: field is added if there is not already a Message-Id |
|
fc5c7d2…
|
drh
|
946 |
** in the pHdr parameter. |
|
fc5c7d2…
|
drh
|
947 |
** |
|
fc5c7d2…
|
drh
|
948 |
** If the zFromName argument is not NULL, then it should be a human-readable |
|
fc5c7d2…
|
drh
|
949 |
** name or handle for the sender. In that case, "From:" becomes a made-up |
|
fc5c7d2…
|
drh
|
950 |
** email address based on a hash of zFromName and the domain of email-self, |
|
b4dcf8e…
|
drh
|
951 |
** and an additional "Sender:" field is inserted with the email-self |
|
b4dcf8e…
|
drh
|
952 |
** address. Downstream software might use the Sender header to set |
|
e0576ea…
|
stephan
|
953 |
** the envelope-from address of the email. If zFromName is a NULL pointer, |
|
b4dcf8e…
|
drh
|
954 |
** then the "From:" is set to the email-self value and Sender is |
|
fc5c7d2…
|
drh
|
955 |
** omitted. |
|
fc5c7d2…
|
drh
|
956 |
*/ |
|
fc5c7d2…
|
drh
|
957 |
void alert_send( |
|
fc5c7d2…
|
drh
|
958 |
AlertSender *p, /* Emailer context */ |
|
fc5c7d2…
|
drh
|
959 |
Blob *pHdr, /* Email header (incomplete) */ |
|
fc5c7d2…
|
drh
|
960 |
Blob *pBody, /* Email body */ |
|
fc5c7d2…
|
drh
|
961 |
const char *zFromName /* Optional human-readable name of sender */ |
|
fc5c7d2…
|
drh
|
962 |
){ |
|
fc5c7d2…
|
drh
|
963 |
Blob all, *pOut; |
|
fc5c7d2…
|
drh
|
964 |
u64 r1, r2; |
|
fc5c7d2…
|
drh
|
965 |
if( p->mFlags & ALERT_TRACE ){ |
|
fc5c7d2…
|
drh
|
966 |
fossil_print("Sending email\n"); |
|
fc5c7d2…
|
drh
|
967 |
} |
|
fc5c7d2…
|
drh
|
968 |
if( fossil_strcmp(p->zDest, "off")==0 ){ |
|
fc5c7d2…
|
drh
|
969 |
return; |
|
fc5c7d2…
|
drh
|
970 |
} |
|
ea4ccfd…
|
drh
|
971 |
blob_init(&all, 0, 0); |
|
fc5c7d2…
|
drh
|
972 |
if( fossil_strcmp(p->zDest, "blob")==0 ){ |
|
fc5c7d2…
|
drh
|
973 |
pOut = &p->out; |
|
fc5c7d2…
|
drh
|
974 |
if( blob_size(pOut) ){ |
|
fc5c7d2…
|
drh
|
975 |
blob_appendf(pOut, "%.72c\n", '='); |
|
fc5c7d2…
|
drh
|
976 |
} |
|
fc5c7d2…
|
drh
|
977 |
}else{ |
|
fc5c7d2…
|
drh
|
978 |
pOut = &all; |
|
fc5c7d2…
|
drh
|
979 |
} |
|
fc5c7d2…
|
drh
|
980 |
blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr)); |
|
488af36…
|
drh
|
981 |
if( p->zFrom==0 || p->zFrom[0]==0 ){ |
|
4c73b4a…
|
drh
|
982 |
return; /* email-self is not set. Error will be reported separately */ |
|
488af36…
|
drh
|
983 |
}else if( zFromName ){ |
|
fc5c7d2…
|
drh
|
984 |
blob_appendf(pOut, "From: %s <%s@%s>\r\n", |
|
fc5c7d2…
|
drh
|
985 |
zFromName, alert_mailbox_name(zFromName), alert_hostname(p->zFrom)); |
|
b4dcf8e…
|
drh
|
986 |
blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom); |
|
fc5c7d2…
|
drh
|
987 |
}else{ |
|
fc5c7d2…
|
drh
|
988 |
blob_appendf(pOut, "From: <%s>\r\n", p->zFrom); |
|
fc5c7d2…
|
drh
|
989 |
} |
|
fc5c7d2…
|
drh
|
990 |
blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0))); |
|
fc5c7d2…
|
drh
|
991 |
if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){ |
|
fc5c7d2…
|
drh
|
992 |
/* Message-id format: "<$(date)x$(random)@$(from-host)>" where $(date) is |
|
fc5c7d2…
|
drh
|
993 |
** the current unix-time in hex, $(random) is a 64-bit random number, |
|
fc5c7d2…
|
drh
|
994 |
** and $(from) is the domain part of the email-self setting. */ |
|
fc5c7d2…
|
drh
|
995 |
sqlite3_randomness(sizeof(r1), &r1); |
|
fc5c7d2…
|
drh
|
996 |
r2 = time(0); |
|
fc5c7d2…
|
drh
|
997 |
blob_appendf(pOut, "Message-Id: <%llxx%016llx@%s>\r\n", |
|
fc5c7d2…
|
drh
|
998 |
r2, r1, alert_hostname(p->zFrom)); |
|
fc5c7d2…
|
drh
|
999 |
} |
|
fc5c7d2…
|
drh
|
1000 |
blob_add_final_newline(pBody); |
|
fc5c7d2…
|
drh
|
1001 |
blob_appendf(pOut, "MIME-Version: 1.0\r\n"); |
|
fc5c7d2…
|
drh
|
1002 |
blob_appendf(pOut, "Content-Type: text/plain; charset=\"UTF-8\"\r\n"); |
|
fc5c7d2…
|
drh
|
1003 |
#if 0 |
|
fc5c7d2…
|
drh
|
1004 |
blob_appendf(pOut, "Content-Transfer-Encoding: base64\r\n\r\n"); |
|
fc5c7d2…
|
drh
|
1005 |
append_base64(pOut, pBody); |
|
fc5c7d2…
|
drh
|
1006 |
#else |
|
fc5c7d2…
|
drh
|
1007 |
blob_appendf(pOut, "Content-Transfer-Encoding: quoted-printable\r\n\r\n"); |
|
fc5c7d2…
|
drh
|
1008 |
append_quoted(pOut, pBody); |
|
fc5c7d2…
|
drh
|
1009 |
#endif |
|
fc5c7d2…
|
drh
|
1010 |
if( p->pStmt ){ |
|
fc5c7d2…
|
drh
|
1011 |
int i, rc; |
|
fc5c7d2…
|
drh
|
1012 |
sqlite3_bind_text(p->pStmt, 1, blob_str(&all), -1, SQLITE_TRANSIENT); |
|
fc5c7d2…
|
drh
|
1013 |
for(i=0; i<100 && sqlite3_step(p->pStmt)==SQLITE_BUSY; i++){ |
|
fc5c7d2…
|
drh
|
1014 |
sqlite3_sleep(10); |
|
fc5c7d2…
|
drh
|
1015 |
} |
|
fc5c7d2…
|
drh
|
1016 |
rc = sqlite3_reset(p->pStmt); |
|
fc5c7d2…
|
drh
|
1017 |
if( rc!=SQLITE_OK ){ |
|
fc5c7d2…
|
drh
|
1018 |
emailerError(p, "Failed to insert email message into output queue.\n" |
|
fc5c7d2…
|
drh
|
1019 |
"%s", sqlite3_errmsg(p->db)); |
|
fc5c7d2…
|
drh
|
1020 |
} |
|
fc5c7d2…
|
drh
|
1021 |
}else if( p->zCmd ){ |
|
fc5c7d2…
|
drh
|
1022 |
FILE *out = popen(p->zCmd, "w"); |
|
fc5c7d2…
|
drh
|
1023 |
if( out ){ |
|
fc5c7d2…
|
drh
|
1024 |
fwrite(blob_buffer(&all), 1, blob_size(&all), out); |
|
1f4b4fc…
|
wyoung
|
1025 |
pclose(out); |
|
fc5c7d2…
|
drh
|
1026 |
}else{ |
|
fc5c7d2…
|
drh
|
1027 |
emailerError(p, "Could not open output pipe \"%s\"", p->zCmd); |
|
fc5c7d2…
|
drh
|
1028 |
} |
|
fc5c7d2…
|
drh
|
1029 |
}else if( p->zDir ){ |
|
fc5c7d2…
|
drh
|
1030 |
char *zFile = file_time_tempname(p->zDir, ".email"); |
|
fc5c7d2…
|
drh
|
1031 |
blob_write_to_file(&all, zFile); |
|
fc5c7d2…
|
drh
|
1032 |
fossil_free(zFile); |
|
fc5c7d2…
|
drh
|
1033 |
}else if( p->pSmtp ){ |
|
fc5c7d2…
|
drh
|
1034 |
char **azTo = 0; |
|
fc5c7d2…
|
drh
|
1035 |
int nTo = 0; |
|
e6c27d3…
|
drh
|
1036 |
SmtpSession *pSmtp = p->pSmtp; |
|
fc5c7d2…
|
drh
|
1037 |
email_header_to(pHdr, &nTo, &azTo); |
|
e6c27d3…
|
drh
|
1038 |
if( nTo>0 && !pSmtp->bFatal ){ |
|
e6c27d3…
|
drh
|
1039 |
smtp_send_msg(pSmtp,p->zFrom,nTo,(const char**)azTo,blob_str(&all)); |
|
e6c27d3…
|
drh
|
1040 |
if( pSmtp->zErr && !pSmtp->bFatal ){ |
|
e6c27d3…
|
drh
|
1041 |
smtp_send_msg(pSmtp,p->zFrom,nTo,(const char**)azTo,blob_str(&all)); |
|
e6c27d3…
|
drh
|
1042 |
} |
|
e6c27d3…
|
drh
|
1043 |
if( pSmtp->zErr ){ |
|
e6c27d3…
|
drh
|
1044 |
fossil_errorlog("SMTP: (%s) %s", pSmtp->bFatal ? "fatal" : "retry", |
|
e6c27d3…
|
drh
|
1045 |
pSmtp->zErr); |
|
e6c27d3…
|
drh
|
1046 |
} |
|
fc5c7d2…
|
drh
|
1047 |
email_header_to_free(nTo, azTo); |
|
fc5c7d2…
|
drh
|
1048 |
} |
|
fc5c7d2…
|
drh
|
1049 |
}else if( strcmp(p->zDest, "stdout")==0 ){ |
|
fc5c7d2…
|
drh
|
1050 |
char **azTo = 0; |
|
fc5c7d2…
|
drh
|
1051 |
int nTo = 0; |
|
fc5c7d2…
|
drh
|
1052 |
int i; |
|
fc5c7d2…
|
drh
|
1053 |
email_header_to(pHdr, &nTo, &azTo); |
|
fc5c7d2…
|
drh
|
1054 |
for(i=0; i<nTo; i++){ |
|
fc5c7d2…
|
drh
|
1055 |
fossil_print("X-To-Test-%d: [%s]\r\n", i, azTo[i]); |
|
fc5c7d2…
|
drh
|
1056 |
} |
|
fc5c7d2…
|
drh
|
1057 |
email_header_to_free(nTo, azTo); |
|
fc5c7d2…
|
drh
|
1058 |
blob_add_final_newline(&all); |
|
fc5c7d2…
|
drh
|
1059 |
fossil_print("%s", blob_str(&all)); |
|
fc5c7d2…
|
drh
|
1060 |
} |
|
fc5c7d2…
|
drh
|
1061 |
blob_reset(&all); |
|
fc5c7d2…
|
drh
|
1062 |
} |
|
fc5c7d2…
|
drh
|
1063 |
|
|
fc5c7d2…
|
drh
|
1064 |
/* |
|
a0ae0c9…
|
drh
|
1065 |
** SETTING: email-url width=40 |
|
7e993c7…
|
drh
|
1066 |
** This is the main URL used to access the repository for cloning or |
|
7e993c7…
|
drh
|
1067 |
** syncing or for operating the web interface. It is also |
|
7e993c7…
|
drh
|
1068 |
** the basename for hyperlinks included in email alert text. |
|
7e993c7…
|
drh
|
1069 |
** Omit the trailing "/". If the repository is not intended to be |
|
7e993c7…
|
drh
|
1070 |
** a long-running server and will not be sending email notifications, |
|
7e993c7…
|
drh
|
1071 |
** then leave this setting blank. |
|
a0ae0c9…
|
drh
|
1072 |
*/ |
|
a0ae0c9…
|
drh
|
1073 |
/* |
|
a0ae0c9…
|
drh
|
1074 |
** SETTING: email-admin width=40 |
|
e0576ea…
|
stephan
|
1075 |
** This is the email address for the human administrator for the system. |
|
a0ae0c9…
|
drh
|
1076 |
** Abuse and trouble reports and password reset requests are send here. |
|
a0ae0c9…
|
drh
|
1077 |
*/ |
|
a0ae0c9…
|
drh
|
1078 |
/* |
|
a0ae0c9…
|
drh
|
1079 |
** SETTING: email-subname width=16 |
|
a0ae0c9…
|
drh
|
1080 |
** This is a short name used to identifies the repository in the Subject: |
|
a0ae0c9…
|
drh
|
1081 |
** line of email alerts. Traditionally this name is included in square |
|
a0ae0c9…
|
drh
|
1082 |
** brackets. Examples: "[fossil-src]", "[sqlite-src]". |
|
a0ae0c9…
|
drh
|
1083 |
*/ |
|
a0ae0c9…
|
drh
|
1084 |
/* |
|
34d45c5…
|
drh
|
1085 |
** SETTING: email-renew-interval width=16 |
|
34d45c5…
|
drh
|
1086 |
** If this setting as an integer N that is 14 or greater then email |
|
7ae1f31…
|
stephan
|
1087 |
** notification is suspended for subscriptions that have a "last contact |
|
34d45c5…
|
drh
|
1088 |
** time" of more than N days ago. The "last contact time" is recorded |
|
34d45c5…
|
drh
|
1089 |
** in the SUBSCRIBER.LASTCONTACT entry of the database. Logging in, |
|
34d45c5…
|
drh
|
1090 |
** sending a forum post, editing a wiki page, changing subscription settings |
|
34d45c5…
|
drh
|
1091 |
** at /alerts, or visiting /renew all update the last contact time. |
|
34d45c5…
|
drh
|
1092 |
** If this setting is not an integer value or is less than 14 or undefined, |
|
34d45c5…
|
drh
|
1093 |
** then subscriptions never expire. |
|
34d45c5…
|
drh
|
1094 |
*/ |
|
34d45c5…
|
drh
|
1095 |
/* X-VARIABLE: email-renew-warning |
|
34d45c5…
|
drh
|
1096 |
** X-VARIABLE: email-renew-cutoff |
|
34d45c5…
|
drh
|
1097 |
** |
|
34d45c5…
|
drh
|
1098 |
** These CONFIG table entries are not considered "settings" since their |
|
34d45c5…
|
drh
|
1099 |
** values are computed and updated automatically. |
|
34d45c5…
|
drh
|
1100 |
** |
|
34d45c5…
|
drh
|
1101 |
** email-renew-cutoff is the lastContact cutoff for subscription. It |
|
34d45c5…
|
drh
|
1102 |
** is measured in days since 1970-01-01. If The lastContact time for |
|
34d45c5…
|
drh
|
1103 |
** a subscription is less than email-renew-cutoff, then now new emails |
|
34d45c5…
|
drh
|
1104 |
** are sent to the subscriber. |
|
34d45c5…
|
drh
|
1105 |
** |
|
34d45c5…
|
drh
|
1106 |
** email-renew-warning is the time (in days since 1970-01-01) when the |
|
34d45c5…
|
drh
|
1107 |
** last batch of "your subscription is about to expire" emails were |
|
34d45c5…
|
drh
|
1108 |
** sent out. |
|
34d45c5…
|
drh
|
1109 |
** |
|
e0576ea…
|
stephan
|
1110 |
** email-renew-cutoff is normally 7 days behind email-renew-warning. |
|
34d45c5…
|
drh
|
1111 |
*/ |
|
34d45c5…
|
drh
|
1112 |
/* |
|
f741baa…
|
drh
|
1113 |
** SETTING: email-send-method width=5 default=off sensitive |
|
fc5c7d2…
|
drh
|
1114 |
** Determine the method used to send email. Allowed values are |
|
fc5c7d2…
|
drh
|
1115 |
** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value |
|
fc5c7d2…
|
drh
|
1116 |
** means no email is ever sent. The "relay" value means emails are sent |
|
fc5c7d2…
|
drh
|
1117 |
** to an Mail Sending Agent using SMTP located at email-send-relayhost. |
|
e0576ea…
|
stephan
|
1118 |
** The "pipe" value means email messages are piped into a command |
|
fc5c7d2…
|
drh
|
1119 |
** determined by the email-send-command setting. The "dir" value means |
|
fc5c7d2…
|
drh
|
1120 |
** emails are written to individual files in a directory determined |
|
fc5c7d2…
|
drh
|
1121 |
** by the email-send-dir setting. The "db" value means that emails |
|
fc5c7d2…
|
drh
|
1122 |
** are added to an SQLite database named by the* email-send-db setting. |
|
fc5c7d2…
|
drh
|
1123 |
** The "stdout" value writes email text to standard output, for debugging. |
|
fc5c7d2…
|
drh
|
1124 |
*/ |
|
fc5c7d2…
|
drh
|
1125 |
/* |
|
f741baa…
|
drh
|
1126 |
** SETTING: email-send-command width=40 sensitive |
|
fc5c7d2…
|
drh
|
1127 |
** This is a command to which outbound email content is piped when the |
|
fc5c7d2…
|
drh
|
1128 |
** email-send-method is set to "pipe". The command must extract |
|
fc5c7d2…
|
drh
|
1129 |
** recipient, sender, subject, and all other relevant information |
|
fc5c7d2…
|
drh
|
1130 |
** from the email header. |
|
fc5c7d2…
|
drh
|
1131 |
*/ |
|
fc5c7d2…
|
drh
|
1132 |
/* |
|
f741baa…
|
drh
|
1133 |
** SETTING: email-send-dir width=40 sensitive |
|
fc5c7d2…
|
drh
|
1134 |
** This is a directory into which outbound emails are written as individual |
|
fc5c7d2…
|
drh
|
1135 |
** files if the email-send-method is set to "dir". |
|
fc5c7d2…
|
drh
|
1136 |
*/ |
|
fc5c7d2…
|
drh
|
1137 |
/* |
|
f741baa…
|
drh
|
1138 |
** SETTING: email-send-db width=40 sensitive |
|
fc5c7d2…
|
drh
|
1139 |
** This is an SQLite database file into which outbound emails are written |
|
fc5c7d2…
|
drh
|
1140 |
** if the email-send-method is set to "db". |
|
fc5c7d2…
|
drh
|
1141 |
*/ |
|
fc5c7d2…
|
drh
|
1142 |
/* |
|
fc5c7d2…
|
drh
|
1143 |
** SETTING: email-self width=40 |
|
fc5c7d2…
|
drh
|
1144 |
** This is the email address for the repository. Outbound emails add |
|
fc5c7d2…
|
drh
|
1145 |
** this email address as the "From:" field. |
|
fc5c7d2…
|
drh
|
1146 |
*/ |
|
fc5c7d2…
|
drh
|
1147 |
/* |
|
e50c362…
|
drh
|
1148 |
** SETTING: email-listid width=40 |
|
e50c362…
|
drh
|
1149 |
** If this setting is not an empty string, then it becomes the argument to |
|
e50c362…
|
drh
|
1150 |
** a "List-ID:" header that is added to all out-bound notification emails. |
|
0151018…
|
stephan
|
1151 |
** A list ID is required for the generation of unsubscribe links in |
|
0151018…
|
stephan
|
1152 |
** notifications. |
|
e50c362…
|
drh
|
1153 |
*/ |
|
e50c362…
|
drh
|
1154 |
/* |
|
8266b5b…
|
drh
|
1155 |
** SETTING: email-send-relayhost width=40 sensitive default=127.0.0.1 |
|
fc5c7d2…
|
drh
|
1156 |
** This is the hostname and TCP port to which output email messages |
|
fc5c7d2…
|
drh
|
1157 |
** are sent when email-send-method is "relay". There should be an |
|
fc5c7d2…
|
drh
|
1158 |
** SMTP server configured as a Mail Submission Agent listening on the |
|
fc5c7d2…
|
drh
|
1159 |
** designated host and port and all times. |
|
fc5c7d2…
|
drh
|
1160 |
*/ |
|
fc5c7d2…
|
drh
|
1161 |
|
|
fc5c7d2…
|
drh
|
1162 |
|
|
fc5c7d2…
|
drh
|
1163 |
/* |
|
3e7c7e2…
|
drh
|
1164 |
** COMMAND: alerts* abbrv-subcom |
|
e0576ea…
|
stephan
|
1165 |
** |
|
fc5c7d2…
|
drh
|
1166 |
** Usage: %fossil alerts SUBCOMMAND ARGS... |
|
fc5c7d2…
|
drh
|
1167 |
** |
|
fc5c7d2…
|
drh
|
1168 |
** Subcommands: |
|
fc5c7d2…
|
drh
|
1169 |
** |
|
fc5c7d2…
|
drh
|
1170 |
** pending Show all pending alerts. Useful for debugging. |
|
fc5c7d2…
|
drh
|
1171 |
** |
|
fc5c7d2…
|
drh
|
1172 |
** reset Hard reset of all email notification tables |
|
fc5c7d2…
|
drh
|
1173 |
** in the repository. This erases all subscription |
|
fc5c7d2…
|
drh
|
1174 |
** information. ** Use with extreme care ** |
|
fc5c7d2…
|
drh
|
1175 |
** |
|
fc5c7d2…
|
drh
|
1176 |
** send Compose and send pending email alerts. |
|
fc5c7d2…
|
drh
|
1177 |
** Some installations may want to do this via |
|
fc5c7d2…
|
drh
|
1178 |
** a cron-job to make sure alerts are sent |
|
fc5c7d2…
|
drh
|
1179 |
** in a timely manner. |
|
fc5c7d2…
|
drh
|
1180 |
** |
|
2512d2d…
|
km
|
1181 |
** Options: |
|
fc5c7d2…
|
drh
|
1182 |
** --digest Send digests |
|
34d45c5…
|
drh
|
1183 |
** --renewal Send subscription renewal |
|
34d45c5…
|
drh
|
1184 |
** notices |
|
fc5c7d2…
|
drh
|
1185 |
** --test Write to standard output |
|
fc5c7d2…
|
drh
|
1186 |
** |
|
fc5c7d2…
|
drh
|
1187 |
** settings [NAME VALUE] With no arguments, list all email settings. |
|
fc5c7d2…
|
drh
|
1188 |
** Or change the value of a single email setting. |
|
fc5c7d2…
|
drh
|
1189 |
** |
|
fc5c7d2…
|
drh
|
1190 |
** status Report on the status of the email alert |
|
fc5c7d2…
|
drh
|
1191 |
** subsystem |
|
fc5c7d2…
|
drh
|
1192 |
** |
|
34d45c5…
|
drh
|
1193 |
** subscribers [PATTERN] List all subscribers matching PATTERN. Either |
|
34d45c5…
|
drh
|
1194 |
** LIKE or GLOB wildcards can be used in PATTERN. |
|
fc5c7d2…
|
drh
|
1195 |
** |
|
fc5c7d2…
|
drh
|
1196 |
** test-message TO [OPTS] Send a single email message using whatever |
|
fc5c7d2…
|
drh
|
1197 |
** email sending mechanism is currently configured. |
|
fc5c7d2…
|
drh
|
1198 |
** Use this for testing the email notification |
|
2512d2d…
|
km
|
1199 |
** configuration. |
|
fc5c7d2…
|
drh
|
1200 |
** |
|
2512d2d…
|
km
|
1201 |
** Options: |
|
34d45c5…
|
drh
|
1202 |
** --body FILENAME Content from FILENAME |
|
34d45c5…
|
drh
|
1203 |
** --smtp-trace Trace SMTP processing |
|
34d45c5…
|
drh
|
1204 |
** --stdout Send msg to stdout |
|
34d45c5…
|
drh
|
1205 |
** -S|--subject SUBJECT Message "subject:" |
|
fc5c7d2…
|
drh
|
1206 |
** |
|
fc5c7d2…
|
drh
|
1207 |
** unsubscribe EMAIL Remove a single subscriber with the given EMAIL. |
|
fc5c7d2…
|
drh
|
1208 |
*/ |
|
fc5c7d2…
|
drh
|
1209 |
void alert_cmd(void){ |
|
fc5c7d2…
|
drh
|
1210 |
const char *zCmd; |
|
fc5c7d2…
|
drh
|
1211 |
int nCmd; |
|
fc5c7d2…
|
drh
|
1212 |
db_find_and_open_repository(0, 0); |
|
fc5c7d2…
|
drh
|
1213 |
alert_schema(0); |
|
fc5c7d2…
|
drh
|
1214 |
zCmd = g.argc>=3 ? g.argv[2] : "x"; |
|
fc5c7d2…
|
drh
|
1215 |
nCmd = (int)strlen(zCmd); |
|
fc5c7d2…
|
drh
|
1216 |
if( strncmp(zCmd, "pending", nCmd)==0 ){ |
|
fc5c7d2…
|
drh
|
1217 |
Stmt q; |
|
fc5c7d2…
|
drh
|
1218 |
verify_all_options(); |
|
fc5c7d2…
|
drh
|
1219 |
if( g.argc!=3 ) usage("pending"); |
|
fc5c7d2…
|
drh
|
1220 |
db_prepare(&q,"SELECT eventid, sentSep, sentDigest, sentMod" |
|
fc5c7d2…
|
drh
|
1221 |
" FROM pending_alert"); |
|
fc5c7d2…
|
drh
|
1222 |
while( db_step(&q)==SQLITE_ROW ){ |
|
fc5c7d2…
|
drh
|
1223 |
fossil_print("%10s %7s %10s %7s\n", |
|
fc5c7d2…
|
drh
|
1224 |
db_column_text(&q,0), |
|
fc5c7d2…
|
drh
|
1225 |
db_column_int(&q,1) ? "sentSep" : "", |
|
fc5c7d2…
|
drh
|
1226 |
db_column_int(&q,2) ? "sentDigest" : "", |
|
fc5c7d2…
|
drh
|
1227 |
db_column_int(&q,3) ? "sentMod" : ""); |
|
fc5c7d2…
|
drh
|
1228 |
} |
|
fc5c7d2…
|
drh
|
1229 |
db_finalize(&q); |
|
fc5c7d2…
|
drh
|
1230 |
}else |
|
fc5c7d2…
|
drh
|
1231 |
if( strncmp(zCmd, "reset", nCmd)==0 ){ |
|
fc5c7d2…
|
drh
|
1232 |
int c; |
|
fc5c7d2…
|
drh
|
1233 |
int bForce = find_option("force","f",0)!=0; |
|
fc5c7d2…
|
drh
|
1234 |
verify_all_options(); |
|
fc5c7d2…
|
drh
|
1235 |
if( bForce ){ |
|
fc5c7d2…
|
drh
|
1236 |
c = 'y'; |
|
fc5c7d2…
|
drh
|
1237 |
}else{ |
|
fc5c7d2…
|
drh
|
1238 |
Blob yn; |
|
fc5c7d2…
|
drh
|
1239 |
fossil_print( |
|
fc5c7d2…
|
drh
|
1240 |
"This will erase all content in the repository tables, thus\n" |
|
fc5c7d2…
|
drh
|
1241 |
"deleting all subscriber information. The information will be\n" |
|
fc5c7d2…
|
drh
|
1242 |
"unrecoverable.\n"); |
|
fc5c7d2…
|
drh
|
1243 |
prompt_user("Continue? (y/N) ", &yn); |
|
fc5c7d2…
|
drh
|
1244 |
c = blob_str(&yn)[0]; |
|
fc5c7d2…
|
drh
|
1245 |
blob_reset(&yn); |
|
fc5c7d2…
|
drh
|
1246 |
} |
|
fc5c7d2…
|
drh
|
1247 |
if( c=='y' ){ |
|
169ba8d…
|
drh
|
1248 |
alert_drop_trigger(); |
|
fc5c7d2…
|
drh
|
1249 |
db_multi_exec( |
|
fc5c7d2…
|
drh
|
1250 |
"DROP TABLE IF EXISTS subscriber;\n" |
|
fc5c7d2…
|
drh
|
1251 |
"DROP TABLE IF EXISTS pending_alert;\n" |
|
fc5c7d2…
|
drh
|
1252 |
"DROP TABLE IF EXISTS alert_bounce;\n" |
|
fc5c7d2…
|
drh
|
1253 |
/* Legacy */ |
|
fc5c7d2…
|
drh
|
1254 |
"DROP TABLE IF EXISTS alert_pending;\n" |
|
fc5c7d2…
|
drh
|
1255 |
"DROP TABLE IF EXISTS subscription;\n" |
|
fc5c7d2…
|
drh
|
1256 |
); |
|
fc5c7d2…
|
drh
|
1257 |
alert_schema(0); |
|
fc5c7d2…
|
drh
|
1258 |
} |
|
fc5c7d2…
|
drh
|
1259 |
}else |
|
fc5c7d2…
|
drh
|
1260 |
if( strncmp(zCmd, "send", nCmd)==0 ){ |
|
fc5c7d2…
|
drh
|
1261 |
u32 eFlags = 0; |
|
fc5c7d2…
|
drh
|
1262 |
if( find_option("digest",0,0)!=0 ) eFlags |= SENDALERT_DIGEST; |
|
34d45c5…
|
drh
|
1263 |
if( find_option("renewal",0,0)!=0 ) eFlags |= SENDALERT_RENEWAL; |
|
fc5c7d2…
|
drh
|
1264 |
if( find_option("test",0,0)!=0 ){ |
|
fc5c7d2…
|
drh
|
1265 |
eFlags |= SENDALERT_PRESERVE|SENDALERT_STDOUT; |
|
fc5c7d2…
|
drh
|
1266 |
} |
|
fc5c7d2…
|
drh
|
1267 |
verify_all_options(); |
|
fc5c7d2…
|
drh
|
1268 |
alert_send_alerts(eFlags); |
|
fc5c7d2…
|
drh
|
1269 |
}else |
|
fc5c7d2…
|
drh
|
1270 |
if( strncmp(zCmd, "settings", nCmd)==0 ){ |
|
fc5c7d2…
|
drh
|
1271 |
int isGlobal = find_option("global",0,0)!=0; |
|
fc5c7d2…
|
drh
|
1272 |
int nSetting; |
|
fc5c7d2…
|
drh
|
1273 |
const Setting *pSetting = setting_info(&nSetting); |
|
fc5c7d2…
|
drh
|
1274 |
db_open_config(1, 0); |
|
fc5c7d2…
|
drh
|
1275 |
verify_all_options(); |
|
fc5c7d2…
|
drh
|
1276 |
if( g.argc!=3 && g.argc!=5 ) usage("setting [NAME VALUE]"); |
|
fc5c7d2…
|
drh
|
1277 |
if( g.argc==5 ){ |
|
fc5c7d2…
|
drh
|
1278 |
const char *zLabel = g.argv[3]; |
|
fc5c7d2…
|
drh
|
1279 |
if( strncmp(zLabel, "email-", 6)!=0 |
|
fc5c7d2…
|
drh
|
1280 |
|| (pSetting = db_find_setting(zLabel, 1))==0 ){ |
|
fc5c7d2…
|
drh
|
1281 |
fossil_fatal("not a valid email setting: \"%s\"", zLabel); |
|
fc5c7d2…
|
drh
|
1282 |
} |
|
0a5d0e1…
|
drh
|
1283 |
db_set(pSetting->name/*works-like:""*/, g.argv[4], isGlobal); |
|
fc5c7d2…
|
drh
|
1284 |
g.argc = 3; |
|
fc5c7d2…
|
drh
|
1285 |
} |
|
fc5c7d2…
|
drh
|
1286 |
pSetting = setting_info(&nSetting); |
|
fc5c7d2…
|
drh
|
1287 |
for(; nSetting>0; nSetting--, pSetting++ ){ |
|
fc5c7d2…
|
drh
|
1288 |
if( strncmp(pSetting->name,"email-",6)!=0 ) continue; |
|
8f4aedc…
|
drh
|
1289 |
print_setting(pSetting, 0, 0); |
|
fc5c7d2…
|
drh
|
1290 |
} |
|
fc5c7d2…
|
drh
|
1291 |
}else |
|
fc5c7d2…
|
drh
|
1292 |
if( strncmp(zCmd, "status", nCmd)==0 ){ |
|
34d45c5…
|
drh
|
1293 |
Stmt q; |
|
34d45c5…
|
drh
|
1294 |
int iCutoff; |
|
fc5c7d2…
|
drh
|
1295 |
int nSetting, n; |
|
fc5c7d2…
|
drh
|
1296 |
static const char *zFmt = "%-29s %d\n"; |
|
fc5c7d2…
|
drh
|
1297 |
const Setting *pSetting = setting_info(&nSetting); |
|
fc5c7d2…
|
drh
|
1298 |
db_open_config(1, 0); |
|
fc5c7d2…
|
drh
|
1299 |
verify_all_options(); |
|
fc5c7d2…
|
drh
|
1300 |
if( g.argc!=3 ) usage("status"); |
|
fc5c7d2…
|
drh
|
1301 |
pSetting = setting_info(&nSetting); |
|
fc5c7d2…
|
drh
|
1302 |
for(; nSetting>0; nSetting--, pSetting++ ){ |
|
fc5c7d2…
|
drh
|
1303 |
if( strncmp(pSetting->name,"email-",6)!=0 ) continue; |
|
8f4aedc…
|
drh
|
1304 |
print_setting(pSetting, 0, 0); |
|
fc5c7d2…
|
drh
|
1305 |
} |
|
fc5c7d2…
|
drh
|
1306 |
n = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentSep"); |
|
fc5c7d2…
|
drh
|
1307 |
fossil_print(zFmt/*works-like:"%s%d"*/, "pending-alerts", n); |
|
fc5c7d2…
|
drh
|
1308 |
n = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentDigest"); |
|
fc5c7d2…
|
drh
|
1309 |
fossil_print(zFmt/*works-like:"%s%d"*/, "pending-digest-alerts", n); |
|
34d45c5…
|
drh
|
1310 |
db_prepare(&q, |
|
34d45c5…
|
drh
|
1311 |
"SELECT" |
|
34d45c5…
|
drh
|
1312 |
" name," |
|
34d45c5…
|
drh
|
1313 |
" value," |
|
34d45c5…
|
drh
|
1314 |
" now()/86400-value," |
|
34d45c5…
|
drh
|
1315 |
" date(value*86400,'unixepoch')" |
|
34d45c5…
|
drh
|
1316 |
" FROM repository.config" |
|
34d45c5…
|
drh
|
1317 |
" WHERE name in ('email-renew-warning','email-renew-cutoff');"); |
|
34d45c5…
|
drh
|
1318 |
while( db_step(&q)==SQLITE_ROW ){ |
|
34d45c5…
|
drh
|
1319 |
fossil_print("%-29s %-6d (%d days ago on %s)\n", |
|
34d45c5…
|
drh
|
1320 |
db_column_text(&q, 0), |
|
34d45c5…
|
drh
|
1321 |
db_column_int(&q, 1), |
|
34d45c5…
|
drh
|
1322 |
db_column_int(&q, 2), |
|
34d45c5…
|
drh
|
1323 |
db_column_text(&q, 3)); |
|
34d45c5…
|
drh
|
1324 |
} |
|
34d45c5…
|
drh
|
1325 |
db_finalize(&q); |
|
fc5c7d2…
|
drh
|
1326 |
n = db_int(0,"SELECT count(*) FROM subscriber"); |
|
fc5c7d2…
|
drh
|
1327 |
fossil_print(zFmt/*works-like:"%s%d"*/, "total-subscribers", n); |
|
34d45c5…
|
drh
|
1328 |
iCutoff = db_get_int("email-renew-cutoff", 0); |
|
fc5c7d2…
|
drh
|
1329 |
n = db_int(0, "SELECT count(*) FROM subscriber WHERE sverified" |
|
34d45c5…
|
drh
|
1330 |
" AND NOT sdonotcall AND length(ssub)>1" |
|
34d45c5…
|
drh
|
1331 |
" AND lastContact>=%d", iCutoff); |
|
fc5c7d2…
|
drh
|
1332 |
fossil_print(zFmt/*works-like:"%s%d"*/, "active-subscribers", n); |
|
fc5c7d2…
|
drh
|
1333 |
}else |
|
fc5c7d2…
|
drh
|
1334 |
if( strncmp(zCmd, "subscribers", nCmd)==0 ){ |
|
fc5c7d2…
|
drh
|
1335 |
Stmt q; |
|
fc5c7d2…
|
drh
|
1336 |
verify_all_options(); |
|
fc5c7d2…
|
drh
|
1337 |
if( g.argc!=3 && g.argc!=4 ) usage("subscribers [PATTERN]"); |
|
fc5c7d2…
|
drh
|
1338 |
if( g.argc==4 ){ |
|
fc5c7d2…
|
drh
|
1339 |
char *zPattern = g.argv[3]; |
|
fc5c7d2…
|
drh
|
1340 |
db_prepare(&q, |
|
fc5c7d2…
|
drh
|
1341 |
"SELECT semail FROM subscriber" |
|
fc5c7d2…
|
drh
|
1342 |
" WHERE semail LIKE '%%%q%%' OR suname LIKE '%%%q%%'" |
|
fc5c7d2…
|
drh
|
1343 |
" OR semail GLOB '*%q*' or suname GLOB '*%q*'" |
|
fc5c7d2…
|
drh
|
1344 |
" ORDER BY semail", |
|
fc5c7d2…
|
drh
|
1345 |
zPattern, zPattern, zPattern, zPattern); |
|
fc5c7d2…
|
drh
|
1346 |
}else{ |
|
fc5c7d2…
|
drh
|
1347 |
db_prepare(&q, |
|
fc5c7d2…
|
drh
|
1348 |
"SELECT semail FROM subscriber" |
|
fc5c7d2…
|
drh
|
1349 |
" ORDER BY semail"); |
|
fc5c7d2…
|
drh
|
1350 |
} |
|
fc5c7d2…
|
drh
|
1351 |
while( db_step(&q)==SQLITE_ROW ){ |
|
fc5c7d2…
|
drh
|
1352 |
fossil_print("%s\n", db_column_text(&q, 0)); |
|
fc5c7d2…
|
drh
|
1353 |
} |
|
fc5c7d2…
|
drh
|
1354 |
db_finalize(&q); |
|
fc5c7d2…
|
drh
|
1355 |
}else |
|
fc5c7d2…
|
drh
|
1356 |
if( strncmp(zCmd, "test-message", nCmd)==0 ){ |
|
fc5c7d2…
|
drh
|
1357 |
Blob prompt, body, hdr; |
|
fc5c7d2…
|
drh
|
1358 |
const char *zDest = find_option("stdout",0,0)!=0 ? "stdout" : 0; |
|
fc5c7d2…
|
drh
|
1359 |
int i; |
|
fc5c7d2…
|
drh
|
1360 |
u32 mFlags = ALERT_IMMEDIATE_FAIL; |
|
fc5c7d2…
|
drh
|
1361 |
const char *zSubject = find_option("subject", "S", 1); |
|
fc5c7d2…
|
drh
|
1362 |
const char *zSource = find_option("body", 0, 1); |
|
fc5c7d2…
|
drh
|
1363 |
AlertSender *pSender; |
|
fc5c7d2…
|
drh
|
1364 |
if( find_option("smtp-trace",0,0)!=0 ) mFlags |= ALERT_TRACE; |
|
fc5c7d2…
|
drh
|
1365 |
verify_all_options(); |
|
fc5c7d2…
|
drh
|
1366 |
blob_init(&prompt, 0, 0); |
|
fc5c7d2…
|
drh
|
1367 |
blob_init(&body, 0, 0); |
|
fc5c7d2…
|
drh
|
1368 |
blob_init(&hdr, 0, 0); |
|
fc5c7d2…
|
drh
|
1369 |
blob_appendf(&hdr,"To: "); |
|
fc5c7d2…
|
drh
|
1370 |
for(i=3; i<g.argc; i++){ |
|
fc5c7d2…
|
drh
|
1371 |
if( i>3 ) blob_append(&hdr, ", ", 2); |
|
fc5c7d2…
|
drh
|
1372 |
blob_appendf(&hdr, "<%s>", g.argv[i]); |
|
fc5c7d2…
|
drh
|
1373 |
} |
|
fc5c7d2…
|
drh
|
1374 |
blob_append(&hdr,"\r\n",2); |
|
fc5c7d2…
|
drh
|
1375 |
if( zSubject==0 ) zSubject = "fossil alerts test-message"; |
|
fc5c7d2…
|
drh
|
1376 |
blob_appendf(&hdr, "Subject: %s\r\n", zSubject); |
|
fc5c7d2…
|
drh
|
1377 |
if( zSource ){ |
|
fc5c7d2…
|
drh
|
1378 |
blob_read_from_file(&body, zSource, ExtFILE); |
|
fc5c7d2…
|
drh
|
1379 |
}else{ |
|
fc5c7d2…
|
drh
|
1380 |
prompt_for_user_comment(&body, &prompt); |
|
fc5c7d2…
|
drh
|
1381 |
} |
|
fc5c7d2…
|
drh
|
1382 |
blob_add_final_newline(&body); |
|
fc5c7d2…
|
drh
|
1383 |
pSender = alert_sender_new(zDest, mFlags); |
|
fc5c7d2…
|
drh
|
1384 |
alert_send(pSender, &hdr, &body, 0); |
|
fc5c7d2…
|
drh
|
1385 |
alert_sender_free(pSender); |
|
fc5c7d2…
|
drh
|
1386 |
blob_reset(&hdr); |
|
fc5c7d2…
|
drh
|
1387 |
blob_reset(&body); |
|
fc5c7d2…
|
drh
|
1388 |
blob_reset(&prompt); |
|
fc5c7d2…
|
drh
|
1389 |
}else |
|
fc5c7d2…
|
drh
|
1390 |
if( strncmp(zCmd, "unsubscribe", nCmd)==0 ){ |
|
fc5c7d2…
|
drh
|
1391 |
verify_all_options(); |
|
fc5c7d2…
|
drh
|
1392 |
if( g.argc!=4 ) usage("unsubscribe EMAIL"); |
|
fc5c7d2…
|
drh
|
1393 |
db_multi_exec( |
|
fc5c7d2…
|
drh
|
1394 |
"DELETE FROM subscriber WHERE semail=%Q", g.argv[3]); |
|
fc5c7d2…
|
drh
|
1395 |
}else |
|
fc5c7d2…
|
drh
|
1396 |
{ |
|
fc5c7d2…
|
drh
|
1397 |
usage("pending|reset|send|setting|status|" |
|
fc5c7d2…
|
drh
|
1398 |
"subscribers|test-message|unsubscribe"); |
|
fc5c7d2…
|
drh
|
1399 |
} |
|
fc5c7d2…
|
drh
|
1400 |
} |
|
fc5c7d2…
|
drh
|
1401 |
|
|
fc5c7d2…
|
drh
|
1402 |
/* |
|
fc5c7d2…
|
drh
|
1403 |
** Do error checking on a submitted subscription form. Return TRUE |
|
fc5c7d2…
|
drh
|
1404 |
** if the submission is valid. Return false if any problems are seen. |
|
fc5c7d2…
|
drh
|
1405 |
*/ |
|
fc5c7d2…
|
drh
|
1406 |
static int subscribe_error_check( |
|
fc5c7d2…
|
drh
|
1407 |
int *peErr, /* Type of error */ |
|
fc5c7d2…
|
drh
|
1408 |
char **pzErr, /* Error message text */ |
|
fc5c7d2…
|
drh
|
1409 |
int needCaptcha /* True if captcha check needed */ |
|
fc5c7d2…
|
drh
|
1410 |
){ |
|
fc5c7d2…
|
drh
|
1411 |
const char *zEAddr; |
|
fc5c7d2…
|
drh
|
1412 |
int i, j, n; |
|
fc5c7d2…
|
drh
|
1413 |
char c; |
|
fc5c7d2…
|
drh
|
1414 |
|
|
fc5c7d2…
|
drh
|
1415 |
*peErr = 0; |
|
fc5c7d2…
|
drh
|
1416 |
*pzErr = 0; |
|
fc5c7d2…
|
drh
|
1417 |
|
|
c00e912…
|
drh
|
1418 |
/* Verify the captcha first */ |
|
c00e912…
|
drh
|
1419 |
if( needCaptcha ){ |
|
c00e912…
|
drh
|
1420 |
if( !captcha_is_correct(1) ){ |
|
c00e912…
|
drh
|
1421 |
*peErr = 2; |
|
c00e912…
|
drh
|
1422 |
*pzErr = mprintf("incorrect security code"); |
|
c00e912…
|
drh
|
1423 |
return 0; |
|
c00e912…
|
drh
|
1424 |
} |
|
c00e912…
|
drh
|
1425 |
} |
|
c00e912…
|
drh
|
1426 |
|
|
fc5c7d2…
|
drh
|
1427 |
/* Check the validity of the email address. |
|
fc5c7d2…
|
drh
|
1428 |
** |
|
fc5c7d2…
|
drh
|
1429 |
** (1) Exactly one '@' character. |
|
fc5c7d2…
|
drh
|
1430 |
** (2) No other characters besides [a-zA-Z0-9._+-] |
|
fc5c7d2…
|
drh
|
1431 |
** |
|
fc5c7d2…
|
drh
|
1432 |
** The local part is currently more restrictive than RFC 5322 allows: |
|
fc5c7d2…
|
drh
|
1433 |
** https://stackoverflow.com/a/2049510/142454 We will expand this as |
|
fc5c7d2…
|
drh
|
1434 |
** necessary. |
|
fc5c7d2…
|
drh
|
1435 |
*/ |
|
fc5c7d2…
|
drh
|
1436 |
zEAddr = P("e"); |
|
c00e912…
|
drh
|
1437 |
if( zEAddr==0 ){ |
|
c00e912…
|
drh
|
1438 |
*peErr = 1; |
|
c00e912…
|
drh
|
1439 |
*pzErr = mprintf("required"); |
|
c00e912…
|
drh
|
1440 |
return 0; |
|
c00e912…
|
drh
|
1441 |
} |
|
fc5c7d2…
|
drh
|
1442 |
for(i=j=n=0; (c = zEAddr[i])!=0; i++){ |
|
fc5c7d2…
|
drh
|
1443 |
if( c=='@' ){ |
|
fc5c7d2…
|
drh
|
1444 |
n = i; |
|
fc5c7d2…
|
drh
|
1445 |
j++; |
|
fc5c7d2…
|
drh
|
1446 |
continue; |
|
fc5c7d2…
|
drh
|
1447 |
} |
|
fc5c7d2…
|
drh
|
1448 |
if( !fossil_isalnum(c) && c!='.' && c!='_' && c!='-' && c!='+' ){ |
|
fc5c7d2…
|
drh
|
1449 |
*peErr = 1; |
|
fc5c7d2…
|
drh
|
1450 |
*pzErr = mprintf("illegal character in email address: 0x%x '%c'", |
|
fc5c7d2…
|
drh
|
1451 |
c, c); |
|
fc5c7d2…
|
drh
|
1452 |
return 0; |
|
fc5c7d2…
|
drh
|
1453 |
} |
|
fc5c7d2…
|
drh
|
1454 |
} |
|
fc5c7d2…
|
drh
|
1455 |
if( j!=1 ){ |
|
fc5c7d2…
|
drh
|
1456 |
*peErr = 1; |
|
fc5c7d2…
|
drh
|
1457 |
*pzErr = mprintf("email address should contain exactly one '@'"); |
|
fc5c7d2…
|
drh
|
1458 |
return 0; |
|
fc5c7d2…
|
drh
|
1459 |
} |
|
fc5c7d2…
|
drh
|
1460 |
if( n<1 ){ |
|
fc5c7d2…
|
drh
|
1461 |
*peErr = 1; |
|
fc5c7d2…
|
drh
|
1462 |
*pzErr = mprintf("name missing before '@' in email address"); |
|
fc5c7d2…
|
drh
|
1463 |
return 0; |
|
fc5c7d2…
|
drh
|
1464 |
} |
|
fc5c7d2…
|
drh
|
1465 |
if( n>i-5 ){ |
|
fc5c7d2…
|
drh
|
1466 |
*peErr = 1; |
|
fc5c7d2…
|
drh
|
1467 |
*pzErr = mprintf("email domain too short"); |
|
fc5c7d2…
|
drh
|
1468 |
return 0; |
|
fc5c7d2…
|
drh
|
1469 |
} |
|
fc5c7d2…
|
drh
|
1470 |
|
|
c00e912…
|
drh
|
1471 |
if( authorized_subscription_email(zEAddr)==0 ){ |
|
c00e912…
|
drh
|
1472 |
*peErr = 1; |
|
c00e912…
|
drh
|
1473 |
*pzErr = mprintf("not an authorized email address"); |
|
fc5c7d2…
|
drh
|
1474 |
return 0; |
|
fc5c7d2…
|
drh
|
1475 |
} |
|
fc5c7d2…
|
drh
|
1476 |
|
|
fc5c7d2…
|
drh
|
1477 |
/* Check to make sure the email address is available for reuse */ |
|
fc5c7d2…
|
drh
|
1478 |
if( db_exists("SELECT 1 FROM subscriber WHERE semail=%Q", zEAddr) ){ |
|
fc5c7d2…
|
drh
|
1479 |
*peErr = 1; |
|
fc5c7d2…
|
drh
|
1480 |
*pzErr = mprintf("this email address is used by someone else"); |
|
fc5c7d2…
|
drh
|
1481 |
return 0; |
|
fc5c7d2…
|
drh
|
1482 |
} |
|
fc5c7d2…
|
drh
|
1483 |
|
|
fc5c7d2…
|
drh
|
1484 |
/* If we reach this point, all is well */ |
|
fc5c7d2…
|
drh
|
1485 |
return 1; |
|
fc5c7d2…
|
drh
|
1486 |
} |
|
fc5c7d2…
|
drh
|
1487 |
|
|
fc5c7d2…
|
drh
|
1488 |
/* |
|
fc5c7d2…
|
drh
|
1489 |
** Text of email message sent in order to confirm a subscription. |
|
fc5c7d2…
|
drh
|
1490 |
*/ |
|
275da70…
|
danield
|
1491 |
static const char zConfirmMsg[] = |
|
fc5c7d2…
|
drh
|
1492 |
@ Someone has signed you up for email alerts on the Fossil repository |
|
fc5c7d2…
|
drh
|
1493 |
@ at %s. |
|
fc5c7d2…
|
drh
|
1494 |
@ |
|
fc5c7d2…
|
drh
|
1495 |
@ To confirm your subscription and begin receiving alerts, click on |
|
fc5c7d2…
|
drh
|
1496 |
@ the following hyperlink: |
|
fc5c7d2…
|
drh
|
1497 |
@ |
|
fc5c7d2…
|
drh
|
1498 |
@ %s/alerts/%s |
|
fc5c7d2…
|
drh
|
1499 |
@ |
|
fc5c7d2…
|
drh
|
1500 |
@ Save the hyperlink above! You can reuse this same hyperlink to |
|
fc5c7d2…
|
drh
|
1501 |
@ unsubscribe or to change the kinds of alerts you receive. |
|
fc5c7d2…
|
drh
|
1502 |
@ |
|
fc5c7d2…
|
drh
|
1503 |
@ If you do not want to subscribe, you can simply ignore this message. |
|
fc5c7d2…
|
drh
|
1504 |
@ You will not be contacted again. |
|
fc5c7d2…
|
drh
|
1505 |
@ |
|
fc5c7d2…
|
drh
|
1506 |
; |
|
fc5c7d2…
|
drh
|
1507 |
|
|
fc5c7d2…
|
drh
|
1508 |
/* |
|
fc5c7d2…
|
drh
|
1509 |
** Append the text of an email confirmation message to the given |
|
fc5c7d2…
|
drh
|
1510 |
** Blob. The security code is in zCode. |
|
fc5c7d2…
|
drh
|
1511 |
*/ |
|
fc5c7d2…
|
drh
|
1512 |
void alert_append_confirmation_message(Blob *pMsg, const char *zCode){ |
|
fc5c7d2…
|
drh
|
1513 |
blob_appendf(pMsg, zConfirmMsg/*works-like:"%s%s%s"*/, |
|
fc5c7d2…
|
drh
|
1514 |
g.zBaseURL, g.zBaseURL, zCode); |
|
fc5c7d2…
|
drh
|
1515 |
} |
|
fc5c7d2…
|
drh
|
1516 |
|
|
fc5c7d2…
|
drh
|
1517 |
/* |
|
fc5c7d2…
|
drh
|
1518 |
** WEBPAGE: subscribe |
|
fc5c7d2…
|
drh
|
1519 |
** |
|
fc5c7d2…
|
drh
|
1520 |
** Allow users to subscribe to email notifications. |
|
fc5c7d2…
|
drh
|
1521 |
** |
|
fc5c7d2…
|
drh
|
1522 |
** This page is usually run by users who are not logged in. |
|
fc5c7d2…
|
drh
|
1523 |
** A logged-in user can add email notifications on the /alerts page. |
|
fc5c7d2…
|
drh
|
1524 |
** Access to this page by a logged in user (other than an |
|
fc5c7d2…
|
drh
|
1525 |
** administrator) results in a redirect to the /alerts page. |
|
fc5c7d2…
|
drh
|
1526 |
** |
|
fc5c7d2…
|
drh
|
1527 |
** Administrators can visit this page in order to sign up other |
|
fc5c7d2…
|
drh
|
1528 |
** users. |
|
fc5c7d2…
|
drh
|
1529 |
** |
|
fc5c7d2…
|
drh
|
1530 |
** The Alerts permission ("7") is required to access this |
|
fc5c7d2…
|
drh
|
1531 |
** page. To allow anonymous passers-by to sign up for email |
|
fc5c7d2…
|
drh
|
1532 |
** notification, set Email-Alerts on user "nobody" or "anonymous". |
|
fc5c7d2…
|
drh
|
1533 |
*/ |
|
fc5c7d2…
|
drh
|
1534 |
void subscribe_page(void){ |
|
fc5c7d2…
|
drh
|
1535 |
int needCaptcha; |
|
11d1233…
|
drh
|
1536 |
unsigned int uSeed = 0; |
|
fc5c7d2…
|
drh
|
1537 |
const char *zDecoded; |
|
fc5c7d2…
|
drh
|
1538 |
char *zCaptcha = 0; |
|
fc5c7d2…
|
drh
|
1539 |
char *zErr = 0; |
|
fc5c7d2…
|
drh
|
1540 |
int eErr = 0; |
|
fc5c7d2…
|
drh
|
1541 |
int di; |
|
fc5c7d2…
|
drh
|
1542 |
|
|
fc5c7d2…
|
drh
|
1543 |
if( alert_webpages_disabled() ) return; |
|
fc5c7d2…
|
drh
|
1544 |
login_check_credentials(); |
|
fc5c7d2…
|
drh
|
1545 |
if( !g.perm.EmailAlert ){ |
|
fc5c7d2…
|
drh
|
1546 |
login_needed(g.anon.EmailAlert); |
|
fc5c7d2…
|
drh
|
1547 |
return; |
|
fc5c7d2…
|
drh
|
1548 |
} |
|
fc5c7d2…
|
drh
|
1549 |
if( login_is_individual() |
|
fc5c7d2…
|
drh
|
1550 |
&& db_exists("SELECT 1 FROM subscriber WHERE suname=%Q",g.zLogin) |
|
fc5c7d2…
|
drh
|
1551 |
){ |
|
fc5c7d2…
|
drh
|
1552 |
/* This person is already signed up for email alerts. Jump |
|
fc5c7d2…
|
drh
|
1553 |
** to the screen that lets them edit their alert preferences. |
|
fc5c7d2…
|
drh
|
1554 |
** Except, administrators can create subscriptions for others so |
|
fc5c7d2…
|
drh
|
1555 |
** do not jump for them. |
|
fc5c7d2…
|
drh
|
1556 |
*/ |
|
fc5c7d2…
|
drh
|
1557 |
if( g.perm.Admin ){ |
|
fc5c7d2…
|
drh
|
1558 |
/* Admins get a link to admin their own account, but they |
|
fc5c7d2…
|
drh
|
1559 |
** stay on this page so that they can create subscriptions |
|
fc5c7d2…
|
drh
|
1560 |
** for other people. */ |
|
fc5c7d2…
|
drh
|
1561 |
style_submenu_element("My Subscription","%R/alerts"); |
|
fc5c7d2…
|
drh
|
1562 |
}else{ |
|
fc5c7d2…
|
drh
|
1563 |
/* Everybody else jumps to the page to administer their own |
|
fc5c7d2…
|
drh
|
1564 |
** account only. */ |
|
fc5c7d2…
|
drh
|
1565 |
cgi_redirectf("%R/alerts"); |
|
fc5c7d2…
|
drh
|
1566 |
return; |
|
fc5c7d2…
|
drh
|
1567 |
} |
|
fc5c7d2…
|
drh
|
1568 |
} |
|
c00e912…
|
drh
|
1569 |
if( !g.perm.Admin && !db_get_boolean("anon-subscribe",1) ){ |
|
c00e912…
|
drh
|
1570 |
register_page(); |
|
c00e912…
|
drh
|
1571 |
return; |
|
c00e912…
|
drh
|
1572 |
} |
|
112c713…
|
drh
|
1573 |
style_set_current_feature("alerts"); |
|
fc5c7d2…
|
drh
|
1574 |
alert_submenu_common(); |
|
fc5c7d2…
|
drh
|
1575 |
needCaptcha = !login_is_individual(); |
|
fc5c7d2…
|
drh
|
1576 |
if( P("submit") |
|
920ace1…
|
drh
|
1577 |
&& cgi_csrf_safe(2) |
|
fc5c7d2…
|
drh
|
1578 |
&& subscribe_error_check(&eErr,&zErr,needCaptcha) |
|
fc5c7d2…
|
drh
|
1579 |
){ |
|
fc5c7d2…
|
drh
|
1580 |
/* A validated request for a new subscription has been received. */ |
|
fc5c7d2…
|
drh
|
1581 |
char ssub[20]; |
|
fc5c7d2…
|
drh
|
1582 |
const char *zEAddr = P("e"); |
|
fc5c7d2…
|
drh
|
1583 |
const char *zCode; /* New subscriber code (in hex) */ |
|
fc5c7d2…
|
drh
|
1584 |
int nsub = 0; |
|
fc5c7d2…
|
drh
|
1585 |
const char *suname = PT("suname"); |
|
fc5c7d2…
|
drh
|
1586 |
if( suname==0 && needCaptcha==0 && !g.perm.Admin ) suname = g.zLogin; |
|
fc5c7d2…
|
drh
|
1587 |
if( suname && suname[0]==0 ) suname = 0; |
|
fc5c7d2…
|
drh
|
1588 |
if( PB("sa") ) ssub[nsub++] = 'a'; |
|
fc5c7d2…
|
drh
|
1589 |
if( g.perm.Read && PB("sc") ) ssub[nsub++] = 'c'; |
|
fc5c7d2…
|
drh
|
1590 |
if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f'; |
|
d4361f6…
|
drh
|
1591 |
if( g.perm.RdForum && PB("sn") ) ssub[nsub++] = 'n'; |
|
d4361f6…
|
drh
|
1592 |
if( g.perm.RdForum && PB("sr") ) ssub[nsub++] = 'r'; |
|
fc5c7d2…
|
drh
|
1593 |
if( g.perm.RdTkt && PB("st") ) ssub[nsub++] = 't'; |
|
d96055c…
|
stephan
|
1594 |
if( g.perm.Admin && PB("su") ) ssub[nsub++] = 'u'; |
|
fc5c7d2…
|
drh
|
1595 |
if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w'; |
|
ea81b30…
|
drh
|
1596 |
if( g.perm.RdForum && PB("sx") ) ssub[nsub++] = 'x'; |
|
fc5c7d2…
|
drh
|
1597 |
ssub[nsub] = 0; |
|
8a3dc1a…
|
drh
|
1598 |
zCode = db_text(0, |
|
fc5c7d2…
|
drh
|
1599 |
"INSERT INTO subscriber(semail,suname," |
|
d7e10ce…
|
drh
|
1600 |
" sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip,lastContact)" |
|
d7e10ce…
|
drh
|
1601 |
"VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q,now()/86400)" |
|
8a3dc1a…
|
drh
|
1602 |
"RETURNING hex(subscriberCode);", |
|
fc5c7d2…
|
drh
|
1603 |
/* semail */ zEAddr, |
|
fc5c7d2…
|
drh
|
1604 |
/* suname */ suname, |
|
fc5c7d2…
|
drh
|
1605 |
/* sverified */ needCaptcha==0, |
|
fc5c7d2…
|
drh
|
1606 |
/* sdigest */ PB("di"), |
|
fc5c7d2…
|
drh
|
1607 |
/* ssub */ ssub, |
|
fc5c7d2…
|
drh
|
1608 |
/* smip */ g.zIpAddr |
|
fc5c7d2…
|
drh
|
1609 |
); |
|
fc5c7d2…
|
drh
|
1610 |
if( !needCaptcha ){ |
|
fc5c7d2…
|
drh
|
1611 |
/* The new subscription has been added on behalf of a logged-in user. |
|
fc5c7d2…
|
drh
|
1612 |
** No verification is required. Jump immediately to /alerts page. |
|
fc5c7d2…
|
drh
|
1613 |
*/ |
|
15e1529…
|
drh
|
1614 |
if( g.perm.Admin ){ |
|
15e1529…
|
drh
|
1615 |
cgi_redirectf("%R/alerts/%.32s", zCode); |
|
15e1529…
|
drh
|
1616 |
}else{ |
|
15e1529…
|
drh
|
1617 |
cgi_redirectf("%R/alerts"); |
|
15e1529…
|
drh
|
1618 |
} |
|
fc5c7d2…
|
drh
|
1619 |
return; |
|
fc5c7d2…
|
drh
|
1620 |
}else{ |
|
fc5c7d2…
|
drh
|
1621 |
/* We need to send a verification email */ |
|
fc5c7d2…
|
drh
|
1622 |
Blob hdr, body; |
|
fc5c7d2…
|
drh
|
1623 |
AlertSender *pSender = alert_sender_new(0,0); |
|
fc5c7d2…
|
drh
|
1624 |
blob_init(&hdr,0,0); |
|
fc5c7d2…
|
drh
|
1625 |
blob_init(&body,0,0); |
|
fc5c7d2…
|
drh
|
1626 |
blob_appendf(&hdr, "To: <%s>\n", zEAddr); |
|
fc5c7d2…
|
drh
|
1627 |
blob_appendf(&hdr, "Subject: Subscription verification\n"); |
|
fc5c7d2…
|
drh
|
1628 |
alert_append_confirmation_message(&body, zCode); |
|
fc5c7d2…
|
drh
|
1629 |
alert_send(pSender, &hdr, &body, 0); |
|
fc5c7d2…
|
drh
|
1630 |
style_header("Email Alert Verification"); |
|
fc5c7d2…
|
drh
|
1631 |
if( pSender->zErr ){ |
|
fc5c7d2…
|
drh
|
1632 |
@ <h1>Internal Error</h1> |
|
fc5c7d2…
|
drh
|
1633 |
@ <p>The following internal error was encountered while trying |
|
fc5c7d2…
|
drh
|
1634 |
@ to send the confirmation email: |
|
fc5c7d2…
|
drh
|
1635 |
@ <blockquote><pre> |
|
fc5c7d2…
|
drh
|
1636 |
@ %h(pSender->zErr) |
|
fc5c7d2…
|
drh
|
1637 |
@ </pre></blockquote> |
|
fc5c7d2…
|
drh
|
1638 |
}else{ |
|
fc5c7d2…
|
drh
|
1639 |
@ <p>An email has been sent to "%h(zEAddr)". That email contains a |
|
c00e912…
|
drh
|
1640 |
@ hyperlink that you must click to activate your |
|
fc5c7d2…
|
drh
|
1641 |
@ subscription.</p> |
|
fc5c7d2…
|
drh
|
1642 |
} |
|
fc5c7d2…
|
drh
|
1643 |
alert_sender_free(pSender); |
|
112c713…
|
drh
|
1644 |
style_finish_page(); |
|
fc5c7d2…
|
drh
|
1645 |
} |
|
fc5c7d2…
|
drh
|
1646 |
return; |
|
fc5c7d2…
|
drh
|
1647 |
} |
|
fc5c7d2…
|
drh
|
1648 |
style_header("Signup For Email Alerts"); |
|
fc5c7d2…
|
drh
|
1649 |
if( P("submit")==0 ){ |
|
fc5c7d2…
|
drh
|
1650 |
/* If this is the first visit to this page (if this HTTP request did not |
|
fc5c7d2…
|
drh
|
1651 |
** come from a prior Submit of the form) then default all of the |
|
fc5c7d2…
|
drh
|
1652 |
** subscription options to "on" */ |
|
fc5c7d2…
|
drh
|
1653 |
cgi_set_parameter_nocopy("sa","1",1); |
|
fc5c7d2…
|
drh
|
1654 |
if( g.perm.Read ) cgi_set_parameter_nocopy("sc","1",1); |
|
fc5c7d2…
|
drh
|
1655 |
if( g.perm.RdForum ) cgi_set_parameter_nocopy("sf","1",1); |
|
d4361f6…
|
drh
|
1656 |
if( g.perm.RdForum ) cgi_set_parameter_nocopy("sn","1",1); |
|
d4361f6…
|
drh
|
1657 |
if( g.perm.RdForum ) cgi_set_parameter_nocopy("sr","1",1); |
|
fc5c7d2…
|
drh
|
1658 |
if( g.perm.RdTkt ) cgi_set_parameter_nocopy("st","1",1); |
|
d96055c…
|
stephan
|
1659 |
if( g.perm.Admin ) cgi_set_parameter_nocopy("su","1",1); |
|
fc5c7d2…
|
drh
|
1660 |
if( g.perm.RdWiki ) cgi_set_parameter_nocopy("sw","1",1); |
|
fc5c7d2…
|
drh
|
1661 |
} |
|
fc5c7d2…
|
drh
|
1662 |
@ <p>To receive email notifications for changes to this |
|
cdcffc4…
|
stephan
|
1663 |
@ repository, fill out the form below and press the "Submit" button.</p> |
|
fc5c7d2…
|
drh
|
1664 |
form_begin(0, "%R/subscribe"); |
|
fc5c7d2…
|
drh
|
1665 |
@ <table class="subscribe"> |
|
fc5c7d2…
|
drh
|
1666 |
@ <tr> |
|
fc5c7d2…
|
drh
|
1667 |
@ <td class="form_label">Email Address:</td> |
|
fc5c7d2…
|
drh
|
1668 |
@ <td><input type="text" name="e" value="%h(PD("e",""))" size="30"></td> |
|
27769be…
|
drh
|
1669 |
@ <tr> |
|
fc5c7d2…
|
drh
|
1670 |
if( eErr==1 ){ |
|
27769be…
|
drh
|
1671 |
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
|
fc5c7d2…
|
drh
|
1672 |
} |
|
fc5c7d2…
|
drh
|
1673 |
@ </tr> |
|
fc5c7d2…
|
drh
|
1674 |
if( needCaptcha ){ |
|
c00e912…
|
drh
|
1675 |
const char *zInit = ""; |
|
c00e912…
|
drh
|
1676 |
if( P("captchaseed")!=0 && eErr!=2 ){ |
|
c00e912…
|
drh
|
1677 |
uSeed = strtoul(P("captchaseed"),0,10); |
|
c00e912…
|
drh
|
1678 |
zInit = P("captcha"); |
|
c00e912…
|
drh
|
1679 |
}else{ |
|
c00e912…
|
drh
|
1680 |
uSeed = captcha_seed(); |
|
c00e912…
|
drh
|
1681 |
} |
|
8659d84…
|
drh
|
1682 |
zDecoded = captcha_decode(uSeed, 0); |
|
fc5c7d2…
|
drh
|
1683 |
zCaptcha = captcha_render(zDecoded); |
|
fc5c7d2…
|
drh
|
1684 |
@ <tr> |
|
fc5c7d2…
|
drh
|
1685 |
@ <td class="form_label">Security Code:</td> |
|
c00e912…
|
drh
|
1686 |
@ <td><input type="text" name="captcha" value="%h(zInit)" size="30"> |
|
a584491…
|
drh
|
1687 |
captcha_speakit_button(uSeed, "Speak the code"); |
|
fc5c7d2…
|
drh
|
1688 |
@ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td> |
|
27769be…
|
drh
|
1689 |
@ </tr> |
|
fc5c7d2…
|
drh
|
1690 |
if( eErr==2 ){ |
|
27769be…
|
drh
|
1691 |
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
|
fc5c7d2…
|
drh
|
1692 |
} |
|
fc5c7d2…
|
drh
|
1693 |
@ </tr> |
|
fc5c7d2…
|
drh
|
1694 |
} |
|
fc5c7d2…
|
drh
|
1695 |
if( g.perm.Admin ){ |
|
fc5c7d2…
|
drh
|
1696 |
@ <tr> |
|
fc5c7d2…
|
drh
|
1697 |
@ <td class="form_label">User:</td> |
|
fc5c7d2…
|
drh
|
1698 |
@ <td><input type="text" name="suname" value="%h(PD("suname",g.zLogin))" \ |
|
fc5c7d2…
|
drh
|
1699 |
@ size="30"></td> |
|
27769be…
|
drh
|
1700 |
@ </tr> |
|
fc5c7d2…
|
drh
|
1701 |
if( eErr==3 ){ |
|
27769be…
|
drh
|
1702 |
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
|
fc5c7d2…
|
drh
|
1703 |
} |
|
fc5c7d2…
|
drh
|
1704 |
@ </tr> |
|
fc5c7d2…
|
drh
|
1705 |
} |
|
fc5c7d2…
|
drh
|
1706 |
@ <tr> |
|
fc5c7d2…
|
drh
|
1707 |
@ <td class="form_label">Topics:</td> |
|
fc5c7d2…
|
drh
|
1708 |
@ <td><label><input type="checkbox" name="sa" %s(PCK("sa"))> \ |
|
fc5c7d2…
|
drh
|
1709 |
@ Announcements</label><br> |
|
fc5c7d2…
|
drh
|
1710 |
if( g.perm.Read ){ |
|
fc5c7d2…
|
drh
|
1711 |
@ <label><input type="checkbox" name="sc" %s(PCK("sc"))> \ |
|
fc5c7d2…
|
drh
|
1712 |
@ Check-ins</label><br> |
|
fc5c7d2…
|
drh
|
1713 |
} |
|
fc5c7d2…
|
drh
|
1714 |
if( g.perm.RdForum ){ |
|
fc5c7d2…
|
drh
|
1715 |
@ <label><input type="checkbox" name="sf" %s(PCK("sf"))> \ |
|
d4361f6…
|
drh
|
1716 |
@ All Forum Posts</label><br> |
|
d4361f6…
|
drh
|
1717 |
@ <label><input type="checkbox" name="sn" %s(PCK("sn"))> \ |
|
d4361f6…
|
drh
|
1718 |
@ New Forum Threads</label><br> |
|
d4361f6…
|
drh
|
1719 |
@ <label><input type="checkbox" name="sr" %s(PCK("sr"))> \ |
|
d4361f6…
|
drh
|
1720 |
@ Replies To My Forum Posts</label><br> |
|
ea81b30…
|
drh
|
1721 |
@ <label><input type="checkbox" name="sx" %s(PCK("sx"))> \ |
|
d4361f6…
|
drh
|
1722 |
@ Edits To Forum Posts</label><br> |
|
fc5c7d2…
|
drh
|
1723 |
} |
|
fc5c7d2…
|
drh
|
1724 |
if( g.perm.RdTkt ){ |
|
fc5c7d2…
|
drh
|
1725 |
@ <label><input type="checkbox" name="st" %s(PCK("st"))> \ |
|
fc5c7d2…
|
drh
|
1726 |
@ Ticket changes</label><br> |
|
fc5c7d2…
|
drh
|
1727 |
} |
|
fc5c7d2…
|
drh
|
1728 |
if( g.perm.RdWiki ){ |
|
fc5c7d2…
|
drh
|
1729 |
@ <label><input type="checkbox" name="sw" %s(PCK("sw"))> \ |
|
fc5c7d2…
|
drh
|
1730 |
@ Wiki</label><br> |
|
d96055c…
|
stephan
|
1731 |
} |
|
d96055c…
|
stephan
|
1732 |
if( g.perm.Admin ){ |
|
d96055c…
|
stephan
|
1733 |
@ <label><input type="checkbox" name="su" %s(PCK("su"))> \ |
|
36f72c0…
|
stephan
|
1734 |
@ User permission changes</label> |
|
bca95cb…
|
drh
|
1735 |
} |
|
fc5c7d2…
|
drh
|
1736 |
di = PB("di"); |
|
fc5c7d2…
|
drh
|
1737 |
@ </td></tr> |
|
fc5c7d2…
|
drh
|
1738 |
@ <tr> |
|
fc5c7d2…
|
drh
|
1739 |
@ <td class="form_label">Delivery:</td> |
|
fc5c7d2…
|
drh
|
1740 |
@ <td><select size="1" name="di"> |
|
fc5c7d2…
|
drh
|
1741 |
@ <option value="0" %s(di?"":"selected")>Individual Emails</option> |
|
fc5c7d2…
|
drh
|
1742 |
@ <option value="1" %s(di?"selected":"")>Daily Digest</option> |
|
fc5c7d2…
|
drh
|
1743 |
@ </select></td> |
|
fc5c7d2…
|
drh
|
1744 |
@ </tr> |
|
fc5c7d2…
|
drh
|
1745 |
if( g.perm.Admin ){ |
|
fc5c7d2…
|
drh
|
1746 |
@ <tr> |
|
fc5c7d2…
|
drh
|
1747 |
@ <td class="form_label">Admin Options:</td><td> |
|
fc5c7d2…
|
drh
|
1748 |
@ <label><input type="checkbox" name="vi" %s(PCK("vi"))> \ |
|
fc5c7d2…
|
drh
|
1749 |
@ Verified</label><br> |
|
fc5c7d2…
|
drh
|
1750 |
@ <label><input type="checkbox" name="dnc" %s(PCK("dnc"))> \ |
|
fc5c7d2…
|
drh
|
1751 |
@ Do not call</label></td></tr> |
|
fc5c7d2…
|
drh
|
1752 |
} |
|
fc5c7d2…
|
drh
|
1753 |
@ <tr> |
|
fc5c7d2…
|
drh
|
1754 |
@ <td></td> |
|
fc5c7d2…
|
drh
|
1755 |
if( needCaptcha && !alert_enabled() ){ |
|
fc5c7d2…
|
drh
|
1756 |
@ <td><input type="submit" name="submit" value="Submit" disabled> |
|
fc5c7d2…
|
drh
|
1757 |
@ (Email current disabled)</td> |
|
fc5c7d2…
|
drh
|
1758 |
}else{ |
|
fc5c7d2…
|
drh
|
1759 |
@ <td><input type="submit" name="submit" value="Submit"></td> |
|
fc5c7d2…
|
drh
|
1760 |
} |
|
fc5c7d2…
|
drh
|
1761 |
@ </tr> |
|
fc5c7d2…
|
drh
|
1762 |
@ </table> |
|
fc5c7d2…
|
drh
|
1763 |
if( needCaptcha ){ |
|
75c89de…
|
drh
|
1764 |
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha"> |
|
fc5c7d2…
|
drh
|
1765 |
@ %h(zCaptcha) |
|
fc5c7d2…
|
drh
|
1766 |
@ </pre> |
|
dcf4410…
|
drh
|
1767 |
@ Enter the 8 characters above in the "Security Code" box<br/> |
|
fc5c7d2…
|
drh
|
1768 |
@ </td></tr></table></div> |
|
fc5c7d2…
|
drh
|
1769 |
} |
|
fc5c7d2…
|
drh
|
1770 |
@ </form> |
|
fc5c7d2…
|
drh
|
1771 |
fossil_free(zErr); |
|
112c713…
|
drh
|
1772 |
style_finish_page(); |
|
fc5c7d2…
|
drh
|
1773 |
} |
|
fc5c7d2…
|
drh
|
1774 |
|
|
fc5c7d2…
|
drh
|
1775 |
/* |
|
fc5c7d2…
|
drh
|
1776 |
** Either shutdown or completely delete a subscription entry given |
|
fc5c7d2…
|
drh
|
1777 |
** by the hex value zName. Then paint a webpage that explains that |
|
fc5c7d2…
|
drh
|
1778 |
** the entry has been removed. |
|
fc5c7d2…
|
drh
|
1779 |
*/ |
|
bca95cb…
|
drh
|
1780 |
static void alert_unsubscribe(int sid, int bTotal){ |
|
b6b5a7d…
|
drh
|
1781 |
const char *zEmail = 0; |
|
b6b5a7d…
|
drh
|
1782 |
const char *zLogin = 0; |
|
b6b5a7d…
|
drh
|
1783 |
int uid = 0; |
|
b6b5a7d…
|
drh
|
1784 |
Stmt q; |
|
b6b5a7d…
|
drh
|
1785 |
db_prepare(&q, "SELECT semail, suname FROM subscriber" |
|
b6b5a7d…
|
drh
|
1786 |
" WHERE subscriberId=%d", sid); |
|
b6b5a7d…
|
drh
|
1787 |
if( db_step(&q)==SQLITE_ROW ){ |
|
b6b5a7d…
|
drh
|
1788 |
zEmail = db_column_text(&q, 0); |
|
b6b5a7d…
|
drh
|
1789 |
zLogin = db_column_text(&q, 1); |
|
b6b5a7d…
|
drh
|
1790 |
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zLogin); |
|
b6b5a7d…
|
drh
|
1791 |
} |
|
112c713…
|
drh
|
1792 |
style_set_current_feature("alerts"); |
|
fc5c7d2…
|
drh
|
1793 |
if( zEmail==0 ){ |
|
fc5c7d2…
|
drh
|
1794 |
style_header("Unsubscribe Fail"); |
|
fc5c7d2…
|
drh
|
1795 |
@ <p>Unable to locate a subscriber with the requested key</p> |
|
fc5c7d2…
|
drh
|
1796 |
}else{ |
|
bca95cb…
|
drh
|
1797 |
db_unprotect(PROTECT_READONLY); |
|
bca95cb…
|
drh
|
1798 |
if( bTotal ){ |
|
bca95cb…
|
drh
|
1799 |
/* Completely delete the subscriber */ |
|
bca95cb…
|
drh
|
1800 |
db_multi_exec( |
|
bca95cb…
|
drh
|
1801 |
"DELETE FROM subscriber WHERE subscriberId=%d", sid |
|
bca95cb…
|
drh
|
1802 |
); |
|
bca95cb…
|
drh
|
1803 |
}else{ |
|
bca95cb…
|
drh
|
1804 |
/* Keep the subscriber, but turn off all notifications */ |
|
bca95cb…
|
drh
|
1805 |
db_multi_exec( |
|
bca95cb…
|
drh
|
1806 |
"UPDATE subscriber SET ssub='k', mtime=now() WHERE subscriberId=%d", |
|
bca95cb…
|
drh
|
1807 |
sid |
|
bca95cb…
|
drh
|
1808 |
); |
|
bca95cb…
|
drh
|
1809 |
} |
|
bca95cb…
|
drh
|
1810 |
db_protect_pop(); |
|
fc5c7d2…
|
drh
|
1811 |
style_header("Unsubscribed"); |
|
4a3909a…
|
drh
|
1812 |
@ <p>The "%h(zEmail)" email address has been unsubscribed from all |
|
4a3909a…
|
drh
|
1813 |
@ notifications. All subscription records for "%h(zEmail)" have |
|
4a3909a…
|
drh
|
1814 |
@ been purged. No further emails will be sent to "%h(zEmail)".</p> |
|
b6b5a7d…
|
drh
|
1815 |
if( uid && g.perm.Admin ){ |
|
b6b5a7d…
|
drh
|
1816 |
@ <p>You may also want to |
|
b6b5a7d…
|
drh
|
1817 |
@ <a href="%R/setup_uedit?id=%d(uid)">edit or delete |
|
b6b5a7d…
|
drh
|
1818 |
@ the corresponding user "%h(zLogin)"</a></p> |
|
b6b5a7d…
|
drh
|
1819 |
} |
|
fc5c7d2…
|
drh
|
1820 |
} |
|
b6b5a7d…
|
drh
|
1821 |
db_finalize(&q); |
|
112c713…
|
drh
|
1822 |
style_finish_page(); |
|
fc5c7d2…
|
drh
|
1823 |
return; |
|
fc5c7d2…
|
drh
|
1824 |
} |
|
fc5c7d2…
|
drh
|
1825 |
|
|
fc5c7d2…
|
drh
|
1826 |
/* |
|
fc5c7d2…
|
drh
|
1827 |
** WEBPAGE: alerts |
|
fc5c7d2…
|
drh
|
1828 |
** |
|
fc5c7d2…
|
drh
|
1829 |
** Edit email alert and notification settings. |
|
fc5c7d2…
|
drh
|
1830 |
** |
|
15e1529…
|
drh
|
1831 |
** The subscriber is identified in several ways: |
|
15e1529…
|
drh
|
1832 |
** |
|
cd06177…
|
drh
|
1833 |
** * The name= query parameter contains the complete subscriberCode. |
|
15e1529…
|
drh
|
1834 |
** This only happens when the user receives a verification |
|
15e1529…
|
drh
|
1835 |
** email and clicks on the link in the email. When a |
|
15e1529…
|
drh
|
1836 |
** compilete subscriberCode is seen on the name= query parameter, |
|
15e1529…
|
drh
|
1837 |
** that constitutes verification of the email address. |
|
15e1529…
|
drh
|
1838 |
** |
|
cd06177…
|
drh
|
1839 |
** * The sid= query parameter contains an integer subscriberId. |
|
15e1529…
|
drh
|
1840 |
** This only works for the administrator. It allows the |
|
15e1529…
|
drh
|
1841 |
** administrator to edit any subscription. |
|
e0576ea…
|
stephan
|
1842 |
** |
|
cd06177…
|
drh
|
1843 |
** * The user is logged into an account other than "nobody" or |
|
fc5c7d2…
|
drh
|
1844 |
** "anonymous". In that case the notification settings |
|
fc5c7d2…
|
drh
|
1845 |
** associated with that account can be edited without needing |
|
fc5c7d2…
|
drh
|
1846 |
** to know the subscriber code. |
|
15e1529…
|
drh
|
1847 |
** |
|
cd06177…
|
drh
|
1848 |
** * The name= query parameter contains a 32-digit prefix of |
|
15e1529…
|
drh
|
1849 |
** subscriber code. (Subscriber codes are normally 64 hex digits |
|
15e1529…
|
drh
|
1850 |
** in length.) This uniquely identifies the subscriber without |
|
15e1529…
|
drh
|
1851 |
** revealing the complete subscriber code, and hence without |
|
15e1529…
|
drh
|
1852 |
** verifying the email address. |
|
54a6f09…
|
drh
|
1853 |
*/ |
|
54a6f09…
|
drh
|
1854 |
void alert_page(void){ |
|
15e1529…
|
drh
|
1855 |
const char *zName = 0; /* Value of the name= query parameter */ |
|
15e1529…
|
drh
|
1856 |
Stmt q; /* For querying the database */ |
|
d96055c…
|
stephan
|
1857 |
int sa, sc, sf, st, su, sw, sx; /* Types of notifications requested */ |
|
d4361f6…
|
drh
|
1858 |
int sn, sr; |
|
15e1529…
|
drh
|
1859 |
int sdigest = 0, sdonotcall = 0, sverified = 0; /* Other fields */ |
|
15e1529…
|
drh
|
1860 |
int isLogin; /* True if logged in as an individual */ |
|
15e1529…
|
drh
|
1861 |
const char *ssub = 0; /* Subscription flags */ |
|
15e1529…
|
drh
|
1862 |
const char *semail = 0; /* Email address */ |
|
15e1529…
|
drh
|
1863 |
const char *smip; /* */ |
|
15e1529…
|
drh
|
1864 |
const char *suname = 0; /* Corresponding user.login value */ |
|
15e1529…
|
drh
|
1865 |
const char *mtime; /* */ |
|
15e1529…
|
drh
|
1866 |
const char *sctime; /* Time subscription created */ |
|
15e1529…
|
drh
|
1867 |
int eErr = 0; /* Type of error */ |
|
15e1529…
|
drh
|
1868 |
char *zErr = 0; /* Error message text */ |
|
15e1529…
|
drh
|
1869 |
int sid = 0; /* Subscriber ID */ |
|
15e1529…
|
drh
|
1870 |
int nName; /* Length of zName in bytes */ |
|
15e1529…
|
drh
|
1871 |
char *zHalfCode; /* prefix of subscriberCode */ |
|
d7e10ce…
|
drh
|
1872 |
int keepAlive = 0; /* True to update the last contact time */ |
|
15e1529…
|
drh
|
1873 |
|
|
c00e912…
|
drh
|
1874 |
db_begin_transaction(); |
|
c00e912…
|
drh
|
1875 |
if( alert_webpages_disabled() ){ |
|
c00e912…
|
drh
|
1876 |
db_commit_transaction(); |
|
15e1529…
|
drh
|
1877 |
return; |
|
15e1529…
|
drh
|
1878 |
} |
|
c00e912…
|
drh
|
1879 |
login_check_credentials(); |
|
54a6f09…
|
drh
|
1880 |
isLogin = login_is_individual(); |
|
15e1529…
|
drh
|
1881 |
zName = P("name"); |
|
15e1529…
|
drh
|
1882 |
nName = zName ? (int)strlen(zName) : 0; |
|
15e1529…
|
drh
|
1883 |
if( g.perm.Admin && P("sid")!=0 ){ |
|
15e1529…
|
drh
|
1884 |
sid = atoi(P("sid")); |
|
54a6f09…
|
drh
|
1885 |
} |
|
15e1529…
|
drh
|
1886 |
if( sid==0 && nName>=32 ){ |
|
15e1529…
|
drh
|
1887 |
sid = db_int(0, |
|
15e1529…
|
drh
|
1888 |
"SELECT CASE WHEN hex(subscriberCode) LIKE (%Q||'%%')" |
|
15e1529…
|
drh
|
1889 |
" THEN subscriberId ELSE 0 END" |
|
15e1529…
|
drh
|
1890 |
" FROM subscriber WHERE subscriberCode>=hextoblob(%Q)" |
|
15e1529…
|
drh
|
1891 |
" LIMIT 1", zName, zName); |
|
d7e10ce…
|
drh
|
1892 |
if( sid ) keepAlive = 1; |
|
15e1529…
|
drh
|
1893 |
} |
|
a27c908…
|
drh
|
1894 |
if( sid==0 && isLogin && g.perm.EmailAlert ){ |
|
15e1529…
|
drh
|
1895 |
sid = db_int(0, "SELECT subscriberId FROM subscriber" |
|
15e1529…
|
drh
|
1896 |
" WHERE suname=%Q", g.zLogin); |
|
15e1529…
|
drh
|
1897 |
} |
|
15e1529…
|
drh
|
1898 |
if( sid==0 ){ |
|
c00e912…
|
drh
|
1899 |
db_commit_transaction(); |
|
54a6f09…
|
drh
|
1900 |
cgi_redirect("subscribe"); |
|
c00e912…
|
drh
|
1901 |
/*NOTREACHED*/ |
|
54a6f09…
|
drh
|
1902 |
} |
|
54a6f09…
|
drh
|
1903 |
alert_submenu_common(); |
|
920ace1…
|
drh
|
1904 |
if( P("submit")!=0 && cgi_csrf_safe(2) ){ |
|
32a8d11…
|
drh
|
1905 |
char newSsub[10]; |
|
54a6f09…
|
drh
|
1906 |
int nsub = 0; |
|
54a6f09…
|
drh
|
1907 |
Blob update; |
|
32a8d11…
|
drh
|
1908 |
|
|
32a8d11…
|
drh
|
1909 |
sdonotcall = PB("sdonotcall"); |
|
32a8d11…
|
drh
|
1910 |
sdigest = PB("sdigest"); |
|
32a8d11…
|
drh
|
1911 |
semail = P("semail"); |
|
32a8d11…
|
drh
|
1912 |
if( PB("sa") ) newSsub[nsub++] = 'a'; |
|
32a8d11…
|
drh
|
1913 |
if( g.perm.Read && PB("sc") ) newSsub[nsub++] = 'c'; |
|
32a8d11…
|
drh
|
1914 |
if( g.perm.RdForum && PB("sf") ) newSsub[nsub++] = 'f'; |
|
d4361f6…
|
drh
|
1915 |
if( g.perm.RdForum && PB("sn") ) newSsub[nsub++] = 'n'; |
|
d4361f6…
|
drh
|
1916 |
if( g.perm.RdForum && PB("sr") ) newSsub[nsub++] = 'r'; |
|
32a8d11…
|
drh
|
1917 |
if( g.perm.RdTkt && PB("st") ) newSsub[nsub++] = 't'; |
|
d96055c…
|
stephan
|
1918 |
if( g.perm.Admin && PB("su") ) newSsub[nsub++] = 'u'; |
|
32a8d11…
|
drh
|
1919 |
if( g.perm.RdWiki && PB("sw") ) newSsub[nsub++] = 'w'; |
|
e5653a4…
|
drh
|
1920 |
if( g.perm.RdForum && PB("sx") ) newSsub[nsub++] = 'x'; |
|
32a8d11…
|
drh
|
1921 |
newSsub[nsub] = 0; |
|
32a8d11…
|
drh
|
1922 |
ssub = newSsub; |
|
54a6f09…
|
drh
|
1923 |
blob_init(&update, "UPDATE subscriber SET", -1); |
|
54a6f09…
|
drh
|
1924 |
blob_append_sql(&update, |
|
54a6f09…
|
drh
|
1925 |
" sdonotcall=%d," |
|
54a6f09…
|
drh
|
1926 |
" sdigest=%d," |
|
54a6f09…
|
drh
|
1927 |
" ssub=%Q," |
|
d7e10ce…
|
drh
|
1928 |
" mtime=now()," |
|
d7e10ce…
|
drh
|
1929 |
" lastContact=now()/86400," |
|
54a6f09…
|
drh
|
1930 |
" smip=%Q", |
|
54a6f09…
|
drh
|
1931 |
sdonotcall, |
|
54a6f09…
|
drh
|
1932 |
sdigest, |
|
54a6f09…
|
drh
|
1933 |
ssub, |
|
54a6f09…
|
drh
|
1934 |
g.zIpAddr |
|
54a6f09…
|
drh
|
1935 |
); |
|
54a6f09…
|
drh
|
1936 |
if( g.perm.Admin ){ |
|
32a8d11…
|
drh
|
1937 |
suname = PT("suname"); |
|
32a8d11…
|
drh
|
1938 |
sverified = PB("sverified"); |
|
54a6f09…
|
drh
|
1939 |
if( suname && suname[0]==0 ) suname = 0; |
|
54a6f09…
|
drh
|
1940 |
blob_append_sql(&update, |
|
54a6f09…
|
drh
|
1941 |
", suname=%Q," |
|
54a6f09…
|
drh
|
1942 |
" sverified=%d", |
|
54a6f09…
|
drh
|
1943 |
suname, |
|
54a6f09…
|
drh
|
1944 |
sverified |
|
54a6f09…
|
drh
|
1945 |
); |
|
54a6f09…
|
drh
|
1946 |
} |
|
32a8d11…
|
drh
|
1947 |
if( isLogin ){ |
|
32a8d11…
|
drh
|
1948 |
if( semail==0 || email_address_is_valid(semail,0)==0 ){ |
|
32a8d11…
|
drh
|
1949 |
eErr = 8; |
|
32a8d11…
|
drh
|
1950 |
} |
|
32a8d11…
|
drh
|
1951 |
blob_append_sql(&update, ", semail=%Q", semail); |
|
54a6f09…
|
drh
|
1952 |
} |
|
15e1529…
|
drh
|
1953 |
blob_append_sql(&update," WHERE subscriberId=%d", sid); |
|
32a8d11…
|
drh
|
1954 |
if( eErr==0 ){ |
|
32a8d11…
|
drh
|
1955 |
db_exec_sql(blob_str(&update)); |
|
32a8d11…
|
drh
|
1956 |
ssub = 0; |
|
32a8d11…
|
drh
|
1957 |
} |
|
54a6f09…
|
drh
|
1958 |
blob_reset(&update); |
|
d7e10ce…
|
drh
|
1959 |
}else if( keepAlive ){ |
|
db16262…
|
drh
|
1960 |
db_unprotect(PROTECT_READONLY); |
|
d7e10ce…
|
drh
|
1961 |
db_multi_exec( |
|
d7e10ce…
|
drh
|
1962 |
"UPDATE subscriber SET lastContact=now()/86400" |
|
d7e10ce…
|
drh
|
1963 |
" WHERE subscriberId=%d", sid |
|
d7e10ce…
|
drh
|
1964 |
); |
|
db16262…
|
drh
|
1965 |
db_protect_pop(); |
|
54a6f09…
|
drh
|
1966 |
} |
|
920ace1…
|
drh
|
1967 |
if( P("delete")!=0 && cgi_csrf_safe(2) ){ |
|
fc5c7d2…
|
drh
|
1968 |
if( !PB("dodelete") ){ |
|
fc5c7d2…
|
drh
|
1969 |
eErr = 9; |
|
fc5c7d2…
|
drh
|
1970 |
zErr = mprintf("Select this checkbox and press \"Unsubscribe\" again to" |
|
fc5c7d2…
|
drh
|
1971 |
" unsubscribe"); |
|
fc5c7d2…
|
drh
|
1972 |
}else{ |
|
bca95cb…
|
drh
|
1973 |
alert_unsubscribe(sid, 1); |
|
c00e912…
|
drh
|
1974 |
db_commit_transaction(); |
|
e0576ea…
|
stephan
|
1975 |
return; |
|
fc5c7d2…
|
drh
|
1976 |
} |
|
fc5c7d2…
|
drh
|
1977 |
} |
|
112c713…
|
drh
|
1978 |
style_set_current_feature("alerts"); |
|
32a8d11…
|
drh
|
1979 |
style_header("Update Subscription"); |
|
fc5c7d2…
|
drh
|
1980 |
db_prepare(&q, |
|
fc5c7d2…
|
drh
|
1981 |
"SELECT" |
|
fc5c7d2…
|
drh
|
1982 |
" semail," /* 0 */ |
|
fc5c7d2…
|
drh
|
1983 |
" sverified," /* 1 */ |
|
fc5c7d2…
|
drh
|
1984 |
" sdonotcall," /* 2 */ |
|
fc5c7d2…
|
drh
|
1985 |
" sdigest," /* 3 */ |
|
fc5c7d2…
|
drh
|
1986 |
" ssub," /* 4 */ |
|
fc5c7d2…
|
drh
|
1987 |
" smip," /* 5 */ |
|
fc5c7d2…
|
drh
|
1988 |
" suname," /* 6 */ |
|
fc5c7d2…
|
drh
|
1989 |
" datetime(mtime,'unixepoch')," /* 7 */ |
|
15e1529…
|
drh
|
1990 |
" datetime(sctime,'unixepoch')," /* 8 */ |
|
34d45c5…
|
drh
|
1991 |
" hex(subscriberCode)," /* 9 */ |
|
34d45c5…
|
drh
|
1992 |
" date(coalesce(lastContact*86400,mtime),'unixepoch')," /* 10 */ |
|
34d45c5…
|
drh
|
1993 |
" now()/86400 - coalesce(lastContact,mtime/86400)" /* 11 */ |
|
15e1529…
|
drh
|
1994 |
" FROM subscriber WHERE subscriberId=%d", sid); |
|
fc5c7d2…
|
drh
|
1995 |
if( db_step(&q)!=SQLITE_ROW ){ |
|
fc5c7d2…
|
drh
|
1996 |
db_finalize(&q); |
|
c00e912…
|
drh
|
1997 |
db_commit_transaction(); |
|
fc5c7d2…
|
drh
|
1998 |
cgi_redirect("subscribe"); |
|
c00e912…
|
drh
|
1999 |
/*NOTREACHED*/ |
|
32a8d11…
|
drh
|
2000 |
} |
|
32a8d11…
|
drh
|
2001 |
if( ssub==0 ){ |
|
32a8d11…
|
drh
|
2002 |
semail = db_column_text(&q, 0); |
|
32a8d11…
|
drh
|
2003 |
sdonotcall = db_column_int(&q, 2); |
|
32a8d11…
|
drh
|
2004 |
sdigest = db_column_int(&q, 3); |
|
32a8d11…
|
drh
|
2005 |
ssub = db_column_text(&q, 4); |
|
32a8d11…
|
drh
|
2006 |
} |
|
32a8d11…
|
drh
|
2007 |
if( suname==0 ){ |
|
32a8d11…
|
drh
|
2008 |
suname = db_column_text(&q, 6); |
|
32a8d11…
|
drh
|
2009 |
sverified = db_column_int(&q, 1); |
|
32a8d11…
|
drh
|
2010 |
} |
|
fc5c7d2…
|
drh
|
2011 |
sa = strchr(ssub,'a')!=0; |
|
fc5c7d2…
|
drh
|
2012 |
sc = strchr(ssub,'c')!=0; |
|
fc5c7d2…
|
drh
|
2013 |
sf = strchr(ssub,'f')!=0; |
|
d4361f6…
|
drh
|
2014 |
sn = strchr(ssub,'n')!=0; |
|
d4361f6…
|
drh
|
2015 |
sr = strchr(ssub,'r')!=0; |
|
fc5c7d2…
|
drh
|
2016 |
st = strchr(ssub,'t')!=0; |
|
d96055c…
|
stephan
|
2017 |
su = strchr(ssub,'u')!=0; |
|
fc5c7d2…
|
drh
|
2018 |
sw = strchr(ssub,'w')!=0; |
|
e5653a4…
|
drh
|
2019 |
sx = strchr(ssub,'x')!=0; |
|
fc5c7d2…
|
drh
|
2020 |
smip = db_column_text(&q, 5); |
|
fc5c7d2…
|
drh
|
2021 |
mtime = db_column_text(&q, 7); |
|
fc5c7d2…
|
drh
|
2022 |
sctime = db_column_text(&q, 8); |
|
fc5c7d2…
|
drh
|
2023 |
if( !g.perm.Admin && !sverified ){ |
|
15e1529…
|
drh
|
2024 |
if( nName==64 ){ |
|
f33976f…
|
drh
|
2025 |
db_unprotect(PROTECT_READONLY); |
|
c00e912…
|
drh
|
2026 |
db_multi_exec( |
|
c00e912…
|
drh
|
2027 |
"UPDATE subscriber SET sverified=1" |
|
c00e912…
|
drh
|
2028 |
" WHERE subscriberCode=hextoblob(%Q)", |
|
15e1529…
|
drh
|
2029 |
zName); |
|
f33976f…
|
drh
|
2030 |
db_protect_pop(); |
|
c00e912…
|
drh
|
2031 |
if( db_get_boolean("selfreg-verify",0) ){ |
|
c00e912…
|
drh
|
2032 |
char *zNewCap = db_get("default-perms","u"); |
|
f741baa…
|
drh
|
2033 |
db_unprotect(PROTECT_USER); |
|
c00e912…
|
drh
|
2034 |
db_multi_exec( |
|
c00e912…
|
drh
|
2035 |
"UPDATE user" |
|
c00e912…
|
drh
|
2036 |
" SET cap=%Q" |
|
c00e912…
|
drh
|
2037 |
" WHERE cap='7' AND login=(" |
|
c00e912…
|
drh
|
2038 |
" SELECT suname FROM subscriber" |
|
c00e912…
|
drh
|
2039 |
" WHERE subscriberCode=hextoblob(%Q))", |
|
c00e912…
|
drh
|
2040 |
zNewCap, zName |
|
c00e912…
|
drh
|
2041 |
); |
|
f741baa…
|
drh
|
2042 |
db_protect_pop(); |
|
c00e912…
|
drh
|
2043 |
login_set_capabilities(zNewCap, 0); |
|
c00e912…
|
drh
|
2044 |
} |
|
15e1529…
|
drh
|
2045 |
@ <h1>Your email alert subscription has been verified!</h1> |
|
15e1529…
|
drh
|
2046 |
@ <p>Use the form below to update your subscription information.</p> |
|
15e1529…
|
drh
|
2047 |
@ <p>Hint: Bookmark this page so that you can more easily update |
|
15e1529…
|
drh
|
2048 |
@ your subscription information in the future</p> |
|
15e1529…
|
drh
|
2049 |
}else{ |
|
15e1529…
|
drh
|
2050 |
@ <h2>Your email address is unverified</h2> |
|
15e1529…
|
drh
|
2051 |
@ <p>You should have received an email message containing a link |
|
15e1529…
|
drh
|
2052 |
@ that you must visit to verify your account. No email notifications |
|
15e1529…
|
drh
|
2053 |
@ will be sent until your email address has been verified.</p> |
|
15e1529…
|
drh
|
2054 |
} |
|
fc5c7d2…
|
drh
|
2055 |
}else{ |
|
fc5c7d2…
|
drh
|
2056 |
@ <p>Make changes to the email subscription shown below and |
|
fc5c7d2…
|
drh
|
2057 |
@ press "Submit".</p> |
|
fc5c7d2…
|
drh
|
2058 |
} |
|
fc5c7d2…
|
drh
|
2059 |
form_begin(0, "%R/alerts"); |
|
15e1529…
|
drh
|
2060 |
zHalfCode = db_text("x","SELECT hex(substr(subscriberCode,1,16))" |
|
15e1529…
|
drh
|
2061 |
" FROM subscriber WHERE subscriberId=%d", sid); |
|
15e1529…
|
drh
|
2062 |
@ <input type="hidden" name="name" value="%h(zHalfCode)"> |
|
fc5c7d2…
|
drh
|
2063 |
@ <table class="subscribe"> |
|
fc5c7d2…
|
drh
|
2064 |
@ <tr> |
|
fc5c7d2…
|
drh
|
2065 |
@ <td class="form_label">Email Address:</td> |
|
54a6f09…
|
drh
|
2066 |
if( isLogin ){ |
|
bb05299…
|
drh
|
2067 |
@ <td><input type="text" name="semail" value="%h(semail)" size="30">\ |
|
32a8d11…
|
drh
|
2068 |
if( eErr==8 ){ |
|
32a8d11…
|
drh
|
2069 |
@ <span class='loginError'>← not a valid email address!</span> |
|
32a8d11…
|
drh
|
2070 |
}else if( g.perm.Admin ){ |
|
32a8d11…
|
drh
|
2071 |
@ <a href="%R/announce?to=%t(semail)">\ |
|
32a8d11…
|
drh
|
2072 |
@ (Send a message to %h(semail))</a>\ |
|
bb05299…
|
drh
|
2073 |
} |
|
bb05299…
|
drh
|
2074 |
@ </td> |
|
54a6f09…
|
drh
|
2075 |
}else{ |
|
54a6f09…
|
drh
|
2076 |
@ <td>%h(semail)</td> |
|
54a6f09…
|
drh
|
2077 |
} |
|
fc5c7d2…
|
drh
|
2078 |
@ </tr> |
|
fc5c7d2…
|
drh
|
2079 |
if( g.perm.Admin ){ |
|
54a6f09…
|
drh
|
2080 |
int uid; |
|
fc5c7d2…
|
drh
|
2081 |
@ <tr> |
|
fc5c7d2…
|
drh
|
2082 |
@ <td class='form_label'>Created:</td> |
|
fc5c7d2…
|
drh
|
2083 |
@ <td>%h(sctime)</td> |
|
fc5c7d2…
|
drh
|
2084 |
@ </tr> |
|
fc5c7d2…
|
drh
|
2085 |
@ <tr> |
|
fc5c7d2…
|
drh
|
2086 |
@ <td class='form_label'>Last Modified:</td> |
|
fc5c7d2…
|
drh
|
2087 |
@ <td>%h(mtime)</td> |
|
fc5c7d2…
|
drh
|
2088 |
@ </tr> |
|
fc5c7d2…
|
drh
|
2089 |
@ <tr> |
|
fc5c7d2…
|
drh
|
2090 |
@ <td class='form_label'>IP Address:</td> |
|
fc5c7d2…
|
drh
|
2091 |
@ <td>%h(smip)</td> |
|
fc5c7d2…
|
drh
|
2092 |
@ </tr> |
|
fc5c7d2…
|
drh
|
2093 |
@ <tr> |
|
15e1529…
|
drh
|
2094 |
@ <td class='form_label'>Subscriber Code:</td> |
|
15e1529…
|
drh
|
2095 |
@ <td>%h(db_column_text(&q,9))</td> |
|
15e1529…
|
drh
|
2096 |
@ <tr> |
|
34d45c5…
|
drh
|
2097 |
@ <tr> |
|
34d45c5…
|
drh
|
2098 |
@ <td class='form_label'>Last Contact:</td> |
|
34d45c5…
|
drh
|
2099 |
@ <td>%h(db_column_text(&q,10)) ← \ |
|
34d45c5…
|
drh
|
2100 |
@ %,d(db_column_int(&q,11)) days ago</td> |
|
34d45c5…
|
drh
|
2101 |
@ </tr> |
|
fc5c7d2…
|
drh
|
2102 |
@ <td class="form_label">User:</td> |
|
fc5c7d2…
|
drh
|
2103 |
@ <td><input type="text" name="suname" value="%h(suname?suname:"")" \ |
|
54a6f09…
|
drh
|
2104 |
@ size="30">\ |
|
54a6f09…
|
drh
|
2105 |
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", suname); |
|
54a6f09…
|
drh
|
2106 |
if( uid ){ |
|
54a6f09…
|
drh
|
2107 |
@ <a href='%R/setup_uedit?id=%d(uid)'>\ |
|
54a6f09…
|
drh
|
2108 |
@ (login info for %h(suname))</a>\ |
|
54a6f09…
|
drh
|
2109 |
} |
|
fc5c7d2…
|
drh
|
2110 |
@ </tr> |
|
fc5c7d2…
|
drh
|
2111 |
} |
|
fc5c7d2…
|
drh
|
2112 |
@ <tr> |
|
fc5c7d2…
|
drh
|
2113 |
@ <td class="form_label">Topics:</td> |
|
fc5c7d2…
|
drh
|
2114 |
@ <td><label><input type="checkbox" name="sa" %s(sa?"checked":"")>\ |
|
fc5c7d2…
|
drh
|
2115 |
@ Announcements</label><br> |
|
fc5c7d2…
|
drh
|
2116 |
if( g.perm.Read ){ |
|
fc5c7d2…
|
drh
|
2117 |
@ <label><input type="checkbox" name="sc" %s(sc?"checked":"")>\ |
|
fc5c7d2…
|
drh
|
2118 |
@ Check-ins</label><br> |
|
fc5c7d2…
|
drh
|
2119 |
} |
|
fc5c7d2…
|
drh
|
2120 |
if( g.perm.RdForum ){ |
|
fc5c7d2…
|
drh
|
2121 |
@ <label><input type="checkbox" name="sf" %s(sf?"checked":"")>\ |
|
d4361f6…
|
drh
|
2122 |
@ All Forum Posts</label><br> |
|
d4361f6…
|
drh
|
2123 |
@ <label><input type="checkbox" name="sn" %s(sn?"checked":"")>\ |
|
d4361f6…
|
drh
|
2124 |
@ New Forum Threads</label><br> |
|
d4361f6…
|
drh
|
2125 |
@ <label><input type="checkbox" name="sr" %s(sr?"checked":"")>\ |
|
d4361f6…
|
drh
|
2126 |
@ Replies To My Posts</label><br> |
|
e5653a4…
|
drh
|
2127 |
@ <label><input type="checkbox" name="sx" %s(sx?"checked":"")>\ |
|
d4361f6…
|
drh
|
2128 |
@ Edits To Forum Posts</label><br> |
|
fc5c7d2…
|
drh
|
2129 |
} |
|
fc5c7d2…
|
drh
|
2130 |
if( g.perm.RdTkt ){ |
|
fc5c7d2…
|
drh
|
2131 |
@ <label><input type="checkbox" name="st" %s(st?"checked":"")>\ |
|
fc5c7d2…
|
drh
|
2132 |
@ Ticket changes</label><br> |
|
fc5c7d2…
|
drh
|
2133 |
} |
|
fc5c7d2…
|
drh
|
2134 |
if( g.perm.RdWiki ){ |
|
fc5c7d2…
|
drh
|
2135 |
@ <label><input type="checkbox" name="sw" %s(sw?"checked":"")>\ |
|
d96055c…
|
stephan
|
2136 |
@ Wiki</label><br> |
|
d96055c…
|
stephan
|
2137 |
} |
|
d96055c…
|
stephan
|
2138 |
if( g.perm.Admin ){ |
|
d96055c…
|
stephan
|
2139 |
/* Corner-case bug: if an admin assigns 'u' to a non-admin, that |
|
d96055c…
|
stephan
|
2140 |
** subscription will get removed if the user later edits their |
|
d96055c…
|
stephan
|
2141 |
** subscriptions, as non-admins are not permitted to add that |
|
d96055c…
|
stephan
|
2142 |
** subscription. */ |
|
d96055c…
|
stephan
|
2143 |
@ <label><input type="checkbox" name="su" %s(su?"checked":"")>\ |
|
36f72c0…
|
stephan
|
2144 |
@ User permission changes</label> |
|
fc5c7d2…
|
drh
|
2145 |
} |
|
fc5c7d2…
|
drh
|
2146 |
@ </td></tr> |
|
bca95cb…
|
drh
|
2147 |
if( strchr(ssub,'k')!=0 ){ |
|
bca95cb…
|
drh
|
2148 |
@ <tr><td></td><td> ↑ |
|
bca95cb…
|
drh
|
2149 |
@ Note: User did a one-click unsubscribe</td></tr> |
|
bca95cb…
|
drh
|
2150 |
} |
|
fc5c7d2…
|
drh
|
2151 |
@ <tr> |
|
fc5c7d2…
|
drh
|
2152 |
@ <td class="form_label">Delivery:</td> |
|
fc5c7d2…
|
drh
|
2153 |
@ <td><select size="1" name="sdigest"> |
|
fc5c7d2…
|
drh
|
2154 |
@ <option value="0" %s(sdigest?"":"selected")>Individual Emails</option> |
|
fc5c7d2…
|
drh
|
2155 |
@ <option value="1" %s(sdigest?"selected":"")>Daily Digest</option> |
|
fc5c7d2…
|
drh
|
2156 |
@ </select></td> |
|
fc5c7d2…
|
drh
|
2157 |
@ </tr> |
|
fc5c7d2…
|
drh
|
2158 |
if( g.perm.Admin ){ |
|
fc5c7d2…
|
drh
|
2159 |
@ <tr> |
|
fc5c7d2…
|
drh
|
2160 |
@ <td class="form_label">Admin Options:</td><td> |
|
fc5c7d2…
|
drh
|
2161 |
@ <label><input type="checkbox" name="sdonotcall" \ |
|
54a6f09…
|
drh
|
2162 |
@ %s(sdonotcall?"checked":"")> Do not disturb</label><br> |
|
fc5c7d2…
|
drh
|
2163 |
@ <label><input type="checkbox" name="sverified" \ |
|
fc5c7d2…
|
drh
|
2164 |
@ %s(sverified?"checked":"")>\ |
|
fc5c7d2…
|
drh
|
2165 |
@ Verified</label></td></tr> |
|
fc5c7d2…
|
drh
|
2166 |
} |
|
fc5c7d2…
|
drh
|
2167 |
if( eErr==9 ){ |
|
fc5c7d2…
|
drh
|
2168 |
@ <tr> |
|
fc5c7d2…
|
drh
|
2169 |
@ <td class="form_label">Verify:</td><td> |
|
fc5c7d2…
|
drh
|
2170 |
@ <label><input type="checkbox" name="dodelete"> |
|
fc5c7d2…
|
drh
|
2171 |
@ Unsubscribe</label> |
|
fc5c7d2…
|
drh
|
2172 |
@ <span class="loginError">← %h(zErr)</span> |
|
fc5c7d2…
|
drh
|
2173 |
@ </td></tr> |
|
fc5c7d2…
|
drh
|
2174 |
} |
|
fc5c7d2…
|
drh
|
2175 |
@ <tr> |
|
fc5c7d2…
|
drh
|
2176 |
@ <td></td> |
|
fc5c7d2…
|
drh
|
2177 |
@ <td><input type="submit" name="submit" value="Submit"> |
|
fc5c7d2…
|
drh
|
2178 |
@ <input type="submit" name="delete" value="Unsubscribe"> |
|
fc5c7d2…
|
drh
|
2179 |
@ </tr> |
|
fc5c7d2…
|
drh
|
2180 |
@ </table> |
|
fc5c7d2…
|
drh
|
2181 |
@ </form> |
|
fc5c7d2…
|
drh
|
2182 |
fossil_free(zErr); |
|
fc5c7d2…
|
drh
|
2183 |
db_finalize(&q); |
|
112c713…
|
drh
|
2184 |
style_finish_page(); |
|
c00e912…
|
drh
|
2185 |
db_commit_transaction(); |
|
c00e912…
|
drh
|
2186 |
return; |
|
c00e912…
|
drh
|
2187 |
} |
|
c00e912…
|
drh
|
2188 |
|
|
7b8be20…
|
drh
|
2189 |
/* |
|
7b8be20…
|
drh
|
2190 |
** WEBPAGE: renew |
|
7b8be20…
|
drh
|
2191 |
** |
|
34d45c5…
|
drh
|
2192 |
** Users visit this page to update the last-contact date on their |
|
34d45c5…
|
drh
|
2193 |
** subscription. The last-contact date is the day that the subscriber |
|
34d45c5…
|
drh
|
2194 |
** last interacted with the repository. If the name= query parameter |
|
34d45c5…
|
drh
|
2195 |
** (or POST parameter) contains a valid subscriber code, then the last-contact |
|
34d45c5…
|
drh
|
2196 |
** subscription associated with that subscriber code is updated to be the |
|
34d45c5…
|
drh
|
2197 |
** current date. |
|
7b8be20…
|
drh
|
2198 |
*/ |
|
7b8be20…
|
drh
|
2199 |
void renewal_page(void){ |
|
7b8be20…
|
drh
|
2200 |
const char *zName = P("name"); |
|
7b8be20…
|
drh
|
2201 |
int iInterval = db_get_int("email-renew-interval", 0); |
|
7b8be20…
|
drh
|
2202 |
Stmt s; |
|
7b8be20…
|
drh
|
2203 |
int rc; |
|
7b8be20…
|
drh
|
2204 |
|
|
7b8be20…
|
drh
|
2205 |
style_header("Subscription Renewal"); |
|
7b8be20…
|
drh
|
2206 |
if( zName==0 || strlen(zName)<4 ){ |
|
7b8be20…
|
drh
|
2207 |
@ <p>No subscription specified</p> |
|
7b8be20…
|
drh
|
2208 |
style_finish_page(); |
|
7b8be20…
|
drh
|
2209 |
return; |
|
7b8be20…
|
drh
|
2210 |
} |
|
7b8be20…
|
drh
|
2211 |
|
|
7b8be20…
|
drh
|
2212 |
if( !db_table_has_column("repository","subscriber","lastContact") |
|
7b8be20…
|
drh
|
2213 |
|| iInterval<1 |
|
7b8be20…
|
drh
|
2214 |
){ |
|
7b8be20…
|
drh
|
2215 |
@ <p>This repository does not expire email notification subscriptions. |
|
7b8be20…
|
drh
|
2216 |
@ No renewals are necessary.</p> |
|
7b8be20…
|
drh
|
2217 |
style_finish_page(); |
|
7b8be20…
|
drh
|
2218 |
return; |
|
7b8be20…
|
drh
|
2219 |
} |
|
7b8be20…
|
drh
|
2220 |
|
|
f33976f…
|
drh
|
2221 |
db_unprotect(PROTECT_READONLY); |
|
7b8be20…
|
drh
|
2222 |
db_prepare(&s, |
|
7b8be20…
|
drh
|
2223 |
"UPDATE subscriber" |
|
7b8be20…
|
drh
|
2224 |
" SET lastContact=now()/86400" |
|
7b8be20…
|
drh
|
2225 |
" WHERE subscriberCode=hextoblob(%Q)" |
|
7b8be20…
|
drh
|
2226 |
" RETURNING semail, date('now','+%d days');", |
|
7b8be20…
|
drh
|
2227 |
zName, iInterval+1 |
|
7b8be20…
|
drh
|
2228 |
); |
|
7b8be20…
|
drh
|
2229 |
rc = db_step(&s); |
|
7b8be20…
|
drh
|
2230 |
if( rc==SQLITE_ROW ){ |
|
7b8be20…
|
drh
|
2231 |
@ <p>The email notification subscription for %h(db_column_text(&s,0)) |
|
7b8be20…
|
drh
|
2232 |
@ has been extended until %h(db_column_text(&s,1)) UTC. |
|
7b8be20…
|
drh
|
2233 |
}else{ |
|
7b8be20…
|
drh
|
2234 |
@ <p>No such subscriber-id: %h(zName)</p> |
|
7b8be20…
|
drh
|
2235 |
} |
|
7b8be20…
|
drh
|
2236 |
db_finalize(&s); |
|
f33976f…
|
drh
|
2237 |
db_protect_pop(); |
|
7b8be20…
|
drh
|
2238 |
style_finish_page(); |
|
7b8be20…
|
drh
|
2239 |
} |
|
7b8be20…
|
drh
|
2240 |
|
|
7b8be20…
|
drh
|
2241 |
|
|
fc5c7d2…
|
drh
|
2242 |
/* This is the message that gets sent to describe how to change |
|
fc5c7d2…
|
drh
|
2243 |
** or modify a subscription |
|
fc5c7d2…
|
drh
|
2244 |
*/ |
|
e0576ea…
|
stephan
|
2245 |
static const char zUnsubMsg[] = |
|
fc5c7d2…
|
drh
|
2246 |
@ To changes your subscription settings at %s visit this link: |
|
fc5c7d2…
|
drh
|
2247 |
@ |
|
fc5c7d2…
|
drh
|
2248 |
@ %s/alerts/%s |
|
fc5c7d2…
|
drh
|
2249 |
@ |
|
fc5c7d2…
|
drh
|
2250 |
@ To completely unsubscribe from %s, visit the following link: |
|
fc5c7d2…
|
drh
|
2251 |
@ |
|
fc5c7d2…
|
drh
|
2252 |
@ %s/unsubscribe/%s |
|
fc5c7d2…
|
drh
|
2253 |
; |
|
fc5c7d2…
|
drh
|
2254 |
|
|
fc5c7d2…
|
drh
|
2255 |
/* |
|
fc5c7d2…
|
drh
|
2256 |
** WEBPAGE: unsubscribe |
|
bca95cb…
|
drh
|
2257 |
** WEBPAGE: oneclickunsub |
|
fc5c7d2…
|
drh
|
2258 |
** |
|
fc5c7d2…
|
drh
|
2259 |
** Users visit this page to be delisted from email alerts. |
|
fc5c7d2…
|
drh
|
2260 |
** |
|
fc5c7d2…
|
drh
|
2261 |
** If a valid subscriber code is supplied in the name= query parameter, |
|
fc5c7d2…
|
drh
|
2262 |
** then that subscriber is delisted. |
|
fc5c7d2…
|
drh
|
2263 |
** |
|
33d3bf3…
|
km
|
2264 |
** Otherwise, if the users are logged in, then they are redirected |
|
fc5c7d2…
|
drh
|
2265 |
** to the /alerts page where they have an unsubscribe button. |
|
fc5c7d2…
|
drh
|
2266 |
** |
|
fc5c7d2…
|
drh
|
2267 |
** Non-logged-in users with no name= query parameter are invited to enter |
|
fc5c7d2…
|
drh
|
2268 |
** an email address to which will be sent the unsubscribe link that |
|
fc5c7d2…
|
drh
|
2269 |
** contains the correct subscriber code. |
|
bca95cb…
|
drh
|
2270 |
** |
|
bca95cb…
|
drh
|
2271 |
** The /unsubscribe page requires comfirmation. The /oneclickunsub |
|
bca95cb…
|
drh
|
2272 |
** page unsubscribes immediately without any need to confirm. |
|
fc5c7d2…
|
drh
|
2273 |
*/ |
|
fc5c7d2…
|
drh
|
2274 |
void unsubscribe_page(void){ |
|
fc5c7d2…
|
drh
|
2275 |
const char *zName = P("name"); |
|
fc5c7d2…
|
drh
|
2276 |
char *zErr = 0; |
|
fc5c7d2…
|
drh
|
2277 |
int eErr = 0; |
|
11d1233…
|
drh
|
2278 |
unsigned int uSeed = 0; |
|
fc5c7d2…
|
drh
|
2279 |
const char *zDecoded; |
|
fc5c7d2…
|
drh
|
2280 |
char *zCaptcha = 0; |
|
fc5c7d2…
|
drh
|
2281 |
int dx; |
|
fc5c7d2…
|
drh
|
2282 |
int bSubmit; |
|
fc5c7d2…
|
drh
|
2283 |
const char *zEAddr; |
|
fc5c7d2…
|
drh
|
2284 |
char *zCode = 0; |
|
15e1529…
|
drh
|
2285 |
int sid = 0; |
|
15e1529…
|
drh
|
2286 |
|
|
f045c5d…
|
drh
|
2287 |
if( zName==0 ) zName = P("scode"); |
|
f045c5d…
|
drh
|
2288 |
|
|
f045c5d…
|
drh
|
2289 |
/* If a valid subscriber code is supplied, then either present the user |
|
e0576ea…
|
stephan
|
2290 |
** with a confirmation, or if already confirmed, unsubscribe immediately. |
|
fc5c7d2…
|
drh
|
2291 |
*/ |
|
e0576ea…
|
stephan
|
2292 |
if( zName |
|
15e1529…
|
drh
|
2293 |
&& (sid = db_int(0, "SELECT subscriberId FROM subscriber" |
|
15e1529…
|
drh
|
2294 |
" WHERE subscriberCode=hextoblob(%Q)", zName))!=0 |
|
fc5c7d2…
|
drh
|
2295 |
){ |
|
f045c5d…
|
drh
|
2296 |
char *zUnsubName = mprintf("confirm%04x", sid); |
|
f045c5d…
|
drh
|
2297 |
if( P(zUnsubName)!=0 ){ |
|
bca95cb…
|
drh
|
2298 |
alert_unsubscribe(sid, 1); |
|
bca95cb…
|
drh
|
2299 |
}else if( sqlite3_strglob("*oneclick*",g.zPath)==0 ){ |
|
bca95cb…
|
drh
|
2300 |
alert_unsubscribe(sid, 0); |
|
f045c5d…
|
drh
|
2301 |
}else if( P("manage")!=0 ){ |
|
f045c5d…
|
drh
|
2302 |
cgi_redirectf("%R/alerts/%s", zName); |
|
f045c5d…
|
drh
|
2303 |
}else{ |
|
37f929e…
|
drh
|
2304 |
style_header("Unsubscribe"); |
|
f045c5d…
|
drh
|
2305 |
form_begin(0, "%R/unsubscribe"); |
|
f045c5d…
|
drh
|
2306 |
@ <input type="hidden" name="scode" value="%h(zName)"> |
|
f045c5d…
|
drh
|
2307 |
@ <table border="0" cellpadding="10" width="100%%"> |
|
f045c5d…
|
drh
|
2308 |
@ <tr><td align="right"> |
|
f045c5d…
|
drh
|
2309 |
@ <input type="submit" name="%h(zUnsubName)" value="Unsubscribe"> |
|
f045c5d…
|
drh
|
2310 |
@ </td><td><big><b>←</b></big></td> |
|
f045c5d…
|
drh
|
2311 |
@ <td>Cancel your subscription to %h(g.zBaseURL) notifications |
|
f045c5d…
|
drh
|
2312 |
@ </td><tr> |
|
f045c5d…
|
drh
|
2313 |
@ <tr><td align="right"> |
|
f045c5d…
|
drh
|
2314 |
@ <input type="submit" name="manage" \ |
|
f045c5d…
|
drh
|
2315 |
@ value="Manage Subscription Settings"> |
|
f045c5d…
|
drh
|
2316 |
@ </td><td><big><b>←</b></big></td> |
|
37f929e…
|
drh
|
2317 |
@ <td>Make other changes to your subscription preferences |
|
f045c5d…
|
drh
|
2318 |
@ </td><tr> |
|
f045c5d…
|
drh
|
2319 |
@ </table> |
|
f045c5d…
|
drh
|
2320 |
@ </form> |
|
f045c5d…
|
drh
|
2321 |
style_finish_page(); |
|
f045c5d…
|
drh
|
2322 |
} |
|
fc5c7d2…
|
drh
|
2323 |
return; |
|
fc5c7d2…
|
drh
|
2324 |
} |
|
fc5c7d2…
|
drh
|
2325 |
|
|
fc5c7d2…
|
drh
|
2326 |
/* Logged in users are redirected to the /alerts page */ |
|
fc5c7d2…
|
drh
|
2327 |
login_check_credentials(); |
|
fc5c7d2…
|
drh
|
2328 |
if( login_is_individual() ){ |
|
fc5c7d2…
|
drh
|
2329 |
cgi_redirectf("%R/alerts"); |
|
fc5c7d2…
|
drh
|
2330 |
return; |
|
fc5c7d2…
|
drh
|
2331 |
} |
|
fc5c7d2…
|
drh
|
2332 |
|
|
112c713…
|
drh
|
2333 |
style_set_current_feature("alerts"); |
|
112c713…
|
drh
|
2334 |
|
|
fc5c7d2…
|
drh
|
2335 |
zEAddr = PD("e",""); |
|
fc5c7d2…
|
drh
|
2336 |
dx = atoi(PD("dx","0")); |
|
920ace1…
|
drh
|
2337 |
bSubmit = P("submit")!=0 && P("e")!=0 && cgi_csrf_safe(2); |
|
fc5c7d2…
|
drh
|
2338 |
if( bSubmit ){ |
|
fc5c7d2…
|
drh
|
2339 |
if( !captcha_is_correct(1) ){ |
|
fc5c7d2…
|
drh
|
2340 |
eErr = 2; |
|
fc5c7d2…
|
drh
|
2341 |
zErr = mprintf("enter the security code shown below"); |
|
fc5c7d2…
|
drh
|
2342 |
bSubmit = 0; |
|
fc5c7d2…
|
drh
|
2343 |
} |
|
fc5c7d2…
|
drh
|
2344 |
} |
|
fc5c7d2…
|
drh
|
2345 |
if( bSubmit ){ |
|
fc5c7d2…
|
drh
|
2346 |
zCode = db_text(0,"SELECT hex(subscriberCode) FROM subscriber" |
|
fc5c7d2…
|
drh
|
2347 |
" WHERE semail=%Q", zEAddr); |
|
fc5c7d2…
|
drh
|
2348 |
if( zCode==0 ){ |
|
fc5c7d2…
|
drh
|
2349 |
eErr = 1; |
|
fc5c7d2…
|
drh
|
2350 |
zErr = mprintf("not a valid email address"); |
|
fc5c7d2…
|
drh
|
2351 |
bSubmit = 0; |
|
fc5c7d2…
|
drh
|
2352 |
} |
|
fc5c7d2…
|
drh
|
2353 |
} |
|
fc5c7d2…
|
drh
|
2354 |
if( bSubmit ){ |
|
fc5c7d2…
|
drh
|
2355 |
/* If we get this far, it means that a valid unsubscribe request has |
|
fc5c7d2…
|
drh
|
2356 |
** been submitted. Send the appropriate email. */ |
|
fc5c7d2…
|
drh
|
2357 |
Blob hdr, body; |
|
fc5c7d2…
|
drh
|
2358 |
AlertSender *pSender = alert_sender_new(0,0); |
|
fc5c7d2…
|
drh
|
2359 |
blob_init(&hdr,0,0); |
|
fc5c7d2…
|
drh
|
2360 |
blob_init(&body,0,0); |
|
fc5c7d2…
|
drh
|
2361 |
blob_appendf(&hdr, "To: <%s>\r\n", zEAddr); |
|
fc5c7d2…
|
drh
|
2362 |
blob_appendf(&hdr, "Subject: Unsubscribe Instructions\r\n"); |
|
fc5c7d2…
|
drh
|
2363 |
blob_appendf(&body, zUnsubMsg/*works-like:"%s%s%s%s%s%s"*/, |
|
fc5c7d2…
|
drh
|
2364 |
g.zBaseURL, g.zBaseURL, zCode, g.zBaseURL, g.zBaseURL, zCode); |
|
fc5c7d2…
|
drh
|
2365 |
alert_send(pSender, &hdr, &body, 0); |
|
fc5c7d2…
|
drh
|
2366 |
style_header("Unsubscribe Instructions Sent"); |
|
fc5c7d2…
|
drh
|
2367 |
if( pSender->zErr ){ |
|
fc5c7d2…
|
drh
|
2368 |
@ <h1>Internal Error</h1> |
|
fc5c7d2…
|
drh
|
2369 |
@ <p>The following error was encountered while trying to send an |
|
fc5c7d2…
|
drh
|
2370 |
@ email to %h(zEAddr): |
|
fc5c7d2…
|
drh
|
2371 |
@ <blockquote><pre> |
|
fc5c7d2…
|
drh
|
2372 |
@ %h(pSender->zErr) |
|
fc5c7d2…
|
drh
|
2373 |
@ </pre></blockquote> |
|
fc5c7d2…
|
drh
|
2374 |
}else{ |
|
fc5c7d2…
|
drh
|
2375 |
@ <p>An email has been sent to "%h(zEAddr)" that explains how to |
|
fc5c7d2…
|
drh
|
2376 |
@ unsubscribe and/or modify your subscription settings</p> |
|
fc5c7d2…
|
drh
|
2377 |
} |
|
fc5c7d2…
|
drh
|
2378 |
alert_sender_free(pSender); |
|
112c713…
|
drh
|
2379 |
style_finish_page(); |
|
fc5c7d2…
|
drh
|
2380 |
return; |
|
e0576ea…
|
stephan
|
2381 |
} |
|
fc5c7d2…
|
drh
|
2382 |
|
|
fc5c7d2…
|
drh
|
2383 |
/* Non-logged-in users have to enter an email address to which is |
|
fc5c7d2…
|
drh
|
2384 |
** sent a message containing the unsubscribe link. |
|
fc5c7d2…
|
drh
|
2385 |
*/ |
|
fc5c7d2…
|
drh
|
2386 |
style_header("Unsubscribe Request"); |
|
fc5c7d2…
|
drh
|
2387 |
@ <p>Fill out the form below to request an email message that will |
|
fc5c7d2…
|
drh
|
2388 |
@ explain how to unsubscribe and/or change your subscription settings.</p> |
|
fc5c7d2…
|
drh
|
2389 |
@ |
|
fc5c7d2…
|
drh
|
2390 |
form_begin(0, "%R/unsubscribe"); |
|
fc5c7d2…
|
drh
|
2391 |
@ <table class="subscribe"> |
|
fc5c7d2…
|
drh
|
2392 |
@ <tr> |
|
fc5c7d2…
|
drh
|
2393 |
@ <td class="form_label">Email Address:</td> |
|
fc5c7d2…
|
drh
|
2394 |
@ <td><input type="text" name="e" value="%h(zEAddr)" size="30"></td> |
|
fc5c7d2…
|
drh
|
2395 |
if( eErr==1 ){ |
|
fc5c7d2…
|
drh
|
2396 |
@ <td><span class="loginError">← %h(zErr)</span></td> |
|
fc5c7d2…
|
drh
|
2397 |
} |
|
fc5c7d2…
|
drh
|
2398 |
@ </tr> |
|
fc5c7d2…
|
drh
|
2399 |
uSeed = captcha_seed(); |
|
8659d84…
|
drh
|
2400 |
zDecoded = captcha_decode(uSeed, 0); |
|
fc5c7d2…
|
drh
|
2401 |
zCaptcha = captcha_render(zDecoded); |
|
fc5c7d2…
|
drh
|
2402 |
@ <tr> |
|
fc5c7d2…
|
drh
|
2403 |
@ <td class="form_label">Security Code:</td> |
|
fc5c7d2…
|
drh
|
2404 |
@ <td><input type="text" name="captcha" value="" size="30"> |
|
a584491…
|
drh
|
2405 |
captcha_speakit_button(uSeed, "Speak the code"); |
|
fc5c7d2…
|
drh
|
2406 |
@ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td> |
|
fc5c7d2…
|
drh
|
2407 |
if( eErr==2 ){ |
|
fc5c7d2…
|
drh
|
2408 |
@ <td><span class="loginError">← %h(zErr)</span></td> |
|
fc5c7d2…
|
drh
|
2409 |
} |
|
fc5c7d2…
|
drh
|
2410 |
@ </tr> |
|
fc5c7d2…
|
drh
|
2411 |
@ <tr> |
|
fc5c7d2…
|
drh
|
2412 |
@ <td class="form_label">Options:</td> |
|
fc5c7d2…
|
drh
|
2413 |
@ <td><label><input type="radio" name="dx" value="0" %s(dx?"":"checked")>\ |
|
fc5c7d2…
|
drh
|
2414 |
@ Modify subscription</label><br> |
|
fc5c7d2…
|
drh
|
2415 |
@ <label><input type="radio" name="dx" value="1" %s(dx?"checked":"")>\ |
|
fc5c7d2…
|
drh
|
2416 |
@ Completely unsubscribe</label><br> |
|
fc5c7d2…
|
drh
|
2417 |
@ <tr> |
|
fc5c7d2…
|
drh
|
2418 |
@ <td></td> |
|
fc5c7d2…
|
drh
|
2419 |
@ <td><input type="submit" name="submit" value="Submit"></td> |
|
fc5c7d2…
|
drh
|
2420 |
@ </tr> |
|
fc5c7d2…
|
drh
|
2421 |
@ </table> |
|
75c89de…
|
drh
|
2422 |
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha"> |
|
fc5c7d2…
|
drh
|
2423 |
@ %h(zCaptcha) |
|
fc5c7d2…
|
drh
|
2424 |
@ </pre> |
|
dcf4410…
|
drh
|
2425 |
@ Enter the 8 characters above in the "Security Code" box<br/> |
|
fc5c7d2…
|
drh
|
2426 |
@ </td></tr></table></div> |
|
fc5c7d2…
|
drh
|
2427 |
@ </form> |
|
fc5c7d2…
|
drh
|
2428 |
fossil_free(zErr); |
|
112c713…
|
drh
|
2429 |
style_finish_page(); |
|
fc5c7d2…
|
drh
|
2430 |
} |
|
fc5c7d2…
|
drh
|
2431 |
|
|
fc5c7d2…
|
drh
|
2432 |
/* |
|
fc5c7d2…
|
drh
|
2433 |
** WEBPAGE: subscribers |
|
fc5c7d2…
|
drh
|
2434 |
** |
|
fc5c7d2…
|
drh
|
2435 |
** This page, accessible to administrators only, |
|
8c40c38…
|
drh
|
2436 |
** shows a list of subscriber email addresses. |
|
fc5c7d2…
|
drh
|
2437 |
** Clicking on an email takes one to the /alerts page |
|
fc5c7d2…
|
drh
|
2438 |
** for that email where the delivery settings can be |
|
fc5c7d2…
|
drh
|
2439 |
** modified. |
|
fc5c7d2…
|
drh
|
2440 |
*/ |
|
fc5c7d2…
|
drh
|
2441 |
void subscriber_list_page(void){ |
|
fc5c7d2…
|
drh
|
2442 |
Blob sql; |
|
fc5c7d2…
|
drh
|
2443 |
Stmt q; |
|
fc5c7d2…
|
drh
|
2444 |
sqlite3_int64 iNow; |
|
8c40c38…
|
drh
|
2445 |
int nTotal; |
|
8c40c38…
|
drh
|
2446 |
int nPending; |
|
8c40c38…
|
drh
|
2447 |
int nDel = 0; |
|
34d45c5…
|
drh
|
2448 |
int iCutoff = db_get_int("email-renew-cutoff",0); |
|
34d45c5…
|
drh
|
2449 |
int iWarning = db_get_int("email-renew-warning",0); |
|
34d45c5…
|
drh
|
2450 |
char zCutoffClr[8]; |
|
34d45c5…
|
drh
|
2451 |
char zWarnClr[8]; |
|
fc5c7d2…
|
drh
|
2452 |
if( alert_webpages_disabled() ) return; |
|
fc5c7d2…
|
drh
|
2453 |
login_check_credentials(); |
|
fc5c7d2…
|
drh
|
2454 |
if( !g.perm.Admin ){ |
|
fc5c7d2…
|
drh
|
2455 |
login_needed(0); |
|
fc5c7d2…
|
drh
|
2456 |
return; |
|
fc5c7d2…
|
drh
|
2457 |
} |
|
fc5c7d2…
|
drh
|
2458 |
alert_submenu_common(); |
|
54a6f09…
|
drh
|
2459 |
style_submenu_element("Users","setup_ulist"); |
|
112c713…
|
drh
|
2460 |
style_set_current_feature("alerts"); |
|
fc5c7d2…
|
drh
|
2461 |
style_header("Subscriber List"); |
|
8c40c38…
|
drh
|
2462 |
nTotal = db_int(0, "SELECT count(*) FROM subscriber"); |
|
8c40c38…
|
drh
|
2463 |
nPending = db_int(0, "SELECT count(*) FROM subscriber WHERE NOT sverified"); |
|
8c40c38…
|
drh
|
2464 |
if( nPending>0 && P("purge") && cgi_csrf_safe(0) ){ |
|
8c40c38…
|
drh
|
2465 |
int nNewPending; |
|
8c40c38…
|
drh
|
2466 |
db_multi_exec( |
|
8c40c38…
|
drh
|
2467 |
"DELETE FROM subscriber" |
|
d7e10ce…
|
drh
|
2468 |
" WHERE NOT sverified AND mtime<now()-86400" |
|
8c40c38…
|
drh
|
2469 |
); |
|
8c40c38…
|
drh
|
2470 |
nNewPending = db_int(0, "SELECT count(*) FROM subscriber" |
|
8c40c38…
|
drh
|
2471 |
" WHERE NOT sverified"); |
|
8c40c38…
|
drh
|
2472 |
nDel = nPending - nNewPending; |
|
8c40c38…
|
drh
|
2473 |
nPending = nNewPending; |
|
8ccab68…
|
drh
|
2474 |
nTotal -= nDel; |
|
8c40c38…
|
drh
|
2475 |
} |
|
8c40c38…
|
drh
|
2476 |
if( nPending>0 ){ |
|
8c40c38…
|
drh
|
2477 |
@ <h1>%,d(nTotal) Subscribers, %,d(nPending) Pending</h1> |
|
8c40c38…
|
drh
|
2478 |
if( nDel==0 && 0<db_int(0,"SELECT count(*) FROM subscriber" |
|
d7e10ce…
|
drh
|
2479 |
" WHERE NOT sverified AND mtime<now()-86400") |
|
8c40c38…
|
drh
|
2480 |
){ |
|
8c40c38…
|
drh
|
2481 |
style_submenu_element("Purge Pending","subscribers?purge"); |
|
8c40c38…
|
drh
|
2482 |
} |
|
8c40c38…
|
drh
|
2483 |
}else{ |
|
8c40c38…
|
drh
|
2484 |
@ <h1>%,d(nTotal) Subscribers</h1> |
|
8c40c38…
|
drh
|
2485 |
} |
|
8c40c38…
|
drh
|
2486 |
if( nDel>0 ){ |
|
8c40c38…
|
drh
|
2487 |
@ <p>*** %d(nDel) pending subscriptions deleted ***</p> |
|
8c40c38…
|
drh
|
2488 |
} |
|
fc5c7d2…
|
drh
|
2489 |
blob_init(&sql, 0, 0); |
|
fc5c7d2…
|
drh
|
2490 |
blob_append_sql(&sql, |
|
15e1529…
|
drh
|
2491 |
"SELECT subscriberId," /* 0 */ |
|
fc5c7d2…
|
drh
|
2492 |
" semail," /* 1 */ |
|
fc5c7d2…
|
drh
|
2493 |
" ssub," /* 2 */ |
|
fc5c7d2…
|
drh
|
2494 |
" suname," /* 3 */ |
|
fc5c7d2…
|
drh
|
2495 |
" sverified," /* 4 */ |
|
fc5c7d2…
|
drh
|
2496 |
" sdigest," /* 5 */ |
|
fc5c7d2…
|
drh
|
2497 |
" mtime," /* 6 */ |
|
54a6f09…
|
drh
|
2498 |
" date(sctime,'unixepoch')," /* 7 */ |
|
d7e10ce…
|
drh
|
2499 |
" (SELECT uid FROM user WHERE login=subscriber.suname)," /* 8 */ |
|
d7e10ce…
|
drh
|
2500 |
" coalesce(lastContact,mtime/86400)" /* 9 */ |
|
fc5c7d2…
|
drh
|
2501 |
" FROM subscriber" |
|
fc5c7d2…
|
drh
|
2502 |
); |
|
fc5c7d2…
|
drh
|
2503 |
if( P("only")!=0 ){ |
|
fc5c7d2…
|
drh
|
2504 |
blob_append_sql(&sql, " WHERE ssub LIKE '%%%q%%'", P("only")); |
|
fc5c7d2…
|
drh
|
2505 |
style_submenu_element("Show All","%R/subscribers"); |
|
fc5c7d2…
|
drh
|
2506 |
} |
|
fc5c7d2…
|
drh
|
2507 |
blob_append_sql(&sql," ORDER BY mtime DESC"); |
|
fc5c7d2…
|
drh
|
2508 |
db_prepare_blob(&q, &sql); |
|
fc5c7d2…
|
drh
|
2509 |
iNow = time(0); |
|
34d45c5…
|
drh
|
2510 |
memcpy(zCutoffClr, hash_color("A"), sizeof(zCutoffClr)); |
|
34d45c5…
|
drh
|
2511 |
memcpy(zWarnClr, hash_color("HIJ"), sizeof(zWarnClr)); |
|
fc5c7d2…
|
drh
|
2512 |
@ <table border='1' class='sortable' \ |
|
d7e10ce…
|
drh
|
2513 |
@ data-init-sort='6' data-column-types='tttttKKt'> |
|
fc5c7d2…
|
drh
|
2514 |
@ <thead> |
|
fc5c7d2…
|
drh
|
2515 |
@ <tr> |
|
fc5c7d2…
|
drh
|
2516 |
@ <th>Email |
|
fc5c7d2…
|
drh
|
2517 |
@ <th>Events |
|
fc5c7d2…
|
drh
|
2518 |
@ <th>Digest-Only? |
|
fc5c7d2…
|
drh
|
2519 |
@ <th>User |
|
fc5c7d2…
|
drh
|
2520 |
@ <th>Verified? |
|
fc5c7d2…
|
drh
|
2521 |
@ <th>Last change |
|
d7e10ce…
|
drh
|
2522 |
@ <th>Last contact |
|
fc5c7d2…
|
drh
|
2523 |
@ <th>Created |
|
fc5c7d2…
|
drh
|
2524 |
@ </tr> |
|
fc5c7d2…
|
drh
|
2525 |
@ </thead><tbody> |
|
fc5c7d2…
|
drh
|
2526 |
while( db_step(&q)==SQLITE_ROW ){ |
|
fc5c7d2…
|
drh
|
2527 |
sqlite3_int64 iMtime = db_column_int64(&q, 6); |
|
fc5c7d2…
|
drh
|
2528 |
double rAge = (iNow - iMtime)/86400.0; |
|
54a6f09…
|
drh
|
2529 |
int uid = db_column_int(&q, 8); |
|
54a6f09…
|
drh
|
2530 |
const char *zUname = db_column_text(&q, 3); |
|
d7e10ce…
|
drh
|
2531 |
sqlite3_int64 iContact = db_column_int64(&q, 9); |
|
57f16ce…
|
drh
|
2532 |
double rContact = (iNow/86400.0) - iContact; |
|
fc5c7d2…
|
drh
|
2533 |
@ <tr> |
|
15e1529…
|
drh
|
2534 |
@ <td><a href='%R/alerts?sid=%d(db_column_int(&q,0))'>\ |
|
fc5c7d2…
|
drh
|
2535 |
@ %h(db_column_text(&q,1))</a></td> |
|
fc5c7d2…
|
drh
|
2536 |
@ <td>%h(db_column_text(&q,2))</td> |
|
fc5c7d2…
|
drh
|
2537 |
@ <td>%s(db_column_int(&q,5)?"digest":"")</td> |
|
54a6f09…
|
drh
|
2538 |
if( uid ){ |
|
54a6f09…
|
drh
|
2539 |
@ <td><a href='%R/setup_uedit?id=%d(uid)'>%h(zUname)</a> |
|
54a6f09…
|
drh
|
2540 |
}else{ |
|
54a6f09…
|
drh
|
2541 |
@ <td>%h(zUname)</td> |
|
54a6f09…
|
drh
|
2542 |
} |
|
fc5c7d2…
|
drh
|
2543 |
@ <td>%s(db_column_int(&q,4)?"yes":"pending")</td> |
|
fc5c7d2…
|
drh
|
2544 |
@ <td data-sortkey='%010llx(iMtime)'>%z(human_readable_age(rAge))</td> |
|
34d45c5…
|
drh
|
2545 |
@ <td data-sortkey='%010llx(iContact)'>\ |
|
34d45c5…
|
drh
|
2546 |
if( iContact>iWarning ){ |
|
34d45c5…
|
drh
|
2547 |
@ <span>\ |
|
34d45c5…
|
drh
|
2548 |
}else if( iContact>iCutoff ){ |
|
34d45c5…
|
drh
|
2549 |
@ <span style='background-color:%s(zWarnClr);'>\ |
|
34d45c5…
|
drh
|
2550 |
}else{ |
|
34d45c5…
|
drh
|
2551 |
@ <span style='background-color:%s(zCutoffClr);'>\ |
|
34d45c5…
|
drh
|
2552 |
} |
|
34d45c5…
|
drh
|
2553 |
@ %z(human_readable_age(rContact))</td> |
|
fc5c7d2…
|
drh
|
2554 |
@ <td>%h(db_column_text(&q,7))</td> |
|
fc5c7d2…
|
drh
|
2555 |
@ </tr> |
|
fc5c7d2…
|
drh
|
2556 |
} |
|
fc5c7d2…
|
drh
|
2557 |
@ </tbody></table> |
|
fc5c7d2…
|
drh
|
2558 |
db_finalize(&q); |
|
fc5c7d2…
|
drh
|
2559 |
style_table_sorter(); |
|
112c713…
|
drh
|
2560 |
style_finish_page(); |
|
fc5c7d2…
|
drh
|
2561 |
} |
|
fc5c7d2…
|
drh
|
2562 |
|
|
fc5c7d2…
|
drh
|
2563 |
#if LOCAL_INTERFACE |
|
fc5c7d2…
|
drh
|
2564 |
/* |
|
fc5c7d2…
|
drh
|
2565 |
** A single event that might appear in an alert is recorded as an |
|
fc5c7d2…
|
drh
|
2566 |
** instance of the following object. |
|
e5653a4…
|
drh
|
2567 |
** |
|
e5653a4…
|
drh
|
2568 |
** type values: |
|
e5653a4…
|
drh
|
2569 |
** |
|
e5653a4…
|
drh
|
2570 |
** c A new check-in |
|
e5653a4…
|
drh
|
2571 |
** f An original forum post |
|
d4361f6…
|
drh
|
2572 |
** n New forum threads |
|
d4361f6…
|
drh
|
2573 |
** r Replies to my forum posts |
|
e5653a4…
|
drh
|
2574 |
** x An edit to a prior forum post |
|
e5653a4…
|
drh
|
2575 |
** t A new ticket or a change to an existing ticket |
|
d96055c…
|
stephan
|
2576 |
** u A user was added or received new permissions |
|
e5653a4…
|
drh
|
2577 |
** w A change to a wiki page |
|
d4361f6…
|
drh
|
2578 |
** x Edits to forum posts |
|
fc5c7d2…
|
drh
|
2579 |
*/ |
|
fc5c7d2…
|
drh
|
2580 |
struct EmailEvent { |
|
d96055c…
|
stephan
|
2581 |
int type; /* 'c', 'f', 'n', 'r', 't', 'u', 'w', 'x' */ |
|
fc5c7d2…
|
drh
|
2582 |
int needMod; /* Pending moderator approval */ |
|
fc5c7d2…
|
drh
|
2583 |
Blob hdr; /* Header content, for forum entries */ |
|
fc5c7d2…
|
drh
|
2584 |
Blob txt; /* Text description to appear in an alert */ |
|
fc5c7d2…
|
drh
|
2585 |
char *zFromName; /* Human name of the sender */ |
|
d4361f6…
|
drh
|
2586 |
char *zPriors; /* Upthread sender IDs for forum posts */ |
|
fc5c7d2…
|
drh
|
2587 |
EmailEvent *pNext; /* Next in chronological order */ |
|
fc5c7d2…
|
drh
|
2588 |
}; |
|
fc5c7d2…
|
drh
|
2589 |
#endif |
|
fc5c7d2…
|
drh
|
2590 |
|
|
fc5c7d2…
|
drh
|
2591 |
/* |
|
fc5c7d2…
|
drh
|
2592 |
** Free a linked list of EmailEvent objects |
|
fc5c7d2…
|
drh
|
2593 |
*/ |
|
fc5c7d2…
|
drh
|
2594 |
void alert_free_eventlist(EmailEvent *p){ |
|
fc5c7d2…
|
drh
|
2595 |
while( p ){ |
|
fc5c7d2…
|
drh
|
2596 |
EmailEvent *pNext = p->pNext; |
|
fc5c7d2…
|
drh
|
2597 |
blob_reset(&p->txt); |
|
fc5c7d2…
|
drh
|
2598 |
blob_reset(&p->hdr); |
|
fc5c7d2…
|
drh
|
2599 |
fossil_free(p->zFromName); |
|
d4361f6…
|
drh
|
2600 |
fossil_free(p->zPriors); |
|
fc5c7d2…
|
drh
|
2601 |
fossil_free(p); |
|
fc5c7d2…
|
drh
|
2602 |
p = pNext; |
|
fc5c7d2…
|
drh
|
2603 |
} |
|
d4361f6…
|
drh
|
2604 |
} |
|
d4361f6…
|
drh
|
2605 |
|
|
d4361f6…
|
drh
|
2606 |
/* |
|
d4361f6…
|
drh
|
2607 |
** Compute a string that is appropriate for the EmailEvent.zPriors field |
|
d4361f6…
|
drh
|
2608 |
** for a particular forum post. |
|
d4361f6…
|
drh
|
2609 |
** |
|
d4361f6…
|
drh
|
2610 |
** This string is an encode list of sender names and rids for all ancestors |
|
33d3bf3…
|
km
|
2611 |
** of the fpid post - the post that fpid answers, the post that parent |
|
d4361f6…
|
drh
|
2612 |
** post answers, and so forth back up to the root post. Duplicates sender |
|
d4361f6…
|
drh
|
2613 |
** names are omitted. |
|
d4361f6…
|
drh
|
2614 |
** |
|
d4361f6…
|
drh
|
2615 |
** The EmailEvent.zPriors field is used to screen events for people who |
|
d4361f6…
|
drh
|
2616 |
** only want to see replies to their own posts or to specific posts. |
|
d4361f6…
|
drh
|
2617 |
*/ |
|
d4361f6…
|
drh
|
2618 |
static char *alert_compute_priors(int fpid){ |
|
d4361f6…
|
drh
|
2619 |
return db_text(0, |
|
d4361f6…
|
drh
|
2620 |
"WITH priors(rid,who) AS (" |
|
d4361f6…
|
drh
|
2621 |
" SELECT firt, coalesce(euser,user)" |
|
d4361f6…
|
drh
|
2622 |
" FROM forumpost LEFT JOIN event ON fpid=objid" |
|
d4361f6…
|
drh
|
2623 |
" WHERE fpid=%d" |
|
d4361f6…
|
drh
|
2624 |
" UNION ALL" |
|
d4361f6…
|
drh
|
2625 |
" SELECT firt, coalesce(euser,user)" |
|
d4361f6…
|
drh
|
2626 |
" FROM priors, forumpost LEFT JOIN event ON fpid=objid" |
|
d4361f6…
|
drh
|
2627 |
" WHERE fpid=rid" |
|
d4361f6…
|
drh
|
2628 |
")" |
|
d4361f6…
|
drh
|
2629 |
"SELECT ','||group_concat(DISTINCT 'u'||who)||" |
|
d4361f6…
|
drh
|
2630 |
"','||group_concat(rid) FROM priors;", |
|
d4361f6…
|
drh
|
2631 |
fpid |
|
d4361f6…
|
drh
|
2632 |
); |
|
fc5c7d2…
|
drh
|
2633 |
} |
|
fc5c7d2…
|
drh
|
2634 |
|
|
fc5c7d2…
|
drh
|
2635 |
/* |
|
fc5c7d2…
|
drh
|
2636 |
** Compute and return a linked list of EmailEvent objects |
|
fc5c7d2…
|
drh
|
2637 |
** corresponding to the current content of the temp.wantalert |
|
fc5c7d2…
|
drh
|
2638 |
** table which should be defined as follows: |
|
fc5c7d2…
|
drh
|
2639 |
** |
|
fc5c7d2…
|
drh
|
2640 |
** CREATE TEMP TABLE wantalert(eventId TEXT, needMod BOOLEAN); |
|
fc5c7d2…
|
drh
|
2641 |
*/ |
|
fc5c7d2…
|
drh
|
2642 |
EmailEvent *alert_compute_event_text(int *pnEvent, int doDigest){ |
|
fc5c7d2…
|
drh
|
2643 |
Stmt q; |
|
fc5c7d2…
|
drh
|
2644 |
EmailEvent *p; |
|
fc5c7d2…
|
drh
|
2645 |
EmailEvent anchor; |
|
fc5c7d2…
|
drh
|
2646 |
EmailEvent *pLast; |
|
fc5c7d2…
|
drh
|
2647 |
const char *zUrl = db_get("email-url","http://localhost:8080"); |
|
fc5c7d2…
|
drh
|
2648 |
const char *zFrom; |
|
fc5c7d2…
|
drh
|
2649 |
const char *zSub; |
|
fc5c7d2…
|
drh
|
2650 |
|
|
fc5c7d2…
|
drh
|
2651 |
|
|
fc5c7d2…
|
drh
|
2652 |
/* First do non-forum post events */ |
|
fc5c7d2…
|
drh
|
2653 |
db_prepare(&q, |
|
fc5c7d2…
|
drh
|
2654 |
"SELECT" |
|
1a5dee1…
|
drh
|
2655 |
" CASE WHEN event.type='t'" |
|
1a5dee1…
|
drh
|
2656 |
" THEN (SELECT substr(tagname,5) FROM tag" |
|
1a5dee1…
|
drh
|
2657 |
" WHERE tagid=event.tagid AND tagname LIKE 'tkt-%%')" |
|
1a5dee1…
|
drh
|
2658 |
" ELSE blob.uuid END," /* 0 */ |
|
fc5c7d2…
|
drh
|
2659 |
" datetime(event.mtime)," /* 1 */ |
|
fc5c7d2…
|
drh
|
2660 |
" coalesce(ecomment,comment)" |
|
fc5c7d2…
|
drh
|
2661 |
" || ' (user: ' || coalesce(euser,user,'?')" |
|
fc5c7d2…
|
drh
|
2662 |
" || (SELECT case when length(x)>0 then ' tags: ' || x else '' end" |
|
fc5c7d2…
|
drh
|
2663 |
" FROM (SELECT group_concat(substr(tagname,5), ', ') AS x" |
|
fc5c7d2…
|
drh
|
2664 |
" FROM tag, tagxref" |
|
fc5c7d2…
|
drh
|
2665 |
" WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" |
|
fc5c7d2…
|
drh
|
2666 |
" AND tagxref.rid=blob.rid AND tagxref.tagtype>0))" |
|
fc5c7d2…
|
drh
|
2667 |
" || ')' as comment," /* 2 */ |
|
fc5c7d2…
|
drh
|
2668 |
" wantalert.eventId," /* 3 */ |
|
fc5c7d2…
|
drh
|
2669 |
" wantalert.needMod" /* 4 */ |
|
fc5c7d2…
|
drh
|
2670 |
" FROM temp.wantalert, event, blob" |
|
fc5c7d2…
|
drh
|
2671 |
" WHERE blob.rid=event.objid" |
|
fc5c7d2…
|
drh
|
2672 |
" AND event.objid=substr(wantalert.eventId,2)+0" |
|
fc5c7d2…
|
drh
|
2673 |
" AND (%d OR eventId NOT GLOB 'f*')" |
|
fc5c7d2…
|
drh
|
2674 |
" ORDER BY event.mtime", |
|
fc5c7d2…
|
drh
|
2675 |
doDigest |
|
fc5c7d2…
|
drh
|
2676 |
); |
|
fc5c7d2…
|
drh
|
2677 |
memset(&anchor, 0, sizeof(anchor)); |
|
fc5c7d2…
|
drh
|
2678 |
pLast = &anchor; |
|
fc5c7d2…
|
drh
|
2679 |
*pnEvent = 0; |
|
fc5c7d2…
|
drh
|
2680 |
while( db_step(&q)==SQLITE_ROW ){ |
|
fc5c7d2…
|
drh
|
2681 |
const char *zType = ""; |
|
7fcfa93…
|
stephan
|
2682 |
const char *zComment = db_column_text(&q, 2); |
|
33877fa…
|
drh
|
2683 |
p = fossil_malloc_zero( sizeof(EmailEvent) ); |
|
fc5c7d2…
|
drh
|
2684 |
pLast->pNext = p; |
|
fc5c7d2…
|
drh
|
2685 |
pLast = p; |
|
fc5c7d2…
|
drh
|
2686 |
p->type = db_column_text(&q, 3)[0]; |
|
fc5c7d2…
|
drh
|
2687 |
p->needMod = db_column_int(&q, 4); |
|
fc5c7d2…
|
drh
|
2688 |
p->zFromName = 0; |
|
fc5c7d2…
|
drh
|
2689 |
p->pNext = 0; |
|
fc5c7d2…
|
drh
|
2690 |
switch( p->type ){ |
|
fc5c7d2…
|
drh
|
2691 |
case 'c': zType = "Check-In"; break; |
|
e5653a4…
|
drh
|
2692 |
/* case 'f': -- forum posts omitted from this loop. See below */ |
|
a8856c6…
|
drh
|
2693 |
case 't': zType = "Ticket Change"; break; |
|
7fcfa93…
|
stephan
|
2694 |
case 'w': { |
|
7fcfa93…
|
stephan
|
2695 |
zType = "Wiki Edit"; |
|
7fcfa93…
|
stephan
|
2696 |
switch( zComment ? *zComment : 0 ){ |
|
7fcfa93…
|
stephan
|
2697 |
case ':': ++zComment; break; |
|
7fcfa93…
|
stephan
|
2698 |
case '+': zType = "Wiki Added"; ++zComment; break; |
|
7fcfa93…
|
stephan
|
2699 |
case '-': zType = "Wiki Removed"; ++zComment; break; |
|
7fcfa93…
|
stephan
|
2700 |
} |
|
7fcfa93…
|
stephan
|
2701 |
break; |
|
7fcfa93…
|
stephan
|
2702 |
} |
|
fc5c7d2…
|
drh
|
2703 |
} |
|
fc5c7d2…
|
drh
|
2704 |
blob_init(&p->hdr, 0, 0); |
|
fc5c7d2…
|
drh
|
2705 |
blob_init(&p->txt, 0, 0); |
|
fc5c7d2…
|
drh
|
2706 |
blob_appendf(&p->txt,"== %s %s ==\n%s\n%s/info/%.20s\n", |
|
fc5c7d2…
|
drh
|
2707 |
db_column_text(&q,1), |
|
fc5c7d2…
|
drh
|
2708 |
zType, |
|
7fcfa93…
|
stephan
|
2709 |
zComment, |
|
fc5c7d2…
|
drh
|
2710 |
zUrl, |
|
fc5c7d2…
|
drh
|
2711 |
db_column_text(&q,0) |
|
fc5c7d2…
|
drh
|
2712 |
); |
|
fc5c7d2…
|
drh
|
2713 |
if( p->needMod ){ |
|
fc5c7d2…
|
drh
|
2714 |
blob_appendf(&p->txt, |
|
fc5c7d2…
|
drh
|
2715 |
"** Pending moderator approval (%s/modreq) **\n", |
|
fc5c7d2…
|
drh
|
2716 |
zUrl |
|
fc5c7d2…
|
drh
|
2717 |
); |
|
fc5c7d2…
|
drh
|
2718 |
} |
|
fc5c7d2…
|
drh
|
2719 |
(*pnEvent)++; |
|
fc5c7d2…
|
drh
|
2720 |
} |
|
fc5c7d2…
|
drh
|
2721 |
db_finalize(&q); |
|
fc5c7d2…
|
drh
|
2722 |
|
|
fc5c7d2…
|
drh
|
2723 |
/* Early-out if forumpost is not a table in this repository */ |
|
fc5c7d2…
|
drh
|
2724 |
if( !db_table_exists("repository","forumpost") ){ |
|
fc5c7d2…
|
drh
|
2725 |
return anchor.pNext; |
|
fc5c7d2…
|
drh
|
2726 |
} |
|
fc5c7d2…
|
drh
|
2727 |
|
|
fc5c7d2…
|
drh
|
2728 |
/* For digests, the previous loop also handled forumposts already */ |
|
fc5c7d2…
|
drh
|
2729 |
if( doDigest ){ |
|
fc5c7d2…
|
drh
|
2730 |
return anchor.pNext; |
|
fc5c7d2…
|
drh
|
2731 |
} |
|
fc5c7d2…
|
drh
|
2732 |
|
|
fc5c7d2…
|
drh
|
2733 |
/* If we reach this point, it means that forumposts exist and this |
|
fc5c7d2…
|
drh
|
2734 |
** is a normal email alert. Construct full-text forum post alerts |
|
fc5c7d2…
|
drh
|
2735 |
** using a format that enables them to be sent as separate emails. |
|
fc5c7d2…
|
drh
|
2736 |
*/ |
|
fc5c7d2…
|
drh
|
2737 |
db_prepare(&q, |
|
fc5c7d2…
|
drh
|
2738 |
"SELECT" |
|
fb4545e…
|
drh
|
2739 |
" forumpost.fpid," /* 0: fpid */ |
|
fb4545e…
|
drh
|
2740 |
" (SELECT uuid FROM blob WHERE rid=forumpost.fpid)," /* 1: hash */ |
|
fb4545e…
|
drh
|
2741 |
" datetime(event.mtime)," /* 2: date/time */ |
|
fb4545e…
|
drh
|
2742 |
" substr(comment,instr(comment,':')+2)," /* 3: comment */ |
|
fb4545e…
|
drh
|
2743 |
" (WITH thread(fpid,fprev) AS (" |
|
fb4545e…
|
drh
|
2744 |
" SELECT fpid,fprev FROM forumpost AS tx" |
|
fb4545e…
|
drh
|
2745 |
" WHERE tx.froot=forumpost.froot)," |
|
fb4545e…
|
drh
|
2746 |
" basepid(fpid,bpid) AS (" |
|
fb4545e…
|
drh
|
2747 |
" SELECT fpid, fpid FROM thread WHERE fprev IS NULL" |
|
fb4545e…
|
drh
|
2748 |
" UNION ALL" |
|
fb4545e…
|
drh
|
2749 |
" SELECT thread.fpid, basepid.bpid FROM basepid, thread" |
|
fb4545e…
|
drh
|
2750 |
" WHERE basepid.fpid=thread.fprev)" |
|
fb4545e…
|
drh
|
2751 |
" SELECT uuid FROM blob, basepid" |
|
fb4545e…
|
drh
|
2752 |
" WHERE basepid.fpid=forumpost.firt" |
|
fb4545e…
|
drh
|
2753 |
" AND blob.rid=basepid.bpid)," /* 4: in-reply-to */ |
|
fb4545e…
|
drh
|
2754 |
" wantalert.needMod," /* 5: moderated */ |
|
fb4545e…
|
drh
|
2755 |
" coalesce(display_name(info),euser,user)," /* 6: user */ |
|
fb4545e…
|
drh
|
2756 |
" forumpost.fprev IS NULL" /* 7: is an edit */ |
|
fc5c7d2…
|
drh
|
2757 |
" FROM temp.wantalert, event, forumpost" |
|
fc5c7d2…
|
drh
|
2758 |
" LEFT JOIN user ON (login=coalesce(euser,user))" |
|
fc5c7d2…
|
drh
|
2759 |
" WHERE event.objid=substr(wantalert.eventId,2)+0" |
|
fc5c7d2…
|
drh
|
2760 |
" AND eventId GLOB 'f*'" |
|
fc5c7d2…
|
drh
|
2761 |
" AND forumpost.fpid=event.objid" |
|
fc5c7d2…
|
drh
|
2762 |
" ORDER BY event.mtime" |
|
fc5c7d2…
|
drh
|
2763 |
); |
|
fc5c7d2…
|
drh
|
2764 |
zFrom = db_get("email-self",0); |
|
fc5c7d2…
|
drh
|
2765 |
zSub = db_get("email-subname",""); |
|
fc5c7d2…
|
drh
|
2766 |
while( db_step(&q)==SQLITE_ROW ){ |
|
d4361f6…
|
drh
|
2767 |
int fpid = db_column_int(&q,0); |
|
d4361f6…
|
drh
|
2768 |
Manifest *pPost = manifest_get(fpid, CFTYPE_FORUM, 0); |
|
fc5c7d2…
|
drh
|
2769 |
const char *zIrt; |
|
fc5c7d2…
|
drh
|
2770 |
const char *zUuid; |
|
fc5c7d2…
|
drh
|
2771 |
const char *zTitle; |
|
fc5c7d2…
|
drh
|
2772 |
const char *z; |
|
fc5c7d2…
|
drh
|
2773 |
if( pPost==0 ) continue; |
|
fc5c7d2…
|
drh
|
2774 |
p = fossil_malloc( sizeof(EmailEvent) ); |
|
fc5c7d2…
|
drh
|
2775 |
pLast->pNext = p; |
|
fc5c7d2…
|
drh
|
2776 |
pLast = p; |
|
e5653a4…
|
drh
|
2777 |
p->type = db_column_int(&q,7) ? 'f' : 'x'; |
|
fc5c7d2…
|
drh
|
2778 |
p->needMod = db_column_int(&q, 5); |
|
fc5c7d2…
|
drh
|
2779 |
z = db_column_text(&q,6); |
|
fc5c7d2…
|
drh
|
2780 |
p->zFromName = z && z[0] ? fossil_strdup(z) : 0; |
|
d4361f6…
|
drh
|
2781 |
p->zPriors = alert_compute_priors(fpid); |
|
fc5c7d2…
|
drh
|
2782 |
p->pNext = 0; |
|
fc5c7d2…
|
drh
|
2783 |
blob_init(&p->hdr, 0, 0); |
|
fc5c7d2…
|
drh
|
2784 |
zUuid = db_column_text(&q, 1); |
|
fc5c7d2…
|
drh
|
2785 |
zTitle = db_column_text(&q, 3); |
|
fc5c7d2…
|
drh
|
2786 |
if( p->needMod ){ |
|
fc5c7d2…
|
drh
|
2787 |
blob_appendf(&p->hdr, "Subject: %s Pending Moderation: %s\r\n", |
|
fc5c7d2…
|
drh
|
2788 |
zSub, zTitle); |
|
fc5c7d2…
|
drh
|
2789 |
}else{ |
|
fc5c7d2…
|
drh
|
2790 |
blob_appendf(&p->hdr, "Subject: %s %s\r\n", zSub, zTitle); |
|
e0576ea…
|
stephan
|
2791 |
blob_appendf(&p->hdr, "Message-Id: <%.32s@%s>\r\n", |
|
fc5c7d2…
|
drh
|
2792 |
zUuid, alert_hostname(zFrom)); |
|
fc5c7d2…
|
drh
|
2793 |
zIrt = db_column_text(&q, 4); |
|
fc5c7d2…
|
drh
|
2794 |
if( zIrt && zIrt[0] ){ |
|
fc5c7d2…
|
drh
|
2795 |
blob_appendf(&p->hdr, "In-Reply-To: <%.32s@%s>\r\n", |
|
fc5c7d2…
|
drh
|
2796 |
zIrt, alert_hostname(zFrom)); |
|
fc5c7d2…
|
drh
|
2797 |
} |
|
fc5c7d2…
|
drh
|
2798 |
} |
|
fc5c7d2…
|
drh
|
2799 |
blob_init(&p->txt, 0, 0); |
|
fc5c7d2…
|
drh
|
2800 |
if( p->needMod ){ |
|
fc5c7d2…
|
drh
|
2801 |
blob_appendf(&p->txt, |
|
fc5c7d2…
|
drh
|
2802 |
"** Pending moderator approval (%s/modreq) **\n", |
|
fc5c7d2…
|
drh
|
2803 |
zUrl |
|
fc5c7d2…
|
drh
|
2804 |
); |
|
fc5c7d2…
|
drh
|
2805 |
} |
|
fc5c7d2…
|
drh
|
2806 |
blob_appendf(&p->txt, |
|
fc5c7d2…
|
drh
|
2807 |
"Forum post by %s on %s\n", |
|
fc5c7d2…
|
drh
|
2808 |
pPost->zUser, db_column_text(&q, 2)); |
|
fc5c7d2…
|
drh
|
2809 |
blob_appendf(&p->txt, "%s/forumpost/%S\n\n", zUrl, zUuid); |
|
fc5c7d2…
|
drh
|
2810 |
blob_append(&p->txt, pPost->zWiki, -1); |
|
fc5c7d2…
|
drh
|
2811 |
manifest_destroy(pPost); |
|
fc5c7d2…
|
drh
|
2812 |
(*pnEvent)++; |
|
fc5c7d2…
|
drh
|
2813 |
} |
|
fc5c7d2…
|
drh
|
2814 |
db_finalize(&q); |
|
fc5c7d2…
|
drh
|
2815 |
|
|
fc5c7d2…
|
drh
|
2816 |
return anchor.pNext; |
|
fc5c7d2…
|
drh
|
2817 |
} |
|
fc5c7d2…
|
drh
|
2818 |
|
|
fc5c7d2…
|
drh
|
2819 |
/* |
|
fc5c7d2…
|
drh
|
2820 |
** Put a header on an alert email |
|
fc5c7d2…
|
drh
|
2821 |
*/ |
|
fc5c7d2…
|
drh
|
2822 |
void email_header(Blob *pOut){ |
|
fc5c7d2…
|
drh
|
2823 |
blob_appendf(pOut, |
|
fc5c7d2…
|
drh
|
2824 |
"This is an automated email reporting changes " |
|
fc5c7d2…
|
drh
|
2825 |
"on Fossil repository %s (%s/timeline)\n", |
|
fc5c7d2…
|
drh
|
2826 |
db_get("email-subname","(unknown)"), |
|
fc5c7d2…
|
drh
|
2827 |
db_get("email-url","http://localhost:8080")); |
|
fc5c7d2…
|
drh
|
2828 |
} |
|
fc5c7d2…
|
drh
|
2829 |
|
|
fc5c7d2…
|
drh
|
2830 |
/* |
|
fc5c7d2…
|
drh
|
2831 |
** COMMAND: test-alert |
|
fc5c7d2…
|
drh
|
2832 |
** |
|
fc5c7d2…
|
drh
|
2833 |
** Usage: %fossil test-alert EVENTID ... |
|
fc5c7d2…
|
drh
|
2834 |
** |
|
fc5c7d2…
|
drh
|
2835 |
** Generate the text of an email alert for all of the EVENTIDs |
|
fc5c7d2…
|
drh
|
2836 |
** listed on the command-line. Or if no events are listed on the |
|
fc5c7d2…
|
drh
|
2837 |
** command line, generate text for all events named in the |
|
34d45c5…
|
drh
|
2838 |
** pending_alert table. The text of the email alerts appears on |
|
34d45c5…
|
drh
|
2839 |
** standard output. |
|
34d45c5…
|
drh
|
2840 |
** |
|
34d45c5…
|
drh
|
2841 |
** This command is intended for testing and debugging Fossil itself, |
|
34d45c5…
|
drh
|
2842 |
** for example when enhancing the email alert system or fixing bugs |
|
34d45c5…
|
drh
|
2843 |
** in the email alert system. If you are not making changes to the |
|
34d45c5…
|
drh
|
2844 |
** Fossil source code, this command is probably not useful to you. |
|
fc5c7d2…
|
drh
|
2845 |
** |
|
34d45c5…
|
drh
|
2846 |
** EVENTIDs are text. The first character is 'c', 'f', 't', or 'w' |
|
34d45c5…
|
drh
|
2847 |
** for check-in, forum, ticket, or wiki. The remaining text is a |
|
34d45c5…
|
drh
|
2848 |
** integer that references the EVENT.OBJID value for the event. |
|
34d45c5…
|
drh
|
2849 |
** Run /timeline?showid to see these OBJID values. |
|
fc5c7d2…
|
drh
|
2850 |
** |
|
fc5c7d2…
|
drh
|
2851 |
** Options: |
|
fc5c7d2…
|
drh
|
2852 |
** --digest Generate digest alert text |
|
fc5c7d2…
|
drh
|
2853 |
** --needmod Assume all events are pending moderator approval |
|
fc5c7d2…
|
drh
|
2854 |
*/ |
|
fc5c7d2…
|
drh
|
2855 |
void test_alert_cmd(void){ |
|
fc5c7d2…
|
drh
|
2856 |
Blob out; |
|
fc5c7d2…
|
drh
|
2857 |
int nEvent; |
|
fc5c7d2…
|
drh
|
2858 |
int needMod; |
|
fc5c7d2…
|
drh
|
2859 |
int doDigest; |
|
fc5c7d2…
|
drh
|
2860 |
EmailEvent *pEvent, *p; |
|
fc5c7d2…
|
drh
|
2861 |
|
|
fc5c7d2…
|
drh
|
2862 |
doDigest = find_option("digest",0,0)!=0; |
|
fc5c7d2…
|
drh
|
2863 |
needMod = find_option("needmod",0,0)!=0; |
|
fc5c7d2…
|
drh
|
2864 |
db_find_and_open_repository(0, 0); |
|
fc5c7d2…
|
drh
|
2865 |
verify_all_options(); |
|
fc5c7d2…
|
drh
|
2866 |
db_begin_transaction(); |
|
fc5c7d2…
|
drh
|
2867 |
alert_schema(0); |
|
fc5c7d2…
|
drh
|
2868 |
db_multi_exec("CREATE TEMP TABLE wantalert(eventid TEXT, needMod BOOLEAN)"); |
|
fc5c7d2…
|
drh
|
2869 |
if( g.argc==2 ){ |
|
fc5c7d2…
|
drh
|
2870 |
db_multi_exec( |
|
fc5c7d2…
|
drh
|
2871 |
"INSERT INTO wantalert(eventId,needMod)" |
|
fc5c7d2…
|
drh
|
2872 |
" SELECT eventid, %d FROM pending_alert", needMod); |
|
fc5c7d2…
|
drh
|
2873 |
}else{ |
|
fc5c7d2…
|
drh
|
2874 |
int i; |
|
fc5c7d2…
|
drh
|
2875 |
for(i=2; i<g.argc; i++){ |
|
fc5c7d2…
|
drh
|
2876 |
db_multi_exec("INSERT INTO wantalert(eventId,needMod) VALUES(%Q,%d)", |
|
fc5c7d2…
|
drh
|
2877 |
g.argv[i], needMod); |
|
fc5c7d2…
|
drh
|
2878 |
} |
|
fc5c7d2…
|
drh
|
2879 |
} |
|
fc5c7d2…
|
drh
|
2880 |
blob_init(&out, 0, 0); |
|
fc5c7d2…
|
drh
|
2881 |
email_header(&out); |
|
fc5c7d2…
|
drh
|
2882 |
pEvent = alert_compute_event_text(&nEvent, doDigest); |
|
fc5c7d2…
|
drh
|
2883 |
for(p=pEvent; p; p=p->pNext){ |
|
fc5c7d2…
|
drh
|
2884 |
blob_append(&out, "\n", 1); |
|
fc5c7d2…
|
drh
|
2885 |
if( blob_size(&p->hdr) ){ |
|
fc5c7d2…
|
drh
|
2886 |
blob_append(&out, blob_buffer(&p->hdr), blob_size(&p->hdr)); |
|
fc5c7d2…
|
drh
|
2887 |
blob_append(&out, "\n", 1); |
|
fc5c7d2…
|
drh
|
2888 |
} |
|
fc5c7d2…
|
drh
|
2889 |
blob_append(&out, blob_buffer(&p->txt), blob_size(&p->txt)); |
|
fc5c7d2…
|
drh
|
2890 |
} |
|
fc5c7d2…
|
drh
|
2891 |
alert_free_eventlist(pEvent); |
|
fc5c7d2…
|
drh
|
2892 |
fossil_print("%s", blob_str(&out)); |
|
fc5c7d2…
|
drh
|
2893 |
blob_reset(&out); |
|
fc5c7d2…
|
drh
|
2894 |
db_end_transaction(0); |
|
fc5c7d2…
|
drh
|
2895 |
} |
|
fc5c7d2…
|
drh
|
2896 |
|
|
fc5c7d2…
|
drh
|
2897 |
/* |
|
fc5c7d2…
|
drh
|
2898 |
** COMMAND: test-add-alerts |
|
fc5c7d2…
|
drh
|
2899 |
** |
|
fc5c7d2…
|
drh
|
2900 |
** Usage: %fossil test-add-alerts [OPTIONS] EVENTID ... |
|
fc5c7d2…
|
drh
|
2901 |
** |
|
fc5c7d2…
|
drh
|
2902 |
** Add one or more events to the pending_alert queue. Use this |
|
fc5c7d2…
|
drh
|
2903 |
** command during testing to force email notifications for specific |
|
fc5c7d2…
|
drh
|
2904 |
** events. |
|
fc5c7d2…
|
drh
|
2905 |
** |
|
fc5c7d2…
|
drh
|
2906 |
** EVENTIDs are text. The first character is 'c', 'f', 't', or 'w' |
|
fc5c7d2…
|
drh
|
2907 |
** for check-in, forum, ticket, or wiki. The remaining text is a |
|
fc5c7d2…
|
drh
|
2908 |
** integer that references the EVENT.OBJID value for the event. |
|
fc5c7d2…
|
drh
|
2909 |
** Run /timeline?showid to see these OBJID values. |
|
fc5c7d2…
|
drh
|
2910 |
** |
|
fc5c7d2…
|
drh
|
2911 |
** Options: |
|
fc5c7d2…
|
drh
|
2912 |
** --backoffice Run alert_backoffice() after all alerts have |
|
fc5c7d2…
|
drh
|
2913 |
** been added. This will cause the alerts to be |
|
fc5c7d2…
|
drh
|
2914 |
** sent out with the SENDALERT_TRACE option. |
|
fc5c7d2…
|
drh
|
2915 |
** --debug Like --backoffice, but add the SENDALERT_STDOUT |
|
fc5c7d2…
|
drh
|
2916 |
** so that emails are printed to standard output |
|
fc5c7d2…
|
drh
|
2917 |
** rather than being sent. |
|
fc5c7d2…
|
drh
|
2918 |
** --digest Process emails using SENDALERT_DIGEST |
|
fc5c7d2…
|
drh
|
2919 |
*/ |
|
fc5c7d2…
|
drh
|
2920 |
void test_add_alert_cmd(void){ |
|
fc5c7d2…
|
drh
|
2921 |
int i; |
|
fc5c7d2…
|
drh
|
2922 |
int doAuto = find_option("backoffice",0,0)!=0; |
|
fc5c7d2…
|
drh
|
2923 |
unsigned mFlags = 0; |
|
fc5c7d2…
|
drh
|
2924 |
if( find_option("debug",0,0)!=0 ){ |
|
fc5c7d2…
|
drh
|
2925 |
doAuto = 1; |
|
fc5c7d2…
|
drh
|
2926 |
mFlags = SENDALERT_STDOUT; |
|
fc5c7d2…
|
drh
|
2927 |
} |
|
fc5c7d2…
|
drh
|
2928 |
if( find_option("digest",0,0)!=0 ){ |
|
fc5c7d2…
|
drh
|
2929 |
mFlags |= SENDALERT_DIGEST; |
|
fc5c7d2…
|
drh
|
2930 |
} |
|
fc5c7d2…
|
drh
|
2931 |
db_find_and_open_repository(0, 0); |
|
fc5c7d2…
|
drh
|
2932 |
verify_all_options(); |
|
fc5c7d2…
|
drh
|
2933 |
db_begin_write(); |
|
fc5c7d2…
|
drh
|
2934 |
alert_schema(0); |
|
fc5c7d2…
|
drh
|
2935 |
for(i=2; i<g.argc; i++){ |
|
fc5c7d2…
|
drh
|
2936 |
db_multi_exec("REPLACE INTO pending_alert(eventId) VALUES(%Q)", g.argv[i]); |
|
fc5c7d2…
|
drh
|
2937 |
} |
|
fc5c7d2…
|
drh
|
2938 |
db_end_transaction(0); |
|
fc5c7d2…
|
drh
|
2939 |
if( doAuto ){ |
|
fc5c7d2…
|
drh
|
2940 |
alert_backoffice(SENDALERT_TRACE|mFlags); |
|
fc5c7d2…
|
drh
|
2941 |
} |
|
fc5c7d2…
|
drh
|
2942 |
} |
|
fc5c7d2…
|
drh
|
2943 |
|
|
34d45c5…
|
drh
|
2944 |
/* |
|
1508169…
|
drh
|
2945 |
** Minimum number of days between renewal messages |
|
1508169…
|
drh
|
2946 |
*/ |
|
1508169…
|
drh
|
2947 |
#define ALERT_RENEWAL_MSG_FREQUENCY 7 /* Do renewals at most once/week */ |
|
1508169…
|
drh
|
2948 |
|
|
1508169…
|
drh
|
2949 |
/* |
|
34d45c5…
|
drh
|
2950 |
** Construct the header and body for an email message that will alert |
|
34d45c5…
|
drh
|
2951 |
** a subscriber that their subscriptions are about to expire. |
|
34d45c5…
|
drh
|
2952 |
*/ |
|
34d45c5…
|
drh
|
2953 |
static void alert_renewal_msg( |
|
34d45c5…
|
drh
|
2954 |
Blob *pHdr, /* Write email header here */ |
|
34d45c5…
|
drh
|
2955 |
Blob *pBody, /* Write email body here */ |
|
34d45c5…
|
drh
|
2956 |
const char *zCode, /* The subscriber code */ |
|
34d45c5…
|
drh
|
2957 |
int lastContact, /* Last contact (days since 1970) */ |
|
34d45c5…
|
drh
|
2958 |
const char *zEAddr, /* Subscriber email address. Send to this. */ |
|
34d45c5…
|
drh
|
2959 |
const char *zSub, /* Subscription codes */ |
|
e2bdc10…
|
danield
|
2960 |
const char *zRepoName, /* Name of the sending Fossil repository */ |
|
e2bdc10…
|
danield
|
2961 |
const char *zUrl /* URL for the sending Fossil repository */ |
|
34d45c5…
|
drh
|
2962 |
){ |
|
34d45c5…
|
drh
|
2963 |
blob_appendf(pHdr,"To: <%s>\r\n", zEAddr); |
|
34d45c5…
|
drh
|
2964 |
blob_appendf(pHdr,"Subject: %s Subscription to %s expires soon\r\n", |
|
34d45c5…
|
drh
|
2965 |
zRepoName, zUrl); |
|
34d45c5…
|
drh
|
2966 |
blob_appendf(pBody, |
|
1508169…
|
drh
|
2967 |
"\nTo renew your subscription, click the following link:\n" |
|
1508169…
|
drh
|
2968 |
"\n %s/renew/%s\n\n", |
|
1508169…
|
drh
|
2969 |
zUrl, zCode |
|
1508169…
|
drh
|
2970 |
); |
|
1508169…
|
drh
|
2971 |
blob_appendf(pBody, |
|
1508169…
|
drh
|
2972 |
"You are currently receiving email notification for the following events\n" |
|
1508169…
|
drh
|
2973 |
"on the %s Fossil repository at %s:\n\n", |
|
34d45c5…
|
drh
|
2974 |
zRepoName, zUrl |
|
34d45c5…
|
drh
|
2975 |
); |
|
34d45c5…
|
drh
|
2976 |
if( strchr(zSub, 'a') ) blob_appendf(pBody, " * Announcements\n"); |
|
34d45c5…
|
drh
|
2977 |
if( strchr(zSub, 'c') ) blob_appendf(pBody, " * Check-ins\n"); |
|
34d45c5…
|
drh
|
2978 |
if( strchr(zSub, 'f') ) blob_appendf(pBody, " * Forum posts\n"); |
|
34d45c5…
|
drh
|
2979 |
if( strchr(zSub, 't') ) blob_appendf(pBody, " * Ticket changes\n"); |
|
d96055c…
|
stephan
|
2980 |
if( strchr(zSub, 'u') ) blob_appendf(pBody, " * User permission elevation\n"); |
|
34d45c5…
|
drh
|
2981 |
if( strchr(zSub, 'w') ) blob_appendf(pBody, " * Wiki changes\n"); |
|
1508169…
|
drh
|
2982 |
blob_appendf(pBody, "\n" |
|
34d45c5…
|
drh
|
2983 |
"If you take no action, your subscription will expire and you will be\n" |
|
1508169…
|
drh
|
2984 |
"unsubscribed in about %d days. To make other changes or to unsubscribe\n" |
|
34d45c5…
|
drh
|
2985 |
"immediately, visit the following webpage:\n\n" |
|
34d45c5…
|
drh
|
2986 |
" %s/alerts/%s\n\n", |
|
1508169…
|
drh
|
2987 |
ALERT_RENEWAL_MSG_FREQUENCY, zUrl, zCode |
|
34d45c5…
|
drh
|
2988 |
); |
|
d4361f6…
|
drh
|
2989 |
} |
|
d4361f6…
|
drh
|
2990 |
|
|
d4361f6…
|
drh
|
2991 |
/* |
|
d4361f6…
|
drh
|
2992 |
** If zUser is a sender of one of the ancestors of a forum post |
|
d4361f6…
|
drh
|
2993 |
** (if zUser appears in zPriors) then return true. |
|
d4361f6…
|
drh
|
2994 |
*/ |
|
d4361f6…
|
drh
|
2995 |
static int alert_in_priors(const char *zUser, const char *zPriors){ |
|
d4361f6…
|
drh
|
2996 |
int n = (int)strlen(zUser); |
|
d4361f6…
|
drh
|
2997 |
char zBuf[200]; |
|
d4361f6…
|
drh
|
2998 |
if( n>195 ) return 0; |
|
d4361f6…
|
drh
|
2999 |
if( zPriors==0 || zPriors[0]==0 ) return 0; |
|
d4361f6…
|
drh
|
3000 |
zBuf[0] = ','; |
|
d4361f6…
|
drh
|
3001 |
zBuf[1] = 'u'; |
|
d4361f6…
|
drh
|
3002 |
memcpy(zBuf+2, zUser, n+1); |
|
d4361f6…
|
drh
|
3003 |
return strstr(zPriors, zBuf)!=0; |
|
34d45c5…
|
drh
|
3004 |
} |
|
34d45c5…
|
drh
|
3005 |
|
|
fc5c7d2…
|
drh
|
3006 |
#if INTERFACE |
|
fc5c7d2…
|
drh
|
3007 |
/* |
|
fc5c7d2…
|
drh
|
3008 |
** Flags for alert_send_alerts() |
|
fc5c7d2…
|
drh
|
3009 |
*/ |
|
fc5c7d2…
|
drh
|
3010 |
#define SENDALERT_DIGEST 0x0001 /* Send a digest */ |
|
fc5c7d2…
|
drh
|
3011 |
#define SENDALERT_PRESERVE 0x0002 /* Do not mark the task as done */ |
|
fc5c7d2…
|
drh
|
3012 |
#define SENDALERT_STDOUT 0x0004 /* Print emails instead of sending */ |
|
fc5c7d2…
|
drh
|
3013 |
#define SENDALERT_TRACE 0x0008 /* Trace operation for debugging */ |
|
34d45c5…
|
drh
|
3014 |
#define SENDALERT_RENEWAL 0x0010 /* Send renewal notices */ |
|
fc5c7d2…
|
drh
|
3015 |
|
|
fc5c7d2…
|
drh
|
3016 |
#endif /* INTERFACE */ |
|
fc5c7d2…
|
drh
|
3017 |
|
|
fc5c7d2…
|
drh
|
3018 |
/* |
|
fc5c7d2…
|
drh
|
3019 |
** Send alert emails to subscribers. |
|
fc5c7d2…
|
drh
|
3020 |
** |
|
fc5c7d2…
|
drh
|
3021 |
** This procedure is run by either the backoffice, or in response to the |
|
fc5c7d2…
|
drh
|
3022 |
** "fossil alerts send" command. Details of operation are controlled by |
|
fc5c7d2…
|
drh
|
3023 |
** the flags parameter. |
|
fc5c7d2…
|
drh
|
3024 |
** |
|
fc5c7d2…
|
drh
|
3025 |
** Here is a summary of what happens: |
|
fc5c7d2…
|
drh
|
3026 |
** |
|
fc5c7d2…
|
drh
|
3027 |
** (1) Create a TEMP table wantalert(eventId,needMod) and fill it with |
|
fc5c7d2…
|
drh
|
3028 |
** all the events that we want to send alerts about. The needMod |
|
fc5c7d2…
|
drh
|
3029 |
** flags is set if and only if the event is still awaiting |
|
fc5c7d2…
|
drh
|
3030 |
** moderator approval. Events with the needMod flag are only |
|
fc5c7d2…
|
drh
|
3031 |
** shown to users that have moderator privileges. |
|
fc5c7d2…
|
drh
|
3032 |
** |
|
fc5c7d2…
|
drh
|
3033 |
** (2) Call alert_compute_event_text() to compute a list of EmailEvent |
|
fc5c7d2…
|
drh
|
3034 |
** objects that describe all events about which we want to send |
|
fc5c7d2…
|
drh
|
3035 |
** alerts. |
|
fc5c7d2…
|
drh
|
3036 |
** |
|
fc5c7d2…
|
drh
|
3037 |
** (3) Loop over all subscribers. Compose and send one or more email |
|
fc5c7d2…
|
drh
|
3038 |
** messages to each subscriber that describe the events for |
|
fc5c7d2…
|
drh
|
3039 |
** which the subscriber has expressed interest and has |
|
fc5c7d2…
|
drh
|
3040 |
** appropriate privileges. |
|
fc5c7d2…
|
drh
|
3041 |
** |
|
fc5c7d2…
|
drh
|
3042 |
** (4) Update the pending_alerts table to indicate that alerts have been |
|
fc5c7d2…
|
drh
|
3043 |
** sent. |
|
fc5c7d2…
|
drh
|
3044 |
** |
|
fc5c7d2…
|
drh
|
3045 |
** Update 2018-08-09: Do step (3) before step (4). Update the |
|
fc5c7d2…
|
drh
|
3046 |
** pending_alerts table *before* the emails are sent. That way, if |
|
fc5c7d2…
|
drh
|
3047 |
** the process malfunctions or crashes, some notifications may never |
|
fc5c7d2…
|
drh
|
3048 |
** be sent. But that is better than some recurring bug causing |
|
fc5c7d2…
|
drh
|
3049 |
** subscribers to be flooded with repeated notifications every 60 |
|
fc5c7d2…
|
drh
|
3050 |
** seconds! |
|
fc5c7d2…
|
drh
|
3051 |
*/ |
|
71b9f35…
|
drh
|
3052 |
int alert_send_alerts(u32 flags){ |
|
fc5c7d2…
|
drh
|
3053 |
EmailEvent *pEvents, *p; |
|
fc5c7d2…
|
drh
|
3054 |
int nEvent = 0; |
|
71b9f35…
|
drh
|
3055 |
int nSent = 0; |
|
fc5c7d2…
|
drh
|
3056 |
Stmt q; |
|
fc5c7d2…
|
drh
|
3057 |
const char *zDigest = "false"; |
|
fc5c7d2…
|
drh
|
3058 |
Blob hdr, body; |
|
fc5c7d2…
|
drh
|
3059 |
const char *zUrl; |
|
fc5c7d2…
|
drh
|
3060 |
const char *zRepoName; |
|
fc5c7d2…
|
drh
|
3061 |
const char *zFrom; |
|
fc5c7d2…
|
drh
|
3062 |
const char *zDest = (flags & SENDALERT_STDOUT) ? "stdout" : 0; |
|
fc5c7d2…
|
drh
|
3063 |
AlertSender *pSender = 0; |
|
fc5c7d2…
|
drh
|
3064 |
u32 senderFlags = 0; |
|
34d45c5…
|
drh
|
3065 |
int iInterval = 0; /* Subscription renewal interval */ |
|
fc5c7d2…
|
drh
|
3066 |
|
|
fc5c7d2…
|
drh
|
3067 |
if( g.fSqlTrace ) fossil_trace("-- BEGIN alert_send_alerts(%u)\n", flags); |
|
fc5c7d2…
|
drh
|
3068 |
alert_schema(0); |
|
4a3909a…
|
drh
|
3069 |
if( !alert_enabled() && (flags & SENDALERT_STDOUT)==0 ) goto send_alert_done; |
|
fc5c7d2…
|
drh
|
3070 |
zUrl = db_get("email-url",0); |
|
fc5c7d2…
|
drh
|
3071 |
if( zUrl==0 ) goto send_alert_done; |
|
fc5c7d2…
|
drh
|
3072 |
zRepoName = db_get("email-subname",0); |
|
fc5c7d2…
|
drh
|
3073 |
if( zRepoName==0 ) goto send_alert_done; |
|
fc5c7d2…
|
drh
|
3074 |
zFrom = db_get("email-self",0); |
|
fc5c7d2…
|
drh
|
3075 |
if( zFrom==0 ) goto send_alert_done; |
|
fc5c7d2…
|
drh
|
3076 |
if( flags & SENDALERT_TRACE ){ |
|
fc5c7d2…
|
drh
|
3077 |
senderFlags |= ALERT_TRACE; |
|
fc5c7d2…
|
drh
|
3078 |
} |
|
fc5c7d2…
|
drh
|
3079 |
pSender = alert_sender_new(zDest, senderFlags); |
|
fc5c7d2…
|
drh
|
3080 |
|
|
fc5c7d2…
|
drh
|
3081 |
/* Step (1): Compute the alerts that need sending |
|
fc5c7d2…
|
drh
|
3082 |
*/ |
|
fc5c7d2…
|
drh
|
3083 |
db_multi_exec( |
|
fc5c7d2…
|
drh
|
3084 |
"DROP TABLE IF EXISTS temp.wantalert;" |
|
fc5c7d2…
|
drh
|
3085 |
"CREATE TEMP TABLE wantalert(eventId TEXT, needMod BOOLEAN, sentMod);" |
|
fc5c7d2…
|
drh
|
3086 |
); |
|
fc5c7d2…
|
drh
|
3087 |
if( flags & SENDALERT_DIGEST ){ |
|
fc5c7d2…
|
drh
|
3088 |
/* Unmoderated changes are never sent as part of a digest */ |
|
fc5c7d2…
|
drh
|
3089 |
db_multi_exec( |
|
fc5c7d2…
|
drh
|
3090 |
"INSERT INTO wantalert(eventId,needMod)" |
|
fc5c7d2…
|
drh
|
3091 |
" SELECT eventid, 0" |
|
fc5c7d2…
|
drh
|
3092 |
" FROM pending_alert" |
|
fc5c7d2…
|
drh
|
3093 |
" WHERE sentDigest IS FALSE" |
|
fc5c7d2…
|
drh
|
3094 |
" AND NOT EXISTS(SELECT 1 FROM private WHERE rid=substr(eventid,2));" |
|
fc5c7d2…
|
drh
|
3095 |
); |
|
fc5c7d2…
|
drh
|
3096 |
zDigest = "true"; |
|
fc5c7d2…
|
drh
|
3097 |
}else{ |
|
fc5c7d2…
|
drh
|
3098 |
/* Immediate alerts might include events that are subject to |
|
fc5c7d2…
|
drh
|
3099 |
** moderator approval */ |
|
fc5c7d2…
|
drh
|
3100 |
db_multi_exec( |
|
fc5c7d2…
|
drh
|
3101 |
"INSERT INTO wantalert(eventId,needMod,sentMod)" |
|
fc5c7d2…
|
drh
|
3102 |
" SELECT eventid," |
|
fc5c7d2…
|
drh
|
3103 |
" EXISTS(SELECT 1 FROM private WHERE rid=substr(eventid,2))," |
|
fc5c7d2…
|
drh
|
3104 |
" sentMod" |
|
fc5c7d2…
|
drh
|
3105 |
" FROM pending_alert" |
|
fc5c7d2…
|
drh
|
3106 |
" WHERE sentSep IS FALSE;" |
|
fc5c7d2…
|
drh
|
3107 |
"DELETE FROM wantalert WHERE needMod AND sentMod;" |
|
fc5c7d2…
|
drh
|
3108 |
); |
|
fc5c7d2…
|
drh
|
3109 |
} |
|
fb040bb…
|
drh
|
3110 |
if( g.fSqlTrace ){ |
|
fb040bb…
|
drh
|
3111 |
fossil_trace("-- wantalert contains %d rows\n", |
|
fb040bb…
|
drh
|
3112 |
db_int(0, "SELECT count(*) FROM wantalert") |
|
fb040bb…
|
drh
|
3113 |
); |
|
fb040bb…
|
drh
|
3114 |
} |
|
fc5c7d2…
|
drh
|
3115 |
|
|
fc5c7d2…
|
drh
|
3116 |
/* Step 2: compute EmailEvent objects for every notification that |
|
fc5c7d2…
|
drh
|
3117 |
** needs sending. |
|
fc5c7d2…
|
drh
|
3118 |
*/ |
|
fc5c7d2…
|
drh
|
3119 |
pEvents = alert_compute_event_text(&nEvent, (flags & SENDALERT_DIGEST)!=0); |
|
34d45c5…
|
drh
|
3120 |
if( nEvent==0 ) goto send_alert_expiration_warnings; |
|
fc5c7d2…
|
drh
|
3121 |
|
|
fc5c7d2…
|
drh
|
3122 |
/* Step 4a: Update the pending_alerts table to designate the |
|
fc5c7d2…
|
drh
|
3123 |
** alerts as having all been sent. This is done *before* step (3) |
|
fc5c7d2…
|
drh
|
3124 |
** so that a crash will not cause alerts to be sent multiple times. |
|
fc5c7d2…
|
drh
|
3125 |
** Better a missed alert than being spammed with hundreds of alerts |
|
fc5c7d2…
|
drh
|
3126 |
** due to a bug. |
|
fc5c7d2…
|
drh
|
3127 |
*/ |
|
fc5c7d2…
|
drh
|
3128 |
if( (flags & SENDALERT_PRESERVE)==0 ){ |
|
fc5c7d2…
|
drh
|
3129 |
if( flags & SENDALERT_DIGEST ){ |
|
fc5c7d2…
|
drh
|
3130 |
db_multi_exec( |
|
fc5c7d2…
|
drh
|
3131 |
"UPDATE pending_alert SET sentDigest=true" |
|
fc5c7d2…
|
drh
|
3132 |
" WHERE eventid IN (SELECT eventid FROM wantalert);" |
|
fc5c7d2…
|
drh
|
3133 |
); |
|
fc5c7d2…
|
drh
|
3134 |
}else{ |
|
fc5c7d2…
|
drh
|
3135 |
db_multi_exec( |
|
fc5c7d2…
|
drh
|
3136 |
"UPDATE pending_alert SET sentSep=true" |
|
fc5c7d2…
|
drh
|
3137 |
" WHERE eventid IN (SELECT eventid FROM wantalert WHERE NOT needMod);" |
|
fc5c7d2…
|
drh
|
3138 |
"UPDATE pending_alert SET sentMod=true" |
|
fc5c7d2…
|
drh
|
3139 |
" WHERE eventid IN (SELECT eventid FROM wantalert WHERE needMod);" |
|
fc5c7d2…
|
drh
|
3140 |
); |
|
fc5c7d2…
|
drh
|
3141 |
} |
|
fc5c7d2…
|
drh
|
3142 |
} |
|
fc5c7d2…
|
drh
|
3143 |
|
|
fc5c7d2…
|
drh
|
3144 |
/* Step 3: Loop over subscribers. Send alerts |
|
fc5c7d2…
|
drh
|
3145 |
*/ |
|
fc5c7d2…
|
drh
|
3146 |
blob_init(&hdr, 0, 0); |
|
fc5c7d2…
|
drh
|
3147 |
blob_init(&body, 0, 0); |
|
fc5c7d2…
|
drh
|
3148 |
db_prepare(&q, |
|
fc5c7d2…
|
drh
|
3149 |
"SELECT" |
|
fc5c7d2…
|
drh
|
3150 |
" hex(subscriberCode)," /* 0 */ |
|
fc5c7d2…
|
drh
|
3151 |
" semail," /* 1 */ |
|
fc5c7d2…
|
drh
|
3152 |
" ssub," /* 2 */ |
|
d4361f6…
|
drh
|
3153 |
" fullcap(user.cap)," /* 3 */ |
|
d4361f6…
|
drh
|
3154 |
" suname" /* 4 */ |
|
fc5c7d2…
|
drh
|
3155 |
" FROM subscriber LEFT JOIN user ON (login=suname)" |
|
34d45c5…
|
drh
|
3156 |
" WHERE sverified" |
|
34d45c5…
|
drh
|
3157 |
" AND NOT sdonotcall" |
|
34d45c5…
|
drh
|
3158 |
" AND sdigest IS %s" |
|
2917bed…
|
drh
|
3159 |
" AND coalesce(subscriber.lastContact*86400,subscriber.mtime)>=%d", |
|
34d45c5…
|
drh
|
3160 |
zDigest/*safe-for-%s*/, |
|
34d45c5…
|
drh
|
3161 |
db_get_int("email-renew-cutoff",0) |
|
fc5c7d2…
|
drh
|
3162 |
); |
|
fc5c7d2…
|
drh
|
3163 |
while( db_step(&q)==SQLITE_ROW ){ |
|
fc5c7d2…
|
drh
|
3164 |
const char *zCode = db_column_text(&q, 0); |
|
fc5c7d2…
|
drh
|
3165 |
const char *zSub = db_column_text(&q, 2); |
|
fc5c7d2…
|
drh
|
3166 |
const char *zEmail = db_column_text(&q, 1); |
|
fc5c7d2…
|
drh
|
3167 |
const char *zCap = db_column_text(&q, 3); |
|
2917bed…
|
drh
|
3168 |
const char *zUser = db_column_text(&q, 4); |
|
fc5c7d2…
|
drh
|
3169 |
int nHit = 0; |
|
fc5c7d2…
|
drh
|
3170 |
for(p=pEvents; p; p=p->pNext){ |
|
d4361f6…
|
drh
|
3171 |
if( strchr(zSub,p->type)==0 ){ |
|
d4361f6…
|
drh
|
3172 |
if( p->type!='f' ) continue; |
|
d4361f6…
|
drh
|
3173 |
if( strchr(zSub,'n')!=0 && (p->zPriors==0 || p->zPriors[0]==0) ){ |
|
d4361f6…
|
drh
|
3174 |
/* New post: accepted */ |
|
2917bed…
|
drh
|
3175 |
}else if( strchr(zSub,'r')!=0 && zUser!=0 |
|
2917bed…
|
drh
|
3176 |
&& alert_in_priors(zUser, p->zPriors) ){ |
|
d4361f6…
|
drh
|
3177 |
/* A follow-up to a post written by the user: accept */ |
|
d4361f6…
|
drh
|
3178 |
}else{ |
|
d4361f6…
|
drh
|
3179 |
continue; |
|
d4361f6…
|
drh
|
3180 |
} |
|
d4361f6…
|
drh
|
3181 |
} |
|
fc5c7d2…
|
drh
|
3182 |
if( p->needMod ){ |
|
fc5c7d2…
|
drh
|
3183 |
/* For events that require moderator approval, only send an alert |
|
1d3dd1d…
|
wyoung
|
3184 |
** if the recipient is a moderator for that type of event. Setup |
|
1d3dd1d…
|
wyoung
|
3185 |
** and Admin users always get notified. */ |
|
fc5c7d2…
|
drh
|
3186 |
char xType = '*'; |
|
1d3dd1d…
|
wyoung
|
3187 |
if( strpbrk(zCap,"as")==0 ){ |
|
1d3dd1d…
|
wyoung
|
3188 |
switch( p->type ){ |
|
d4361f6…
|
drh
|
3189 |
case 'x': case 'f': |
|
d4361f6…
|
drh
|
3190 |
case 'n': case 'r': xType = '5'; break; |
|
e5653a4…
|
drh
|
3191 |
case 't': xType = 'q'; break; |
|
e5653a4…
|
drh
|
3192 |
case 'w': xType = 'l'; break; |
|
d96055c…
|
stephan
|
3193 |
/* Note: case 'u' is not handled here */ |
|
1d3dd1d…
|
wyoung
|
3194 |
} |
|
1d3dd1d…
|
wyoung
|
3195 |
if( strchr(zCap,xType)==0 ) continue; |
|
fc5c7d2…
|
drh
|
3196 |
} |
|
fc5c7d2…
|
drh
|
3197 |
}else if( strchr(zCap,'s')!=0 || strchr(zCap,'a')!=0 ){ |
|
fc5c7d2…
|
drh
|
3198 |
/* Setup and admin users can get any notification that does not |
|
fc5c7d2…
|
drh
|
3199 |
** require moderation */ |
|
fc5c7d2…
|
drh
|
3200 |
}else{ |
|
fc5c7d2…
|
drh
|
3201 |
/* Other users only see the alert if they have sufficient |
|
fc5c7d2…
|
drh
|
3202 |
** privilege to view the event itself */ |
|
fc5c7d2…
|
drh
|
3203 |
char xType = '*'; |
|
fc5c7d2…
|
drh
|
3204 |
switch( p->type ){ |
|
e5653a4…
|
drh
|
3205 |
case 'c': xType = 'o'; break; |
|
d4361f6…
|
drh
|
3206 |
case 'x': case 'f': |
|
d4361f6…
|
drh
|
3207 |
case 'n': case 'r': xType = '2'; break; |
|
e5653a4…
|
drh
|
3208 |
case 't': xType = 'r'; break; |
|
e5653a4…
|
drh
|
3209 |
case 'w': xType = 'j'; break; |
|
d96055c…
|
stephan
|
3210 |
/* Note: case 'u' is not handled here */ |
|
fc5c7d2…
|
drh
|
3211 |
} |
|
fc5c7d2…
|
drh
|
3212 |
if( strchr(zCap,xType)==0 ) continue; |
|
fc5c7d2…
|
drh
|
3213 |
} |
|
fc5c7d2…
|
drh
|
3214 |
if( blob_size(&p->hdr)>0 ){ |
|
fc5c7d2…
|
drh
|
3215 |
/* This alert should be sent as a separate email */ |
|
fc5c7d2…
|
drh
|
3216 |
Blob fhdr, fbody; |
|
fc5c7d2…
|
drh
|
3217 |
blob_init(&fhdr, 0, 0); |
|
fc5c7d2…
|
drh
|
3218 |
blob_appendf(&fhdr, "To: <%s>\r\n", zEmail); |
|
fc5c7d2…
|
drh
|
3219 |
blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr)); |
|
fc5c7d2…
|
drh
|
3220 |
blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt)); |
|
908612e…
|
drh
|
3221 |
if( pSender->zListId && pSender->zListId[0] ){ |
|
908612e…
|
drh
|
3222 |
blob_appendf(&fhdr, "List-Id: %s\r\n", pSender->zListId); |
|
908612e…
|
drh
|
3223 |
blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n", |
|
908612e…
|
drh
|
3224 |
zUrl, zCode); |
|
908612e…
|
drh
|
3225 |
blob_appendf(&fhdr, |
|
908612e…
|
drh
|
3226 |
"List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n"); |
|
908612e…
|
drh
|
3227 |
blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n", |
|
908612e…
|
drh
|
3228 |
zUrl, zCode); |
|
908612e…
|
drh
|
3229 |
/* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n", |
|
908612e…
|
drh
|
3230 |
** zUrl, zCode); */ |
|
908612e…
|
drh
|
3231 |
} |
|
fc5c7d2…
|
drh
|
3232 |
alert_send(pSender,&fhdr,&fbody,p->zFromName); |
|
71b9f35…
|
drh
|
3233 |
nSent++; |
|
fc5c7d2…
|
drh
|
3234 |
blob_reset(&fhdr); |
|
fc5c7d2…
|
drh
|
3235 |
blob_reset(&fbody); |
|
fc5c7d2…
|
drh
|
3236 |
}else{ |
|
fc5c7d2…
|
drh
|
3237 |
/* Events other than forum posts are gathered together into |
|
fc5c7d2…
|
drh
|
3238 |
** a single email message */ |
|
fc5c7d2…
|
drh
|
3239 |
if( nHit==0 ){ |
|
fc5c7d2…
|
drh
|
3240 |
blob_appendf(&hdr,"To: <%s>\r\n", zEmail); |
|
fc5c7d2…
|
drh
|
3241 |
blob_appendf(&hdr,"Subject: %s activity alert\r\n", zRepoName); |
|
fc5c7d2…
|
drh
|
3242 |
blob_appendf(&body, |
|
fc5c7d2…
|
drh
|
3243 |
"This is an automated email sent by the Fossil repository " |
|
fc5c7d2…
|
drh
|
3244 |
"at %s to report changes.\n", |
|
fc5c7d2…
|
drh
|
3245 |
zUrl |
|
fc5c7d2…
|
drh
|
3246 |
); |
|
fc5c7d2…
|
drh
|
3247 |
} |
|
fc5c7d2…
|
drh
|
3248 |
nHit++; |
|
fc5c7d2…
|
drh
|
3249 |
blob_append(&body, "\n", 1); |
|
fc5c7d2…
|
drh
|
3250 |
blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt)); |
|
fc5c7d2…
|
drh
|
3251 |
} |
|
fc5c7d2…
|
drh
|
3252 |
} |
|
fc5c7d2…
|
drh
|
3253 |
if( nHit==0 ) continue; |
|
908612e…
|
drh
|
3254 |
if( pSender->zListId && pSender->zListId[0] ){ |
|
908612e…
|
drh
|
3255 |
blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId); |
|
908612e…
|
drh
|
3256 |
blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n", |
|
908612e…
|
drh
|
3257 |
zUrl, zCode); |
|
908612e…
|
drh
|
3258 |
blob_appendf(&hdr, |
|
908612e…
|
drh
|
3259 |
"List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n"); |
|
908612e…
|
drh
|
3260 |
blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n", |
|
908612e…
|
drh
|
3261 |
zUrl, zCode); |
|
908612e…
|
drh
|
3262 |
} |
|
fc5c7d2…
|
drh
|
3263 |
alert_send(pSender,&hdr,&body,0); |
|
71b9f35…
|
drh
|
3264 |
nSent++; |
|
fc5c7d2…
|
drh
|
3265 |
blob_truncate(&hdr, 0); |
|
fc5c7d2…
|
drh
|
3266 |
blob_truncate(&body, 0); |
|
fc5c7d2…
|
drh
|
3267 |
} |
|
fc5c7d2…
|
drh
|
3268 |
blob_reset(&hdr); |
|
fc5c7d2…
|
drh
|
3269 |
blob_reset(&body); |
|
fc5c7d2…
|
drh
|
3270 |
db_finalize(&q); |
|
fc5c7d2…
|
drh
|
3271 |
alert_free_eventlist(pEvents); |
|
fc5c7d2…
|
drh
|
3272 |
|
|
fc5c7d2…
|
drh
|
3273 |
/* Step 4b: Update the pending_alerts table to remove all of the |
|
fc5c7d2…
|
drh
|
3274 |
** alerts that have been completely sent. |
|
fc5c7d2…
|
drh
|
3275 |
*/ |
|
fc5c7d2…
|
drh
|
3276 |
db_multi_exec("DELETE FROM pending_alert WHERE sentDigest AND sentSep;"); |
|
34d45c5…
|
drh
|
3277 |
|
|
34d45c5…
|
drh
|
3278 |
/* Send renewal messages to subscribers whose subscriptions are about |
|
34d45c5…
|
drh
|
3279 |
** to expire. Only do this if: |
|
34d45c5…
|
drh
|
3280 |
** |
|
275da70…
|
danield
|
3281 |
** (1) email-renew-interval is 14 or greater (or in other words if |
|
34d45c5…
|
drh
|
3282 |
** subscription expiration is enabled). |
|
34d45c5…
|
drh
|
3283 |
** |
|
34d45c5…
|
drh
|
3284 |
** (2) The SENDALERT_RENEWAL flag is set |
|
34d45c5…
|
drh
|
3285 |
*/ |
|
34d45c5…
|
drh
|
3286 |
send_alert_expiration_warnings: |
|
34d45c5…
|
drh
|
3287 |
if( (flags & SENDALERT_RENEWAL)!=0 |
|
34d45c5…
|
drh
|
3288 |
&& (iInterval = db_get_int("email-renew-interval",0))>=14 |
|
34d45c5…
|
drh
|
3289 |
){ |
|
34d45c5…
|
drh
|
3290 |
int iNow = (int)(time(0)/86400); |
|
34d45c5…
|
drh
|
3291 |
int iOldWarn = db_get_int("email-renew-warning",0); |
|
34d45c5…
|
drh
|
3292 |
int iNewWarn = iNow - iInterval + ALERT_RENEWAL_MSG_FREQUENCY; |
|
34d45c5…
|
drh
|
3293 |
if( iNewWarn >= iOldWarn + ALERT_RENEWAL_MSG_FREQUENCY ){ |
|
34d45c5…
|
drh
|
3294 |
db_prepare(&q, |
|
34d45c5…
|
drh
|
3295 |
"SELECT" |
|
34d45c5…
|
drh
|
3296 |
" hex(subscriberCode)," /* 0 */ |
|
34d45c5…
|
drh
|
3297 |
" lastContact," /* 1 */ |
|
34d45c5…
|
drh
|
3298 |
" semail," /* 2 */ |
|
34d45c5…
|
drh
|
3299 |
" ssub" /* 3 */ |
|
34d45c5…
|
drh
|
3300 |
" FROM subscriber" |
|
34d45c5…
|
drh
|
3301 |
" WHERE lastContact<=%d AND lastContact>%d" |
|
34d45c5…
|
drh
|
3302 |
" AND NOT sdonotcall" |
|
34d45c5…
|
drh
|
3303 |
" AND length(sdigest)>0", |
|
34d45c5…
|
drh
|
3304 |
iNewWarn, iOldWarn |
|
34d45c5…
|
drh
|
3305 |
); |
|
34d45c5…
|
drh
|
3306 |
while( db_step(&q)==SQLITE_ROW ){ |
|
34d45c5…
|
drh
|
3307 |
Blob hdr, body; |
|
908612e…
|
drh
|
3308 |
const char *zCode = db_column_text(&q,0); |
|
34d45c5…
|
drh
|
3309 |
blob_init(&hdr, 0, 0); |
|
34d45c5…
|
drh
|
3310 |
blob_init(&body, 0, 0); |
|
e0576ea…
|
stephan
|
3311 |
alert_renewal_msg(&hdr, &body, |
|
908612e…
|
drh
|
3312 |
zCode, |
|
34d45c5…
|
drh
|
3313 |
db_column_int(&q,1), |
|
34d45c5…
|
drh
|
3314 |
db_column_text(&q,2), |
|
34d45c5…
|
drh
|
3315 |
db_column_text(&q,3), |
|
34d45c5…
|
drh
|
3316 |
zRepoName, zUrl); |
|
908612e…
|
drh
|
3317 |
if( pSender->zListId && pSender->zListId[0] ){ |
|
908612e…
|
drh
|
3318 |
blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId); |
|
908612e…
|
drh
|
3319 |
blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n", |
|
908612e…
|
drh
|
3320 |
zUrl, zCode); |
|
908612e…
|
drh
|
3321 |
blob_appendf(&hdr, |
|
908612e…
|
drh
|
3322 |
"List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n"); |
|
908612e…
|
drh
|
3323 |
blob_appendf(&body, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n", |
|
908612e…
|
drh
|
3324 |
zUrl, zCode); |
|
908612e…
|
drh
|
3325 |
} |
|
34d45c5…
|
drh
|
3326 |
alert_send(pSender,&hdr,&body,0); |
|
34d45c5…
|
drh
|
3327 |
blob_reset(&hdr); |
|
34d45c5…
|
drh
|
3328 |
blob_reset(&body); |
|
34d45c5…
|
drh
|
3329 |
} |
|
34d45c5…
|
drh
|
3330 |
db_finalize(&q); |
|
34d45c5…
|
drh
|
3331 |
if( (flags & SENDALERT_PRESERVE)==0 ){ |
|
34d45c5…
|
drh
|
3332 |
if( iOldWarn>0 ){ |
|
34d45c5…
|
drh
|
3333 |
db_set_int("email-renew-cutoff", iOldWarn, 0); |
|
34d45c5…
|
drh
|
3334 |
} |
|
34d45c5…
|
drh
|
3335 |
db_set_int("email-renew-warning", iNewWarn, 0); |
|
34d45c5…
|
drh
|
3336 |
} |
|
34d45c5…
|
drh
|
3337 |
} |
|
34d45c5…
|
drh
|
3338 |
} |
|
71b9f35…
|
drh
|
3339 |
|
|
fc5c7d2…
|
drh
|
3340 |
send_alert_done: |
|
fc5c7d2…
|
drh
|
3341 |
alert_sender_free(pSender); |
|
fc5c7d2…
|
drh
|
3342 |
if( g.fSqlTrace ) fossil_trace("-- END alert_send_alerts(%u)\n", flags); |
|
71b9f35…
|
drh
|
3343 |
return nSent; |
|
fc5c7d2…
|
drh
|
3344 |
} |
|
fc5c7d2…
|
drh
|
3345 |
|
|
fc5c7d2…
|
drh
|
3346 |
/* |
|
fc5c7d2…
|
drh
|
3347 |
** Do backoffice processing for email notifications. In other words, |
|
fc5c7d2…
|
drh
|
3348 |
** check to see if any email notifications need to occur, and then |
|
fc5c7d2…
|
drh
|
3349 |
** do them. |
|
fc5c7d2…
|
drh
|
3350 |
** |
|
fc5c7d2…
|
drh
|
3351 |
** This routine is intended to run in the background, after webpages. |
|
fc5c7d2…
|
drh
|
3352 |
** |
|
fc5c7d2…
|
drh
|
3353 |
** The mFlags option is zero or more of the SENDALERT_* flags. Normally |
|
fc5c7d2…
|
drh
|
3354 |
** this flag is zero, but the test-set-alert command sets it to |
|
fc5c7d2…
|
drh
|
3355 |
** SENDALERT_TRACE. |
|
fc5c7d2…
|
drh
|
3356 |
*/ |
|
71b9f35…
|
drh
|
3357 |
int alert_backoffice(u32 mFlags){ |
|
fc5c7d2…
|
drh
|
3358 |
int iJulianDay; |
|
71b9f35…
|
drh
|
3359 |
int nSent = 0; |
|
71b9f35…
|
drh
|
3360 |
if( !alert_tables_exist() ) return 0; |
|
77377e6…
|
drh
|
3361 |
nSent = alert_send_alerts(mFlags); |
|
fc5c7d2…
|
drh
|
3362 |
iJulianDay = db_int(0, "SELECT julianday('now')"); |
|
fc5c7d2…
|
drh
|
3363 |
if( iJulianDay>db_get_int("email-last-digest",0) ){ |
|
fc5c7d2…
|
drh
|
3364 |
db_set_int("email-last-digest",iJulianDay,0); |
|
34d45c5…
|
drh
|
3365 |
nSent += alert_send_alerts(SENDALERT_DIGEST|SENDALERT_RENEWAL|mFlags); |
|
fc5c7d2…
|
drh
|
3366 |
} |
|
71b9f35…
|
drh
|
3367 |
return nSent; |
|
fc5c7d2…
|
drh
|
3368 |
} |
|
fc5c7d2…
|
drh
|
3369 |
|
|
fc5c7d2…
|
drh
|
3370 |
/* |
|
fc5c7d2…
|
drh
|
3371 |
** WEBPAGE: contact_admin |
|
fc5c7d2…
|
drh
|
3372 |
** |
|
fc5c7d2…
|
drh
|
3373 |
** A web-form to send an email message to the repository administrator, |
|
fc5c7d2…
|
drh
|
3374 |
** or (with appropriate permissions) to anybody. |
|
fc5c7d2…
|
drh
|
3375 |
*/ |
|
fc5c7d2…
|
drh
|
3376 |
void contact_admin_page(void){ |
|
fc5c7d2…
|
drh
|
3377 |
const char *zAdminEmail = db_get("email-admin",0); |
|
fc5c7d2…
|
drh
|
3378 |
unsigned int uSeed = 0; |
|
fc5c7d2…
|
drh
|
3379 |
const char *zDecoded; |
|
fc5c7d2…
|
drh
|
3380 |
char *zCaptcha = 0; |
|
fc5c7d2…
|
drh
|
3381 |
|
|
fc5c7d2…
|
drh
|
3382 |
login_check_credentials(); |
|
112c713…
|
drh
|
3383 |
style_set_current_feature("alerts"); |
|
fc5c7d2…
|
drh
|
3384 |
if( zAdminEmail==0 || zAdminEmail[0]==0 ){ |
|
fc5c7d2…
|
drh
|
3385 |
style_header("Outbound Email Disabled"); |
|
fc5c7d2…
|
drh
|
3386 |
@ <p>Outbound email is disabled on this repository |
|
112c713…
|
drh
|
3387 |
style_finish_page(); |
|
fc5c7d2…
|
drh
|
3388 |
return; |
|
fc5c7d2…
|
drh
|
3389 |
} |
|
e0576ea…
|
stephan
|
3390 |
if( P("submit")!=0 |
|
fc5c7d2…
|
drh
|
3391 |
&& P("subject")!=0 |
|
fc5c7d2…
|
drh
|
3392 |
&& P("msg")!=0 |
|
fc5c7d2…
|
drh
|
3393 |
&& P("from")!=0 |
|
920ace1…
|
drh
|
3394 |
&& cgi_csrf_safe(2) |
|
fc5c7d2…
|
drh
|
3395 |
&& captcha_is_correct(0) |
|
fc5c7d2…
|
drh
|
3396 |
){ |
|
fc5c7d2…
|
drh
|
3397 |
Blob hdr, body; |
|
fc5c7d2…
|
drh
|
3398 |
AlertSender *pSender = alert_sender_new(0,0); |
|
fc5c7d2…
|
drh
|
3399 |
blob_init(&hdr, 0, 0); |
|
fc5c7d2…
|
drh
|
3400 |
blob_appendf(&hdr, "To: <%s>\r\nSubject: %s administrator message\r\n", |
|
fc5c7d2…
|
drh
|
3401 |
zAdminEmail, db_get("email-subname","Fossil Repo")); |
|
fc5c7d2…
|
drh
|
3402 |
blob_init(&body, 0, 0); |
|
fc5c7d2…
|
drh
|
3403 |
blob_appendf(&body, "Message from [%s]\n", PT("from")/*safe-for-%s*/); |
|
fc5c7d2…
|
drh
|
3404 |
blob_appendf(&body, "Subject: [%s]\n\n", PT("subject")/*safe-for-%s*/); |
|
fc5c7d2…
|
drh
|
3405 |
blob_appendf(&body, "%s", PT("msg")/*safe-for-%s*/); |
|
fc5c7d2…
|
drh
|
3406 |
alert_send(pSender, &hdr, &body, 0); |
|
fc5c7d2…
|
drh
|
3407 |
style_header("Message Sent"); |
|
fc5c7d2…
|
drh
|
3408 |
if( pSender->zErr ){ |
|
fc5c7d2…
|
drh
|
3409 |
@ <h1>Internal Error</h1> |
|
fc5c7d2…
|
drh
|
3410 |
@ <p>The following error was reported by the system: |
|
fc5c7d2…
|
drh
|
3411 |
@ <blockquote><pre> |
|
fc5c7d2…
|
drh
|
3412 |
@ %h(pSender->zErr) |
|
fc5c7d2…
|
drh
|
3413 |
@ </pre></blockquote> |
|
fc5c7d2…
|
drh
|
3414 |
}else{ |
|
fc5c7d2…
|
drh
|
3415 |
@ <p>Your message has been sent to the repository administrator. |
|
fc5c7d2…
|
drh
|
3416 |
@ Thank you for your input.</p> |
|
fc5c7d2…
|
drh
|
3417 |
} |
|
fc5c7d2…
|
drh
|
3418 |
alert_sender_free(pSender); |
|
112c713…
|
drh
|
3419 |
style_finish_page(); |
|
fc5c7d2…
|
drh
|
3420 |
return; |
|
fc5c7d2…
|
drh
|
3421 |
} |
|
fc5c7d2…
|
drh
|
3422 |
if( captcha_needed() ){ |
|
fc5c7d2…
|
drh
|
3423 |
uSeed = captcha_seed(); |
|
8659d84…
|
drh
|
3424 |
zDecoded = captcha_decode(uSeed, 0); |
|
fc5c7d2…
|
drh
|
3425 |
zCaptcha = captcha_render(zDecoded); |
|
fc5c7d2…
|
drh
|
3426 |
} |
|
112c713…
|
drh
|
3427 |
style_set_current_feature("alerts"); |
|
fc5c7d2…
|
drh
|
3428 |
style_header("Message To Administrator"); |
|
fc5c7d2…
|
drh
|
3429 |
form_begin(0, "%R/contact_admin"); |
|
fc5c7d2…
|
drh
|
3430 |
@ <p>Enter a message to the repository administrator below:</p> |
|
fc5c7d2…
|
drh
|
3431 |
@ <table class="subscribe"> |
|
fc5c7d2…
|
drh
|
3432 |
if( zCaptcha ){ |
|
fc5c7d2…
|
drh
|
3433 |
@ <tr> |
|
fc5c7d2…
|
drh
|
3434 |
@ <td class="form_label">Security Code:</td> |
|
fc5c7d2…
|
drh
|
3435 |
@ <td><input type="text" name="captcha" value="" size="10"> |
|
a584491…
|
drh
|
3436 |
captcha_speakit_button(uSeed, "Speak the code"); |
|
fc5c7d2…
|
drh
|
3437 |
@ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td> |
|
fc5c7d2…
|
drh
|
3438 |
@ </tr> |
|
fc5c7d2…
|
drh
|
3439 |
} |
|
fc5c7d2…
|
drh
|
3440 |
@ <tr> |
|
fc5c7d2…
|
drh
|
3441 |
@ <td class="form_label">Your Email Address:</td> |
|
fc5c7d2…
|
drh
|
3442 |
@ <td><input type="text" name="from" value="%h(PT("from"))" size="30"></td> |
|
fc5c7d2…
|
drh
|
3443 |
@ </tr> |
|
fc5c7d2…
|
drh
|
3444 |
@ <tr> |
|
fc5c7d2…
|
drh
|
3445 |
@ <td class="form_label">Subject:</td> |
|
fc5c7d2…
|
drh
|
3446 |
@ <td><input type="text" name="subject" value="%h(PT("subject"))"\ |
|
fc5c7d2…
|
drh
|
3447 |
@ size="80"></td> |
|
fc5c7d2…
|
drh
|
3448 |
@ </tr> |
|
fc5c7d2…
|
drh
|
3449 |
@ <tr> |
|
fc5c7d2…
|
drh
|
3450 |
@ <td class="form_label">Message:</td> |
|
fc5c7d2…
|
drh
|
3451 |
@ <td><textarea name="msg" cols="80" rows="10" wrap="virtual">\ |
|
fc5c7d2…
|
drh
|
3452 |
@ %h(PT("msg"))</textarea> |
|
fc5c7d2…
|
drh
|
3453 |
@ </tr> |
|
fc5c7d2…
|
drh
|
3454 |
@ <tr> |
|
fc5c7d2…
|
drh
|
3455 |
@ <td></td> |
|
fc5c7d2…
|
drh
|
3456 |
@ <td><input type="submit" name="submit" value="Send Message"> |
|
fc5c7d2…
|
drh
|
3457 |
@ </tr> |
|
fc5c7d2…
|
drh
|
3458 |
@ </table> |
|
fc5c7d2…
|
drh
|
3459 |
if( zCaptcha ){ |
|
75c89de…
|
drh
|
3460 |
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha"> |
|
fc5c7d2…
|
drh
|
3461 |
@ %h(zCaptcha) |
|
fc5c7d2…
|
drh
|
3462 |
@ </pre> |
|
dcf4410…
|
drh
|
3463 |
@ Enter the 8 characters above in the "Security Code" box<br/> |
|
fc5c7d2…
|
drh
|
3464 |
@ </td></tr></table></div> |
|
fc5c7d2…
|
drh
|
3465 |
} |
|
fc5c7d2…
|
drh
|
3466 |
@ </form> |
|
112c713…
|
drh
|
3467 |
style_finish_page(); |
|
fc5c7d2…
|
drh
|
3468 |
} |
|
fc5c7d2…
|
drh
|
3469 |
|
|
fc5c7d2…
|
drh
|
3470 |
/* |
|
e2bdc10…
|
danield
|
3471 |
** Send an announcement message described by query parameter. |
|
fc5c7d2…
|
drh
|
3472 |
** Permission to do this has already been verified. |
|
fc5c7d2…
|
drh
|
3473 |
*/ |
|
fc5c7d2…
|
drh
|
3474 |
static char *alert_send_announcement(void){ |
|
fc5c7d2…
|
drh
|
3475 |
AlertSender *pSender; |
|
fc5c7d2…
|
drh
|
3476 |
char *zErr; |
|
fc5c7d2…
|
drh
|
3477 |
const char *zTo = PT("to"); |
|
fc5c7d2…
|
drh
|
3478 |
char *zSubject = PT("subject"); |
|
fc5c7d2…
|
drh
|
3479 |
int bAll = PB("all"); |
|
fc5c7d2…
|
drh
|
3480 |
int bAA = PB("aa"); |
|
84d854c…
|
drh
|
3481 |
int bMods = PB("mods"); |
|
fc5c7d2…
|
drh
|
3482 |
const char *zSub = db_get("email-subname", "[Fossil Repo]"); |
|
bbfca4c…
|
drh
|
3483 |
const char *zName = P("name"); /* Debugging options */ |
|
bbfca4c…
|
drh
|
3484 |
const char *zDest = 0; /* How to send the announcement */ |
|
bbfca4c…
|
drh
|
3485 |
int bTest = 0; |
|
fc5c7d2…
|
drh
|
3486 |
Blob hdr, body; |
|
bbfca4c…
|
drh
|
3487 |
|
|
bbfca4c…
|
drh
|
3488 |
if( fossil_strcmp(zName, "test2")==0 ){ |
|
bbfca4c…
|
drh
|
3489 |
bTest = 2; |
|
bbfca4c…
|
drh
|
3490 |
zDest = "blob"; |
|
bbfca4c…
|
drh
|
3491 |
}else if( fossil_strcmp(zName, "test3")==0 ){ |
|
bbfca4c…
|
drh
|
3492 |
bTest = 3; |
|
bbfca4c…
|
drh
|
3493 |
if( fossil_strcmp(db_get("email-send-method",""),"relay")==0 ){ |
|
bbfca4c…
|
drh
|
3494 |
zDest = "debug-relay"; |
|
bbfca4c…
|
drh
|
3495 |
} |
|
bbfca4c…
|
drh
|
3496 |
} |
|
fc5c7d2…
|
drh
|
3497 |
blob_init(&body, 0, 0); |
|
fc5c7d2…
|
drh
|
3498 |
blob_init(&hdr, 0, 0); |
|
fc5c7d2…
|
drh
|
3499 |
blob_appendf(&body, "%s", PT("msg")/*safe-for-%s*/); |
|
bbfca4c…
|
drh
|
3500 |
pSender = alert_sender_new(zDest, 0); |
|
fc5c7d2…
|
drh
|
3501 |
if( zTo[0] ){ |
|
fc5c7d2…
|
drh
|
3502 |
blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject); |
|
fc5c7d2…
|
drh
|
3503 |
alert_send(pSender, &hdr, &body, 0); |
|
fc5c7d2…
|
drh
|
3504 |
} |
|
84d854c…
|
drh
|
3505 |
if( bAll || bAA || bMods ){ |
|
fc5c7d2…
|
drh
|
3506 |
Stmt q; |
|
fc5c7d2…
|
drh
|
3507 |
int nUsed = blob_size(&body); |
|
fc5c7d2…
|
drh
|
3508 |
const char *zURL = db_get("email-url",0); |
|
84d854c…
|
drh
|
3509 |
if( bAll ){ |
|
84d854c…
|
drh
|
3510 |
db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber " |
|
84d854c…
|
drh
|
3511 |
" WHERE sverified AND NOT sdonotcall"); |
|
84d854c…
|
drh
|
3512 |
}else if( bAA ){ |
|
84d854c…
|
drh
|
3513 |
db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber " |
|
84d854c…
|
drh
|
3514 |
" WHERE sverified AND NOT sdonotcall" |
|
84d854c…
|
drh
|
3515 |
" AND ssub LIKE '%%a%%'"); |
|
84d854c…
|
drh
|
3516 |
}else if( bMods ){ |
|
84d854c…
|
drh
|
3517 |
db_prepare(&q, |
|
84d854c…
|
drh
|
3518 |
"SELECT semail, hex(subscriberCode)" |
|
84d854c…
|
drh
|
3519 |
" FROM subscriber, user " |
|
84d854c…
|
drh
|
3520 |
" WHERE sverified AND NOT sdonotcall" |
|
84d854c…
|
drh
|
3521 |
" AND suname=login" |
|
84d854c…
|
drh
|
3522 |
" AND fullcap(cap) GLOB '*5*'"); |
|
84d854c…
|
drh
|
3523 |
} |
|
fc5c7d2…
|
drh
|
3524 |
while( db_step(&q)==SQLITE_ROW ){ |
|
fc5c7d2…
|
drh
|
3525 |
const char *zCode = db_column_text(&q, 1); |
|
fc5c7d2…
|
drh
|
3526 |
zTo = db_column_text(&q, 0); |
|
fc5c7d2…
|
drh
|
3527 |
blob_truncate(&hdr, 0); |
|
fc5c7d2…
|
drh
|
3528 |
blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject); |
|
fc5c7d2…
|
drh
|
3529 |
if( zURL ){ |
|
fc5c7d2…
|
drh
|
3530 |
blob_truncate(&body, nUsed); |
|
fc5c7d2…
|
drh
|
3531 |
blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n", |
|
fc5c7d2…
|
drh
|
3532 |
zURL, zCode); |
|
fc5c7d2…
|
drh
|
3533 |
} |
|
fc5c7d2…
|
drh
|
3534 |
alert_send(pSender, &hdr, &body, 0); |
|
fc5c7d2…
|
drh
|
3535 |
} |
|
fc5c7d2…
|
drh
|
3536 |
db_finalize(&q); |
|
fc5c7d2…
|
drh
|
3537 |
} |
|
056c83d…
|
drh
|
3538 |
if( bTest && blob_size(&pSender->out) ){ |
|
056c83d…
|
drh
|
3539 |
/* If the URL is "/announce/test2" then no email is actually sent. |
|
056c83d…
|
drh
|
3540 |
** Instead, the text of the email that would have been sent is |
|
056c83d…
|
drh
|
3541 |
** displayed in the result window. |
|
056c83d…
|
drh
|
3542 |
** |
|
056c83d…
|
drh
|
3543 |
** If the URL is "/announce/test3" and the email-send-method is "relay" |
|
056c83d…
|
drh
|
3544 |
** then the announcement is sent as it normally would be, but a |
|
056c83d…
|
drh
|
3545 |
** transcript of the SMTP conversation with the MTA is shown here. |
|
056c83d…
|
drh
|
3546 |
*/ |
|
056c83d…
|
drh
|
3547 |
blob_trim(&pSender->out); |
|
bbfca4c…
|
drh
|
3548 |
@ <pre style='border: 2px solid blue; padding: 1ex;'> |
|
fc5c7d2…
|
drh
|
3549 |
@ %h(blob_str(&pSender->out)) |
|
fc5c7d2…
|
drh
|
3550 |
@ </pre> |
|
bbfca4c…
|
drh
|
3551 |
blob_reset(&pSender->out); |
|
fc5c7d2…
|
drh
|
3552 |
} |
|
fc5c7d2…
|
drh
|
3553 |
zErr = pSender->zErr; |
|
fc5c7d2…
|
drh
|
3554 |
pSender->zErr = 0; |
|
fc5c7d2…
|
drh
|
3555 |
alert_sender_free(pSender); |
|
fc5c7d2…
|
drh
|
3556 |
return zErr; |
|
fc5c7d2…
|
drh
|
3557 |
} |
|
fc5c7d2…
|
drh
|
3558 |
|
|
fc5c7d2…
|
drh
|
3559 |
|
|
fc5c7d2…
|
drh
|
3560 |
/* |
|
fc5c7d2…
|
drh
|
3561 |
** WEBPAGE: announce |
|
fc5c7d2…
|
drh
|
3562 |
** |
|
fc5c7d2…
|
drh
|
3563 |
** A web-form, available to users with the "Send-Announcement" or "A" |
|
fc5c7d2…
|
drh
|
3564 |
** capability, that allows one to send announcements to whomever |
|
fc5c7d2…
|
drh
|
3565 |
** has subscribed to receive announcements. The administrator can |
|
fc5c7d2…
|
drh
|
3566 |
** also send a message to an arbitrary email address and/or to all |
|
fc5c7d2…
|
drh
|
3567 |
** subscribers regardless of whether or not they have elected to |
|
fc5c7d2…
|
drh
|
3568 |
** receive announcements. |
|
fc5c7d2…
|
drh
|
3569 |
*/ |
|
fc5c7d2…
|
drh
|
3570 |
void announce_page(void){ |
|
bbfca4c…
|
drh
|
3571 |
const char *zAction = "announce"; |
|
bbfca4c…
|
drh
|
3572 |
const char *zName = PD("name",""); |
|
bbfca4c…
|
drh
|
3573 |
/* |
|
bbfca4c…
|
drh
|
3574 |
** Debugging Notes: |
|
bbfca4c…
|
drh
|
3575 |
** |
|
bbfca4c…
|
drh
|
3576 |
** /announce/test1 -> Shows query parameter values |
|
bbfca4c…
|
drh
|
3577 |
** /announce/test2 -> Shows the formatted message but does |
|
bbfca4c…
|
drh
|
3578 |
** not send it. |
|
bbfca4c…
|
drh
|
3579 |
** /announce/test3 -> Sends the message, but also shows |
|
bbfca4c…
|
drh
|
3580 |
** the SMTP transcript. |
|
bbfca4c…
|
drh
|
3581 |
*/ |
|
fc5c7d2…
|
drh
|
3582 |
login_check_credentials(); |
|
fc5c7d2…
|
drh
|
3583 |
if( !g.perm.Announce ){ |
|
fc5c7d2…
|
drh
|
3584 |
login_needed(0); |
|
fc5c7d2…
|
drh
|
3585 |
return; |
|
fc5c7d2…
|
drh
|
3586 |
} |
|
bbfca4c…
|
drh
|
3587 |
if( !g.perm.Setup ){ |
|
bbfca4c…
|
drh
|
3588 |
zName = 0; /* Disable debugging feature for non-admin users */ |
|
bbfca4c…
|
drh
|
3589 |
} |
|
112c713…
|
drh
|
3590 |
style_set_current_feature("alerts"); |
|
bbfca4c…
|
drh
|
3591 |
if( fossil_strcmp(zName,"test1")==0 ){ |
|
fc5c7d2…
|
drh
|
3592 |
/* Visit the /announce/test1 page to see the CGI variables */ |
|
2fa43ef…
|
stephan
|
3593 |
zAction = "announce/test1"; |
|
fc5c7d2…
|
drh
|
3594 |
@ <p style='border: 1px solid black; padding: 1ex;'> |
|
0204f4a…
|
drh
|
3595 |
cgi_print_all(0, 0, 0); |
|
fc5c7d2…
|
drh
|
3596 |
@ </p> |
|
920ace1…
|
drh
|
3597 |
}else if( P("submit")!=0 && cgi_csrf_safe(2) ){ |
|
fc5c7d2…
|
drh
|
3598 |
char *zErr = alert_send_announcement(); |
|
fc5c7d2…
|
drh
|
3599 |
style_header("Announcement Sent"); |
|
fc5c7d2…
|
drh
|
3600 |
if( zErr ){ |
|
056c83d…
|
drh
|
3601 |
@ <h1>Error</h1> |
|
056c83d…
|
drh
|
3602 |
@ <p>The following error was reported by the |
|
056c83d…
|
drh
|
3603 |
@ announcement-sending subsystem: |
|
fc5c7d2…
|
drh
|
3604 |
@ <blockquote><pre> |
|
fc5c7d2…
|
drh
|
3605 |
@ %h(zErr) |
|
fc5c7d2…
|
drh
|
3606 |
@ </pre></blockquote> |
|
fc5c7d2…
|
drh
|
3607 |
}else{ |
|
84d854c…
|
drh
|
3608 |
@ <p>The announcement has been sent. |
|
84d854c…
|
drh
|
3609 |
@ <a href="%h(PD("REQUEST_URI","/"))">Send another</a></p> |
|
fc5c7d2…
|
drh
|
3610 |
} |
|
112c713…
|
drh
|
3611 |
style_finish_page(); |
|
ea64695…
|
wyoung
|
3612 |
return; |
|
ea64695…
|
wyoung
|
3613 |
} else if( !alert_enabled() ){ |
|
ea64695…
|
wyoung
|
3614 |
style_header("Cannot Send Announcement"); |
|
ea64695…
|
wyoung
|
3615 |
@ <p>Either you have no subscribers yet, or email alerts are not yet |
|
ea64695…
|
wyoung
|
3616 |
@ <a href="https://fossil-scm.org/fossil/doc/trunk/www/alerts.md">set up</a> |
|
ea64695…
|
wyoung
|
3617 |
@ for this repository.</p> |
|
fc5c7d2…
|
drh
|
3618 |
return; |
|
fc5c7d2…
|
drh
|
3619 |
} |
|
ea64695…
|
wyoung
|
3620 |
|
|
fc5c7d2…
|
drh
|
3621 |
style_header("Send Announcement"); |
|
4859a91…
|
drh
|
3622 |
alert_submenu_common(); |
|
bbfca4c…
|
drh
|
3623 |
if( fossil_strcmp(zName,"test2")==0 ){ |
|
bbfca4c…
|
drh
|
3624 |
zAction = "announce/test2"; |
|
bbfca4c…
|
drh
|
3625 |
}else if( fossil_strcmp(zName,"test3")==0 ){ |
|
bbfca4c…
|
drh
|
3626 |
zAction = "announce/test3"; |
|
bbfca4c…
|
drh
|
3627 |
} |
|
2fa43ef…
|
stephan
|
3628 |
@ <form method="POST" action="%R/%s(zAction)"> |
|
920ace1…
|
drh
|
3629 |
login_insert_csrf_secret(); |
|
fc5c7d2…
|
drh
|
3630 |
@ <table class="subscribe"> |
|
fc5c7d2…
|
drh
|
3631 |
if( g.perm.Admin ){ |
|
fc5c7d2…
|
drh
|
3632 |
int aa = PB("aa"); |
|
fc5c7d2…
|
drh
|
3633 |
int all = PB("all"); |
|
84d854c…
|
drh
|
3634 |
int aMod = PB("mods"); |
|
fc5c7d2…
|
drh
|
3635 |
const char *aack = aa ? "checked" : ""; |
|
fc5c7d2…
|
drh
|
3636 |
const char *allck = all ? "checked" : ""; |
|
84d854c…
|
drh
|
3637 |
const char *modck = aMod ? "checked" : ""; |
|
fc5c7d2…
|
drh
|
3638 |
@ <tr> |
|
fc5c7d2…
|
drh
|
3639 |
@ <td class="form_label">To:</td> |
|
fc5c7d2…
|
drh
|
3640 |
@ <td><input type="text" name="to" value="%h(PT("to"))" size="30"><br> |
|
fc5c7d2…
|
drh
|
3641 |
@ <label><input type="checkbox" name="aa" %s(aack)> \ |
|
fc5c7d2…
|
drh
|
3642 |
@ All "announcement" subscribers</label> \ |
|
fc5c7d2…
|
drh
|
3643 |
@ <a href="%R/subscribers?only=a" target="_blank">(list)</a><br> |
|
fc5c7d2…
|
drh
|
3644 |
@ <label><input type="checkbox" name="all" %s(allck)> \ |
|
fc5c7d2…
|
drh
|
3645 |
@ All subscribers</label> \ |
|
84d854c…
|
drh
|
3646 |
@ <a href="%R/subscribers" target="_blank">(list)</a><br> |
|
84d854c…
|
drh
|
3647 |
@ <label><input type="checkbox" name="mods" %s(modck)> \ |
|
84d854c…
|
drh
|
3648 |
@ All moderators</label> \ |
|
84d854c…
|
drh
|
3649 |
@ <a href="%R/setup_ulist?with=5" target="_blank">(list)</a><br></td> |
|
fc5c7d2…
|
drh
|
3650 |
@ </tr> |
|
fc5c7d2…
|
drh
|
3651 |
} |
|
fc5c7d2…
|
drh
|
3652 |
@ <tr> |
|
fc5c7d2…
|
drh
|
3653 |
@ <td class="form_label">Subject:</td> |
|
fc5c7d2…
|
drh
|
3654 |
@ <td><input type="text" name="subject" value="%h(PT("subject"))"\ |
|
fc5c7d2…
|
drh
|
3655 |
@ size="80"></td> |
|
fc5c7d2…
|
drh
|
3656 |
@ </tr> |
|
fc5c7d2…
|
drh
|
3657 |
@ <tr> |
|
fc5c7d2…
|
drh
|
3658 |
@ <td class="form_label">Message:</td> |
|
fc5c7d2…
|
drh
|
3659 |
@ <td><textarea name="msg" cols="80" rows="10" wrap="virtual">\ |
|
fc5c7d2…
|
drh
|
3660 |
@ %h(PT("msg"))</textarea> |
|
fc5c7d2…
|
drh
|
3661 |
@ </tr> |
|
fc5c7d2…
|
drh
|
3662 |
@ <tr> |
|
fc5c7d2…
|
drh
|
3663 |
@ <td></td> |
|
bbfca4c…
|
drh
|
3664 |
if( fossil_strcmp(zName,"test2")==0 ){ |
|
84d854c…
|
drh
|
3665 |
@ <td><input type="submit" name="submit" value="Dry Run"> |
|
84d854c…
|
drh
|
3666 |
}else{ |
|
84d854c…
|
drh
|
3667 |
@ <td><input type="submit" name="submit" value="Send Message"> |
|
84d854c…
|
drh
|
3668 |
} |
|
fc5c7d2…
|
drh
|
3669 |
@ </tr> |
|
fc5c7d2…
|
drh
|
3670 |
@ </table> |
|
fc5c7d2…
|
drh
|
3671 |
@ </form> |
|
c98eec2…
|
drh
|
3672 |
if( g.perm.Setup ){ |
|
c98eec2…
|
drh
|
3673 |
@ <hr> |
|
c98eec2…
|
drh
|
3674 |
@ <p>Trouble-shooting Options:</p> |
|
c98eec2…
|
drh
|
3675 |
@ <ol> |
|
c98eec2…
|
drh
|
3676 |
@ <li> <a href="%R/announce">Normal Processing</a> |
|
c98eec2…
|
drh
|
3677 |
@ <li> Only <a href="%R/announce/test1">show POST parameters</a> |
|
c98eec2…
|
drh
|
3678 |
@ - Do not send the announcement. |
|
c98eec2…
|
drh
|
3679 |
@ <li> <a href="%R/announce/test2">Show the email text</a> but do |
|
c98eec2…
|
drh
|
3680 |
@ not actually send it. |
|
c98eec2…
|
drh
|
3681 |
@ <li> Send the message and also <a href="%R/announce/test3">show the |
|
c98eec2…
|
drh
|
3682 |
@ SMTP traffic</a> when using "relay" mode. |
|
c98eec2…
|
drh
|
3683 |
@ </ol> |
|
c98eec2…
|
drh
|
3684 |
} |
|
112c713…
|
drh
|
3685 |
style_finish_page(); |
|
fc5c7d2…
|
drh
|
3686 |
} |