modify usage() to optionally display help
[cfe_generate_password.git] / cfe_generate_password.c
1 static const char *title = \
2 "Generate Broadcom CFE seeds and passwords for many popular modem/router devices\n"
3 ;
4 static const float VERSION = 1.3f;
5
6 static const char *copyright = \
7 "Copyright 2015 TJ <hacker@iam.tj>\n"
8 "Licenced on the terms of the GNU General Public Licence version 3\n"
9 ;
10
11 static const char *help = \
12 "This tool can generate passwords for use with many devices that contain Broadcom Common Firmware Environment (CFE) bootbase which has a debug mode that is enabled using the 'ATEN 1 XXXXXXXX' command, where XXXXXXXX is an eight digit hexadecimal 'password'.\n\n"
13
14 "It is NOT necessary to have the device generate a 'seed' using 'ATSE [MODEL-ID]' because this tool can generate the seed from the device's first (base) MAC address.\n\n"
15
16 "When the device generates a seed it combines the number of seconds since 1970-01-01 00:00:00 with the router MAC address. Both are encoded in a single 6-byte hexadecimal number\n\n"
17
18 "Each value is truncated to its 3 least significant bytes so, for example:\n\n"
19
20 " $ date +%F.%T; echo \"obase=16;$(date +%s)\" | bc\n"
21 " 2016-03-26.23:06:32\n"
22 " 56F715F8\n\n"
23
24 "and MAC Address: EC:43:F6:46:C0:80\n\n"
25
26 "becomes F715F8 concatenated with 46C080\n\n"
27
28 " CFE> ATSE DSL-2492GNAU-B1BC\n"
29 " F715F846C080   <<<< last 3 bytes of MAC address\n"
30 " ^^^^^^\n"
31 "   seconds since 1970-01-01 00:00:00 (2016-03-26 23:06:32)\n\n"
32
33 "*NOTE: the default seed after power-up is 000000 so no time value needs to be specifed if 'ATSE <model-id-string>' has not been executed on the device.\n\n"
34
35 "Access to the device's console via a serial UART port, or a network telnet/ssh session, is required to enter the password.\n\n"
36
37 "So, for a device with base MAC address (reported by the CFE during boot) E.g:\n\n"
38
39 " CFE version 1.0.38-112.118 for BCM963268 (32bit,SP,BE)\n"
40 "  ...\n"
41 " Base MAC Address                  : ec:43:f6:46:c0:80\n"
42 "  ...\n"
43 " *** Press any key to stop auto run (1 seconds) ***\n"
44 " CFE>\n\n"
45
46 "Using this tool do:\n\n"
47
48 " ./cfe_gen_pass -s ec:43:f6:46:c0:80 -p\n\n"
49
50 " MAC address: ec:43:f6:46:c0:80 Timestamp: 000000 Seed: 00000046c080 Password: 10f0a563\n\n"
51
52 "And on the device do:\n\n"
53
54 " CFE> ATEN 1 10f0a563\n"
55 " OK\n"
56 " *** command status = 0\n\n"
57
58 "The tool can accept a timestamp as 8 hexadecimal characters (useful for testing the algorithm):\n\n"
59
60 " ./cfe_gen_pass -t 0FF020 -s ec:43:f6:46:c0:80 -p\n\n"
61
62 "MAC address: ec:43:f6:46:c0:80 Timestamp: 0FF020 Seed: 0FF02046c080 Password: 110f65a3\n\n"
63 ;
64
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <stdarg.h>
68 #include <string.h>
69 #include <time.h>
70
71 static const size_t TIMESTAMP_SIZE = 8;
72 static const size_t SEED_SIZE = 12;
73 static const size_t PASSWORD_SIZE = 8;
74 static const size_t MESSAGE_SIZE = 128;
75 static const size_t MAC_ADDR_SIZE = 17;
76 static const size_t DATESTRING_SIZE = 20;
77
78 static void
79 pr_usage(int verbose)
80 {
81   fprintf(stderr,
82     "Usage:\n"
83     "  -v                   show version\n"
84     "  -s 00:01:02:03:04:05 create seed from MAC address\n"
85     "  -t [00000000]        seconds since 1970-01-01 (defaults to NOW) \n"
86     "  -p [SEED]            generate password (with optional seed)\n\n"
87     "  E.g. -s 01:02:03:04:05 \n"
88     "       -s 01:02:03:04:05 -p\n"
89     "       -p 000000030405\n"
90     "\n"
91     "%s",
92     verbose ? help : ""
93   );
94 }
95
96 static void
97 pr_error_exit(unsigned int usage, const char *error, ...)
98 {
99  va_list args;
100  char error_message[MESSAGE_SIZE + 1];
101
102  if (!error) return;
103
104  va_start(args, error);
105  (void) vsnprintf(error_message, MESSAGE_SIZE + 1, error, args);
106  va_end(args);
107  fprintf(stderr, "Error: %s\n", error_message);
108
109  if (usage) pr_usage(usage);
110
111  exit(EXIT_FAILURE);
112 }
113
114 static const unsigned int passwords[8] = {
115   0x10F0A563,
116   0x887852B1,
117   0xC43C2958,
118   0x621E14AC,
119   0x310F0A56,
120   0x1887852B,
121   0x8C43C295,
122   0xC621E14A
123 };
124
125 static unsigned int
126 generate_seed(char *mac, char *timestamp, char *seed)
127 {
128   unsigned int result = 0;
129   if (mac && strlen(mac) == MAC_ADDR_SIZE) {
130     size_t i;
131     char *mac_ptr = mac + 9;
132     size_t ts_len = strlen(timestamp);
133     if (ts_len == TIMESTAMP_SIZE) {
134       for (i = 0; i < SEED_SIZE; ++i) {
135         if (i < 6) // first half of seed is the truncated timestamp
136           seed[i] = timestamp[i+2];
137         else { // second half is the truncated MAC address
138           if (*mac_ptr == ':' || *mac_ptr == '-')
139             ++mac_ptr;
140           seed[i] = *mac_ptr++;
141         }
142       }
143       result = 1;
144     } else
145       pr_error_exit(0, "Timestamp ('%s') should be %d hexadecimal characters e.g: 56F715F8", timestamp, TIMESTAMP_SIZE);
146   } else
147    pr_error_exit(0, "MAC-ADDR should be %d characters, e.g: 00:01:02:03:04:05", MAC_ADDR_SIZE);
148
149   return result;
150 }
151
152 static unsigned int
153 generate_pass(char *seed, char *password)
154 {
155   unsigned int result = 0;
156
157   if (seed && strlen(seed) == SEED_SIZE) {
158     unsigned int timestamp, byte, key, pass;
159     timestamp = byte = 0;
160     if(! sscanf(seed, "%06x", &timestamp))
161       pr_error_exit(1, "unable to parse seed's timestamp");
162     if (! sscanf(&seed[10], "%02x", &byte))
163       pr_error_exit(1, "unable to parse seed's MAC address");
164     key = byte & 0x07;
165     pass = (passwords[key] + timestamp) ^ timestamp;
166     snprintf(password, PASSWORD_SIZE + 1,  "%08x", pass);
167     result = 1;
168   } else
169     pr_error_exit(0, "Seed should be %d hex characters", SEED_SIZE);
170
171   return result;
172 }
173
174 int
175 main(int argc, char **argv, char **env)
176 {
177   int result = 0;
178
179   if (argc == 1) {
180     pr_usage(0);
181   }
182   else {
183     unsigned int arg;
184     char *MAC_ADDR = NULL;
185     char timestamp[TIMESTAMP_SIZE + 1];
186     char seed[SEED_SIZE + 1];
187     char password[PASSWORD_SIZE + 1];
188     char date_string[DATESTRING_SIZE + 1];
189     unsigned int opt_seed, opt_pass, opt_ts;
190     time_t ts = 0;
191     struct tm *t = NULL;
192     seed[0] = password[0] = timestamp[0] = 0;
193     seed[SEED_SIZE] = password[PASSWORD_SIZE] = 0;
194     opt_seed = opt_pass = opt_ts = 0;
195     strncpy(timestamp, "00000000", TIMESTAMP_SIZE + 1);
196
197     for (arg = 1; arg < (unsigned) argc; ++arg) {
198       size_t arg_len = strlen(argv[arg]);
199
200       if (argv[arg][0] == '-') {
201         switch (argv[arg][1]) {
202           case 's':
203             opt_seed = 1;
204             break;
205           case 'p':
206             opt_pass = 1;
207             break;
208           case 't':
209             opt_ts = 1;
210             break;
211           case 'v':
212             fprintf(stderr, "Version: %0.2f\n", VERSION);
213         }
214       } else if (opt_seed == 1) {
215         MAC_ADDR = argv[arg];
216         ++opt_seed;
217       } else if (opt_pass == 1 && opt_seed == 0) {
218         if (arg_len != SEED_SIZE)
219           pr_error_exit(1, "seed length must be %d characters", SEED_SIZE);
220
221         strncpy(seed, argv[arg], SEED_SIZE);
222         ++opt_pass;
223       } else if (opt_ts == 1) {
224         if (arg_len != TIMESTAMP_SIZE)
225           pr_error_exit(1, "timestamp length must be %d hexadecimal characters", TIMESTAMP_SIZE);
226
227         strncpy(timestamp, argv[arg], TIMESTAMP_SIZE);
228         ++opt_ts;
229       }
230     }
231     if (! opt_seed && ! opt_pass)
232       pr_usage(0);
233     else if (opt_seed && opt_seed != 2)
234       pr_error_exit(1, "seed requires MAC-ADDRESS");
235     else if (! opt_seed && opt_pass && opt_pass != 2)
236       pr_error_exit(1, "password on its own requires a pre-generated seed");
237     else if (opt_seed && opt_pass && opt_pass != 1)
238       pr_error_exit(1, "generating seed and password; cannot also accept pre-generated seed");
239     else if (opt_pass == 2 && opt_ts)
240       pr_error_exit(1, "seed already contains a timestamp; cannot over-ride it");
241     else if (opt_ts == 1 || opt_pass == 2) { // no timestamp provided; use NOW
242       ts = time(NULL);
243       if (ts)
244         snprintf(timestamp, TIMESTAMP_SIZE + 1, "%08lX", ts);
245     }
246
247     if (opt_pass == 2) { // try to figure out the correct date-time from the seed
248       // inherits the most significant 2 characters from the NOW time
249       strncpy(timestamp+2, seed, 6);
250       time_t tmp;
251       if (sscanf(timestamp, "%08lx", &tmp))
252         if (tmp > ts-3600 && tmp < ts+3600) // timestamps are so close they must be for the same date
253           ts = tmp;
254     }
255
256     if(opt_ts) { // ts needs to be valid to be converted to a time string
257       if(! sscanf(timestamp, "%08lx", &ts))
258         pr_error_exit(1, "converting timestamp string ('%s') to number", timestamp);
259     }
260     t = gmtime(&ts);
261     strftime(date_string, DATESTRING_SIZE, "%F %T", t);
262
263     if (opt_seed)
264       if (! generate_seed(MAC_ADDR, timestamp, seed))
265         pr_error_exit(1, "unable to generate seed; aborting");
266     if (opt_pass)
267       if (! generate_pass(seed, password))
268         pr_error_exit(0, "unable to generate password");
269
270     if (opt_seed || opt_pass)
271       printf("MAC address: %s Timestamp: %s (%s) Seed: %s Password: %s\n", MAC_ADDR, timestamp, date_string, seed, password);
272   }
273
274   return result;
275 }
276