1/* saslpasswd.c -- SASL password setting program
2 * Rob Earhart
3 */
4/*
5 * Copyright (c) 1998-2003 Carnegie Mellon University.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 *
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in
16 *    the documentation and/or other materials provided with the
17 *    distribution.
18 *
19 * 3. The name "Carnegie Mellon University" must not be used to
20 *    endorse or promote products derived from this software without
21 *    prior written permission. For permission or any other legal
22 *    details, please contact
23 *      Office of Technology Transfer
24 *      Carnegie Mellon University
25 *      5000 Forbes Avenue
26 *      Pittsburgh, PA  15213-3890
27 *      (412) 268-4387, fax: (412) 268-7395
28 *      tech-transfer@andrew.cmu.edu
29 *
30 * 4. Redistributions of any form whatsoever must retain the following
31 *    acknowledgment:
32 *    "This product includes software developed by Computing Services
33 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
34 *
35 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
36 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
37 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
38 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
39 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
40 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
41 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
42 */
43
44#include <config.h>
45#include <stdio.h>
46#include <assert.h>
47
48#ifndef WIN32
49#include <termios.h>
50#include <unistd.h>
51
52/* perror can't be used on Windows system calls, so we define a new macro to underline this */
53#define	p_oserror(str)	    perror(str)
54
55#else /* WIN32 */
56
57#include <stdio.h>
58#include <io.h>
59
60#include <saslutil.h>
61__declspec(dllimport) char *optarg;
62__declspec(dllimport) int optind;
63
64/* perror can't be used on Windows system calls, so we define a new macro to underline this */
65void p_oserror (const char *string);
66#endif /*WIN32*/
67
68#include <sasl.h>
69#include <saslplug.h>
70
71char myhostname[1025];
72
73#define PW_BUF_SIZE 2048
74
75static const char build_ident[] = "$Build: saslpasswd " PACKAGE "-" VERSION " $";
76
77const char *progname = NULL;
78char *sasldb_path = NULL;
79
80#ifdef WIN32
81
82/* This is almost like _plug_get_error_message(), but uses malloc */
83char * _get_error_message (
84   DWORD error
85)
86{
87    char * return_value;
88    LPVOID lpMsgBuf;
89
90    FormatMessage(
91	FORMAT_MESSAGE_ALLOCATE_BUFFER |
92	FORMAT_MESSAGE_FROM_SYSTEM |
93	FORMAT_MESSAGE_IGNORE_INSERTS,
94	NULL,
95	error,
96	MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */
97	(LPTSTR) &lpMsgBuf,
98	0,
99	NULL
100    );
101
102    return_value = strdup (lpMsgBuf);
103
104    LocalFree( lpMsgBuf );
105    return (return_value);
106}
107
108/* perror() like function that works on OS error codes returned by GetLastError() */
109void p_oserror (
110    const char *message
111)
112{
113/* Try to match perror() behaviour:
114    string is printed first, followed by a colon, then by the system error message
115    for the last library call that produced the error, and finally by a newline
116    character. If string is a null pointer or a pointer to a null string, perror
117    prints only the system error message.
118 */
119    if (message && *message) {
120	fprintf (stderr, "%s: %s\n", message, _get_error_message(GetLastError()));
121    } else {
122	fprintf (stderr, "%s\n", _get_error_message(GetLastError()));
123    }
124}
125#endif /* WIN32 */
126
127void read_password(const char *prompt,
128		   int flag_pipe,
129		   char ** password,
130		   unsigned *passlen)
131{
132  char buf[PW_BUF_SIZE];
133#ifndef WIN32
134  struct termios ts, nts;
135  ssize_t n_read;
136#else
137  HANDLE hStdin;
138  DWORD n_read, fdwMode, fdwOldMode;
139  hStdin = GetStdHandle(STD_INPUT_HANDLE);
140  if (hStdin == INVALID_HANDLE_VALUE) {
141	  p_oserror(progname);
142	  exit(-(SASL_FAIL));
143  }
144#endif /*WIN32*/
145
146  if (! flag_pipe) {
147    fputs(prompt, stdout);
148    fflush(stdout);
149#ifndef WIN32
150    tcgetattr(STDIN_FILENO, &ts);
151    nts = ts;
152    nts.c_lflag &= ~(ECHO | ECHOE | ECHOK
153#ifdef ECHOCTL
154    | ECHOCTL
155#endif
156#ifdef ECHOPRT
157    | ECHOPRT
158#endif
159#ifdef ECHOKE
160    | ECHOKE
161#endif
162    );
163    nts.c_lflag |= ICANON | ECHONL;
164    tcsetattr(STDIN_FILENO, TCSAFLUSH, &nts);
165#else
166  if (! GetConsoleMode(hStdin, &fdwOldMode)) {
167	  p_oserror(progname);
168	  exit(-(SASL_FAIL));
169  }
170  fdwMode = fdwOldMode & ~ENABLE_ECHO_INPUT;
171  if (! SetConsoleMode(hStdin, fdwMode)) {
172	  p_oserror(progname);
173	  exit(-(SASL_FAIL));
174  }
175#endif /*WIN32*/
176  }
177
178#ifndef WIN32
179  n_read = read(STDIN_FILENO, buf, PW_BUF_SIZE);
180  if (n_read < 0) {
181#else
182  if (! ReadFile(hStdin, buf, PW_BUF_SIZE, &n_read, NULL)) {
183#endif /*WIN32*/
184
185    p_oserror(progname);
186    exit(-(SASL_FAIL));
187  }
188
189  if (! flag_pipe) {
190#ifndef WIN32
191    tcsetattr(STDIN_FILENO, TCSANOW, &ts);
192    if (0 < n_read && buf[n_read - 1] != '\n') {
193      /* if we didn't end with a \n, echo one */
194      putchar('\n');
195      fflush(stdout);
196    }
197#else
198    SetConsoleMode(hStdin, fdwOldMode);
199    putchar('\n');
200    fflush(stdout);
201#endif /*WIN32*/
202  }
203
204  if (0 < n_read && buf[n_read - 1] == '\n') /* if we ended with a \n */
205    n_read--;			             /* remove it */
206
207#ifdef WIN32
208  /*WIN32 will have a CR in the buffer also*/
209  if (0 < n_read && buf[n_read - 1] == '\r') /* if we ended with a \r */
210    n_read--;			             /* remove it */
211#endif /*WIN32*/
212
213  *password = malloc(n_read + 1);
214  if (! *password) {
215/* Can use perror() here even on Windows, as malloc is in std C library */
216    perror(progname);
217    exit(-(SASL_FAIL));
218  }
219
220  memcpy(*password, buf, n_read);
221  (*password)[n_read] = '\0';	/* be nice... */
222  *passlen = (unsigned int)n_read;
223}
224
225void exit_sasl(int result, const char *errstr) __attribute__((noreturn));
226
227void
228exit_sasl(int result, const char *errstr)
229{
230  /* APPLE: split condition to fix unused format string parameter */
231  if (errstr)
232      (void)fprintf(stderr, "%s: %s: %s\n",
233                    progname,
234                    sasl_errstring(result, NULL, NULL),
235                    errstr);
236  else
237      (void)fprintf(stderr, "%s: %s\n",
238                    progname,
239                    sasl_errstring(result, NULL, NULL));
240
241  exit(result < 0 ? -result : result);
242}
243
244int good_getopt(void *context __attribute__((unused)),
245		const char *plugin_name __attribute__((unused)),
246		const char *option,
247		const char **result,
248		unsigned *len)
249{
250    if (sasldb_path && !strcmp(option, "sasldb_path")) {
251	*result = sasldb_path;
252	if (len)
253	    *len = (unsigned) strlen(sasldb_path);
254	return SASL_OK;
255    }
256
257    return SASL_FAIL;
258}
259
260static struct sasl_callback goodsasl_cb[] = {
261    { SASL_CB_GETOPT, (sasl_callback_ft)&good_getopt, NULL },
262    { SASL_CB_LIST_END, NULL, NULL }
263};
264
265int
266main(int argc, char *argv[])
267{
268  int flag_pipe = 0, flag_create = 0, flag_disable = 0, flag_error = 0;
269  int flag_nouserpass = 0;
270  int c;
271  char *userid;
272  char *password = NULL;
273  char *verify;
274  unsigned passlen = 0;
275  unsigned verifylen;
276  const char *errstr = NULL;
277  int result;
278  sasl_conn_t *conn;
279  char *user_domain = NULL;
280  char *appname = "saslpasswd";
281  const char *sasl_implementation;
282  int libsasl_version;
283  int libsasl_major;
284  int libsasl_minor;
285  int libsasl_step;
286
287#ifdef WIN32
288  /* initialize winsock */
289  WSADATA wsaData;
290
291  result = WSAStartup( MAKEWORD(2, 0), &wsaData );
292  if ( result != 0) {
293    exit_sasl(SASL_FAIL, "WSAStartup");
294  }
295#endif
296
297  memset(myhostname, 0, sizeof(myhostname));
298  result = gethostname(myhostname, sizeof(myhostname)-1);
299  if (result == -1) exit_sasl(SASL_FAIL, "gethostname");
300
301  if (! argv[0])
302    progname = "saslpasswd";
303  else {
304    progname = strrchr(argv[0], HIER_DELIMITER);
305    if (progname)
306      progname++;
307    else
308      progname = argv[0];
309  }
310
311  while ((c = getopt(argc, argv, "vpcdnf:u:a:h?")) != EOF)
312    switch (c) {
313    case 'p':
314      flag_pipe = 1;
315      break;
316    case 'c':
317      if (flag_disable)
318	flag_error = 1;
319      else
320	flag_create = 1;
321      break;
322    case 'd':
323      if (flag_create)
324	flag_error = 1;
325      else
326	flag_disable = 1;
327      break;
328    case 'n':
329	flag_nouserpass = 1;
330	break;
331    case 'u':
332      user_domain = optarg;
333      break;
334    case 'f':
335      sasldb_path = optarg;
336      break;
337    case 'a':
338      appname = optarg;
339      if (strchr(optarg, '/') != NULL) {
340        (void)fprintf(stderr, "appname must not contain /\n");
341        exit(-(SASL_FAIL));
342      }
343      break;
344    case 'v':
345      sasl_version (&sasl_implementation, &libsasl_version);
346      libsasl_major = libsasl_version >> 24;
347      libsasl_minor = (libsasl_version >> 16) & 0xFF;
348      libsasl_step = libsasl_version & 0xFFFF;
349
350      (void)fprintf(stderr, "\nThis product includes software developed by Computing Services\n"
351	 "at Carnegie Mellon University (http://www.cmu.edu/computing/).\n\n"
352	 "Built against SASL API version %u.%u.%u\n"
353	 "LibSasl version %u.%u.%u by \"%s\"\n",
354	 SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
355	 libsasl_major, libsasl_minor, libsasl_step, sasl_implementation);
356      exit(0);
357      break;
358    default:
359      flag_error = 1;
360      break;
361    }
362
363  if (optind != argc - 1)
364    flag_error = 1;
365
366  if (flag_error) {
367    (void)fprintf(stderr,
368	"\nThis product includes software developed by Computing Services\n"
369	 "at Carnegie Mellon University (http://www.cmu.edu/computing/).\n\n"
370	"%s: usage: %s [-v] [-c [-p] [-n]] [-d] [-a appname] [-f sasldb] [-u DOM] userid\n"
371		  "\t-p\tpipe mode -- no prompt, password read on stdin\n"
372		  "\t-c\tcreate -- ask mechs to create the account\n"
373		  "\t-d\tdisable -- ask mechs to disable/delete the account\n"
374		  "\t-n\tno userPassword -- don't set plaintext userPassword property\n"
375		  "\t  \t                   (only set mechanism-specific secrets)\n"
376		  "\t-f sasldb\tuse given file as sasldb\n"
377		  "\t-a appname\tuse appname as application name\n"
378		  "\t-u DOM\tuse DOM for user domain\n"
379		  "\t-v\tprint version numbers and exit\n",
380		  progname, progname);
381    exit(-(SASL_FAIL));
382  }
383
384  userid = argv[optind];
385
386  result = sasl_server_init(goodsasl_cb, appname);
387  if (result != SASL_OK)
388    exit_sasl(result, NULL);
389
390  result = sasl_server_new("sasldb",
391			   myhostname,
392			   user_domain,
393			   NULL,
394			   NULL,
395			   NULL,
396			   0,
397			   &conn);
398  if (result != SASL_OK)
399    exit_sasl(result, NULL);
400
401#ifndef WIN32
402  if (! flag_pipe && ! isatty(STDIN_FILENO))
403    flag_pipe = 1;
404#endif /*WIN32*/
405
406  if (!flag_disable) {
407      read_password("Password: ", flag_pipe, &password, &passlen);
408
409      if (! flag_pipe) {
410	  read_password("Again (for verification): ", flag_pipe, &verify,
411		  &verifylen);
412	  if (passlen != verifylen
413	      || memcmp(password, verify, verifylen)) {
414	      fprintf(stderr, "%s: passwords don't match; aborting\n",
415		      progname);
416	      exit(-(SASL_BADPARAM));
417	  }
418      }
419  }
420
421  result = sasl_setpass(conn,
422			userid,
423			password,
424			passlen,
425			NULL, 0,
426			(flag_create ? SASL_SET_CREATE : 0)
427			| (flag_disable ? SASL_SET_DISABLE : 0)
428			| (flag_nouserpass ? SASL_SET_NOPLAIN : 0));
429
430  if (result != SASL_OK && !flag_disable)
431      exit_sasl(result, NULL);
432  else {
433      struct propctx *propctx = NULL;
434      const char *delete_request[] = { "cmusaslsecretCRAM-MD5",
435				       "cmusaslsecretDIGEST-MD5",
436				       "cmusaslsecretPLAIN",
437				       NULL };
438      int ret = SASL_OK;
439      /* Either we were setting and succeeded or we were disabling and
440	 failed.  In either case, we want to wipe old entries */
441
442      /* Delete the possibly old entries */
443      /* We don't care if these fail */
444      propctx = prop_new(0);
445      if (!propctx) ret = SASL_FAIL;
446      if (!ret) ret = prop_request(propctx, delete_request);
447      if (!ret) {
448	  ret = prop_set(propctx, "cmusaslsecretCRAM-MD5", NULL, 0);
449	  ret = prop_set(propctx, "cmusaslsecretDIGEST-MD5", NULL, 0);
450	  ret = prop_set(propctx, "cmusaslsecretPLAIN", NULL, 0);
451	  ret = sasl_auxprop_store(conn, propctx, userid);
452      }
453      if (propctx) prop_dispose(&propctx);
454  }
455
456  if (result != SASL_OK)
457/* errstr is currently always NULL */
458    exit_sasl(result, errstr);
459
460  sasl_dispose(&conn);
461  sasl_done();
462
463  return 0;
464}
465