s3/utils: Detect (and report) failure to parse sddl
[samba.git] / source3 / utils / net_offlinejoin.c
1 /*
2    Samba Unix/Linux SMB client library
3    net join commands
4    Copyright (C) 2021 Guenther Deschner (gd@samba.org)
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "includes.h"
21 #include "utils/net.h"
22 #include <netapi.h>
23 #include "netapi/netapi_net.h"
24 #include "libcli/registry/util_reg.h"
25 #include "libcli/security/dom_sid.h"
26 #include "lib/cmdline/cmdline.h"
27
28 int net_offlinejoin_usage(struct net_context *c, int argc, const char **argv)
29 {
30         d_printf(_("\nnet offlinejoin [misc. options]\n"
31                    "\tjoins a computer to a domain\n"));
32         d_printf(_("Valid commands:\n"));
33         d_printf(_("\tprovision\t\t\tProvision machine account in AD\n"));
34         d_printf(_("\trequestodj\t\t\tRequest offline domain join\n"));
35         d_printf(_("\tcomposeodj\t\t\tCompose offline domain join blob\n"));
36         net_common_flags_usage(c, argc, argv);
37         return -1;
38 }
39
40 int net_offlinejoin(struct net_context *c, int argc, const char **argv)
41 {
42         int ret;
43         NET_API_STATUS status;
44
45         if ((argc > 0) && (strcasecmp_m(argv[0], "HELP") == 0)) {
46                 net_offlinejoin_usage(c, argc, argv);
47                 return 0;
48         }
49
50         if (argc == 0) {
51                 net_offlinejoin_usage(c, argc, argv);
52                 return -1;
53         }
54
55         net_warn_member_options();
56
57         status = libnetapi_net_init(&c->netapi_ctx, c->lp_ctx, c->creds);
58         if (status != 0) {
59                 return -1;
60         }
61
62         if (c->opt_kerberos) {
63                 libnetapi_set_use_kerberos(c->netapi_ctx);
64         }
65
66         if (strcasecmp_m(argv[0], "provision") == 0) {
67                 ret = net_offlinejoin_provision(c, argc, argv);
68                 if (ret != 0) {
69                         return ret;
70                 }
71         }
72
73         if (strcasecmp_m(argv[0], "requestodj") == 0) {
74                 ret = net_offlinejoin_requestodj(c, argc, argv);
75                 if (ret != 0) {
76                         return ret;
77                 }
78         }
79
80         if (strcasecmp_m(argv[0], "composeodj") == 0) {
81                 ret = net_offlinejoin_composeodj(c, argc, argv);
82                 if (ret != 0) {
83                         return ret;
84                 }
85         }
86
87         return 0;
88 }
89
90 static int net_offlinejoin_provision_usage(struct net_context *c, int argc, const char **argv)
91 {
92         d_printf(_("\nnet offlinejoin provision [misc. options]\n"
93                    "\tProvisions machine account in AD\n"));
94         d_printf(_("Valid options:\n"));
95         d_printf(_("\tdomain=<DOMAIN>\t\t\t\tDefines AD Domain to join\n"));
96         d_printf(_("\tmachine_name=<MACHINE_NAME>\t\tDefines the machine account name\n"));
97         d_printf(_("\tmachine_account_ou=<OU>\t\t\tDefines the machine account organizational unit DN\n"));
98         d_printf(_("\tdcname=<DCNAME>\t\t\t\tSpecify a Domain Controller to join to\n"));
99         d_printf(_("\tdefpwd\t\t\t\t\tUse default machine account password\n"));
100         d_printf(_("\treuse\t\t\t\t\tReuse existing machine account in AD\n"));
101         d_printf(_("\tsavefile=<FILENAME>\t\t\tFile to store the ODJ data\n"));
102         d_printf(_("\tprintblob\t\t\t\tPrint the base64 encoded ODJ data on stdout\n"));
103         net_common_flags_usage(c, argc, argv);
104         return -1;
105 }
106
107 int net_offlinejoin_provision(struct net_context *c,
108                               int argc, const char **argv)
109 {
110         NET_API_STATUS status;
111         const char *dcname = NULL;
112         const char *domain = NULL;
113         const char *machine_name = NULL;
114         const char *machine_account_ou = NULL;
115         const char *provision_text_data = NULL;
116         uint32_t options = 0;
117         const char *savefile = NULL;
118         bool printblob = false;
119         int i;
120
121         if (c->display_usage || argc == 1) {
122                 return net_offlinejoin_provision_usage(c, argc, argv);
123         }
124
125         /* process additional command line args */
126
127         for (i = 0; i < argc; i++) {
128
129                 if (strnequal(argv[i], "domain", strlen("domain"))) {
130                         domain = get_string_param(argv[i]);
131                         if (domain == NULL) {
132                                 return -1;
133                         }
134                 }
135                 if (strnequal(argv[i], "machine_name", strlen("machine_name"))) {
136                         machine_name = get_string_param(argv[i]);
137                         if (machine_name == NULL) {
138                                 return -1;
139                         }
140                 }
141                 if (strnequal(argv[i], "machine_account_ou", strlen("machine_account_ou"))) {
142                         machine_account_ou = get_string_param(argv[i]);
143                         if (machine_account_ou == NULL) {
144                                 return -1;
145                         }
146                 }
147                 if (strnequal(argv[i], "dcname", strlen("dcname"))) {
148                         dcname = get_string_param(argv[i]);
149                         if (dcname == NULL) {
150                                 return -1;
151                         }
152                 }
153                 if (strnequal(argv[i], "defpwd", strlen("defpwd"))) {
154                         options |= NETSETUP_PROVISION_USE_DEFAULT_PASSWORD;
155                 }
156                 if (strnequal(argv[i], "reuse", strlen("reuse"))) {
157                         options |= NETSETUP_PROVISION_REUSE_ACCOUNT;
158                 }
159                 if (strnequal(argv[i], "savefile", strlen("savefile"))) {
160                         savefile = get_string_param(argv[i]);
161                         if (savefile == NULL) {
162                                 return -1;
163                         }
164                 }
165                 if (strnequal(argv[i], "printblob", strlen("printblob"))) {
166                         printblob = true;
167                 }
168         }
169
170         if (domain == NULL) {
171                 d_printf("Failed to provision computer account: %s\n",
172                          libnetapi_errstr(W_ERROR_V(WERR_INVALID_DOMAINNAME)));
173                 return -1;
174         }
175
176         if (machine_name == NULL) {
177                 d_printf("Failed to provision computer account: %s\n",
178                          libnetapi_errstr(W_ERROR_V(WERR_INVALID_COMPUTERNAME)));
179                 return -1;
180         }
181
182         status = NetProvisionComputerAccount(domain,
183                                              machine_name,
184                                              machine_account_ou,
185                                              dcname,
186                                              options,
187                                              NULL,
188                                              0,
189                                              &provision_text_data);
190         if (status != 0) {
191                 d_printf("Failed to provision computer account: %s\n",
192                         libnetapi_get_error_string(c->netapi_ctx, status));
193                 return status;
194         }
195
196         if (savefile != NULL) {
197
198                 DATA_BLOB ucs2_blob, blob;
199                 bool ok;
200
201                 /*
202                  * Windows produces and consumes UTF16/UCS2 encoded blobs
203                  * so we also do it for compatibility. Someone may provision an
204                  * account for a Windows machine with samba.
205                  */
206                 ok = push_reg_sz(c, &ucs2_blob, provision_text_data);
207                 if (!ok) {
208                         return -1;
209                 }
210
211                 /* Add the unicode BOM mark */
212                 blob = data_blob_talloc(c, NULL, ucs2_blob.length + 2);
213                 if (blob.data == NULL) {
214                         d_printf("Failed to allocate blob: %s\n",
215                                  strerror(errno));
216                         return -1;
217                 }
218
219                 blob.data[0] = 0xff;
220                 blob.data[1] = 0xfe;
221
222                 memcpy(blob.data + 2, ucs2_blob.data, ucs2_blob.length);
223
224                 ok = file_save(savefile, blob.data, blob.length);
225                 if (!ok) {
226                         d_printf("Failed to save %s: %s\n", savefile,
227                                         strerror(errno));
228                         return -1;
229                 }
230         }
231
232         d_printf("Successfully provisioned computer '%s' in domain '%s'\n",
233                         machine_name, domain);
234
235         if (printblob) {
236                 printf("%s\n", provision_text_data);
237         }
238
239         return 0;
240 }
241
242 static int net_offlinejoin_requestodj_usage(struct net_context *c, int argc, const char **argv)
243 {
244         d_printf(_("\nnet offlinejoin requestodj [misc. options]\n"
245                    "\tRequests offline domain join\n"));
246         d_printf(_("Valid options:\n"));
247         d_printf(_("\t-i\t\t\t\t\tRead ODJ data from STDIN\n"));
248         d_printf(_("\tloadfile=<FILENAME>\t\t\tFile that provides the ODJ data\n"));
249         /*d_printf(_("\tlocalos\t\t\t\t\tModify the local machine\n"));*/
250         net_common_flags_usage(c, argc, argv);
251         return -1;
252 }
253
254 int net_offlinejoin_requestodj(struct net_context *c,
255                                int argc, const char **argv)
256 {
257         NET_API_STATUS status;
258         uint8_t *provision_bin_data = NULL;
259         size_t provision_bin_data_size = 0;
260         uint32_t options = NETSETUP_PROVISION_ONLINE_CALLER;
261         const char *windows_path = NULL;
262         int i;
263
264         if (c->display_usage) {
265                 return net_offlinejoin_requestodj_usage(c, argc, argv);
266         }
267
268         /* process additional command line args */
269
270         for (i = 0; i < argc; i++) {
271
272                 if (strnequal(argv[i], "loadfile", strlen("loadfile"))) {
273                         const char *loadfile = NULL;
274
275                         loadfile = get_string_param(argv[i]);
276                         if (loadfile == NULL) {
277                                 return -1;
278                         }
279
280                         provision_bin_data =
281                                 (uint8_t *)file_load(loadfile,
282                                                      &provision_bin_data_size,
283                                                      0,
284                                                      c);
285                         if (provision_bin_data == NULL) {
286                                 d_printf("Failed to read loadfile: %s\n",
287                                 loadfile);
288                                 return -1;
289                         }
290                 }
291 #if 0
292                 if (strnequal(argv[i], "localos", strlen("localos"))) {
293                         options |= NETSETUP_PROVISION_ONLINE_CALLER;
294                 }
295 #endif
296         }
297
298         if (c->opt_stdin) {
299                 if (isatty(STDIN_FILENO) == 1) {
300                         d_fprintf(stderr,
301                                   "hint: stdin waiting for ODJ blob, end "
302                                   "with <crtl-D>.\n");
303                 }
304                 provision_bin_data =
305                         (uint8_t *)fd_load(STDIN_FILENO,
306                                            &provision_bin_data_size, 0, c);
307                 if (provision_bin_data == NULL) {
308                         d_printf("Failed to read ODJ blob from stdin\n");
309                         return -1;
310                 }
311
312                 /* Strip last newline */
313                 if (provision_bin_data[provision_bin_data_size - 1] == '\n') {
314                         provision_bin_data[provision_bin_data_size - 1] = '\0';
315                 }
316         }
317
318         if (provision_bin_data == NULL || provision_bin_data_size == 0) {
319                 d_printf("Please provide provision data either from file "
320                          "(using loadfile parameter) or from stdin (-i)\n");
321                 return -1;
322         }
323         if (provision_bin_data_size > UINT32_MAX) {
324                 d_printf("provision binary data size too big: %zu\n",
325                          provision_bin_data_size);
326                 return -1;
327         }
328
329         status = NetRequestOfflineDomainJoin(provision_bin_data,
330                                              provision_bin_data_size,
331                                              options,
332                                              windows_path);
333         if (status != 0 && status != 0x00000a99) {
334                 /* NERR_JoinPerformedMustRestart */
335                 printf("Failed to call NetRequestOfflineDomainJoin: %s\n",
336                         libnetapi_get_error_string(c->netapi_ctx, status));
337                 return -1;
338         }
339
340         d_printf("Successfully requested Offline Domain Join\n");
341
342         return 0;
343 }
344
345 static int net_offlinejoin_composeodj_usage(struct net_context *c,
346                                             int argc,
347                                             const char **argv)
348 {
349         d_printf(_("\nnet offlinejoin composeodj [misc. options]\n"
350                    "\tComposes offline domain join blob\n"));
351         d_printf(_("Valid options:\n"));
352         d_printf(_("\tdomain_sid=<SID>\t\t\tThe domain SID\n"));
353         d_printf(_("\tdomain_guid=<GUID>\t\t\tThe domain GUID\n"));
354         d_printf(_("\tforest_name=<NAME>\t\t\tThe forest name\n"));
355         d_printf(_("\tdomain_is_nt4\t\t\t\tThe domain not AD but NT4\n"));
356         d_printf(_("\tsavefile=<FILENAME>\t\t\tFile to store the ODJ data\n"));
357         d_printf(_("\tprintblob\t\t\t\tPrint the base64 encoded ODJ data on stdout\n"));
358         net_common_flags_usage(c, argc, argv);
359         d_printf(_("Example:\n"));
360         d_printf("\tnet offlinejoin composeodj --realm=<realm> "
361                  "--workgroup=<domain> domain_sid=<sid> domain_guid=<guid> "
362                  "forest_name=<name> -S <dc name> -I <dc address> "
363                  "--password=<password> printblob\n");
364         return -1;
365 }
366
367 int net_offlinejoin_composeodj(struct net_context *c,
368                                int argc,
369                                const char **argv)
370 {
371         struct cli_credentials *creds = samba_cmdline_get_creds();
372         NET_API_STATUS status;
373         const char *dns_domain_name = NULL;
374         const char *netbios_domain_name = NULL;
375         const char *machine_account_name = NULL;
376         const char *machine_account_password = NULL;
377         const char *domain_sid_str = NULL;
378         const char *domain_guid_str = NULL;
379         struct dom_sid domain_sid;
380         struct GUID domain_guid;
381         const char *forest_name = NULL;
382         const char *dc_name = NULL;
383         char dc_address[INET6_ADDRSTRLEN] = { 0 };
384         bool domain_is_ad = true;
385         const char *provision_text_data = NULL;
386         const char *savefile = NULL;
387         bool printblob = false;
388         enum credentials_obtained obtained;
389         bool ok;
390         NTSTATUS ntstatus;
391         int i;
392
393         if (c->display_usage || argc < 4) {
394                 return net_offlinejoin_composeodj_usage(c, argc, argv);
395         }
396
397         dns_domain_name = cli_credentials_get_realm(creds);
398         netbios_domain_name = cli_credentials_get_domain(creds);
399
400         machine_account_name = cli_credentials_get_username_and_obtained(creds, &obtained);
401         if (obtained < CRED_CALLBACK_RESULT) {
402                 const char *netbios_name = cli_credentials_get_workstation(creds);
403                 cli_credentials_set_username(
404                         creds,
405                         talloc_asprintf(c, "%s$", netbios_name),
406                         CRED_SPECIFIED);
407         }
408
409         machine_account_name = cli_credentials_get_username(creds);
410         machine_account_password = cli_credentials_get_password(creds);
411         dc_name = c->opt_host;
412
413         if (c->opt_have_ip) {
414                 struct sockaddr_in *in4 = NULL;
415                 struct sockaddr_in6 *in6 = NULL;
416                 const char *p = NULL;
417
418                 switch(c->opt_dest_ip.ss_family) {
419                 case AF_INET:
420                         in4 = (struct sockaddr_in *)&c->opt_dest_ip;
421                         p = inet_ntop(AF_INET, &in4->sin_addr, dc_address, sizeof(dc_address));
422                         break;
423                 case AF_INET6:
424                         in6 = (struct sockaddr_in6 *)&c->opt_dest_ip;
425                         p = inet_ntop(AF_INET6, &in6->sin6_addr, dc_address, sizeof(dc_address));
426                         break;
427                 default:
428                         d_printf("Unknown IP address family\n");
429                         return -1;
430                 }
431
432                 if (p == NULL) {
433                         d_fprintf(stderr, "Failed to parse IP address: %s\n", strerror(errno));
434                         return -1;
435                 }
436         }
437
438         /* process additional command line args */
439
440         for (i = 0; i < argc; i++) {
441                 if (strnequal(argv[i], "domain_sid", strlen("domain_sid"))) {
442                         domain_sid_str = get_string_param(argv[i]);
443                         if (domain_sid_str == NULL) {
444                                 return -1;
445                         }
446                 }
447
448                 if (strnequal(argv[i], "domain_guid", strlen("domain_guid"))) {
449                         domain_guid_str = get_string_param(argv[i]);
450                         if (domain_guid_str == NULL) {
451                                 return -1;
452                         }
453                 }
454
455                 if (strnequal(argv[i], "forest_name", strlen("forest_name"))) {
456                         forest_name = get_string_param(argv[i]);
457                         if (forest_name == NULL) {
458                                 return -1;
459                         }
460                 }
461
462                 if (strnequal(argv[i], "savefile", strlen("savefile"))) {
463                         savefile = get_string_param(argv[i]);
464                         if (savefile == NULL) {
465                                 return -1;
466                         }
467                 }
468
469                 if (strnequal(argv[i], "printblob", strlen("printblob"))) {
470                         printblob = true;
471                 }
472
473                 if (strnequal(argv[i], "domain_is_nt4", strlen("domain_is_nt4"))) {
474                         domain_is_ad = false;
475                 }
476         }
477
478         /* Check command line arguments */
479
480         if (savefile == NULL && !printblob) {
481                 d_printf("Choose either save the blob to a file or print it\n");
482                 return -1;
483         }
484
485         if (dns_domain_name == NULL) {
486                 d_printf("Please provide a valid realm parameter (--realm)\n");
487                 return -1;
488         }
489
490         if (netbios_domain_name == NULL) {
491                 d_printf("Please provide a valid domain parameter (--workgroup)\n");
492                 return -1;
493         }
494
495         if (dc_name == NULL) {
496                 d_printf("Please provide a valid DC name parameter (-S)\n");
497                 return -1;
498         }
499
500         if (strlen(dc_address) == 0) {
501                 d_printf("Please provide a valid domain controller address parameter (-I)\n");
502                 return -1;
503         }
504
505         if (machine_account_name == NULL) {
506                 d_printf("Please provide a valid netbios name parameter\n");
507                 return -1;
508         }
509
510         if (machine_account_password == NULL) {
511                 d_printf("Please provide a valid password parameter\n");
512                 return -1;
513         }
514
515         if (domain_sid_str == NULL) {
516                 d_printf("Please provide a valid <domain_sid> parameter\n");
517                 return -1;
518         }
519
520         if (domain_guid_str == NULL) {
521                 d_printf("Please provide a valid <domain_guid> parameter\n");
522                 return -1;
523         }
524
525         if (forest_name == NULL) {
526                 d_printf("Please provide a valid <forest_name> parameter\n");
527                 return -1;
528         }
529
530         ok = dom_sid_parse(domain_sid_str, &domain_sid);
531         if (!ok) {
532                 d_fprintf(stderr, _("Failed to parse domain SID\n"));
533                 return -1;
534         }
535
536         ntstatus = GUID_from_string(domain_guid_str, &domain_guid);
537         if (NT_STATUS_IS_ERR(ntstatus)) {
538                 d_fprintf(stderr, _("Failed to parse domain GUID\n"));
539                 return -1;
540         }
541
542         status = NetComposeOfflineDomainJoin(dns_domain_name,
543                                              netbios_domain_name,
544                                              (struct domsid *)&domain_sid,
545                                              &domain_guid,
546                                              forest_name,
547                                              machine_account_name,
548                                              machine_account_password,
549                                              dc_name,
550                                              dc_address,
551                                              domain_is_ad,
552                                              NULL,
553                                              0,
554                                              &provision_text_data);
555         if (status != 0) {
556                 d_printf("Failed to compose offline domain join blob: %s\n",
557                         libnetapi_get_error_string(c->netapi_ctx, status));
558                 return status;
559         }
560
561         if (savefile != NULL) {
562                 DATA_BLOB ucs2_blob, blob;
563
564                 /*
565                  * Windows produces and consumes UTF16/UCS2 encoded blobs
566                  * so we also do it for compatibility. Someone may provision an
567                  * account for a Windows machine with samba.
568                  */
569                 ok = push_reg_sz(c, &ucs2_blob, provision_text_data);
570                 if (!ok) {
571                         return -1;
572                 }
573
574                 /* Add the unicode BOM mark */
575                 blob = data_blob_talloc(c, NULL, ucs2_blob.length + 2);
576                 if (blob.data == NULL) {
577                         d_printf("Failed to allocate blob: %s\n",
578                                  strerror(errno));
579                         return -1;
580                 }
581
582                 blob.data[0] = 0xff;
583                 blob.data[1] = 0xfe;
584
585                 memcpy(blob.data + 2, ucs2_blob.data, ucs2_blob.length);
586
587                 ok = file_save(savefile, blob.data, blob.length);
588                 if (!ok) {
589                         d_printf("Failed to save %s: %s\n", savefile,
590                                         strerror(errno));
591                         return -1;
592                 }
593         }
594
595         if (printblob) {
596                 printf("%s\n", provision_text_data);
597         }
598
599         return 0;
600 }