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