Fix programming typo
[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.46 2005/09/04 03:34:02 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   const unsigned int def_timeout = DEF_TIMEOUT;
141
142   DENTER
143
144   mysql_options(&ci.link, MYSQL_OPT_CONNECT_TIMEOUT,
145                 (const char *) &def_timeout);
146   mysql_options(&ci.link, MYSQL_READ_DEFAULT_GROUP, "libnss-mysql");
147
148   DEXIT
149 }
150
151 /*
152  * Validate existing connection.
153  * This does NOT run mysql_ping because that function is
154  * extraordinarily slow (~doubles time to fetch a query)
155  */
156 static nboolean
157 _nss_mysql_check_existing_connection (MYSQL_RES **mresult)
158 {
159   DN ("_nss_mysql_check_existing_connection")
160   static pid_t pid = -1;
161
162   DENTER
163   if (ci.valid == nfalse || conf.valid == nfalse)
164     DBRETURN (nfalse)
165
166   if (pid == -1)
167     pid = getpid ();
168   else if (pid == getppid ())
169     {
170       /* saved pid == ppid = we've forked; We MUST create a new connection */
171       D ("%s: fork() detected", FUNCNAME);
172       ci.valid = nfalse;
173       pid = getpid ();
174       DBRETURN (nfalse)
175     }
176
177   if (_nss_mysql_validate_socket () == nfalse)
178     {
179        /* Do *NOT* CLOSE_LINK - the socket is invalid! */
180       D ("%s: invalid socket detected", FUNCNAME);
181       _nss_mysql_close_sql (mresult, nfalse);
182       ci.valid = nfalse;
183       DBRETURN (nfalse)
184     }
185
186   DBRETURN (ntrue)
187 }
188
189 /*
190  * Connect to a MySQL server.
191  */
192 static NSS_STATUS
193 _nss_mysql_connect_sql (MYSQL_RES **mresult)
194 {
195   DN ("_nss_mysql_connect_sql")
196   int retval;
197   sql_server_t *server = &conf.sql.server;
198   unsigned int port;
199
200   DENTER
201
202   if (_nss_mysql_check_existing_connection (mresult) == ntrue)
203     {
204       D ("%s: Using existing connection", FUNCNAME);
205       DSRETURN (NSS_SUCCESS)
206     }
207
208   retval = _nss_mysql_load_config ();
209   if (retval != NSS_SUCCESS)
210     {
211       _nss_mysql_log (LOG_ALERT, "Failed to load configuration");
212       DSRETURN (NSS_UNAVAIL)
213     }
214
215   if (mysql_init (&ci.link) == NULL)
216     {
217       _nss_mysql_log (LOG_ALERT, "mysql_init() failed");
218       DSRETURN (NSS_UNAVAIL)
219     }
220
221   _nss_mysql_set_options (server);
222   D ("%s: Connecting to %s", FUNCNAME, server->host);
223   if (server->port)
224     port = atoi (server->port);
225   else
226     port = 0;
227   if (mysql_real_connect (&ci.link, server->host,
228                           server->username[0] ? server->username : NULL,
229                           server->password[0] ? server->password : NULL,
230                           server->database, port,
231                           server->socket[0] ? server->socket : NULL,
232                           0))
233     {
234       if (_nss_mysql_save_socket_info () != RETURN_SUCCESS )
235         {
236           _nss_mysql_log (LOG_ALERT, "Unable to save socket info");
237           _nss_mysql_close_sql (mresult, ntrue);
238           DSRETURN (NSS_UNAVAIL)
239         }
240       ci.valid = ntrue;
241       ci.link.reconnect = 0; /* Safety: We can't let MySQL assume socket is
242                                 still valid; see _nss_mysql_validate_socket */
243       DSRETURN (NSS_SUCCESS)
244     }
245   _nss_mysql_log (LOG_ALERT, "Connection to server '%s' failed: %s",
246                   server->host, mysql_error (&ci.link));
247   DSRETURN (NSS_UNAVAIL)
248 }
249
250 void
251 _nss_mysql_close_result (MYSQL_RES **mresult)
252 {
253   DN ("_nss_mysql_close_result")
254   DENTER
255   if (mresult && *mresult && ci.valid)
256     {
257       D ("%s, calling mysql_free_result()", FUNCNAME);
258       mysql_free_result (*mresult);
259     }
260   if (mresult)
261     *mresult = NULL;
262   DEXIT
263 }
264
265 /*
266  * Run MySQL query
267  */
268 NSS_STATUS
269 _nss_mysql_run_query (char *query, MYSQL_RES **mresult, int *attempts)
270 {
271   DN ("_nss_mysql_run_query")
272   int retval;
273
274   DENTER
275   if (!query)
276     DSRETURN (NSS_NOTFOUND)
277
278   D ("%s: Executing query: %s", FUNCNAME, query);
279
280   retval = _nss_mysql_connect_sql (mresult);
281   if (retval != NSS_SUCCESS)
282     DSRETURN (retval);
283
284   retval = mysql_query (&ci.link, query);
285   if (retval != RETURN_SUCCESS)
286     {
287       --(*attempts);
288       if (*attempts > 0)
289         {
290           _nss_mysql_log (LOG_ALERT,
291                           "mysql_query failed: %s, trying again (%d)",
292                           mysql_error (&ci.link), *attempts);
293           DSRETURN (_nss_mysql_run_query (query, mresult, attempts));
294         }
295       else
296         {
297           _nss_mysql_log (LOG_ALERT, "mysql_query failed: %s",
298                           mysql_error (&ci.link));
299           DSRETURN (retval);
300         }
301     }
302
303   if ((*mresult = mysql_store_result (&ci.link)) == NULL)
304     {
305       _nss_mysql_log (LOG_ALERT, "mysql_store_result failed: %s",
306                       mysql_error (&ci.link));
307       DSRETURN (NSS_UNAVAIL);
308     }
309   DSRETURN (NSS_SUCCESS)
310 }
311
312 NSS_STATUS
313 _nss_mysql_fetch_row (MYSQL_ROW *row, MYSQL_RES *mresult)
314 {
315   DN ("_nss_mysql_fetch_row")
316
317   DENTER
318   if ((*row = mysql_fetch_row (mresult)) == NULL)
319     {
320       if (mysql_errno (&ci.link))
321         {
322           _nss_mysql_log (LOG_ALERT, "mysql_fetch_row() failed: %s",
323                           mysql_error (&ci.link));
324           DSRETURN (NSS_UNAVAIL)
325         }
326       else
327         DSRETURN (NSS_NOTFOUND)
328     }
329   DSRETURN (NSS_SUCCESS)
330 }
331
332 NSS_STATUS
333 _nss_mysql_escape_string (char *to, const char *from, MYSQL_RES **mresult)
334 {
335   DN ("_nss_mysql_escape_string")
336
337   DENTER
338   if (_nss_mysql_connect_sql (mresult) != NSS_SUCCESS)
339     DSRETURN (NSS_UNAVAIL)
340   mysql_real_escape_string (&ci.link, to, from, strlen(from));
341   DSRETURN (NSS_SUCCESS)
342 }
343