Note appropriate krb5 build dependency
[openssh.git] / ssh-vulnkey.c
1 /*
2  * Copyright (c) 2008 Canonical Ltd.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24
25 #include "includes.h"
26
27 #include <sys/types.h>
28 #include <sys/stat.h>
29
30 #include <errno.h>
31 #include <string.h>
32 #include <stdio.h>
33 #include <fcntl.h>
34 #include <unistd.h>
35
36 #include <openssl/evp.h>
37
38 #include "xmalloc.h"
39 #include "ssh.h"
40 #include "log.h"
41 #include "key.h"
42 #include "authfile.h"
43 #include "pathnames.h"
44 #include "uidswap.h"
45 #include "misc.h"
46
47 extern char *__progname;
48
49 /* Default files to check */
50 static char *default_host_files[] = {
51         _PATH_HOST_RSA_KEY_FILE,
52         _PATH_HOST_DSA_KEY_FILE,
53         _PATH_HOST_KEY_FILE,
54         NULL
55 };
56 static char *default_files[] = {
57         _PATH_SSH_CLIENT_ID_RSA,
58         _PATH_SSH_CLIENT_ID_DSA,
59         _PATH_SSH_CLIENT_IDENTITY,
60         _PATH_SSH_USER_PERMITTED_KEYS,
61         _PATH_SSH_USER_PERMITTED_KEYS2,
62         NULL
63 };
64
65 static int verbosity = 0;
66
67 static int some_keys = 0;
68 static int some_unknown = 0;
69 static int some_compromised = 0;
70
71 static void
72 usage(void)
73 {
74         fprintf(stderr, "usage: %s [-aqv] [file ...]\n", __progname);
75         fprintf(stderr, "Options:\n");
76         fprintf(stderr, "  -a          Check keys of all users.\n");
77         fprintf(stderr, "  -q          Quiet mode.\n");
78         fprintf(stderr, "  -v          Verbose mode.\n");
79         exit(1);
80 }
81
82 static void
83 describe_key(const char *filename, u_long linenum, const char *msg,
84     Key *key, const char *comment, int min_verbosity)
85 {
86         char *fp;
87
88         fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
89         if (verbosity >= min_verbosity) {
90                 if (strchr(filename, ':'))
91                         printf("\"%s\"", filename);
92                 else
93                         printf("%s", filename);
94                 printf(":%lu: %s: %s %u %s %s\n", linenum, msg,
95                     key_type(key), key_size(key), fp, comment);
96         }
97         xfree(fp);
98 }
99
100 static int
101 do_key(const char *filename, u_long linenum,
102     Key *key, const char *comment)
103 {
104         Key *public;
105         int blacklist_status;
106         int ret = 1;
107
108         some_keys = 1;
109
110         public = key_demote(key);
111         if (public->type == KEY_RSA1)
112                 public->type = KEY_RSA;
113
114         blacklist_status = blacklisted_key(public, NULL);
115         if (blacklist_status == -1) {
116                 describe_key(filename, linenum,
117                     "Unknown (blacklist file not installed)", key, comment, 0);
118                 some_unknown = 1;
119         } else if (blacklist_status == 1) {
120                 describe_key(filename, linenum,
121                     "COMPROMISED", key, comment, 0);
122                 some_compromised = 1;
123                 ret = 0;
124         } else
125                 describe_key(filename, linenum,
126                     "Not blacklisted", key, comment, 1);
127
128         key_free(public);
129
130         return ret;
131 }
132
133 static int
134 do_filename(const char *filename, int quiet_open)
135 {
136         FILE *f;
137         char line[SSH_MAX_PUBKEY_BYTES];
138         char *cp;
139         u_long linenum = 0;
140         Key *key;
141         char *comment = NULL;
142         int found = 0, ret = 1;
143
144         /* Copy much of key_load_public's logic here so that we can read
145          * several keys from a single file (e.g. authorized_keys).
146          */
147
148         if (strcmp(filename, "-") != 0) {
149                 int save_errno;
150                 f = fopen(filename, "r");
151                 save_errno = errno;
152                 if (!f) {
153                         char pubfile[MAXPATHLEN];
154                         if (strlcpy(pubfile, filename, sizeof pubfile) <
155                             sizeof(pubfile) &&
156                             strlcat(pubfile, ".pub", sizeof pubfile) <
157                             sizeof(pubfile))
158                                 f = fopen(pubfile, "r");
159                 }
160                 errno = save_errno; /* earlier errno is more useful */
161                 if (!f) {
162                         if (!quiet_open)
163                                 perror(filename);
164                         return -1;
165                 }
166                 if (verbosity > 0)
167                         printf("# %s\n", filename);
168         } else
169                 f = stdin;
170         while (read_keyfile_line(f, filename, line, sizeof(line),
171                     &linenum) != -1) {
172                 int i;
173                 char *space;
174                 int type;
175                 char *end;
176
177                 /* Chop trailing newline. */
178                 i = strlen(line) - 1;
179                 if (line[i] == '\n')
180                         line[i] = '\0';
181
182                 /* Skip leading whitespace, empty and comment lines. */
183                 for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
184                         ;
185                 if (!*cp || *cp == '\n' || *cp == '#')
186                         continue;
187
188                 /* Cope with ssh-keyscan output and options in
189                  * authorized_keys files.
190                  */
191                 space = strchr(cp, ' ');
192                 if (!space)
193                         continue;
194                 *space = '\0';
195                 type = key_type_from_name(cp);
196                 *space = ' ';
197                 /* Leading number (RSA1) or valid type (RSA/DSA) indicates
198                  * that we have no host name or options to skip.
199                  */
200                 if ((strtol(cp, &end, 10) == 0 || *end != ' ') &&
201                     type == KEY_UNSPEC) {
202                         int quoted = 0;
203
204                         for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) {
205                                 if (*cp == '\\' && cp[1] == '"')
206                                         cp++;   /* Skip both */
207                                 else if (*cp == '"')
208                                         quoted = !quoted;
209                         }
210                         /* Skip remaining whitespace. */
211                         for (; *cp == ' ' || *cp == '\t'; cp++)
212                                 ;
213                         if (!*cp)
214                                 continue;
215                 }
216
217                 /* Read and process the key itself. */
218                 key = key_new(KEY_RSA1);
219                 if (key_read(key, &cp) == 1) {
220                         while (*cp == ' ' || *cp == '\t')
221                                 cp++;
222                         if (!do_key(filename, linenum,
223                             key, *cp ? cp : filename))
224                                 ret = 0;
225                         found = 1;
226                 } else {
227                         key_free(key);
228                         key = key_new(KEY_UNSPEC);
229                         if (key_read(key, &cp) == 1) {
230                                 while (*cp == ' ' || *cp == '\t')
231                                         cp++;
232                                 if (!do_key(filename, linenum,
233                                     key, *cp ? cp : filename))
234                                         ret = 0;
235                                 found = 1;
236                         }
237                 }
238                 key_free(key);
239         }
240         if (f != stdin)
241                 fclose(f);
242
243         if (!found && filename) {
244                 key = key_load_public(filename, &comment);
245                 if (key) {
246                         if (!do_key(filename, 1, key, comment))
247                                 ret = 0;
248                         found = 1;
249                 }
250                 if (comment)
251                         xfree(comment);
252         }
253
254         return ret;
255 }
256
257 static int
258 do_host(int quiet_open)
259 {
260         int i;
261         struct stat st;
262         int ret = 1;
263
264         for (i = 0; default_host_files[i]; i++) {
265                 if (stat(default_host_files[i], &st) < 0 && errno == ENOENT)
266                         continue;
267                 if (!do_filename(default_host_files[i], quiet_open))
268                         ret = 0;
269         }
270
271         return ret;
272 }
273
274 static int
275 do_user(const char *dir)
276 {
277         int i;
278         char *file;
279         struct stat st;
280         int ret = 1;
281
282         for (i = 0; default_files[i]; i++) {
283                 xasprintf(&file, "%s/%s", dir, default_files[i]);
284                 if (stat(file, &st) < 0 && errno == ENOENT) {
285                         xfree(file);
286                         continue;
287                 }
288                 if (!do_filename(file, 0))
289                         ret = 0;
290                 xfree(file);
291         }
292
293         return ret;
294 }
295
296 int
297 main(int argc, char **argv)
298 {
299         int opt, all_users = 0;
300         int ret = 1;
301         extern int optind;
302
303         /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
304         sanitise_stdfd();
305
306         __progname = ssh_get_progname(argv[0]);
307
308         SSLeay_add_all_algorithms();
309         log_init(argv[0], SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_USER, 1);
310
311         /* We don't need the RNG ourselves, but symbol references here allow
312          * ld to link us properly.
313          */
314         init_rng();
315         seed_rng();
316
317         while ((opt = getopt(argc, argv, "ahqv")) != -1) {
318                 switch (opt) {
319                 case 'a':
320                         all_users = 1;
321                         break;
322                 case 'q':
323                         verbosity--;
324                         break;
325                 case 'v':
326                         verbosity++;
327                         break;
328                 case 'h':
329                 default:
330                         usage();
331                 }
332         }
333
334         if (all_users) {
335                 struct passwd *pw;
336
337                 if (!do_host(0))
338                         ret = 0;
339
340                 while ((pw = getpwent()) != NULL) {
341                         if (pw->pw_dir) {
342                                 temporarily_use_uid(pw);
343                                 if (!do_user(pw->pw_dir))
344                                         ret = 0;
345                                 restore_uid();
346                         }
347                 }
348         } else if (optind == argc) {
349                 struct passwd *pw;
350
351                 if (!do_host(1))
352                         ret = 0;
353
354                 if ((pw = getpwuid(geteuid())) == NULL)
355                         fprintf(stderr, "No user found with uid %u\n",
356                             (u_int)geteuid());
357                 else {
358                         if (!do_user(pw->pw_dir))
359                                 ret = 0;
360                 }
361         } else {
362                 while (optind < argc)
363                         if (!do_filename(argv[optind++], 0))
364                                 ret = 0;
365         }
366
367         if (verbosity >= 0) {
368                 if (some_unknown) {
369                         printf("#\n");
370                         printf("# The status of some keys on your system is unknown.\n");
371                         printf("# You may need to install additional blacklist files.\n");
372                 }
373                 if (some_compromised) {
374                         printf("#\n");
375                         printf("# Some keys on your system have been compromised!\n");
376                         printf("# You must replace them using ssh-keygen(1).\n");
377                 }
378                 if (some_unknown || some_compromised) {
379                         printf("#\n");
380                         printf("# See the ssh-vulnkey(1) manual page for further advice.\n");
381                 } else if (some_keys && verbosity > 0) {
382                         printf("#\n");
383                         printf("# No blacklisted keys!\n");
384                 }
385         }
386
387         return ret;
388 }