Coverage Report

Created: 2026-09-03 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/proftpd/src/event.c
Line
Count
Source
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 2003-2026 The ProFTPD Project team
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
17
 *
18
 * As a special exemption, The ProFTPD Project team and other respective
19
 * copyright holders give permission to link this program with OpenSSL, and
20
 * distribute the resulting executable, without including the source code for
21
 * OpenSSL in the source distribution.
22
 */
23
24
/* Event management code */
25
26
#include "conf.h"
27
28
/* Note: as more events are added, and as this API grows more and more used
29
 * by the core code, look into using a different ADT for storage/retrieval
30
 * of these objects, such as hash tables.
31
 */
32
33
struct event_handler {
34
  struct event_handler *next, *prev;
35
  module *module;
36
  void (*cb)(const void *, void *);
37
  void *user_data;
38
  unsigned long flags;
39
};
40
41
struct event_list {
42
  struct event_list *next;
43
  pool *pool;
44
  const char *event;
45
  size_t event_len;
46
  struct event_handler *handlers;
47
};
48
49
static pool *event_pool = NULL;
50
static struct event_list *events = NULL;
51
52
static const char *curr_event = NULL;
53
static struct event_list *curr_evl = NULL;
54
static struct event_handler *curr_evh = NULL;
55
56
/* Certain events are NOT logged via Trace logging (in order to prevent
57
 * event/trace loops).
58
 */
