2 Samba Unix/Linux SMB client library
4 Copyright (C) 2001 Andrew Tridgell (tridge@samba.org)
5 Copyright (C) 2001 Remus Koos (remuskoos@yahoo.com)
6 Copyright (C) 2002 Jim McDonough (jmcd@us.ibm.com)
7 Copyright (C) 2006 Gerald (Jerry) Carter (jerry@samba.org)
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 #include "utils/net.h"
27 /* Macro for checking RPC error codes to make things more readable */
30 #define CHECK_RPC_ERR(rpc, msg) \
31 if (!NT_STATUS_IS_OK(result = rpc)) { \
32 DEBUG(0, (msg ": %s\n", nt_errstr(result))); \
36 #define CHECK_RPC_ERR_DEBUG(rpc, debug_args) \
37 if (!NT_STATUS_IS_OK(result = rpc)) { \
38 DEBUG(0, debug_args); \
45 int net_ads_usage(int argc
, const char **argv
)
48 "\nnet ads join <org_unit>"\
49 "\n\tjoins the local machine to a ADS realm\n"\
51 "\n\tremoves the local machine from a ADS realm\n"\
53 "\n\ttests that an exiting join is OK\n"\
55 "\n\tlist, add, or delete users in the realm\n"\
57 "\n\tlist, add, or delete groups in the realm\n"\
59 "\n\tshows some info on the server\n"\
61 "\n\tdump the machine account details to stdout\n"
63 "\n\tperform a CLDAP search on the server\n"
64 "\nnet ads password <username@realm> <password> -Uadmin_username@realm%%admin_pass"\
65 "\n\tchange a user's password using an admin account"\
66 "\n\t(note: use realm in UPPERCASE, prompts if password is obmitted)\n"\
67 "\nnet ads changetrustpw"\
68 "\n\tchange the trust account password of this machine in the AD tree\n"\
69 "\nnet ads printer [info | publish | remove] <printername> <servername>"\
70 "\n\t lookup, add, or remove directory entry for a printer\n"\
72 "\n\tperform a raw LDAP search and dump the results\n"
74 "\n\tperform a raw LDAP search and dump attributes of a particular DN\n"
76 "\n\tperform a raw LDAP search and dump attributes of a particular SID\n"
78 "\n\tcreates and updates the kerberos system keytab file\n"
85 do a cldap netlogon query
87 static int net_ads_cldap_netlogon(ADS_STRUCT
*ads
)
89 struct cldap_netlogon_reply reply
;
91 if ( !ads_cldap_netlogon( inet_ntoa(ads
->ldap_ip
), ads
->server
.realm
, &reply
) ) {
92 d_fprintf(stderr
, "CLDAP query failed!\n");
96 d_printf("Information for Domain Controller: %s\n\n",
97 inet_ntoa(ads
->ldap_ip
));
99 d_printf("Response Type: ");
100 switch (reply
.type
) {
101 case SAMLOGON_AD_UNK_R
:
102 d_printf("SAMLOGON\n");
105 d_printf("SAMLOGON_USER\n");
108 d_printf("0x%x\n", reply
.type
);
111 d_printf("GUID: %s\n",
112 smb_uuid_string_static(smb_uuid_unpack_static(reply
.guid
)));
115 "\tIs a GC of the forest: %s\n"
116 "\tIs an LDAP server: %s\n"
117 "\tSupports DS: %s\n"
118 "\tIs running a KDC: %s\n"
119 "\tIs running time services: %s\n"
120 "\tIs the closest DC: %s\n"
121 "\tIs writable: %s\n"
122 "\tHas a hardware clock: %s\n"
123 "\tIs a non-domain NC serviced by LDAP server: %s\n",
124 (reply
.flags
& ADS_PDC
) ? "yes" : "no",
125 (reply
.flags
& ADS_GC
) ? "yes" : "no",
126 (reply
.flags
& ADS_LDAP
) ? "yes" : "no",
127 (reply
.flags
& ADS_DS
) ? "yes" : "no",
128 (reply
.flags
& ADS_KDC
) ? "yes" : "no",
129 (reply
.flags
& ADS_TIMESERV
) ? "yes" : "no",
130 (reply
.flags
& ADS_CLOSEST
) ? "yes" : "no",
131 (reply
.flags
& ADS_WRITABLE
) ? "yes" : "no",
132 (reply
.flags
& ADS_GOOD_TIMESERV
) ? "yes" : "no",
133 (reply
.flags
& ADS_NDNC
) ? "yes" : "no");
135 printf("Forest:\t\t\t%s\n", reply
.forest
);
136 printf("Domain:\t\t\t%s\n", reply
.domain
);
137 printf("Domain Controller:\t%s\n", reply
.hostname
);
139 printf("Pre-Win2k Domain:\t%s\n", reply
.netbios_domain
);
140 printf("Pre-Win2k Hostname:\t%s\n", reply
.netbios_hostname
);
142 if (*reply
.unk
) printf("Unk:\t\t\t%s\n", reply
.unk
);
143 if (*reply
.user_name
) printf("User name:\t%s\n", reply
.user_name
);
145 printf("Site Name:\t\t%s\n", reply
.site_name
);
146 printf("Site Name (2):\t\t%s\n", reply
.site_name_2
);
148 d_printf("NT Version: %d\n", reply
.version
);
149 d_printf("LMNT Token: %.2x\n", reply
.lmnt_token
);
150 d_printf("LM20 Token: %.2x\n", reply
.lm20_token
);
157 this implements the CLDAP based netlogon lookup requests
158 for finding the domain controller of a ADS domain
160 static int net_ads_lookup(int argc
, const char **argv
)
164 const char *realm
= NULL
;
166 if ( strequal(lp_workgroup(), opt_target_workgroup
) )
169 ads
= ads_init(realm
, opt_target_workgroup
, opt_host
);
171 ads
->auth
.flags
|= ADS_AUTH_NO_BIND
;
174 status
= ads_connect(ads
);
175 if (!ADS_ERR_OK(status
) || !ads
) {
176 d_fprintf(stderr
, "Didn't find the cldap server!\n");
180 if (!ads
->config
.realm
) {
181 ads
->config
.realm
= CONST_DISCARD(char *, opt_target_workgroup
);
182 ads
->ldap_port
= 389;
185 return net_ads_cldap_netlogon(ads
);
190 static int net_ads_info(int argc
, const char **argv
)
194 if ( (ads
= ads_init(lp_realm(), opt_target_workgroup
, opt_host
)) != NULL
) {
195 ads
->auth
.flags
|= ADS_AUTH_NO_BIND
;
200 if (!ads
|| !ads
->config
.realm
) {
201 d_fprintf(stderr
, "Didn't find the ldap server!\n");
205 /* Try to set the server's current time since we didn't do a full
206 TCP LDAP session initially */
208 if ( !ADS_ERR_OK(ads_current_time( ads
)) ) {
209 d_fprintf( stderr
, "Failed to get server's current time!\n");
212 d_printf("LDAP server: %s\n", inet_ntoa(ads
->ldap_ip
));
213 d_printf("LDAP server name: %s\n", ads
->config
.ldap_server_name
);
214 d_printf("Realm: %s\n", ads
->config
.realm
);
215 d_printf("Bind Path: %s\n", ads
->config
.bind_path
);
216 d_printf("LDAP port: %d\n", ads
->ldap_port
);
217 d_printf("Server time: %s\n", http_timestring(ads
->config
.current_time
));
219 d_printf("KDC server: %s\n", ads
->auth
.kdc_server
);
220 d_printf("Server time offset: %d\n", ads
->auth
.time_offset
);
225 static void use_in_memory_ccache(void) {
226 /* Use in-memory credentials cache so we do not interfere with
227 * existing credentials */
228 setenv(KRB5_ENV_CCNAME
, "MEMORY:net_ads", 1);
231 static ADS_STRUCT
*ads_startup(void)
235 BOOL need_password
= False
;
236 BOOL second_time
= False
;
239 /* lp_realm() should be handled by a command line param,
240 However, the join requires that realm be set in smb.conf
241 and compares our realm with the remote server's so this is
242 ok until someone needs more flexibility */
244 ads
= ads_init(lp_realm(), opt_target_workgroup
, opt_host
);
246 if (!opt_user_name
) {
247 opt_user_name
= "administrator";
250 if (opt_user_specified
) {
251 need_password
= True
;
255 if (!opt_password
&& need_password
&& !opt_machine_pass
) {
257 asprintf(&prompt
,"%s's password: ", opt_user_name
);
258 opt_password
= getpass(prompt
);
263 use_in_memory_ccache();
264 ads
->auth
.password
= smb_xstrdup(opt_password
);
267 ads
->auth
.user_name
= smb_xstrdup(opt_user_name
);
270 * If the username is of the form "name@realm",
271 * extract the realm and convert to upper case.
272 * This is only used to establish the connection.
274 if ((cp
= strchr_m(ads
->auth
.user_name
, '@'))!=0) {
276 ads
->auth
.realm
= smb_xstrdup(cp
);
277 strupper_m(ads
->auth
.realm
);
280 status
= ads_connect(ads
);
282 if (!ADS_ERR_OK(status
)) {
283 if (!need_password
&& !second_time
) {
284 need_password
= True
;
288 DEBUG(0,("ads_connect: %s\n", ads_errstr(status
)));
297 Check to see if connection can be made via ads.
298 ads_startup() stores the password in opt_password if it needs to so
299 that rpc or rap can use it without re-prompting.
301 int net_ads_check(void)
306 if ( (ads
= ads_init( lp_realm(), lp_workgroup(), NULL
)) == NULL
) {
310 ads
->auth
.flags
|= ADS_AUTH_NO_BIND
;
312 status
= ads_connect(ads
);
313 if ( !ADS_ERR_OK(status
) ) {
322 determine the netbios workgroup name for a domain
324 static int net_ads_workgroup(int argc
, const char **argv
)
328 const char *realm
= NULL
;
329 struct cldap_netlogon_reply reply
;
331 if ( strequal(lp_workgroup(), opt_target_workgroup
) )
334 ads
= ads_init(realm
, opt_target_workgroup
, opt_host
);
336 ads
->auth
.flags
|= ADS_AUTH_NO_BIND
;
339 status
= ads_connect(ads
);
340 if (!ADS_ERR_OK(status
) || !ads
) {
341 d_fprintf(stderr
, "Didn't find the cldap server!\n");
345 if (!ads
->config
.realm
) {
346 ads
->config
.realm
= CONST_DISCARD(char *, opt_target_workgroup
);
347 ads
->ldap_port
= 389;
350 if ( !ads_cldap_netlogon( inet_ntoa(ads
->ldap_ip
), ads
->server
.realm
, &reply
) ) {
351 d_fprintf(stderr
, "CLDAP query failed!\n");
355 d_printf("Workgroup: %s\n", reply
.netbios_domain
);
364 static BOOL
usergrp_display(char *field
, void **values
, void *data_area
)
366 char **disp_fields
= (char **) data_area
;
368 if (!field
) { /* must be end of record */
369 if (disp_fields
[0]) {
370 if (!strchr_m(disp_fields
[0], '$')) {
372 d_printf("%-21.21s %s\n",
373 disp_fields
[0], disp_fields
[1]);
375 d_printf("%s\n", disp_fields
[0]);
378 SAFE_FREE(disp_fields
[0]);
379 SAFE_FREE(disp_fields
[1]);
382 if (!values
) /* must be new field, indicate string field */
384 if (StrCaseCmp(field
, "sAMAccountName") == 0) {
385 disp_fields
[0] = SMB_STRDUP((char *) values
[0]);
387 if (StrCaseCmp(field
, "description") == 0)
388 disp_fields
[1] = SMB_STRDUP((char *) values
[0]);
392 static int net_ads_user_usage(int argc
, const char **argv
)
394 return net_help_user(argc
, argv
);
397 static int ads_user_add(int argc
, const char **argv
)
405 if (argc
< 1) return net_ads_user_usage(argc
, argv
);
407 if (!(ads
= ads_startup())) {
411 status
= ads_find_user_acct(ads
, &res
, argv
[0]);
413 if (!ADS_ERR_OK(status
)) {
414 d_fprintf(stderr
, "ads_user_add: %s\n", ads_errstr(status
));
418 if (ads_count_replies(ads
, res
)) {
419 d_fprintf(stderr
, "ads_user_add: User %s already exists\n", argv
[0]);
423 if (opt_container
== NULL
) {
424 opt_container
= ads_default_ou_string(ads
, WELL_KNOWN_GUID_USERS
);
427 status
= ads_add_user_acct(ads
, argv
[0], opt_container
, opt_comment
);
429 if (!ADS_ERR_OK(status
)) {
430 d_fprintf(stderr
, "Could not add user %s: %s\n", argv
[0],
435 /* if no password is to be set, we're done */
437 d_printf("User %s added\n", argv
[0]);
442 /* try setting the password */
443 asprintf(&upn
, "%s@%s", argv
[0], ads
->config
.realm
);
444 status
= ads_krb5_set_password(ads
->auth
.kdc_server
, upn
, argv
[1],
445 ads
->auth
.time_offset
);
447 if (ADS_ERR_OK(status
)) {
448 d_printf("User %s added\n", argv
[0]);
453 /* password didn't set, delete account */
454 d_fprintf(stderr
, "Could not add user %s. Error setting password %s\n",
455 argv
[0], ads_errstr(status
));
456 ads_msgfree(ads
, res
);
457 status
=ads_find_user_acct(ads
, &res
, argv
[0]);
458 if (ADS_ERR_OK(status
)) {
459 userdn
= ads_get_dn(ads
, res
);
460 ads_del_dn(ads
, userdn
);
461 ads_memfree(ads
, userdn
);
466 ads_msgfree(ads
, res
);
471 static int ads_user_info(int argc
, const char **argv
)
476 const char *attrs
[] = {"memberOf", NULL
};
477 char *searchstring
=NULL
;
482 return net_ads_user_usage(argc
, argv
);
485 escaped_user
= escape_ldap_string_alloc(argv
[0]);
488 d_fprintf(stderr
, "ads_user_info: failed to escape user %s\n", argv
[0]);
492 if (!(ads
= ads_startup())) {
493 SAFE_FREE(escaped_user
);
497 asprintf(&searchstring
, "(sAMAccountName=%s)", escaped_user
);
498 rc
= ads_search(ads
, &res
, searchstring
, attrs
);
499 safe_free(searchstring
);
501 if (!ADS_ERR_OK(rc
)) {
502 d_fprintf(stderr
, "ads_search: %s\n", ads_errstr(rc
));
504 SAFE_FREE(escaped_user
);
508 grouplist
= ldap_get_values(ads
->ld
, res
, "memberOf");
513 for (i
=0;grouplist
[i
];i
++) {
514 groupname
= ldap_explode_dn(grouplist
[i
], 1);
515 d_printf("%s\n", groupname
[0]);
516 ldap_value_free(groupname
);
518 ldap_value_free(grouplist
);
521 ads_msgfree(ads
, res
);
523 SAFE_FREE(escaped_user
);
527 static int ads_user_delete(int argc
, const char **argv
)
535 return net_ads_user_usage(argc
, argv
);
538 if (!(ads
= ads_startup())) {
542 rc
= ads_find_user_acct(ads
, &res
, argv
[0]);
543 if (!ADS_ERR_OK(rc
)) {
544 DEBUG(0, ("User %s does not exist\n", argv
[0]));
548 userdn
= ads_get_dn(ads
, res
);
549 ads_msgfree(ads
, res
);
550 rc
= ads_del_dn(ads
, userdn
);
551 ads_memfree(ads
, userdn
);
552 if (!ADS_ERR_OK(rc
)) {
553 d_printf("User %s deleted\n", argv
[0]);
557 d_fprintf(stderr
, "Error deleting user %s: %s\n", argv
[0],
563 int net_ads_user(int argc
, const char **argv
)
565 struct functable func
[] = {
566 {"ADD", ads_user_add
},
567 {"INFO", ads_user_info
},
568 {"DELETE", ads_user_delete
},
573 const char *shortattrs
[] = {"sAMAccountName", NULL
};
574 const char *longattrs
[] = {"sAMAccountName", "description", NULL
};
575 char *disp_fields
[2] = {NULL
, NULL
};
578 if (!(ads
= ads_startup())) {
582 if (opt_long_list_entries
)
583 d_printf("\nUser name Comment"\
584 "\n-----------------------------\n");
586 rc
= ads_do_search_all_fn(ads
, ads
->config
.bind_path
,
588 "(objectCategory=user)",
589 opt_long_list_entries
? longattrs
:
590 shortattrs
, usergrp_display
,
596 return net_run_function(argc
, argv
, func
, net_ads_user_usage
);
599 static int net_ads_group_usage(int argc
, const char **argv
)
601 return net_help_group(argc
, argv
);
604 static int ads_group_add(int argc
, const char **argv
)
612 return net_ads_group_usage(argc
, argv
);
615 if (!(ads
= ads_startup())) {
619 status
= ads_find_user_acct(ads
, &res
, argv
[0]);
621 if (!ADS_ERR_OK(status
)) {
622 d_fprintf(stderr
, "ads_group_add: %s\n", ads_errstr(status
));
626 if (ads_count_replies(ads
, res
)) {
627 d_fprintf(stderr
, "ads_group_add: Group %s already exists\n", argv
[0]);
628 ads_msgfree(ads
, res
);
632 if (opt_container
== NULL
) {
633 opt_container
= ads_default_ou_string(ads
, WELL_KNOWN_GUID_USERS
);
636 status
= ads_add_group_acct(ads
, argv
[0], opt_container
, opt_comment
);
638 if (ADS_ERR_OK(status
)) {
639 d_printf("Group %s added\n", argv
[0]);
642 d_fprintf(stderr
, "Could not add group %s: %s\n", argv
[0],
648 ads_msgfree(ads
, res
);
653 static int ads_group_delete(int argc
, const char **argv
)
661 return net_ads_group_usage(argc
, argv
);
664 if (!(ads
= ads_startup())) {
668 rc
= ads_find_user_acct(ads
, &res
, argv
[0]);
669 if (!ADS_ERR_OK(rc
)) {
670 DEBUG(0, ("Group %s does not exist\n", argv
[0]));
674 groupdn
= ads_get_dn(ads
, res
);
675 ads_msgfree(ads
, res
);
676 rc
= ads_del_dn(ads
, groupdn
);
677 ads_memfree(ads
, groupdn
);
678 if (!ADS_ERR_OK(rc
)) {
679 d_printf("Group %s deleted\n", argv
[0]);
683 d_fprintf(stderr
, "Error deleting group %s: %s\n", argv
[0],
689 int net_ads_group(int argc
, const char **argv
)
691 struct functable func
[] = {
692 {"ADD", ads_group_add
},
693 {"DELETE", ads_group_delete
},
698 const char *shortattrs
[] = {"sAMAccountName", NULL
};
699 const char *longattrs
[] = {"sAMAccountName", "description", NULL
};
700 char *disp_fields
[2] = {NULL
, NULL
};
703 if (!(ads
= ads_startup())) {
707 if (opt_long_list_entries
)
708 d_printf("\nGroup name Comment"\
709 "\n-----------------------------\n");
710 rc
= ads_do_search_all_fn(ads
, ads
->config
.bind_path
,
712 "(objectCategory=group)",
713 opt_long_list_entries
? longattrs
:
714 shortattrs
, usergrp_display
,
720 return net_run_function(argc
, argv
, func
, net_ads_group_usage
);
723 static int net_ads_status(int argc
, const char **argv
)
729 if (!(ads
= ads_startup())) {
733 rc
= ads_find_machine_acct(ads
, &res
, global_myname());
734 if (!ADS_ERR_OK(rc
)) {
735 d_fprintf(stderr
, "ads_find_machine_acct: %s\n", ads_errstr(rc
));
740 if (ads_count_replies(ads
, res
) == 0) {
741 d_fprintf(stderr
, "No machine account for '%s' found\n", global_myname());
751 /*******************************************************************
752 Leave an AD domain. Windows XP disables the machine account.
753 We'll try the same. The old code would do an LDAP delete.
754 That only worked using the machine creds because added the machine
755 with full control to the computer object's ACL.
756 *******************************************************************/
757 static int net_ads_leave(int argc
, const char **argv
)
759 ADS_STRUCT
*ads
= NULL
;
761 struct cli_state
*cli
= NULL
;
763 DOM_SID
*dom_sid
= NULL
;
765 if (!secrets_init()) {
766 DEBUG(1,("Failed to initialise secrets database\n"));
770 if (!(ctx
= talloc_init("net_ads_leave"))) {
771 DEBUG(0, ("Could not initialise talloc context\n"));
775 /* The finds a DC and takes care of getting the
776 user creds if necessary */
778 if (!(ads
= ads_startup())) {
782 /* make RPC calls here */
784 if ( !NT_STATUS_IS_OK(connect_to_ipc_krb5(&cli
, &ads
->ldap_ip
,
785 ads
->config
.ldap_server_name
)) )
790 saf_store( cli
->server_domain
, cli
->desthost
);
792 if ( !NT_STATUS_IS_OK(netdom_get_domain_sid( ctx
, cli
, &dom_sid
)) ) {
796 if ( !NT_STATUS_IS_OK(netdom_leave_domain( ctx
, cli
, dom_sid
)) ) {
797 d_fprintf(stderr
, "Failed to disable machine account for '%s' in realm '%s'\n",
798 global_myname(), ads
->config
.realm
);
802 d_printf("Disabled account for '%s' in realm '%s'\n",
803 global_myname(), ads
->config
.realm
);
817 static int net_ads_join_ok(void)
819 ADS_STRUCT
*ads
= NULL
;
821 if (!secrets_init()) {
822 DEBUG(1,("Failed to initialise secrets database\n"));
826 net_use_machine_password();
828 if (!(ads
= ads_startup())) {
837 check that an existing join is OK
839 int net_ads_testjoin(int argc
, const char **argv
)
841 use_in_memory_ccache();
843 /* Display success or failure */
844 if (net_ads_join_ok() != 0) {
845 fprintf(stderr
,"Join to domain is not valid\n");
849 printf("Join is OK\n");
853 /*******************************************************************
854 Simple configu checks before beginning the join
855 ********************************************************************/
857 static int check_ads_config( void )
859 if (lp_server_role() != ROLE_DOMAIN_MEMBER
) {
860 d_printf("Host is not configured as a member server.\n");
864 if (strlen(global_myname()) > 15) {
865 d_printf("Our netbios name can be at most 15 chars long, "
866 "\"%s\" is %d chars long\n",
867 global_myname(), strlen(global_myname()));
871 if ( lp_security() == SEC_ADS
&& !*lp_realm()) {
872 d_fprintf(stderr
, "realm must be set in in smb.conf for ADS "
873 "join to succeed.\n");
877 if (!secrets_init()) {
878 DEBUG(1,("Failed to initialise secrets database\n"));
885 /*******************************************************************
887 ********************************************************************/
889 static int net_join_domain( TALLOC_CTX
*ctx
, const char *servername
,
890 struct in_addr
*ip
, DOM_SID
**dom_sid
, const char *password
)
893 struct cli_state
*cli
= NULL
;
895 if ( !NT_STATUS_IS_OK(connect_to_ipc_krb5(&cli
, ip
, servername
)) )
898 saf_store( cli
->server_domain
, cli
->desthost
);
900 if ( !NT_STATUS_IS_OK(netdom_get_domain_sid( ctx
, cli
, dom_sid
)) )
903 if ( !NT_STATUS_IS_OK(netdom_join_domain( ctx
, cli
, *dom_sid
,
904 password
, ND_TYPE_AD
)) )
918 /*******************************************************************
919 Set a machines dNSHostName and servicePrincipalName attributes
920 ********************************************************************/
922 static ADS_STATUS
net_set_machine_spn(TALLOC_CTX
*ctx
, ADS_STRUCT
*ads_s
)
924 ADS_STATUS status
= ADS_ERROR(LDAP_SERVER_DOWN
);
925 char *host_upn
, *new_dn
;
927 const char *servicePrincipalName
[3] = {NULL
, NULL
, NULL
};
930 LDAPMessage
*res
= NULL
;
931 char *dn_string
= NULL
;
932 const char *machine_name
= global_myname();
935 if ( !machine_name
) {
936 return ADS_ERROR(LDAP_NO_MEMORY
);
941 status
= ads_find_machine_acct(ads_s
, (void **)(void *)&res
, machine_name
);
942 if (!ADS_ERR_OK(status
))
945 if ( (count
= ads_count_replies(ads_s
, res
)) != 1 ) {
946 DEBUG(1,("net_set_machine_spn: %d entries returned!\n", count
));
947 return ADS_ERROR(LDAP_NO_MEMORY
);
950 if ( (dn_string
= ads_get_dn(ads_s
, res
)) == NULL
) {
951 DEBUG(1, ("ads_add_machine_acct: ads_get_dn returned NULL (malloc failure?)\n"));
955 new_dn
= talloc_strdup(ctx
, dn_string
);
956 ads_memfree(ads_s
, dn_string
);
958 return ADS_ERROR(LDAP_NO_MEMORY
);
961 /* Windows only creates HOST/shortname & HOST/fqdn. We create
962 the UPN as well so that 'kinit -k' will work. You can only
963 request a TGT for entries with a UPN in AD. */
965 if ( !(psp
= talloc_asprintf(ctx
, "HOST/%s", machine_name
)) )
968 servicePrincipalName
[0] = psp
;
970 name_to_fqdn(my_fqdn
, machine_name
);
972 if ( !(psp
= talloc_asprintf(ctx
, "HOST/%s", my_fqdn
)) )
974 servicePrincipalName
[1] = psp
;
976 if (!(host_upn
= talloc_asprintf(ctx
, "%s@%s", servicePrincipalName
[0], ads_s
->config
.realm
)))
979 /* now do the mods */
981 if (!(mods
= ads_init_mods(ctx
))) {
985 /* fields of primary importance */
987 ads_mod_str(ctx
, &mods
, "dNSHostName", my_fqdn
);
988 ads_mod_strlist(ctx
, &mods
, "servicePrincipalName", servicePrincipalName
);
990 status
= ads_gen_mod(ads_s
, new_dn
, mods
);
993 ads_msgfree(ads_s
, res
);
999 /*******************************************************************
1000 join a domain using ADS (LDAP mods)
1001 ********************************************************************/
1003 static ADS_STATUS
net_precreate_machine_acct( ADS_STRUCT
*ads
, const char *ou
)
1005 ADS_STATUS rc
= ADS_ERROR(LDAP_SERVER_DOWN
);
1007 LDAPMessage
*res
= NULL
;
1009 ou_str
= ads_ou_string(ads
, ou
);
1010 asprintf(&dn
, "%s,%s", ou_str
, ads
->config
.bind_path
);
1013 rc
= ads_search_dn(ads
, (void**)&res
, dn
, NULL
);
1014 ads_msgfree(ads
, res
);
1016 if (ADS_ERR_OK(rc
)) {
1017 /* Attempt to create the machine account and bail if this fails.
1018 Assume that the admin wants exactly what they requested */
1020 rc
= ads_create_machine_acct( ads
, global_myname(), dn
);
1021 if ( rc
.error_type
== ENUM_ADS_ERROR_LDAP
&& rc
.err
.rc
== LDAP_ALREADY_EXISTS
) {
1031 /*******************************************************************
1032 join a domain using ADS (LDAP mods)
1033 ********************************************************************/
1035 int net_ads_join(int argc
, const char **argv
)
1039 char *machine_account
= NULL
;
1040 const char *short_domain_name
= NULL
;
1041 char *tmp_password
, *password
;
1042 struct cldap_netlogon_reply cldap_reply
;
1044 DOM_SID
*domain_sid
= NULL
;
1046 if ( check_ads_config() != 0 ) {
1047 d_fprintf(stderr
, "Invalid configuration. Exiting....\n");
1051 if ( (ads
= ads_startup()) == NULL
) {
1055 if (strcmp(ads
->config
.realm
, lp_realm()) != 0) {
1056 d_fprintf(stderr
, "realm of remote server (%s) and realm in smb.conf "
1057 "(%s) DO NOT match. Aborting join\n", ads
->config
.realm
,
1063 if (!(ctx
= talloc_init("net_ads_join"))) {
1064 DEBUG(0, ("Could not initialise talloc context\n"));
1068 /* If we were given an OU, try to create the machine in the OU account
1069 first and then do the normal RPC join */
1072 status
= net_precreate_machine_acct( ads
, argv
[0] );
1073 if ( !ADS_ERR_OK(status
) ) {
1074 d_fprintf( stderr
, "Failed to pre-create the machine object "
1075 "in OU %s.\n", argv
[0]);
1076 ads_destroy( &ads
);
1081 /* Do the domain join here */
1083 tmp_password
= generate_random_str(DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH
);
1084 password
= talloc_strdup(ctx
, tmp_password
);
1086 if ( net_join_domain( ctx
, ads
->config
.ldap_server_name
, &ads
->ldap_ip
, &domain_sid
, password
) != 0 ) {
1087 d_fprintf(stderr
, "Failed to join domain!\n");
1091 /* Check the short name of the domain */
1093 ZERO_STRUCT( cldap_reply
);
1095 if ( ads_cldap_netlogon( ads
->config
.ldap_server_name
,
1096 ads
->server
.realm
, &cldap_reply
) )
1098 short_domain_name
= talloc_strdup( ctx
, cldap_reply
.netbios_domain
);
1099 if ( !strequal(lp_workgroup(), short_domain_name
) ) {
1100 d_printf("The workgroup in smb.conf does not match the short\n");
1101 d_printf("domain name obtained from the server.\n");
1102 d_printf("Using the name [%s] from the server.\n", short_domain_name
);
1103 d_printf("You should set \"workgroup = %s\" in smb.conf.\n", short_domain_name
);
1106 short_domain_name
= lp_workgroup();
1109 d_printf("Using short domain name -- %s\n", short_domain_name
);
1111 /* HACK ALERT! Store the sid and password under both the lp_workgroup()
1112 value from smb.conf and the string returned from the server. The former is
1113 neede to bootstrap winbindd's first connection to the DC to get the real
1114 short domain name --jerry */
1116 if ( (netdom_store_machine_account( lp_workgroup(), domain_sid
, password
) == -1)
1117 || (netdom_store_machine_account( short_domain_name
, domain_sid
, password
) == -1) )
1123 /* Verify that everything is ok */
1125 if ( net_rpc_join_ok(short_domain_name
, ads
->config
.ldap_server_name
, &ads
->ldap_ip
) != 0 ) {
1126 d_fprintf(stderr
, "Failed to verify membership in domain!\n");
1130 /* create the dNSHostName & servicePrincipalName values */
1132 status
= net_set_machine_spn( ctx
, ads
);
1133 if ( !ADS_ERR_OK(status
) ) {
1134 d_fprintf(stderr
, "Failed to set servicePrincipalNames. Only NTLM authentication will be possible.\n");
1135 d_fprintf(stderr
, "Please ensure that the DNS domain of this server matches the AD domain,\n");
1136 d_fprintf(stderr
, "Or rejoin with using Domain Admin credentials.\n");
1141 #if defined(HAVE_KRB5)
1142 if (asprintf(&machine_account
, "%s$", global_myname()) == -1) {
1143 d_fprintf(stderr
, "asprintf failed\n");
1148 if (!kerberos_derive_salting_principal(machine_account
)) {
1149 DEBUG(1,("Failed to determine salting principal\n"));
1154 if (!kerberos_derive_cifs_salting_principals()) {
1155 DEBUG(1,("Failed to determine salting principals\n"));
1160 /* Now build the keytab, using the same ADS connection */
1161 if (lp_use_kerberos_keytab() && ads_keytab_create_default(ads
)) {
1162 DEBUG(1,("Error creating host keytab!\n"));
1166 d_printf("Joined '%s' to realm '%s'\n", global_myname(), ads
->config
.realm
);
1168 SAFE_FREE(machine_account
);
1175 /*******************************************************************
1176 ********************************************************************/
1178 int net_ads_printer_usage(int argc
, const char **argv
)
1181 "\nnet ads printer search <printer>"
1182 "\n\tsearch for a printer in the directory\n"
1183 "\nnet ads printer info <printer> <server>"
1184 "\n\tlookup info in directory for printer on server"
1185 "\n\t(note: printer defaults to \"*\", server defaults to local)\n"
1186 "\nnet ads printer publish <printername>"
1187 "\n\tpublish printer in directory"
1188 "\n\t(note: printer name is required)\n"
1189 "\nnet ads printer remove <printername>"
1190 "\n\tremove printer from directory"
1191 "\n\t(note: printer name is required)\n");
1195 /*******************************************************************
1196 ********************************************************************/
1198 static int net_ads_printer_search(int argc
, const char **argv
)
1204 if (!(ads
= ads_startup())) {
1208 rc
= ads_find_printers(ads
, &res
);
1210 if (!ADS_ERR_OK(rc
)) {
1211 d_fprintf(stderr
, "ads_find_printer: %s\n", ads_errstr(rc
));
1212 ads_msgfree(ads
, res
);
1217 if (ads_count_replies(ads
, res
) == 0) {
1218 d_fprintf(stderr
, "No results found\n");
1219 ads_msgfree(ads
, res
);
1225 ads_msgfree(ads
, res
);
1230 static int net_ads_printer_info(int argc
, const char **argv
)
1234 const char *servername
, *printername
;
1237 if (!(ads
= ads_startup())) {
1242 printername
= argv
[0];
1248 servername
= argv
[1];
1250 servername
= global_myname();
1253 rc
= ads_find_printer_on_server(ads
, &res
, printername
, servername
);
1255 if (!ADS_ERR_OK(rc
)) {
1256 d_fprintf(stderr
, "ads_find_printer_on_server: %s\n", ads_errstr(rc
));
1257 ads_msgfree(ads
, res
);
1262 if (ads_count_replies(ads
, res
) == 0) {
1263 d_fprintf(stderr
, "Printer '%s' not found\n", printername
);
1264 ads_msgfree(ads
, res
);
1270 ads_msgfree(ads
, res
);
1276 void do_drv_upgrade_printer(int msg_type
, struct process_id src
,
1277 void *buf
, size_t len
)
1282 static int net_ads_printer_publish(int argc
, const char **argv
)
1286 const char *servername
, *printername
;
1287 struct cli_state
*cli
;
1288 struct rpc_pipe_client
*pipe_hnd
;
1289 struct in_addr server_ip
;
1291 TALLOC_CTX
*mem_ctx
= talloc_init("net_ads_printer_publish");
1292 ADS_MODLIST mods
= ads_init_mods(mem_ctx
);
1293 char *prt_dn
, *srv_dn
, **srv_cn
;
1296 if (!(ads
= ads_startup())) {
1301 return net_ads_printer_usage(argc
, argv
);
1304 printername
= argv
[0];
1307 servername
= argv
[1];
1309 servername
= global_myname();
1312 /* Get printer data from SPOOLSS */
1314 resolve_name(servername
, &server_ip
, 0x20);
1316 nt_status
= cli_full_connection(&cli
, global_myname(), servername
,
1319 opt_user_name
, opt_workgroup
,
1320 opt_password
? opt_password
: "",
1321 CLI_FULL_CONNECTION_USE_KERBEROS
,
1324 if (NT_STATUS_IS_ERR(nt_status
)) {
1325 d_fprintf(stderr
, "Unable to open a connnection to %s to obtain data "
1326 "for %s\n", servername
, printername
);
1331 /* Publish on AD server */
1333 ads_find_machine_acct(ads
, &res
, servername
);
1335 if (ads_count_replies(ads
, res
) == 0) {
1336 d_fprintf(stderr
, "Could not find machine account for server %s\n",
1342 srv_dn
= ldap_get_dn(ads
->ld
, res
);
1343 srv_cn
= ldap_explode_dn(srv_dn
, 1);
1345 asprintf(&prt_dn
, "cn=%s-%s,%s", srv_cn
[0], printername
, srv_dn
);
1347 pipe_hnd
= cli_rpc_pipe_open_noauth(cli
, PI_SPOOLSS
, &nt_status
);
1349 d_fprintf(stderr
, "Unable to open a connnection to the spoolss pipe on %s\n",
1355 get_remote_printer_publishing_data(pipe_hnd
, mem_ctx
, &mods
,
1358 rc
= ads_add_printer_entry(ads
, prt_dn
, mem_ctx
, &mods
);
1359 if (!ADS_ERR_OK(rc
)) {
1360 d_fprintf(stderr
, "ads_publish_printer: %s\n", ads_errstr(rc
));
1365 d_printf("published printer\n");
1371 static int net_ads_printer_remove(int argc
, const char **argv
)
1375 const char *servername
;
1379 if (!(ads
= ads_startup())) {
1384 return net_ads_printer_usage(argc
, argv
);
1388 servername
= argv
[1];
1390 servername
= global_myname();
1393 rc
= ads_find_printer_on_server(ads
, &res
, argv
[0], servername
);
1395 if (!ADS_ERR_OK(rc
)) {
1396 d_fprintf(stderr
, "ads_find_printer_on_server: %s\n", ads_errstr(rc
));
1397 ads_msgfree(ads
, res
);
1402 if (ads_count_replies(ads
, res
) == 0) {
1403 d_fprintf(stderr
, "Printer '%s' not found\n", argv
[1]);
1404 ads_msgfree(ads
, res
);
1409 prt_dn
= ads_get_dn(ads
, res
);
1410 ads_msgfree(ads
, res
);
1411 rc
= ads_del_dn(ads
, prt_dn
);
1412 ads_memfree(ads
, prt_dn
);
1414 if (!ADS_ERR_OK(rc
)) {
1415 d_fprintf(stderr
, "ads_del_dn: %s\n", ads_errstr(rc
));
1424 static int net_ads_printer(int argc
, const char **argv
)
1426 struct functable func
[] = {
1427 {"SEARCH", net_ads_printer_search
},
1428 {"INFO", net_ads_printer_info
},
1429 {"PUBLISH", net_ads_printer_publish
},
1430 {"REMOVE", net_ads_printer_remove
},
1434 return net_run_function(argc
, argv
, func
, net_ads_printer_usage
);
1438 static int net_ads_password(int argc
, const char **argv
)
1441 const char *auth_principal
= opt_user_name
;
1442 const char *auth_password
= opt_password
;
1444 char *new_password
= NULL
;
1449 if (opt_user_name
== NULL
|| opt_password
== NULL
) {
1450 d_fprintf(stderr
, "You must supply an administrator username/password\n");
1455 d_fprintf(stderr
, "ERROR: You must say which username to change password for\n");
1460 if (!strchr_m(user
, '@')) {
1461 asprintf(&c
, "%s@%s", argv
[0], lp_realm());
1465 use_in_memory_ccache();
1466 c
= strchr_m(auth_principal
, '@');
1473 /* use the realm so we can eventually change passwords for users
1474 in realms other than default */
1475 if (!(ads
= ads_init(realm
, NULL
, NULL
))) {
1479 /* we don't actually need a full connect, but it's the easy way to
1480 fill in the KDC's addresss */
1483 if (!ads
|| !ads
->config
.realm
) {
1484 d_fprintf(stderr
, "Didn't find the kerberos server!\n");
1489 new_password
= (char *)argv
[1];
1491 asprintf(&prompt
, "Enter new password for %s:", user
);
1492 new_password
= getpass(prompt
);
1496 ret
= kerberos_set_password(ads
->auth
.kdc_server
, auth_principal
,
1497 auth_password
, user
, new_password
, ads
->auth
.time_offset
);
1498 if (!ADS_ERR_OK(ret
)) {
1499 d_fprintf(stderr
, "Password change failed :-( ...\n");
1504 d_printf("Password change for %s completed.\n", user
);
1510 int net_ads_changetrustpw(int argc
, const char **argv
)
1513 char *host_principal
;
1517 if (!secrets_init()) {
1518 DEBUG(1,("Failed to initialise secrets database\n"));
1522 net_use_machine_password();
1524 use_in_memory_ccache();
1526 if (!(ads
= ads_startup())) {
1530 fstrcpy(my_name
, global_myname());
1531 strlower_m(my_name
);
1532 asprintf(&host_principal
, "%s@%s", my_name
, ads
->config
.realm
);
1533 d_printf("Changing password for principal: HOST/%s\n", host_principal
);
1535 ret
= ads_change_trust_account_password(ads
, host_principal
);
1537 if (!ADS_ERR_OK(ret
)) {
1538 d_fprintf(stderr
, "Password change failed :-( ...\n");
1540 SAFE_FREE(host_principal
);
1544 d_printf("Password change for principal HOST/%s succeeded.\n", host_principal
);
1546 if (lp_use_kerberos_keytab()) {
1547 d_printf("Attempting to update system keytab with new password.\n");
1548 if (ads_keytab_create_default(ads
)) {
1549 d_printf("Failed to update system keytab.\n");
1554 SAFE_FREE(host_principal
);
1560 help for net ads search
1562 static int net_ads_search_usage(int argc
, const char **argv
)
1565 "\nnet ads search <expression> <attributes...>\n"\
1566 "\nperform a raw LDAP search on a ADS server and dump the results\n"\
1567 "The expression is a standard LDAP search expression, and the\n"\
1568 "attributes are a list of LDAP fields to show in the results\n\n"\
1569 "Example: net ads search '(objectCategory=group)' sAMAccountName\n\n"
1571 net_common_flags_usage(argc
, argv
);
1577 general ADS search function. Useful in diagnosing problems in ADS
1579 static int net_ads_search(int argc
, const char **argv
)
1583 const char *ldap_exp
;
1588 return net_ads_search_usage(argc
, argv
);
1591 if (!(ads
= ads_startup())) {
1598 rc
= ads_do_search_all(ads
, ads
->config
.bind_path
,
1600 ldap_exp
, attrs
, &res
);
1601 if (!ADS_ERR_OK(rc
)) {
1602 d_fprintf(stderr
, "search failed: %s\n", ads_errstr(rc
));
1607 d_printf("Got %d replies\n\n", ads_count_replies(ads
, res
));
1609 /* dump the results */
1612 ads_msgfree(ads
, res
);
1620 help for net ads search
1622 static int net_ads_dn_usage(int argc
, const char **argv
)
1625 "\nnet ads dn <dn> <attributes...>\n"\
1626 "\nperform a raw LDAP search on a ADS server and dump the results\n"\
1627 "The DN standard LDAP DN, and the attributes are a list of LDAP fields \n"\
1628 "to show in the results\n\n"\
1629 "Example: net ads dn 'CN=administrator,CN=Users,DC=my,DC=domain' sAMAccountName\n\n"
1631 net_common_flags_usage(argc
, argv
);
1637 general ADS search function. Useful in diagnosing problems in ADS
1639 static int net_ads_dn(int argc
, const char **argv
)
1648 return net_ads_dn_usage(argc
, argv
);
1651 if (!(ads
= ads_startup())) {
1658 rc
= ads_do_search_all(ads
, dn
,
1660 "(objectclass=*)", attrs
, &res
);
1661 if (!ADS_ERR_OK(rc
)) {
1662 d_fprintf(stderr
, "search failed: %s\n", ads_errstr(rc
));
1667 d_printf("Got %d replies\n\n", ads_count_replies(ads
, res
));
1669 /* dump the results */
1672 ads_msgfree(ads
, res
);
1679 help for net ads sid search
1681 static int net_ads_sid_usage(int argc
, const char **argv
)
1684 "\nnet ads sid <sid> <attributes...>\n"\
1685 "\nperform a raw LDAP search on a ADS server and dump the results\n"\
1686 "The SID is in string format, and the attributes are a list of LDAP fields \n"\
1687 "to show in the results\n\n"\
1688 "Example: net ads sid 'S-1-5-32' distinguishedName\n\n"
1690 net_common_flags_usage(argc
, argv
);
1696 general ADS search function. Useful in diagnosing problems in ADS
1698 static int net_ads_sid(int argc
, const char **argv
)
1702 const char *sid_string
;
1708 return net_ads_sid_usage(argc
, argv
);
1711 if (!(ads
= ads_startup())) {
1715 sid_string
= argv
[0];
1718 if (!string_to_sid(&sid
, sid_string
)) {
1719 d_fprintf(stderr
, "could not convert sid\n");
1724 rc
= ads_search_retry_sid(ads
, &res
, &sid
, attrs
);
1725 if (!ADS_ERR_OK(rc
)) {
1726 d_fprintf(stderr
, "search failed: %s\n", ads_errstr(rc
));
1731 d_printf("Got %d replies\n\n", ads_count_replies(ads
, res
));
1733 /* dump the results */
1736 ads_msgfree(ads
, res
);
1743 static int net_ads_keytab_usage(int argc
, const char **argv
)
1746 "net ads keytab <COMMAND>\n"\
1747 "<COMMAND> can be either:\n"\
1748 " CREATE Creates a fresh keytab\n"\
1749 " ADD Adds new service principal\n"\
1750 " FLUSH Flushes out all keytab entries\n"\
1751 " HELP Prints this help message\n"\
1752 "The ADD command will take arguments, the other commands\n"\
1753 "will not take any arguments. The arguments given to ADD\n"\
1754 "should be a list of principals to add. For example, \n"\
1755 " net ads keytab add srv1 srv2\n"\
1756 "will add principals for the services srv1 and srv2 to the\n"\
1757 "system's keytab.\n"\
1763 static int net_ads_keytab_flush(int argc
, const char **argv
)
1768 if (!(ads
= ads_startup())) {
1771 ret
= ads_keytab_flush(ads
);
1776 static int net_ads_keytab_add(int argc
, const char **argv
)
1782 d_printf("Processing principals to add...\n");
1783 if (!(ads
= ads_startup())) {
1786 for (i
= 0; i
< argc
; i
++) {
1787 ret
|= ads_keytab_add_entry(ads
, argv
[i
]);
1793 static int net_ads_keytab_create(int argc
, const char **argv
)
1798 if (!(ads
= ads_startup())) {
1801 ret
= ads_keytab_create_default(ads
);
1806 int net_ads_keytab(int argc
, const char **argv
)
1808 struct functable func
[] = {
1809 {"CREATE", net_ads_keytab_create
},
1810 {"ADD", net_ads_keytab_add
},
1811 {"FLUSH", net_ads_keytab_flush
},
1812 {"HELP", net_ads_keytab_usage
},
1816 if (!lp_use_kerberos_keytab()) {
1817 d_printf("\nWarning: \"use kerberos keytab\" must be set to \"true\" in order to \
1818 use keytab functions.\n");
1821 return net_run_function(argc
, argv
, func
, net_ads_keytab_usage
);
1824 int net_ads_help(int argc
, const char **argv
)
1826 struct functable func
[] = {
1827 {"USER", net_ads_user_usage
},
1828 {"GROUP", net_ads_group_usage
},
1829 {"PRINTER", net_ads_printer_usage
},
1830 {"SEARCH", net_ads_search_usage
},
1832 {"INFO", net_ads_info
},
1833 {"JOIN", net_ads_join
},
1834 {"JOIN2", net_ads_join2
},
1835 {"LEAVE", net_ads_leave
},
1836 {"STATUS", net_ads_status
},
1837 {"PASSWORD", net_ads_password
},
1838 {"CHANGETRUSTPW", net_ads_changetrustpw
},
1843 return net_run_function(argc
, argv
, func
, net_ads_usage
);
1846 int net_ads(int argc
, const char **argv
)
1848 struct functable func
[] = {
1849 {"INFO", net_ads_info
},
1850 {"JOIN", net_ads_join
},
1851 {"TESTJOIN", net_ads_testjoin
},
1852 {"LEAVE", net_ads_leave
},
1853 {"STATUS", net_ads_status
},
1854 {"USER", net_ads_user
},
1855 {"GROUP", net_ads_group
},
1856 {"PASSWORD", net_ads_password
},
1857 {"CHANGETRUSTPW", net_ads_changetrustpw
},
1858 {"PRINTER", net_ads_printer
},
1859 {"SEARCH", net_ads_search
},
1861 {"SID", net_ads_sid
},
1862 {"WORKGROUP", net_ads_workgroup
},
1863 {"LOOKUP", net_ads_lookup
},
1864 {"KEYTAB", net_ads_keytab
},
1865 {"HELP", net_ads_help
},
1869 return net_run_function(argc
, argv
, func
, net_ads_usage
);
1874 static int net_ads_noads(void)
1876 d_fprintf(stderr
, "ADS support not compiled in\n");
1880 int net_ads_keytab(int argc
, const char **argv
)
1882 return net_ads_noads();
1885 int net_ads_usage(int argc
, const char **argv
)
1887 return net_ads_noads();
1890 int net_ads_help(int argc
, const char **argv
)
1892 return net_ads_noads();
1895 int net_ads_changetrustpw(int argc
, const char **argv
)
1897 return net_ads_noads();
1900 int net_ads_join(int argc
, const char **argv
)
1902 return net_ads_noads();
1905 int net_ads_user(int argc
, const char **argv
)
1907 return net_ads_noads();
1910 int net_ads_group(int argc
, const char **argv
)
1912 return net_ads_noads();
1915 /* this one shouldn't display a message */
1916 int net_ads_check(void)
1921 int net_ads(int argc
, const char **argv
)
1923 return net_ads_usage(argc
, argv
);