1 /*****************************************************************************
5 * Description: Main program source.
7 * Copyright (c) 1997-2000 Messaging Direct Ltd.
10 * Portions Copyright (c) 2003 Jeremy Rumpf
11 * jrumpf@heavyload.net
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in the
22 * documentation and/or other materials provided with the distribution.
24 * THIS SOFTWARE IS PROVIDED BY MESSAGING DIRECT LTD. ``AS IS'' AND ANY
25 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
27 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MESSAGING DIRECT LTD. OR
28 * ITS EMPLOYEES OR AGENTS BE LIABLE FOR ANY DIRECT, INDIRECT,
29 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
31 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
32 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
33 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
34 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
40 * saslauthd is a re-implementation of the pwcheck utility included
41 * with the CMU Cyrus IMAP server circa 1997. This implementation
42 * was written by Lyndon Nerenberg of Messaging Direct Inc. (which
43 * at that time was the Esys Corporation) and was included in the
44 * company's IMAP message store product (Simeon Message Service) as
45 * the smsauthd utility.
47 * This implementation was contributed to CMU by Messaging Direct Ltd.
50 * September 2001 (Ken Murchison of Oceana Matrix Ltd.):
51 * - Modified the protocol to use counted length strings instead of
52 * nul delimited strings.
53 * - Augmented the protocol to accept the service name and user realm.
55 * Feb 2003: Partial rewrite and cleanup by Jeremy Rumpf jrumpf@heavyload.net
56 * - Merge the doors and unix IPC methods under a common framework.
60 * saslauthd provides an interface between the SASL library and various
61 * external authentication mechanisms. The primary goal is to isolate
62 * code that requires superuser privileges (for example, access to
63 * the shadow password file) into a single easily audited module. It
64 * can also act as an authentication proxy between plaintext-equivelent
65 * authentication schemes (i.e. CRAM-MD5) and more secure authentication
66 * services such as Kerberos, although such usage is STRONGLY discouraged
67 * because it exposes the strong credentials via the insecure plaintext
70 * The program listens for connections on a UNIX domain socket. Access to
71 * the service is controlled by the UNIX filesystem permissions on the
74 * The service speaks a very simple protocol. The client connects and
75 * sends the authentication identifier, the plaintext password, the
76 * service name and user realm as counted length strings (a 16-bit
77 * unsigned integer in network byte order followed by the string
78 * itself). The server returns a single response as a counted length
79 * string. The response begins with "OK" or "NO", and is followed by
80 * an optional text string (separated from the OK/NO by a single space
81 * character), and a NUL. The server then closes the connection.
83 * An "OK" response indicates the authentication credentials are valid.
84 * A "NO" response indicates the authentication failed.
86 * The optional text string may be used to indicate an exceptional
87 * condition in the authentication environment that should be communicated
90 *****************************************************************************/
104 #include <sys/file.h>
105 #include <sys/types.h>
106 #include <sys/stat.h>
107 #include <sys/socket.h>
109 #include <sys/wait.h>
114 #include "saslauthd-main.h"
118 /* max login + max realm + '@' */
119 #define MAX_LOGIN_REALM_LEN (MAX_REQ_LEN * 2) + 1
121 /****************************************
122 * declarations/protos
123 *****************************************/
124 static void show_version();
125 static void show_usage();
127 /****************************************
128 * application globals
129 *****************************************/
130 int flags = 0; /* Runtime flags */
131 int g_argc; /* Copy of argc for those who need it*/
132 char **g_argv; /* Copy of argv for those who need it*/
133 char *run_path = NULL; /* path to our working directory */
134 authmech_t *auth_mech = NULL; /* Authentication mechanism to use */
135 char *mech_option = NULL; /* mechanism-specific option */
136 int num_procs = 5; /* The max number of worker processes*/
139 /****************************************
141 *****************************************/
142 extern char *optarg; /* For getopt() */
143 static int master_pid; /* Pid of the master process */
144 static int pid_fd; /* Descriptor to the open pid file */
145 static int pid_file_lock_fd; /* Descriptor to the open pid lock file */
146 static char *pid_file; /* Pid file name */
147 static char *pid_file_lock; /* Pid lock file name */
148 static int startup_pipe[2] = { -1, -1 };
150 int main(int argc, char **argv) {
154 struct flock lockinfo;
155 char *auth_mech_name = NULL;
156 size_t pid_file_size;
158 SET_AUTH_PARAMETERS(argc, argv);
164 flags |= USE_ACCEPT_LOCK;
166 flags |= LOG_USE_SYSLOG;
167 flags |= LOG_USE_STDERR;
170 while ((option = getopt(argc, argv, "a:cdhO:lm:n:rs:t:vV")) != -1) {
173 /* Only one at a time, please! */
179 auth_mech_name = strdup(optarg);
180 if (!auth_mech_name) {
181 logger(L_ERR, L_FUNC,
182 "could not allocate memory");
188 flags |= CACHE_ENABLED;
193 flags &= ~DETACH_TTY;
201 set_mech_option(optarg);
205 flags &= ~USE_ACCEPT_LOCK;
209 set_run_path(optarg);
213 set_max_procs(optarg);
217 flags |= CONCAT_LOGIN_REALM;
221 cache_set_table_size(optarg);
225 cache_set_timeout(optarg);
242 if (run_path == NULL)
243 run_path = PATH_SASLAUTHD_RUNDIR;
245 if (auth_mech_name == NULL) {
246 logger(L_ERR, L_FUNC, "no authentication mechanism specified");
251 set_auth_mech(auth_mech_name);
253 if (flags & VERBOSE) {
254 logger(L_DEBUG, L_FUNC, "num_procs : %d", num_procs);
256 if (mech_option == NULL)
257 logger(L_DEBUG, L_FUNC, "mech_option: NULL");
259 logger(L_DEBUG, L_FUNC, "mech_option: %s", mech_option);
261 logger(L_DEBUG, L_FUNC, "run_path : %s", run_path);
262 logger(L_DEBUG, L_FUNC, "auth_mech : %s", auth_mech->name);
265 /*********************************************************
266 * Change our working directory to the dir where the
267 * run path is set to, core dumps will go there to keep
269 **********************************************************/
270 if (chdir(run_path) == -1) {
272 logger(L_ERR, L_FUNC, "could not chdir to: %s", run_path);
273 logger(L_ERR, L_FUNC, "chdir: %s", strerror(rc));
274 logger(L_ERR, L_FUNC, "Check to make sure the directory exists and is");
275 logger(L_ERR, L_FUNC, "writeable by the user this process runs as.");
281 pid_file_size = strlen(run_path) + sizeof(PID_FILE_LOCK) + 1;
282 if ((pid_file_lock = malloc(pid_file_size)) == NULL) {
283 logger(L_ERR, L_FUNC, "could not allocate memory");
287 strlcpy(pid_file_lock, run_path, pid_file_size);
288 strlcat(pid_file_lock, PID_FILE_LOCK, pid_file_size);
290 if ((pid_file_lock_fd = open(pid_file_lock, O_CREAT|O_TRUNC|O_RDWR, 644)) < 0) {
292 logger(L_ERR, L_FUNC, "could not open pid lock file: %s", pid_file_lock);
293 logger(L_ERR, L_FUNC, "open: %s", strerror(rc));
294 logger(L_ERR, L_FUNC,
295 "Check to make sure the directory exists and is");
296 logger(L_ERR, L_FUNC, "writeable by the user this process runs as.");
300 lockinfo.l_type = F_WRLCK;
301 lockinfo.l_start = 0;
303 lockinfo.l_whence = SEEK_SET;
305 if (fcntl(pid_file_lock_fd, F_SETLK, &lockinfo) == -1) {
307 logger(L_ERR, L_FUNC, "could not lock pid lock file: %s", pid_file_lock);
308 logger(L_ERR, L_FUNC, "fcntl: %s", strerror(rc));
312 if(pipe(startup_pipe) == -1) {
313 logger(L_ERR, L_FUNC, "can't create startup pipe");
317 /*********************************************************
318 * Enable signal handlers.
319 **********************************************************/
322 /*********************************************************
323 * Cache setup, exit if it doesn't succeed (optional would
324 * be to disable the cache and log a warning).
325 **********************************************************/
326 if (cache_init() != 0)
329 /*********************************************************
330 * Call the ipc specific initializer. This should also
331 * call detach_tty() at the appropriate point.
332 **********************************************************/
335 /*********************************************************
336 * Enable general cleanup.
337 **********************************************************/
340 /*********************************************************
341 * If required, enable the process model.
342 **********************************************************/
343 if (flags & USE_PROCESS_MODEL) {
345 logger(L_DEBUG, L_FUNC, "using process model");
347 for (x = 1; x < num_procs; x++) {
348 if (have_baby() != 0)
349 continue; /* parent */
355 /*********************************************************
356 * Enter the ipc loop, we should never return.
357 **********************************************************/
364 /*************************************************************
365 * Performs all authentication centric duties. We should be
366 * getting callbacks from the ipc method here. We'll simply
367 * return a pointer to a string to send back to the client.
368 * The caller is responsible for freeing the pointer.
369 **************************************************************/
370 char *do_auth(const char *_login, const char *password, const char *service, const char *realm) {
372 struct cache_result lkup_result;
375 char login_buff[MAX_LOGIN_REALM_LEN];
379 /***********************************************************
380 * Check to concat the login and realm into a single login.
381 * Aka, login: foo realm: bar becomes login: foo@bar.
382 * We do this because some mechs have no concept of a realm.
383 * Ie. auth_pam and friends.
384 ***********************************************************/
385 if ((flags & CONCAT_LOGIN_REALM) && realm && realm[0] != '\0') {
386 strlcpy(login_buff, _login, sizeof(login_buff));
387 strlcat(login_buff, "@", sizeof(login_buff));
388 strlcat(login_buff, realm, sizeof(login_buff));
392 login = (char *)_login;
395 if (cache_lookup(login, realm, service, password, &lkup_result) == CACHE_OK) {
396 response = strdup("OK");
399 response = auth_mech->authenticate(login, password, service, realm);
401 if (response == NULL) {
402 logger(L_ERR, L_FUNC, "internal mechanism failure: %s", auth_mech->name);
403 response = strdup("NO internal mechanism failure");
407 if (strncmp(response, "OK", 2) == 0) {
408 cache_commit(&lkup_result);
410 if (flags & VERBOSE) {
412 logger(L_DEBUG, L_FUNC, "auth success (cached): [user=%s] [service=%s] [realm=%s]", \
413 login, service, realm);
415 logger(L_DEBUG, L_FUNC, "auth success: [user=%s] [service=%s] [realm=%s] [mech=%s]", \
416 login, service, realm, auth_mech->name);
421 if (strncmp(response, "NO", 2) == 0) {
422 logger(L_INFO, L_FUNC, "auth failure: [user=%s] [service=%s] [realm=%s] [mech=%s] [reason=%s]", \
423 login, service, realm, auth_mech->name,
424 strlen(response) >= 4 ? response+3 : "Unknown");
429 logger(L_ERR, L_FUNC, "mechanism returned unknown response: %s", auth_mech->name);
430 response = strdup("NO internal mechanism failure");
436 /*************************************************************
437 * Allow someone to set the auth mech to use
438 **************************************************************/
439 void set_auth_mech(const char *mech) {
440 for (auth_mech = mechanisms; auth_mech->name != NULL; auth_mech++) {
441 if (strcasecmp(auth_mech->name, mech) == 0)
445 if (auth_mech->name == NULL) {
446 logger(L_ERR, L_FUNC, "unknown authentication mechanism: %s", mech);
450 if (auth_mech->initialize) {
451 if(auth_mech->initialize() != 0) {
452 logger(L_ERR, L_FUNC, "failed to initialize mechanism %s",
460 /*************************************************************
461 * Allow someone to set the number of worker processes we
462 * will use. Only applicable to unix ipc.
463 **************************************************************/
464 void set_max_procs(const char *procs) {
465 num_procs = atoi(procs);
468 logger(L_ERR, L_FUNC, "invalid number of worker processes defined");
476 /*************************************************************
477 * Allow someone to set the mechanism specific option
478 **************************************************************/
479 void set_mech_option(const char *option) {
484 if ((mech_option = strdup(option)) == NULL) {
485 logger(L_ERR, L_FUNC, "could not allocate memory");
493 /*************************************************************
494 * Allow someone to set the path to our working directory
495 **************************************************************/
496 void set_run_path(const char *path) {
499 logger(L_ERR, L_FUNC, "-m requires an absolute pathname");
506 if ((run_path = strdup(path)) == NULL) {
507 logger(L_ERR, L_FUNC, "could not allocate memory");
516 /*************************************************************
517 * Setup all the proper signal masks.
518 **************************************************************/
519 void signal_setup() {
521 static struct sigaction act_sigchld;
522 static struct sigaction act_sigalrm;
523 static struct sigaction act_sigterm;
524 static struct sigaction act_sigpipe;
525 static struct sigaction act_sighup;
526 static struct sigaction act_sigint;
529 /**************************************************************
530 * Handler for SIGCHLD
531 **************************************************************/
532 act_sigchld.sa_handler = handle_sigchld;
533 sigemptyset(&act_sigchld.sa_mask);
535 if (sigaction(SIGCHLD, &act_sigchld, NULL) != 0) {
537 logger(L_ERR, L_FUNC, "failed to set sigaction for SIGCHLD");
538 logger(L_ERR, L_FUNC, "sigaction: %s", strerror(rc));
542 /**************************************************************
543 * Handler for SIGALRM (IGNORE)
544 **************************************************************/
545 act_sigalrm.sa_handler = SIG_IGN;
546 sigemptyset(&act_sigalrm.sa_mask);
548 if (sigaction(SIGALRM, &act_sigalrm, NULL) != 0) {
550 logger(L_ERR, L_FUNC, "failed to set sigaction for SIGALRM");
551 logger(L_ERR, L_FUNC, "sigaction: %s", strerror(rc));
555 /**************************************************************
556 * Handler for SIGPIPE (IGNORE)
557 **************************************************************/
558 act_sigpipe.sa_handler = SIG_IGN;
559 sigemptyset(&act_sigpipe.sa_mask);
561 if (sigaction(SIGPIPE, &act_sigpipe, NULL) != 0) {
563 logger(L_ERR, L_FUNC, "failed to set sigaction for SIGPIPE");
564 logger(L_ERR, L_FUNC, "sigaction: %s", strerror(rc));
568 /**************************************************************
569 * Handler for SIGHUP (IGNORE)
570 **************************************************************/
571 act_sighup.sa_handler = SIG_IGN;
572 sigemptyset(&act_sighup.sa_mask);
574 if (sigaction(SIGHUP, &act_sighup, NULL) != 0) {
576 logger(L_ERR, L_FUNC, "failed to set sigaction for SIGHUP");
577 logger(L_ERR, L_FUNC, "sigaction: %s", strerror(rc));
581 /**************************************************************
582 * Handler for SIGTERM
583 **************************************************************/
584 act_sigterm.sa_handler = server_exit;
585 sigemptyset(&act_sigterm.sa_mask);
587 if (sigaction(SIGTERM, &act_sigterm, NULL) != 0) {
589 logger(L_ERR, L_FUNC, "failed to set sigaction for SIGTERM");
590 logger(L_ERR, L_FUNC, "sigaction: %s", strerror(rc));
594 /**************************************************************
596 **************************************************************/
597 act_sigint.sa_handler = server_exit;
598 sigemptyset(&act_sigint.sa_mask);
600 if (sigaction(SIGINT, &act_sigint, NULL) != 0) {
602 logger(L_ERR, L_FUNC, "failed to set sigaction for SIGINT");
603 logger(L_ERR, L_FUNC, "sigaction: %s", strerror(rc));
611 /*************************************************************
612 * Detaches us from the controlling tty (aka daemonize).
613 * More than likely this will be called from an ipc_init()
614 * function as we want to stay in the foreground for as long
616 **************************************************************/
624 struct flock lockinfo;
626 /**************************************************************
627 * Make sure we're supposed to do this, the user may have
628 * requested us to stay in the foreground.
629 **************************************************************/
630 if (flags & DETACH_TTY) {
634 if ((pid == -1) && (errno == EAGAIN)) {
635 logger(L_ERR, L_FUNC,
636 "fork failed, retrying");
645 /* Non retryable error. */
647 logger(L_ERR, L_FUNC, "Cannot start saslauthd");
648 logger(L_ERR, L_FUNC, "saslauthd master fork failed: %s",
651 } else if (pid != 0) {
654 /* Parent, wait for child */
655 if(read(startup_pipe[0], &exit_code, sizeof(exit_code)) == -1) {
656 logger(L_ERR, L_FUNC,
657 "Cannot start saslauthd");
658 logger(L_ERR, L_FUNC,
659 "could not read from startup_pipe");
660 unlink(pid_file_lock);
663 if (exit_code != 0) {
664 logger(L_ERR, L_FUNC, "Cannot start saslauthd");
665 if (exit_code == 2) {
666 logger(L_ERR, L_FUNC,
667 "Another instance of saslauthd is currently running");
669 logger(L_ERR, L_FUNC, "Check syslog for errors");
672 unlink(pid_file_lock);
678 close(startup_pipe[0]);
682 if (setsid() == -1) {
686 logger(L_ERR, L_FUNC, "failed to set session id: %s",
689 /* Tell our parent that we failed. */
690 write(startup_pipe[1], &exit_result, sizeof(exit_result));
695 if ((null_fd = open("/dev/null", O_RDWR, 0)) == -1) {
699 logger(L_ERR, L_FUNC, "failed to open /dev/null: %s",
702 /* Tell our parent that we failed. */
703 write(startup_pipe[1], &exit_result, sizeof(exit_result));
708 /*********************************************************
709 * From this point on, stop printing errors out to stderr.
710 **********************************************************/
711 flags &= ~LOG_USE_STDERR;
714 close(STDOUT_FILENO);
715 close(STDERR_FILENO);
717 dup2(null_fd, STDIN_FILENO);
718 dup2(null_fd, STDOUT_FILENO);
719 dup2(null_fd, STDERR_FILENO);
724 /*********************************************************
725 * Locks don't persist across forks. Relock the pid file
726 * to keep folks from having duplicate copies running...
727 *********************************************************/
728 if (!(pid_file = malloc(strlen(run_path) + sizeof(PID_FILE) + 1))) {
730 logger(L_ERR, L_FUNC, "could not allocate memory");
731 write(startup_pipe[1], &exit_result, sizeof(exit_result));
735 strcpy(pid_file, run_path);
736 strcat(pid_file, PID_FILE);
738 /* Write out the pidfile */
739 pid_fd = open(pid_file, O_CREAT|O_RDWR, 0644);
744 logger(L_ERR, L_FUNC, "could not open pid file %s: %s",
745 pid_file, strerror(rc));
747 /* Tell our parent that we failed. */
748 write(startup_pipe[1], &exit_result, sizeof(exit_result));
754 lockinfo.l_type = F_WRLCK;
755 lockinfo.l_start = 0;
757 lockinfo.l_whence = SEEK_SET;
759 if (fcntl(pid_fd, F_SETLK, &lockinfo) == -1) {
763 logger(L_ERR, L_FUNC, "could not lock pid file %s: %s",
764 pid_file, strerror(rc));
766 /* Tell our parent that we failed. */
767 write(startup_pipe[1], &exit_result, sizeof(exit_result));
771 int pid_fd_flags = fcntl(pid_fd, F_GETFD, 0);
773 if (pid_fd_flags != -1) {
775 fcntl(pid_fd, F_SETFD, pid_fd_flags | FD_CLOEXEC);
778 if (pid_fd_flags == -1) {
781 logger(L_ERR, L_FUNC, "unable to set close-on-exec for pidfile");
783 /* Tell our parent that we failed. */
784 write(startup_pipe[1], &exit_result, sizeof(exit_result));
790 master_pid = getpid();
791 snprintf(buf, sizeof(buf), "%lu\n", (unsigned long)master_pid);
792 if (lseek(pid_fd, 0, SEEK_SET) == -1 ||
793 ftruncate(pid_fd, 0) == -1 ||
794 write(pid_fd, buf, strlen(buf)) == -1) {
798 logger(L_ERR, L_FUNC, "could not write to pid file %s: %s", pid_file, strerror(rc));
800 /* Tell our parent that we failed. */
801 write(startup_pipe[1], &exit_result, sizeof(exit_result));
813 if(write(startup_pipe[1], &exit_result, sizeof(exit_result)) == -1) {
814 logger(L_ERR, L_FUNC,
815 "could not write success result to startup pipe");
820 close(startup_pipe[1]);
821 if(pid_file_lock_fd != -1) close(pid_file_lock_fd);
824 logger(L_INFO, L_FUNC, "master pid is: %lu", (unsigned long)master_pid);
830 /*************************************************************
831 * Fork off a copy of ourselves. Return 0 if we're the child,
832 * > 0 for the parent. Die if we can't fork (the environment
833 * is probably unstable?).
834 **************************************************************/
843 logger(L_ERR, L_FUNC, "could not fork child process");
844 logger(L_ERR, L_FUNC, "fork: %s", strerror(rc));
848 /*********************************************************
849 * If we're the child, clear the AM_MASTER flag.
850 **********************************************************/
856 if (flags & VERBOSE) {
857 logger(L_DEBUG, L_FUNC, "forked child: %lu",
865 /*************************************************************
866 * Reap in all the dead children
867 **************************************************************/
868 void handle_sigchld() {
871 while ((pid = waitpid(-1, 0, WNOHANG)) > 0) {
873 logger(L_DEBUG, L_FUNC, "child exited: %lu", (unsigned long)pid);
881 /*************************************************************
882 * Do some final cleanup here.
883 **************************************************************/
885 struct flock lock_st;
887 /*********************************************************
888 * If we're not the master process, don't do anything
889 **********************************************************/
890 if (!(flags & AM_MASTER)) {
892 logger(L_DEBUG, L_FUNC, "child exited: %d", getpid());
897 kill(-master_pid, SIGTERM);
899 /*********************************************************
900 * Tidy up and delete the pid_file. (close will release the lock)
901 * besides, we want to unlink it first anyway to avoid a race.
902 * Note that only one process (the master, in our case) should
904 **********************************************************/
905 if(flags & DETACH_TTY) {
906 if(getpid() == master_pid) unlink(pid_file);
910 logger(L_DEBUG, L_FUNC, "pid file removed: %s", pid_file);
914 /* Tidy up and delete the pid_file_lock. (in the detached
915 case this is covered by the parent process already */
917 unlink(pid_file_lock);
918 close(pid_file_lock_fd);
921 logger(L_DEBUG, L_FUNC, "pid file lock removed: %s",
928 /*********************************************************
929 * Cleanup the cache, if it's enabled
930 **********************************************************/
931 if (flags & CACHE_ENABLED) {
932 cache_cleanup_lock();
936 /*********************************************************
937 * Tell the IPC method to clean its room.
938 **********************************************************/
941 /*********************************************************
942 * Any other cleanup should go here
943 **********************************************************/
945 logger(L_INFO, L_FUNC, "master exited: %d", master_pid);
951 /*************************************************************
952 * Dump out our version and all the auth mechs we support
953 **************************************************************/
954 void show_version() {
955 authmech_t *authmech;
957 fprintf(stderr, "saslauthd %s\nauthentication mechanisms:", VERSION);
959 for (authmech = mechanisms; authmech->name != NULL; authmech++) {
960 fprintf(stderr, " %s", authmech->name);
963 fprintf(stderr, "\n\n");
968 /*************************************************************
969 * Dump out our usage info and tag a show_version after it
970 **************************************************************/
972 fprintf(stderr, "usage: saslauthd [options]\n\n");
973 fprintf(stderr, "option information:\n");
974 fprintf(stderr, " -a <authmech> Selects the authentication mechanism to use.\n");
975 fprintf(stderr, " -c Enable credential caching.\n");
976 fprintf(stderr, " -d Debugging (don't detach from tty, implies -V)\n");
977 fprintf(stderr, " -r Combine the realm with the login before passing to authentication mechanism\n");
978 fprintf(stderr, " Ex. login: \"foo\" realm: \"bar\" will get passed as login: \"foo@bar\"\n");
979 fprintf(stderr, " The realm name is passed untouched.\n");
980 fprintf(stderr, " -O <option> Optional argument to pass to the authentication\n");
981 fprintf(stderr, " mechanism.\n");
982 fprintf(stderr, " -l Disable accept() locking. Increases performance, but\n");
983 fprintf(stderr, " may not be compatible with some operating systems.\n");
984 fprintf(stderr, " -m <path> Alternate path for the saslauthd working directory,\n");
985 fprintf(stderr, " must be absolute.\n");
986 fprintf(stderr, " -n <procs> Number of worker processes to create.\n");
987 fprintf(stderr, " -s <kilobytes> Size of the credential cache (in kilobytes)\n");
988 fprintf(stderr, " -t <seconds> Timeout for items in the credential cache (in seconds)\n");
989 fprintf(stderr, " -v Display version information and available mechs\n");
990 fprintf(stderr, " -V Enable verbose logging\n");
991 fprintf(stderr, " -h Display this message.\n\n");