File: whack_add.c

package info (click to toggle)
libreswan 5.2-2.3
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 81,644 kB
  • sloc: ansic: 129,988; sh: 32,018; xml: 20,646; python: 10,303; makefile: 3,022; javascript: 1,506; sed: 574; yacc: 511; perl: 264; awk: 52
file content (367 lines) | stat: -rw-r--r-- 9,610 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
/* <<ipsec add ...>> aka addconn, for libreswan
 *
 * Copyright (C) 2023 Andrew Cagney
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <https://www.gnu.org/licenses/gpl2.txt>.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 */

#include "lswlog.h"	/* for RC_FATAL */
#include "whack.h"

#include "whack_add.h"
#include "show.h"
#include "connections.h"
#include "whack_delete.h"

PRINTF_LIKE(3)
static void llog_add_connection_failed(const struct whack_message *wm,
				       struct logger *logger,
				       const char *fmt, ...)
{
	LLOG_JAMBUF(RC_FATAL, logger, buf) {
		jam(buf, "\"%s\": failed to add connection: ", wm->name);
		va_list ap;
		va_start(ap, fmt);
		jam_va_list(buf, fmt, ap);
		va_end(ap);
	}
}

/*
 * When false, should also check error.
 */

struct subnets {
	const char *leftright;
	const char *name;
	unsigned start;
	/* results */
	ip_subnets subnets;
};

/*
 * The first combination is the current leftsubnet/rightsubnet value,
 * and then each iteration of rightsubnets, and then each permutation
 * of leftsubnets X rightsubnets.
 *
 * If both subnet= is set and subnets=, then it is as if an extra
 * element of subnets= has been added, so subnets= for only one side
 * will do the right thing, as will some combinations of also=
 */

static bool parse_subnets(struct subnets *sn,
			  const struct whack_message *wm,
			  const struct whack_end *end,
			  struct logger *logger)
{
	*sn = (struct subnets) {
		.name = wm->name,
		.leftright = end->leftright,
	};

	unsigned len = 0;

	ip_subnet subnet = unset_subnet;
	if (end->subnet != NULL) {
		ip_address nonzero_host;
		err_t e = ttosubnet_num(shunk1(end->subnet), /*afi*/NULL,
					&subnet, &nonzero_host);
		if (e != NULL) {
			llog_add_connection_failed(wm, logger, 
						   "%ssubnet=%s invalid, %s",
						   end->leftright, end->subnet, e);
			return false;
		}
		if (nonzero_host.is_set) {
			llog_add_connection_failed(wm, logger,
						   "%ssubnet=%s contains non-zero host identifier",
						   end->leftright, end->subnet);
			return false;
		}
		/* make space */
		len += 1;
	}

	ip_subnets subnets = {0};
	if (end->subnets != NULL) {
		diag_t d = ttosubnets_num(shunk1(end->subnets), /*afi*/NULL, &subnets);
		if (d != NULL) {
			llog_add_connection_failed(wm, logger,
						   "%ssubnets=%s invalid, %s",
						   end->leftright, end->subnets,
						   str_diag(d));
			pfree_diag(&d);
			return false;
		}
		/* make space */
		len += subnets.len;
	}

	/*
	 * Merge lists.
	 */
	sn->start = (subnet.is_set ? 0 :
		     subnets.len == 0 ? 0 :
		     1);
	sn->subnets.len = len;
	sn->subnets.list = alloc_things(ip_subnet, len, "subnets");
	unsigned pos = 0;
	if (subnet.is_set) {
		sn->subnets.list[pos++] = subnet;
	}
	FOR_EACH_ITEM(s, &subnets) {
		sn->subnets.list[pos++] = *s;
	}
	pfreeany(subnets.list);
	return true;
}

/*
 * Determine the next_subnet.
 *
 * When subnet= and subnets= were both NULL, set .subnet to NULL so
 * add_connection() will fill in valid, presumably from host.
 */

static const struct ip_info *next_subnet(struct whack_end *end,
					 const ip_subnets *subnets,
					 unsigned i)
{
	if (subnets->len > 0) {
		ip_subnet subnet = subnets->list[i];
		subnet_buf b;
		str_subnet(&subnet, &b);
		/* freed by free_wam() */
		end->subnet = clone_str(str_subnet(&subnet, &b), "subnet name");
		return subnet_info(subnet);
	}

	/*
	 * There's no subnet, clear things so that add_connection()
	 * will fill it in using the host address.
	 */
	end->subnet = NULL;
	return NULL; /* unknown */
}

static void free_wam(struct whack_message *wam)
{
	pfreeany(wam->name);
	pfreeany(wam->end[LEFT_END].subnet);
	pfreeany(wam->end[RIGHT_END].subnet);
}

/*
 * permutate_conns - generate all combinations of subnets={}
 *
 * @operation - the function to apply to each generated conn
 * @cfg       - the base configuration
 * @conn      - the conn to permute
 *
 * This function goes through the set of N x M combinations of the subnets
 * defined in conn's "subnets=" declarations and synthesizes conns with
 * the proper left/right subnet settings, and then calls operation(),
 * (which is usually add/delete/route/etc.)
 *
 */

static void permutate_connection_subnets(const struct whack_message *wm,
					 const struct subnets *left,
					 const struct subnets *right,
					 struct logger *logger)
{
	/*
	 * The first combination is the current leftsubnet/rightsubnet
	 * value, and then each iteration of rightsubnets, and then
	 * each permutation of leftsubnets X rightsubnets.
	 *
	 * Both loops execute at least once.  When an end has
	 * subnet=NULL and subnets=NULL, the value unset_subnet is
	 * used and .subnet is set to NULL so that add_connection()
	 * will fill it in using the host address.
	 */

