GSS_S_PROMPTING_NEEDED is a bit
[cyrus-sasl.git] / plugins / sql.c
1 /*
2 **
3 ** SQL Auxprop plugin
4 **
5 ** Ken Murchison
6 ** Maya Nigrosh -- original store() and txn support
7 ** Simon Loader -- original mysql plugin
8 ** Patrick Welche -- original pgsql plugin
9 **
10 ** $Id: sql.c,v 1.29 2006/04/07 13:42:16 jeaton Exp $
11 **
12 */
13
14 #include <config.h>
15
16 #include <stdio.h>
17 #include <assert.h>
18 #include <stdlib.h>
19 #include <string.h>
20
21 #include "sasl.h"
22 #include "saslutil.h"
23 #include "saslplug.h"
24
25 #include <ctype.h>
26
27 #include "plugin_common.h"
28
29 #define sql_max(a, b) ((a) > (b) ? (a) : (b))
30 #define sql_len(input) ((input) ? strlen(input) : 0)
31 #define sql_exists(input) ((input) && (*input))
32
33 typedef struct sql_engine {
34     const char *name;
35     void *(*sql_open)(char *host, char *port, int usessl,
36                       const char *user, const char *password,
37                       const char *database, const sasl_utils_t *utils);
38     int (*sql_escape_str)(char *to, const char *from);
39     int (*sql_begin_txn)(void *conn, const sasl_utils_t *utils);
40     int (*sql_commit_txn)(void *conn, const sasl_utils_t *utils);
41     int (*sql_rollback_txn)(void *conn, const sasl_utils_t *utils);
42     int (*sql_exec)(void *conn, const char *cmd, char *value, size_t size,
43                     size_t *value_len, const sasl_utils_t *utils);
44     void (*sql_close)(void *conn);
45 } sql_engine_t;
46
47 typedef struct sql_settings {
48     const sql_engine_t *sql_engine;
49     const char *sql_user;
50     const char *sql_passwd;
51     const char *sql_hostnames;
52     const char *sql_database;
53     const char *sql_select;
54     const char *sql_insert;
55     const char *sql_update;
56     int sql_usessl;
57 } sql_settings_t;
58
59 static const char * SQL_BLANK_STRING = "";
60 static const char * SQL_WILDCARD = "*";
61 static const char * SQL_NULL_VALUE = "NULL";
62
63
64 #ifdef HAVE_MYSQL
65 #include <mysql.h>
66
67 static void *_mysql_open(char *host, char *port, int usessl,
68                          const char *user, const char *password,
69                          const char *database, const sasl_utils_t *utils)
70 {
71     MYSQL *mysql;
72     
73     if (!(mysql = mysql_init(NULL))) {
74         utils->log(NULL, SASL_LOG_ERR,
75                    "sql plugin: could not execute mysql_init()");
76         return NULL;
77     }
78     
79     return mysql_real_connect(mysql, host, user, password, database,
80                               port ? strtoul(port, NULL, 10) : 0, NULL,
81                               usessl ? CLIENT_SSL : 0);
82 }
83
84 static int _mysql_escape_str(char *to, const char *from)
85 {
86     return mysql_escape_string(to, from, strlen(from));
87 }
88
89 static int _mysql_exec(void *conn, const char *cmd, char *value, size_t size,
90                        size_t *value_len, const sasl_utils_t *utils)
91 {
92     MYSQL_RES *result;
93     MYSQL_ROW row;
94     int row_count, len;
95     
96     len = strlen(cmd);
97     /* mysql_real_query() doesn't want a terminating ';' */
98     if (cmd[len-1] == ';') len--;
99
100     /* 
101      *  Run the query. It is important to note that mysql_real_query
102      *  will return success even if the sql statement 
103      *  had an error in it. However, mysql_errno() will alsways
104      *  tell us if there was an error. Therefore we can ignore
105      *  the result from mysql_real_query and simply check mysql_errno()
106      *  to decide if there was really an error.
107      */
108     (void)mysql_real_query(conn, cmd, len);
109
110     if(mysql_errno(conn)) {
111         utils->log(NULL, SASL_LOG_ERR, "sql query failed: %s",
112                    mysql_error(conn));
113         return -1;
114     }
115
116     /* see if we should expect some results */
117     if (!mysql_field_count(conn)) {
118         /* no results (BEGIN, COMMIT, DELETE, INSERT, UPDATE) */
119         return 0;
120     }
121
122     /* get the results */
123     result = mysql_store_result(conn);
124     if (!result) {
125         /* umm nothing found */
126         utils->log(NULL, SASL_LOG_NOTE, "sql plugin: no result found");
127         return -1;
128     }
129
130     /* quick row check */
131     row_count = mysql_num_rows(result);
132     if (!row_count) {
133         /* umm nothing found */
134         mysql_free_result(result);
135         utils->log(NULL, SASL_LOG_NOTE, "sql plugin: no result found");
136         return -1;
137     }
138     if (row_count > 1) {
139         utils->log(NULL, SASL_LOG_WARN,
140                    "sql plugin: found duplicate row for query %s", cmd);
141     }
142     
143     /* now get the result set value and value_len */
144     /* we only fetch one because we don't care about the rest */
145     row = mysql_fetch_row(result);
146     if (!row || !row[0]) {
147         /* umm nothing found */
148         utils->log(NULL, SASL_LOG_NOTE, "sql plugin: no result found");
149         mysql_free_result(result);
150         return -1;
151     }
152     if (value) {
153         strncpy(value, row[0], size-2);
154         value[size-1] = '\0';
155         if (value_len) *value_len = strlen(value);
156     }
157     
158     /* free result */
159     mysql_free_result(result);
160     
161     return 0;
162 }
163
164 static int _mysql_begin_txn(void *conn, const sasl_utils_t *utils)
165 {
166     return _mysql_exec(conn,
167 #if MYSQL_VERSION_ID >= 40011
168                        "START TRANSACTION",
169 #else
170                        "BEGIN",
171 #endif
172                        NULL, 0, NULL, utils);
173 }
174
175 static int _mysql_commit_txn(void *conn, const sasl_utils_t *utils)
176 {
177     return _mysql_exec(conn, "COMMIT", NULL, 0, NULL, utils);
178 }
179
180 static int _mysql_rollback_txn(void *conn, const sasl_utils_t *utils)
181 {
182     return _mysql_exec(conn, "ROLLBACK", NULL, 0, NULL, utils);
183 }
184
185 static void _mysql_close(void *conn)
186 {
187     mysql_close(conn);
188 }
189 #endif /* HAVE_MYSQL */
190
191 #ifdef HAVE_PGSQL
192 #include <libpq-fe.h>
193
194 static void *_pgsql_open(char *host, char *port, int usessl,
195                          const char *user, const char *password,
196                          const char *database, const sasl_utils_t *utils)
197 {
198     PGconn *conn = NULL;
199     char *conninfo, *sep;
200     
201     /* create the connection info string */
202     /* The 64 represents the number of characters taken by
203      * the keyword tokens, plus a small pad
204      */
205     conninfo = utils->malloc(64 + sql_len(host) + sql_len(port)
206                              + sql_len(user) + sql_len(password)
207                              + sql_len(database));
208     if (!conninfo) {
209         MEMERROR(utils);
210         return NULL;
211     }
212     
213     /* add each term that exists */
214     conninfo[0] = '\0';
215     sep = "";
216     if (sql_exists(host)) {
217         strcat(conninfo, sep);
218         strcat(conninfo, "host='");
219         strcat(conninfo, host);
220         strcat(conninfo, "'");
221         sep = " ";
222     }
223     if (sql_exists(port)) {
224         strcat(conninfo, sep);
225         strcat(conninfo, "port='");
226         strcat(conninfo, port);
227         strcat(conninfo, "'");
228         sep = " ";
229     }
230     if (sql_exists(user)) {
231         strcat(conninfo, sep);
232         strcat(conninfo, "user='");
233         strcat(conninfo, user);
234         strcat(conninfo, "'");
235         sep = " ";
236     }
237     if (sql_exists(password)) {
238         strcat(conninfo, sep);
239         strcat(conninfo, "password='");
240         strcat(conninfo, password);
241         strcat(conninfo, "'");
242         sep = " ";
243     }
244     if (sql_exists(database)) {
245         strcat(conninfo, sep);
246         strcat(conninfo, "dbname='");
247         strcat(conninfo, database);
248         strcat(conninfo, "'");
249         sep = " ";
250     }
251     if (usessl) {
252         strcat(conninfo, sep);
253         strcat(conninfo, "requiressl='1'");
254     }
255     
256     conn = PQconnectdb(conninfo);
257     free(conninfo);
258     
259     if ((PQstatus(conn) != CONNECTION_OK)) {
260         utils->log(NULL, SASL_LOG_ERR, "sql plugin: %s", PQerrorMessage(conn));
261         return NULL;
262     }
263     
264     return conn;
265 }
266
267 static int _pgsql_escape_str(char *to, const char *from)
268 {
269     return PQescapeString(to, from, strlen(from));
270 }
271
272 static int _pgsql_exec(void *conn, const char *cmd, char *value, size_t size,
273                        size_t *value_len, const sasl_utils_t *utils)
274 {
275     PGresult *result;
276     int row_count;
277     ExecStatusType status;
278     
279     /* run the query */
280     result = PQexec(conn, cmd);
281     
282     /* check the status */
283     status = PQresultStatus(result);
284     if (status == PGRES_COMMAND_OK) {
285         /* no results (BEGIN, COMMIT, DELETE, INSERT, UPDATE) */
286         PQclear(result);
287         return 0;
288     }
289     else if (status != PGRES_TUPLES_OK) {
290         /* error */
291         utils->log(NULL, SASL_LOG_DEBUG, "sql plugin: %s ",
292                    PQresStatus(status));
293         PQclear(result);
294         return -1;
295     }
296     
297     /* quick row check */
298     row_count = PQntuples(result);
299     if (!row_count) {
300         /* umm nothing found */
301         utils->log(NULL, SASL_LOG_NOTE, "sql plugin: no result found");
302         PQclear(result);
303         return -1;
304     }
305     if (row_count > 1) {
306         utils->log(NULL, SASL_LOG_WARN,
307                    "sql plugin: found duplicate row for query %s", cmd);
308     }
309     
310     /* now get the result set value and value_len */
311     /* we only fetch one because we don't care about the rest */
312     if (value) {
313         strncpy(value, PQgetvalue(result,0,0), size-2);
314         value[size-1] = '\0';
315         if (value_len) *value_len = strlen(value);
316     }
317     
318     /* free result */
319     PQclear(result);
320     return 0;
321 }
322
323 static int _pgsql_begin_txn(void *conn, const sasl_utils_t *utils)
324 {
325     return _pgsql_exec(conn, "BEGIN;", NULL, 0, NULL, utils);
326 }
327
328 static int _pgsql_commit_txn(void *conn, const sasl_utils_t *utils)
329 {
330     return _pgsql_exec(conn, "COMMIT;", NULL, 0, NULL, utils);
331 }
332
333 static int _pgsql_rollback_txn(void *conn, const sasl_utils_t *utils)
334 {
335     return _pgsql_exec(conn, "ROLLBACK;", NULL, 0, NULL, utils);
336 }
337
338 static void _pgsql_close(void *conn)
339 {
340     PQfinish(conn);
341 }
342 #endif /* HAVE_PGSQL */
343
344 #ifdef HAVE_SQLITE
345 #include <sqlite.h>
346
347 static void *_sqlite_open(char *host __attribute__((unused)),
348                           char *port __attribute__((unused)),
349                           int usessl __attribute__((unused)),
350                           const char *user __attribute__((unused)),
351                           const char *password __attribute__((unused)),
352                           const char *database, const sasl_utils_t *utils)
353 {
354     int rc;
355     sqlite *db;
356     char *zErrMsg = NULL;
357
358     db = sqlite_open(database, 0, &zErrMsg);
359     if (db == NULL) {
360         utils->log(NULL, SASL_LOG_ERR, "sql plugin: %s", zErrMsg);
361         sqlite_freemem (zErrMsg);
362         return NULL;
363     }
364
365     rc = sqlite_exec(db, "PRAGMA empty_result_callbacks = ON", NULL, NULL, &zErrMsg);
366     if (rc != SQLITE_OK) {
367         utils->log(NULL, SASL_LOG_ERR, "sql plugin: %s", zErrMsg);
368         sqlite_freemem (zErrMsg);
369         sqlite_close(db);
370         return NULL;
371     }
372
373     return (void*)db;
374 }
375
376 static int _sqlite_escape_str(char *to, const char *from)
377 {
378     char s;
379
380     while ( (s = *from++) != '\0' ) {
381         if (s == '\'' || s == '\\') {
382             *to++ = '\\';
383         }
384         *to++ = s;
385     }
386     *to = '\0';
387
388     return 0;
389 }
390
391 static int sqlite_my_callback(void *pArg, int argc __attribute__((unused)),
392                               char **argv,
393                               char **columnNames __attribute__((unused)))
394 {
395     char **result = (char**)pArg;
396
397     if (argv == NULL) {
398         *result = NULL;                         /* no record */
399     } else if (argv[0] == NULL) {
400         *result = strdup(SQL_NULL_VALUE);       /* NULL IS SQL_NULL_VALUE */
401     } else {
402         *result = strdup(argv[0]);
403     }
404
405     return /*ABORT*/1;
406 }
407
408 static int _sqlite_exec(void *db, const char *cmd, char *value, size_t size,
409                         size_t *value_len, const sasl_utils_t *utils)
410 {
411     int rc;
412     char *result = NULL;
413     char *zErrMsg = NULL;
414
415     rc = sqlite_exec((sqlite*)db, cmd, sqlite_my_callback, (void*)&result, &zErrMsg);
416     if (rc != SQLITE_OK && rc != SQLITE_ABORT) {
417         utils->log(NULL, SASL_LOG_DEBUG, "sql plugin: %s ", zErrMsg);
418         sqlite_freemem (zErrMsg);
419         return -1;
420     }
421
422     if (rc == SQLITE_OK) {
423         /* no results (BEGIN, COMMIT, DELETE, INSERT, UPDATE) */
424         return 0;
425     }
426
427     if (result == NULL) {
428         /* umm nothing found */
429         utils->log(NULL, SASL_LOG_NOTE, "sql plugin: no result found");
430         return -1;
431     }
432
433     /* XXX: Duplication cannot be found by this method. */
434
435     /* now get the result set value and value_len */
436     /* we only fetch one because we don't care about the rest */
437     if (value) {
438         strncpy(value, result, size - 2);
439         value[size - 1] = '\0';
440         if (value_len) {
441             *value_len = strlen(value);
442         }
443     }
444
445     /* free result */
446     free(result);
447     return 0;
448 }
449
450 static int _sqlite_begin_txn(void *db, const sasl_utils_t *utils)
451 {
452     return _sqlite_exec(db, "BEGIN TRANSACTION", NULL, 0, NULL, utils);
453 }
454
455 static int _sqlite_commit_txn(void *db, const sasl_utils_t *utils)
456 {
457     return _sqlite_exec(db, "COMMIT TRANSACTION", NULL, 0, NULL, utils);
458 }
459
460 static int _sqlite_rollback_txn(void *db, const sasl_utils_t *utils)
461 {
462     return _sqlite_exec(db, "ROLLBACK TRANSACTION", NULL, 0, NULL, utils);
463 }
464
465 static void _sqlite_close(void *db)
466 {
467     sqlite_close((sqlite*)db);
468 }
469 #endif /* HAVE_SQLITE */
470
471 static const sql_engine_t sql_engines[] = {
472 #ifdef HAVE_MYSQL
473     { "mysql", &_mysql_open, &_mysql_escape_str,
474       &_mysql_begin_txn, &_mysql_commit_txn, &_mysql_rollback_txn,
475       &_mysql_exec, &_mysql_close },
476 #endif /* HAVE_MYSQL */
477 #ifdef HAVE_PGSQL
478     { "pgsql", &_pgsql_open, &_pgsql_escape_str,
479       &_pgsql_begin_txn, &_pgsql_commit_txn, &_pgsql_rollback_txn,
480       &_pgsql_exec, &_pgsql_close },
481 #endif
482 #ifdef HAVE_SQLITE
483     { "sqlite", &_sqlite_open, &_sqlite_escape_str,
484       &_sqlite_begin_txn, &_sqlite_commit_txn, &_sqlite_rollback_txn,
485       &_sqlite_exec, &_sqlite_close },
486 #endif
487     { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
488 };
489
490 /*
491 **  Sql_create_statement
492 **   uses statement line and allocate memory to replace
493 **  Parts with the strings provided.
494 **   %<char> =  no change
495 **   %% = %
496 **   %u = user
497 **   %p = prop
498 **   %r = realm
499 **   %v = value of prop
500 **  e.g select %p from auth where user = %p and domain = %r;
501 **  Note: calling function must free memory.
502 **
503 */
504
505 static char *sql_create_statement(const char *statement, const char *prop,
506                                   const char *user, const char *realm, 
507                                   const char *value,  
508                                   const sasl_utils_t *utils)
509 {
510     const char *ptr, *line_ptr;
511     char *buf, *buf_ptr;
512     int filtersize;
513     int ulen, plen, rlen, vlen;
514     int numpercents=0;
515     int biggest;
516     size_t i;
517     
518     /* calculate memory needed for creating the complete query string. */
519     ulen = strlen(user);
520     rlen = strlen(realm);
521     plen = strlen(prop);
522     vlen = sql_len(value);
523     
524     /* what if we have multiple %foo occurrences in the input query? */
525     for (i = 0; i < strlen(statement); i++) {
526         if (statement[i] == '%') {
527             numpercents++;
528         }
529     }
530     
531     /* find the biggest of ulen, rlen, plen, vlen */
532     biggest = sql_max(sql_max(ulen, rlen), sql_max(plen, vlen));
533     
534     /* plus one for the semicolon...and don't forget the trailing 0x0 */
535     filtersize = strlen(statement) + 1 + (numpercents*biggest)+1;
536     
537     /* ok, now try to allocate a chunk of that size */
538     buf = (char *) utils->malloc(filtersize);
539     
540     if (!buf) {
541         MEMERROR(utils);
542         return NULL;
543     }
544     
545     buf_ptr = buf;
546     line_ptr = statement;
547     
548     /* replace the strings */
549     while ( (ptr = strchr(line_ptr, '%')) ) {
550         /* copy up to but not including the next % */
551         memcpy(buf_ptr, line_ptr, ptr - line_ptr); 
552         buf_ptr += ptr - line_ptr;
553         ptr++;
554         switch (ptr[0]) {
555         case '%':
556             buf_ptr[0] = '%';
557             buf_ptr++;
558             break;
559         case 'u':
560             memcpy(buf_ptr, user, ulen);
561             buf_ptr += ulen;
562             break;
563         case 'r':
564             memcpy(buf_ptr, realm, rlen);
565             buf_ptr += rlen;
566             break;
567         case 'p':
568             memcpy(buf_ptr, prop, plen);
569             buf_ptr += plen;
570             break;
571         case 'v':
572             if (value != NULL) {
573                 memcpy(buf_ptr, value, vlen);
574                 buf_ptr += vlen;
575             }
576             else {
577                 utils->log(NULL, SASL_LOG_ERR,
578                            "'%%v' shouldn't be in a SELECT or DELETE");
579             }
580             break;
581         default:
582             buf_ptr[0] = '%';
583             buf_ptr[1] = ptr[0];
584             buf_ptr += 2;
585             break;
586         }
587         ptr++;
588         line_ptr = ptr;
589     }
590     
591     memcpy(buf_ptr, line_ptr, strlen(line_ptr)+1);
592     /* Make sure the current portion of the statement ends with a semicolon */
593     if (buf_ptr[strlen(buf_ptr-1)] != ';') {
594         strcat(buf_ptr, ";");
595     }
596
597     return (buf);
598 }
599
600 /* sql_get_settings
601  *
602  * Get the auxprop settings and put them in the global context array
603 */
604 static void sql_get_settings(const sasl_utils_t *utils, void *glob_context)
605 {
606     sql_settings_t *settings;
607     int r;
608     const char *usessl, *engine_name;
609     const sql_engine_t *e;
610     
611     settings = (sql_settings_t *) glob_context;
612     
613     r = utils->getopt(utils->getopt_context,"SQL", "sql_engine",
614                       &engine_name, NULL);
615     if (r || !engine_name) {
616         engine_name = "mysql";
617     }
618     
619     /* find the correct engine */
620     e = sql_engines;
621     while (e->name) {
622         if (!strcasecmp(engine_name, e->name)) break;
623         e++;
624     }
625
626     if (!e->name) {
627         utils->log(NULL, SASL_LOG_ERR, "SQL engine '%s' not supported",
628                    engine_name);
629     }
630
631     settings->sql_engine = e;
632
633     r = utils->getopt(utils->getopt_context,"SQL","sql_user",
634                       &settings->sql_user, NULL);
635     if ( r || !settings->sql_user ) {
636         settings->sql_user = SQL_BLANK_STRING;
637     }
638
639     r = utils->getopt(utils->getopt_context,"SQL", "sql_passwd",
640                       &settings->sql_passwd, NULL);
641     if (r || !settings->sql_passwd ) {
642         settings->sql_passwd = SQL_BLANK_STRING;
643     }
644
645     r = utils->getopt(utils->getopt_context,"SQL", "sql_hostnames",
646                       &settings->sql_hostnames, NULL);
647     if (r || !settings->sql_hostnames ) {
648         settings->sql_hostnames = SQL_BLANK_STRING;
649     }
650
651     r = utils->getopt(utils->getopt_context,"SQL", "sql_database",
652                       &settings->sql_database, NULL);
653     if (r || !settings->sql_database ) {
654         settings->sql_database = SQL_BLANK_STRING;
655     }
656
657     r = utils->getopt(utils->getopt_context,"SQL", "sql_select",
658                       &settings->sql_select, NULL);
659     if (r || !settings->sql_select ) {
660         /* backwards compatibility */
661         r = utils->getopt(utils->getopt_context,"SQL", "sql_statement",
662                           &settings->sql_select, NULL);
663         if (r || !settings->sql_select) {
664             settings->sql_select = SQL_BLANK_STRING;
665         }
666     }
667
668     r = utils->getopt(utils->getopt_context, "SQL", "sql_insert",
669                       &settings->sql_insert, NULL);
670     if (r || !settings->sql_insert) {
671         settings->sql_insert = SQL_BLANK_STRING;
672     }
673
674     r = utils->getopt(utils->getopt_context, "SQL", "sql_update",
675                       &settings->sql_update, NULL);
676     if (r || !settings->sql_update) {
677         settings->sql_update = SQL_BLANK_STRING;
678     }
679
680     r = utils->getopt(utils->getopt_context, "SQL", "sql_usessl",
681                   &usessl, NULL);
682     if (r || !usessl) usessl = "no";
683
684     if (*usessl == '1' || *usessl == 'y'  || *usessl == 't' ||
685         (*usessl == 'o' && usessl[1] == 'n')) {
686         settings->sql_usessl = 1;
687     } else {
688         settings->sql_usessl = 0;
689     }
690 }
691
692 static void *sql_connect(sql_settings_t *settings, const sasl_utils_t *utils)
693 {
694     void *conn = NULL;
695     char *db_host_ptr = NULL;
696     char *db_host = NULL;
697     char *cur_host, *cur_port;
698
699     /* loop around hostnames till we get a connection 
700      * it should probably save the connection but for 
701      * now we will just disconnect everytime
702      */
703     utils->log(NULL, SASL_LOG_DEBUG,
704                "sql plugin try and connect to a host\n");
705     
706     /* create a working version of the hostnames */
707     _plug_strdup(utils, settings->sql_hostnames, &db_host_ptr, NULL);
708     db_host = db_host_ptr;
709     cur_host = db_host;
710     while (cur_host != NULL) {
711         db_host = strchr(db_host,',');
712         if (db_host != NULL) {  
713             db_host[0] = '\0';
714
715             /* loop till we find some text */
716             while (!isalnum(db_host[0])) db_host++;
717         }
718         
719         utils->log(NULL, SASL_LOG_DEBUG,
720                    "sql plugin trying to open db '%s' on host '%s'%s\n",
721                    settings->sql_database, cur_host,
722                    settings->sql_usessl ? " using SSL" : "");
723         
724         /* set the optional port */
725         if ((cur_port = strchr(cur_host, ':'))) *cur_port++ = '\0';
726         
727         conn = settings->sql_engine->sql_open(cur_host, cur_port,
728                                               settings->sql_usessl,
729                                               settings->sql_user,
730                                               settings->sql_passwd,
731                                               settings->sql_database,
732                                               utils);
733         if (conn) break;
734         
735         utils->log(NULL, SASL_LOG_ERR,
736                    "sql plugin could not connect to host %s", cur_host);
737         
738         cur_host = db_host;
739     }
740
741     if (db_host_ptr) utils->free(db_host_ptr);
742
743     return conn;
744 }
745
746 static void sql_auxprop_lookup(void *glob_context,
747                                sasl_server_params_t *sparams,
748                                unsigned flags,
749                                const char *user,
750                                unsigned ulen) 
751 {
752     char *userid = NULL;
753     /* realm could be used for something clever */
754     char *realm = NULL;
755     const char *user_realm = NULL;
756     const struct propval *to_fetch, *cur;
757     char value[8192];
758     size_t value_len;
759     
760     char *user_buf;
761     char *query = NULL;
762     char *escap_userid = NULL;
763     char *escap_realm = NULL;
764     sql_settings_t *settings;
765     void *conn = NULL;
766     int do_txn = 0;
767     
768     if (!glob_context || !sparams || !user) return;
769     
770     /* setup the settings */
771     settings = (sql_settings_t *) glob_context;
772     
773     sparams->utils->log(NULL, SASL_LOG_DEBUG,
774                         "sql plugin Parse the username %s\n", user);
775     
776     user_buf = sparams->utils->malloc(ulen + 1);
777     if (!user_buf) goto done;
778     
779     memcpy(user_buf, user, ulen);
780     user_buf[ulen] = '\0';
781     
782     if(sparams->user_realm) {
783         user_realm = sparams->user_realm;
784     } else {
785         user_realm = sparams->serverFQDN;
786     }
787     
788     if (_plug_parseuser(sparams->utils, &userid, &realm, user_realm,
789                         sparams->serverFQDN, user_buf) != SASL_OK )
790         goto done;
791     
792     /* just need to escape userid and realm now */
793     /* allocate some memory */
794     escap_userid = (char *)sparams->utils->malloc(strlen(userid)*2+1);
795     escap_realm = (char *)sparams->utils->malloc(strlen(realm)*2+1);
796     
797     if (!escap_userid || !escap_realm) {
798         MEMERROR(sparams->utils);
799         goto done;
800     }
801     
802     /*************************************/
803     
804     /* find out what we need to get */
805     /* this corrupts const char *user */
806     to_fetch = sparams->utils->prop_get(sparams->propctx);
807     if (!to_fetch) goto done;
808
809     conn = sql_connect(settings, sparams->utils);
810     if (!conn) {
811         sparams->utils->log(NULL, SASL_LOG_ERR,
812                             "sql plugin couldn't connect to any host\n");
813         
814         goto done;
815     }
816     
817     /* escape out */
818     settings->sql_engine->sql_escape_str(escap_userid, userid);
819     settings->sql_engine->sql_escape_str(escap_realm, realm);
820     
821     for (cur = to_fetch; cur->name; cur++) {
822         char *realname = (char *) cur->name;
823
824         /* Only look up properties that apply to this lookup! */
825         if (cur->name[0] == '*'
826             && (flags & SASL_AUXPROP_AUTHZID))
827             continue;
828         if (!(flags & SASL_AUXPROP_AUTHZID)) {
829             if(cur->name[0] != '*')
830                 continue;
831             else
832                 realname = (char*)cur->name + 1;
833         }
834         
835         /* If it's there already, we want to see if it needs to be
836          * overridden */
837         if (cur->values && !(flags & SASL_AUXPROP_OVERRIDE))
838             continue;
839         else if (cur->values)
840             sparams->utils->prop_erase(sparams->propctx, cur->name);
841
842         if (!do_txn) {
843             do_txn = 1;
844             sparams->utils->log(NULL, SASL_LOG_DEBUG, "begin transaction");
845             if (settings->sql_engine->sql_begin_txn(conn, sparams->utils)) {
846                 sparams->utils->log(NULL, SASL_LOG_ERR, 
847                                     "Unable to begin transaction\n");
848             }
849         }
850     
851         sparams->utils->log(NULL, SASL_LOG_DEBUG,
852                             "sql plugin create statement from %s %s %s\n",
853                             realname, escap_userid, escap_realm);
854         
855         /* create a statement that we will use */
856         query = sql_create_statement(settings->sql_select,
857                                      realname,escap_userid,
858                                      escap_realm, NULL,
859                                      sparams->utils);
860         
861         sparams->utils->log(NULL, SASL_LOG_DEBUG,
862                             "sql plugin doing query %s\n", query);
863         
864         /* run the query */
865         if (!settings->sql_engine->sql_exec(conn, query, value, sizeof(value),
866                                             &value_len, sparams->utils)) {
867             sparams->utils->prop_set(sparams->propctx, cur->name,
868                                      value, value_len);
869         }
870         
871         sparams->utils->free(query);
872     }
873
874     if (do_txn) {
875         sparams->utils->log(NULL, SASL_LOG_DEBUG, "commit transaction");
876         if (settings->sql_engine->sql_commit_txn(conn, sparams->utils)) {
877             sparams->utils->log(NULL, SASL_LOG_ERR, 
878                                 "Unable to commit transaction\n");
879         }
880     }
881     
882   done:
883     if (escap_userid) sparams->utils->free(escap_userid);
884     if (escap_realm) sparams->utils->free(escap_realm);
885     if (conn) settings->sql_engine->sql_close(conn);
886     if (userid) sparams->utils->free(userid);
887     if (realm) sparams->utils->free(realm);
888     if (user_buf) sparams->utils->free(user_buf);
889 }
890
891 static int sql_auxprop_store(void *glob_context,
892                              sasl_server_params_t *sparams,
893                              struct propctx *ctx,
894                              const char *user,
895                              unsigned ulen) 
896 {
897     char *userid = NULL;
898     char *realm = NULL;
899     const char *user_realm = NULL;
900     int ret = SASL_FAIL;
901     const struct propval *to_store, *cur;
902     
903     char *user_buf;
904     char *statement = NULL;
905     char *escap_userid = NULL;
906     char *escap_realm = NULL;
907     const char *cmd;
908     
909     sql_settings_t *settings;
910     void *conn = NULL;
911     
912     settings = (sql_settings_t *) glob_context; 
913
914     /* just checking if we are enabled */
915     if (!ctx &&
916         sql_exists(settings->sql_insert) &&
917         sql_exists(settings->sql_update)) return SASL_OK;
918     
919     /* make sure our input is okay */
920     if (!glob_context || !sparams || !user) return SASL_BADPARAM;
921     
922     sparams->utils->log(NULL, SASL_LOG_DEBUG,
923                         "sql plugin Parse the username %s\n", user);
924     
925     user_buf = sparams->utils->malloc(ulen + 1);
926     if (!user_buf) {
927         ret = SASL_NOMEM;
928         goto done;
929     }
930     
931     memcpy(user_buf, user, ulen);
932     user_buf[ulen] = '\0';
933     
934     if (sparams->user_realm) {
935         user_realm = sparams->user_realm;
936     }
937     else {
938         user_realm = sparams->serverFQDN;
939     }
940     
941     ret = _plug_parseuser(sparams->utils, &userid, &realm, user_realm,
942                           sparams->serverFQDN, user_buf);
943     if (ret != SASL_OK) goto done;
944     
945     /* just need to escape userid and realm now */
946     /* allocate some memory */
947     
948     escap_userid = (char *) sparams->utils->malloc(strlen(userid)*2+1);
949     escap_realm = (char *) sparams->utils->malloc(strlen(realm)*2+1);
950     
951     if (!escap_userid || !escap_realm) {
952         MEMERROR(sparams->utils);
953         goto done;
954     }
955     
956     to_store = sparams->utils->prop_get(ctx);
957     
958     if (!to_store) {
959         ret = SASL_BADPARAM;
960         goto done;
961     }
962
963     conn = sql_connect(settings, sparams->utils);
964     if (!conn) {
965         sparams->utils->log(NULL, SASL_LOG_ERR,
966                             "sql plugin couldn't connect to any host\n");
967         goto done;
968     }
969     
970     settings->sql_engine->sql_escape_str(escap_userid, userid);
971     settings->sql_engine->sql_escape_str(escap_realm, realm);
972     
973     if (settings->sql_engine->sql_begin_txn(conn, sparams->utils)) {
974         sparams->utils->log(NULL, SASL_LOG_ERR, 
975                             "Unable to begin transaction\n");
976     }
977     for (cur = to_store; ret == SASL_OK && cur->name; cur++) {
978         /* determine which command we need */
979         /* see if we already have a row for this user */
980         statement = sql_create_statement(settings->sql_select,
981                                          SQL_WILDCARD, escap_userid,
982                                          escap_realm, NULL,
983                                          sparams->utils);
984         if (!settings->sql_engine->sql_exec(conn, statement, NULL, 0, NULL,
985                                             sparams->utils)) {
986             /* already have a row => UPDATE */
987             cmd = settings->sql_update;
988         } else {
989             /* new row => INSERT */
990             cmd = settings->sql_insert;
991         }
992         sparams->utils->free(statement);
993
994         /* create a statement that we will use */
995         statement = sql_create_statement(cmd, cur->name, escap_userid,
996                                          escap_realm,
997                                          cur->values && cur->values[0] ?
998                                          cur->values[0] : SQL_NULL_VALUE,
999                                          sparams->utils);
1000         
1001         {
1002             char *log_statement =
1003                 sql_create_statement(cmd, cur->name,
1004                                      escap_userid,
1005                                      escap_realm,
1006                                      cur->values && cur->values[0] ?
1007                                      "<omitted>" : SQL_NULL_VALUE,
1008                                      sparams->utils);
1009             sparams->utils->log(NULL, SASL_LOG_DEBUG,
1010                                 "sql plugin doing statement %s\n",
1011                                 log_statement);
1012             sparams->utils->free(log_statement);
1013         }
1014         
1015         /* run the statement */
1016         if (settings->sql_engine->sql_exec(conn, statement, NULL, 0, NULL,
1017                                            sparams->utils)) {
1018             ret = SASL_FAIL;
1019         }
1020         
1021         sparams->utils->free(statement);
1022     }
1023     if (ret != SASL_OK) {
1024         sparams->utils->log(NULL, SASL_LOG_ERR,
1025                             "Failed to store auxprop; aborting transaction\n");
1026         if (settings->sql_engine->sql_rollback_txn(conn, sparams->utils)) {
1027             sparams->utils->log(NULL, SASL_LOG_ERR, 
1028                                 "Unable to rollback transaction\n");
1029         }
1030     }
1031     else if (settings->sql_engine->sql_commit_txn(conn, sparams->utils)) {
1032         sparams->utils->log(NULL, SASL_LOG_ERR, 
1033                             "Unable to commit transaction\n");
1034     }
1035     
1036   done:
1037     if (escap_userid) sparams->utils->free(escap_userid);
1038     if (escap_realm) sparams->utils->free(escap_realm);
1039     if (conn) settings->sql_engine->sql_close(conn);
1040     if (userid) sparams->utils->free(userid);
1041     if (realm) sparams->utils->free(realm);
1042     if (user_buf) sparams->utils->free(user_buf);
1043     
1044     return ret;
1045     
1046     /* do a little dance */
1047 }
1048
1049
1050 static void sql_auxprop_free(void *glob_context, const sasl_utils_t *utils)
1051 {
1052     sql_settings_t *settings;
1053     
1054     settings = (sql_settings_t *)glob_context;
1055     
1056     if (!settings) return;
1057     
1058     utils->log(NULL, SASL_LOG_DEBUG, "sql freeing memory\n");
1059     
1060     utils->free(settings);
1061 }
1062
1063 static sasl_auxprop_plug_t sql_auxprop_plugin = {
1064     0,                  /* Features */
1065     0,                  /* spare */
1066     NULL,               /* glob_context */
1067     sql_auxprop_free,   /* auxprop_free */
1068     sql_auxprop_lookup, /* auxprop_lookup */
1069     "sql",              /* name */
1070     sql_auxprop_store   /* auxprop_store */
1071 };
1072
1073 int sql_auxprop_plug_init(const sasl_utils_t *utils,
1074                           int max_version,
1075                           int *out_version,
1076                           sasl_auxprop_plug_t **plug,
1077                           const char *plugname __attribute__((unused))) 
1078 {
1079     sql_settings_t *settings;
1080     
1081     if (!out_version || !plug) return SASL_BADPARAM;
1082     
1083     if (max_version < SASL_AUXPROP_PLUG_VERSION) return SASL_BADVERS;
1084     *out_version = SASL_AUXPROP_PLUG_VERSION;
1085     
1086     *plug = &sql_auxprop_plugin;
1087     
1088     settings = (sql_settings_t *) utils->malloc(sizeof(sql_settings_t));
1089     
1090     if (!settings) {
1091         MEMERROR(utils);
1092         return SASL_NOMEM;
1093     }
1094     
1095     memset(settings, 0, sizeof(sql_settings_t));
1096     sql_get_settings(utils, settings);
1097     
1098     if (!settings->sql_engine->name) return SASL_NOMECH;
1099
1100     if (!sql_exists(settings->sql_select)) {
1101         utils->log(NULL, SASL_LOG_ERR, "sql_select option missing");
1102         utils->free(settings);  
1103         return SASL_NOMECH;
1104     }
1105
1106     utils->log(NULL, SASL_LOG_DEBUG,
1107                "sql auxprop plugin using %s engine\n",
1108                settings->sql_engine->name);
1109     
1110     sql_auxprop_plugin.glob_context = settings;
1111     
1112     return SASL_OK;
1113 }