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