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