1 /* Copyright 2003-2008 Wang, Chun-Pin All rights reserved. */
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <pwd.h>
6 #include <string.h>
7 #include <unistd.h>
8 #include <syslog.h>
9 #include <grp.h>
10 
11 #include "smbftpd.h"
12 
13 #ifdef	USE_PAM
14 
15 extern smbftpd_session_t smbftpd_session;
16 
17 #include <security/pam_appl.h>
18 
19 pam_handle_t *pamh = NULL;
20 
21 struct usercache {
22 	char *user;
23 	char *home;
24 	uid_t uid;
25 	gid_t gid;
26 };
27 static struct usercache pwcache;
28 
user_cache_free(struct usercache * p)29 static void user_cache_free(struct usercache *p)
30 {
31 	if (p->user) free(p->user);
32 	if (p->home) free(p->home);
33 	p->user = NULL;
34 	p->home = NULL;
35 	p->uid = -1;
36 	p->gid = -1;
37 }
38 
39 /*
40  * the following code is stolen from imap-uw PAM authentication module and
41  * login.c
42  */
43 	#define COPY_STRING(s) (s ? strdup(s) : NULL)
44 
45 struct cred_t {
46 	const char *uname;		/* user name */
47 	const char *pass;		/* password */
48 };
49 typedef struct cred_t cred_t;
50 
auth_conv(int num_msg,const struct pam_message ** msg,struct pam_response ** resp,void * appdata)51 static int auth_conv(int num_msg, const struct pam_message **msg,
52 					 struct pam_response **resp, void *appdata)
53 {
54 	int i;
55 	cred_t *cred = (cred_t *) appdata;
56 	struct pam_response *response;
57 
58 	response = calloc(num_msg, sizeof *response);
59 	if (response == NULL)
60 		return PAM_BUF_ERR;
61 
62 	for (i = 0; i < num_msg; i++) {
63 		switch (msg[i]->msg_style) {
64 		case PAM_PROMPT_ECHO_ON:	/* assume want user name */
65 			response[i].resp_retcode = PAM_SUCCESS;
66 			response[i].resp = COPY_STRING(cred->uname);
67 			/* PAM frees resp. */
68 			break;
69 		case PAM_PROMPT_ECHO_OFF:	/* assume want password */
70 			response[i].resp_retcode = PAM_SUCCESS;
71 			response[i].resp = COPY_STRING(cred->pass);
72 			/* PAM frees resp. */
73 			break;
74 		case PAM_TEXT_INFO:
75 		case PAM_ERROR_MSG:
76 			response[i].resp_retcode = PAM_SUCCESS;
77 			response[i].resp = NULL;
78 			break;
79 		default:			/* unknown message style */
80 			free(response);
81 			return PAM_CONV_ERR;
82 		}
83 	}
84 
85 	*resp = response;
86 	return PAM_SUCCESS;
87 }
88 
89 /**
90  * There is no config parser for PAM authentication. This function just
91  * initial the user cache.
92  *
93  * @param path
94  *
95  * @return Always return 0.
96  */
auth_pam_config_parse(const char * path)97 int auth_pam_config_parse(const char *path)
98 {
99 	/* Initial user cache when daemon starts */
100 	user_cache_free(&pwcache);
101 
102 	return 0;
103 }
104 
105 /**
106  * Attempt to authenticate the user using PAM.  Returns 0 if the user is
107  * authenticated, or -1 if not authenticated.
108  *
109  * If some sort of PAM system error occurs (e.g., the "/etc/pam.d/ftpd"
110  * is missing) then this function returns -1, too.
111  *
112  * @param user
113  * @param password
114  *
115  * @return 0: User is authenticated
116  *         -1: Not authenticated or PAM system error
117  */
auth_pam_check(const char * user,const char * password)118 int auth_pam_check(const char *user, const char *password)
119 {
120 	cred_t auth_cred = { user, password};
121 	struct pam_conv conv = { &auth_conv, &auth_cred};
122 	struct passwd *pw;
123 	int rval = -1;
124 	int e;
125 
126 	/* Initial user cache before login. User can login with different name
127 	 * in the same session. */
128 	user_cache_free(&pwcache);
129 
130 	e = pam_start("ftpd", user, &conv, &pamh);
131 	if (e != PAM_SUCCESS) {
132 		syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, e));
133 		return -1;
134 	}
135 
136 	e = pam_set_item(pamh, PAM_RHOST, smbftpd_session.remotehost);
137 	if (e != PAM_SUCCESS) {
138 		syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s",
139 			   pam_strerror(pamh, e));
140 		return -1;
141 	}
142 
143 	e = pam_authenticate(pamh, 0);
144 	switch (e) {
145 	case PAM_SUCCESS:
146 		// User/Password is valid
147 		break;
148 
149 	case PAM_AUTH_ERR:
150 	case PAM_USER_UNKNOWN:
151 	case PAM_MAXTRIES:
152 		// Authentication failed
153 		goto Error;
154 	default:
155 		syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e));
156 		goto Error;
157 	}
158 
159 	pw = getpwnam(user);
160 	if (!pw) {
161 		goto Error;
162 	}
163 
164 	e = pam_acct_mgmt(pamh, 0);
165 	if (e != PAM_SUCCESS) {
166 		syslog(LOG_ERR, "pam_acct_mgmt: %s", pam_strerror(pamh, e));
167 		goto Error;
168 	}
169 
170 	e = pam_setcred(pamh, PAM_ESTABLISH_CRED);
171 	if (e != PAM_SUCCESS) {
172 		syslog(LOG_ERR, "%s (%d)pam_setcred: %s", __FILE__, __LINE__, pam_strerror(pamh, e));
173 		goto Error;
174 	}
175 
176 	pam_open_session(pamh, 0);
177 	pam_close_session(pamh,0);
178 
179 	pwcache.user = strdup(user);
180 	pwcache.home = strdup(pw->pw_dir);
181 	pwcache.uid = pw->pw_uid;
182 	pwcache.gid = pw->pw_gid;
183 
184 	rval = 0;
185 Error:
186 	if (pamh) {
187 		if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
188 			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
189 		}
190 		pamh = NULL;
191 	}
192 	return rval;
193 }
194 
195 /**
196  * Close/free config and user cache.
197  */
auth_pam_config_free()198 void auth_pam_config_free()
199 {
200 	user_cache_free(&pwcache);
201 
202 	return;
203 }
204 
205 /**
206  * Check whether user belongs to the group.
207  *
208  * We will:
209  * 1. getpwnam() to check whether pw_gid is the same with the group's
210  *    gr_gid from getgrnam.
211  * 2. Check whether user is in group's gr_mem.
212  *
213  * @param user   The user name to check
214  * @param group  The group name to check
215  *
216  * @return 1: Yes, user belongs to the group
217  *         0: No, user does not belong to the group
218  */
auth_pam_is_user_in_group(const char * user,const char * group)219 int auth_pam_is_user_in_group(const char *user, const char *group)
220 {
221 	struct group *grp = NULL;
222 	gid_t gid;
223 	struct passwd *pw = NULL;
224 	char **ppmember;
225 	int err = 0;
226 
227 	if (!user || !group) {
228 		return err;
229 	}
230 
231 	if (pwcache.user && strcmp(pwcache.user, user) == 0 && pwcache.gid != -1) {
232 		/* user is cached. Use the gid in cache. */
233 		gid = pwcache.gid;
234 	} else {
235 		pw = getpwnam(user);
236 		if (NULL == pw) {
237 			return err;
238 		}
239 		gid = pw->pw_gid;
240 	}
241 
242 	grp = getgrnam(group);
243 	if (NULL == grp) {
244 		return err;
245 	}
246 
247 	if (gid == grp->gr_gid) {
248 		return 1;
249 	}
250 
251 	ppmember = grp->gr_mem;
252 	while (*ppmember) {
253 		if (strcmp(*ppmember, user) == 0) {
254 			err = 1;
255 			break;
256 		}
257 		ppmember++;
258 	}
259 
260 	return err;
261 }
262 
263 /**
264  * Get the user's home. If the user name is in cache. We will return
265  * home in cache. Otherwise, use getpwnam() to get pw_dir and return.
266  *
267  * Caller should free() the returned string.
268  *
269  * @param user   Which user's home to get?
270  *
271  * @return String pointer or NULL on faill
272  */
auth_pam_get_home(const char * user)273 char *auth_pam_get_home(const char *user)
274 {
275 	struct passwd *pw = NULL;
276 
277 	if (!user) {
278 		return NULL;
279 	}
280 
281 	if (pwcache.user && strcmp(pwcache.user, user) == 0 && pwcache.home) {
282 		return strdup(pwcache.home);
283 	}
284 
285 	pw = getpwnam(user);
286 	if (NULL == pw) {
287 		return NULL;
288 	}
289 
290 	if (pw->pw_dir) {
291 		return strdup(pw->pw_dir);
292 	}
293 
294 	return NULL;
295 }
296 #endif
297