59
static const char *untraced_events[] = {
60
  PR_LOG_NAME_UNSPEC,
61
  PR_LOG_NAME_XFERLOG,
62
  PR_LOG_NAME_SYSLOG,
63
  PR_LOG_NAME_SYSTEMLOG,
64
  PR_LOG_NAME_EXTLOG,
65
  PR_LOG_NAME_TRACELOG,
66
  NULL
67
};
68
69
0
#define PR_EVENT_FL_UNTRACED    0x001
70
71
static const char *trace_channel = "event";
72
73
0
#define EVENT_POOL_SZ 256
74
75
0
static void event_cleanup_cb(void *user_data) {
76
0
  event_pool = NULL;
77
0
  events = NULL;
78
79
0
  curr_event = NULL;
80
0
  curr_evl = NULL;
81
0
  curr_evh = NULL;
82
0
}
83
84
int pr_event_register(module *m, const char *event,
85
0
    void (*cb)(const void *, void *), void *user_data) {
86
0
  register unsigned int i;
87
0
  struct event_handler *evh;
88
0
  struct event_list *evl;
89
0
  pool *evl_pool;
90
0
  unsigned long flags = 0;
91
92
0
  if (event == NULL ||
93
0
      cb == NULL) {
94
0
    errno = EINVAL;
95
0
    return -1;
96
0
  }
97
98
0
  if (event_pool == NULL) {
99
0
    event_pool = make_sub_pool(permanent_pool);
100
0
    pr_pool_tag(event_pool, "Event Pool");
101
102
0
    register_cleanup2(event_pool, NULL, event_cleanup_cb);
103
0
  }
104
105
0
  pr_trace_msg(trace_channel, 3,
106
0
    "module '%s' (%p) registering handler for event '%s' (at %p)",
107
0
    m ? m->name : "(none)", m, event, cb);
108
109
0
  evh = pcalloc(event_pool, sizeof(struct event_handler));
110
111
0
  evh->module = m;
112
0
  evh->cb = cb;
113
0
  evh->user_data = user_data;
114
115
  /* Is this an untraced event? */
116
0
  for (i = 0; untraced_events[i] != NULL; i++) {
117
0
    if (strcmp(event, untraced_events[i]) == 0) {
118
0
      flags = PR_EVENT_FL_UNTRACED;
119
0
      break;
120
0
    }
121
0
  }
122
123
0
  evh->flags = flags;
124
125
  /* Scan the currently registered lists, looking for where to add this
126
   * registration.
127
   */
128
129
0
  for (evl = events; evl; evl = evl->next) {
130
0
    if (strncmp(evl->event, event, evl->event_len + 1) == 0) {
131
0
      struct event_handler *evhi, *evhl = NULL;
132
133
0
      evhi = evl->handlers;
134
0
      if (evhi) {
135
        /* Make sure this event handler is added to the START of the list,
136
         * in order to preserve module load order handling of events (i.e.
137
         * last module loaded, first module handled).  The exception to this
138
         * rule are core callbacks (i.e. where m == NULL); these will always
139
         * be invoked last.
140
         *
141
         * Before that, though, check for duplicate registration/subscription.
142
         */
143
0
        while (evhi) {
144
0
          pr_signals_handle();
145
146
0
          if (evhi->cb == evh->cb) {
147
            /* Duplicate callback */
148
0
            errno = EEXIST;
149
0
            return -1;
150
0
          }
151
152
0
          evhl = evhi;
153
154
0
          if (evhi->next == NULL) {
155
0
            break;
156
0
          }
157
158
0
          evhi = evhi->next;
159
0
        }
160
161
0
        if (evh->module != NULL) {
162
0
          if (evl->handlers != NULL) {
163
0
            evl->handlers->prev = evh;
164
0
          }
165
166
0
          evh->next = evl->handlers;
167
0
          evl->handlers = evh;
168
169
0
        } else {
170
          /* Core event listeners go at the end. */
171
0
          evhl->next = evh;
172
0
          evh->prev = evhl;
173
0
        }
174
175
0
      } else {
176
0
        evl->handlers = evh;
177
0
      }
178
179
      /* All done */
180
0
      return 0;
181
0
    }
182
0
  }
183
184
0
  evl_pool = pr_pool_create_sz(event_pool, EVENT_POOL_SZ);
185
0
  pr_pool_tag(evl_pool, "Event listener list pool");
186
187
0
  evl = pcalloc(evl_pool, sizeof(struct event_list));
188
0
  evl->pool = evl_pool;
189
0
  evl->event = pstrdup(evl->pool, event);
190
0
  evl->event_len = strlen(evl->event);
191
0
  evl->handlers = evh;
192
0
  evl->next = events;
193
194
0
  events = evl;
195
196
  /* Clear any cached data. */
197
0
  curr_event = NULL;
198
0
  curr_evl = NULL;
199
0
  curr_evh = NULL;
200
201
0
  return 0;
202
0
}
203
204
int pr_event_unregister(module *m, const char *event,
205
0
    void (*cb)(const void *, void *)) {
206
0
  struct event_list *evl;
207
0
  int unregistered = FALSE;
208
209
0
  if (events == NULL) {
210
0
    return 0;
211
0
  }
212
213
0
  pr_trace_msg(trace_channel, 3,
214
0
    "module '%s' (%p) unregistering handler for event '%s'",
215
0
    m ? m->name : "(none)", m, event ? event : "(all)");
216
217
  /* For now, simply remove the event_handler entry for this callback.  In
218
   * the future, add a static counter, and churn the event pool after a
219
   * certain number of unregistrations, so that the memory pool doesn't
220
   * grow unnecessarily.
221
   */
222
223
0
  for (evl = events; evl; evl = evl->next) {
224
0
    pr_signals_handle();
225
226
0
    if (event == NULL ||
227
0
        strncmp(evl->event, event, evl->event_len + 1) == 0) {
228
0
      struct event_handler *evh;
229
230
      /* If there are no handlers for this event, there is nothing to
231
       * unregister.  Skip on to the next list.
232
       */
233
0
      if (evl->handlers == NULL) {
234
0
        continue;
235
0
      }
236
237
0
      for (evh = evl->handlers; evh;) {
238
239
0
        if ((m == NULL || evh->module == m) &&
240
0
            (cb == NULL || evh->cb == cb)) {
241
0
          struct event_handler *tmp = evh->next;
242
243
0
          if (evh->next) {
244
0
            evh->next->prev = evh->prev;
245
0
          }
246
247
0
          if (evh->prev) {
248
0
            evh->prev->next = evh->next;
249
250
0
          } else {
251
            /* This is the head of the list. */
252
0
            evl->handlers = evh->next;
253
0
          }
254
255
0
          evh->module = NULL;
256
0
          evh = tmp;
257
0
          unregistered = TRUE;
258
259
0
        } else {
260
0
          evh = evh->next;
261
0
        }
262
0
      }
263
0
    }
264
0
  }
265
266
  /* Clear any cached data. */
267
0
  curr_event = NULL;
268
0
  curr_evl = NULL;
269
0
  curr_evh = NULL;
270
271
0
  if (!unregistered) {
272
0
    errno = ENOENT;
273
0
    return -1;
274
0
  }
275
276
0
  return 0;
277
0
}
278
279
5.23k
int pr_event_listening(const char *event) {
280
5.23k
  struct event_list *evl;
281
5.23k
  int count = 0;
282
283
5.23k
  if (event == NULL) {
284
0
    errno = EINVAL;
285
0
    return -1;
286
0
  }
287
288
5.23k
  if (events == NULL) {
289
    /* No registered listeners at all. */
290
5.23k
    return 0;
291
5.23k
  }
292
293
  /* Lookup callbacks for this event. */
294
0
  for (evl = events; evl; evl = evl->next) {
295
296
0
    if (strncmp(evl->event, event, evl->event_len + 1) == 0) {
297
0
      struct event_handler *evh;
298
299
      /* If there are no registered callbacks for this event, be done. */
300
0
      if (evl->handlers == NULL) {
301
0
        return 0;
302
0
      }
303
304
0
      for (evh = evl->handlers; evh; evh = evh->next) {
305
0
        count++;
306
0
      }
307
308
0
      break;
309
0
    }
310
0
  }
311
312
0
  return count;
313
0
}
314
315
0
void pr_event_generate(const char *event, const void *event_data) {
316
0
  int use_cache = FALSE;
317
0
  struct event_list *evl;
318
319
0
  if (event == NULL) {
320
0
    return;
321
0
  }
322
323
  /* If there are no registered callbacks, be done. */
324
0
  if (events == NULL) {
325
0
    return;
326
0
  }
327
328
  /* If there is a cached event, see if the given event matches. */
329
0
  if (curr_event != NULL &&
330
0
      strcmp(curr_event, event) == 0) {
331
0
    use_cache = TRUE;
332
0
  }
333
334
  /* Lookup callbacks for this event. */
335
0
  for (evl = use_cache ? curr_evl : events; evl; evl = evl->next) {
336
337
0
    if (strncmp(evl->event, event, evl->event_len + 1) == 0) {
338
0
      struct event_handler *evh;
339
340
      /* If there are no registered callbacks for this event, be done. */
341
0
      if (!evl->handlers) {
342
0
        pr_trace_msg(trace_channel, 8, "no event handlers registered for '%s'",
343
0
          event);
344
0
        return;
345
0
      }
346
347
0
      curr_event = event;
348
0
      curr_evl = evl;
349
350
0
      for (evh = use_cache ? curr_evh : evl->handlers; evh; evh = evh->next) {
351
        /* Make sure that if the same event is generated by the current
352
         * listener, the next time through we go to the next listener, rather
353
         * sending the same event against to the same listener (Bug#3619).
354
         */
355
0
        curr_evh = evh->next;
356
357
0
        if (!(evh->flags & PR_EVENT_FL_UNTRACED)) {
358
0
          if (evh->module) {
359
0
            pr_trace_msg(trace_channel, 8,
360
0
              "dispatching event '%s' to mod_%s (at %p, use cache = %s)", event,
361
0
              evh->module->name, evh->cb, use_cache ? "true" : "false");
362
363
0
          } else {
364
0
            pr_trace_msg(trace_channel, 8,
365
0
              "dispatching event '%s' to core (at %p, use cache = %s)", event,
366
0
              evh->cb, use_cache ? "true" : "false");
367
0
          }
368
0
        }
369
370
0
        evh->cb(event_data, evh->user_data);
371
0
      }
372
373
0
      break;
374
0
    }
375
0
  }
376
377
  /* Clear any cached data after publishing the event to all interested
378
   * listeners.
379
   */
380
0
  curr_event = NULL;
381
0
  curr_evl = NULL;
382
0
  curr_evh = NULL;
383
0
}
384
385
0
void pr_event_dump(void (*dumpf)(const char *, ...)) {
386
0
  struct event_list *evl;
387
388
0
  if (dumpf == NULL) {
389
0
    return;
390
0
  }
391
392
0
  if (events == NULL) {
393
0
    dumpf("%s", "No events registered");
394
0
    return;
395
0
  }
396
397
0
  for (evl = events; evl; evl = evl->next) {
398
0
    pr_signals_handle();
399
400
0
    if (evl->handlers == NULL) {
401
0
      dumpf("No handlers registered for '%s'", evl->event);
402
403
0
    } else {
404
0
      struct event_handler *evh;
405
406
0
      dumpf("Registered for '%s':", evl->event);
407
0
      for (evh = evl->handlers; evh; evh = evh->next) {
408
0
        if (evh->module != NULL) {
409
0
          dumpf("  mod_%s.c", evh->module->name);
410
411
0
        } else {
412
0
          dumpf("  (core)");
413
0
        }
414
0
      }
415
0
    }
416
0
  }
417
0
}