1 /*
2 $Id: courierpasswd.c,v 1.3 2014/11/30 17:39:45 astjean Exp $
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License as
6 published by the Free Software Foundation; either version 2, or (at
7 your option) any later version.
8 
9 This program is distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 General Public License for more details.
13 
14 Copyright (c) Andrew St. Jean <andrew@arda.homeunix.net> 2002-2014
15 
16 */
17 
18 #include "courierpasswd.h"
19 
20 static const char *short_options = "s:cC:hvV";
21 
22 static struct option long_options[] = {
23 	{ "service", required_argument, NULL, 's' },
24 	{ "changepwd", no_argument, NULL, 'c' },
25 	{ "cramtype", required_argument, NULL, 'C' },
26 	{ "help", no_argument, NULL, 'h' },
27 	{ "verbose", no_argument, NULL, 'v' },
28 	{ "version", no_argument, NULL, 'V' },
29 	{ "stdin", no_argument, NULL, OPT_STDIN },
30 	{ "stderr", no_argument, NULL, OPT_STDERR },
31 	{ NULL, 0, NULL, 0 }
32 };
33 
34 const char *usage =
35 "Usage: " PACKAGE "  [ OPTION [ OPTION ] ] [ prog... ]\n"
36 "\n"
37 "Perform one of two operations using the Courier\n"
38 "authentication library\n"
39 "1. Authenticate the specified user and optionally run\n"
40 "   the program indicated by 'prog'.\n"
41 "2. Change the password of the specified user. This\n"
42 "   operation is not available when using CRAM.\n"
43 "\n"
44 "Options:\n"
45 "  -s, --service=SERVICE\tspecify the service name to use\n"
46 "\t\t\tdefault value is 'login'\n"
47 "  -c, --changepwd\ttells courierpasswd to change the\n"
48 "\t\t\tspecified user's password\n"
49 "  -C, --cramtype=TYPE\ttells courierpasswd the type of\n"
50 "\t\t\tCRAM that will be used for authentication\n"
51 "\t\t\tvalue can be 'md5', 'sha1' or 'sha256'\n"
52 "\t\t\tthis option will be ignored if the -c option\n"
53 "\t\t\tis also present\n"
54 "  --stdin\t\tread authentication tokens from stdin\n"
55 "\t\t\tthree variations are possible:\n"
56 "\t\t\tif the -c option is not used\n"
57 "\t\t\tusername\\0oldpassword\\0\n"
58 "\t\t\tusername\\0challenge\\0response\\0\n"
59 "\t\t\tor if the -c option is used\n"
60 "\t\t\tusername\\0oldpassword\\0newpassword\\0\n"
61 "  --stderr\t\tsend logging information to stderr\n"
62 "\t\t\tinstead of syslog\n"
63 "  -h, --help\t\tdisplay this help and exit\n"
64 "  -v, --verbose\t\tturn on verbose output\n"
65 "  -V, --version\t\tdisplay version information and exit\n";
66 
67 int opt_stderr = 0;
68 int opt_verbose = 0;
69 
70 #define MAX_LEN_ADDR		 46
71 #define MAX_LEN_SERVICE		129
72 #define BUFSIZE			513
73 
74 char upp[BUFSIZE];
75 static int input_fd = 3;
76 char *username = NULL;
77 char *oldpwd = NULL;
78 char *newpwd = NULL;
79 char response[BUFSIZE];
80 char *b64_challenge = NULL;
81 char *b64_response = NULL;
82 
83 static int
callback_func(struct authinfo * a,void * dummy)84 callback_func(struct authinfo *a, void *dummy) {
85 	        return(0);
86 }
87 
88 static int
callback_pre(struct authinfo * a,void * p)89 callback_pre(struct authinfo *a, void *p) {
90 
91 	uid_t min_uid = *((uid_t *)p);
92 	struct passwd *pw;
93 
94 	verbose("Minimum user ID allowed for password change: %d", min_uid);
95 
96 	if (a->sysuserid != NULL){
97 		verbose("User ID from authinfo struct: %d", *(a->sysuserid));
98 		if(*(a->sysuserid) < min_uid) return(-1);
99 	} else if (a->sysusername) {
100 		pw = getpwnam(a->sysusername);
101 		if (!pw){
102 			logging(LOG_ERR, "getpwnam failed: %s", strerror(errno));
103 			/* perror("getpwnam"); */
104 			return(-2);
105 		}
106 
107 		verbose("User ID from passwd struct: %d", pw->pw_uid);
108 
109 		if (pw->pw_uid < min_uid) return(-1);
110 	}
111 
112 	return(0);
113 }
114 
115 int
findcramtype(const char * cram,char * authtype_cram)116 findcramtype(const char *cram, char *authtype_cram){
117 	int n=0;
118 
119 	if (!strncmp(cram, "md5", 3)){
120 		n = strlcpy(authtype_cram, AUTHTYPE_CRAMMD5, sizeof(AUTHTYPE_CRAMMD5)+1);
121 	} else if (!strncmp(cram, "sha1", 4)){
122 		n = strlcpy(authtype_cram, AUTHTYPE_CRAMSHA1, sizeof(AUTHTYPE_CRAMSHA1)+1);
123         } else if (!strncmp(cram, "sha256", 6)){
124                 n = strlcpy(authtype_cram, AUTHTYPE_CRAMSHA256, sizeof(AUTHTYPE_CRAMSHA256)+1);
125 	}
126 
127 	return(n);
128 }
129 
130 size_t
validate_str(const char * data)131 validate_str(const char *data){
132 	return(strspn(data, SAFE_CHARS));
133 }
134 
135 void
end(int exit_code)136 end(int exit_code) {
137 
138         memset(upp, 0x00, sizeof(upp));
139         end_logging();
140         exit(exit_code);
141 }
142 
main(int argc,char ** argv)143 int main(int argc, char **argv) {
144 FILE	*tokens;
145 int	upplen, i, rc;
146 size_t	n;
147 char	*sp;
148 char	service[MAX_LEN_SERVICE];
149 char	cram[MAX_LEN_SERVICE];
150 char	authtype_cram[BUFSIZE];
151 int	changepwd = 0;
152 uid_t	min_uid = MIN_UID;
153 char	remoteip[MAX_LEN_ADDR];
154 
155 	*service = *cram = *authtype_cram = 0;
156 
157 	/* set the default value for service */
158 	strlcpy(service, "login", 6);
159 	/* set default value for cram */
160 	strlcpy(cram, "md5", 4);
161 
162 	init_logging();
163 
164 	/* process the command line options */
165 	opterr = 0;
166 	while (1){
167 		int option_index = 0;
168 		int c = getopt_long(argc, argv, short_options, long_options, &option_index);
169 
170 		if (c == -1) break;
171 
172 		switch (c){
173 		case 's':
174 			n = validate_str(optarg);
175 			if (optarg[n]){
176 				logging(LOG_ERR, "illegal character in service name: %c", optarg[n]);
177 				end(2);
178 			}
179 			n = strlcpy(service, optarg, sizeof(service));
180 			if (n >= sizeof(service)){
181 				logging(LOG_ERR, "service name too long (max %d characters)", MAX_LEN_SERVICE - 1);
182 				end(2);
183 			}
184 			break;
185 		case 'c':
186 			changepwd = 1;
187 			break;
188 		case 'C':
189 			n = validate_str(optarg);
190 			if (optarg[n]){
191 				logging(LOG_ERR, "illegal character in CRAM type: %c", optarg[n]);
192 				end(2);
193 			}
194 			n = strlcpy(cram, optarg, sizeof(cram));
195 			if (n >= sizeof(cram)){
196 				logging(LOG_ERR, "CRAM type too long (max %d characters)", MAX_LEN_SERVICE - 1);
197 				end(2);
198 			}
199 			/* convert the string to lower case. */
200 		        for (sp = cram; isalpha(*sp); sp++) *sp = tolower(*sp);
201 			n = findcramtype(cram, authtype_cram);
202 			if (!n){
203 				logging(LOG_ERR, "unknown CRAM type: %s", cram);
204 				end(2);
205 			}
206 			break;
207 		case OPT_STDIN:
208 			input_fd = 0;
209 			break;
210 		case OPT_STDERR:
211 			opt_stderr = 1;
212 			break;
213 		case 'h':
214 			puts(usage);
215 			exit(0);
216 		case 'v':
217 			opt_verbose = 1;
218 			break;
219 		case 'V':
220 			puts(PACKAGE " "  VERSION);
221 			exit(0);
222 		case '?':
223 			puts("Invalid command-line, see --help");
224 			exit(2);
225 		}
226 	}
227 
228         /* grab the remote IP if it exists */
229         const char *temp = getenv("TCPREMOTEIP");
230         if (temp != NULL){
231                 strlcpy(remoteip, temp, sizeof(remoteip));
232                 n = validate_str(remoteip);
233                 if (remoteip[n]){
234                         logging(LOG_WARNING, "illegal character in remote ip: %c", remoteip[n]);
235                         strlcpy(remoteip, "unknown", 8);
236                 }
237         } else {
238                 strlcpy(remoteip, "unknown", 8);
239         }
240 
241 	/* convert the CRAM string to lower case. */
242 	for (sp = cram; isalpha(*sp); sp++) *sp = tolower(*sp);
243 	/* do we recognise the CRAM type specified? */
244 	n = findcramtype(cram, authtype_cram);
245 	if (!n){
246 		logging(LOG_ERR, "unknown CRAM type: %s", cram);
247 		end(2);
248 	}
249 
250 	/* read authentication tokens from file descriptor */
251 	tokens = fdopen(input_fd, "r");
252 	if (tokens == NULL) {
253 		logging(LOG_ERR, "Error opening fd %d: %s", input_fd, strerror(errno));
254 		end(2);
255 	}
256 
257 	upplen = fread(upp, 1, BUFSIZE, tokens);
258 	if (upplen == 0) {
259 		logging(LOG_ERR, "Failure reading authentication tokens: zero bytes read");
260 		end(2);
261 	}
262 
263 	i = 0;
264 	/* get username */
265 	username = upp + i;
266 	while (upp[i++]) {
267 		if (i >= upplen) {
268 			logging(LOG_ERR, "Failure while reading username");
269 			end(2);
270 		}
271 	}
272 
273 	verbose("Username is: %s", username);
274 
275 	/* get authentication token */
276 	oldpwd = upp + i;
277 	while (upp[i++]) {
278 		if (i >= upplen) {
279 			logging(LOG_ERR, "Failure reading authentication tokens");
280 			end(2);
281 		}
282 	}
283 
284 	/* if there's a third token, get it */
285 	if (i < (upplen - 1)) {
286 		newpwd = upp + i;
287 		while (upp[i++]) {
288 			if (i >= upplen) {
289 				logging(LOG_ERR,
290 					"Failure reading authentication tokens");
291 				end(2);
292 			}
293 		}
294 	}
295 
296 	if (newpwd == NULL) {
297 		verbose("Password is: %s", oldpwd);
298 	} else {
299 		verbose(changepwd ? "Old password is: %s" : "Challenge is: %s", oldpwd);
300 		verbose(changepwd ? "New password is: %s" : "Response is: %s", newpwd);
301 	}
302 
303 	if (!changepwd) {
304 		/* check the password */
305 		char *authdata;
306 
307 		if (newpwd != NULL) {
308 			base64_encode(oldpwd, strlen(oldpwd), &b64_challenge);
309 			/* prepend username */
310 			sprintf(response, "%s %s", username, newpwd);
311 			base64_encode(response, strlen(response), &b64_response);
312 			authdata = XMALLOC(char, strlen(b64_challenge)+strlen(b64_response)+3);
313 			sprintf(authdata, "%s\n%s\n", b64_challenge, b64_response);
314 			rc = auth_generic(service, authtype_cram, authdata, callback_func, 0);
315 		} else {
316 			authdata = XMALLOC(char, strlen(username)+strlen(oldpwd)+3);
317 			sprintf(authdata, "%s\n%s\n", username, oldpwd);
318 			rc = auth_generic(service, AUTHTYPE_LOGIN, authdata, callback_func, 0);
319 
320 		}
321 
322 		XFREE(authdata);
323 
324 		if (rc != 0) {
325 			if (errno != EPERM) {
326 				logging(LOG_ERR, "Temporary authentication failure for user %s from %s: %s", username, remoteip, strerror(errno));
327 				end(111);
328 			}
329 
330 			logging(LOG_ERR, "Authentication failure for user %s from %s", username, remoteip);
331                         end(1);
332 		} else {
333 
334 			logging(LOG_INFO, "Authenticated for user %s from %s", username, remoteip);
335 		}
336 
337 	} else {
338 
339 		/* is the uid restricted? */
340 		rc = auth_getuserinfo(service, username, callback_pre, &min_uid);
341 
342 		if (rc != 0){
343 			logging(LOG_ERR, "Attempt to change password for the restricted or non-existant user %s from %s", username, remoteip);
344 			end(1);
345 		}
346 
347 		/* change the password */
348 		rc = auth_passwd(service, username, oldpwd, newpwd);
349 
350 		if (rc == 0){
351 			logging(LOG_INFO, "Password changed for %s from %s", username, remoteip);
352 			end(0);
353 		} else {
354 			logging(LOG_ERR, "Password change failed for %s from %s: %s", username, remoteip, strerror(errno));
355 			end(1);
356 		}
357 	}
358 
359 	if (optind < argc) {
360 		verbose("Executing %s", argv[optind]);
361 
362 		execvp(argv[optind], argv + optind);
363 		logging(LOG_ERR, "Failed to execute %s: %s", argv[optind], strerror(errno));
364 		end(2);
365 	}
366 
367 	end(0);
368 	return(0);
369 }
370