/src/mosquitto/libcommon/file_common.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 <pwd.h> |
42 | | # include <grp.h> |
43 | | # include <unistd.h> |
44 | | # include <fcntl.h> |
45 | | #endif |
46 | | |
47 | | #include "mosquitto.h" |
48 | | |
49 | | FILE *mosquitto_fopen(const char *path, const char *mode, bool restrict_read) |
50 | 11.4k | { |
51 | | #ifdef WIN32 |
52 | | char buf[4096]; |
53 | | int rc; |
54 | | int flags = 0; |
55 | | |
56 | | rc = ExpandEnvironmentStringsA(path, buf, 4096); |
57 | | if(rc == 0 || rc > 4096){ |
58 | | return NULL; |
59 | | }else{ |
60 | | if(restrict_read){ |
61 | | HANDLE hfile; |
62 | | SECURITY_ATTRIBUTES sec; |
63 | | EXPLICIT_ACCESS_A ea; |
64 | | PACL pacl = NULL; |
65 | | char username[UNLEN + 1]; |
66 | | DWORD ulen = UNLEN; |
67 | | SECURITY_DESCRIPTOR sd; |
68 | | DWORD dwCreationDisposition; |
69 | | DWORD dwShareMode; |
70 | | int fd; |
71 | | FILE *fptr; |
72 | | |
73 | | switch(mode[0]){ |
74 | | case 'a': |
75 | | dwCreationDisposition = OPEN_ALWAYS; |
76 | | dwShareMode = GENERIC_WRITE; |
77 | | flags = _O_APPEND; |
78 | | break; |
79 | | case 'r': |
80 | | dwCreationDisposition = OPEN_EXISTING; |
81 | | dwShareMode = GENERIC_READ; |
82 | | flags = _O_RDONLY; |
83 | | break; |
84 | | case 'w': |
85 | | dwCreationDisposition = CREATE_ALWAYS; |
86 | | dwShareMode = GENERIC_WRITE; |
87 | | break; |
88 | | default: |
89 | | return NULL; |
90 | | } |
91 | | if(mode[1] == '+'){ |
92 | | dwShareMode = GENERIC_READ | GENERIC_WRITE; |
93 | | } |
94 | | |
95 | | GetUserNameA(username, &ulen); |
96 | | if(!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)){ |
97 | | return NULL; |
98 | | } |
99 | | BuildExplicitAccessWithNameA(&ea, username, GENERIC_ALL, SET_ACCESS, NO_INHERITANCE); |
100 | | if(SetEntriesInAclA(1, &ea, NULL, &pacl) != ERROR_SUCCESS){ |
101 | | return NULL; |
102 | | } |
103 | | if(!SetSecurityDescriptorDacl(&sd, TRUE, pacl, FALSE)){ |
104 | | LocalFree(pacl); |
105 | | return NULL; |
106 | | } |
107 | | |
108 | | memset(&sec, 0, sizeof(sec)); |
109 | | sec.nLength = sizeof(SECURITY_ATTRIBUTES); |
110 | | sec.bInheritHandle = FALSE; |
111 | | sec.lpSecurityDescriptor = &sd; |
112 | | |
113 | | hfile = CreateFileA(buf, dwShareMode, FILE_SHARE_READ, |
114 | | &sec, |
115 | | dwCreationDisposition, |
116 | | FILE_ATTRIBUTE_NORMAL, |
117 | | NULL); |
118 | | |
119 | | LocalFree(pacl); |
120 | | |
121 | | fd = _open_osfhandle((intptr_t)hfile, flags); |
122 | | if(fd < 0){ |
123 | | return NULL; |
124 | | } |
125 | | |
126 | | fptr = _fdopen(fd, mode); |
127 | | if(!fptr){ |
128 | | _close(fd); |
129 | | return NULL; |
130 | | } |
131 | | if(mode[0] == 'a'){ |
132 | | fseek(fptr, 0, SEEK_END); |
133 | | } |
134 | | return fptr; |
135 | | |
136 | | }else { |
137 | | return fopen(buf, mode); |
138 | | } |
139 | | } |
140 | | #else |
141 | 11.4k | FILE *fptr; |
142 | 11.4k | struct stat statbuf; |
143 | | |
144 | 11.4k | if(restrict_read){ |
145 | 3.24k | mode_t old_mask; |
146 | | |
147 | 3.24k | old_mask = umask(0077); |
148 | | |
149 | 3.24k | int open_flags = O_NOFOLLOW; |
150 | 9.95k | for(size_t i = 0; i<strlen(mode); i++){ |
151 | 6.70k | if(mode[i] == 'r'){ |
152 | 3.24k | open_flags |= O_RDONLY; |
153 | 3.46k | }else if(mode[i] == 'w'){ |
154 | 1 | open_flags |= O_WRONLY; |
155 | 1 | open_flags |= (O_TRUNC | O_CREAT | O_EXCL); |
156 | | |
157 | 3.46k | }else if(mode[i] == 'a'){ |
158 | 0 | open_flags |= O_WRONLY; |
159 | 0 | open_flags |= (O_APPEND | O_CREAT); |
160 | 3.46k | }else if(mode[i] == 't'){ |
161 | 3.24k | }else if(mode[i] == 'b'){ |
162 | 223 | }else if(mode[i] == '+'){ |
163 | 223 | open_flags |= O_RDWR; |
164 | 223 | } |
165 | 6.70k | } |
166 | 3.24k | int fd = open(path, open_flags, 0600); |
167 | 3.24k | if(fd < 0) return NULL; |
168 | 3.24k | fptr = fdopen(fd, mode); |
169 | | |
170 | 3.24k | umask(old_mask); |
171 | 8.16k | }else{ |
172 | 8.16k | fptr = fopen(path, mode); |
173 | 8.16k | } |
174 | 11.4k | if(!fptr) return NULL; |
175 | | |
176 | 11.4k | if(fstat(fileno(fptr), &statbuf) < 0){ |
177 | 0 | fclose(fptr); |
178 | 0 | return NULL; |
179 | 0 | } |
180 | | |
181 | 11.4k | if(restrict_read){ |
182 | 3.24k | if(statbuf.st_mode & S_IRWXO){ |
183 | 0 | fprintf(stderr, |
184 | 0 | "Warning: File %s has world readable permissions. Future versions will refuse to load this file.\n" |
185 | 0 | "To fix this, use `chmod 0700 %s`.\n", |
186 | 0 | path, path); |
187 | | #if 0 |
188 | | return NULL; |
189 | | #endif |
190 | 0 | } |
191 | 3.24k | if(statbuf.st_uid != getuid()){ |
192 | 0 | char buf[4096]; |
193 | 0 | struct passwd pw, *result; |
194 | |
|
195 | 0 | getpwuid_r(getuid(), &pw, buf, sizeof(buf), &result); |
196 | 0 | if(result){ |
197 | 0 | fprintf(stderr, |
198 | 0 | "Warning: File %s owner is not %s. Future versions will refuse to load this file." |
199 | 0 | "To fix this, use `chown %s %s`.\n", |
200 | 0 | path, result->pw_name, result->pw_name, path); |
201 | 0 | } |
202 | | #if 0 |
203 | | // Future version |
204 | | return NULL; |
205 | | #endif |
206 | 0 | } |
207 | 3.24k | if(statbuf.st_gid != getgid()){ |
208 | 0 | char buf[4096]; |
209 | 0 | struct group grp, *result; |
210 | |
|
211 | 0 | if(getgrgid_r(getgid(), &grp, buf, sizeof(buf), &result) == 0){ |
212 | 0 | fprintf(stderr, |
213 | 0 | "Warning: File %s group is not %s. Future versions will refuse to load this file.\n", |
214 | 0 | path, result->gr_name); |
215 | 0 | } |
216 | | #if 0 |
217 | | // Future version |
218 | | return NULL |
219 | | #endif |
220 | 0 | } |
221 | 3.24k | } |
222 | | |
223 | 11.4k | if(!S_ISREG(statbuf.st_mode)){ |
224 | 0 | fprintf(stderr, "Error: %s is not a file.", path); |
225 | 0 | fclose(fptr); |
226 | 0 | return NULL; |
227 | 0 | } |
228 | 11.4k | return fptr; |
229 | 11.4k | #endif |
230 | 11.4k | } |
231 | | |
232 | | |
233 | | char *mosquitto_trimblanks(char *str) |
234 | 347k | { |
235 | 347k | char *endptr; |
236 | | |
237 | 347k | if(str == NULL) return NULL; |
238 | | |
239 | 347k | while(isspace(str[0])){ |
240 | 67.7k | str++; |
241 | 67.7k | } |
242 | 347k | endptr = &str[strlen(str)-1]; |
243 | 376k | while(endptr > str && isspace(endptr[0])){ |
244 | 28.0k | endptr[0] = '\0'; |
245 | 28.0k | endptr--; |
246 | 28.0k | } |
247 | 347k | return str; |
248 | 347k | } |
249 | | |
250 | | |
251 | | char *mosquitto_fgets(char **buf, int *buflen, FILE *stream) |
252 | 983k | { |
253 | 983k | char *rc; |
254 | 983k | char endchar; |
255 | 983k | int offset = 0; |
256 | 983k | char *newbuf; |
257 | 983k | size_t len; |
258 | | |
259 | 983k | if(stream == NULL || buf == NULL || buflen == NULL || *buflen < 1){ |
260 | 0 | return NULL; |
261 | 0 | } |
262 | | |
263 | 1.03M | do{ |
264 | 1.03M | rc = fgets(&((*buf)[offset]), (*buflen)-offset, stream); |
265 | 1.03M | if(feof(stream) || rc == NULL){ |
266 | 11.9k | return rc; |
267 | 11.9k | } |
268 | | |
269 | 1.02M | len = strlen(*buf); |
270 | 1.02M | if(len == 0){ |
271 | 1.59k | return rc; |
272 | 1.59k | } |
273 | 1.02M | endchar = (*buf)[len-1]; |
274 | 1.02M | if(endchar == '\n'){ |
275 | 964k | return rc; |
276 | 964k | } |
277 | 56.8k | if((int)(len+1) < *buflen){ |
278 | | /* Embedded nulls, invalid string */ |
279 | 5.82k | return NULL; |
280 | 5.82k | } |
281 | | |
282 | | /* No EOL char found, so extend buffer */ |
283 | 51.0k | offset = (*buflen)-1; |
284 | 51.0k | *buflen += 1000; |
285 | 51.0k | newbuf = realloc(*buf, (size_t)*buflen); |
286 | 51.0k | if(!newbuf){ |
287 | 0 | return NULL; |
288 | 0 | } |
289 | 51.0k | *buf = newbuf; |
290 | 51.0k | }while(1); |
291 | 983k | } |
292 | | |
293 | | |
294 | | #define INVOKE_LOG_FN(format, ...) \ |
295 | 0 | do{ \ |
296 | 0 | if(log_fn){ \ |
297 | 0 | int tmp_err_no = errno; \ |
298 | 0 | char msg[2*PATH_MAX]; \ |
299 | 0 | snprintf(msg, sizeof(msg), (format), __VA_ARGS__); \ |
300 | 0 | msg[sizeof(msg)-1] = '\0'; \ |
301 | 0 | (*log_fn)(msg); \ |
302 | 0 | errno = tmp_err_no; \ |
303 | 0 | } \ |
304 | 0 | }while (0) |
305 | | |
306 | | 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)) |
307 | 1 | { |
308 | 1 | int rc = 0; |
309 | 1 | FILE *fptr = NULL; |
310 | 1 | char tmp_file_path[PATH_MAX]; |
311 | | |
312 | 1 | if(!target_path || !write_fn){ |
313 | 0 | return MOSQ_ERR_INVAL; |
314 | 0 | } |
315 | | |
316 | 1 | rc = snprintf(tmp_file_path, PATH_MAX, "%s.new", target_path); |
317 | 1 | if(rc < 0 || rc >= PATH_MAX){ |
318 | 0 | return MOSQ_ERR_INVAL; |
319 | 0 | } |
320 | | |
321 | 1 | #ifndef WIN32 |
322 | | /** |
323 | | * |
324 | | * If a system lost power during the rename operation at the |
325 | | * end of this file the filesystem could potentially be left |
326 | | * with a directory that looks like this after powerup: |
327 | | * |
328 | | * 24094 -rw-r--r-- 2 root root 4099 May 30 16:27 mosquitto.db |
329 | | * 24094 -rw-r--r-- 2 root root 4099 May 30 16:27 mosquitto.db.new |
330 | | * |
331 | | * The 24094 shows that mosquitto.db.new is hard-linked to the |
332 | | * same file as mosquitto.db. If fopen(outfile, "wb") is naively |
333 | | * called then mosquitto.db will be truncated and the database |
334 | | * potentially corrupted. |
335 | | * |
336 | | * Any existing mosquitto.db.new file must be removed prior to |
337 | | * opening to guarantee that it is not hard-linked to |
338 | | * mosquitto.db. |
339 | | * |
340 | | */ |
341 | 1 | if(unlink(tmp_file_path) && errno != ENOENT){ |
342 | 0 | INVOKE_LOG_FN("unable to remove stale tmp file %s, error %s", tmp_file_path, strerror(errno)); |
343 | 0 | return MOSQ_ERR_INVAL; |
344 | 0 | } |
345 | 1 | #endif |
346 | | |
347 | 1 | fptr = mosquitto_fopen(tmp_file_path, "wb", restrict_read); |
348 | 1 | if(fptr == NULL){ |
349 | 0 | INVOKE_LOG_FN("unable to open %s for writing, error %s", tmp_file_path, strerror(errno)); |
350 | 0 | return MOSQ_ERR_INVAL; |
351 | 0 | } |
352 | | |
353 | 1 | if((rc = (*write_fn)(fptr, user_data)) != MOSQ_ERR_SUCCESS){ |
354 | 0 | goto error; |
355 | 0 | } |
356 | | |
357 | 1 | rc = MOSQ_ERR_ERRNO; |
358 | 1 | #ifndef WIN32 |
359 | | /** |
360 | | * |
361 | | * Closing a file does not guarantee that the contents are |
362 | | * written to disk. Need to flush to send data from app to OS |
363 | | * buffers, then fsync to deliver data from OS buffers to disk |
364 | | * (as well as disk hardware permits). |
365 | | * |
366 | | * man close (http://linux.die.net/man/2/close, 2016-06-20): |
367 | | * |
368 | | * "successful close does not guarantee that the data has |
369 | | * been successfully saved to disk, as the kernel defers |
370 | | * writes. It is not common for a filesystem to flush |
371 | | * the buffers when the stream is closed. If you need |
372 | | * to be sure that the data is physically stored, use |
373 | | * fsync(2). (It will depend on the disk hardware at this |
374 | | * point." |
375 | | * |
376 | | * This guarantees that the new state file will not overwrite |
377 | | * the old state file before its contents are valid. |
378 | | * |
379 | | */ |
380 | 1 | if(fflush(fptr) != 0 && errno != EINTR){ |
381 | 0 | INVOKE_LOG_FN("unable to flush %s, error %s", tmp_file_path, strerror(errno)); |
382 | 0 | goto error; |
383 | 0 | } |
384 | | |
385 | 1 | if(fsync(fileno(fptr)) != 0 && errno != EINTR){ |
386 | 0 | INVOKE_LOG_FN("unable to sync %s to disk, error %s", tmp_file_path, strerror(errno)); |
387 | 0 | goto error; |
388 | 0 | } |
389 | 1 | #endif |
390 | | |
391 | 1 | if(fclose(fptr) != 0){ |
392 | 0 | INVOKE_LOG_FN("unable to close %s on disk, error %s", tmp_file_path, strerror(errno)); |
393 | 0 | fptr = NULL; |
394 | 0 | goto error; |
395 | 0 | } |
396 | 1 | fptr = NULL; |
397 | | |
398 | | #ifdef WIN32 |
399 | | if(remove(target_path) != 0 && errno != ENOENT){ |
400 | | INVOKE_LOG_FN("unable to remove %s on disk, error %s", target_path, strerror(errno)); |
401 | | goto error; |
402 | | } |
403 | | #endif |
404 | | |
405 | 1 | if(rename(tmp_file_path, target_path) != 0){ |
406 | 0 | INVOKE_LOG_FN("unable to replace %s by tmp file %s, error %s", target_path, tmp_file_path, strerror(errno)); |
407 | 0 | goto error; |
408 | 0 | } |
409 | 1 | return MOSQ_ERR_SUCCESS; |
410 | | |
411 | 0 | error: |
412 | 0 | if(fptr){ |
413 | 0 | fclose(fptr); |
414 | 0 | unlink(tmp_file_path); |
415 | 0 | } |
416 | 0 | return MOSQ_ERR_ERRNO; |
417 | 1 | } |
418 | | |
419 | | |
420 | | int mosquitto_read_file(const char *file, char **buf, size_t *buflen) |
421 | 0 | { |
422 | 0 | FILE *fptr; |
423 | 0 | long l; |
424 | 0 | size_t buflen_i; |
425 | |
|
426 | 0 | *buf = NULL; |
427 | 0 | fptr = fopen(file, "rt"); |
428 | 0 | if(fptr == NULL){ |
429 | 0 | return MOSQ_ERR_ERRNO; |
430 | 0 | } |
431 | | |
432 | 0 | fseek(fptr, 0, SEEK_END); |
433 | 0 | l = ftell(fptr); |
434 | 0 | fseek(fptr, 0, SEEK_SET); |
435 | 0 | if(l < 0){ |
436 | 0 | fclose(fptr); |
437 | 0 | return MOSQ_ERR_ERRNO; |
438 | 0 | }else if(l == 0){ |
439 | 0 | *buf = NULL; |
440 | 0 | if(buflen){ |
441 | 0 | *buflen = 0; |
442 | 0 | } |
443 | 0 | fclose(fptr); |
444 | 0 | return MOSQ_ERR_SUCCESS; |
445 | 0 | } |
446 | 0 | buflen_i = (size_t)l; |
447 | |
|
448 | 0 | *buf = mosquitto_calloc(buflen_i+1, sizeof(char)); |
449 | 0 | if((*buf) == NULL){ |
450 | 0 | fclose(fptr); |
451 | 0 | return MOSQ_ERR_NOMEM; |
452 | 0 | } |
453 | 0 | if(fread(*buf, 1, buflen_i, fptr) != buflen_i){ |
454 | 0 | mosquitto_FREE(*buf); |
455 | 0 | fclose(fptr); |
456 | 0 | return MOSQ_ERR_INVAL; |
457 | 0 | } |
458 | 0 | fclose(fptr); |
459 | 0 | if(buflen){ |
460 | 0 | *buflen = buflen_i; |
461 | 0 | } |
462 | |
|
463 | 0 | return MOSQ_ERR_SUCCESS; |
464 | 0 | } |