Coverage Report

Created: 2026-06-09 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-master/master-admin-client.c
Line
Count
Source
1
/* Copyright (c) 2022 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "connection.h"
5
#include "ostream.h"
6
#include "str.h"
7
#include "master-service-private.h"
8
#include "master-admin-client.h"
9
10
struct master_admin_client {
11
  struct connection conn;
12
  int refcount;
13
14
  struct ioloop *wait_ioloop;
15
  bool reply_pending;
16
};
17
18
static struct connection_list *master_admin_clients = NULL;
19
static struct master_admin_client_callback master_admin_client_callbacks;
20
21
static void master_admin_client_ref(struct master_admin_client *client)
22
0
{
23
0
  i_assert(client->refcount > 0);
24
0
  client->refcount++;
25
0
}
26
27
static void master_admin_client_unref(struct master_admin_client **_client)
28
0
{
29
0
  struct master_admin_client *client = *_client;
30
31
0
  i_assert(client->refcount > 0);
32
0
  *_client = NULL;
33
34
0
  if (--client->refcount == 0)
35
0
    i_free(client);
36
0
}
37
38
static void
39
cmd_kick_user(struct master_admin_client *client, const char *const *args)
40
0
{
41
0
  const char *user = args[0];
42
0
  if (user == NULL) {
43
0
    master_admin_client_send_reply(client, "-Missing parameter");
44
0
    return;
45
0
  }
46
0
  guid_128_t conn_guid;
47
0
  if (args[1] == NULL)
48
0
    guid_128_empty(conn_guid);
49
0
  else if (guid_128_from_string(args[1], conn_guid) < 0) {
50
0
    master_admin_client_send_reply(client,
51
0
                 "-Invalid conn-guid parameter");
52
0
    return;
53
0
  } else if (args[2] != NULL) {
54
0
    master_admin_client_send_reply(client, "-Extra parameters");
55
0
    return;
56
0
  }
57
58
0
  master_admin_client_send_reply(client, t_strdup_printf("+%u",
59
0
    master_admin_client_callbacks.cmd_kick_user(user, conn_guid)));
60
0
}
61
62
static void
63
cmd_kick_user_signal(struct master_admin_client *client,
64
         const char *const *args)
65
0
{
66
0
  const char *user = args[0];
67
0
  if (user == NULL) {
68
0
    master_admin_client_send_reply(client, "-Missing parameter");
69
0
    return;
70
0
  }
71
72
  /* This command is usually handled by the signal handler, but looks
73
     like the signal handling was delayed. Remember the username for
74
     the signal. */
75
0
  master_service_set_last_kick_signal_user(master_service, user);
76
  /* Don't send a response back, just like the signal handler won't. */
77
0
  client->reply_pending = FALSE;
78
0
  master_admin_client_unref(&client);
79
0
}
80
81
static int
82
master_admin_client_input_args(struct connection *conn, const char *const *args)
83
0
{
84
0
  struct master_admin_client *client =
85
0
    container_of(conn, struct master_admin_client, conn);
86
87
0
  if (client->reply_pending) {
88
    /* Delay handling the command until the previous command is
89
       replied to. */
90
0
    connection_input_halt(conn);
91
0
    return 0;
92
0
  }
93
0
  const char *cmd = args[0];
94
0
  args++;
95
96
0
  if (client->wait_ioloop != NULL) {
97
    /* A command was received while waiting in
98
       master_admin_client_initial_read(). Now that we've seen it,
99
       stop the wait ioloop after the command is finished. */
100
0
    io_loop_stop(client->wait_ioloop);
101
0
  }
102
103
  /* Delay freeing the client until reply is sent */
104
0
  master_admin_client_ref(client);
105
0
  client->reply_pending = TRUE;
106
107
0
  if (strcmp(cmd, "KICK-USER") == 0 &&
108
0
      master_admin_client_callbacks.cmd_kick_user != NULL)
109
0
    cmd_kick_user(client, args);
110
0
  else if (strcmp(cmd, "KICK-USER-SIGNAL") == 0) {
111
0
    cmd_kick_user_signal(client, args);
112
0
    return -1;
113
0
  } else if (master_admin_client_callbacks.cmd == NULL ||
114
0
       !master_admin_client_callbacks.cmd(client, cmd, args)) {
115
0
    client->reply_pending = FALSE;
116
0
    o_stream_nsend_str(conn->output, "-Unknown command\n");
117
0
    master_admin_client_unref(&client);
118
0
  }
119
0
  return 1;
120
0
}
121
122
void master_admin_client_send_reply(struct master_admin_client *client,
123
            const char *reply)
