Revert manifest to default one
[external/cups.git] / systemv / lppasswd.c
1 /*
2  * "$Id: lppasswd.c 9384 2010-11-22 07:06:39Z mike $"
3  *
4  *   MD5 password program for CUPS.
5  *
6  *   Copyright 2007-2010 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
51 static void     usage(FILE *fp);
52
53
54 /*
55  * 'main()' - Add, change, or delete passwords from the MD5 password file.
56  */
57
58 int                                     /* O - Exit status */
59 main(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
472 static void
473 usage(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 9384 2010-11-22 07:06:39Z mike $".
489  */