5117d78d4797c9bf7891dc31dab00ee9a9e99faa
[samba.git] / source3 / libsmb / passchange.c
1 /* 
2    Unix SMB/CIFS implementation.
3    SMB client password change routine
4    Copyright (C) Andrew Tridgell 1994-1998
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 "../librpc/gen_ndr/ndr_samr.h"
22 #include "rpc_client/cli_samr.h"
23
24 /*************************************************************
25  Change a password on a remote machine using IPC calls.
26 *************************************************************/
27
28 NTSTATUS remote_password_change(const char *remote_machine, const char *user_name, 
29                                 const char *old_passwd, const char *new_passwd,
30                                 char **err_str)
31 {
32         struct nmb_name calling, called;
33         struct cli_state *cli = NULL;
34         struct rpc_pipe_client *pipe_hnd = NULL;
35         struct sockaddr_storage ss;
36         char *user, *domain, *p;
37
38         NTSTATUS result;
39         bool pass_must_change = False;
40
41         user = talloc_strdup(talloc_tos(), user_name);
42         SMB_ASSERT(user != NULL);
43         domain = talloc_strdup(talloc_tos(), "");
44         SMB_ASSERT(domain != NULL);
45
46         /* allow usernames of the form domain\\user or domain/user */
47         if ((p = strchr_m(user,'\\')) || (p = strchr_m(user,'/')) ||
48             (p = strchr_m(user,*lp_winbind_separator()))) {
49                 *p = 0;
50                 domain = user;
51                 user = p+1;
52         }
53
54         *err_str = NULL;
55
56         if(!resolve_name( remote_machine, &ss, 0x20, false)) {
57                 if (asprintf(err_str, "Unable to find an IP address for machine "
58                          "%s.\n", remote_machine) == -1) {
59                         *err_str = NULL;
60                 }
61                 return NT_STATUS_UNSUCCESSFUL;
62         }
63
64         cli = cli_initialise();
65         if (!cli) {
66                 return NT_STATUS_NO_MEMORY;
67         }
68
69         result = cli_connect(cli, remote_machine, &ss);
70         if (!NT_STATUS_IS_OK(result)) {
71                 if (asprintf(err_str, "Unable to connect to SMB server on "
72                          "machine %s. Error was : %s.\n",
73                          remote_machine, nt_errstr(result))==-1) {
74                         *err_str = NULL;
75                 }
76                 cli_shutdown(cli);
77                 return result;
78         }
79
80         make_nmb_name(&calling, global_myname() , 0x0);
81         make_nmb_name(&called , remote_machine, 0x20);
82
83         if (!cli_session_request(cli, &calling, &called)) {
84                 if (asprintf(err_str, "machine %s rejected the session setup. "
85                          "Error was : %s.\n",
86                          remote_machine, cli_errstr(cli)) == -1) {
87                         *err_str = NULL;
88                 }
89                 result = cli_nt_error(cli);
90                 cli_shutdown(cli);
91                 return result;
92         }
93
94         cli->protocol = PROTOCOL_NT1;
95
96         result = cli_negprot(cli);
97
98         if (!NT_STATUS_IS_OK(result)) {
99                 if (asprintf(err_str, "machine %s rejected the negotiate "
100                          "protocol. Error was : %s.\n",        
101                          remote_machine, nt_errstr(result)) == -1) {
102                         *err_str = NULL;
103                 }
104                 result = cli_nt_error(cli);
105                 cli_shutdown(cli);
106                 return result;
107         }
108
109         /* Given things like SMB signing, restrict anonymous and the like, 
110            try an authenticated connection first */
111         result = cli_session_setup(cli, user_name,
112                                    old_passwd, strlen(old_passwd)+1,
113                                    old_passwd, strlen(old_passwd)+1, "");
114
115         if (!NT_STATUS_IS_OK(result)) {
116
117                 /* Password must change or Password expired are the only valid
118                  * error conditions here from where we can proceed, the rest like
119                  * account locked out or logon failure will lead to errors later
120                  * anyway */
121
122                 if (!NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_MUST_CHANGE) &&
123                     !NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_EXPIRED)) {
124                         if (asprintf(err_str, "Could not connect to machine %s: "
125                                  "%s\n", remote_machine, cli_errstr(cli)) == -1) {
126                                 *err_str = NULL;
127                         }
128                         cli_shutdown(cli);
129                         return result;
130                 }
131
132                 pass_must_change = True;
133
134                 /*
135                  * We should connect as the anonymous user here, in case
136                  * the server has "must change password" checked...
137                  * Thanks to <Nicholas.S.Jenkins@cdc.com> for this fix.
138                  */
139
140                 result = cli_session_setup(cli, "", "", 0, "", 0, "");
141
142                 if (!NT_STATUS_IS_OK(result)) {
143                         if (asprintf(err_str, "machine %s rejected the session "
144                                  "setup. Error was : %s.\n",        
145                                  remote_machine, cli_errstr(cli)) == -1) {
146                                 *err_str = NULL;
147                         }
148                         cli_shutdown(cli);
149                         return result;
150                 }
151
152                 result = cli_init_creds(cli, "", "", NULL);
153                 if (!NT_STATUS_IS_OK(result)) {
154                         cli_shutdown(cli);
155                         return result;
156                 }
157         } else {
158                 result = cli_init_creds(cli, user, domain, old_passwd);
159                 if (!NT_STATUS_IS_OK(result)) {
160                         cli_shutdown(cli);
161                         return result;
162                 }
163         }
164
165         result = cli_tcon_andx(cli, "IPC$", "IPC", "", 1);
166         if (!NT_STATUS_IS_OK(result)) {
167                 if (asprintf(err_str, "machine %s rejected the tconX on the "
168                              "IPC$ share. Error was : %s.\n",
169                              remote_machine, nt_errstr(result))) {
170                         *err_str = NULL;
171                 }
172                 cli_shutdown(cli);
173                 return result;
174         }
175
176         /* Try not to give the password away too easily */
177
178         if (!pass_must_change) {
179                 result = cli_rpc_pipe_open_ntlmssp(cli,
180                                                    &ndr_table_samr.syntax_id,
181                                                    NCACN_NP,
182                                                    DCERPC_AUTH_LEVEL_PRIVACY,
183                                                    domain, user,
184                                                    old_passwd,
185                                                    &pipe_hnd);
186         } else {
187                 /*
188                  * If the user password must be changed the ntlmssp bind will
189                  * fail the same way as the session setup above did. The
190                  * difference ist that with a pipe bind we don't get a good
191                  * error message, the result will be that the rpc call below
192                  * will just fail. So we do it anonymously, there's no other
193                  * way.
194                  */
195                 result = cli_rpc_pipe_open_noauth(
196                         cli, &ndr_table_samr.syntax_id, &pipe_hnd);
197         }
198
199         if (!NT_STATUS_IS_OK(result)) {
200                 if (lp_client_lanman_auth()) {
201                         /* Use the old RAP method. */
202                         if (!cli_oem_change_password(cli, user_name, new_passwd, old_passwd)) {
203                                 if (asprintf(err_str, "machine %s rejected the "
204                                          "password change: Error was : %s.\n",
205                                          remote_machine, cli_errstr(cli)) == -1) {
206                                         *err_str = NULL;
207                                 }
208                                 result = cli_nt_error(cli);
209                                 cli_shutdown(cli);
210                                 return result;
211                         }
212                 } else {
213                         if (asprintf(err_str, "SAMR connection to machine %s "
214                                  "failed. Error was %s, but LANMAN password "
215                                  "changes are disabled\n",
216                                  remote_machine, nt_errstr(result)) == -1) {
217                                 *err_str = NULL;
218                         }
219                         result = cli_nt_error(cli);
220                         cli_shutdown(cli);
221                         return result;
222                 }
223         }
224
225         result = rpccli_samr_chgpasswd_user2(pipe_hnd, talloc_tos(),
226                                              user_name, new_passwd, old_passwd);
227         if (NT_STATUS_IS_OK(result)) {
228                 /* Great - it all worked! */
229                 cli_shutdown(cli);
230                 return NT_STATUS_OK;
231
232         } else if (!(NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED) 
233                      || NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))) {
234                 /* it failed, but for reasons such as wrong password, too short etc ... */
235
236                 if (asprintf(err_str, "machine %s rejected the password change: "
237                          "Error was : %s.\n",
238                          remote_machine, get_friendly_nt_error_msg(result)) == -1) {
239                         *err_str = NULL;
240                 }
241                 cli_shutdown(cli);
242                 return result;
243         }
244
245         /* OK, that failed, so try again... */
246         TALLOC_FREE(pipe_hnd);
247
248         /* Try anonymous NTLMSSP... */
249         result = cli_init_creds(cli, "", "", NULL);
250         if (!NT_STATUS_IS_OK(result)) {
251                 cli_shutdown(cli);
252                 return result;
253         }
254
255         result = NT_STATUS_UNSUCCESSFUL;
256
257         /* OK, this is ugly, but... try an anonymous pipe. */
258         result = cli_rpc_pipe_open_noauth(cli, &ndr_table_samr.syntax_id,
259                                           &pipe_hnd);
260
261         if ( NT_STATUS_IS_OK(result) &&
262                 (NT_STATUS_IS_OK(result = rpccli_samr_chgpasswd_user2(
263                                          pipe_hnd, talloc_tos(), user_name,
264                                          new_passwd, old_passwd)))) {
265                 /* Great - it all worked! */
266                 cli_shutdown(cli);
267                 return NT_STATUS_OK;
268         } else {
269                 if (!(NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED) 
270                       || NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))) {
271                         /* it failed, but again it was due to things like new password too short */
272
273                         if (asprintf(err_str, "machine %s rejected the "
274                                  "(anonymous) password change: Error was : "
275                                  "%s.\n", remote_machine,
276                                  get_friendly_nt_error_msg(result)) == -1) {
277                                 *err_str = NULL;
278                         }
279                         cli_shutdown(cli);
280                         return result;
281                 }
282
283                 /* We have failed to change the user's password, and we think the server
284                    just might not support SAMR password changes, so fall back */
285
286                 if (lp_client_lanman_auth()) {
287                         /* Use the old RAP method. */
288                         if (cli_oem_change_password(cli, user_name, new_passwd, old_passwd)) {
289                                 /* SAMR failed, but the old LanMan protocol worked! */
290
291                                 cli_shutdown(cli);
292                                 return NT_STATUS_OK;
293                         }
294                         if (asprintf(err_str, "machine %s rejected the password "
295                                  "change: Error was : %s.\n",
296                                  remote_machine, cli_errstr(cli)) == -1) {
297                                 *err_str = NULL;
298                         }
299                         result = cli_nt_error(cli);
300                         cli_shutdown(cli);
301                         return result;
302                 } else {
303                         if (asprintf(err_str, "SAMR connection to machine %s "
304                                  "failed. Error was %s, but LANMAN password "
305                                  "changes are disabled\n",
306                                 nt_errstr(result), remote_machine) == -1) {
307                                 *err_str = NULL;
308                         }
309                         cli_shutdown(cli);
310                         return NT_STATUS_UNSUCCESSFUL;
311                 }
312         }
313 }