xref: /illumos-gate/usr/src/cmd/gss/gsscred/gsscred.c (revision bc1f688b4872ace323eaddbb1a6365d054e7bf56)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 1997-2002 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  *  gsscred utility
31  *  Manages mapping between a security principal name and unix uid
32  */
33 
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <pwd.h>
37 #include <unistd.h>
38 #include <string.h>
39 #include <gssapi/gssapi_ext.h>
40 #include "gsscred.h"
41 
42 #define	MAX_STR_LEN	1024
43 
44 
45 /*
46  * Internal Functions
47  */
48 static void usage(void);
49 static void addUser(const char *name, const char *oid, const char *userUid,
50 		const char *userComment, const char *userMech);
51 static int file_listUsers(const gss_OID mechOid, const char *userUid,
52 		char **errDetails);
53 static int listUsers(const char *name, const char *nameTypeOid,
54 		const char *uid, const char *mechOid);
55 static int file_removeUsers(const gss_OID mechOid, const char *userUid,
56 		char **errDetails);
57 static int removeUsers(const char *name, const char *nameTypeOid,
58 		const char *uid, const char *mechOid);
59 
60 /*
61  * Global variables
62  */
63 static int tableSource;
64 static char *PROG_NAME = NULL;
65 
66 int
67 main(int argc, char *args[])
68 {
69 	char *userName = NULL, *nameTypeOID = NULL,
70 		*uid = NULL, *comment = NULL, *mech = NULL,
71 		operation = '0';
72 	int c, errflag = 0;
73 	extern char *optarg;
74 
75 	PROG_NAME = *args;
76 
77 	/* set locale and domain for internationalization */
78 	setlocale(LC_ALL, "");
79 	textdomain(TEXT_DOMAIN);
80 
81 	if (argc < 2)
82 		usage();
83 
84 	/* Process the input arguments */
85 	while ((c = getopt(argc, args, "arln:o:u:m:c:")) != EOF) {
86 
87 		switch (c) {
88 		case 'n':
89 			userName = optarg;
90 			break;
91 
92 		case 'o':
93 			nameTypeOID = optarg;
94 			break;
95 
96 		case 'u':
97 			uid = optarg;
98 			break;
99 
100 		case 'm':
101 			mech = optarg;
102 			break;
103 
104 		case 'c':
105 			comment = optarg;
106 			break;
107 
108 		case 'a':
109 		case 'r':
110 		case 'l':
111 			operation = c;
112 			errflag++;
113 			if (errflag > 1)
114 				usage();
115 			break;
116 
117 		default:
118 			usage();
119 		}
120 	}
121 
122 	/* determine which back-end to use as the gsscred store */
123 	tableSource = gsscred_read_config_file();
124 
125 	/* perform the requested operation */
126 	switch (operation) {
127 		case 'a':
128 			addUser(userName, nameTypeOID, uid, comment, mech);
129 			break;
130 
131 		case 'r':
132 			removeUsers(userName, nameTypeOID, uid, mech);
133 			break;
134 
135 		case 'l':
136 			listUsers(userName, nameTypeOID, uid, mech);
137 			break;
138 
139 		default:
140 			usage();
141 	}
142 	fprintf(stdout, "\n");
143 	return (0);
144 }  /* main */
145 
146 /*
147  * Handles the addition of users to the gsscred table.
148  */
149 static void
150 addUser(const char *name, const char *nameOidStr,
151 	    const char *userUid, const char *userComment,
152 	    const char *mechOidStr)
153 {
154 	gss_OID mechOid;
155 	gss_buffer_desc fullName = GSS_C_EMPTY_BUFFER,
156 		hexBufDesc = GSS_C_EMPTY_BUFFER,
157 		hexMechOid = GSS_C_EMPTY_BUFFER;
158 	char comment[MAX_STR_LEN+1], hexBuf[MAX_STR_LEN+MAX_STR_LEN+1],
159 		hexMechOidBuf[MAX_STR_LEN+1], *commentPtr = NULL,
160 		*errDetail = NULL, uidStr[256], *uidPtr;
161 	struct passwd *aUser;
162 	OM_uint32 minor;
163 	int count = 0, retCode;
164 
165 	hexMechOid.length = MAX_STR_LEN;
166 	hexMechOid.value = (void*)hexMechOidBuf;
167 
168 	/* addition of users can only be performed by super users */
169 	if (getuid()) {
170 		fprintf(stderr,
171 			gettext("\nUser addition requires"
172 				" root privileges."));
173 		return;
174 	}
175 
176 	/* the mechanism OID is required */
177 	if (mechOidStr == NULL) {
178 		fprintf(stderr, gettext("\nUnspecified mechanism."));
179 		usage();
180 	}
181 
182 	/* Convert from string mechanism Oid to ASN.1 oid and then hex */
183 	if (__gss_mech_to_oid(mechOidStr, &mechOid) != GSS_S_COMPLETE) {
184 		fprintf(stderr,
185 			gettext("\nInvalid mechanism specified [%s]."),
186 			mechOidStr);
187 		return;
188 	}
189 
190 	hexBufDesc.length = mechOid->length;
191 	hexBufDesc.value = mechOid->elements;
192 
193 	if (!gsscred_AsHex(&hexBufDesc, &hexMechOid)) {
194 		fprintf(stderr,
195 			gettext("\nInternal error.  "
196 				"Conversion to hex failed."));
197 		return;
198 	}
199 
200 	/*
201 	 * if the name is specified, then do single addition.
202 	 * Might have to look up the uid.
203 	 */
204 	if (name != NULL) {
205 		hexBufDesc.length = sizeof (hexBuf);
206 		hexBufDesc.value = hexBuf;
207 
208 		/* build the name as needed */
209 		if (!gsscred_MakeName(mechOid, name, nameOidStr, &fullName)) {
210 			fprintf(stderr,
211 				gettext("\nError adding user [%s]."), name);
212 			return;
213 		}
214 
215 		/* convert it to hex */
216 		if (!gsscred_AsHex(&fullName, &hexBufDesc)) {
217 			gss_release_buffer(&minor, &fullName);
218 			fprintf(stderr,
219 				gettext("\nInternal error.  "
220 					"Conversion to hex failed."));
221 			return;
222 		}
223 
224 		/* might require the lookup of the uid if one not specified */
225 		if (userUid == NULL) {
226 
227 			if ((aUser = getpwnam(name)) == NULL) {
228 				fprintf(stderr,
229 					gettext("\nUnable to obtain password"
230 						" information for [%s]."),
231 					name);
232 				gss_release_buffer(&minor, &fullName);
233 				return;
234 			}
235 			sprintf(uidStr, "%ld", aUser->pw_uid);
236 			uidPtr = uidStr;
237 		}
238 		else
239 			uidPtr = (char *)userUid;
240 
241 		if (userComment == NULL) {
242 			sprintf(comment, "%s, %s", name, mechOidStr);
243 			commentPtr = comment;
244 		} else
245 			commentPtr = (char *)userComment;
246 
247 		if (tableSource == GSSCRED_FLAT_FILE)
248 			retCode = file_addGssCredEntry(&hexBufDesc,
249 					uidPtr, commentPtr, &errDetail);
250 		else
251 			/* other backends (ldap, dss) coming soon */
252 			retCode	= 0;
253 
254 		if (!retCode) {
255 			fprintf(stderr, gettext("\nError adding user [%s]."),
256 				commentPtr);
257 
258 			if (errDetail) {
259 				fprintf(stderr, "\n%s\n", errDetail);
260 				free(errDetail);
261 				errDetail = NULL;
262 			}
263 		}
264 
265 		gss_release_buffer(&minor, &fullName);
266 		return;
267 	}
268 
269 	/*
270 	 * since no name specified, then we will load everyone from
271 	 * password table.  This means that -u and -o options are invalid.
272 	 * We just ignore it, but we could flag it as error.
273 	 */
274 	setpwent();
275 
276 	while ((aUser = getpwent()) != NULL) {
277 		hexBufDesc.length = sizeof (hexBuf);
278 		hexBufDesc.value = hexBuf;
279 
280 		if (!gsscred_MakeName(mechOid, aUser->pw_name,
281 			nameOidStr, &fullName)) {
282 			fprintf(stderr,
283 				gettext("\nError adding user [%s]."),
284 				aUser->pw_name);
285 			continue;
286 		}
287 
288 		if (!gsscred_AsHex(&fullName, &hexBufDesc)) {
289 			gss_release_buffer(&minor, &fullName);
290 			fprintf(stderr,
291 				gettext("\nInternal error.  "
292 					"Conversion to hex failed."));
293 			continue;
294 		}
295 
296 		sprintf(uidStr, "%ld", aUser->pw_uid);
297 		sprintf(comment, "%s, %s", aUser->pw_name, mechOidStr);
298 		if (tableSource == GSSCRED_FLAT_FILE)
299 			retCode = file_addGssCredEntry(&hexBufDesc,
300 					uidStr, comment, &errDetail);
301 		else
302 			retCode	= 0;
303 
304 		if (!retCode) {
305 			fprintf(stderr,
306 				gettext("\nError adding user [%s]."),
307 				comment);
308 
309 			if (errDetail) {
310 				fprintf(stderr, "\n%s\n", errDetail);
311 				free(errDetail);
312 				errDetail = NULL;
313 			}
314 		} else {
315 			count++;
316 			if ((count % 50) == 0)
317 				fprintf(stdout,
318 					gettext("\n[%d] users added..."),
319 					count);
320 		}
321 		gss_release_buffer(&minor, &fullName);
322 	}
323 	endpwent();
324 }  /* addUser */
325 
326 
327 /*
328  *  Handles the searching of the gsscred table.
329  */
330 static int listUsers(const char *name, const char *nameOidStr,
331 		const char *uidStr, const char *mechOidStr)
332 {
333 	GssCredEntry *entryPtr, *entryTmpPtr;
334 	char hexMech[256],
335 		hexName[(MAX_STR_LEN *2) + 1];
336 	gss_OID anOid = NULL, userMechOid = NULL;
337 	gss_OID_set mechSet = NULL;
338 	gss_buffer_desc inBufDesc = GSS_C_EMPTY_BUFFER,
339 		outBufDesc = GSS_C_EMPTY_BUFFER,
340 		searchName = GSS_C_EMPTY_BUFFER;
341 	int status = 1, numOfMechs, i;
342 	OM_uint32 minor;
343 	char *errDetails = NULL;
344 
345 	/* Do we need to convert the mechanism oid? */
346 	if (mechOidStr != NULL) {
347 
348 		if (__gss_mech_to_oid(mechOidStr, &userMechOid) !=
349 			GSS_S_COMPLETE) {
350 			fprintf(stderr,
351 				gettext("\nInvalid mechanism specified [%s]."),
352 				mechOidStr);
353 			return (0);
354 		}
355 		inBufDesc.length = userMechOid->length;
356 		inBufDesc.value = userMechOid->elements;
357 		outBufDesc.length = sizeof (hexMech);
358 		outBufDesc.value = hexMech;
359 
360 		if (!gsscred_AsHex(&inBufDesc, &outBufDesc)) {
361 			fprintf(stderr,
362 				gettext("\nInternal error.  "
363 					"Conversion to hex failed."));
364 			status = 0;
365 			goto cleanup;
366 		}
367 
368 	}	/* mechOidStr != NULL */
369 
370 	/* are we retrieving everyone ? or searching by mech ? */
371 	if ((name == NULL && uidStr == NULL && mechOidStr == NULL) ||
372 	    (name == NULL && uidStr == NULL)) {
373 
374 		if (tableSource == GSSCRED_FLAT_FILE) {
375 			file_listUsers(userMechOid, NULL, &errDetails);
376 
377 			if (errDetails) {
378 				fprintf(stderr,
379 					gettext("\nError searching gsscred"
380 						" table [%s]."),
381 					errDetails);
382 				free(errDetails);
383 				errDetails = NULL;
384 				return (0);
385 			}
386 			return (1);
387 		}
388 
389 	}
390 
391 	/* Are we searching by uid or uid and mech? */
392 	if (name == NULL && uidStr != NULL) {
393 
394 		if (tableSource == GSSCRED_FLAT_FILE)
395 			file_listUsers(userMechOid, uidStr, &errDetails);
396 		else {
397 			entryPtr = NULL;
398 			while (entryPtr != NULL) {
399 				fprintf(stdout, "\n%s\t%d\t%s",
400 					entryPtr->principal_name,
401 					entryPtr->unix_uid, entryPtr->comment);
402 				free(entryPtr->principal_name);
403 				free(entryPtr->comment);
404 				entryTmpPtr = entryPtr->next;
405 				free(entryPtr);
406 				entryPtr = entryTmpPtr;
407 			}
408 		}
409 
410 		/* check for any errors */
411 		if (errDetails) {
412 			fprintf(stderr,
413 				gettext("\nError searching gsscred table "
414 					"[%s]."),
415 				errDetails);
416 			free(errDetails);
417 			errDetails = NULL;
418 			status = 0;
419 		}
420 
421 		goto cleanup;
422 	}
423 
424 	/*
425 	 * We are searching by name;
426 	 * how many mechs must we check?
427 	 */
428 	if (mechOidStr == NULL) {
429 
430 		if (gss_indicate_mechs(&minor, &mechSet) != GSS_S_COMPLETE) {
431 			fprintf(stderr,
432 				gettext("\nInternal error.  "
433 					"GSS-API call failed."));
434 			return (0);
435 		}
436 		numOfMechs = mechSet->count;
437 	}
438 	else
439 		numOfMechs = 1;
440 
441 	/* now look through all the mechs searching */
442 	for (i = 0; i < numOfMechs; i++) {
443 
444 		if (mechOidStr == NULL) {
445 			anOid = &mechSet->elements[i];
446 			inBufDesc.length = anOid->length;
447 			inBufDesc.value = anOid->elements;
448 			outBufDesc.length = sizeof (hexMech);
449 			outBufDesc.value = hexMech;
450 
451 			if (!gsscred_AsHex(&inBufDesc, &outBufDesc))
452 				continue;
453 		} else
454 			anOid = userMechOid;
455 
456 		/* create a gss name */
457 		if (!gsscred_MakeName(anOid, name, nameOidStr, &outBufDesc))
458 			continue;
459 
460 		/* now convert it to hex, and find it */
461 		searchName.value = hexName;
462 		searchName.length = sizeof (hexName);
463 		status = gsscred_AsHex(&outBufDesc, &searchName);
464 		free(outBufDesc.value);
465 
466 		if (!status)
467 			continue;
468 
469 		if (tableSource == GSSCRED_FLAT_FILE)
470 			file_getGssCredEntry(&searchName, uidStr, &errDetails);
471 		else {
472 			entryPtr = NULL;  /* other backends coming soon */
473 			while (entryPtr != NULL) {
474 				fprintf(stdout, "\n%s\t%d\t%s",
475 					entryPtr->principal_name,
476 					entryPtr->unix_uid, entryPtr->comment);
477 				free(entryPtr->principal_name);
478 				free(entryPtr->comment);
479 				entryTmpPtr = entryPtr->next;
480 				free(entryPtr);
481 				entryPtr = entryTmpPtr;
482 			}
483 		}
484 
485 		/* any errors to display */
486 		if (errDetails) {
487 			fprintf(stderr,
488 				gettext("\nError searching gsscred table "
489 					"[%s]."),
490 				errDetails);
491 			free(errDetails);
492 			errDetails = NULL;
493 			status = 0;
494 		}
495 	}	/* for */
496 
497 cleanup:
498 	if (mechSet != NULL)
499 		gss_release_oid_set(&minor, &mechSet);
500 
501 	return (status);
502 }  /* listUsers */
503 
504 /*
505  * Performs additional handling while searching for users
506  * stored in the flat file table.
507  */
508 int
509 file_listUsers(const gss_OID mechOid, const char *unixUid,
510 		char **errDetails)
511 {
512 	gss_buffer_desc mechBufDesc = GSS_C_EMPTY_BUFFER,
513 		mechHexBufDesc = GSS_C_EMPTY_BUFFER;
514 	char mechBuf[128], mechHexBuf[256];
515 
516 	if (mechOid != NULL) {
517 		/* must make the name header whic contains mech oid */
518 		mechBufDesc.value = (void *) mechBuf;
519 		mechBufDesc.length = sizeof (mechBuf);
520 		mechHexBufDesc.value = (void*) mechHexBuf;
521 		mechHexBufDesc.length = sizeof (mechHexBuf);
522 
523 		if ((!gsscred_MakeNameHeader(mechOid, &mechBufDesc)) ||
524 			(!gsscred_AsHex(&mechBufDesc, &mechHexBufDesc))) {
525 			(*errDetails) = strdup(
526 					gettext("\nInternal error. "
527 					" Conversion to hex failed."));
528 			return (0);
529 		}
530 
531 		return (file_getGssCredEntry(&mechHexBufDesc,
532 				unixUid, errDetails));
533 	}
534 
535 	return (file_getGssCredEntry(NULL, unixUid, errDetails));
536 }  /* file_listUsers */
537 
538 
539 /*
540  *  Handles the deletion of users.
541  */
542 static int removeUsers(const char *name, const char *nameOidStr,
543 		const char *uidStr, const char *mechOidStr)
544 {
545 	char hexMech[256],
546 		hexName[(MAX_STR_LEN *2) + 1],
547 		*errDetails = NULL;
548 	gss_OID anOid = NULL, userMechOid = NULL;
549 	gss_OID_set mechSet = NULL;
550 	gss_buffer_desc inBufDesc = GSS_C_EMPTY_BUFFER,
551 		outBufDesc = GSS_C_EMPTY_BUFFER,
552 		searchName = GSS_C_EMPTY_BUFFER;
553 	int status = 0, numOfMechs, i;
554 	OM_uint32 minor;
555 
556 
557 	/* user deletion can only be performed by super user */
558 	if (getuid()) {
559 
560 		fprintf(stderr,
561 			gettext("\nUser deletion requires"
562 				" root privileges."));
563 		return (0);
564 	}
565 
566 	/* do we need to convert the mechanism oid? */
567 	if (mechOidStr != NULL) {
568 		if (__gss_mech_to_oid(mechOidStr, &userMechOid) !=
569 		GSS_S_COMPLETE) {
570 			fprintf(stderr,
571 				gettext("\nInvalid mechanism specified [%s]."),
572 				mechOidStr);
573 			return (0);
574 		}
575 
576 		inBufDesc.length = userMechOid->length;
577 		inBufDesc.value = userMechOid->elements;
578 		outBufDesc.length = sizeof (hexMech);
579 		outBufDesc.value = hexMech;
580 
581 		if (!gsscred_AsHex(&inBufDesc, &outBufDesc)) {
582 			fprintf(stderr,
583 				gettext("\nInternal error."
584 					"  Conversion to hex failed."));
585 			status = 0;
586 			goto cleanup;
587 		}
588 
589 	}	 /* mechOidStr != NULL */
590 
591 	/* are we deleting the entire table or an entire mech ? */
592 	if (name == NULL && uidStr == NULL) {
593 
594 		if (tableSource == GSSCRED_FLAT_FILE)
595 			status = file_removeUsers(userMechOid,
596 					NULL, &errDetails);
597 		else
598 			status = 0;
599 
600 		/* display any errors */
601 		if (errDetails) {
602 			fprintf(stderr,
603 				gettext("\nError deleting gsscred entry "
604 					"[%s]."),
605 				errDetails);
606 			free(errDetails);
607 			errDetails = NULL;
608 		}
609 		goto cleanup;
610 	}
611 
612 	/* are we deleting by uid or uid and mech? */
613 	if (name == NULL && uidStr != NULL) {
614 
615 		if (tableSource == GSSCRED_FLAT_FILE)
616 			status = file_removeUsers(userMechOid, uidStr,
617 						&errDetails);
618 		else
619 			status = 0;
620 
621 		/* check for any errors */
622 		if (errDetails) {
623 			fprintf(stderr,
624 				gettext("\nError deleting gsscred entry "
625 					"[%s]."),
626 				errDetails);
627 			free(errDetails);
628 			errDetails = NULL;
629 		}
630 		goto cleanup;
631 	}
632 
633 	/*
634 	 * We are deleting by name;
635 	 * how many mechs must we check?
636 	 */
637 	if (mechOidStr == NULL) {
638 
639 		if (gss_indicate_mechs(&minor, &mechSet) != GSS_S_COMPLETE) {
640 			fprintf(stderr,
641 				gettext("\nInternal error.  "
642 					"GSS-API call failed."));
643 			status = 0;
644 			goto cleanup;
645 		}
646 		numOfMechs = mechSet->count;
647 	}
648 	else
649 		numOfMechs = 1;
650 
651 	/* now look through all the mechs, deleting */
652 	for (i = 0; i < numOfMechs; i++) {
653 
654 		if (mechOidStr == NULL) {
655 			anOid = &mechSet->elements[i];
656 			inBufDesc.length = anOid->length;
657 			inBufDesc.value = anOid->elements;
658 			outBufDesc.length = sizeof (hexMech);
659 			outBufDesc.value = hexMech;
660 			if (!gsscred_AsHex(&inBufDesc, &outBufDesc))
661 				continue;
662 		} else
663 			anOid = userMechOid;
664 
665 		/* create a gss name */
666 		if (!gsscred_MakeName(anOid, name, nameOidStr, &outBufDesc))
667 			continue;
668 
669 		/* now convert it to hex, and delete it */
670 		searchName.value = hexName;
671 		searchName.length = sizeof (hexName);
672 		status = gsscred_AsHex(&outBufDesc, &searchName);
673 		free(outBufDesc.value);
674 
675 		if (!status)
676 			continue;
677 
678 		if (tableSource == GSSCRED_FLAT_FILE)
679 			status = file_deleteGssCredEntry(&searchName,
680 					uidStr, &errDetails);
681 		else
682 			status = 0;
683 
684 		/* check for any errors */
685 		if (errDetails) {
686 			fprintf(stderr,
687 				gettext("\nError deleting gsscred entry"
688 					" [%s]."),
689 				errDetails);
690 			free(errDetails);
691 			errDetails = NULL;
692 		}
693 	}	 /* for */
694 
695 cleanup:
696 	if (mechSet != NULL)
697 		gss_release_oid_set(&minor, &mechSet);
698 
699 	return (status);
700 }  /* removeUsers */
701 
702 
703 /*
704  * Performs additional handling while deleting users
705  * stored in the flat file table.
706  */
707 int file_removeUsers(const gss_OID mechOid, const char *unixUid,
708 		char **errDetails)
709 {
710 	gss_buffer_desc mechBufDesc = GSS_C_EMPTY_BUFFER,
711 		mechHexBufDesc = GSS_C_EMPTY_BUFFER;
712 	char mechBuf[128], mechHexBuf[256];
713 
714 	if (mechOid != NULL) {
715 		/*
716 		 * need to create the buffer header which contains
717 		 * the mechanism oid.
718 		 */
719 		mechBufDesc.value = (void*) mechBuf;
720 		mechBufDesc.length = sizeof (mechBuf);
721 		mechHexBufDesc.value = (void *) mechHexBuf;
722 		mechHexBufDesc.length = sizeof (mechHexBuf);
723 
724 		if ((!gsscred_MakeNameHeader(mechOid, &mechBufDesc)) ||
725 		    (!gsscred_AsHex(&mechBufDesc, &mechHexBufDesc))) {
726 			(*errDetails) = strdup(
727 				gettext("\nInternal error."
728 					"  Conversion to hex failed."));
729 			return (0);
730 		}
731 
732 		return (file_deleteGssCredEntry(&mechHexBufDesc, unixUid,
733 						errDetails));
734 	}
735 
736 	return (file_deleteGssCredEntry(NULL, unixUid, errDetails));
737 }  /* file_removeUsers */
738 
739 
740 /*
741  * Prints the usage string, and terminates.
742  */
743 static void usage(void)
744 {
745 
746 	fprintf(stderr,
747 		gettext("\nUsage:\t %s [-n user [-o oid] [-u uid]]"
748 			" [-c comment] -m mech -a"
749 			"\n\t %s [-n user [-o oid]] [-u uid] [-m mech] -r"
750 			"\n\t %s [-n user [-o oid]] [-u uid] [-m mech] -l\n"),
751 		PROG_NAME, PROG_NAME, PROG_NAME);
752 	exit(1);
753 }  /* usage */
754