	for (unsigned left_i = 0;
	     left_i == 0 || left_i < left->subnets.len;
	     left_i++) {

		for (unsigned right_i = 0;
		     right_i == 0 || right_i < right->subnets.len;
		     right_i++) {

			/*
			 * whack message --- we can borrow all
			 * pointers, since this is a temporary copy.
			 */
			struct whack_message wam = *wm;
			wam.connalias = wm->name;

			/*
			 * Leave .subnets values alone.
			 *
			 * This way, add_connection() can see the
			 * original value that the subnet was taken
			 * from and log accordingly.
			 *
			 * For instance addresspool vs subnets should
			 * complain about subnets and not subnet.
			 */
#if 0
			wam.left.subnets = NULL;
			wam.right.subnets = NULL;
#endif

			/*
			 * Build a new connection name by appending
			 * /<left-nr>x<right-nr>.
			 *
			 * When the connection also contained subnet=,
			 * that has NR==0.
			 *
			 * MUST FREE
			 */
			wam.name = alloc_printf("%s/%ux%u",
						wm->name,
						left->start+left_i,
						right->start+right_i);

			/*
			 * Either .subnet is !.is_set or is valid.
			 * {left,right}_afi can be NULL.
			 */
			const struct ip_info *left_afi = next_subnet(&wam.end[LEFT_END], &left->subnets, left_i);
			const struct ip_info *right_afi = next_subnet(&wam.end[RIGHT_END], &right->subnets, right_i);

			if (left_afi == right_afi ||
			    left_afi == NULL ||
			    right_afi == NULL) {
				diag_t d = add_connection(&wam, logger);
				if (d != NULL) {
					llog_add_connection_failed(&wam, logger, "%s", str_diag(d));
					pfree_diag(&d);
					free_wam(&wam);
					return;
				}
			} else {
				PEXPECT(logger, (wam.end[LEFT_END].subnet != NULL &&
						 wam.end[RIGHT_END].subnet != NULL));
				llog(RC_LOG, logger,
				     "\"%s\": warning: skipping mismatched leftsubnets=%s rightsubnets=%s",
				     wm->name, wam.end[LEFT_END].subnet, wam.end[RIGHT_END].subnet);
			}

			free_wam(&wam);
		}
	}

}

static void add_connections(const struct whack_message *wm, struct logger *logger)
{
	/*
	 * Reject {left,right}subnets=... combined with
	 * {left,right}subnet=a,b
	 */
	bool have_subnets = false;
	FOR_EACH_THING(subnets, &wm->end[LEFT_END], &wm->end[RIGHT_END]) {
		if (subnets->subnets == NULL) {
			continue;
		}
		have_subnets = true;
		/* have subnets=... */
		FOR_EACH_THING(subnet, &wm->end[LEFT_END], &wm->end[RIGHT_END]) {
			if (subnet->subnet == NULL) {
				continue;
			}
			if (strchr(subnet->subnet, ',') == NULL) {
				continue;
			}
			/* have subnets=.. and subnet=a,b... */
			llog_add_connection_failed(wm, logger,
						   "multi-selector %ssubnet=\"%s\" combined with %ssubnets=\"%s\"",
						   subnet->leftright, subnet->subnet,
						   subnets->leftright, subnets->subnets);
			return;
		}
	}

	/* basic case, nothing special to synthize! */
	if (!have_subnets) {
		diag_t d = add_connection(wm, logger);
		if (d != NULL) {
			llog_add_connection_failed(wm, logger, "%s", str_diag(d));
			pfree_diag(&d);
		}
		return;
	}

	struct subnets left = {0};
	if (!parse_subnets(&left, wm, &wm->end[LEFT_END], logger)) {
		pfreeany(left.subnets.list);
		return;
	}

	struct subnets right = {0};
	if (!parse_subnets(&right, wm, &wm->end[RIGHT_END], logger)) {
		pfreeany(left.subnets.list);
		pfreeany(right.subnets.list);
		return;
	}

	permutate_connection_subnets(wm, &left, &right, logger);
	pfreeany(left.subnets.list);
	pfreeany(right.subnets.list);
}

void whack_add(const struct whack_message *wm, struct show *s)
{
	if (wm->name == NULL) {
		whack_log(RC_FATAL, s,
			  "received command to add a connection, but did not receive the connection name - ignored");
		return;
	}

	switch (wm->whack_from) {
	case WHACK_FROM_ADDCONN:
		/*
		 * "ipsec add" semantics.
		 *
		 * Any existing connection matching .name is purged
		 * before this connection is added.  When no
		 * connection matching name is found, it will delete
		 * aliases.
		 */
		whack_delete(wm, s, /*log_unknown_name*/false);
		/*
		 * Confirm above did its job.
		 */
		if (connection_with_name_exists(wm->name)) {
			llog_pexpect(show_logger(s), HERE,
				     "attempt to redefine connection \"%s\"", wm->name);
			return;
		}
		break;
	case WHACK_FROM_WHACK:
		/*
		 * "ipsec whack" semantics: any attempt to add a
		 * duplicate connection is rejected.
		 */
		if (connection_with_name_exists(wm->name)) {
			whack_log(RC_DUPNAME, s,
				  "attempt to redefine connection \"%s\"", wm->name);
			return;
		}
		break;
	}

	add_connections(wm, show_logger(s));
}