Pazpar2 compiles on Windows. But does not yet work
[pazpar2-moved-to-github.git] / src / connection.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 2006-2008 Index Data
3
4 Pazpar2 is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18 */
19
20 /** \file connection.c
21     \brief Z39.50 connection (low-level client)
22 */
23
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #if HAVE_SYS_TIME_H
28 #include <sys/time.h>
29 #endif
30 #if HAVE_UNISTD_H
31 #include <unistd.h>
32 #endif
33 #if HAVE_SYS_SOCKET_H
34 #include <sys/socket.h>
35 #endif
36 #ifdef WIN32
37 #include <winsock.h>
38 typedef int socklen_t;
39 #endif
40 #if HAVE_NETDB_H
41 #include <netdb.h>
42 #endif
43
44 #include <signal.h>
45 #include <ctype.h>
46 #include <assert.h>
47
48 #if HAVE_CONFIG_H
49 #include <config.h>
50 #endif
51
52 #include <yaz/log.h>
53 #include <yaz/comstack.h>
54 #include <yaz/tcpip.h>
55 #include "connection.h"
56 #include "eventl.h"
57 #include "pazpar2.h"
58 #include "host.h"
59 #include "client.h"
60 #include "settings.h"
61 #include "parameters.h"
62
63
64 /** \brief Represents a physical, reusable  connection to a remote Z39.50 host
65  */
66 struct connection {
67     IOCHAN iochan;
68     COMSTACK link;
69     struct host *host;
70     struct client *client;
71     char *ibuf;
72     int ibufsize;
73     char *authentication; // Empty string or authentication string if set
74     char *zproxy;
75     enum {
76         Conn_Resolving,
77         Conn_Connecting,
78         Conn_Open,
79         Conn_Waiting,
80     } state;
81     struct connection *next; // next for same host or next in free list
82 };
83
84 static struct connection *connection_freelist = 0;
85
86 static void remove_connection_from_host(struct connection *con)
87 {
88     struct connection **conp = &con->host->connections;
89     assert(con);
90     while (*conp)
91     {
92         if (*conp == con)
93         {
94             *conp = (*conp)->next;
95             return;
96         }
97         conp = &(*conp)->next;
98     }
99     assert(*conp == 0);
100 }
101
102 // Close connection and recycle structure
103 void connection_destroy(struct connection *co)
104 {
105     if (co->link)
106     {
107         cs_close(co->link);
108         iochan_destroy(co->iochan);
109     }
110
111     yaz_log(YLOG_DEBUG, "Connection destroy %s", co->host->hostport);
112
113     remove_connection_from_host(co);
114     if (co->client)
115     {
116         client_disconnect(co->client);
117     }
118     xfree(co->zproxy);
119     co->zproxy = 0;
120     co->next = connection_freelist;
121     connection_freelist = co;
122 }
123
124 // Creates a new connection for client, associated with the host of 
125 // client's database
126 struct connection *connection_create(struct client *cl)
127 {
128     struct connection *new;
129     struct host *host = client_get_host(cl);
130
131     if ((new = connection_freelist))
132         connection_freelist = new->next;
133     else
134     {
135         new = xmalloc(sizeof (struct connection));
136         new->ibuf = 0;
137         new->ibufsize = 0;
138     }
139     new->host = host;
140     new->next = new->host->connections;
141     new->host->connections = new;
142     new->client = cl;
143     new->authentication = "";
144     new->zproxy = 0;
145     client_set_connection(cl, new);
146     new->link = 0;
147     new->state = Conn_Resolving;
148     if (host->ipport)
149         connection_connect(new);
150     return new;
151 }
152
153 static void connection_handler(IOCHAN i, int event)
154 {
155     struct connection *co = iochan_getdata(i);
156     struct client *cl = co->client;
157     struct session *se = 0;
158
159     if (cl)
160         se = client_get_session(cl);
161     else
162     {
163         connection_destroy(co);
164         return;
165     }
166
167     if (event & EVENT_TIMEOUT)
168     {
169         if (co->state == Conn_Connecting)
170         {
171             yaz_log(YLOG_WARN,  "connect timeout %s", client_get_url(cl));
172             client_fatal(cl);
173         }
174         else
175         {
176             yaz_log(YLOG_LOG,  "idle timeout %s", client_get_url(cl));
177             connection_destroy(co);
178         }
179         return;
180     }
181     if (co->state == Conn_Connecting && event & EVENT_OUTPUT)
182     {
183         int errcode;
184         socklen_t errlen = sizeof(errcode);
185
186         if (getsockopt(cs_fileno(co->link), SOL_SOCKET, SO_ERROR, &errcode,
187             &errlen) < 0 || errcode != 0)
188         {
189             client_fatal(cl);
190             return;
191         }
192         else
193         {
194             yaz_log(YLOG_DEBUG, "Connect OK");
195             co->state = Conn_Open;
196             if (cl)
197                 client_set_state(cl, Client_Connected);
198             iochan_settimeout(i, global_parameters.z3950_session_timeout);
199         }
200     }
201
202     else if (event & EVENT_INPUT)
203     {
204         int len = cs_get(co->link, &co->ibuf, &co->ibufsize);
205
206         if (len < 0)
207         {
208             yaz_log(YLOG_WARN|YLOG_ERRNO, "Error reading from %s", 
209                     client_get_url(cl));
210             connection_destroy(co);
211             return;
212         }
213         else if (len == 0)
214         {
215             yaz_log(YLOG_WARN, "EOF reading from %s", client_get_url(cl));
216             connection_destroy(co);
217             return;
218         }
219         else if (len > 1) // We discard input if we have no connection
220         {
221             co->state = Conn_Open;
222
223             if (client_is_our_response(cl))
224             {
225                 Z_APDU *a;
226                 struct session_database *sdb = client_get_database(cl);
227                 const char *apdulog = session_setting_oneval(sdb, PZ_APDULOG);
228
229                 odr_reset(global_parameters.odr_in);
230                 odr_setbuf(global_parameters.odr_in, co->ibuf, len, 0);
231                 if (!z_APDU(global_parameters.odr_in, &a, 0, 0))
232                 {
233                     client_fatal(cl);
234                     return;
235                 }
236
237                 if (apdulog && *apdulog && *apdulog != '0')
238                 {
239                     ODR p = odr_createmem(ODR_PRINT);
240                     yaz_log(YLOG_LOG, "recv APDU %s", client_get_url(cl));
241                     
242                     odr_setprint(p, yaz_log_file());
243                     z_APDU(p, &a, 0, 0);
244                     odr_setprint(p, stderr);
245                     odr_destroy(p);
246                 }
247                 switch (a->which)
248                 {
249                     case Z_APDU_initResponse:
250                         client_init_response(cl, a);
251                         break;
252                     case Z_APDU_searchResponse:
253                         client_search_response(cl, a);
254                         break;
255                     case Z_APDU_presentResponse:
256                         client_present_response(cl, a);
257                         break;
258                     case Z_APDU_close:
259                         client_close_response(cl, a);
260                         break;
261                     default:
262                         yaz_log(YLOG_WARN, 
263                                 "Unexpected Z39.50 response from %s",  
264                                 client_get_url(cl));
265                         client_fatal(cl);
266                         return;
267                 }
268                 // We aren't expecting staggered output from target
269                 // if (cs_more(t->link))
270                 //    iochan_setevent(i, EVENT_INPUT);
271             }
272             else  // we throw away response and go to idle mode
273             {
274                 yaz_log(YLOG_DEBUG, "Ignoring result of expired operation");
275                 client_set_state(cl, Client_Continue);
276             }
277         }
278         /* if len==1 we do nothing but wait for more input */
279     }
280     client_continue(cl);
281 }
282
283 // Disassociate connection from client
284 void connection_release(struct connection *co)
285 {
286     struct client *cl = co->client;
287
288     yaz_log(YLOG_DEBUG, "Connection release %s", co->host->hostport);
289     if (!cl)
290         return;
291     client_set_connection(cl, 0);
292     co->client = 0;
293 }
294
295 void connect_resolver_host(struct host *host)
296 {
297     struct connection *con = host->connections;
298     while (con)
299     {
300         if (con->state == Conn_Resolving)
301         {
302             if (!host->ipport) /* unresolved */
303             {
304                 connection_destroy(con);
305                 /* start all over .. at some point it will be NULL */
306                 con = host->connections;
307                 continue;
308             }
309             else if (!con->client)
310             {
311                 connection_destroy(con);
312                 /* start all over .. at some point it will be NULL */
313                 con = host->connections;
314                 continue;
315             }
316             else
317             {
318                 connection_connect(con);
319             }
320         }
321         else
322         {
323             yaz_log(YLOG_LOG, "connect_resolver_host: state=%d", con->state);
324         }
325         con = con->next;
326     }
327 }
328
329 int connection_send_apdu(struct connection *co, Z_APDU *a)
330 {
331     char *buf;
332     int len, r;
333
334     if (!z_APDU(global_parameters.odr_out, &a, 0, 0))
335     {
336         odr_perror(global_parameters.odr_out, "Encoding APDU");
337         abort();
338     }
339     buf = odr_getbuf(global_parameters.odr_out, &len, 0);
340     r = cs_put(co->link, buf, len);
341     if (r < 0)
342     {
343         yaz_log(YLOG_WARN, "cs_put: %s", cs_errmsg(cs_errno(co->link)));
344         return -1;
345     }
346     else if (r == 1)
347     {
348         fprintf(stderr, "cs_put incomplete (ParaZ does not handle that)\n");
349         return -1;;
350     }
351     odr_reset(global_parameters.odr_out); /* release the APDU structure  */
352     co->state = Conn_Waiting;
353     iochan_setflags(co->iochan, EVENT_INPUT);
354     return 0;
355 }
356
357 struct host *connection_get_host(struct connection *con)
358 {
359     return con->host;
360 }
361
362 int connection_connect(struct connection *con)
363 {
364     COMSTACK link = 0;
365     struct host *host = connection_get_host(con);
366     void *addr;
367     int res;
368
369     struct session_database *sdb = client_get_database(con->client);
370     const char *zproxy = session_setting_oneval(sdb, PZ_ZPROXY);
371
372     assert(host->ipport);
373     assert(con);
374
375     if (!(link = cs_create(tcpip_type, 0, PROTO_Z3950)))
376     {
377         yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to create comstack");
378         return -1;
379     }
380
381     if (zproxy && *zproxy)
382         con->zproxy = xstrdup(zproxy);
383
384     if (!con->zproxy)
385     {
386         /* no Z39.50 proxy needed - direct connect */
387         yaz_log(YLOG_DEBUG, "Connection create %s", connection_get_url(con));
388         
389         if (!(addr = cs_straddr(link, host->ipport)))
390         {
391             yaz_log(YLOG_WARN|YLOG_ERRNO, 
392                     "Lookup of IP address %s failed", host->ipport);
393             return -1;
394         }
395         
396     } else {
397         /* Z39.50 proxy connect */
398         yaz_log(YLOG_DEBUG, "Connection create %s proxy %s", 
399                 connection_get_url(con), con->zproxy);
400         
401         if (!(addr = cs_straddr(link, con->zproxy)))
402         {
403             yaz_log(YLOG_WARN|YLOG_ERRNO, 
404                     "Lookup of ZProxy IP address %s failed", 
405                     con->zproxy);
406             return -1;
407         }
408     }
409     
410     res = cs_connect(link, addr);
411     if (res < 0)
412     {
413         yaz_log(YLOG_WARN|YLOG_ERRNO, "cs_connect %s",
414                 connection_get_url(con));
415         return -1;
416     }
417     con->link = link;
418     con->state = Conn_Connecting;
419     con->iochan = iochan_create(cs_fileno(link), connection_handler, 0);
420     iochan_settimeout(con->iochan, global_parameters.z3950_connect_timeout);
421     iochan_setdata(con->iochan, con);
422     pazpar2_add_channel(con->iochan);
423
424     /* this fragment is bad DRY: from client_prep_connection */
425     client_set_state(con->client, Client_Connecting);
426     iochan_setflag(con->iochan, EVENT_OUTPUT);
427     return 0;
428 }
429
430 const char *connection_get_url(struct connection *co)
431 {
432     return client_get_url(co->client);
433 }
434
435 void connection_set_authentication(struct connection *co, char *auth)
436 {
437     co->authentication = auth;
438 }
439
440 // Ensure that client has a connection associated
441 int client_prep_connection(struct client *cl)
442 {
443     struct connection *co;
444     struct session *se = client_get_session(cl);
445     struct host *host = client_get_host(cl);
446     struct session_database *sdb = client_get_database(cl);
447     const char *zproxy = session_setting_oneval(sdb, PZ_ZPROXY);
448
449     if (zproxy && zproxy[0] == '\0')
450         zproxy = 0;
451
452     co = client_get_connection(cl);
453
454     yaz_log(YLOG_DEBUG, "Client prep %s", client_get_url(cl));
455
456     if (!co)
457     {
458         // See if someone else has an idle connection
459         // We should look at timestamps here to select the longest-idle connection
460         for (co = host->connections; co; co = co->next)
461             if (co->state == Conn_Open &&
462                 (!co->client || client_get_session(co->client) != se) &&
463                 !strcmp(co->authentication,
464                     session_setting_oneval(client_get_database(cl),
465                     PZ_AUTHENTICATION)))
466             {
467                 if (zproxy == 0 && co->zproxy == 0)
468                     break;
469                 if (zproxy && co->zproxy && !strcmp(zproxy, co->zproxy))
470                     break;
471             }
472         if (co)
473         {
474             connection_release(co);
475             client_set_connection(cl, co);
476             co->client = cl;
477         }
478         else
479             co = connection_create(cl);
480     }
481     if (co)
482     {
483         if (co->state == Conn_Connecting)
484         {
485             client_set_state(cl, Client_Connecting);
486             iochan_setflag(co->iochan, EVENT_OUTPUT);
487         }
488         else if (co->state == Conn_Open)
489         {
490             if (client_get_state(cl) == Client_Error 
491                 || client_get_state(cl) == Client_Disconnected
492                 || client_get_state(cl) == Client_Idle)
493                 client_set_state(cl, Client_Continue);
494             iochan_setflag(co->iochan, EVENT_OUTPUT);
495         }
496         return 1;
497     }
498     else
499         return 0;
500 }
501
502
503
504 /*
505  * Local variables:
506  * c-basic-offset: 4
507  * indent-tabs-mode: nil
508  * End:
509  * vim: shiftwidth=4 tabstop=8 expandtab
510  */