1/*
2 * MySQL password backend for samba
3 * Copyright (C) Jelmer Vernooij 2002-2004
4 *
5 * This program is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 675
17 * Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20#include "includes.h"
21#include <mysql/mysql.h>
22
23#define CONFIG_HOST_DEFAULT				"localhost"
24#define CONFIG_USER_DEFAULT				"samba"
25#define CONFIG_PASS_DEFAULT				""
26#define CONFIG_PORT_DEFAULT				"3306"
27#define CONFIG_DB_DEFAULT				"samba"
28
29static int mysqlsam_debug_level = DBGC_ALL;
30
31#undef DBGC_CLASS
32#define DBGC_CLASS mysqlsam_debug_level
33
34typedef struct pdb_mysql_data {
35	MYSQL *handle;
36	MYSQL_RES *pwent;
37	const char *location;
38} pdb_mysql_data;
39
40#define SET_DATA(data,methods) { \
41	if(!methods){ \
42		DEBUG(0, ("invalid methods!\n")); \
43			return NT_STATUS_INVALID_PARAMETER; \
44	} \
45	data = (struct pdb_mysql_data *)methods->private_data; \
46		if(!data || !(data->handle)){ \
47			DEBUG(0, ("invalid handle!\n")); \
48				return NT_STATUS_INVALID_HANDLE; \
49		} \
50}
51
52#define config_value( data, name, default_value ) \
53  lp_parm_const_string( GLOBAL_SECTION_SNUM, (data)->location, name, default_value )
54
55static long xatol(const char *d)
56{
57	if(!d) return 0;
58	return atol(d);
59}
60
61static NTSTATUS row_to_sam_account(MYSQL_RES * r, SAM_ACCOUNT * u)
62{
63	MYSQL_ROW row;
64	pstring temp;
65	unsigned int num_fields;
66	DOM_SID sid;
67
68	num_fields = mysql_num_fields(r);
69	row = mysql_fetch_row(r);
70	if (!row)
71		return NT_STATUS_INVALID_PARAMETER;
72
73	pdb_set_logon_time(u, xatol(row[0]), PDB_SET);
74	pdb_set_logoff_time(u, xatol(row[1]), PDB_SET);
75	pdb_set_kickoff_time(u, xatol(row[2]), PDB_SET);
76	pdb_set_pass_last_set_time(u, xatol(row[3]), PDB_SET);
77	pdb_set_pass_can_change_time(u, xatol(row[4]), PDB_SET);
78	pdb_set_pass_must_change_time(u, xatol(row[5]), PDB_SET);
79	pdb_set_username(u, row[6], PDB_SET);
80	pdb_set_domain(u, row[7], PDB_SET);
81	pdb_set_nt_username(u, row[8], PDB_SET);
82	pdb_set_fullname(u, row[9], PDB_SET);
83	pdb_set_homedir(u, row[10], PDB_SET);
84	pdb_set_dir_drive(u, row[11], PDB_SET);
85	pdb_set_logon_script(u, row[12], PDB_SET);
86	pdb_set_profile_path(u, row[13], PDB_SET);
87	pdb_set_acct_desc(u, row[14], PDB_SET);
88	pdb_set_workstations(u, row[15], PDB_SET);
89	pdb_set_unknown_str(u, row[16], PDB_SET);
90	pdb_set_munged_dial(u, row[17], PDB_SET);
91
92	if(!row[18] || !string_to_sid(&sid, row[18])) {
93		DEBUG(0,("No user SID retrieved from database!\n"));
94	} else {
95		pdb_set_user_sid(u, &sid, PDB_SET);
96	}
97
98	if(row[19]) {
99		string_to_sid(&sid, row[19]);
100		pdb_set_group_sid(u, &sid, PDB_SET);
101	}
102
103	if (pdb_gethexpwd(row[20], temp))
104		pdb_set_lanman_passwd(u, temp, PDB_SET);
105	if (pdb_gethexpwd(row[21], temp))
106		pdb_set_nt_passwd(u, temp, PDB_SET);
107
108	/* Only use plaintext password storage when lanman and nt are
109	 * NOT used */
110	if (!row[20] || !row[21])
111		pdb_set_plaintext_passwd(u, row[22]);
112
113	pdb_set_acct_ctrl(u, xatol(row[23]), PDB_SET);
114	pdb_set_logon_divs(u, xatol(row[24]), PDB_SET);
115	pdb_set_hours_len(u, xatol(row[25]), PDB_SET);
116	pdb_set_bad_password_count(u, xatol(row[26]), PDB_SET);
117	pdb_set_logon_count(u, xatol(row[27]), PDB_SET);
118	pdb_set_unknown_6(u, xatol(row[28]), PDB_SET);
119
120	return NT_STATUS_OK;
121}
122
123static NTSTATUS mysqlsam_setsampwent(struct pdb_methods *methods, BOOL update, uint16 acb_mask)
124{
125	struct pdb_mysql_data *data =
126		(struct pdb_mysql_data *) methods->private_data;
127	char *query;
128	int ret;
129
130	if (!data || !(data->handle)) {
131		DEBUG(0, ("invalid handle!\n"));
132		return NT_STATUS_INVALID_HANDLE;
133	}
134
135	query = sql_account_query_select(data->location, update, SQL_SEARCH_NONE, NULL);
136
137	ret = mysql_query(data->handle, query);
138	SAFE_FREE(query);
139
140	if (ret) {
141		DEBUG(0,
142			   ("Error executing MySQL query %s\n", mysql_error(data->handle)));
143		return NT_STATUS_UNSUCCESSFUL;
144	}
145
146	data->pwent = mysql_store_result(data->handle);
147
148	if (data->pwent == NULL) {
149		DEBUG(0,
150			("Error storing results: %s\n", mysql_error(data->handle)));
151		return NT_STATUS_UNSUCCESSFUL;
152	}
153
154	DEBUG(5,
155		("mysqlsam_setsampwent succeeded(%llu results)!\n",
156				mysql_num_rows(data->pwent)));
157
158	return NT_STATUS_OK;
159}
160
161/***************************************************************
162  End enumeration of the passwd list.
163 ****************************************************************/
164
165static void mysqlsam_endsampwent(struct pdb_methods *methods)
166{
167	struct pdb_mysql_data *data =
168		(struct pdb_mysql_data *) methods->private_data;
169
170	if (data == NULL) {
171		DEBUG(0, ("invalid handle!\n"));
172		return;
173	}
174
175	if (data->pwent != NULL)
176		mysql_free_result(data->pwent);
177
178	data->pwent = NULL;
179
180	DEBUG(5, ("mysql_endsampwent called\n"));
181}
182
183/*****************************************************************
184  Get one SAM_ACCOUNT from the list (next in line)
185 *****************************************************************/
186
187static NTSTATUS mysqlsam_getsampwent(struct pdb_methods *methods, SAM_ACCOUNT * user)
188{
189	struct pdb_mysql_data *data;
190
191	SET_DATA(data, methods);
192
193	if (data->pwent == NULL) {
194		DEBUG(0, ("invalid pwent\n"));
195		return NT_STATUS_INVALID_PARAMETER;
196	}
197
198	return row_to_sam_account(data->pwent, user);
199}
200
201static NTSTATUS mysqlsam_select_by_field(struct pdb_methods * methods, SAM_ACCOUNT * user,
202						 enum sql_search_field field, const char *sname)
203{
204	char *esc_sname;
205	char *query;
206	NTSTATUS ret;
207	MYSQL_RES *res;
208	int mysql_ret;
209	struct pdb_mysql_data *data;
210	char *tmp_sname;
211
212	SET_DATA(data, methods);
213
214	esc_sname = malloc(strlen(sname) * 2 + 1);
215	if (!esc_sname) {
216		return NT_STATUS_NO_MEMORY;
217	}
218
219	tmp_sname = smb_xstrdup(sname);
220
221	/* Escape sname */
222	mysql_real_escape_string(data->handle, esc_sname, tmp_sname,
223							 strlen(tmp_sname));
224
225	SAFE_FREE(tmp_sname);
226
227	if (user == NULL) {
228		DEBUG(0, ("pdb_getsampwnam: SAM_ACCOUNT is NULL.\n"));
229		SAFE_FREE(esc_sname);
230		return NT_STATUS_INVALID_PARAMETER;
231	}
232
233	query = sql_account_query_select(data->location, True, field, esc_sname);
234
235	SAFE_FREE(esc_sname);
236
237	DEBUG(5, ("Executing query %s\n", query));
238
239	mysql_ret = mysql_query(data->handle, query);
240
241	SAFE_FREE(query);
242
243	if (mysql_ret) {
244		DEBUG(0,
245			("Error while executing MySQL query %s\n",
246				mysql_error(data->handle)));
247		return NT_STATUS_UNSUCCESSFUL;
248	}
249
250	res = mysql_store_result(data->handle);
251	if (res == NULL) {
252		DEBUG(0,
253			("Error storing results: %s\n", mysql_error(data->handle)));
254		return NT_STATUS_UNSUCCESSFUL;
255	}
256
257	ret = row_to_sam_account(res, user);
258	mysql_free_result(res);
259
260	return ret;
261}
262
263/******************************************************************
264  Lookup a name in the SAM database
265 ******************************************************************/
266
267static NTSTATUS mysqlsam_getsampwnam(struct pdb_methods *methods, SAM_ACCOUNT * user,
268					 const char *sname)
269{
270	struct pdb_mysql_data *data;
271
272	SET_DATA(data, methods);
273
274	if (!sname) {
275		DEBUG(0, ("invalid name specified"));
276		return NT_STATUS_INVALID_PARAMETER;
277	}
278
279	return mysqlsam_select_by_field(methods, user,
280			SQL_SEARCH_USER_NAME, sname);
281}
282
283
284/***************************************************************************
285  Search by sid
286 **************************************************************************/
287
288static NTSTATUS mysqlsam_getsampwsid(struct pdb_methods *methods, SAM_ACCOUNT * user,
289					 const DOM_SID * sid)
290{
291	struct pdb_mysql_data *data;
292	fstring sid_str;
293
294	SET_DATA(data, methods);
295
296	sid_to_string(sid_str, sid);
297
298	return mysqlsam_select_by_field(methods, user, SQL_SEARCH_USER_SID, sid_str);
299}
300
301/***************************************************************************
302  Delete a SAM_ACCOUNT
303 ****************************************************************************/
304
305static NTSTATUS mysqlsam_delete_sam_account(struct pdb_methods *methods,
306							SAM_ACCOUNT * sam_pass)
307{
308	const char *sname = pdb_get_username(sam_pass);
309	char *esc;
310	char *query;
311	int ret;
312	struct pdb_mysql_data *data;
313	char *tmp_sname;
314
315	SET_DATA(data, methods);
316
317	if (!methods) {
318		DEBUG(0, ("invalid methods!\n"));
319		return NT_STATUS_INVALID_PARAMETER;
320	}
321
322	data = (struct pdb_mysql_data *) methods->private_data;
323	if (!data || !(data->handle)) {
324		DEBUG(0, ("invalid handle!\n"));
325		return NT_STATUS_INVALID_HANDLE;
326	}
327
328	if (!sname) {
329		DEBUG(0, ("invalid name specified\n"));
330		return NT_STATUS_INVALID_PARAMETER;
331	}
332
333	/* Escape sname */
334	esc = malloc(strlen(sname) * 2 + 1);
335	if (!esc) {
336		DEBUG(0, ("Can't allocate memory to store escaped name\n"));
337		return NT_STATUS_NO_MEMORY;
338	}
339
340	tmp_sname = smb_xstrdup(sname);
341
342	mysql_real_escape_string(data->handle, esc, tmp_sname,
343							 strlen(tmp_sname));
344
345	SAFE_FREE(tmp_sname);
346
347	query = sql_account_query_delete(data->location, esc);
348
349	SAFE_FREE(esc);
350
351	ret = mysql_query(data->handle, query);
352
353	SAFE_FREE(query);
354
355	if (ret) {
356		DEBUG(0,
357			  ("Error while executing query: %s\n",
358			   mysql_error(data->handle)));
359		return NT_STATUS_UNSUCCESSFUL;
360	}
361
362	DEBUG(5, ("User '%s' deleted\n", sname));
363	return NT_STATUS_OK;
364}
365
366static NTSTATUS mysqlsam_replace_sam_account(struct pdb_methods *methods,
367							 const SAM_ACCOUNT * newpwd, char isupdate)
368{
369	struct pdb_mysql_data *data;
370	char *query;
371
372	if (!methods) {
373		DEBUG(0, ("invalid methods!\n"));
374		return NT_STATUS_INVALID_PARAMETER;
375	}
376
377	data = (struct pdb_mysql_data *) methods->private_data;
378
379	if (data == NULL || data->handle == NULL) {
380		DEBUG(0, ("invalid handle!\n"));
381		return NT_STATUS_INVALID_HANDLE;
382	}
383
384	query = sql_account_query_update(data->location, newpwd, isupdate);
385
386	/* Execute the query */
387	if (mysql_query(data->handle, query)) {
388		DEBUG(0,
389			  ("Error executing %s, %s\n", query,
390			   mysql_error(data->handle)));
391		return NT_STATUS_INVALID_PARAMETER;
392	}
393	SAFE_FREE(query);
394
395	return NT_STATUS_OK;
396}
397
398static NTSTATUS mysqlsam_add_sam_account(struct pdb_methods *methods, SAM_ACCOUNT * newpwd)
399{
400	return mysqlsam_replace_sam_account(methods, newpwd, 0);
401}
402
403static NTSTATUS mysqlsam_update_sam_account(struct pdb_methods *methods,
404							SAM_ACCOUNT * newpwd)
405{
406	return mysqlsam_replace_sam_account(methods, newpwd, 1);
407}
408
409static NTSTATUS mysqlsam_init(struct pdb_context * pdb_context, struct pdb_methods ** pdb_method,
410		 const char *location)
411{
412	NTSTATUS nt_status;
413	struct pdb_mysql_data *data;
414
415	mysqlsam_debug_level = debug_add_class("mysqlsam");
416	if (mysqlsam_debug_level == -1) {
417		mysqlsam_debug_level = DBGC_ALL;
418		DEBUG(0,
419			  ("mysqlsam: Couldn't register custom debugging class!\n"));
420	}
421
422
423	if (!pdb_context) {
424		DEBUG(0, ("invalid pdb_methods specified\n"));
425		return NT_STATUS_UNSUCCESSFUL;
426	}
427
428	if (!NT_STATUS_IS_OK
429		(nt_status = make_pdb_methods(pdb_context->mem_ctx, pdb_method))) {
430		return nt_status;
431	}
432
433	(*pdb_method)->name = "mysqlsam";
434
435	(*pdb_method)->setsampwent = mysqlsam_setsampwent;
436	(*pdb_method)->endsampwent = mysqlsam_endsampwent;
437	(*pdb_method)->getsampwent = mysqlsam_getsampwent;
438	(*pdb_method)->getsampwnam = mysqlsam_getsampwnam;
439	(*pdb_method)->getsampwsid = mysqlsam_getsampwsid;
440	(*pdb_method)->add_sam_account = mysqlsam_add_sam_account;
441	(*pdb_method)->update_sam_account = mysqlsam_update_sam_account;
442	(*pdb_method)->delete_sam_account = mysqlsam_delete_sam_account;
443
444	data = talloc(pdb_context->mem_ctx, sizeof(struct pdb_mysql_data));
445	(*pdb_method)->private_data = data;
446	data->handle = NULL;
447	data->pwent = NULL;
448
449	if (!location) {
450		DEBUG(0, ("No identifier specified. Check the Samba HOWTO Collection for details\n"));
451		return NT_STATUS_INVALID_PARAMETER;
452	}
453
454	data->location = smb_xstrdup(location);
455
456	DEBUG(1,
457		  ("Connecting to database server, host: %s, user: %s, database: %s, port: %ld\n",
458		   config_value(data, "mysql host", CONFIG_HOST_DEFAULT),
459		   config_value(data, "mysql user", CONFIG_USER_DEFAULT),
460		   config_value(data, "mysql database", CONFIG_DB_DEFAULT),
461		   xatol(config_value(data, "mysql port", CONFIG_PORT_DEFAULT))));
462
463	/* Do the mysql initialization */
464	data->handle = mysql_init(NULL);
465	if (!data->handle) {
466		DEBUG(0, ("Failed to connect to server\n"));
467		return NT_STATUS_UNSUCCESSFUL;
468	}
469
470	if(!sql_account_config_valid(data->location)) {
471		return NT_STATUS_INVALID_PARAMETER;
472	}
473
474	/* Process correct entry in $HOME/.my.conf */
475	if (!mysql_real_connect(data->handle,
476			config_value(data, "mysql host", CONFIG_HOST_DEFAULT),
477			config_value(data, "mysql user", CONFIG_USER_DEFAULT),
478			config_value(data, "mysql password", CONFIG_PASS_DEFAULT),
479			config_value(data, "mysql database", CONFIG_DB_DEFAULT),
480			xatol(config_value (data, "mysql port", CONFIG_PORT_DEFAULT)),
481			NULL, 0)) {
482		DEBUG(0,
483			  ("Failed to connect to mysql database: error: %s\n",
484			   mysql_error(data->handle)));
485		return NT_STATUS_UNSUCCESSFUL;
486	}
487
488	DEBUG(5, ("Connected to mysql db\n"));
489
490	return NT_STATUS_OK;
491}
492
493NTSTATUS pdb_mysql_init(void)
494{
495	return smb_register_passdb(PASSDB_INTERFACE_VERSION, "mysql", mysqlsam_init);
496}
497