Changed errno to SOCKET_ERRNO/SOCKET_SET_ERRNO for Mac OT SocketsLib
[krb5.git] / src / lib / krb5 / os / changepw.c
1 /*
2  * lib/krb5/os/changepw.c
3  *
4  * Copyright 1990 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  * 
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  M.I.T. makes no representations about the suitability of
20  * this software for any purpose.  It is provided "as is" without express
21  * or implied warranty.
22  * 
23  */
24
25 #define NEED_SOCKETS
26 #include "k5-int.h"
27 #include "adm_err.h"
28
29 #include <stdio.h>
30 #include <errno.h>
31
32 /* Win32 defines. */
33 #if defined(_WIN16) || (defined(_WIN32) && !defined(__CYGWIN32__))
34 #ifndef ECONNABORTED
35 #define ECONNABORTED WSAECONNABORTED
36 #endif
37 #ifndef ECONNREFUSED
38 #define ECONNREFUSED WSAECONNREFUSED
39 #endif
40 #ifndef EHOSTUNREACH
41 #define EHOSTUNREACH WSAEHOSTUNREACH
42 #endif
43 #endif /* _WIN32 && !__CYGWIN32__ */
44
45 KRB5_DLLIMP krb5_error_code KRB5_CALLCONV
46 krb5_change_password(context, creds, newpw, result_code,
47                      result_code_string, result_string)
48     krb5_context context;
49     krb5_creds *creds;
50     char *newpw;
51     int *result_code;
52     krb5_data *result_code_string;
53     krb5_data *result_string;
54 {
55     krb5_auth_context auth_context;
56     krb5_data ap_req, chpw_req, chpw_rep;
57     krb5_address local_kaddr, remote_kaddr;
58     const char *realm_kdc_names[4];
59     int default_port;
60     char **hostlist, *host, *port, *cp, *code_string;
61     krb5_error_code code;
62     int i, j, out, count, addrlen;
63     struct sockaddr *addr_p, local_addr, remote_addr, tmp_addr;
64     struct sockaddr_in *sin_p;
65     struct hostent *hp;
66     struct servent *sp;
67 #ifdef HAVE_NETINET_IN_H
68     u_short udpport = htons(KRB5_DEFAULT_PORT);
69 #endif
70     int cc, local_result_code, tmp_len;
71     SOCKET s1, s2;
72
73     auth_context = NULL;
74
75     if (code = krb5_mk_req_extended(context, &auth_context, AP_OPTS_USE_SUBKEY,
76                                     NULL, creds, &ap_req))
77         return(code);
78
79     if ((host = malloc(krb5_princ_realm(context, creds->client)->length + 1))
80         == NULL) 
81         return ENOMEM;
82
83     strncpy(host, krb5_princ_realm(context, creds->client)->data,
84             krb5_princ_realm(context, creds->client)->length);
85     host[krb5_princ_realm(context, creds->client)->length] = '\0';
86     hostlist = 0;
87     
88     realm_kdc_names[0] = "realms";
89     realm_kdc_names[1] = host;
90     realm_kdc_names[2] = "kpasswd_server";
91     realm_kdc_names[3] = 0;
92
93     default_port = 0;
94
95     code = profile_get_values(context->profile, realm_kdc_names, &hostlist);
96
97     if (code == PROF_NO_RELATION) {
98         realm_kdc_names[2] = "admin_server";
99
100         default_port = 1;
101
102         code = profile_get_values(context->profile, realm_kdc_names,
103                                   &hostlist);
104     }
105
106     krb5_xfree(host);
107
108     if (code == PROF_NO_SECTION)
109         return KRB5_REALM_UNKNOWN;
110     else if (code == PROF_NO_RELATION)
111         return KRB5_CONFIG_BADFORMAT;
112     else if (code)
113         return code;
114
115 #ifdef HAVE_NETINET_IN_H
116     /* XXX should look for "kpasswd" in /etc/services */
117     udpport = htons(DEFAULT_KPASSWD_PORT);
118 #endif
119
120     count = 0;
121     while (hostlist && hostlist[count])
122             count++;
123     
124     if (count == 0)
125         /* XXX */
126         return(KADM_NO_HOST);
127     
128     addr_p = (struct sockaddr *) malloc(sizeof(struct sockaddr) * count);
129     if (addr_p == NULL)
130         return ENOMEM;
131
132     host = hostlist[0];
133     out = 0;
134
135     /*
136      * Strip off excess whitespace
137      */
138     cp = strchr(host, ' ');
139     if (cp)
140         *cp = 0;
141     cp = strchr(host, '\t');
142     if (cp)
143         *cp = 0;
144     port = strchr(host, ':');
145     if (port) {
146         *port = 0;
147         port++;
148         /* if the admin_server line was used, ignore the specified
149            port */
150         if (default_port)
151             port = NULL;
152     }
153     hp = gethostbyname(hostlist[0]);
154
155     if (hp != 0) {
156         switch (hp->h_addrtype) {
157 #ifdef HAVE_NETINET_IN_H
158         case AF_INET:
159             for (j=0; hp->h_addr_list[j]; j++) {
160                 sin_p = (struct sockaddr_in *) &addr_p[out++];
161                 memset ((char *)sin_p, 0, sizeof(struct sockaddr));
162                 sin_p->sin_family = hp->h_addrtype;
163                 sin_p->sin_port = port ? htons(atoi(port)) : udpport;
164                 memcpy((char *)&sin_p->sin_addr,
165                        (char *)hp->h_addr_list[j],
166                        sizeof(struct in_addr));
167                 if (out+1 >= count) {
168                     count += 5;
169                     addr_p = (struct sockaddr *)
170                         realloc ((char *)addr_p,
171                                  sizeof(struct sockaddr) * count);
172                     if (addr_p == NULL)
173                         return ENOMEM;
174                 }
175             }
176             break;
177 #endif
178         default:
179             break;
180         }
181     }
182
183     for (i=0; hostlist[i]; i++)
184         free(hostlist[i]);
185     free ((char *)hostlist);
186
187     if (out == 0) {     /* Couldn't resolve any KDC names */
188         free (addr_p);
189         return(KADM_NO_HOST);
190     }
191
192     /* this is really obscure.  s1 is used for all communications.  it
193        is left unconnected in case the server is multihomed and routes
194        are asymmetric.  s2 is connected to resolve routes and get
195        addresses.  this is the *only* way to get proper addresses for
196        multihomed hosts if routing is asymmetric.  
197
198        A related problem in the server, but not the client, is that
199        many os's have no way to disconnect a connected udp socket, so
200        the s2 socket needs to be closed and recreated for each
201        request.  The s1 socket must not be closed, or else queued
202        requests will be lost.
203
204        A "naive" client implementation (one socket, no connect,
205        hostname resolution to get the local ip addr) will work and
206        interoperate if the client is single-homed. */
207
208     if ((s1 = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET) {
209         free(addr_p);
210         return(SOCKET_ERRNO);
211     }
212
213     if ((s2 = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET) {
214         free(addr_p);
215         return(SOCKET_ERRNO);
216     }
217
218     for (i=0; i<out; i++) {
219         if (connect(s2, &addr_p[i], sizeof(addr_p[i])) == SOCKET_ERROR) {
220             if ((cc < 0) && ((SOCKET_ERRNO == ECONNREFUSED) ||
221                              (SOCKET_ERRNO == EHOSTUNREACH)))
222                 continue; /* try the next addr */
223             free(addr_p);
224             closesocket(s1);
225             closesocket(s2);
226             return(SOCKET_ERRNO);
227         }
228
229         addrlen = sizeof(local_addr);
230
231         if (getsockname(s2, &local_addr, &addrlen) < 0) {
232             if ((SOCKET_ERRNO == ECONNREFUSED) ||
233                 (SOCKET_ERRNO == EHOSTUNREACH))
234                 continue; /* try the next addr */
235             free(addr_p);
236             closesocket(s1);
237             closesocket(s2);
238             return(SOCKET_ERRNO);
239         }
240
241         /* some brain-dead OS's don't return useful information from
242          * the getsockname call.  Namely, windows and solaris.  */
243
244         if (((struct sockaddr_in *)&local_addr)->sin_addr.s_addr != 0) {
245             local_kaddr.addrtype = ADDRTYPE_INET;
246             local_kaddr.length =
247               sizeof(((struct sockaddr_in *) &local_addr)->sin_addr);
248             local_kaddr.contents = 
249               (krb5_octet *) &(((struct sockaddr_in *) &local_addr)->sin_addr);
250         } else {
251             krb5_address **addrs;
252
253             krb5_os_localaddr(context, &addrs);
254             local_kaddr.magic = addrs[0]->magic;
255             local_kaddr.addrtype = addrs[0]->addrtype;
256             local_kaddr.length = addrs[0]->length;
257             local_kaddr.contents = malloc(addrs[0]->length);
258             memcpy(local_kaddr.contents, addrs[0]->contents, addrs[0]->length);
259
260             krb5_free_addresses(context, addrs);
261         }
262
263         addrlen = sizeof(remote_addr);
264         if (getpeername(s2, &remote_addr, &addrlen) < 0) {
265             if ((SOCKET_ERRNO == ECONNREFUSED) ||
266                 (SOCKET_ERRNO == EHOSTUNREACH))
267                 continue; /* try the next addr */
268             free(addr_p);
269             closesocket(s1);
270             closesocket(s2);
271             return(SOCKET_ERRNO);
272         }
273
274         remote_kaddr.addrtype = ADDRTYPE_INET;
275         remote_kaddr.length =
276             sizeof(((struct sockaddr_in *) &remote_addr)->sin_addr);
277         remote_kaddr.contents = 
278             (krb5_octet *) &(((struct sockaddr_in *) &remote_addr)->sin_addr);
279
280         /* mk_priv requires that the local address be set.
281           getsockname is used for this.  rd_priv requires that the
282           remote address be set.  recvfrom is used for this.  If
283           rd_priv is given a local address, and the message has the
284           recipient addr in it, this will be checked.  However, there
285           is simply no way to know ahead of time what address the
286           message will be delivered *to*.  Therefore, it is important
287           that either no recipient address is in the messages when
288           mk_priv is called, or that no local address is passed to
289           rd_priv.  Both is a better idea, and I have done that.  In
290           summary, when mk_priv is called, *only* a local address is
291           specified.  when rd_priv is called, *only* a remote address
292           is specified.  Are we having fun yet?  */
293
294         if (code = krb5_auth_con_setaddrs(context, auth_context, &local_kaddr,
295                                           NULL)) {
296             free(addr_p);
297             closesocket(s1);
298             closesocket(s2);
299             return(code);
300         }
301
302         if (code = krb5_mk_chpw_req(context, auth_context, &ap_req,
303                                     newpw, &chpw_req)) {
304             free(addr_p);
305             closesocket(s1);
306             closesocket(s2);
307             return(code);
308         }
309
310         if ((cc = sendto(s1, chpw_req.data, chpw_req.length, 0,
311                          (struct sockaddr *) &addr_p[i],
312                          sizeof(addr_p[i]))) !=
313             chpw_req.length) {
314             if ((cc < 0) && ((SOCKET_ERRNO == ECONNREFUSED) ||
315                              (SOCKET_ERRNO == EHOSTUNREACH)))
316                 continue; /* try the next addr */
317             free(addr_p);
318             closesocket(s1);
319             closesocket(s2);
320             return((cc < 0)?SOCKET_ERRNO:ECONNABORTED);
321         }
322
323         krb5_xfree(chpw_req.data);
324
325         chpw_rep.length = 1500;
326         chpw_rep.data = (char *) malloc(chpw_rep.length);
327
328         /* XXX need a timeout/retry loop here */
329
330         /* "recv" would be good enough here... except that Windows/NT
331            commits the atrocity of returning -1 to indicate failure,
332            but leaving errno set to 0.
333            
334            "recvfrom(...,NULL,NULL)" would seem to be a good enough
335            alternative, and it works on NT, but it doesn't work on
336            SunOS 4.1.4 or Irix 5.3.  Thus we must actually accept the
337            value and discard it. */
338         tmp_len = sizeof(tmp_addr);
339         if ((cc = recvfrom(s1, chpw_rep.data, chpw_rep.length, 0, &tmp_addr, &tmp_len)) < 0) {
340             free(addr_p);
341             closesocket(s1);
342             closesocket(s2);
343             return(SOCKET_ERRNO);
344         }
345
346         closesocket(s1);
347         closesocket(s2);
348
349         chpw_rep.length = cc;
350
351         if (code = krb5_auth_con_setaddrs(context, auth_context, NULL,
352                                           &remote_kaddr)) {
353             free(addr_p);
354             closesocket(s1);
355             closesocket(s2);
356             return(code);
357         }
358
359         code = krb5_rd_chpw_rep(context, auth_context, &chpw_rep,
360                                 &local_result_code, result_string);
361
362         free(chpw_rep.data);
363         free(addr_p);
364
365         if (code)
366             return(code);
367
368         if (result_code)
369             *result_code = local_result_code;
370
371         if (result_code_string) {
372             if (code = krb5_chpw_result_code_string(context, local_result_code,
373                                                     &code_string))
374                 return(code);
375
376             result_code_string->length = strlen(code_string);
377             if ((result_code_string->data =
378                  (char *) malloc(result_code_string->length)) == NULL)
379                 return(ENOMEM);
380             strncpy(result_code_string->data, code_string,
381                     result_code_string->length);
382         }
383
384         return(0);
385     }
386
387     free(addr_p);
388     closesocket(s1);
389     closesocket(s2);
390     return(SOCKET_ERRNO);
391 }