The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
sync_touch.c
Go to the documentation of this file.
1/*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * @file src/listen/ldap_sync/sync_touch.c
19 *
20 * @brief Test utility that touches entries, so LDAP sync clients re-process them.
21 *
22 * Replaces an attribute with its current values, which OpenLDAP records as a
23 * modification (other servers may not), and skips entries without the attribute.
24 *
25 * @author Arran Cudbard-Bell
26 *
27 * @copyright 2017 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
28 */
29RCSID("$Id: debacd0b2e89370b4469a712c888e0392620d925 $")
30
32
33#include <freeradius-devel/autoconf.h>
34
35#include <ldap.h>
36
37#include <stdbool.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41
42#ifdef HAVE_GETOPT_H
43# include <getopt.h>
44#endif
45
46typedef struct {
47 char const *uri; //!< LDAP server to connect to.
48 bool start_tls; //!< Whether to use the StartTLS extended operation.
49 char const *bind_dn; //!< Distinguished Name (DN) to bind as. NULL for an anonymous bind.
50 char const *bind_pw; //!< Password to bind with.
51 char const *base_dn; //!< Where to start the search.
52 int scope; //!< LDAP_SCOPE_* value for the search.
53 char const *filter; //!< Search filter that selects the entries to touch.
54 char const *attr; //!< Attribute to replace with the current values of the attribute.
55 bool dry_run; //!< Print the entries that sync_touch would touch, without modifying the entries.
56 int verbose; //!< 1 prints each entry. 2 or more also prints libldap debug output.
58
59static NEVER_RETURNS void usage(int status)
60{
61 FILE *fp = status ? stderr : stdout;
62
63 fprintf(fp, "Usage: sync_touch [options] <attribute> [<filter>]\n");
64 fprintf(fp, "Replace <attribute> with the current values of <attribute> in every entry\n");
65 fprintf(fp, "matching <filter>, so that LDAP sync clients see the entries as changed.\n");
66 fprintf(fp, "<filter> defaults to (objectClass=*).\n\n");
67 fprintf(fp, " -H <uri> LDAP server URI (default ldap://localhost).\n");
68 fprintf(fp, " -Z Use StartTLS.\n");
69 fprintf(fp, " -D <dn> DN to bind as (default anonymous).\n");
70 fprintf(fp, " -w <password> Password to bind with.\n");
71 fprintf(fp, " -y <file> Read the password from the first line of <file>.\n");
72 fprintf(fp, " -b <dn> Search base (required).\n");
73 fprintf(fp, " -s <scope> base, one, or sub (default sub).\n");
74 fprintf(fp, " -n Dry run. List the entries that sync_touch would touch.\n");
75 fprintf(fp, " -v Print each entry. Use -vv to also print libldap debug output.\n");
76 fprintf(fp, " -h Print this help.\n");
77
78 exit(status);
79}
80
81/** Read the bind password from the first line of a file
82 *
83 * @param[in] path of the file.
84 * @return the password, or NULL on error. The caller must free() the password.
85 */
86static char *password_from_file(char const *path)
87{
88 FILE *fp;
89 char buff[1024];
90 size_t len;
91
92 fp = fopen(path, "r");
93 if (!fp) {
94 fprintf(stderr, "sync_touch: Failed opening %s\n", path);
95 return NULL;
96 }
97
98 if (!fgets(buff, sizeof(buff), fp)) {
99 fprintf(stderr, "sync_touch: Failed reading %s\n", path);
100 fclose(fp);
101 return NULL;
102 }
103 fclose(fp);
104
105 len = strlen(buff);
106 while ((len > 0) && ((buff[len - 1] == '\n') || (buff[len - 1] == '\r'))) {
107 buff[--len] = '\0';
108 }
109
110 return strdup(buff);
111}
112
113/** Touch one entry by replacing an attribute with the current values of the attribute
114 *
115 * @param[in] ld connection to the directory.
116 * @param[in] conf search and touch options.
117 * @param[in] entry from the search.
118 * @return
119 * - 1 if the entry was touched.
120 * - 0 if the entry was skipped, because the entry does not have the attribute.
121 * - -1 on error.
122 */
123static int touch_entry(LDAP *ld, sync_touch_conf_t const *conf, LDAPMessage *entry)
124{
125 char *dn;
126 struct berval **values;
127 LDAPMod mod;
128 LDAPMod *mods[2];
129 int ret;
130
131 dn = ldap_get_dn(ld, entry);
132 if (!dn) {
133 fprintf(stderr, "sync_touch: Failed getting DN of entry\n");
134 return -1;
135 }
136
137 values = ldap_get_values_len(ld, entry, conf->attr);
138 if (!values) {
139 if (conf->verbose) printf("skipped %s (no %s)\n", dn, conf->attr);
140 ldap_memfree(dn);
141 return 0;
142 }
143
144 if (conf->dry_run) {
145 printf("would touch %s\n", dn);
146 ldap_value_free_len(values);
147 ldap_memfree(dn);
148 return 1;
149 }
150
151 mod = (LDAPMod) {
152 .mod_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES,
153 .mod_type = UNCONST(char *, conf->attr),
154 .mod_bvalues = values
155 };
156 mods[0] = &mod;
157 mods[1] = NULL;
158
159 ret = ldap_modify_ext_s(ld, dn, mods, NULL, NULL);
160 ldap_value_free_len(values);
161
162 if (ret != LDAP_SUCCESS) {
163 fprintf(stderr, "sync_touch: Failed touching %s: %s\n", dn, ldap_err2string(ret));
164 ldap_memfree(dn);
165 return -1;
166 }
167
168 if (conf->verbose) printf("touched %s\n", dn);
169 ldap_memfree(dn);
170
171 return 1;
172}
173
174int main(int argc, char **argv)
175{
177 .uri = "ldap://localhost",
178 .scope = LDAP_SCOPE_SUBTREE,
179 .filter = "(objectClass=*)"
180 };
181 char *pw_from_file = NULL;
182 char *attrs[2];
183 LDAP *ld = NULL;
184 LDAPMessage *result = NULL, *entry;
185 struct berval cred;
186 int c, ret, version = LDAP_VERSION3;
187 int touched = 0, skipped = 0, failed = 0;
188 int status = EXIT_FAILURE;
189
190 while ((c = getopt(argc, argv, "b:D:hH:ns:vw:y:Z")) != -1) switch (c) {
191 case 'b':
192 conf.base_dn = optarg;
193 break;
194
195 case 'D':
196 conf.bind_dn = optarg;
197 break;
198
199 case 'h':
200 usage(EXIT_SUCCESS);
201
202 case 'H':
203 conf.uri = optarg;
204 break;
205
206 case 'n':
207 conf.dry_run = true;
208 break;
209
210 case 's':
211 if (strcmp(optarg, "base") == 0) {
212 conf.scope = LDAP_SCOPE_BASE;
213 } else if (strcmp(optarg, "one") == 0) {
214 conf.scope = LDAP_SCOPE_ONELEVEL;
215 } else if (strcmp(optarg, "sub") == 0) {
216 conf.scope = LDAP_SCOPE_SUBTREE;
217 } else {
218 fprintf(stderr, "sync_touch: Invalid scope \"%s\"\n", optarg);
219 usage(EXIT_FAILURE);
220 }
221 break;
222
223 case 'v':
224 conf.verbose++;
225 break;
226
227 case 'w':
228 conf.bind_pw = optarg;
229 break;
230
231 case 'y':
232 free(pw_from_file);
233 pw_from_file = password_from_file(optarg);
234 if (!pw_from_file) exit(EXIT_FAILURE);
235 conf.bind_pw = pw_from_file;
236 break;
237
238 case 'Z':
239 conf.start_tls = true;
240 break;
241
242 default:
243 usage(EXIT_FAILURE);
244 }
245 argc -= optind;
246 argv += optind;
247
248 if ((argc < 1) || (argc > 2)) usage(EXIT_FAILURE);
249 conf.attr = argv[0];
250 if (argc == 2) conf.filter = argv[1];
251
252 if (!conf.base_dn) {
253 fprintf(stderr, "sync_touch: A search base (-b) is required\n");
254 usage(EXIT_FAILURE);
255 }
256
257 if (conf.verbose > 1) {
258 int debug = -1;
259
260 if (ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &debug) != LDAP_OPT_SUCCESS) {
261 fprintf(stderr, "sync_touch: Failed setting debug level");
262 goto finish;
263 }
264 }
265
266 ret = ldap_initialize(&ld, conf.uri);
267 if (ret != LDAP_SUCCESS) {
268 fprintf(stderr, "sync_touch: Failed initialising connection to %s: %s\n",
269 conf.uri, ldap_err2string(ret));
270 goto finish;
271 }
272 if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version) != LDAP_OPT_SUCCESS) {
273 fprintf(stderr, "sync_touch: Failed setting protocol version");
274 goto finish;
275 }
276
277 if (conf.start_tls) {
278 ret = ldap_start_tls_s(ld, NULL, NULL);
279 if (ret != LDAP_SUCCESS) {
280 fprintf(stderr, "sync_touch: StartTLS failed: %s\n", ldap_err2string(ret));
281 goto finish;
282 }
283 }
284
285 cred = (struct berval) {
286 .bv_val = UNCONST(char *, conf.bind_pw),
287 .bv_len = conf.bind_pw ? strlen(conf.bind_pw) : 0
288 };
289 ret = ldap_sasl_bind_s(ld, conf.bind_dn, LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL);
290 if (ret != LDAP_SUCCESS) {
291 fprintf(stderr, "sync_touch: Bind failed: %s\n", ldap_err2string(ret));
292 goto finish;
293 }
294
295 /*
296 * Only fetch the attribute we replace.
297 */
298 attrs[0] = UNCONST(char *, conf.attr);
299 attrs[1] = NULL;
300
301 ret = ldap_search_ext_s(ld, conf.base_dn, conf.scope, conf.filter, attrs, 0,
302 NULL, NULL, NULL, LDAP_NO_LIMIT, &result);
303 if ((ret != LDAP_SUCCESS) && (ret != LDAP_SIZELIMIT_EXCEEDED)) {
304 fprintf(stderr, "sync_touch: Search failed: %s\n", ldap_err2string(ret));
305 goto finish;
306 }
307 if (ret == LDAP_SIZELIMIT_EXCEEDED) {
308 fprintf(stderr, "sync_touch: Server size limit reached, not all matching entries will be touched\n");
309 }
310
311 for (entry = ldap_first_entry(ld, result); entry; entry = ldap_next_entry(ld, entry)) {
312 switch (touch_entry(ld, &conf, entry)) {
313 case 1:
314 touched++;
315 break;
316
317 case 0:
318 skipped++;
319 break;
320
321 default:
322 failed++;
323 break;
324 }
325 }
326
327 printf("%s %i, skipped %i, failed %i\n", conf.dry_run ? "would touch" : "touched", touched, skipped, failed);
328
329 /*
330 * Matching nothing is a failure, as the caller wanted
331 * something to be re-synced.
332 */
333 if ((failed == 0) && (touched > 0)) status = EXIT_SUCCESS;
334
335finish:
336 if (result) ldap_msgfree(result);
337 if (ld) ldap_unbind_ext_s(ld, NULL, NULL);
338 free(pw_from_file);
339
340 return status;
341}
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition build.h:203
#define USES_APPLE_DEPRECATED_API
Definition build.h:590
#define RCSID(id)
Definition build.h:603
#define NEVER_RETURNS
Should be placed before the function return type.
Definition build.h:413
static NEVER_RETURNS void usage(void)
Definition dhcpclient.c:113
free(array)
static rs_t * conf
Definition radsniff.c:52
static char buff[sizeof("18446744073709551615")+3]
Definition size_tests.c:37
int scope
LDAP_SCOPE_* value for the search.
Definition sync_touch.c:52
char const * filter
Search filter that selects the entries to touch.
Definition sync_touch.c:53
char const * bind_pw
Password to bind with.
Definition sync_touch.c:50
int main(int argc, char **argv)
Definition sync_touch.c:174
static int touch_entry(LDAP *ld, sync_touch_conf_t const *conf, LDAPMessage *entry)
Touch one entry by replacing an attribute with the current values of the attribute.
Definition sync_touch.c:123
int verbose
1 prints each entry. 2 or more also prints libldap debug output.
Definition sync_touch.c:56
bool start_tls
Whether to use the StartTLS extended operation.
Definition sync_touch.c:48
char const * base_dn
Where to start the search.
Definition sync_touch.c:51
char const * bind_dn
Distinguished Name (DN) to bind as. NULL for an anonymous bind.
Definition sync_touch.c:49
char const * attr
Attribute to replace with the current values of the attribute.
Definition sync_touch.c:54
char const * uri
LDAP server to connect to.
Definition sync_touch.c:47
static char * password_from_file(char const *path)
Read the bind password from the first line of a file.
Definition sync_touch.c:86
bool dry_run
Print the entries that sync_touch would touch, without modifying the entries.
Definition sync_touch.c:55
static fr_sbuff_err_t char size_t * len
Definition value.h:1061