Index: sendmail/conf.c --- sendmail/conf.c.orig 2014-11-07 19:01:17.000000000 +0100 +++ sendmail/conf.c 2015-01-03 12:26:40.869964710 +0100 @@ -524,6 +524,12 @@ ndbm_map_lookup, ndbm_map_store); #endif /* NDBM */ +#if MYSQLMAP + MAPDEF("mysql", NULL, MCF_ALIASOK | MCF_NOTPERSIST, + mysql_map_parseargs, mysql_map_open, mysql_map_close, + mysql_map_lookup, null_map_store); +#endif + #if NIS MAPDEF("nis", NULL, MCF_ALIASOK, map_parseargs, nis_map_open, null_map_close, @@ -5795,6 +5801,9 @@ #if NDBM "NDBM", #endif +#if MYSQLMAP + "MYSQL", +#endif /* MYSQLMAP */ #if NETINET "NETINET", #endif Index: sendmail/map.c --- sendmail/map.c.orig 2014-10-21 14:55:53.000000000 +0200 +++ sendmail/map.c 2015-01-03 12:23:54.010131127 +0100 @@ -1904,12 +1904,443 @@ } #endif /* NDBM */ + + /* -** NEWDB (Hash and BTree) Modules +* MySQL map class for Sendmail 8.12.x +* +* (c) 2001 Igmar Palsenberg +* JDI Media Solutions +* +* MySQL can be obtained from http://www.mysql.com +* +* This version is subject to the Sendmail license. Sendmail Inc. is NOT +* responsible for this code, they don't have anything to do with it, and +* don't support it. +* +* USE AT YOUR OWN RISK. NO WARRANTY OF ANY KIND IS PROVIDED. PLEASE +* READ THE INSTRUCTIONS FOR USE OF THIS PATCH BEFORE CONTACTING THE +* AUTHOR OR SENDMAIL, INC. NO SUPPORT OF ANY KIND WILL BE PROVIDED +* BY SENDMAIL, INC. FOR THIS PATCH. +* +* Please use the sendmail_mysql@jdimedia.nl adress for questions, comments, +* remarks, etc, not my personal address. +* +* See http://projects.jdimedia.nl for a HOWTO on installing / using it. +* +*/ +#ifdef MYSQLMAP +#include + +struct mysql_conn { + char * host; + char * user; + char * passwd; + char * db; + int port; +}; + +static char * parse_opt_arg(char * in, char * option, char ** value) +{ + int len = strlen(in); + char * tmp; + + /* Skip whitespaces */ + while ((*in == ' ') || (*in == '\t') && (*in != '\0')) + in++; + if (*in == '\0') + return NULL; + + while ((*in != '-') && (*in != '"')) + in++; + + /* " is an error in this case */ + if (*in == '"') + return NULL; + + in++; + *option = *in; + in++; + + while ((*in == ' ') || (*in == '\t')) + in++; + + /* Not a " is an error */ + if (*in != '"') + return NULL; + in++; + + tmp = (char *) sm_malloc(sizeof(char) * len); + *value = tmp; + + while (*in != '"') + *tmp++ = *in++; + + /* Null terminate */ + *tmp = '\0'; + in++; + + return in; +} + +static char * parse_delim_arg(char * in, char delim, char ** key, char ** value) +{ + int len = strlen(in); + char * tmp; + + while ((*in == ' ') || (*in == '\t') && (*in != '\0')) + in++; + if (*in == '\0') + return NULL; + + /* Key */ + tmp = (char *) sm_malloc(sizeof(char) * len); + + *key = tmp; + + while ((*in != delim) && (*in != '\0') && (*in != ' ') && (*in != '\t')) + *tmp++ = *in++; + + if ((*in != delim) ) { + free(*key); + return NULL; + } + *tmp = '\0'; + in++; + + /* Value */ + tmp = (char *) sm_malloc(sizeof(char) * len); + *value = tmp; + + while ((*in != '\0') && (*in != ' ') && (*in != '\t')) + *tmp++ = *in++; + *tmp = '\0'; + + return in; +} + + +/* +* Parse MYSQL map definitions. I call it an ugly hack :) +* +* return false if failed, true if succeeded +* +* Copied from the sendmail generic map_parseargs() code */ +bool +mysql_map_parseargs(map, args) + MAP * map; + char * args; +{ + char *p = args; + char option; + char * value; -#if NEWDB + map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL; + map->map_spacesub = SpaceSub; /* default value */ + while (p) + { + p = parse_opt_arg(p, &option, &value); + if (!p) + break; + switch (option) + { + case 'N': /* Append NULL byte to all keys */ + map->map_mflags |= MF_INCLNULL; + map->map_mflags &= ~MF_TRY0NULL; + break; + + case 'O': /* Adaptive versus never add NULL */ + map->map_mflags &= ~MF_TRY1NULL; + break; + + case 'o': /* Database file is optional */ + map->map_mflags |= MF_OPTIONAL; + break; + + case 'f': /* Don't fold keys to lowercase */ + map->map_mflags |= MF_NOFOLDCASE; + break; + + case 'm': /* Supress replacement on match */ + map->map_mflags |= MF_MATCHONLY; + break; + + case 'A': /* Append values for duplicate keys */ + map->map_mflags |= MF_APPEND; + break; + + case 'q': /* Don't strip quotes */ + map->map_mflags |= MF_KEEPQUOTES; + break; + + case 'a': /* Append tag on successful match */ + map->map_app = value; + value = NULL; + break; + + case 'T': /* No idea */ + map->map_tapp = value; + value = NULL; + break; + + case 'k': /* No idea */ + map->map_keycolnm = value; + value = NULL; + break; + + case 'v': /* Specify the value's column */ + map->map_valcolnm = value; + value = NULL; + break; + + case 'z': /* Column delimiter */ + if (value[0] != '\\') + map->map_coldelim = value[0]; + else + { + switch (value[1]) + { + case 'n': + map->map_coldelim = '\n'; + break; + + case 't': + map->map_coldelim = '\t'; + break; + + default: + map->map_coldelim = '\\'; + } + } + break; + + case 't': /* No idea */ + map->map_mflags |= MF_NODEFER; + break; + + + case 'S': /* No idea */ + map->map_spacesub = value[0]; + break; + + case 'D': /* No idea, I need updated docs :( */ + map->map_mflags |= MF_DEFER; + break; + + case 'c': + map->map_keycolnm = value; + value = NULL; + break; + + case 's': + map->map_valcolnm = value; + value = NULL; + break; + default: + syserr("mysql_map_parse_args : illegal option %c map `%s'", *p, map->map_mname); + break; + } + if (value) + sm_free(value); + } + if (map->map_keycolnm == NULL) { + syserr("mysql_map_parse_args : no connect string for MySQL map `%s'", map->map_mname); + return false; + } + + if (map->map_valcolnm == NULL) { + syserr("mysql_map_parse_args : no select statement for MySQL map `%s'", map->map_mname); + return false; + } + + return true; +} + +static bool +mysql_map_parse_connect(char * cs, struct mysql_conn * conn, MAP * map) +{ + char * p; + char * key, * value; + char * endptr; + + p = cs; + + memset(conn, '\0', sizeof(struct mysql_conn)); + + while (p) { + p = parse_delim_arg(p, '=', &key, &value); + if (!p) + continue; + if (strcmp(key, "host") == 0) { + conn->host = value; + } else if (strcmp(key, "user") == 0) { + conn->user = value; + } else if (strcmp(key, "passwd") == 0) { + conn->passwd = value; + } else if (strcmp(key, "db") == 0) { + conn->db = value; + } else if (strcmp(key, "port") == 0) { + conn->port = (int) strtoul(value, &endptr, 10); + if (*endptr != '\0') + conn->port = 0; + sm_free(value); + } else { + syserr("mysql_map_parse_connect : illegal MySQL option %s map `%s'", key, map->map_mname); + } + sm_free(key); + } + + if ((conn->host == NULL) || (conn->user == NULL) || (conn->passwd == NULL)) + return false; + + return true; +} + +static void mysql_map_free_conn(struct mysql_conn * conn) +{ + if (conn->host) + sm_free(conn->host); + if (conn->user) + sm_free(conn->user); + if (conn->passwd) + sm_free(conn->passwd); + if (conn->db) + sm_free(conn->db); +} + +/* +* Open connection to the MySQL server +* +* returns true if all where OK, false if something went wrong +* +*/ +bool +mysql_map_open(map, mode) + MAP * map; + int mode; +{ + MYSQL * mysql; + struct mysql_conn conn; + + /* Make sure newaliases doesn't rebuild it */ + mode &= O_ACCMODE; + + if (mode != O_RDONLY) { + errno = EPERM; + return false; + } + + mysql = mysql_init(NULL); + + if (mysql_map_parse_connect(map->map_keycolnm, &conn, map) == false) { + syserr("mysql_map_open : cannot parse map arguments %s for map `%s'", map->map_keycolnm, map->map_mname); + mysql_map_free_conn(&conn); + return false; + } + + if (!mysql_real_connect(mysql, conn.host, conn.user, conn.passwd, conn.db, conn.port, NULL, 0)) { + mysql_map_free_conn(&conn); + syserr("mysql_map_open : cannot open a MySQL connection for map `%s' : %s", map->map_mname, mysql_error(mysql)); + return false; + } + mysql_map_free_conn(&conn); + map->map_db1 = (ARBPTR_T) mysql; + map->map_pid = getpid(); + + return true; +} + +/* +* Close connection to the MYSQL database +* +* +*/ +void +mysql_map_close(map) + MAP * map; +{ + if (map->map_pid == getpid()) + mysql_close(map->map_db1); +} + +/* +* +* Do a map look up. +* +* Returns value that comes with the key +* +*/ +char * +mysql_map_lookup(map, name, av, statp) + MAP * map; + char * name; + char ** av; + int * statp; +{ + MYSQL * mysql = (MYSQL *) map->map_db1; + MYSQL_RES * result; + MYSQL_ROW row; + char * query; + char * tmp; + int len; + + len = strlen(name) + strlen(map->map_valcolnm) + 5; + if (len > MAXNAME) + return NULL; + + tmp = (char *) sm_malloc(strlen(name) + 1); + strncpy(tmp, name, strlen(name) + 1); + + if (!bitset(MF_NOFOLDCASE, map->map_mflags)) + makelower(tmp); + + query = (char *) sm_malloc(sizeof(char) * len); + /* Create query */ + snprintf(query, len, map->map_valcolnm, tmp); + + sm_free(tmp); + + /* Query and retreive rows */ + if (mysql_real_query(mysql, query, strlen(query))) { + syserr("mysql_map_lookup : query %s : %s", query, mysql_error(mysql)); + sm_free(query); + return NULL; + } + + result = mysql_store_result(mysql); + if (result == NULL) { + syserr("mysql_map_lookup : store result : %s", mysql_error(mysql)); + sm_free(query); + return NULL; + } + + /* 0 rows == no result */ + if (mysql_num_rows(result) == 0) { + sm_free(query); + return NULL; + } + + row = mysql_fetch_row(result); + if (row[0] == NULL || strlen(row[0]) == 0) { + sm_free(query); + mysql_free_result(result); + return NULL; + } + + if (bitset(MF_MATCHONLY, map->map_mflags)) + return map_rewrite(map, name, strlen(name), NULL); + else + return map_rewrite(map, row[0], strlen(row[0]), av); +} +#endif /* MYSQLMAP */ + + +/* +** NEWDB (Hash and BTree) Modules +*/ +#if NEWDB /* ** BT_MAP_OPEN, HASH_MAP_OPEN -- database open primitives. **