Line data Source code
1 : //TODO test this thoroughly
2 85 : int path_parse(LOGGER log, char* pathspec, MAILPATH* path){
3 : //See http://cr.yp.to/smtp/address.html for hints on address parsing
4 85 : bool quotes = false, done_parsing = false, comment = false;
5 85 : unsigned out_pos = 0, in_pos = 0;
6 :
7 : //skip leading spaces
8 85 : for(; isspace(pathspec[0]); pathspec++){
9 : }
10 :
11 85 : logprintf(log, LOG_DEBUG, "Parsing path %s\n", pathspec);
12 :
13 1389 : for(in_pos = 0; !done_parsing && out_pos < (SMTP_MAX_PATH_LENGTH - 1) && pathspec[in_pos]; in_pos++){
14 1306 : if(!comment){
15 1245 : switch(pathspec[in_pos]){
16 : case '@':
17 70 : if(out_pos == 0){
18 : //route syntax. skip until next colon.
19 0 : for(; pathspec[in_pos] && pathspec[in_pos] != ':'; in_pos++){
20 : }
21 0 : if(pathspec[in_pos] != ':'){
22 : //colon was somehow the last character. someone blew this.
23 0 : done_parsing = true;
24 : }
25 : }
26 : else{
27 70 : if(quotes){
28 1 : path->path[out_pos++] = pathspec[in_pos];
29 : }
30 69 : else if(path->delimiter_position == 0 && path->path[path->delimiter_position] != '@'){
31 : //copy to out buffer and update delimiter position
32 68 : path->delimiter_position = out_pos;
33 68 : path->path[out_pos++] = pathspec[in_pos];
34 : }
35 : else{
36 1 : logprintf(log, LOG_WARNING, "Multiple delimiters in path\n");
37 1 : return -1;
38 : }
39 : }
40 69 : break;
41 : case '"':
42 6 : quotes = !quotes;
43 6 : break;
44 : case '\\':
45 : //escape character. add next char to out buffer
46 : //WARNING this can cause an issue when implemented incorrectly.
47 : //if the last character sent is a backslash escaping \0 the
48 : //loop potentially accesses memory out of bounds.
49 : //so, we check for that.
50 : //actually, in this implementation, there are at least
51 : //2 \0 bytes in that case, so this is a non-issue.
52 : //FIXME allow only printable/space characters here
53 0 : if(pathspec[in_pos + 1]){
54 0 : in_pos++;
55 0 : if(isprint(pathspec[in_pos])){
56 0 : path->path[out_pos++] = pathspec[in_pos];
57 : }
58 : }
59 : else{
60 0 : done_parsing = true;
61 0 : break;
62 : }
63 0 : break;
64 : case '(':
65 : //comment delimiter
66 3 : comment = true;
67 3 : break;
68 : case ')':
69 : //comment closed without active comment context
70 1 : logprintf(log, LOG_WARNING, "Path contained illegal parenthesis\n");
71 1 : return -1;
72 : case '<':
73 85 : if(!quotes){
74 : //start mark. ignore it.
75 84 : break;
76 : }
77 : //fall through
78 : case '>':
79 : //FIXME allow only printable nonspace(?) characters here
80 83 : if(!quotes){
81 81 : done_parsing = true;
82 81 : break;
83 : }
84 : //fall through
85 : default:
86 : //copy to out buffer
87 1000 : if(isprint(pathspec[in_pos])){
88 1000 : path->path[out_pos++] = pathspec[in_pos];
89 : }
90 : }
91 : }
92 61 : else if(pathspec[in_pos] == ')'){
93 2 : comment = false;
94 : }
95 : }
96 :
97 83 : path->path[out_pos] = 0;
98 :
99 83 : if(comment){
100 1 : logprintf(log, LOG_WARNING, "Path contains unterminated comment\n");
101 1 : return -1;
102 : }
103 :
104 82 : if(!path->delimiter_position){
105 15 : path->delimiter_position = strlen(path->path);
106 : }
107 :
108 82 : logprintf(log, LOG_DEBUG, "Result is %s, delimiter is at %d\n", path->path, path->delimiter_position);
109 82 : return 0;
110 : }
111 :
112 : // If originating_user is set, this checks if the user may use the path outbound
113 82 : int path_resolve(LOGGER log, MAILPATH* path, DATABASE* database, char* originating_user, bool is_reverse){
114 82 : int status, rv = -1;
115 :
116 : //this early exit should never have to be taken
117 82 : if(path->route.router){
118 0 : logprintf(log, LOG_WARNING, "Taking early exit for path %s, please notify the developers\n", path->path);
119 0 : return 0;
120 : }
121 :
122 82 : status = sqlite3_bind_text(database->query_address, 1, path->path, -1, SQLITE_STATIC);
123 82 : if(status == SQLITE_OK){
124 : do{
125 82 : status = sqlite3_step(database->query_address);
126 82 : if(status == SQLITE_ROW){
127 45 : if(originating_user || is_reverse){
128 : //Test whether the originating_user may user the supplied path outbound.
129 : //This implies traversing all entries and testing the following conditions
130 : // 1. Router must be 'store'
131 : // 2. Route must be the originating user
132 : // Else, try the next entry
133 1 : if(originating_user &&
134 0 : (strcmp((char*)sqlite3_column_text(database->query_address, 0), "store")
135 0 : || strcmp(originating_user, (char*)sqlite3_column_text(database->query_address, 1)))){
136 : // Falling through to SQLITE_DONE here implies a non-local origin while routers for this adress are set
137 : // (just not for this user). The defined router will then fail the address, the any router will accept it
138 0 : continue;
139 : }
140 :
141 : //Continuing here if no originating_user given or the current routing information
142 : //applies to the originating_user
143 : }
144 :
145 : //heap-copy the routing information
146 45 : path->route.router = common_strdup((char*)sqlite3_column_text(database->query_address, 0));
147 45 : if(sqlite3_column_text(database->query_address, 1)){
148 22 : path->route.argument = common_strdup((char*)sqlite3_column_text(database->query_address, 1));
149 : }
150 :
151 45 : if(!path->route.router){
152 0 : logprintf(log, LOG_ERROR, "Failed to allocate storage for routing data\n");
153 : //fail temporarily
154 0 : rv = -1;
155 0 : break;
156 : }
157 :
158 : //check for reject
159 45 : if(!strcmp(path->route.router, "reject")){
160 5 : rv = 1;
161 5 : break;
162 : }
163 :
164 : //all is well
165 40 : rv = 0;
166 40 : break;
167 : }
168 37 : } while(status == SQLITE_ROW);
169 :
170 82 : switch(status){
171 : case SQLITE_ROW:
172 : //already handled during loop
173 45 : break;
174 : case SQLITE_DONE:
175 37 : logprintf(log, LOG_INFO, "No address match found\n");
176 : //continue with this path marked as non-local
177 37 : rv = 0;
178 37 : break;
179 : default:
180 0 : logprintf(log, LOG_ERROR, "Failed to query wildcard: %s\n", sqlite3_errmsg(database->conn));
181 0 : rv = -1;
182 0 : break;
183 : }
184 : }
185 : else{
186 0 : logprintf(log, LOG_ERROR, "Failed to bind search parameter: %s\n", sqlite3_errmsg(database->conn));
187 0 : rv = -1;
188 : }
189 :
190 82 : sqlite3_reset(database->query_address);
191 82 : sqlite3_clear_bindings(database->query_address);
192 :
193 : // Calling contract
194 : // 0 -> Accept path
195 : // 1 -> Reject path (500), if possible use router argument
196 : // * -> Reject path (400)
197 82 : return rv;
198 : }
199 :
200 148 : void path_reset(MAILPATH* path){
201 148 : MAILPATH reset_path = {
202 : .delimiter_position = 0,
203 : .in_transaction = false,
204 : .path = "",
205 : .route = {
206 : .router = NULL,
207 : .argument = NULL
208 : }
209 : };
210 :
211 148 : route_free(&(path->route));
212 :
213 148 : *path = reset_path;
214 148 : }
|