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