Line data Source code
1 : //this code queries outbound routing information for a user
2 3 : MAILROUTE route_query(LOGGER log, DATABASE* database, char* user){
3 3 : MAILROUTE route = {
4 : .router = NULL,
5 : .argument = NULL
6 : };
7 : int status;
8 :
9 3 : if(!user){
10 0 : return route;
11 : }
12 :
13 3 : status = sqlite3_bind_text(database->query_outbound_router, 1, user, -1, SQLITE_STATIC);
14 3 : if(status != SQLITE_OK){
15 0 : logprintf(log, LOG_ERROR, "Failed to bind user parameter to routing query: %s\n", sqlite3_errmsg(database->conn));
16 0 : sqlite3_reset(database->query_outbound_router);
17 0 : sqlite3_clear_bindings(database->query_outbound_router);
18 0 : return route;
19 : }
20 :
21 3 : switch(sqlite3_step(database->query_outbound_router)){
22 : case SQLITE_ROW:
23 : //copy data
24 : //This works because router is NOT NULL in schema
25 3 : route.router = common_strdup((char*)sqlite3_column_text(database->query_outbound_router, 0));
26 :
27 3 : if(sqlite3_column_text(database->query_outbound_router, 1)){
28 0 : route.argument = common_strdup((char*)sqlite3_column_text(database->query_outbound_router, 1));
29 : }
30 :
31 3 : if(!route.router){
32 0 : logprintf(log, LOG_ERROR, "Failed to allocate memory for ROUTE structure\n");
33 : }
34 3 : break;
35 : case SQLITE_DONE:
36 0 : logprintf(log, LOG_ERROR, "Database contains no outbound router definition for %s\n", user);
37 0 : break;
38 : default:
39 0 : logprintf(log, LOG_WARNING, "Unhandled query return value: %s\n", sqlite3_errmsg(database->conn));
40 0 : break;
41 : }
42 :
43 3 : sqlite3_reset(database->query_outbound_router);
44 3 : sqlite3_clear_bindings(database->query_outbound_router);
45 :
46 3 : return route;
47 : }
48 :
49 163 : void route_free(MAILROUTE* route){
50 163 : if(route){
51 163 : if(route->router){
52 48 : free(route->router);
53 48 : route->router = NULL;
54 : }
55 :
56 163 : if(route->argument){
57 22 : free(route->argument);
58 22 : route->argument = NULL;
59 : }
60 : }
61 163 : }
62 :
63 33 : int route_local_path(LOGGER log, DATABASE* database, MAIL* mail, MAILPATH* current_path){
64 33 : int rv = 0;
65 : USER_DATABASE* user_db;
66 : char path_replacement[SMTP_MAX_PATH_LENGTH];
67 : char forward_path[SMTP_MAX_PATH_LENGTH];
68 :
69 : //reject the path if the path does not have a router table entry
70 : //this check should be redundant, as paths without routers are non-local
71 33 : if(!current_path->route.router){
72 0 : return -1;
73 : }
74 :
75 33 : logprintf(log, LOG_DEBUG, "Inbound router %s (%s) for %s\n", current_path->route.router, current_path->route.argument ? current_path->route.argument:"none", current_path->path);
76 :
77 33 : if(!strcmp(current_path->route.router, "store")){
78 11 : if(!(current_path->route.argument)){
79 2 : logprintf(log, LOG_ERROR, "Path is assigned store router without argument, rejecting transaction\n");
80 : //Fail the transaction permanently
81 2 : rv = -1;
82 : }
83 : else{
84 : //preemptively fail the transaction...
85 9 : rv = -1;
86 :
87 : //check for user existence (as well as a user database path)
88 9 : if(sqlite3_bind_text(database->query_authdata, 1, current_path->route.argument, -1, SQLITE_STATIC) == SQLITE_OK){
89 9 : switch(sqlite3_step(database->query_authdata)){
90 : case SQLITE_ROW:
91 : //check for a user database
92 9 : if(sqlite3_column_text(database->query_authdata, 2)){
93 4 : logprintf(log, LOG_DEBUG, "Fetching user database for user %s (%s)\n", current_path->route.argument, (char*)sqlite3_column_text(database->query_authdata, 2));
94 4 : user_db = database_userdb(log, database, (char*)sqlite3_column_text(database->query_authdata, 2));
95 :
96 4 : if(!user_db){
97 : //try to refresh the user database set
98 3 : database_refresh(log, database);
99 3 : user_db = database_userdb(log, database, (char*)sqlite3_column_text(database->query_authdata, 2));
100 :
101 3 : if(!user_db){
102 : //as last resort, store to master db
103 1 : logprintf(log, LOG_WARNING, "Stored mail for user %s to master instead of defined database\n", current_path->route.argument);
104 1 : rv = mail_store_inbox(log, database->mail_storage.mailbox_master, mail, current_path);
105 : }
106 : else{
107 2 : logprintf(log, LOG_DEBUG, "Storing mail for %s to user database %s after database refresh\n", current_path->route.argument, user_db->file_name);
108 2 : rv = mail_store_inbox(log, user_db->mailbox, mail, current_path);
109 : }
110 : }
111 : else{
112 1 : logprintf(log, LOG_DEBUG, "Storing mail for %s to user database %s\n", current_path->route.argument, user_db->file_name);
113 1 : rv = mail_store_inbox(log, user_db->mailbox, mail, current_path);
114 : }
115 :
116 : }
117 : else{
118 : //simply store to master
119 5 : logprintf(log, LOG_DEBUG, "Storing mail for %s to master database\n", current_path->route.argument);
120 5 : rv = mail_store_inbox(log, database->mail_storage.mailbox_master, mail, current_path);
121 : }
122 9 : break;
123 : case SQLITE_DONE:
124 0 : logprintf(log, LOG_ERROR, "Failed to resolve router argument %s to a user, failing transaction\n", current_path->route.argument);
125 0 : break;
126 : default:
127 0 : logprintf(log, LOG_ERROR, "Failed to execute user info query: %s\n", sqlite3_errmsg(database->conn));
128 0 : break;
129 : }
130 : }
131 : else{
132 0 : logprintf(log, LOG_ERROR, "Failed to bind user to user info query\n");
133 : }
134 :
135 9 : sqlite3_reset(database->query_authdata);
136 9 : sqlite3_clear_bindings(database->query_authdata);
137 :
138 : //if we could not store mail, retry with master
139 9 : if(rv){
140 0 : logprintf(log, LOG_ERROR, "Failed to store mail for %s (%d: %s), retrying one last time with master db\n", current_path->route.argument, rv, sqlite3_errmsg(database->conn));
141 0 : rv = mail_store_inbox(log, database->mail_storage.mailbox_master, mail, current_path);
142 0 : if(rv){
143 0 : logprintf(log, LOG_ERROR, "Failed to store mail: %s\n", sqlite3_errmsg(database->conn));
144 : }
145 : }
146 : }
147 : }
148 22 : else if(!strcmp(current_path->route.router, "redirect")){
149 10 : if(current_path->route.argument){
150 : //FIXME this should be ensured to be properly terminated
151 8 : strncpy(forward_path, current_path->route.argument, sizeof(forward_path) - 1);
152 :
153 : //mangle the envelope recipient according to the route
154 : //we're able to do this and not worry about exploitation from unsanitized input
155 : //because the path parser removes all comments from user-supplied paths
156 8 : strncpy(path_replacement, current_path->path, current_path->delimiter_position);
157 8 : path_replacement[current_path->delimiter_position] = 0;
158 8 : if(common_strrepl(forward_path, sizeof(forward_path), "(to-local)", path_replacement) < 0){
159 0 : logprintf(log, LOG_ERROR, "Failed to replace to-local variable in redirect router\n");
160 : //fail the transaction
161 0 : rv = -1;
162 : }
163 :
164 16 : if(!rv &&
165 8 : common_strrepl(forward_path, sizeof(forward_path), "(to-domain)",
166 : //this ensures that the variable always gets replaced, with an empty string if need be
167 8 : current_path->path + current_path->delimiter_position + (current_path->path[current_path->delimiter_position] ? 1:0) ) < 0){
168 0 : logprintf(log, LOG_ERROR, "Failed to replace to-domain variable in redirect router\n");
169 : //fail the transaction
170 0 : rv = -1;
171 : }
172 :
173 8 : if(!rv){
174 8 : strncpy(path_replacement, mail->reverse_path.path, mail->reverse_path.delimiter_position);
175 8 : path_replacement[mail->reverse_path.delimiter_position] = 0;
176 8 : if(common_strrepl(forward_path, sizeof(forward_path), "(from-local)", path_replacement) < 0){
177 0 : logprintf(log, LOG_ERROR, "Failed to replace from-local variable in redirect router\n");
178 : //fail the transaction
179 0 : rv = -1;
180 : }
181 : }
182 :
183 16 : if(!rv &&
184 8 : common_strrepl(forward_path, sizeof(forward_path), "(from-domain)",
185 8 : mail->reverse_path.path + mail->reverse_path.delimiter_position + (current_path->path[current_path->delimiter_position] ? 1:0)) < 0){
186 0 : logprintf(log, LOG_ERROR, "Failed to replace from-domain variable in redirect router\n");
187 : //fail the transaction
188 0 : rv = -1;
189 : }
190 :
191 : //insert into outbound table
192 8 : if(!rv){
193 8 : rv = mail_store_outbox(log, database->mail_storage.outbox_master, NULL, forward_path, mail);
194 : }
195 : }
196 : else{
197 2 : logprintf(log, LOG_ERROR, "Redirect router without argument, failing transaction\n");
198 : //fail the transaction permanently
199 2 : rv = -1;
200 : }
201 : }
202 12 : else if(!strcmp(current_path->route.router, "handoff")){
203 4 : if(current_path->route.argument){
204 : //insert into outbound table
205 2 : rv = mail_store_outbox(log, database->mail_storage.outbox_master, current_path->route.argument, current_path->path, mail);
206 : }
207 : else{
208 2 : logprintf(log, LOG_ERROR, "Handoff router without argument, failing transaction\n");
209 2 : rv = -1;
210 : }
211 : }
212 8 : else if(!strcmp(current_path->route.router, "reject")){
213 : //this should probably never be reached as the path
214 : //of the user should already have been rejected.
215 : //otherwise, a mail with multiple recipients including
216 : //this one might get bounced on the basis of one
217 : //rejecting recipient
218 0 : rv = -1;
219 : }
220 8 : else if(!strcmp(current_path->route.router, "drop")){
221 : //this one is easy.
222 : }
223 : else{
224 : //TODO call plugins for other routers
225 : }
226 :
227 33 : if(rv>0){
228 0 : logprintf(log, LOG_INFO, "Additional information: %s\n", sqlite3_errmsg(database->conn));
229 : }
230 :
231 33 : return rv;
232 : }
|