1 /*
2 * System Security Services Daemon. NSS client interface
3 *
4 * Copyright (C) Simo Sorce 2007
5 *
6 * Winbind derived code:
7 * Copyright (C) Tim Potter 2000
8 * Copyright (C) Andrew Tridgell 2000
9 * Copyright (C) Andrew Bartlett 2002
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as
13 * published by the Free Software Foundation; either version 2.1 of the
14 * License, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
20 *
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with this program; if not, write to the Free Software
23 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 */
25
26 /* for struct ucred */
27 #define _GNU_SOURCE
28
29 #include <nss.h>
30 #include <security/pam_modules.h>
31 #include <errno.h>
32 #include <sys/types.h>
33 #include <sys/socket.h>
34 #include <sys/un.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <stdlib.h>
38 #include <stdint.h>
39 #include <string.h>
40 #include <fcntl.h>
41 #include <poll.h>
42
43 #include <libintl.h>
44 #define _(STRING) dgettext (PACKAGE, STRING)
45 #include "config.h"
46 #include "sss_cli.h"
47
48 /* common functions */
49
50 int sss_cli_sd = -1; /* the sss client socket descriptor */
51
52 static void sss_cli_close_socket(void)
53 {
54 if (sss_cli_sd != -1) {
55 close(sss_cli_sd);
56 sss_cli_sd = -1;
57 }
58 }
59
60 /* Requests:
61 *
62 * byte 0-3: 32bit unsigned with length (the complete packet length: 0 to X)
63 * byte 4-7: 32bit unsigned with command code
64 * byte 8-11: 32bit unsigned (reserved)
65 * byte 12-15: 32bit unsigned (reserved)
66 * byte 16-X: (optional) request structure associated to the command code used
67 */
68 static enum nss_status sss_nss_send_req(enum sss_cli_command cmd,
69 struct sss_cli_req_data *rd,
70 int *errnop)
71 {
72 uint32_t header[4];
73 size_t datasent;
74
75 header[0] = SSS_NSS_HEADER_SIZE + (rd?rd->len:0);
76 header[1] = cmd;
77 header[2] = 0;
78 header[3] = 0;
79
80 datasent = 0;
81
82 while (datasent < header[0]) {
83 struct pollfd pfd;
84 int rdsent;
85 int res, error;
86
87 *errnop = 0;
88 pfd.fd = sss_cli_sd;
89 pfd.events = POLLOUT;
90
91 do {
92 errno = 0;
93 res = poll(&pfd, 1, SSS_CLI_SOCKET_TIMEOUT);
94 error = errno;
95
96 /* If error is EINTR here, we'll try again
97 * If it's any other error, we'll catch it
98 * below.
99 */
100 } while (error == EINTR);
101
102 switch (res) {
103 case -1:
104 *errnop = error;
105 break;
106 case 0:
107 *errnop = ETIME;
108 break;
109 case 1:
110 if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
111 *errnop = EPIPE;
112 }
113 if (!(pfd.revents & POLLOUT)) {
114 *errnop = EBUSY;
115 }
116 break;
117 default: /* more than one avail ?? */
118 *errnop = EBADF;
119 break;
120 }
121 if (*errnop) {
122 sss_cli_close_socket();
123 return NSS_STATUS_UNAVAIL;
124 }
125
126 if (datasent < SSS_NSS_HEADER_SIZE) {
127 res = write(sss_cli_sd,
128 (char *)header + datasent,
129 SSS_NSS_HEADER_SIZE - datasent);
130 } else {
131 rdsent = datasent - SSS_NSS_HEADER_SIZE;
132 res = write(sss_cli_sd,
133 (const char *)rd->data + rdsent,
134 rd->len - rdsent);
135 }
136
137 if ((res == -1) || (res == 0)) {
138
139 /* Write failed */
140 sss_cli_close_socket();
141 *errnop = errno;
142 return NSS_STATUS_UNAVAIL;
143 }
144
145 datasent += res;
146 }
147
148 return NSS_STATUS_SUCCESS;
149 }
150
151 /* Replies:
152 *
153 * byte 0-3: 32bit unsigned with length (the complete packet length: 0 to X)
154 * byte 4-7: 32bit unsigned with command code
155 * byte 8-11: 32bit unsigned with the request status (server errno)
156 * byte 12-15: 32bit unsigned (reserved)
157 * byte 16-X: (optional) reply structure associated to the command code used
158 */
159
160 static enum nss_status sss_nss_recv_rep(enum sss_cli_command cmd,
161 uint8_t **buf, int *len,
162 int *errnop)
163 {
164 uint32_t header[4];
165 size_t datarecv;
166
167 header[0] = SSS_NSS_HEADER_SIZE; /* unitl we know the real lenght */
168 header[1] = 0;
169 header[2] = 0;
170 header[3] = 0;
171
172 datarecv = 0;
173 *buf = NULL;
174 *len = 0;
175 *errnop = 0;
176
At conditional (1): "datarecv < header[0]": Taking true branch.
At conditional (17): "datarecv < header[0]": Taking true branch.
| |
177 while (datarecv < header[0]) {
178 struct pollfd pfd;
179 int bufrecv;
180 int res, error;
181
182 pfd.fd = sss_cli_sd;
183 pfd.events = POLLIN;
184
185 do {
186 errno = 0;
187 res = poll(&pfd, 1, SSS_CLI_SOCKET_TIMEOUT);
188 error = errno;
189
190 /* If error is EINTR here, we'll try again
191 * If it's any other error, we'll catch it
192 * below.
193 */
At conditional (2): "error == 4": Taking true branch.
At conditional (3): "error == 4": Taking false branch.
At conditional (18): "error == 4": Taking true branch.
At conditional (19): "error == 4": Taking false branch.
| | | |
194 } while (error == EINTR);
195
196 switch (res) {
197 case -1:
198 *errnop = error;
199 break;
At conditional (20): switch case value "0": Taking true branch.
|
200 case 0:
201 *errnop = ETIME;
202 break;
At conditional (4): switch case value "1": Taking true branch.
|
203 case 1:
At conditional (5): "pfd.revents & 0x38": Taking false branch.
|
204 if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
205 *errnop = EPIPE;
206 }
At conditional (6): "!(pfd.revents & 1)": Taking false branch.
|
207 if (!(pfd.revents & POLLIN)) {
208 *errnop = EBUSY;
209 }
210 break;
211 default: /* more than one avail ?? */
212 *errnop = EBADF;
213 break;
214 }
At conditional (7): "*errnop": Taking false branch.
At conditional (21): "*errnop": Taking true branch.
| |
215 if (*errnop) {
216 sss_cli_close_socket();
217 return NSS_STATUS_UNAVAIL;
218 }
219
At conditional (8): "datarecv < 16UL": Taking true branch.
|
220 if (datarecv < SSS_NSS_HEADER_SIZE) {
221 res = read(sss_cli_sd,
222 (char *)header + datarecv,
223 SSS_NSS_HEADER_SIZE - datarecv);
224 } else {
225 bufrecv = datarecv - SSS_NSS_HEADER_SIZE;
226 res = read(sss_cli_sd,
227 (char *)(*buf) + bufrecv,
228 header[0] - datarecv);
229 }
230
At conditional (9): "res == -1": Taking false branch.
At conditional (10): "res == 0": Taking false branch.
| |
231 if ((res == -1) || (res == 0)) {
232
233 /* Read failed. I think the only useful thing
234 * we can do here is just return -1 and fail
235 * since the transaction has failed half way
236 * through. */
237
238 sss_cli_close_socket();
239 *errnop = errno;
240 return NSS_STATUS_UNAVAIL;
241 }
242
243 datarecv += res;
244
At conditional (11): "datarecv == 16UL": Taking true branch.
At conditional (12): "*len == 0": Taking true branch.
| |
245 if (datarecv == SSS_NSS_HEADER_SIZE && *len == 0) {
246 /* at this point recv buf is not yet
247 * allocated and the header has just
248 * been read, do checks and proceed */
At conditional (13): "header[2] != 0U": Taking false branch.
|
249 if (header[2] != 0) {
250 /* server side error */
251 sss_cli_close_socket();
252 *errnop = header[2];
253 if (*errnop == EAGAIN) {
254 return NSS_STATUS_TRYAGAIN;
255 } else {
256 return NSS_STATUS_UNAVAIL;
257 }
258 }
At conditional (14): "header[1] != cmd": Taking false branch.
|
259 if (header[1] != cmd) {
260 /* wrong command id */
261 sss_cli_close_socket();
262 *errnop = EBADMSG;
263 return NSS_STATUS_UNAVAIL;
264 }
At conditional (15): "header[0] > 16UL": Taking true branch.
|
265 if (header[0] > SSS_NSS_HEADER_SIZE) {
266 *len = header[0] - SSS_NSS_HEADER_SIZE;
Event alloc_fn: Storage is returned from allocation function "malloc". Event var_assign: Assigning: "*buf" = "malloc(*len)".
|
267 *buf = malloc(*len);
At conditional (16): "!*buf": Taking false branch.
|
268 if (!*buf) {
269 sss_cli_close_socket();
270 *errnop = ENOMEM;
271 return NSS_STATUS_UNAVAIL;
272 }
273 }
274 }
275 }
276
277 return NSS_STATUS_SUCCESS;
278 }
279
280 /* this function will check command codes match and returned length is ok */
281 /* repbuf and replen report only the data section not the header */
282 static enum nss_status sss_nss_make_request_nochecks(
283 enum sss_cli_command cmd,
284 struct sss_cli_req_data *rd,
285 uint8_t **repbuf, size_t *replen,
286 int *errnop)
287 {
288 enum nss_status ret;
289 uint8_t *buf = NULL;
290 int len = 0;
291
292 /* send data */
293 ret = sss_nss_send_req(cmd, rd, errnop);
At conditional (1): "ret != 1": Taking false branch.
|
294 if (ret != NSS_STATUS_SUCCESS) {
295 return ret;
296 }
297
298 /* data sent, now get reply */
Event alloc_arg: "sss_nss_recv_rep" allocates "buf". [model]
Also see events: [var_assign] | |
299 ret = sss_nss_recv_rep(cmd, &buf, &len, errnop);
At conditional (2): "ret != 1": Taking false branch.
|
300 if (ret != NSS_STATUS_SUCCESS) {
301 return ret;
302 }
303
304 /* we got through, now we have the custom data in buf if any,
305 * return it if requested */
At conditional (3): "repbuf": Taking true branch.
At conditional (4): "buf": Taking true branch.
| |
306 if (repbuf && buf) {
Event var_assign: Assigning: "*repbuf" = "buf".
Also see events: [alloc_arg] | |
307 *repbuf = buf;
At conditional (5): "replen": Taking true branch.
|
308 if (replen) {
309 *replen = len;
310 }
311 } else {
312 free(buf);
313 if (replen) {
314 *replen = 0;
315 }
316 }
317
318 return NSS_STATUS_SUCCESS;
319 }
320
321 /* GET_VERSION Reply:
322 * 0-3: 32bit unsigned version number
323 */
324
325 static int sss_nss_check_version(const char *socket_name)
326 {
327 uint8_t *repbuf;
328 size_t replen;
329 enum nss_status nret;
330 int errnop;
331 int res = NSS_STATUS_UNAVAIL;
332 uint32_t expected_version;
333 struct sss_cli_req_data req;
334
335 if (strcmp(socket_name, SSS_NSS_SOCKET_NAME) == 0) {
336 expected_version = SSS_NSS_PROTOCOL_VERSION;
337 } else if (strcmp(socket_name, SSS_PAM_SOCKET_NAME) == 0 ||
338 strcmp(socket_name, SSS_PAM_PRIV_SOCKET_NAME) == 0) {
339 expected_version = SSS_PAM_PROTOCOL_VERSION;
340 } else {
341 return NSS_STATUS_UNAVAIL;
342 }
343
344 req.len = sizeof(expected_version);
345 req.data = &expected_version;
346
347 nret = sss_nss_make_request_nochecks(SSS_GET_VERSION, &req,
348 &repbuf, &replen, &errnop);
349 if (nret != NSS_STATUS_SUCCESS) {
350 return nret;
351 }
352
353 if (!repbuf) {
354 return res;
355 }
356
357 if (((uint32_t *)repbuf)[0] == expected_version) {
358 res = NSS_STATUS_SUCCESS;
359 }
360
361 free(repbuf);
362 return res;
363 }
364
365 /* this 2 functions are adapted from samba3 winbinbd's wb_common.c */
366
367 /* Make sure socket handle isn't stdin (0), stdout(1) or stderr(2) by setting
368 * the limit to 3 */
369 #define RECURSION_LIMIT 3
370
371 static int make_nonstd_fd_internals(int fd, int limit)
372 {
373 int new_fd;
374 if (fd >= 0 && fd <= 2) {
375 #ifdef F_DUPFD
376 if ((new_fd = fcntl(fd, F_DUPFD, 3)) == -1) {
377 return -1;
378 }
379 /* Paranoia */
380 if (new_fd < 3) {
381 close(new_fd);
382 return -1;
383 }
384 close(fd);
385 return new_fd;
386 #else
387 if (limit <= 0)
388 return -1;
389
390 new_fd = dup(fd);
391 if (new_fd == -1)
392 return -1;
393
394 /* use the program stack to hold our list of FDs to close */
395 new_fd = make_nonstd_fd_internals(new_fd, limit - 1);
396 close(fd);
397 return new_fd;
398 #endif
399 }
400 return fd;
401 }
402
403 /****************************************************************************
404 Set a fd into blocking/nonblocking mode. Uses POSIX O_NONBLOCK if available,
405 else
406 if SYSV use O_NDELAY
407 if BSD use FNDELAY
408 Set close on exec also.
409 ****************************************************************************/
410
411 static int make_safe_fd(int fd)
412 {
413 int result, flags;
414 int new_fd = make_nonstd_fd_internals(fd, RECURSION_LIMIT);
415 if (new_fd == -1) {
416 close(fd);
417 return -1;
418 }
419
420 /* Socket should be nonblocking. */
421 #ifdef O_NONBLOCK
422 #define FLAG_TO_SET O_NONBLOCK
423 #else
424 #ifdef SYSV
425 #define FLAG_TO_SET O_NDELAY
426 #else /* BSD */
427 #define FLAG_TO_SET FNDELAY
428 #endif
429 #endif
430
431 if ((flags = fcntl(new_fd, F_GETFL)) == -1) {
432 close(new_fd);
433 return -1;
434 }
435
436 flags |= FLAG_TO_SET;
437 if (fcntl(new_fd, F_SETFL, flags) == -1) {
438 close(new_fd);
439 return -1;
440 }
441
442 #undef FLAG_TO_SET
443
444 /* Socket should be closed on exec() */
445 #ifdef FD_CLOEXEC
446 result = flags = fcntl(new_fd, F_GETFD, 0);
447 if (flags >= 0) {
448 flags |= FD_CLOEXEC;
449 result = fcntl( new_fd, F_SETFD, flags );
450 }
451 if (result < 0) {
452 close(new_fd);
453 return -1;
454 }
455 #endif
456 return new_fd;
457 }
458
459 static int sss_nss_open_socket(int *errnop, const char *socket_name)
460 {
461 struct sockaddr_un nssaddr;
462 int inprogress = 1;
463 int wait_time, sleep_time;
464 int sd;
465
466 memset(&nssaddr, 0, sizeof(struct sockaddr_un));
467 nssaddr.sun_family = AF_UNIX;
468 strncpy(nssaddr.sun_path, socket_name,
469 strlen(socket_name) + 1);
470
471 sd = socket(AF_UNIX, SOCK_STREAM, 0);
472 if (sd == -1) {
473 *errnop = errno;
474 return -1;
475 }
476
477 /* set as non-blocking, close on exec, and make sure standard
478 * descriptors are not used */
479 sd = make_safe_fd(sd);
480 if (sd == -1) {
481 *errnop = errno;
482 return -1;
483 }
484
485 /* this piece is adapted from winbind client code */
486 wait_time = 0;
487 sleep_time = 0;
488 while(inprogress) {
489 int connect_errno = 0;
490 socklen_t errnosize;
491 struct timeval tv;
492 fd_set w_fds;
493 int ret;
494
495 wait_time += sleep_time;
496
497 ret = connect(sd, (struct sockaddr *)&nssaddr,
498 sizeof(nssaddr));
499 if (ret == 0) {
500 return sd;
501 }
502
503 switch(errno) {
504 case EINPROGRESS:
505 FD_ZERO(&w_fds);
506 FD_SET(sd, &w_fds);
507 tv.tv_sec = SSS_CLI_SOCKET_TIMEOUT - wait_time;
508 tv.tv_usec = 0;
509
510 ret = select(sd + 1, NULL, &w_fds, NULL, &tv);
511
512 if (ret > 0) {
513 errnosize = sizeof(connect_errno);
514 ret = getsockopt(sd, SOL_SOCKET, SO_ERROR,
515 &connect_errno, &errnosize);
516 if (ret >= 0 && connect_errno == 0) {
517 return sd;
518 }
519 }
520 wait_time += SSS_CLI_SOCKET_TIMEOUT;
521 break;
522 case EAGAIN:
523 if (wait_time < SSS_CLI_SOCKET_TIMEOUT) {
524 sleep_time = rand() % 2 + 1;
525 sleep(sleep_time);
526 }
527 break;
528 default:
529 *errnop = errno;
530 inprogress = 0;
531 break;
532 }
533
534 if (wait_time >= SSS_CLI_SOCKET_TIMEOUT) {
535 inprogress = 0;
536 }
537 }
538
539 /* if we get here connect() failed or we timed out */
540
541 close(sd);
542 return -1;
543 }
544
545 static enum sss_status sss_cli_check_socket(int *errnop, const char *socket_name)
546 {
547 static pid_t mypid;
548 int mysd;
549
550 if (getpid() != mypid) {
551 sss_cli_close_socket();
552 mypid = getpid();
553 }
554
555 /* check if the socket has been closed on the other side */
556 if (sss_cli_sd != -1) {
557 struct pollfd pfd;
558 int res, error;
559
560 *errnop = 0;
561 pfd.fd = sss_cli_sd;
562 pfd.events = POLLIN | POLLOUT;
563
564 do {
565 errno = 0;
566 res = poll(&pfd, 1, SSS_CLI_SOCKET_TIMEOUT);
567 error = errno;
568
569 /* If error is EINTR here, we'll try again
570 * If it's any other error, we'll catch it
571 * below.
572 */
573 } while (error == EINTR);
574
575 switch (res) {
576 case -1:
577 *errnop = error;
578 break;
579 case 0:
580 *errnop = ETIME;
581 break;
582 case 1:
583 if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
584 *errnop = EPIPE;
585 }
586 if (!(pfd.revents & (POLLIN | POLLOUT))) {
587 *errnop = EBUSY;
588 }
589 break;
590 default: /* more than one avail ?? */
591 *errnop = EBADF;
592 break;
593 }
594 if (*errnop) {
595 sss_cli_close_socket();
596 return SSS_STATUS_UNAVAIL;
597 }
598
599 return SSS_STATUS_SUCCESS;
600 }
601
602 mysd = sss_nss_open_socket(errnop, socket_name);
603 if (mysd == -1) {
604 return SSS_STATUS_UNAVAIL;
605 }
606
607 sss_cli_sd = mysd;
608
609 if (sss_nss_check_version(socket_name) == NSS_STATUS_SUCCESS) {
610 return SSS_STATUS_SUCCESS;
611 }
612
613 sss_cli_close_socket();
614 *errnop = EFAULT;
615 return SSS_STATUS_UNAVAIL;
616 }
617
618 /* this function will check command codes match and returned length is ok */
619 /* repbuf and replen report only the data section not the header */
620 enum nss_status sss_nss_make_request(enum sss_cli_command cmd,
621 struct sss_cli_req_data *rd,
622 uint8_t **repbuf, size_t *replen,
623 int *errnop)
624 {
625 enum nss_status ret;
626 char *envval;
627
628 /* avoid looping in the nss daemon */
629 envval = getenv("_SSS_LOOPS");
At conditional (1): "envval": Taking true branch.
At conditional (2): "strcmp(envval, &"NO") == 0": Taking false branch.
| |
630 if (envval && strcmp(envval, "NO") == 0) {
631 return NSS_STATUS_NOTFOUND;
632 }
633
634 ret = sss_cli_check_socket(errnop, SSS_NSS_SOCKET_NAME);
At conditional (3): "ret != 1": Taking false branch.
|
635 if (ret != SSS_STATUS_SUCCESS) {
636 return NSS_STATUS_UNAVAIL;
637 }
638
Event alloc_arg: "sss_nss_make_request_nochecks" allocates "*repbuf". [model]
|
639 return sss_nss_make_request_nochecks(cmd, rd, repbuf, replen, errnop);
640 }
641
642 errno_t check_server_cred(int sockfd)
643 {
644 #ifdef HAVE_UCRED
645 int ret;
646 struct ucred server_cred;
647 socklen_t server_cred_len = sizeof(server_cred);
648
649 ret = getsockopt(sockfd, SOL_SOCKET, SO_PEERCRED, &server_cred,
650 &server_cred_len);
651 if (ret != 0) {
652 return errno;
653 }
654
655 if (server_cred_len != sizeof(struct ucred)) {
656 return ESSS_BAD_CRED_MSG;
657 }
658
659 if (server_cred.uid != 0 || server_cred.gid != 0) {
660 return ESSS_SERVER_NOT_TRUSTED;
661 }
662 #endif
663 return 0;
664 }
665 int sss_pam_make_request(enum sss_cli_command cmd,
666 struct sss_cli_req_data *rd,
667 uint8_t **repbuf, size_t *replen,
668 int *errnop)
669 {
670 int ret;
671 char *envval;
672 struct stat stat_buf;
673
674 /* avoid looping in the pam daemon */
675 envval = getenv("_SSS_LOOPS");
676 if (envval && strcmp(envval, "NO") == 0) {
677 return PAM_SERVICE_ERR;
678 }
679
680 /* only root shall use the privileged pipe */
681 if (getuid() == 0 && getgid() == 0) {
682 ret = stat(SSS_PAM_PRIV_SOCKET_NAME, &stat_buf);
683 if (ret != 0) return PAM_SERVICE_ERR;
684 if ( ! (stat_buf.st_uid == 0 &&
685 stat_buf.st_gid == 0 &&
686 S_ISSOCK(stat_buf.st_mode) &&
687 (stat_buf.st_mode & ~S_IFMT) == 0600 )) {
688 *errnop = ESSS_BAD_PRIV_SOCKET;
689 return PAM_SERVICE_ERR;
690 }
691
692 ret = sss_cli_check_socket(errnop, SSS_PAM_PRIV_SOCKET_NAME);
693 } else {
694 ret = stat(SSS_PAM_SOCKET_NAME, &stat_buf);
695 if (ret != 0) return PAM_SERVICE_ERR;
696 if ( ! (stat_buf.st_uid == 0 &&
697 stat_buf.st_gid == 0 &&
698 S_ISSOCK(stat_buf.st_mode) &&
699 (stat_buf.st_mode & ~S_IFMT) == 0666 )) {
700 *errnop = ESSS_BAD_PUB_SOCKET;
701 return PAM_SERVICE_ERR;
702 }
703
704 ret = sss_cli_check_socket(errnop, SSS_PAM_SOCKET_NAME);
705 }
706 if (ret != NSS_STATUS_SUCCESS) {
707 return PAM_SERVICE_ERR;
708 }
709
710 ret = check_server_cred(sss_cli_sd);
711 if (ret != 0) {
712 sss_cli_close_socket();
713 *errnop = ret;
714 return PAM_SERVICE_ERR;
715 }
716
717 return sss_nss_make_request_nochecks(cmd, rd, repbuf, replen, errnop);
718 }
719
720
721 const char *ssscli_err2string(int err)
722 {
723 const char *m;
724
725 switch(err) {
726 case ESSS_BAD_PRIV_SOCKET:
727 return _("Privileged socket has wrong ownership or permissions.");
728 break;
729 case ESSS_BAD_PUB_SOCKET:
730 return _("Public socket has wrong ownership or permissions.");
731 break;
732 case ESSS_BAD_CRED_MSG:
733 return _("Unexpected format of the server credential message.");
734 break;
735 case ESSS_SERVER_NOT_TRUSTED:
736 return _("SSSD is not run by root.");
737 break;
738 default:
739 m = strerror(err);
740 if (m == NULL) {
741 return _("An error occurred, but no description can be found.");
742 }
743 return m;
744 break;
745 }
746
747 return _("Unexpected error while looking for an error description");
748 }