/src/mosquitto/common/misc_mosq.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | Copyright (c) 2009-2021 Roger Light <roger@atchoo.org> |
3 | | |
4 | | All rights reserved. This program and the accompanying materials |
5 | | are made available under the terms of the Eclipse Public License 2.0 |
6 | | and Eclipse Distribution License v1.0 which accompany this distribution. |
7 | | |
8 | | The Eclipse Public License is available at |
9 | | https://www.eclipse.org/legal/epl-2.0/ |
10 | | and the Eclipse Distribution License is available at |
11 | | http://www.eclipse.org/org/documents/edl-v10.php. |
12 | | |
13 | | SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
14 | | |
15 | | Contributors: |
16 | | Roger Light - initial implementation and documentation. |
17 | | */ |
18 | | |
19 | | /* This contains general purpose utility functions that are not specific to |
20 | | * Mosquitto/MQTT features. */ |
21 | | |
22 | | #include "config.h" |
23 | | |
24 | | #include <ctype.h> |
25 | | #include <errno.h> |
26 | | #include <limits.h> |
27 | | #include <stdbool.h> |
28 | | #include <stdio.h> |
29 | | #include <stdlib.h> |
30 | | #include <string.h> |
31 | | |
32 | | #ifdef WIN32 |
33 | | # include <winsock2.h> |
34 | | # include <aclapi.h> |
35 | | # include <io.h> |
36 | | # include <lmcons.h> |
37 | | # include <fcntl.h> |
38 | | # define PATH_MAX MAX_PATH |
39 | | #else |
40 | | # include <sys/stat.h> |
41 | | # include <unistd.h> |
42 | | #endif |
43 | | |
44 | | #include "misc_mosq.h" |
45 | | #include "logging_mosq.h" |
46 | | |
47 | | |
48 | | FILE *mosquitto__fopen(const char *path, const char *mode, bool restrict_read) |
49 | 0 | { |
50 | | #ifdef WIN32 |
51 | | char buf[4096]; |
52 | | int rc; |
53 | | int flags = 0; |
54 | | |
55 | | rc = ExpandEnvironmentStringsA(path, buf, 4096); |
56 | | if(rc == 0 || rc > 4096){ |
57 | | return NULL; |
58 | | }else{ |
59 | | if(restrict_read){ |
60 | | HANDLE hfile; |
61 | | SECURITY_ATTRIBUTES sec; |
62 | | EXPLICIT_ACCESS_A ea; |
63 | | PACL pacl = NULL; |
64 | | char username[UNLEN + 1]; |
65 | | DWORD ulen = UNLEN; |
66 | | SECURITY_DESCRIPTOR sd; |
67 | | DWORD dwCreationDisposition; |
68 | | int fd; |
69 | | FILE *fptr; |
70 | | |
71 | | switch(mode[0]){ |
72 | | case 'a': |
73 | | dwCreationDisposition = OPEN_ALWAYS; |
74 | | flags = _O_APPEND; |
75 | | break; |
76 | | case 'r': |
77 | | dwCreationDisposition = OPEN_EXISTING; |
78 | | flags = _O_RDONLY; |
79 | | break; |
80 | | case 'w': |
81 | | dwCreationDisposition = CREATE_ALWAYS; |
82 | | break; |
83 | | default: |
84 | | return NULL; |
85 | | } |
86 | | |
87 | | GetUserNameA(username, &ulen); |
88 | | if(!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)){ |
89 | | return NULL; |
90 | | } |
91 | | BuildExplicitAccessWithNameA(&ea, username, GENERIC_ALL, SET_ACCESS, NO_INHERITANCE); |
92 | | if(SetEntriesInAclA(1, &ea, NULL, &pacl) != ERROR_SUCCESS){ |
93 | | return NULL; |
94 | | } |
95 | | if(!SetSecurityDescriptorDacl(&sd, TRUE, pacl, FALSE)){ |
96 | | LocalFree(pacl); |
97 | | return NULL; |
98 | | } |
99 | | |
100 | | memset(&sec, 0, sizeof(sec)); |
101 | | sec.nLength = sizeof(SECURITY_ATTRIBUTES); |
102 | | sec.bInheritHandle = FALSE; |
103 | | sec.lpSecurityDescriptor = &sd; |
104 | | |
105 | | hfile = CreateFileA(buf, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, |
106 | | &sec, |
107 | | dwCreationDisposition, |
108 | | FILE_ATTRIBUTE_NORMAL, |
109 | | NULL); |
110 | | |
111 | | LocalFree(pacl); |
112 | | |
113 | | fd = _open_osfhandle((intptr_t)hfile, flags); |
114 | | if(fd < 0){ |
115 | | return NULL; |
116 | | } |
117 | | |
118 | | fptr = _fdopen(fd, mode); |
119 | | if(!fptr){ |
120 | | _close(fd); |
121 | | return NULL; |
122 | | } |
123 | | if(mode[0] == 'a'){ |
124 | | fseek(fptr, 0, SEEK_END); |
125 | | } |
126 | | return fptr; |
127 | | |
128 | | }else { |
129 | | return fopen(buf, mode); |
130 | | } |
131 | | } |
132 | | #else |
133 | 0 | FILE *fptr; |
134 | 0 | struct stat statbuf; |
135 | |
|
136 | 0 | if(restrict_read){ |
137 | 0 | mode_t old_mask; |
138 | |
|
139 | 0 | old_mask = umask(0077); |
140 | 0 | fptr = fopen(path, mode); |
141 | 0 | umask(old_mask); |
142 | 0 | }else{ |
143 | 0 | fptr = fopen(path, mode); |
144 | 0 | } |
145 | 0 | if(!fptr) return NULL; |
146 | | |
147 | 0 | if(fstat(fileno(fptr), &statbuf) < 0){ |
148 | 0 | fclose(fptr); |
149 | 0 | return NULL; |
150 | 0 | } |
151 | | |
152 | 0 | if(!S_ISREG(statbuf.st_mode) && !S_ISLNK(statbuf.st_mode)){ |
153 | | #ifdef WITH_BROKER |
154 | | log__printf(NULL, MOSQ_LOG_ERR, "Error: %s is not a file.", path); |
155 | | #endif |
156 | 0 | fclose(fptr); |
157 | 0 | return NULL; |
158 | 0 | } |
159 | 0 | return fptr; |
160 | 0 | #endif |
161 | 0 | } |
162 | | |
163 | | |
164 | | char *misc__trimblanks(char *str) |
165 | 0 | { |
166 | 0 | char *endptr; |
167 | |
|
168 | 0 | if(str == NULL) return NULL; |
169 | | |
170 | 0 | while(isspace(str[0])){ |
171 | 0 | str++; |
172 | 0 | } |
173 | 0 | endptr = &str[strlen(str)-1]; |
174 | 0 | while(endptr > str && isspace(endptr[0])){ |
175 | 0 | endptr[0] = '\0'; |
176 | 0 | endptr--; |
177 | 0 | } |
178 | 0 | return str; |
179 | 0 | } |
180 | | |
181 | | |
182 | | char *fgets_extending(char **buf, int *buflen, FILE *stream) |
183 | 0 | { |
184 | 0 | char *rc; |
185 | 0 | char endchar; |
186 | 0 | int offset = 0; |
187 | 0 | char *newbuf; |
188 | 0 | size_t len; |
189 | |
|
190 | 0 | if(stream == NULL || buf == NULL || buflen == NULL || *buflen < 1){ |
191 | 0 | return NULL; |
192 | 0 | } |
193 | | |
194 | 0 | do{ |
195 | 0 | rc = fgets(&((*buf)[offset]), (*buflen)-offset, stream); |
196 | 0 | if(feof(stream) || rc == NULL){ |
197 | 0 | return rc; |
198 | 0 | } |
199 | | |
200 | 0 | len = strlen(*buf); |
201 | 0 | if(len == 0){ |
202 | 0 | return rc; |
203 | 0 | } |
204 | 0 | endchar = (*buf)[len-1]; |
205 | 0 | if(endchar == '\n'){ |
206 | 0 | return rc; |
207 | 0 | } |
208 | | /* No EOL char found, so extend buffer */ |
209 | 0 | offset = (*buflen)-1; |
210 | 0 | *buflen += 1000; |
211 | 0 | newbuf = realloc(*buf, (size_t)*buflen); |
212 | 0 | if(!newbuf){ |
213 | 0 | return NULL; |
214 | 0 | } |
215 | 0 | *buf = newbuf; |
216 | 0 | }while(1); |
217 | 0 | } |
218 | | |
219 | | |
220 | | #define INVOKE_LOG_FN(format, ...) \ |
221 | 0 | do{ \ |
222 | 0 | if(log_fn){ \ |
223 | 0 | int tmp_err_no = errno; \ |
224 | 0 | char msg[2*PATH_MAX]; \ |
225 | 0 | snprintf(msg, sizeof(msg), (format), __VA_ARGS__); \ |
226 | 0 | msg[sizeof(msg)-1] = '\0'; \ |
227 | 0 | (*log_fn)(msg); \ |
228 | 0 | errno = tmp_err_no; \ |
229 | 0 | } \ |
230 | 0 | }while (0) |
231 | | |
232 | | int mosquitto_write_file(const char* target_path, bool restrict_read, int (*write_fn)(FILE* fptr, void* user_data), void* user_data, void (*log_fn)(const char* msg)) |
233 | 0 | { |
234 | 0 | int rc = 0; |
235 | 0 | FILE *fptr = NULL; |
236 | 0 | char tmp_file_path[PATH_MAX]; |
237 | |
|
238 | 0 | if(!target_path || !write_fn){ |
239 | 0 | return MOSQ_ERR_INVAL; |
240 | 0 | } |
241 | | |
242 | 0 | rc = snprintf(tmp_file_path, PATH_MAX, "%s.new", target_path); |
243 | 0 | if(rc < 0 || rc >= PATH_MAX){ |
244 | 0 | return MOSQ_ERR_INVAL; |
245 | 0 | } |
246 | | |
247 | 0 | #ifndef WIN32 |
248 | | /** |
249 | | * |
250 | | * If a system lost power during the rename operation at the |
251 | | * end of this file the filesystem could potentially be left |
252 | | * with a directory that looks like this after powerup: |
253 | | * |
254 | | * 24094 -rw-r--r-- 2 root root 4099 May 30 16:27 mosquitto.db |
255 | | * 24094 -rw-r--r-- 2 root root 4099 May 30 16:27 mosquitto.db.new |
256 | | * |
257 | | * The 24094 shows that mosquitto.db.new is hard-linked to the |
258 | | * same file as mosquitto.db. If fopen(outfile, "wb") is naively |
259 | | * called then mosquitto.db will be truncated and the database |
260 | | * potentially corrupted. |
261 | | * |
262 | | * Any existing mosquitto.db.new file must be removed prior to |
263 | | * opening to guarantee that it is not hard-linked to |
264 | | * mosquitto.db. |
265 | | * |
266 | | */ |
267 | 0 | if(unlink(tmp_file_path) && errno != ENOENT){ |
268 | 0 | INVOKE_LOG_FN("unable to remove stale tmp file %s, error %s", tmp_file_path, strerror(errno)); |
269 | 0 | return MOSQ_ERR_INVAL; |
270 | 0 | } |
271 | 0 | #endif |
272 | | |
273 | 0 | fptr = mosquitto__fopen(tmp_file_path, "wb", restrict_read); |
274 | 0 | if(fptr == NULL){ |
275 | 0 | INVOKE_LOG_FN("unable to open %s for writing, error %s", tmp_file_path, strerror(errno)); |
276 | 0 | return MOSQ_ERR_INVAL; |
277 | 0 | } |
278 | | |
279 | 0 | if((rc = (*write_fn)(fptr, user_data)) != MOSQ_ERR_SUCCESS){ |
280 | 0 | goto error; |
281 | 0 | } |
282 | | |
283 | 0 | rc = MOSQ_ERR_ERRNO; |
284 | 0 | #ifndef WIN32 |
285 | | /** |
286 | | * |
287 | | * Closing a file does not guarantee that the contents are |
288 | | * written to disk. Need to flush to send data from app to OS |
289 | | * buffers, then fsync to deliver data from OS buffers to disk |
290 | | * (as well as disk hardware permits). |
291 | | * |
292 | | * man close (http://linux.die.net/man/2/close, 2016-06-20): |
293 | | * |
294 | | * "successful close does not guarantee that the data has |
295 | | * been successfully saved to disk, as the kernel defers |
296 | | * writes. It is not common for a filesystem to flush |
297 | | * the buffers when the stream is closed. If you need |
298 | | * to be sure that the data is physically stored, use |
299 | | * fsync(2). (It will depend on the disk hardware at this |
300 | | * point." |
301 | | * |
302 | | * This guarantees that the new state file will not overwrite |
303 | | * the old state file before its contents are valid. |
304 | | * |
305 | | */ |
306 | 0 | if(fflush(fptr) != 0 && errno != EINTR){ |
307 | 0 | INVOKE_LOG_FN("unable to flush %s, error %s", tmp_file_path, strerror(errno)); |
308 | 0 | goto error; |
309 | 0 | } |
310 | | |
311 | 0 | if(fsync(fileno(fptr)) != 0 && errno != EINTR){ |
312 | 0 | INVOKE_LOG_FN("unable to sync %s to disk, error %s", tmp_file_path, strerror(errno)); |
313 | 0 | goto error; |
314 | 0 | } |
315 | 0 | #endif |
316 | | |
317 | 0 | if(fclose(fptr) != 0){ |
318 | 0 | INVOKE_LOG_FN("unable to close %s on disk, error %s", tmp_file_path, strerror(errno)); |
319 | 0 | fptr = NULL; |
320 | 0 | goto error; |
321 | 0 | } |
322 | 0 | fptr = NULL; |
323 | |
|
324 | | #ifdef WIN32 |
325 | | if(remove(target_path) != 0 && errno != ENOENT){ |
326 | | INVOKE_LOG_FN("unable to remove %s on disk, error %s", target_path, strerror(errno)); |
327 | | goto error; |
328 | | } |
329 | | #endif |
330 | |
|
331 | 0 | if(rename(tmp_file_path, target_path) != 0){ |
332 | 0 | INVOKE_LOG_FN("unable to replace %s by tmp file %s, error %s", target_path, tmp_file_path, strerror(errno)); |
333 | 0 | goto error; |
334 | 0 | } |
335 | 0 | return MOSQ_ERR_SUCCESS; |
336 | | |
337 | 0 | error: |
338 | 0 | if(fptr){ |
339 | 0 | fclose(fptr); |
340 | 0 | unlink(tmp_file_path); |
341 | 0 | } |
342 | 0 | return MOSQ_ERR_ERRNO; |
343 | 0 | } |