124
0
{
125
0
  i_assert(client->reply_pending);
126
0
  client->reply_pending = FALSE;
127
128
0
  struct const_iovec iov[] = {
129
0
    { reply, strlen(reply) },
130
0
    { "\n", 1 }
131
0
  };
132
0
  if (client->conn.output != NULL) {
133
0
    o_stream_nsendv(client->conn.output, iov, N_ELEMENTS(iov));
134
0
    connection_input_resume(&client->conn);
135
0
  } else {
136
    /* client already disconnected */
137
0
  }
138
0
  master_admin_client_unref(&client);
139
0
}
140
141
static void master_admin_client_destroy(struct connection *conn)
142
0
{
143
0
  struct master_admin_client *client =
144
0
    container_of(conn, struct master_admin_client, conn);
145
146
0
  connection_deinit(conn);
147
0
  master_admin_client_unref(&client);
148
0
}
149
150
static const struct connection_settings master_admin_conn_set = {
151
  .service_name_in = "master-admin-client",
152
  .service_name_out = "master-admin-server",
153
  .major_version = 1,
154
  .minor_version = 0,
155
156
  .input_max_size = 1024,
157
  .output_max_size = SIZE_MAX,
158
  .client = FALSE
159
};
160
161
static const struct connection_vfuncs master_admin_conn_vfuncs = {
162
  .destroy = master_admin_client_destroy,
163
  .input_args = master_admin_client_input_args
164
};
165
166
static void master_admin_client_initial_read(struct master_admin_client *client)
167
0
{
168
0
  struct ioloop *prev_ioloop = current_ioloop;
169
0
  client->wait_ioloop = io_loop_create();
170
0
  connection_switch_ioloop(&client->conn);
171
0
  struct timeout *to =
172
0
    timeout_add_short(100, io_loop_stop, client->wait_ioloop);
173
174
0
  io_loop_run(client->wait_ioloop);
175
176
0
  timeout_remove(&to);
177
0
  connection_switch_ioloop_to(&client->conn, prev_ioloop);
178
0
  io_loop_destroy(&client->wait_ioloop);
179
0
}
180
181
void master_admin_client_create(struct master_service_connection *master_conn)
182
0
{
183
0
  struct master_admin_client *client;
184
185
0
  if (master_admin_clients == NULL) {
186
0
    master_admin_clients =
187
0
      connection_list_init(&master_admin_conn_set,
188
0
               &master_admin_conn_vfuncs);
189
0
  }
190
191
0
  client = i_new(struct master_admin_client, 1);
192
0
  client->refcount = 1;
193
0
  connection_init_server(master_admin_clients, &client->conn, master_conn->name,
194
0
             master_conn->fd, master_conn->fd);
195
0
  if (master_service_get_client_limit(master_service) == 1) {
196
    /* client_limit=1 for this process, so this connection is
197
       likely to be for KICK-USER-SIGNAL command. We're currently
198
       blocking the SIGTERM, so try for a while to read the command
199
       here. This way the command can be handled more reliably
200
       instead of SIGTERM interrupting its handling too early. */
201
0
    master_admin_client_ref(client);
202
0
    master_admin_client_initial_read(client);
203
0
    master_admin_client_unref(&client);
204
0
  }
205
0
}
206
207
bool master_admin_client_can_accept(const char *name)
208
0
{
209
0
  return name != NULL && strcmp(name, "%{pid}") == 0;
210
0
}
211
212
void master_admin_clients_init(const struct master_admin_client_callback *callbacks)
213
0
{
214
0
  master_admin_client_callbacks = *callbacks;
215
0
}
216
217
void master_admin_clients_deinit(void)
218
0
{
219
0
  if (master_admin_clients != NULL)
220
0
    connection_list_deinit(&master_admin_clients);
221
0
}