Coverage Report

Created: 2026-03-29 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}