Coverage Report

Created: 2023-06-07 06:10

/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
}