1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17/****************************************************************************** 18 ****************************************************************************** 19 * NOTE! This program is not safe as a setuid executable! Do not make it 20 * setuid! 21 ****************************************************************************** 22 *****************************************************************************/ 23/* 24 * htpasswd.c: simple program for manipulating password file for 25 * the Apache HTTP server 26 * 27 * Originally by Rob McCool 28 * 29 * Exit values: 30 * 0: Success 31 * 1: Failure; file access/permission problem 32 * 2: Failure; command line syntax problem (usage message issued) 33 * 3: Failure; password verification failure 34 * 4: Failure; operation interrupted (such as with CTRL/C) 35 * 5: Failure; buffer would overflow (username, filename, or computed 36 * record too long) 37 * 6: Failure; username contains illegal or reserved characters 38 * 7: Failure; file is not a valid htpasswd file 39 */ 40 41#include "passwd_common.h" 42#include "apr_signal.h" 43#include "apr_getopt.h" 44 45#if APR_HAVE_STDIO_H 46#include <stdio.h> 47#endif 48 49#include "apr_md5.h" 50#include "apr_sha1.h" 51 52#if APR_HAVE_STDLIB_H 53#include <stdlib.h> 54#endif 55#if APR_HAVE_STRING_H 56#include <string.h> 57#endif 58#if APR_HAVE_UNISTD_H 59#include <unistd.h> 60#endif 61 62#ifdef WIN32 63#include <conio.h> 64#define unlink _unlink 65#endif 66 67#define APHTP_NEWFILE 1 68#define APHTP_NOFILE 2 69#define APHTP_DELUSER 4 70#define APHTP_VERIFY 8 71 72apr_file_t *ftemp = NULL; 73 74static int mkrecord(struct passwd_ctx *ctx, char *user) 75{ 76 char hash_str[MAX_STRING_LEN]; 77 int ret; 78 ctx->out = hash_str; 79 ctx->out_len = sizeof(hash_str); 80 81 ret = mkhash(ctx); 82 if (ret) 83 return ret; 84 85 ctx->out = apr_pstrcat(ctx->pool, user, ":", hash_str, NL, NULL); 86 if (strlen(ctx->out) >= MAX_STRING_LEN) { 87 ctx->errstr = "resultant record too long"; 88 return ERR_OVERFLOW; 89 } 90 return 0; 91} 92 93static void usage(void) 94{ 95 apr_file_printf(errfile, "Usage:" NL 96 "\thtpasswd [-cimBdpsDv] [-C cost] passwordfile username" NL 97 "\thtpasswd -b[cmBdpsDv] [-C cost] passwordfile username password" NL 98 NL 99 "\thtpasswd -n[imBdps] [-C cost] username" NL 100 "\thtpasswd -nb[mBdps] [-C cost] username password" NL 101 " -c Create a new file." NL 102 " -n Don't update file; display results on stdout." NL 103 " -b Use the password from the command line rather than prompting " 104 "for it." NL 105 " -i Read password from stdin without verification (for script usage)." NL 106 " -m Force MD5 encryption of the password (default)." NL 107 " -B Force bcrypt encryption of the password (very secure)." NL 108 " -C Set the computing time used for the bcrypt algorithm" NL 109 " (higher is more secure but slower, default: %d, valid: 4 to 31)." NL 110 " -d Force CRYPT encryption of the password (8 chars max, insecure)." NL 111 " -s Force SHA encryption of the password (insecure)." NL 112 " -p Do not encrypt the password (plaintext, insecure)." NL 113 " -D Delete the specified user." NL 114 " -v Verify password for the specified user." NL 115 "On other systems than Windows and NetWare the '-p' flag will " 116 "probably not work." NL 117 "The SHA algorithm does not use a salt and is less secure than the " 118 "MD5 algorithm." NL, 119 BCRYPT_DEFAULT_COST 120 ); 121 exit(ERR_SYNTAX); 122} 123 124/* 125 * Check to see if the specified file can be opened for the given 126 * access. 127 */ 128static int accessible(apr_pool_t *pool, char *fname, int mode) 129{ 130 apr_file_t *f = NULL; 131 132 if (apr_file_open(&f, fname, mode, APR_OS_DEFAULT, pool) != APR_SUCCESS) { 133 return 0; 134 } 135 apr_file_close(f); 136 return 1; 137} 138 139/* 140 * Return true if the named file exists, regardless of permissions. 141 */ 142static int exists(char *fname, apr_pool_t *pool) 143{ 144 apr_finfo_t sbuf; 145 apr_status_t check; 146 147 check = apr_stat(&sbuf, fname, APR_FINFO_TYPE, pool); 148 return ((check || sbuf.filetype != APR_REG) ? 0 : 1); 149} 150 151static void terminate(void) 152{ 153 apr_terminate(); 154#ifdef NETWARE 155 pressanykey(); 156#endif 157} 158 159static void check_args(int argc, const char *const argv[], 160 struct passwd_ctx *ctx, unsigned *mask, char **user, 161 char **pwfilename) 162{ 163 const char *arg; 164 int args_left = 2; 165 int i, ret; 166 apr_getopt_t *state; 167 apr_status_t rv; 168 char opt; 169 const char *opt_arg; 170 apr_pool_t *pool = ctx->pool; 171 172 rv = apr_getopt_init(&state, pool, argc, argv); 173 if (rv != APR_SUCCESS) 174 exit(ERR_SYNTAX); 175 176 while ((rv = apr_getopt(state, "cnmspdBbDiC:v", &opt, &opt_arg)) == APR_SUCCESS) { 177 switch (opt) { 178 case 'c': 179 *mask |= APHTP_NEWFILE; 180 break; 181 case 'n': 182 args_left--; 183 *mask |= APHTP_NOFILE; 184 break; 185 case 'D': 186 *mask |= APHTP_DELUSER; 187 break; 188 case 'v': 189 *mask |= APHTP_VERIFY; 190 break; 191 default: 192 ret = parse_common_options(ctx, opt, opt_arg); 193 if (ret) { 194 apr_file_printf(errfile, "%s: %s" NL, argv[0], ctx->errstr); 195 exit(ret); 196 } 197 } 198 } 199 if (ctx->passwd_src == PW_ARG) 200 args_left++; 201 if (rv != APR_EOF) 202 usage(); 203 204 if ((*mask) & (*mask - 1)) { 205 /* not a power of two, i.e. more than one flag specified */ 206 apr_file_printf(errfile, "%s: only one of -c -n -v -D may be specified" NL, 207 argv[0]); 208 exit(ERR_SYNTAX); 209 } 210 if ((*mask & APHTP_VERIFY) && ctx->passwd_src == PW_PROMPT) 211 ctx->passwd_src = PW_PROMPT_VERIFY; 212 213 /* 214 * Make sure we still have exactly the right number of arguments left 215 * (the filename, the username, and possibly the password if -b was 216 * specified). 217 */ 218 i = state->ind; 219 if ((argc - i) != args_left) { 220 usage(); 221 } 222 223 if (!(*mask & APHTP_NOFILE)) { 224 if (strlen(argv[i]) > (APR_PATH_MAX - 1)) { 225 apr_file_printf(errfile, "%s: filename too long" NL, argv[0]); 226 exit(ERR_OVERFLOW); 227 } 228 *pwfilename = apr_pstrdup(pool, argv[i++]); 229 } 230 if (strlen(argv[i]) > (MAX_STRING_LEN - 1)) { 231 apr_file_printf(errfile, "%s: username too long (> %d)" NL, 232 argv[0], MAX_STRING_LEN - 1); 233 exit(ERR_OVERFLOW); 234 } 235 *user = apr_pstrdup(pool, argv[i++]); 236 if ((arg = strchr(*user, ':')) != NULL) { 237 apr_file_printf(errfile, "%s: username contains illegal " 238 "character '%c'" NL, argv[0], *arg); 239 exit(ERR_BADUSER); 240 } 241 if (ctx->passwd_src == PW_ARG) { 242 if (strlen(argv[i]) > (MAX_STRING_LEN - 1)) { 243 apr_file_printf(errfile, "%s: password too long (> %d)" NL, 244 argv[0], MAX_STRING_LEN); 245 exit(ERR_OVERFLOW); 246 } 247 ctx->passwd = apr_pstrdup(pool, argv[i]); 248 } 249} 250 251static int verify(struct passwd_ctx *ctx, const char *hash) 252{ 253 apr_status_t rv; 254 int ret; 255 256 if (ctx->passwd == NULL && (ret = get_password(ctx)) != 0) 257 return ret; 258 rv = apr_password_validate(ctx->passwd, hash); 259 if (rv == APR_SUCCESS) 260 return 0; 261 if (APR_STATUS_IS_EMISMATCH(rv)) { 262 ctx->errstr = "password verification failed"; 263 return ERR_PWMISMATCH; 264 } 265 ctx->errstr = apr_psprintf(ctx->pool, "Could not verify password: %pm", 266 &rv); 267 return ERR_GENERAL; 268} 269 270/* 271 * Let's do it. We end up doing a lot of file opening and closing, 272 * but what do we care? This application isn't run constantly. 273 */ 274int main(int argc, const char * const argv[]) 275{ 276 apr_file_t *fpw = NULL; 277 char line[MAX_STRING_LEN]; 278 char *pwfilename = NULL; 279 char *user = NULL; 280 char tn[] = "htpasswd.tmp.XXXXXX"; 281 char *dirname; 282 char *scratch, cp[MAX_STRING_LEN]; 283 int found = 0; 284 int i; 285 unsigned mask = 0; 286 apr_pool_t *pool; 287 int existing_file = 0; 288 struct passwd_ctx ctx = { 0 }; 289#if APR_CHARSET_EBCDIC 290 apr_status_t rv; 291 apr_xlate_t *to_ascii; 292#endif 293 294 apr_app_initialize(&argc, &argv, NULL); 295 atexit(terminate); 296 apr_pool_create(&pool, NULL); 297 apr_pool_abort_set(abort_on_oom, pool); 298 apr_file_open_stderr(&errfile, pool); 299 ctx.pool = pool; 300 ctx.alg = ALG_APMD5; 301 302#if APR_CHARSET_EBCDIC 303 rv = apr_xlate_open(&to_ascii, "ISO-8859-1", APR_DEFAULT_CHARSET, pool); 304 if (rv) { 305 apr_file_printf(errfile, "apr_xlate_open(to ASCII)->%d" NL, rv); 306 exit(1); 307 } 308 rv = apr_SHA1InitEBCDIC(to_ascii); 309 if (rv) { 310 apr_file_printf(errfile, "apr_SHA1InitEBCDIC()->%d" NL, rv); 311 exit(1); 312 } 313 rv = apr_MD5InitEBCDIC(to_ascii); 314 if (rv) { 315 apr_file_printf(errfile, "apr_MD5InitEBCDIC()->%d" NL, rv); 316 exit(1); 317 } 318#endif /*APR_CHARSET_EBCDIC*/ 319 320 check_args(argc, argv, &ctx, &mask, &user, &pwfilename); 321 322 /* 323 * Only do the file checks if we're supposed to frob it. 324 */ 325 if (!(mask & APHTP_NOFILE)) { 326 existing_file = exists(pwfilename, pool); 327 if (existing_file) { 328 /* 329 * Check that this existing file is readable and writable. 330 */ 331 if (!accessible(pool, pwfilename, APR_FOPEN_READ|APR_FOPEN_WRITE)) { 332 apr_file_printf(errfile, "%s: cannot open file %s for " 333 "read/write access" NL, argv[0], pwfilename); 334 exit(ERR_FILEPERM); 335 } 336 } 337 else { 338 /* 339 * Error out if -c was omitted for this non-existant file. 340 */ 341 if (!(mask & APHTP_NEWFILE)) { 342 apr_file_printf(errfile, 343 "%s: cannot modify file %s; use '-c' to create it" NL, 344 argv[0], pwfilename); 345 exit(ERR_FILEPERM); 346 } 347 /* 348 * As it doesn't exist yet, verify that we can create it. 349 */ 350 if (!accessible(pool, pwfilename, APR_FOPEN_WRITE|APR_FOPEN_CREATE)) { 351 apr_file_printf(errfile, "%s: cannot create file %s" NL, 352 argv[0], pwfilename); 353 exit(ERR_FILEPERM); 354 } 355 } 356 } 357 358 /* 359 * All the file access checks (if any) have been made. Time to go to work; 360 * try to create the record for the username in question. If that 361 * fails, there's no need to waste any time on file manipulations. 362 * Any error message text is returned in the record buffer, since 363 * the mkrecord() routine doesn't have access to argv[]. 364 */ 365 if ((mask & (APHTP_DELUSER|APHTP_VERIFY)) == 0) { 366 i = mkrecord(&ctx, user); 367 if (i != 0) { 368 apr_file_printf(errfile, "%s: %s" NL, argv[0], ctx.errstr); 369 exit(i); 370 } 371 if (mask & APHTP_NOFILE) { 372 printf("%s" NL, ctx.out); 373 exit(0); 374 } 375 } 376 377 if ((mask & APHTP_VERIFY) == 0) { 378 /* 379 * We can access the files the right way, and we have a record 380 * to add or update. Let's do it.. 381 */ 382 if (apr_temp_dir_get((const char**)&dirname, pool) != APR_SUCCESS) { 383 apr_file_printf(errfile, "%s: could not determine temp dir" NL, 384 argv[0]); 385 exit(ERR_FILEPERM); 386 } 387 dirname = apr_psprintf(pool, "%s/%s", dirname, tn); 388 389 if (apr_file_mktemp(&ftemp, dirname, 0, pool) != APR_SUCCESS) { 390 apr_file_printf(errfile, "%s: unable to create temporary file %s" NL, 391 argv[0], dirname); 392 exit(ERR_FILEPERM); 393 } 394 } 395 396 /* 397 * If we're not creating a new file, copy records from the existing 398 * one to the temporary file until we find the specified user. 399 */ 400 if (existing_file && !(mask & APHTP_NEWFILE)) { 401 if (apr_file_open(&fpw, pwfilename, APR_READ | APR_BUFFERED, 402 APR_OS_DEFAULT, pool) != APR_SUCCESS) { 403 apr_file_printf(errfile, "%s: unable to read file %s" NL, 404 argv[0], pwfilename); 405 exit(ERR_FILEPERM); 406 } 407 while (apr_file_gets(line, sizeof(line), fpw) == APR_SUCCESS) { 408 char *colon; 409 410 strcpy(cp, line); 411 scratch = cp; 412 while (apr_isspace(*scratch)) { 413 ++scratch; 414 } 415 416 if (!*scratch || (*scratch == '#')) { 417 putline(ftemp, line); 418 continue; 419 } 420 /* 421 * See if this is our user. 422 */ 423 colon = strchr(scratch, ':'); 424 if (colon != NULL) { 425 *colon = '\0'; 426 } 427 else { 428 /* 429 * If we've not got a colon on the line, this could well 430 * not be a valid htpasswd file. 431 * We should bail at this point. 432 */ 433 apr_file_printf(errfile, "%s: The file %s does not appear " 434 "to be a valid htpasswd file." NL, 435 argv[0], pwfilename); 436 apr_file_close(fpw); 437 exit(ERR_INVALID); 438 } 439 if (strcmp(user, scratch) != 0) { 440 putline(ftemp, line); 441 continue; 442 } 443 else { 444 /* We found the user we were looking for */ 445 found++; 446 if ((mask & APHTP_DELUSER)) { 447 /* Delete entry from the file */ 448 apr_file_printf(errfile, "Deleting "); 449 } 450 else if ((mask & APHTP_VERIFY)) { 451 /* Verify */ 452 char *hash = colon + 1; 453 size_t len; 454 455 len = strcspn(hash, "\r\n"); 456 if (len == 0) { 457 apr_file_printf(errfile, "Empty hash for user %s" NL, 458 user); 459 exit(ERR_INVALID); 460 } 461 hash[len] = '\0'; 462 463 i = verify(&ctx, hash); 464 if (i != 0) { 465 apr_file_printf(errfile, "%s" NL, ctx.errstr); 466 exit(i); 467 } 468 } 469 else { 470 /* Update entry */ 471 apr_file_printf(errfile, "Updating "); 472 putline(ftemp, ctx.out); 473 } 474 } 475 } 476 apr_file_close(fpw); 477 } 478 if (!found) { 479 if (mask & APHTP_DELUSER) { 480 apr_file_printf(errfile, "User %s not found" NL, user); 481 exit(0); 482 } 483 else if (mask & APHTP_VERIFY) { 484 apr_file_printf(errfile, "User %s not found" NL, user); 485 exit(ERR_BADUSER); 486 } 487 else { 488 apr_file_printf(errfile, "Adding "); 489 putline(ftemp, ctx.out); 490 } 491 } 492 if (mask & APHTP_VERIFY) { 493 apr_file_printf(errfile, "Password for user %s correct." NL, user); 494 exit(0); 495 } 496 497 apr_file_printf(errfile, "password for user %s" NL, user); 498 499 /* The temporary file has all the data, just copy it to the new location. 500 */ 501 if (apr_file_copy(dirname, pwfilename, APR_FILE_SOURCE_PERMS, pool) != 502 APR_SUCCESS) { 503 apr_file_printf(errfile, "%s: unable to update file %s" NL, 504 argv[0], pwfilename); 505 exit(ERR_FILEPERM); 506 } 507 apr_file_close(ftemp); 508 return 0; 509} 510