1/* 2 * "$Id: lppasswd.c 11093 2013-07-03 20:48:42Z msweet $" 3 * 4 * MD5 password program for CUPS. 5 * 6 * Copyright 2007-2011 by Apple Inc. 7 * Copyright 1997-2006 by Easy Software Products. 8 * 9 * These coded instructions, statements, and computer programs are the 10 * property of Apple Inc. and are protected by Federal copyright 11 * law. Distribution and use rights are outlined in the file "LICENSE.txt" 12 * which should have been included with this file. If this file is 13 * file is missing or damaged, see the license at "http://www.cups.org/". 14 * 15 * Contents: 16 * 17 * main() - Add, change, or delete passwords from the MD5 password file. 18 * usage() - Show program usage. 19 */ 20 21/* 22 * Include necessary headers... 23 */ 24 25#include <cups/cups-private.h> 26#include <cups/md5-private.h> 27#include <fcntl.h> 28#include <grp.h> 29#include <sys/types.h> 30#include <sys/stat.h> 31 32#ifndef WIN32 33# include <unistd.h> 34# include <signal.h> 35#endif /* !WIN32 */ 36 37 38/* 39 * Operations... 40 */ 41 42#define ADD 0 43#define CHANGE 1 44#define DELETE 2 45 46 47/* 48 * Local functions... 49 */ 50 51static void usage(FILE *fp) __attribute__((noreturn)); 52 53 54/* 55 * 'main()' - Add, change, or delete passwords from the MD5 password file. 56 */ 57 58int /* O - Exit status */ 59main(int argc, /* I - Number of command-line arguments */ 60 char *argv[]) /* I - Command-line arguments */ 61{ 62 int i; /* Looping var */ 63 char *opt; /* Option pointer */ 64 const char *username; /* Pointer to username */ 65 const char *groupname; /* Pointer to group name */ 66 int op; /* Operation (add, change, delete) */ 67 const char *passwd; /* Password string */ 68 FILE *infile, /* Input file */ 69 *outfile; /* Output file */ 70 char line[256], /* Line from file */ 71 userline[17], /* User from line */ 72 groupline[17], /* Group from line */ 73 md5line[33], /* MD5-sum from line */ 74 md5new[33]; /* New MD5 sum */ 75 char passwdmd5[1024], /* passwd.md5 file */ 76 passwdold[1024], /* passwd.old file */ 77 passwdnew[1024]; /* passwd.tmp file */ 78 char *newpass, /* new password */ 79 *oldpass; /* old password */ 80 int flag; /* Password check flags... */ 81 int fd; /* Password file descriptor */ 82 int error; /* Write error */ 83 _cups_globals_t *cg = _cupsGlobals(); /* Global data */ 84 cups_lang_t *lang; /* Language info */ 85#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET) 86 struct sigaction action; /* Signal action */ 87#endif /* HAVE_SIGACTION && !HAVE_SIGSET*/ 88 89 90 _cupsSetLocale(argv); 91 lang = cupsLangDefault(); 92 93 /* 94 * Check to see if stdin, stdout, and stderr are still open... 95 */ 96 97 if (fcntl(0, F_GETFD, &i) || 98 fcntl(1, F_GETFD, &i) || 99 fcntl(2, F_GETFD, &i)) 100 { 101 /* 102 * No, return exit status 2 and don't try to send any output since 103 * someone is trying to bypass the security on the server. 104 */ 105 106 return (2); 107 } 108 109 /* 110 * Find the server directory... 111 */ 112 113 snprintf(passwdmd5, sizeof(passwdmd5), "%s/passwd.md5", cg->cups_serverroot); 114 snprintf(passwdold, sizeof(passwdold), "%s/passwd.old", cg->cups_serverroot); 115 snprintf(passwdnew, sizeof(passwdnew), "%s/passwd.new", cg->cups_serverroot); 116 117 /* 118 * Find the default system group... 119 */ 120 121 if (getgrnam(CUPS_DEFAULT_GROUP)) 122 groupname = CUPS_DEFAULT_GROUP; 123 else 124 groupname = "unknown"; 125 126 endgrent(); 127 128 username = NULL; 129 op = CHANGE; 130 131 /* 132 * Parse command-line options... 133 */ 134 135 for (i = 1; i < argc; i ++) 136 if (argv[i][0] == '-') 137 for (opt = argv[i] + 1; *opt; opt ++) 138 switch (*opt) 139 { 140 case 'a' : /* Add */ 141 op = ADD; 142 break; 143 case 'x' : /* Delete */ 144 op = DELETE; 145 break; 146 case 'g' : /* Group */ 147 i ++; 148 if (i >= argc) 149 usage(stderr); 150 151 groupname = argv[i]; 152 break; 153 case 'h' : /* Help */ 154 usage(stdout); 155 break; 156 default : /* Bad option */ 157 usage(stderr); 158 break; 159 } 160 else if (!username) 161 username = argv[i]; 162 else 163 usage(stderr); 164 165 /* 166 * See if we are trying to add or delete a password when we aren't logged in 167 * as root... 168 */ 169 170 if (getuid() && getuid() != geteuid() && (op != CHANGE || username)) 171 { 172 _cupsLangPuts(stderr, 173 _("lppasswd: Only root can add or delete passwords.")); 174 return (1); 175 } 176 177 /* 178 * Fill in missing info... 179 */ 180 181 if (!username) 182 username = cupsUser(); 183 184 oldpass = newpass = NULL; 185 186 /* 187 * Obtain old and new password _before_ locking the database 188 * to keep users from locking the file indefinitely. 189 */ 190 191 if (op == CHANGE && getuid()) 192 { 193 if ((passwd = cupsGetPassword(_("Enter old password:"))) == NULL) 194 return (1); 195 196 if ((oldpass = strdup(passwd)) == NULL) 197 { 198 _cupsLangPrintf(stderr, 199 _("lppasswd: Unable to copy password string: %s"), 200 strerror(errno)); 201 return (1); 202 } 203 } 204 205 /* 206 * Now get the new password, if necessary... 207 */ 208 209 if (op != DELETE) 210 { 211 if ((passwd = cupsGetPassword( 212 _cupsLangString(lang, _("Enter password:")))) == NULL) 213 return (1); 214 215 if ((newpass = strdup(passwd)) == NULL) 216 { 217 _cupsLangPrintf(stderr, 218 _("lppasswd: Unable to copy password string: %s"), 219 strerror(errno)); 220 return (1); 221 } 222 223 if ((passwd = cupsGetPassword( 224 _cupsLangString(lang, _("Enter password again:")))) == NULL) 225 return (1); 226 227 if (strcmp(passwd, newpass) != 0) 228 { 229 _cupsLangPuts(stderr, 230 _("lppasswd: Sorry, passwords don't match.")); 231 return (1); 232 } 233 234 /* 235 * Check that the password contains at least one letter and number. 236 */ 237 238 flag = 0; 239 240 for (passwd = newpass; *passwd; passwd ++) 241 if (isdigit(*passwd & 255)) 242 flag |= 1; 243 else if (isalpha(*passwd & 255)) 244 flag |= 2; 245 246 /* 247 * Only allow passwords that are at least 6 chars, have a letter and 248 * a number, and don't contain the username. 249 */ 250 251 if (strlen(newpass) < 6 || strstr(newpass, username) != NULL || flag != 3) 252 { 253 _cupsLangPuts(stderr, _("lppasswd: Sorry, password rejected.")); 254 _cupsLangPuts(stderr, _("Your password must be at least 6 characters " 255 "long, cannot contain your username, and must " 256 "contain at least one letter and number.")); 257 return (1); 258 } 259 } 260 261 /* 262 * Ignore SIGHUP, SIGINT, SIGTERM, and SIGXFSZ (if defined) for the 263 * remainder of the time so that we won't end up with bogus password 264 * files... 265 */ 266 267#ifndef WIN32 268# if defined(HAVE_SIGSET) 269 sigset(SIGHUP, SIG_IGN); 270 sigset(SIGINT, SIG_IGN); 271 sigset(SIGTERM, SIG_IGN); 272# ifdef SIGXFSZ 273 sigset(SIGXFSZ, SIG_IGN); 274# endif /* SIGXFSZ */ 275# elif defined(HAVE_SIGACTION) 276 memset(&action, 0, sizeof(action)); 277 action.sa_handler = SIG_IGN; 278 279 sigaction(SIGHUP, &action, NULL); 280 sigaction(SIGINT, &action, NULL); 281 sigaction(SIGTERM, &action, NULL); 282# ifdef SIGXFSZ 283 sigaction(SIGXFSZ, &action, NULL); 284# endif /* SIGXFSZ */ 285# else 286 signal(SIGHUP, SIG_IGN); 287 signal(SIGINT, SIG_IGN); 288 signal(SIGTERM, SIG_IGN); 289# ifdef SIGXFSZ 290 signal(SIGXFSZ, SIG_IGN); 291# endif /* SIGXFSZ */ 292# endif 293#endif /* !WIN32 */ 294 295 /* 296 * Open the output file. 297 */ 298 299 if ((fd = open(passwdnew, O_WRONLY | O_CREAT | O_EXCL, 0400)) < 0) 300 { 301 if (errno == EEXIST) 302 _cupsLangPuts(stderr, _("lppasswd: Password file busy.")); 303 else 304 _cupsLangPrintf(stderr, _("lppasswd: Unable to open password file: %s"), 305 strerror(errno)); 306 307 return (1); 308 } 309 310 if ((outfile = fdopen(fd, "w")) == NULL) 311 { 312 _cupsLangPrintf(stderr, _("lppasswd: Unable to open password file: %s"), 313 strerror(errno)); 314 315 unlink(passwdnew); 316 317 return (1); 318 } 319 320 setbuf(outfile, NULL); 321 322 /* 323 * Open the existing password file and create a new one... 324 */ 325 326 infile = fopen(passwdmd5, "r"); 327 if (infile == NULL && errno != ENOENT && op != ADD) 328 { 329 _cupsLangPrintf(stderr, _("lppasswd: Unable to open password file: %s"), 330 strerror(errno)); 331 332 fclose(outfile); 333 334 unlink(passwdnew); 335 336 return (1); 337 } 338 339 /* 340 * Read lines from the password file; the format is: 341 * 342 * username:group:MD5-sum 343 */ 344 345 error = 0; 346 userline[0] = '\0'; 347 groupline[0] = '\0'; 348 md5line[0] = '\0'; 349 350 if (infile) 351 { 352 while (fgets(line, sizeof(line), infile) != NULL) 353 { 354 if (sscanf(line, "%16[^:]:%16[^:]:%32s", userline, groupline, md5line) != 3) 355 continue; 356 357 if (strcmp(username, userline) == 0 && 358 strcmp(groupname, groupline) == 0) 359 break; 360 361 if (fputs(line, outfile) == EOF) 362 { 363 _cupsLangPrintf(stderr, 364 _("lppasswd: Unable to write to password file: %s"), 365 strerror(errno)); 366 error = 1; 367 break; 368 } 369 } 370 371 if (!error) 372 { 373 while (fgets(line, sizeof(line), infile) != NULL) 374 if (fputs(line, outfile) == EOF) 375 { 376 _cupsLangPrintf(stderr, 377 _("lppasswd: Unable to write to password file: %s"), 378 strerror(errno)); 379 error = 1; 380 break; 381 } 382 } 383 } 384 385 if (op == CHANGE && 386 (strcmp(username, userline) || strcmp(groupname, groupline))) 387 { 388 _cupsLangPrintf(stderr, 389 _("lppasswd: user \"%s\" and group \"%s\" do not exist."), 390 username, groupname); 391 error = 1; 392 } 393 else if (op != DELETE) 394 { 395 if (oldpass && 396 strcmp(httpMD5(username, "CUPS", oldpass, md5new), md5line) != 0) 397 { 398 _cupsLangPuts(stderr, _("lppasswd: Sorry, password doesn't match.")); 399 error = 1; 400 } 401 else 402 { 403 snprintf(line, sizeof(line), "%s:%s:%s\n", username, groupname, 404 httpMD5(username, "CUPS", newpass, md5new)); 405 if (fputs(line, outfile) == EOF) 406 { 407 _cupsLangPrintf(stderr, 408 _("lppasswd: Unable to write to password file: %s"), 409 strerror(errno)); 410 error = 1; 411 } 412 } 413 } 414 415 /* 416 * Close the files... 417 */ 418 419 if (infile) 420 fclose(infile); 421 422 if (fclose(outfile) == EOF) 423 error = 1; 424 425 /* 426 * Error out gracefully as needed... 427 */ 428 429 if (error) 430 { 431 _cupsLangPuts(stderr, _("lppasswd: Password file not updated.")); 432 433 unlink(passwdnew); 434 435 return (1); 436 } 437 438 /* 439 * Save old passwd file 440 */ 441 442 unlink(passwdold); 443 if (link(passwdmd5, passwdold) && errno != ENOENT) 444 { 445 _cupsLangPrintf(stderr, 446 _("lppasswd: failed to backup old password file: %s"), 447 strerror(errno)); 448 unlink(passwdnew); 449 return (1); 450 } 451 452 /* 453 * Install new password file 454 */ 455 456 if (rename(passwdnew, passwdmd5) < 0) 457 { 458 _cupsLangPrintf(stderr, _("lppasswd: failed to rename password file: %s"), 459 strerror(errno)); 460 unlink(passwdnew); 461 return (1); 462 } 463 464 return (0); 465} 466 467 468/* 469 * 'usage()' - Show program usage. 470 */ 471 472static void 473usage(FILE *fp) /* I - File to send usage to */ 474{ 475 if (getuid()) 476 _cupsLangPuts(fp, _("Usage: lppasswd [-g groupname]")); 477 else 478 _cupsLangPuts(fp, 479 _("Usage: lppasswd [-g groupname] [username]\n" 480 " lppasswd [-g groupname] -a [username]\n" 481 " lppasswd [-g groupname] -x [username]")); 482 483 exit(1); 484} 485 486 487/* 488 * End of "$Id: lppasswd.c 11093 2013-07-03 20:48:42Z msweet $". 489 */ 490