1 /* netio.c --- Network I/O functions.
2  * Copyright (C) 2002-2013 Simon Josefsson
3  *
4  * This file is part of Shishi.
5  *
6  * Shishi is free software; you can redistribute it and/or modify it
7  * 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  * Shishi is distributed in the hope that it will be useful, but
12  * 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 Shishi; if not, see http://www.gnu.org/licenses or write
18  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
19  * Floor, Boston, MA 02110-1301, USA
20  *
21  */
22 
23 #include "internal.h"
24 
25 /* Get _shishi_sendrecv_tls, etc. */
26 #include "starttls.h"
27 
28 /* Get _shishi_realminfo, etc. */
29 #include "diskio.h"
30 
31 /* Get _shishi_realminfo. */
32 #include "cfg.h"
33 
34 static int
sendrecv_udp(Shishi * handle,struct addrinfo * ai,const char * indata,int inlen,char ** outdata,size_t * outlen)35 sendrecv_udp (Shishi * handle,
36 	      struct addrinfo *ai,
37 	      const char *indata, int inlen, char **outdata, size_t * outlen)
38 {
39   char tmpbuf[BUFSIZ];		/* XXX can we do without it?
40 				   MSG_PEEK|MSG_TRUNC doesn't work for udp.. */
41   int sockfd;
42   int bytes_sent;
43   fd_set readfds;
44   struct timeval tout;
45   ssize_t slen;
46   int rc;
47 
48   sockfd = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol);
49   if (sockfd < 0)
50     {
51       shishi_error_set (handle, strerror (errno));
52       return SHISHI_SOCKET_ERROR;
53     }
54 
55   if (connect (sockfd, ai->ai_addr, ai->ai_addrlen) != 0)
56     {
57       shishi_error_set (handle, strerror (errno));
58       close (sockfd);
59       return SHISHI_BIND_ERROR;
60     }
61 
62   bytes_sent = write (sockfd, indata, inlen);
63   if (bytes_sent != inlen)
64     {
65       shishi_error_set (handle, strerror (errno));
66       close (sockfd);
67       return SHISHI_SENDTO_ERROR;
68     }
69 
70   FD_ZERO (&readfds);
71   FD_SET (sockfd, &readfds);
72   tout.tv_sec = handle->kdctimeout;
73   tout.tv_usec = 0;
74   if ((rc = select (sockfd + 1, &readfds, NULL, NULL, &tout)) != 1)
75     {
76       if (rc == -1)
77 	shishi_error_set (handle, strerror (errno));
78       else
79 	shishi_error_clear (handle);
80       close (sockfd);
81       return SHISHI_KDC_TIMEOUT;
82     }
83 
84   *outlen = sizeof (tmpbuf);
85   slen = read (sockfd, tmpbuf, *outlen);
86   if (slen == -1)
87     {
88       shishi_error_set (handle, strerror (errno));
89       close (sockfd);
90       return SHISHI_RECVFROM_ERROR;
91     }
92 
93   *outdata = xmalloc (slen);
94   *outlen = slen;
95   memcpy (*outdata, tmpbuf, slen);
96 
97   if (close (sockfd) != 0)
98     {
99       shishi_error_set (handle, strerror (errno));
100       return SHISHI_CLOSE_ERROR;
101     }
102 
103   return SHISHI_OK;
104 }
105 
106 static int
sendrecv_tcp(Shishi * handle,struct addrinfo * ai,const char * indata,int inlen,char ** outdata,size_t * outlen)107 sendrecv_tcp (Shishi * handle,
108 	      struct addrinfo *ai,
109 	      const char *indata, int inlen, char **outdata, size_t * outlen)
110 {
111   char tmpbuf[BUFSIZ];		/* XXX can we do without it?
112 				   MSG_PEEK|MSG_TRUNC doesn't work for udp.. */
113   int sockfd;
114   int bytes_sent;
115   struct sockaddr_storage from_sa;
116   socklen_t length = sizeof (struct sockaddr_storage);
117   fd_set readfds;
118   struct timeval tout;
119   int rc;
120   ssize_t slen;
121 
122   sockfd = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol);
123   if (sockfd < 0)
124     {
125       shishi_error_set (handle, strerror (errno));
126       return SHISHI_SOCKET_ERROR;
127     }
128 
129   if (connect (sockfd, ai->ai_addr, ai->ai_addrlen) != 0)
130     {
131       shishi_error_set (handle, strerror (errno));
132       close (sockfd);
133       return SHISHI_BIND_ERROR;
134     }
135 
136   tmpbuf[3] = inlen & 0xFF;
137   tmpbuf[2] = (inlen >> 8) & 0xFF;
138   tmpbuf[1] = (inlen >> 16) & 0xFF;
139   tmpbuf[0] = (inlen >> 24) & 0xFF;
140 
141   bytes_sent = write (sockfd, tmpbuf, 4);
142   if (bytes_sent != 4)
143     {
144       shishi_error_set (handle, strerror (errno));
145       return SHISHI_SENDTO_ERROR;
146     }
147 
148   bytes_sent = write (sockfd, (const void *) indata, inlen);
149   if (bytes_sent != inlen)
150     {
151       shishi_error_set (handle, strerror (errno));
152       return SHISHI_SENDTO_ERROR;
153     }
154 
155   FD_ZERO (&readfds);
156   FD_SET (sockfd, &readfds);
157   tout.tv_sec = handle->kdctimeout;
158   tout.tv_usec = 0;
159   if ((rc = select (sockfd + 1, &readfds, NULL, NULL, &tout)) != 1)
160     {
161       if (rc == -1)
162 	shishi_error_set (handle, strerror (errno));
163       else
164 	shishi_error_clear (handle);
165       return SHISHI_KDC_TIMEOUT;
166     }
167 
168   *outlen = 4;
169   slen = recvfrom (sockfd, tmpbuf, *outlen, 0,
170 		   (struct sockaddr *) &from_sa, &length);
171   if (slen == -1)
172     {
173       shishi_error_set (handle, strerror (errno));
174       return SHISHI_RECVFROM_ERROR;
175     }
176 
177   *outlen = sizeof (tmpbuf);
178   slen = recvfrom (sockfd, tmpbuf, *outlen, 0,
179 		   (struct sockaddr *) &from_sa, &length);
180   if (slen == -1)
181     {
182       shishi_error_set (handle, strerror (errno));
183       return SHISHI_RECVFROM_ERROR;
184     }
185 
186   *outdata = xmalloc (slen);
187   *outlen = slen;
188   memcpy (*outdata, tmpbuf, slen);
189 
190   if (close (sockfd) != 0)
191     {
192       shishi_error_set (handle, strerror (errno));
193       return SHISHI_CLOSE_ERROR;
194     }
195 
196   return SHISHI_OK;
197 }
198 
199 static int
sendrecv_host(Shishi * handle,int transport,const char * host,const char * port,const char * indata,size_t inlen,char ** outdata,size_t * outlen)200 sendrecv_host (Shishi * handle,
201 	       int transport, const char *host, const char *port,
202 	       const char *indata, size_t inlen,
203 	       char **outdata, size_t * outlen)
204 {
205   struct addrinfo hints;
206   struct addrinfo *ai;
207   int rc;
208 
209   memset (&hints, 0, sizeof (hints));
210   if (transport == TCP || transport == TLS)
211     hints.ai_socktype = SOCK_STREAM;
212   else
213     hints.ai_socktype = SOCK_DGRAM;
214   hints.ai_flags = AI_ADDRCONFIG;
215 
216   if (port == NULL)
217     port = "88";
218 
219   rc = getaddrinfo (host, port, &hints, &ai);
220   if (rc != 0)
221     {
222       shishi_error_printf (handle, "Cannot find host %s", host);
223       return SHISHI_KDC_NOT_KNOWN_FOR_REALM;
224     }
225 
226   do
227     {
228       char nodename[NI_MAXHOST];
229       size_t j = 0;
230 
231       rc = getnameinfo (ai->ai_addr, ai->ai_addrlen,
232 			nodename, sizeof (nodename), NULL, 0, NI_NUMERICHOST);
233       shishi_verbose (handle, "Sending to %s (%s) port %s transport %s",
234 		      host, rc == 0 ? nodename : "unknown address", port,
235 		      _shishi_transport2string (transport));
236 
237       do
238 	{
239 	  if (transport == TCP)
240 	    rc = sendrecv_tcp (handle, ai, indata, inlen, outdata, outlen);
241 #ifdef USE_STARTTLS
242 	  else if (transport == TLS)
243 	    rc = _shishi_sendrecv_tls (handle, ai, indata, inlen,
244 				       outdata, outlen);
245 #endif
246 	  else
247 	    rc = sendrecv_udp (handle, ai, indata, inlen, outdata, outlen);
248 
249 	  if (rc != SHISHI_OK)
250 	    shishi_verbose (handle, "Error sending to KDC: %s",
251 			    shishi_strerror (rc));
252 	}
253       while (rc == SHISHI_KDC_TIMEOUT && ++j < handle->kdcretries);
254     }
255   while (rc != SHISHI_OK && (ai = ai->ai_next));
256 
257   return rc;
258 }
259 
260 static int
sendrecv_srv3(Shishi * handle,int transport,const char * realm,const char * indata,size_t inlen,char ** outdata,size_t * outlen,Shishi_dns rrs,bool * found_srv_records)261 sendrecv_srv3 (Shishi * handle,
262 	       int transport,
263 	       const char *realm,
264 	       const char *indata, size_t inlen,
265 	       char **outdata, size_t * outlen,
266 	       Shishi_dns rrs, bool * found_srv_records)
267 {
268   int rc = SHISHI_KDC_NOT_KNOWN_FOR_REALM;
269 
270   for (; rrs; rrs = rrs->next)
271     {
272       Shishi_dns_srv srv = rrs->rr;
273       char *port;
274 
275       if (rrs->class != SHISHI_DNS_IN)
276 	continue;
277       if (rrs->type != SHISHI_DNS_SRV)
278 	continue;
279 
280       shishi_verbose (handle, "Found SRV host %s port %d",
281 		      srv->name, srv->port);
282       *found_srv_records = true;
283 
284       port = xasprintf ("%d", srv->port);
285       rc = sendrecv_host (handle, transport,
286 			  srv->name, port, indata, inlen, outdata, outlen);
287       free (port);
288 
289       if (rc == SHISHI_OK)
290 	return rc;
291     }
292 
293   return rc;
294 }
295 
296 static int
sendrecv_srv2(Shishi * handle,int transport,const char * realm,const char * indata,size_t inlen,char ** outdata,size_t * outlen,bool * found_srv_records)297 sendrecv_srv2 (Shishi * handle,
298 	       int transport,
299 	       const char *realm,
300 	       const char *indata, size_t inlen,
301 	       char **outdata, size_t * outlen, bool * found_srv_records)
302 {
303   Shishi_dns rrs;
304   char *tmp;
305   int rc;
306 
307   if (transport != UDP && transport != TCP)
308     return SHISHI_KDC_NOT_KNOWN_FOR_REALM;
309 
310   tmp = xasprintf ("_kerberos._%s.%s", transport == UDP ? "udp" : "tcp",
311 		   realm);
312   shishi_verbose (handle, "Looking up SRV for %s", tmp);
313   rrs = shishi_resolv (tmp, SHISHI_DNS_SRV);
314   free (tmp);
315 
316   if (rrs)
317     rc = sendrecv_srv3 (handle, transport, realm, indata, inlen,
318 			outdata, outlen, rrs, found_srv_records);
319   else
320     rc = SHISHI_KDC_NOT_KNOWN_FOR_REALM;
321 
322   shishi_resolv_free (rrs);
323 
324   return rc;
325 }
326 
327 static int
sendrecv_srv(Shishi * handle,const char * realm,const char * indata,size_t inlen,char ** outdata,size_t * outlen,bool * found_srv_records)328 sendrecv_srv (Shishi * handle, const char *realm,
329 	      const char *indata, size_t inlen,
330 	      char **outdata, size_t * outlen, bool * found_srv_records)
331 {
332   int rc = sendrecv_srv2 (handle, UDP, realm, indata, inlen,
333 			  outdata, outlen, found_srv_records);
334   if (rc == SHISHI_OK)
335     return rc;
336   return sendrecv_srv2 (handle, TCP, realm, indata, inlen,
337 			outdata, outlen, found_srv_records);
338 }
339 
340 static int
sendrecv_static(Shishi * handle,const char * realm,const char * indata,size_t inlen,char ** outdata,size_t * outlen)341 sendrecv_static (Shishi * handle, const char *realm,
342 		 const char *indata, size_t inlen,
343 		 char **outdata, size_t * outlen)
344 {
345   struct Shishi_realminfo *ri;
346   size_t k;
347   int rc;
348 
349   ri = _shishi_realminfo (handle, realm);
350   if (!ri || ri->nkdcaddresses == 0)
351     {
352       shishi_error_printf (handle, "No KDC configured for %s", realm);
353       return SHISHI_KDC_NOT_KNOWN_FOR_REALM;
354     }
355 
356   rc = SHISHI_KDC_NOT_KNOWN_FOR_REALM;
357   for (k = 0; k < ri->nkdcaddresses; k++)
358     {
359       rc = sendrecv_host (handle,
360 			  ri->kdcaddresses[k].transport,
361 			  ri->kdcaddresses[k].hostname,
362 			  ri->kdcaddresses[k].port,
363 			  indata, inlen, outdata, outlen);
364       if (rc == SHISHI_OK)
365 	return rc;
366     }
367 
368   return rc;
369 }
370 
371 /**
372  * shishi_kdc_sendrecv_hint:
373  * @handle: Shishi library handle create by shishi_init().
374  * @realm: string with realm name.
375  * @indata: Packet to send to KDC.
376  * @inlen: Length of @indata.
377  * @outdata: Newly allocated string with data returned from KDC.
378  * @outlen: Length of @outdata.
379  * @hint: a #Shishi_tkts_hint structure with flags.
380  *
381  * Send packet to KDC for realm and receive response.  The code finds
382  * KDC addresses from configuration file, then by querying for SRV
383  * records for the realm, and finally by using the realm name as a
384  * hostname.
385  *
386  * Returns: %SHISHI_OK on success, %SHISHI_KDC_TIMEOUT if a timeout
387  *   was reached, or other errors.
388  **/
389 int
shishi_kdc_sendrecv_hint(Shishi * handle,const char * realm,const char * indata,size_t inlen,char ** outdata,size_t * outlen,Shishi_tkts_hint * hint)390 shishi_kdc_sendrecv_hint (Shishi * handle, const char *realm,
391 			  const char *indata, size_t inlen,
392 			  char **outdata, size_t * outlen,
393 			  Shishi_tkts_hint * hint)
394 {
395   struct Shishi_realminfo *ri;
396   bool found_srv_records = false;
397   int rc;
398 
399   ri = _shishi_realminfo (handle, realm);
400   if (ri && ri->nkdcaddresses > 0)
401     /* If we have configured KDCs, never use DNS or direct method. */
402     return sendrecv_static (handle, realm, indata, inlen, outdata, outlen);
403 
404   rc = sendrecv_srv (handle, realm, indata, inlen, outdata, outlen,
405 		     &found_srv_records);
406   if (rc != SHISHI_OK && !found_srv_records)
407     {
408       shishi_verbose (handle, "No SRV RRs, trying realm host mapping for %s",
409 		      realm);
410       rc = sendrecv_host (handle, UDP, realm, NULL,
411 			  indata, inlen, outdata, outlen);
412     }
413 
414   return rc;
415 }
416 
417 /**
418  * shishi_kdc_sendrecv:
419  * @handle: Shishi library handle create by shishi_init().
420  * @realm: string with realm name.
421  * @indata: Packet to send to KDC.
422  * @inlen: Length of @indata.
423  * @outdata: Newly allocated string with data returned from KDC.
424  * @outlen: Length of @outdata.
425  *
426  * Send packet to KDC for realm and receive response.  The code finds
427  * KDC addresses from configuration file, then by querying for SRV
428  * records for the realm, and finally by using the realm name as a
429  * hostname.
430  *
431  * Returns: %SHISHI_OK on success, %SHISHI_KDC_TIMEOUT if a timeout
432  *   was reached, or other errors.
433  **/
434 int
shishi_kdc_sendrecv(Shishi * handle,const char * realm,const char * indata,size_t inlen,char ** outdata,size_t * outlen)435 shishi_kdc_sendrecv (Shishi * handle, const char *realm,
436 		     const char *indata, size_t inlen,
437 		     char **outdata, size_t * outlen)
438 {
439   return shishi_kdc_sendrecv_hint (handle, realm, indata, inlen,
440 				   outdata, outlen, NULL);
441 }
442