updating documentation to reflect code a little bit.
[Samba.git] / source / utils / smbpasswd.c
blobc79aa15c807638702c2560e81da69dd87fb59c4c
1 #ifdef SMB_PASSWD
3 /*
4 * Unix SMB/Netbios implementation. Version 1.9. smbpasswd module. Copyright
5 * (C) Jeremy Allison 1995.
6 *
7 * This program is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 675
19 * Mass Ave, Cambridge, MA 02139, USA.
22 #include "includes.h"
23 #include "des.h"
25 /* Static buffers we will return. */
26 static struct smb_passwd pw_buf;
27 static pstring user_name;
28 static unsigned char smbpwd[16];
29 static unsigned char smbntpwd[16];
31 static int gethexpwd(char *p, char *pwd)
33 int i;
34 unsigned char lonybble, hinybble;
35 char *hexchars = "0123456789ABCDEF";
36 char *p1, *p2;
37 for (i = 0; i < 32; i += 2) {
38 hinybble = toupper(p[i]);
39 lonybble = toupper(p[i + 1]);
41 p1 = strchr(hexchars, hinybble);
42 p2 = strchr(hexchars, lonybble);
43 if (!p1 || !p2)
44 return (False);
46 hinybble = PTR_DIFF(p1, hexchars);
47 lonybble = PTR_DIFF(p2, hexchars);
49 pwd[i / 2] = (hinybble << 4) | lonybble;
51 return (True);
54 struct smb_passwd *
55 _my_get_smbpwnam(FILE * fp, char *name, BOOL * valid_old_pwd,
56 BOOL *got_valid_nt_entry, long *pwd_seekpos)
58 char linebuf[256];
59 unsigned char c;
60 unsigned char *p;
61 long uidval;
62 long linebuf_len;
65 * Scan the file, a line at a time and check if the name matches.
67 while (!feof(fp)) {
68 linebuf[0] = '\0';
69 *pwd_seekpos = ftell(fp);
71 fgets(linebuf, 256, fp);
72 if (ferror(fp))
73 return NULL;
76 * Check if the string is terminated with a newline - if not
77 * then we must keep reading and discard until we get one.
79 linebuf_len = strlen(linebuf);
80 if (linebuf[linebuf_len - 1] != '\n') {
81 c = '\0';
82 while (!ferror(fp) && !feof(fp)) {
83 c = fgetc(fp);
84 if (c == '\n')
85 break;
87 } else
88 linebuf[linebuf_len - 1] = '\0';
90 if ((linebuf[0] == 0) && feof(fp))
91 break;
93 * The line we have should be of the form :-
95 * username:uid:[32hex bytes]:....other flags presently
96 * ignored....
98 * or,
100 * username:uid:[32hex bytes]:[32hex bytes]:....ignored....
102 * if Windows NT compatible passwords are also present.
105 if (linebuf[0] == '#' || linebuf[0] == '\0')
106 continue;
107 p = (unsigned char *) strchr(linebuf, ':');
108 if (p == NULL)
109 continue;
111 * As 256 is shorter than a pstring we don't need to check
112 * length here - if this ever changes....
114 strncpy(user_name, linebuf, PTR_DIFF(p, linebuf));
115 user_name[PTR_DIFF(p, linebuf)] = '\0';
116 if (!strequal(user_name, name))
117 continue;
119 /* User name matches - get uid and password */
120 p++; /* Go past ':' */
121 if (!isdigit(*p))
122 return (False);
124 uidval = atoi((char *) p);
125 while (*p && isdigit(*p))
126 p++;
128 if (*p != ':')
129 return (False);
132 * Now get the password value - this should be 32 hex digits
133 * which are the ascii representations of a 16 byte string.
134 * Get two at a time and put them into the password.
136 p++;
137 *pwd_seekpos += PTR_DIFF(p, linebuf); /* Save exact position
138 * of passwd in file -
139 * this is used by
140 * smbpasswd.c */
141 if (*p == '*' || *p == 'X') {
142 /* Password deliberately invalid - end here. */
143 *valid_old_pwd = False;
144 *got_valid_nt_entry = False;
145 pw_buf.smb_nt_passwd = NULL; /* No NT password (yet)*/
147 /* Now check if the NT compatible password is
148 available. */
149 p += 33; /* Move to the first character of the line after
150 the lanman password. */
151 if ((linebuf_len >= (PTR_DIFF(p, linebuf) + 33)) && (p[32] == ':')) {
152 /* NT Entry was valid - even if 'X' or '*', can be overwritten */
153 *got_valid_nt_entry = True;
154 if (*p != '*' && *p != 'X') {
155 if (gethexpwd((char *)p,(char *)smbntpwd))
156 pw_buf.smb_nt_passwd = smbntpwd;
159 pw_buf.smb_name = user_name;
160 pw_buf.smb_userid = uidval;
161 pw_buf.smb_passwd = NULL; /* No password */
162 return (&pw_buf);
164 if (linebuf_len < (PTR_DIFF(p, linebuf) + 33))
165 return (False);
167 if (p[32] != ':')
168 return (False);
170 if (!strncasecmp((char *)p, "NO PASSWORD", 11)) {
171 pw_buf.smb_passwd = NULL; /* No password */
172 } else {
173 if(!gethexpwd((char *)p,(char *)smbpwd))
174 return False;
175 pw_buf.smb_passwd = smbpwd;
178 pw_buf.smb_name = user_name;
179 pw_buf.smb_userid = uidval;
180 pw_buf.smb_nt_passwd = NULL;
181 *got_valid_nt_entry = False;
182 *valid_old_pwd = True;
184 /* Now check if the NT compatible password is
185 available. */
186 p += 33; /* Move to the first character of the line after
187 the lanman password. */
188 if ((linebuf_len >= (PTR_DIFF(p, linebuf) + 33)) && (p[32] == ':')) {
189 /* NT Entry was valid - even if 'X' or '*', can be overwritten */
190 *got_valid_nt_entry = True;
191 if (*p != '*' && *p != 'X') {
192 if (gethexpwd((char *)p,(char *)smbntpwd))
193 pw_buf.smb_nt_passwd = smbntpwd;
196 return &pw_buf;
198 return NULL;
202 * Print command usage on stderr and die.
204 static void usage(char *name)
206 fprintf(stderr, "Usage is : %s [username]\n", name);
207 exit(1);
210 int main(int argc, char **argv)
212 int real_uid;
213 struct passwd *pwd;
214 fstring old_passwd;
215 uchar old_p16[16];
216 uchar old_nt_p16[16];
217 fstring new_passwd;
218 uchar new_p16[16];
219 uchar new_nt_p16[16];
220 char *p;
221 struct smb_passwd *smb_pwent;
222 FILE *fp;
223 BOOL valid_old_pwd = False;
224 BOOL got_valid_nt_entry = False;
225 long seekpos;
226 int pwfd;
227 char ascii_p16[66];
228 char c;
229 int ret, i, err, writelen;
230 int lockfd = -1;
231 char *pfile = SMB_PASSWD_FILE;
232 char readbuf[16 * 1024];
234 TimeInit();
236 setup_logging(argv[0],True);
238 charset_initialise();
240 #ifndef DEBUG_PASSWORD
241 /* Check the effective uid */
242 if (geteuid() != 0) {
243 fprintf(stderr, "%s: Must be setuid root.\n", argv[0]);
244 exit(1);
246 #endif
248 /* Get the real uid */
249 real_uid = getuid();
251 /* Deal with usage problems */
252 if (real_uid == 0) {
253 /* As root we can change anothers password. */
254 if (argc != 1 && argc != 2)
255 usage(argv[0]);
256 } else if (argc != 1)
257 usage(argv[0]);
260 if (real_uid == 0 && argc == 2) {
261 /* If we are root we can change anothers password. */
262 strncpy(user_name, argv[1], sizeof(user_name) - 1);
263 user_name[sizeof(user_name) - 1] = '\0';
264 pwd = getpwnam(user_name);
265 } else {
266 pwd = getpwuid(real_uid);
269 if (pwd == 0) {
270 fprintf(stderr, "%s: Unable to get UNIX password entry for user.\n", argv[0]);
271 exit(1);
273 /* If we are root we don't ask for the old password. */
274 old_passwd[0] = '\0';
275 if (real_uid != 0) {
276 p = getpass("Old SMB password:");
277 strncpy(old_passwd, p, sizeof(fstring));
278 old_passwd[sizeof(fstring)-1] = '\0';
280 new_passwd[0] = '\0';
281 p = getpass("New SMB password:");
282 strncpy(new_passwd, p, sizeof(fstring));
283 new_passwd[sizeof(fstring)-1] = '\0';
284 p = getpass("Retype new SMB password:");
285 if (strcmp(p, new_passwd)) {
286 fprintf(stderr, "%s: Mismatch - password unchanged.\n", argv[0]);
287 exit(1);
290 if (new_passwd[0] == '\0') {
291 printf("Password not set\n");
292 exit(0);
295 /* Calculate the MD4 hash (NT compatible) of the old and new passwords */
296 memset(old_nt_p16, '\0', 16);
297 E_md4hash((uchar *)old_passwd, old_nt_p16);
299 memset(new_nt_p16, '\0', 16);
300 E_md4hash((uchar *) new_passwd, new_nt_p16);
302 /* Mangle the passwords into Lanman format */
303 old_passwd[14] = '\0';
304 strupper(old_passwd);
305 new_passwd[14] = '\0';
306 strupper(new_passwd);
309 * Calculate the SMB (lanman) hash functions of both old and new passwords.
312 memset(old_p16, '\0', 16);
313 E_P16((uchar *) old_passwd, old_p16);
315 memset(new_p16, '\0', 16);
316 E_P16((uchar *) new_passwd, new_p16);
319 * Open the smbpaswd file XXXX - we need to parse smb.conf to get the
320 * filename
322 if ((fp = fopen(pfile, "r+")) == NULL) {
323 err = errno;
324 fprintf(stderr, "%s: Failed to open password file %s.\n",
325 argv[0], pfile);
326 errno = err;
327 perror(argv[0]);
328 exit(err);
330 /* Set read buffer to 16k for effiecient reads */
331 setvbuf(fp, readbuf, _IOFBF, sizeof(readbuf));
333 /* make sure it is only rw by the owner */
334 chmod(pfile, 0600);
336 /* Lock the smbpasswd file for write. */
337 if ((lockfd = pw_file_lock(pfile, F_WRLCK, 5)) < 0) {
338 err = errno;
339 fprintf(stderr, "%s: Failed to lock password file %s.\n",
340 argv[0], pfile);
341 fclose(fp);
342 errno = err;
343 perror(argv[0]);
344 exit(err);
346 /* Get the smb passwd entry for this user */
347 smb_pwent = _my_get_smbpwnam(fp, pwd->pw_name, &valid_old_pwd,
348 &got_valid_nt_entry, &seekpos);
349 if (smb_pwent == NULL) {
350 fprintf(stderr, "%s: Failed to find entry for user %s in file %s.\n",
351 argv[0], pwd->pw_name, pfile);
352 fclose(fp);
353 pw_file_unlock(lockfd);
354 exit(1);
356 /* If we are root we don't need to check the old password. */
357 if (real_uid != 0) {
358 if ((valid_old_pwd == False) || (smb_pwent->smb_passwd == NULL)) {
359 fprintf(stderr, "%s: User %s is disabled, plase contact your administrator to enable it.\n", argv[0], pwd->pw_name);
360 fclose(fp);
361 pw_file_unlock(lockfd);
362 exit(1);
364 /* Check the old Lanman password */
365 if (memcmp(old_p16, smb_pwent->smb_passwd, 16)) {
366 fprintf(stderr, "%s: Couldn't change password.\n", argv[0]);
367 fclose(fp);
368 pw_file_unlock(lockfd);
369 exit(1);
371 /* Check the NT password if it exists */
372 if (smb_pwent->smb_nt_passwd != NULL) {
373 if (memcmp(old_nt_p16, smb_pwent->smb_nt_passwd, 16)) {
374 fprintf(stderr, "%s: Couldn't change password.\n", argv[0]);
375 fclose(fp);
376 pw_file_unlock(lockfd);
377 exit(1);
382 * If we get here either we were root or the old password checked out
383 * ok.
385 /* Create the 32 byte representation of the new p16 */
386 for (i = 0; i < 16; i++) {
387 sprintf(&ascii_p16[i * 2], "%02X", (uchar) new_p16[i]);
389 if(got_valid_nt_entry) {
390 /* Add on the NT md4 hash */
391 ascii_p16[32] = ':';
392 for (i = 0; i < 16; i++) {
393 sprintf(&ascii_p16[(i * 2)+33], "%02X", (uchar) new_nt_p16[i]);
397 * Do an atomic write into the file at the position defined by
398 * seekpos.
400 pwfd = fileno(fp);
401 ret = lseek(pwfd, seekpos - 1, SEEK_SET);
402 if (ret != seekpos - 1) {
403 err = errno;
404 fprintf(stderr, "%s: seek fail on file %s.\n",
405 argv[0], pfile);
406 fclose(fp);
407 errno = err;
408 perror(argv[0]);
409 pw_file_unlock(lockfd);
410 exit(1);
412 /* Sanity check - ensure the character is a ':' */
413 if (read(pwfd, &c, 1) != 1) {
414 err = errno;
415 fprintf(stderr, "%s: read fail on file %s.\n",
416 argv[0], pfile);
417 fclose(fp);
418 errno = err;
419 perror(argv[0]);
420 pw_file_unlock(lockfd);
421 exit(1);
423 if (c != ':') {
424 fprintf(stderr, "%s: sanity check on passwd file %s failed.\n",
425 argv[0], pfile);
426 fclose(fp);
427 pw_file_unlock(lockfd);
428 exit(1);
430 writelen = (got_valid_nt_entry) ? 65 : 32;
431 if (write(pwfd, ascii_p16, writelen) != writelen) {
432 err = errno;
433 fprintf(stderr, "%s: write fail in file %s.\n",
434 argv[0], pfile);
435 fclose(fp);
436 errno = err;
437 perror(argv[0]);
438 pw_file_unlock(lockfd);
439 exit(err);
441 fclose(fp);
442 pw_file_unlock(lockfd);
443 printf("Password changed\n");
444 return 0;
447 #else
449 #include "includes.h"
451 int
452 main(int argc, char **argv)
454 printf("smb password encryption not selected in Makefile\n");
455 return 0;
457 #endif