[svn-inject] Installing original source of libnss-mysql-bg
[manu/libnss-mysql-bg.git] / src / mysql.c
1 /* Copyright (C) 2002 Ben Goodwin
2    This file is part of the nss-mysql library.
3
4    The nss-mysql library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU General Public License as published
6    by the Free Software Foundation; either version 2 of the License, or
7    (at your option) any later version.
8
9    The nss-mysql library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with the nss-mysql library; if not, write to the Free Software
16    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19 /*
20  * MySQL-specific functions; ALL MySQL calls that any other source functions
21  * need should come from here.
22  */
23
24 static const char rcsid[] =
25     "$Id: mysql.c,v 1.44 2004/11/13 16:25:26 cinergi Exp $";
26
27 #include "nss_mysql.h"
28 #include <string.h>     /* strlen() */
29 #include <netinet/in.h> /* struct sockaddr_in */
30
31 #ifndef HAVE_SOCKLEN_T
32 typedef size_t socklen_t;
33 #endif
34
35 con_info_t ci = { nfalse };
36 extern conf_t conf;
37
38 /*
39  * Immediately after connecting to a MySQL server, save the current
40  * socket information for later comparison purposes
41  */
42 static freturn_t
43 _nss_mysql_save_socket_info (void)
44 {
45   DN ("_nss_mysql_save_socket_info")
46   socklen_t local_size = sizeof (struct sockaddr);
47   socklen_t remote_size = sizeof (struct sockaddr);
48   int r;
49
50   DENTER
51   memset(&(ci.sock_info.local), 0, sizeof (ci.sock_info.local));
52   r = getsockname (ci.link.net.fd, &(ci.sock_info.local), &local_size);
53   if (r != RETURN_SUCCESS)
54     DFRETURN (RETURN_FAILURE)
55
56   memset(&(ci.sock_info.remote), 0, sizeof (ci.sock_info.remote));
57   r = getpeername (ci.link.net.fd, &(ci.sock_info.remote), &remote_size);
58   DFRETURN (r)
59 }
60
61 /*
62  * Compare ORIG and CUR
63  */
64 static nboolean
65 _nss_mysql_is_same_sockaddr (struct sockaddr orig, struct sockaddr cur)
66 {
67   DN ("_nss_mysql_is_same_sockaddr")
68
69   DENTER
70   switch (((struct sockaddr_in *)&ci.sock_info.local)->sin_family)
71     {
72     case AF_INET:
73         if ((*(struct sockaddr_in *) &orig).sin_port != 
74             (*(struct sockaddr_in *) &cur).sin_port)
75           DBRETURN (nfalse)
76         if ((*(struct sockaddr_in *) &orig).sin_addr.s_addr != 
77             (*(struct sockaddr_in *) &cur).sin_addr.s_addr)
78           DBRETURN (nfalse)
79         break;
80     case AF_UNIX:
81         if (memcmp (&orig, &cur, sizeof (struct sockaddr)) != 0)
82           DBRETURN (nfalse)
83         break;
84     default:
85         _nss_mysql_log (LOG_ERR, "%s: Unhandled sin_family", FUNCNAME);
86         DBRETURN (nfalse)
87         break;
88     }
89   DBRETURN (ntrue)
90 }
91
92 /*
93  * Check to see what current socket info is and compare to the saved
94  * socket info (from _nss_mysql_save_socket_info() above).  Return
95  * NTRUE if the current socket and saved socket match.
96  */
97 static nboolean
98 _nss_mysql_validate_socket (void)
99 {
100   DN ("_nss_mysql_validate_socket")
101   socklen_t local_size = sizeof (struct sockaddr);
102   socklen_t remote_size = sizeof (struct sockaddr);
103   struct sockaddr check;
104
105   DENTER
106   memset(&check, 0, sizeof (check));
107   if (getpeername (ci.link.net.fd, &check, &remote_size) != RETURN_SUCCESS)
108     DBRETURN (nfalse)
109   if (_nss_mysql_is_same_sockaddr (ci.sock_info.remote, check) != ntrue)
110     DBRETURN (nfalse)
111
112   memset(&check, 0, sizeof (check));
113   if (getsockname (ci.link.net.fd, &check, &local_size) != RETURN_SUCCESS)
114     DBRETURN (nfalse)
115   if (_nss_mysql_is_same_sockaddr (ci.sock_info.local, check) != ntrue)
116     DBRETURN (nfalse)
117
118   DBRETURN (ntrue)
119 }
120
121 NSS_STATUS
122 _nss_mysql_close_sql (MYSQL_RES **mresult, nboolean graceful)
123 {
124   DN ("_nss_mysql_close_sql")
125   DENTER
126   _nss_mysql_close_result (mresult);
127   if (graceful && ci.valid)
128     {
129       D ("%s: calling mysql_close()", FUNCNAME);
130       mysql_close (&ci.link);
131     }
132   ci.valid = nfalse;
133   DSRETURN (NSS_SUCCESS)
134 }
135
136 static void
137 _nss_mysql_set_options (sql_server_t *server)
138 {
139   DN ("_nss_mysql_set_options")
140   unsigned int timeout;
141
142   DENTER
143   if (server->options.timeout)
144     timeout = (unsigned int) atoi (server->options.timeout);
145   else
146     timeout = DEF_TIMEOUT;
147   D ("%s: Setting connect timeout to %d", FUNCNAME, timeout);
148   mysql_options(&ci.link, MYSQL_OPT_CONNECT_TIMEOUT,
149                 (char *)&timeout);
150   if (server->options.compress && atoi (server->options.compress))
151     {
152       D ("%s: Setting compressed protocol", FUNCNAME);
153       mysql_options(&ci.link, MYSQL_OPT_COMPRESS, 0);
154     }
155   if (server->options.initcmd && strlen (server->options.initcmd))
156     {
157       D ("%s: Setting init-command to '%s'", FUNCNAME, server->options.initcmd);
158       mysql_options(&ci.link, MYSQL_INIT_COMMAND,
159                     (char *)server->options.initcmd);
160     }
161   DEXIT
162 }
163
164 /*
165  * Validate existing connection.
166  * This does NOT run mysql_ping because that function is
167  * extraordinarily slow (~doubles time to fetch a query)
168  */
169 static nboolean
170 _nss_mysql_check_existing_connection (MYSQL_RES **mresult)
171 {
172   DN ("_nss_mysql_check_existing_connection")
173   static pid_t pid = -1;
174
175   DENTER
176   if (ci.valid == nfalse || conf.valid == nfalse)
177     DBRETURN (nfalse)
178
179   if (pid == -1)
180     pid = getpid ();
181   else if (pid == getppid ())
182     {
183       /* saved pid == ppid = we've forked; We MUST create a new connection */
184       D ("%s: fork() detected", FUNCNAME);
185       ci.valid = nfalse;
186       pid = getpid ();
187       DBRETURN (nfalse)
188     }
189
190   if (_nss_mysql_validate_socket () == nfalse)
191     {
192        /* Do *NOT* CLOSE_LINK - the socket is invalid! */
193       D ("%s: invalid socket detected", FUNCNAME);
194       _nss_mysql_close_sql (mresult, nfalse);
195       ci.valid = nfalse;
196       DBRETURN (nfalse)
197     }
198
199   DBRETURN (ntrue)
200 }
201
202 /*
203  * Connect to a MySQL server.
204  */
205 static NSS_STATUS
206 _nss_mysql_connect_sql (MYSQL_RES **mresult)
207 {
208   DN ("_nss_mysql_connect_sql")
209   int retval;
210   sql_server_t *server = &conf.sql.server;
211   unsigned int port;
212
213   DENTER
214
215   if (_nss_mysql_check_existing_connection (mresult) == ntrue)
216     {
217       D ("%s: Using existing connection", FUNCNAME);
218       DSRETURN (NSS_SUCCESS)
219     }
220
221   retval = _nss_mysql_load_config ();
222   if (retval != NSS_SUCCESS)
223     {
224       _nss_mysql_log (LOG_ALERT, "Failed to load configuration");
225       DSRETURN (NSS_UNAVAIL)
226     }
227
228   if (mysql_init (&ci.link) == NULL)
229     {
230       _nss_mysql_log (LOG_ALERT, "mysql_init() failed");
231       DSRETURN (NSS_UNAVAIL)
232     }
233
234   _nss_mysql_set_options (server);
235   D ("%s: Connecting to %s", FUNCNAME, server->host);
236   if (server->port)
237     port = atoi (server->port);
238   else
239     port = 0;
240   if (mysql_real_connect (&ci.link, server->host,
241                           server->username[0] ? server->username : NULL,
242                           server->password[0] ? server->password : NULL,
243                           server->database, port,
244                           server->socket[0] ? server->socket : NULL,
245                           0))
246     {
247       if (_nss_mysql_save_socket_info () != RETURN_SUCCESS )
248         {
249           _nss_mysql_log (LOG_ALERT, "Unable to save socket info");
250           _nss_mysql_close_sql (mresult, ntrue);
251           DSRETURN (NSS_UNAVAIL)
252         }
253       ci.valid = ntrue;
254       ci.link.reconnect = 0; /* Safety: We can't let MySQL assume socket is
255                                 still valid; see _nss_mysql_validate_socket */
256       DSRETURN (NSS_SUCCESS)
257     }
258   _nss_mysql_log (LOG_ALERT, "Connection to server '%s' failed: %s",
259                   server->host, mysql_error (&ci.link));
260   DSRETURN (NSS_UNAVAIL)
261 }
262
263 void
264 _nss_mysql_close_result (MYSQL_RES **mresult)
265 {
266   DN ("_nss_mysql_close_result")
267   DENTER
268   if (mresult && *mresult && ci.valid)
269     {
270       D ("%s, calling mysql_free_result()", FUNCNAME);
271       mysql_free_result (*mresult);
272     }
273   if (mresult)
274     *mresult = NULL;
275   DEXIT
276 }
277
278 /*
279  * Run MySQL query
280  */
281 NSS_STATUS
282 _nss_mysql_run_query (char *query, MYSQL_RES **mresult, int *attempts)
283 {
284   DN ("_nss_mysql_run_query")
285   int retval;
286
287   DENTER
288   if (!query)
289     DSRETURN (NSS_NOTFOUND)
290
291   D ("%s: Executing query: %s", FUNCNAME, query);
292
293   retval = _nss_mysql_connect_sql (mresult);
294   if (retval != NSS_SUCCESS)
295     DSRETURN (retval);
296
297   retval = mysql_query (&ci.link, query);
298   if (retval != RETURN_SUCCESS)
299     {
300       --(*attempts);
301       if (*attempts > 0)
302         {
303           _nss_mysql_log (LOG_ALERT,
304                           "mysql_query failed: %s, trying again (%d)",
305                           mysql_error (&ci.link), *attempts);
306           DSRETURN (_nss_mysql_run_query (query, mresult, attempts));
307         }
308       else
309         {
310           _nss_mysql_log (LOG_ALERT, "mysql_query failed: %s",
311                           mysql_error (&ci.link));
312           DSRETURN (retval);
313         }
314     }
315
316   if ((*mresult = mysql_store_result (&ci.link)) == NULL)
317     {
318       _nss_mysql_log (LOG_ALERT, "mysql_store_result failed: %s",
319                       mysql_error (&ci.link));
320       DSRETURN (NSS_UNAVAIL);
321     }
322   DSRETURN (NSS_SUCCESS)
323 }
324
325 NSS_STATUS
326 _nss_mysql_fetch_row (MYSQL_ROW *row, MYSQL_RES *mresult)
327 {
328   DN ("_nss_mysql_fetch_row")
329
330   DENTER
331   if ((*row = mysql_fetch_row (mresult)) == NULL)
332     {
333       if (mysql_errno (&ci.link))
334         {
335           _nss_mysql_log (LOG_ALERT, "mysql_fetch_row() failed: %s",
336                           mysql_error (&ci.link));
337           DSRETURN (NSS_UNAVAIL)
338         }
339       else
340         DSRETURN (NSS_NOTFOUND)
341     }
342   DSRETURN (NSS_SUCCESS)
343 }
344
345 NSS_STATUS
346 _nss_mysql_escape_string (char *to, const char *from, MYSQL_RES **mresult)
347 {
348   DN ("_nss_mysql_escape_string")
349
350   DENTER
351   if (_nss_mysql_connect_sql (mresult) != NSS_SUCCESS)
352     DSRETURN (NSS_UNAVAIL)
353   mysql_real_escape_string (&ci.link, to, from, strlen(from));
354   DSRETURN (NSS_SUCCESS)
355 }
356