]> andersk Git - openssh.git/blob - servconf.c
- OpenBSD CVS updates:
[openssh.git] / servconf.c
1 /*
2  *
3  * servconf.c
4  *
5  * Author: Tatu Ylonen <ylo@cs.hut.fi>
6  *
7  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
8  *                    All rights reserved
9  *
10  * Created: Mon Aug 21 15:48:58 1995 ylo
11  *
12  */
13
14 #include "includes.h"
15 RCSID("$Id$");
16
17 #include "ssh.h"
18 #include "servconf.h"
19 #include "xmalloc.h"
20 #include "compat.h"
21
22 /* add listen address */
23 void add_listen_addr(ServerOptions *options, char *addr);
24
25 /* Initializes the server options to their default values. */
26
27 void
28 initialize_server_options(ServerOptions *options)
29 {
30         memset(options, 0, sizeof(*options));
31         options->num_ports = 0;
32         options->ports_from_cmdline = 0;
33         options->listen_addrs = NULL;
34         options->host_key_file = NULL;
35         options->host_dsa_key_file = NULL;
36         options->pid_file = NULL;
37         options->server_key_bits = -1;
38         options->login_grace_time = -1;
39         options->key_regeneration_time = -1;
40         options->permit_root_login = -1;
41         options->ignore_rhosts = -1;
42         options->ignore_user_known_hosts = -1;
43         options->print_motd = -1;
44         options->check_mail = -1;
45         options->x11_forwarding = -1;
46         options->x11_display_offset = -1;
47         options->xauth_location = NULL;
48         options->strict_modes = -1;
49         options->keepalives = -1;
50         options->log_facility = (SyslogFacility) - 1;
51         options->log_level = (LogLevel) - 1;
52         options->rhosts_authentication = -1;
53         options->rhosts_rsa_authentication = -1;
54         options->rsa_authentication = -1;
55         options->dsa_authentication = -1;
56 #ifdef KRB4
57         options->kerberos_authentication = -1;
58         options->kerberos_or_local_passwd = -1;
59         options->kerberos_ticket_cleanup = -1;
60 #endif
61 #ifdef AFS
62         options->kerberos_tgt_passing = -1;
63         options->afs_token_passing = -1;
64 #endif
65         options->password_authentication = -1;
66 #ifdef SKEY
67         options->skey_authentication = -1;
68 #endif
69         options->permit_empty_passwd = -1;
70         options->use_login = -1;
71         options->num_allow_users = 0;
72         options->num_deny_users = 0;
73         options->num_allow_groups = 0;
74         options->num_deny_groups = 0;
75         options->ciphers = NULL;
76         options->protocol = SSH_PROTO_UNKNOWN;
77         options->gateway_ports = -1;
78         options->num_subsystems = 0;
79 }
80
81 void
82 fill_default_server_options(ServerOptions *options)
83 {
84         if (options->num_ports == 0)
85                 options->ports[options->num_ports++] = SSH_DEFAULT_PORT;
86         if (options->listen_addrs == NULL)
87                 add_listen_addr(options, NULL);
88         if (options->host_key_file == NULL)
89                 options->host_key_file = HOST_KEY_FILE;
90         if (options->host_dsa_key_file == NULL)
91                 options->host_dsa_key_file = HOST_DSA_KEY_FILE;
92         if (options->pid_file == NULL)
93                 options->pid_file = SSH_DAEMON_PID_FILE;
94         if (options->server_key_bits == -1)
95                 options->server_key_bits = 768;
96         if (options->login_grace_time == -1)
97                 options->login_grace_time = 600;
98         if (options->key_regeneration_time == -1)
99                 options->key_regeneration_time = 3600;
100         if (options->permit_root_login == -1)
101                 options->permit_root_login = 1;                 /* yes */
102         if (options->ignore_rhosts == -1)
103                 options->ignore_rhosts = 1;
104         if (options->ignore_user_known_hosts == -1)
105                 options->ignore_user_known_hosts = 0;
106         if (options->check_mail == -1)
107                 options->check_mail = 0;
108         if (options->print_motd == -1)
109                 options->print_motd = 1;
110         if (options->x11_forwarding == -1)
111                 options->x11_forwarding = 0;
112         if (options->x11_display_offset == -1)
113                 options->x11_display_offset = 10;
114 #ifdef XAUTH_PATH
115         if (options->xauth_location == NULL)
116                 options->xauth_location = XAUTH_PATH;
117 #endif /* XAUTH_PATH */
118         if (options->strict_modes == -1)
119                 options->strict_modes = 1;
120         if (options->keepalives == -1)
121                 options->keepalives = 1;
122         if (options->log_facility == (SyslogFacility) (-1))
123                 options->log_facility = SYSLOG_FACILITY_AUTH;
124         if (options->log_level == (LogLevel) (-1))
125                 options->log_level = SYSLOG_LEVEL_INFO;
126         if (options->rhosts_authentication == -1)
127                 options->rhosts_authentication = 0;
128         if (options->rhosts_rsa_authentication == -1)
129                 options->rhosts_rsa_authentication = 0;
130         if (options->rsa_authentication == -1)
131                 options->rsa_authentication = 1;
132         if (options->dsa_authentication == -1)
133                 options->dsa_authentication = 1;
134 #ifdef KRB4
135         if (options->kerberos_authentication == -1)
136                 options->kerberos_authentication = (access(KEYFILE, R_OK) == 0);
137         if (options->kerberos_or_local_passwd == -1)
138                 options->kerberos_or_local_passwd = 1;
139         if (options->kerberos_ticket_cleanup == -1)
140                 options->kerberos_ticket_cleanup = 1;
141 #endif /* KRB4 */
142 #ifdef AFS
143         if (options->kerberos_tgt_passing == -1)
144                 options->kerberos_tgt_passing = 0;
145         if (options->afs_token_passing == -1)
146                 options->afs_token_passing = k_hasafs();
147 #endif /* AFS */
148         if (options->password_authentication == -1)
149                 options->password_authentication = 1;
150 #ifdef SKEY
151         if (options->skey_authentication == -1)
152                 options->skey_authentication = 1;
153 #endif
154         if (options->permit_empty_passwd == -1)
155                 options->permit_empty_passwd = 0;
156         if (options->use_login == -1)
157                 options->use_login = 0;
158         if (options->protocol == SSH_PROTO_UNKNOWN)
159                 options->protocol = SSH_PROTO_1|SSH_PROTO_2;
160         if (options->gateway_ports == -1)
161                 options->gateway_ports = 0;
162 }
163
164 #define WHITESPACE " \t\r\n="
165
166 /* Keyword tokens. */
167 typedef enum {
168         sBadOption,             /* == unknown option */
169         sPort, sHostKeyFile, sServerKeyBits, sLoginGraceTime, sKeyRegenerationTime,
170         sPermitRootLogin, sLogFacility, sLogLevel,
171         sRhostsAuthentication, sRhostsRSAAuthentication, sRSAAuthentication,
172 #ifdef KRB4
173         sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup,
174 #endif
175 #ifdef AFS
176         sKerberosTgtPassing, sAFSTokenPassing,
177 #endif
178 #ifdef SKEY
179         sSkeyAuthentication,
180 #endif
181         sPasswordAuthentication, sListenAddress,
182         sPrintMotd, sIgnoreRhosts, sX11Forwarding, sX11DisplayOffset,
183         sStrictModes, sEmptyPasswd, sRandomSeedFile, sKeepAlives, sCheckMail,
184         sUseLogin, sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups,
185         sIgnoreUserKnownHosts, sHostDSAKeyFile, sCiphers, sProtocol, sPidFile,
186         sGatewayPorts, sDSAAuthentication, sXAuthLocation, sSubsystem
187 } ServerOpCodes;
188
189 /* Textual representation of the tokens. */
190 static struct {
191         const char *name;
192         ServerOpCodes opcode;
193 } keywords[] = {
194         { "port", sPort },
195         { "hostkey", sHostKeyFile },
196         { "hostdsakey", sHostDSAKeyFile },
197         { "pidfile", sPidFile },
198         { "serverkeybits", sServerKeyBits },
199         { "logingracetime", sLoginGraceTime },
200         { "keyregenerationinterval", sKeyRegenerationTime },
201         { "permitrootlogin", sPermitRootLogin },
202         { "syslogfacility", sLogFacility },
203         { "loglevel", sLogLevel },
204         { "rhostsauthentication", sRhostsAuthentication },
205         { "rhostsrsaauthentication", sRhostsRSAAuthentication },
206         { "rsaauthentication", sRSAAuthentication },
207         { "dsaauthentication", sDSAAuthentication },
208 #ifdef KRB4
209         { "kerberosauthentication", sKerberosAuthentication },
210         { "kerberosorlocalpasswd", sKerberosOrLocalPasswd },
211         { "kerberosticketcleanup", sKerberosTicketCleanup },
212 #endif
213 #ifdef AFS
214         { "kerberostgtpassing", sKerberosTgtPassing },
215         { "afstokenpassing", sAFSTokenPassing },
216 #endif
217         { "passwordauthentication", sPasswordAuthentication },
218 #ifdef SKEY
219         { "skeyauthentication", sSkeyAuthentication },
220 #endif
221         { "checkmail", sCheckMail },
222         { "listenaddress", sListenAddress },
223         { "printmotd", sPrintMotd },
224         { "ignorerhosts", sIgnoreRhosts },
225         { "ignoreuserknownhosts", sIgnoreUserKnownHosts },
226         { "x11forwarding", sX11Forwarding },
227         { "x11displayoffset", sX11DisplayOffset },
228         { "xauthlocation", sXAuthLocation },
229         { "strictmodes", sStrictModes },
230         { "permitemptypasswords", sEmptyPasswd },
231         { "uselogin", sUseLogin },
232         { "randomseed", sRandomSeedFile },
233         { "keepalive", sKeepAlives },
234         { "allowusers", sAllowUsers },
235         { "denyusers", sDenyUsers },
236         { "allowgroups", sAllowGroups },
237         { "denygroups", sDenyGroups },
238         { "ciphers", sCiphers },
239         { "protocol", sProtocol },
240         { "gatewayports", sGatewayPorts },
241         { "subsystem", sSubsystem },
242         { NULL, 0 }
243 };
244
245 /*
246  * Returns the number of the token pointed to by cp of length len. Never
247  * returns if the token is not known.
248  */
249
250 static ServerOpCodes
251 parse_token(const char *cp, const char *filename,
252             int linenum)
253 {
254         unsigned int i;
255
256         for (i = 0; keywords[i].name; i++)
257                 if (strcasecmp(cp, keywords[i].name) == 0)
258                         return keywords[i].opcode;
259
260         fprintf(stderr, "%s: line %d: Bad configuration option: %s\n",
261                 filename, linenum, cp);
262         return sBadOption;
263 }
264
265 /*
266  * add listen address
267  */
268 void
269 add_listen_addr(ServerOptions *options, char *addr)
270 {
271         extern int IPv4or6;
272         struct addrinfo hints, *ai, *aitop;
273         char strport[NI_MAXSERV];
274         int gaierr;
275         int i;
276
277         if (options->num_ports == 0)
278                 options->ports[options->num_ports++] = SSH_DEFAULT_PORT;
279         for (i = 0; i < options->num_ports; i++) {
280                 memset(&hints, 0, sizeof(hints));
281                 hints.ai_family = IPv4or6;
282                 hints.ai_socktype = SOCK_STREAM;
283                 hints.ai_flags = (addr == NULL) ? AI_PASSIVE : 0;
284                 snprintf(strport, sizeof strport, "%d", options->ports[i]);
285                 if ((gaierr = getaddrinfo(addr, strport, &hints, &aitop)) != 0)
286                         fatal("bad addr or host: %s (%s)\n",
287                             addr ? addr : "<NULL>",
288                             gai_strerror(gaierr));
289                 for (ai = aitop; ai->ai_next; ai = ai->ai_next)
290                         ;
291                 ai->ai_next = options->listen_addrs;
292                 options->listen_addrs = aitop;
293         }
294 }
295
296 /* Reads the server configuration file. */
297
298 void
299 read_server_config(ServerOptions *options, const char *filename)
300 {
301         FILE *f;
302         char line[1024];
303         char *cp, **charptr;
304         int linenum, *intptr, value;
305         int bad_options = 0;
306         ServerOpCodes opcode;
307         int i;
308
309         f = fopen(filename, "r");
310         if (!f) {
311                 perror(filename);
312                 exit(1);
313         }
314         linenum = 0;
315         while (fgets(line, sizeof(line), f)) {
316                 linenum++;
317                 cp = line + strspn(line, WHITESPACE);
318                 if (!*cp || *cp == '#')
319                         continue;
320                 cp = strtok(cp, WHITESPACE);
321                 opcode = parse_token(cp, filename, linenum);
322                 switch (opcode) {
323                 case sBadOption:
324                         bad_options++;
325                         continue;
326                 case sPort:
327                         /* ignore ports from configfile if cmdline specifies ports */
328                         if (options->ports_from_cmdline)
329                                 continue;
330                         if (options->listen_addrs != NULL)
331                                 fatal("%s line %d: ports must be specified before "
332                                     "ListenAdress.\n", filename, linenum);
333                         if (options->num_ports >= MAX_PORTS)
334                                 fatal("%s line %d: too many ports.\n",
335                                     filename, linenum);
336                         cp = strtok(NULL, WHITESPACE);
337                         if (!cp)
338                                 fatal("%s line %d: missing port number.\n",
339                                     filename, linenum);
340                         options->ports[options->num_ports++] = atoi(cp);
341                         break;
342
343                 case sServerKeyBits:
344                         intptr = &options->server_key_bits;
345 parse_int:
346                         cp = strtok(NULL, WHITESPACE);
347                         if (!cp) {
348                                 fprintf(stderr, "%s line %d: missing integer value.\n",
349                                         filename, linenum);
350                                 exit(1);
351                         }
352                         value = atoi(cp);
353                         if (*intptr == -1)
354                                 *intptr = value;
355                         break;
356
357                 case sLoginGraceTime:
358                         intptr = &options->login_grace_time;
359                         goto parse_int;
360
361                 case sKeyRegenerationTime:
362                         intptr = &options->key_regeneration_time;
363                         goto parse_int;
364
365                 case sListenAddress:
366                         cp = strtok(NULL, WHITESPACE);
367                         if (!cp)
368                                 fatal("%s line %d: missing inet addr.\n",
369                                     filename, linenum);
370                         add_listen_addr(options, cp);
371                         break;
372
373                 case sHostKeyFile:
374                 case sHostDSAKeyFile:
375                         charptr = (opcode == sHostKeyFile ) ?
376                             &options->host_key_file : &options->host_dsa_key_file;
377 parse_filename:
378                         cp = strtok(NULL, WHITESPACE);
379                         if (!cp) {
380                                 fprintf(stderr, "%s line %d: missing file name.\n",
381                                     filename, linenum);
382                                 exit(1);
383                         }
384                         if (*charptr == NULL)
385                                 *charptr = tilde_expand_filename(cp, getuid());
386                         break;
387
388                 case sPidFile:
389                         charptr = &options->pid_file;
390                         goto parse_filename;
391
392                 case sRandomSeedFile:
393                         fprintf(stderr, "%s line %d: \"randomseed\" option is obsolete.\n",
394                                 filename, linenum);
395                         cp = strtok(NULL, WHITESPACE);
396                         break;
397
398                 case sPermitRootLogin:
399                         intptr = &options->permit_root_login;
400                         cp = strtok(NULL, WHITESPACE);
401                         if (!cp) {
402                                 fprintf(stderr, "%s line %d: missing yes/without-password/no argument.\n",
403                                         filename, linenum);
404                                 exit(1);
405                         }
406                         if (strcmp(cp, "without-password") == 0)
407                                 value = 2;
408                         else if (strcmp(cp, "yes") == 0)
409                                 value = 1;
410                         else if (strcmp(cp, "no") == 0)
411                                 value = 0;
412                         else {
413                                 fprintf(stderr, "%s line %d: Bad yes/without-password/no argument: %s\n",
414                                         filename, linenum, cp);
415                                 exit(1);
416                         }
417                         if (*intptr == -1)
418                                 *intptr = value;
419                         break;
420
421                 case sIgnoreRhosts:
422                         intptr = &options->ignore_rhosts;
423 parse_flag:
424                         cp = strtok(NULL, WHITESPACE);
425                         if (!cp) {
426                                 fprintf(stderr, "%s line %d: missing yes/no argument.\n",
427                                         filename, linenum);
428                                 exit(1);
429                         }
430                         if (strcmp(cp, "yes") == 0)
431                                 value = 1;
432                         else if (strcmp(cp, "no") == 0)
433                                 value = 0;
434                         else {
435                                 fprintf(stderr, "%s line %d: Bad yes/no argument: %s\n",
436                                         filename, linenum, cp);
437                                 exit(1);
438                         }
439                         if (*intptr == -1)
440                                 *intptr = value;
441                         break;
442
443                 case sIgnoreUserKnownHosts:
444                         intptr = &options->ignore_user_known_hosts;
445                         goto parse_flag;
446
447                 case sRhostsAuthentication:
448                         intptr = &options->rhosts_authentication;
449                         goto parse_flag;
450
451                 case sRhostsRSAAuthentication:
452                         intptr = &options->rhosts_rsa_authentication;
453                         goto parse_flag;
454
455                 case sRSAAuthentication:
456                         intptr = &options->rsa_authentication;
457                         goto parse_flag;
458
459                 case sDSAAuthentication:
460                         intptr = &options->dsa_authentication;
461                         goto parse_flag;
462
463 #ifdef KRB4
464                 case sKerberosAuthentication:
465                         intptr = &options->kerberos_authentication;
466                         goto parse_flag;
467
468                 case sKerberosOrLocalPasswd:
469                         intptr = &options->kerberos_or_local_passwd;
470                         goto parse_flag;
471
472                 case sKerberosTicketCleanup:
473                         intptr = &options->kerberos_ticket_cleanup;
474                         goto parse_flag;
475 #endif
476
477 #ifdef AFS
478                 case sKerberosTgtPassing:
479                         intptr = &options->kerberos_tgt_passing;
480                         goto parse_flag;
481
482                 case sAFSTokenPassing:
483                         intptr = &options->afs_token_passing;
484                         goto parse_flag;
485 #endif
486
487                 case sPasswordAuthentication:
488                         intptr = &options->password_authentication;
489                         goto parse_flag;
490
491                 case sCheckMail:
492                         intptr = &options->check_mail;
493                         goto parse_flag;
494
495 #ifdef SKEY
496                 case sSkeyAuthentication:
497                         intptr = &options->skey_authentication;
498                         goto parse_flag;
499 #endif
500
501                 case sPrintMotd:
502                         intptr = &options->print_motd;
503                         goto parse_flag;
504
505                 case sX11Forwarding:
506                         intptr = &options->x11_forwarding;
507                         goto parse_flag;
508
509                 case sX11DisplayOffset:
510                         intptr = &options->x11_display_offset;
511                         goto parse_int;
512
513                 case sXAuthLocation:
514                         charptr = &options->xauth_location;
515                         goto parse_filename;
516                         
517                 case sStrictModes:
518                         intptr = &options->strict_modes;
519                         goto parse_flag;
520
521                 case sKeepAlives:
522                         intptr = &options->keepalives;
523                         goto parse_flag;
524
525                 case sEmptyPasswd:
526                         intptr = &options->permit_empty_passwd;
527                         goto parse_flag;
528
529                 case sUseLogin:
530                         intptr = &options->use_login;
531                         goto parse_flag;
532
533                 case sGatewayPorts:
534                         intptr = &options->gateway_ports;
535                         goto parse_flag;
536
537                 case sLogFacility:
538                         intptr = (int *) &options->log_facility;
539                         cp = strtok(NULL, WHITESPACE);
540                         value = log_facility_number(cp);
541                         if (value == (SyslogFacility) - 1)
542                                 fatal("%.200s line %d: unsupported log facility '%s'\n",
543                                     filename, linenum, cp ? cp : "<NONE>");
544                         if (*intptr == -1)
545                                 *intptr = (SyslogFacility) value;
546                         break;
547
548                 case sLogLevel:
549                         intptr = (int *) &options->log_level;
550                         cp = strtok(NULL, WHITESPACE);
551                         value = log_level_number(cp);
552                         if (value == (LogLevel) - 1)
553                                 fatal("%.200s line %d: unsupported log level '%s'\n",
554                                     filename, linenum, cp ? cp : "<NONE>");
555                         if (*intptr == -1)
556                                 *intptr = (LogLevel) value;
557                         break;
558
559                 case sAllowUsers:
560                         while ((cp = strtok(NULL, WHITESPACE))) {
561                                 if (options->num_allow_users >= MAX_ALLOW_USERS)
562                                         fatal("%s line %d: too many allow users.\n",
563                                             filename, linenum);
564                                 options->allow_users[options->num_allow_users++] = xstrdup(cp);
565                         }
566                         break;
567
568                 case sDenyUsers:
569                         while ((cp = strtok(NULL, WHITESPACE))) {
570                                 if (options->num_deny_users >= MAX_DENY_USERS)
571                                         fatal( "%s line %d: too many deny users.\n",
572                                             filename, linenum);
573                                 options->deny_users[options->num_deny_users++] = xstrdup(cp);
574                         }
575                         break;
576
577                 case sAllowGroups:
578                         while ((cp = strtok(NULL, WHITESPACE))) {
579                                 if (options->num_allow_groups >= MAX_ALLOW_GROUPS)
580                                         fatal("%s line %d: too many allow groups.\n",
581                                             filename, linenum);
582                                 options->allow_groups[options->num_allow_groups++] = xstrdup(cp);
583                         }
584                         break;
585
586                 case sDenyGroups:
587                         while ((cp = strtok(NULL, WHITESPACE))) {
588                                 if (options->num_deny_groups >= MAX_DENY_GROUPS)
589                                         fatal("%s line %d: too many deny groups.\n",
590                                             filename, linenum);
591                                 options->deny_groups[options->num_deny_groups++] = xstrdup(cp);
592                         }
593                         break;
594
595                 case sCiphers:
596                         cp = strtok(NULL, WHITESPACE);
597                         if (!cp)
598                                 fatal("%s line %d: Missing argument.", filename, linenum);
599                         if (!ciphers_valid(cp))
600                                 fatal("%s line %d: Bad SSH2 cipher spec '%s'.",
601                                     filename, linenum, cp ? cp : "<NONE>");
602                         if (options->ciphers == NULL)
603                                 options->ciphers = xstrdup(cp);
604                         break;
605
606                 case sProtocol:
607                         intptr = &options->protocol;
608                         cp = strtok(NULL, WHITESPACE);
609                         if (!cp)
610                                 fatal("%s line %d: Missing argument.", filename, linenum);
611                         value = proto_spec(cp);
612                         if (value == SSH_PROTO_UNKNOWN)
613                                 fatal("%s line %d: Bad protocol spec '%s'.",
614                                       filename, linenum, cp ? cp : "<NONE>");
615                         if (*intptr == SSH_PROTO_UNKNOWN)
616                                 *intptr = value;
617                         break;
618
619                 case sSubsystem:
620                         if(options->num_subsystems >= MAX_SUBSYSTEMS) {
621                                 fatal("%s line %d: too many subsystems defined.",
622                                       filename, linenum);
623                         }
624                         cp = strtok(NULL, WHITESPACE);
625                         if (!cp)
626                                 fatal("%s line %d: Missing subsystem name.",
627                                       filename, linenum);
628                         for (i = 0; i < options->num_subsystems; i++)
629                                 if(strcmp(cp, options->subsystem_name[i]) == 0)
630                                         fatal("%s line %d: Subsystem '%s' already defined.",
631                                               filename, linenum, cp);
632                         options->subsystem_name[options->num_subsystems] = xstrdup(cp);
633                         cp = strtok(NULL, WHITESPACE);
634                         if (!cp)
635                                 fatal("%s line %d: Missing subsystem command.",
636                                       filename, linenum);
637                         options->subsystem_command[options->num_subsystems] = xstrdup(cp);
638                         options->num_subsystems++;
639                         break;
640
641                 default:
642                         fprintf(stderr, "%s line %d: Missing handler for opcode %s (%d)\n",
643                                 filename, linenum, cp, opcode);
644                         exit(1);
645                 }
646                 if (strtok(NULL, WHITESPACE) != NULL) {
647                         fprintf(stderr, "%s line %d: garbage at end of line.\n",
648                                 filename, linenum);
649                         exit(1);
650                 }
651         }
652         fclose(f);
653         if (bad_options > 0) {
654                 fprintf(stderr, "%s: terminating, %d bad configuration options\n",
655                         filename, bad_options);
656                 exit(1);
657         }
658 }
This page took 0.095921 seconds and 5 git commands to generate.