Backport convert-conf fix from head; remove bogus /dev/poll assertion.
[ircu2.10.12-pk.git] / ircd / convert-conf.c
1 /* convert-conf.c - Convert ircu2.10.11 ircd.conf to ircu2.10.12 format.
2  * Copyright 2005 Michael Poole
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
17  * USA.
18  */
19
20 #include <ctype.h> /* tolower(), toupper(), isdigit() */
21 #include <stdio.h> /* *printf(), fgets() */
22 #include <stdlib.h> /* free(), strtol() */
23 #include <string.h> /* strlen(), memcpy(), strchr(), strspn() */
24
25 #define MAX_FIELDS 5
26
27 const char *admin_names[] = { "location", "contact", "contact", 0 },
28     *connect_names[] = { "host", "password", "name", "#port", "class", 0 },
29     *crule_names[] = { "server", "",  "rule", 0 },
30     *general_names[] = { "name", "vhost", "description", "", "#numeric", 0 },
31     *motd_names[] = { "host", "file", 0 },
32     *class_names[] = { "name", "#pingfreq", "#connectfreq", "#maxlinks", "#sendq", 0 },
33     *removed_features[] = { "VIRTUAL_HOST", "OPERS_SEE_IN_SECRET_CHANNELS", "LOCOP_SEE_IN_SECRET_CHANNELS", 0 };
34 char orig_line[512], line[512], dbuf[512];
35 char *fields[MAX_FIELDS + 1];
36 unsigned int nfields;
37 unsigned int lineno;
38
39 /*** GENERIC SUPPORT CODE ***/
40
41 static int split_line(char *input, char **output)
42 {
43     size_t quoted = 0, jj;
44     char *dest = dbuf, ch;
45
46     nfields = 1;
47     output[0] = dest;
48     while (*input != '\0' && *input != '#') switch (ch = *input++) {
49     case ':':
50         if (quoted)
51             *dest++ = ch;
52         else {
53             *dest++ = '\0';
54             if (nfields >= MAX_FIELDS)
55                 return nfields;
56             output[nfields++] = dest;
57         }
58         break;
59     case '\\':
60         switch (ch = *input++) {
61         case 'b': *dest++ = '\b'; break;
62         case 'f': *dest++ = '\f'; break;
63         case 'n': *dest++ = '\n'; break;
64         case 'r': *dest++ = '\r'; break;
65         case 't': *dest++ = '\t'; break;
66         case 'v': *dest++ = '\v'; break;
67         default: *dest++ = ch; break;
68         }
69         break;
70     case '"': quoted = !quoted; break;
71     default: *dest++ = ch;  break;
72     }
73
74     *dest = '\0';
75     for (jj = nfields; jj < MAX_FIELDS; ++jj)
76         output[jj] = dest;
77     return nfields;
78 }
79
80 static void simple_line(const char *block, const char **names, const char *extra)
81 {
82     size_t ii;
83
84     /* Print the current line and start the new block. */
85     fprintf(stdout, "# %s\n%s {\n", orig_line, block);
86
87     /* Iterate over fields in input line, formatting each. */
88     for (ii = 0; ii < nfields && names[ii]; ++ii) {
89         if (!fields[ii][0] || !names[ii][0])
90             continue;
91         else if (names[ii][0] == '#')
92             fprintf(stdout, "\t%s = %s;\n", names[ii] + 1, fields[ii]);
93         else
94             fprintf(stdout, "\t%s = \"%s\";\n", names[ii], fields[ii]);
95     }
96
97     /* Close the new block (including any fixed-form text). */
98     if (extra)
99         fprintf(stdout, "\t%s\n", extra);
100     fputs("};\n", stdout);
101 }
102
103 #define dupstring(TARGET, SOURCE) do { free(TARGET); if (SOURCE) { size_t len = strlen(SOURCE); (TARGET) = malloc(len+1); memcpy((TARGET), (SOURCE), len); } else (TARGET) = 0; } while(0)
104
105 /*** MANAGING LISTS OF STRINGS ***/
106
107 struct string_list {
108     struct string_list *next;
109     char *origin;
110     char *extra;
111     char value[1];
112 };
113
114 /** Find or insert the element from \a list that contains \a value.
115  * If an element of \a list already contains \a value, return it.
116  * Otherwise, append a new element to \a list containing \a value and
117  * return it.
118  * @param[in,out] list A list of strings.
119  * @param[in] value A string to search for.
120  * @return A string list element from \a list containing \a value.
121  */
122 static struct string_list *string_get(struct string_list **list, const char *value)
123 {
124     struct string_list *curr;
125     size_t len = strlen(value), ii;
126
127     while ((curr = *list)) {
128         for (ii = 0; tolower(curr->value[ii]) == tolower(value[ii]) && ii < len; ++ii) ;
129         if (curr->value[ii] == '\0' && value[ii] == '\0')
130             return curr;
131         list = &curr->next;
132     }
133
134     *list = calloc(1, sizeof(**list) + len);
135     memcpy((*list)->value, value, len);
136     return *list;
137 }
138
139 /*** SERVER CONNECTION RELATED CODE ***/
140
141 struct connect {
142     char *host;
143     char *password;
144     char *port;
145     char *class;
146     char *hub;
147     char *maximum;
148     struct connect *next;
149     struct string_list *origins;
150     char name[1];
151 } *connects;
152
153 static struct connect *get_connect(const char *name)
154 {
155     struct connect *conn;
156     size_t ii, nlen;
157
158     /* Look for a pre-existing connection with the same name. */
159     nlen = strlen(name);
160     for (conn = connects; conn; conn = conn->next)
161     {
162         for (ii = 0; tolower(name[ii]) == conn->name[ii] && ii < nlen; ++ii) ;
163         if (conn->name[ii] == '\0' && name[ii] == '\0')
164             break;
165     }
166
167     /* If none was found, create a new one. */
168     if (!conn)
169     {
170         conn = calloc(1, sizeof(*conn) + nlen);
171         for (ii = 0; ii < nlen; ++ii)
172             conn->name[ii] = tolower(name[ii]);
173         conn->next = connects;
174         connects = conn;
175     }
176
177     /* Return the connection. */
178     return conn;
179 }
180
181 static void do_connect(void)
182 {
183     struct connect *conn = get_connect(fields[2]);
184     dupstring(conn->host, fields[0]);
185     dupstring(conn->password, fields[1]);
186     dupstring(conn->port, fields[3]);
187     dupstring(conn->class, fields[4]);
188     string_get(&conn->origins, orig_line);
189 }
190
191 static void do_hub(void)
192 {
193     struct connect *conn = get_connect(fields[2]);
194     dupstring(conn->hub, fields[0]);
195     dupstring(conn->maximum, fields[3]);
196     string_get(&conn->origins, orig_line);
197 }
198
199 static void do_leaf(void)
200 {
201     struct connect *conn = get_connect(fields[2]);
202     free(conn->hub);
203     conn->hub = 0;
204     string_get(&conn->origins, orig_line);
205 }
206
207 static void finish_connects(void)
208 {
209     struct connect *conn;
210     struct string_list *sl;
211
212     for (conn = connects; conn; conn = conn->next)
213     {
214         for (sl = conn->origins; sl; sl = sl->next)
215             fprintf(stdout, "# %s\n", sl->value);
216         if (conn->name == NULL
217             || conn->host == NULL
218             || conn->password == NULL
219             || conn->class == NULL)
220         {
221             fprintf(stderr, "H:line missing C:line for %s\n",sl->value);
222             continue;
223         }
224
225         fprintf(stdout,
226                 "Connect {\n\tname =\"%s\";\n\thost = \"%s\";\n"
227                 "\tpassword = \"%s\";\n\tclass = \"%s\";\n",
228                 conn->name, conn->host, conn->password, conn->class);
229         if (conn->port && conn->port[0] != '\0')
230             fprintf(stdout, "\tport = %s;\n", conn->port);
231         else
232             fprintf(stdout,
233                     "# Every Connect block should have a port number.\n"
234                     "# To prevent autoconnects, set autoconnect = no.\n"
235                     "#\tport = 4400;\n"
236                     "\tautoconnect = no;\n");
237         if (conn->maximum && conn->maximum[0] != '\0')
238             fprintf(stdout, "\tmaxhops = %s;\n", conn->maximum);
239         if (conn->hub && conn->hub[0] != '\0')
240             fprintf(stdout, "\thub = \"%s\";\n", conn->hub);
241         fprintf(stdout, "};\n\n");
242
243     }
244 }
245
246 /*** FEATURE MANAGEMENT CODE ***/
247
248 struct feature {
249     struct string_list *values;
250     struct string_list *origins;
251     struct feature *next;
252     char name[1];
253 } *features;
254
255 struct remapped_feature {
256     const char *name;
257     const char *privilege;
258     int flags; /* 2 = global, 1 = local */
259     struct feature *feature;
260 } remapped_features[] = {
261     /* Specially handled privileges: If you change the index of
262      * anything with NULL privilege, change the code in
263      * finish_operators() to match!
264      */
265     { "CRYPT_OPER_PASSWORD", NULL, 0, 0 }, /* default: true */
266     { "OPER_KILL", NULL, 2, 0 }, /* default: true */
267     { "LOCAL_KILL_ONLY", NULL, 2, 0 }, /* default: false */
268     /* remapped features that affect all opers  */
269     { "OPER_NO_CHAN_LIMIT", "chan_limit", 3, 0 },
270     { "OPER_MODE_LCHAN", "mode_lchan", 3, 0 },
271     { "OPER_WALK_THROUGH_LMODES", "walk_lchan", 3, 0 },
272     { "NO_OPER_DEOP_LCHAN", "deop_lchan", 3, 0 },
273     { "SHOW_INVISIBLE_USERS", "show_invis", 3, 0 },
274     { "SHOW_ALL_INVISIBLE_USERS", "show_all_invis", 3, 0 },
275     { "UNLIMIT_OPER_QUERY", "unlimit_query", 3, 0 },
276     /* remapped features affecting only global opers */
277     { "OPER_REHASH", "rehash", 2, 0 },
278     { "OPER_RESTART", "restart", 2, 0 },
279     { "OPER_DIE", "die", 2, 0 },
280     { "OPER_GLINE", "gline", 2, 0 },
281     { "OPER_LGLINE", "local_gline", 2, 0 },
282     { "OPER_JUPE", "jupe", 2, 0 },
283     { "OPER_LJUPE", "local_jupe", 2, 0 },
284     { "OPER_OPMODE", "opmode", 2, 0 },
285     { "OPER_LOPMODE", "local_opmode", 2, 0 },
286     { "OPER_FORCE_OPMODE", "force_opmode", 2, 0 },
287     { "OPER_FORCE_LOPMODE", "force_local_opmode", 2, 0 },
288     { "OPER_BADCHAN", "badchan", 2, 0 },
289     { "OPER_LBADCHAN", "local_badchan", 2, 0 },
290     { "OPER_SET", "set", 2, 0 },
291     { "OPER_WIDE_GLINE", "wide_gline", 2, 0 },
292     /* remapped features affecting only local opers */
293     { "LOCOP_KILL", "kill", 1, 0 },
294     { "LOCOP_REHASH", "rehash", 1, 0 },
295     { "LOCOP_RESTART", "restart", 1, 0 },
296     { "LOCOP_DIE", "die", 1, 0 },
297     { "LOCOP_LGLINE", "local_gline", 1, 0 },
298     { "LOCOP_LJUPE", "local_jupe", 1, 0 },
299     { "LOCOP_LOPMODE", "local_opmode", 1, 0 },
300     { "LOCOP_FORCE_LOPMODE", "force_local_opmode", 1, 0 },
301     { "LOCOP_LBADCHAN", "local_badchan", 1, 0 },
302     { "LOCOP_WIDE_GLINE", "wide_gline", 1, 0 },
303     { 0, 0, 0, 0 }
304 };
305
306 static void do_feature(void)
307 {
308     struct feature *feat;
309     size_t ii;
310
311     ii = strlen(fields[0]);
312     feat = calloc(1, sizeof(*feat) + ii);
313     while (ii-- > 0)
314         feat->name[ii] = toupper(fields[0][ii]);
315     feat->next = features;
316     features = feat;
317     string_get(&feat->origins, orig_line);
318     for (ii = 1; fields[ii] && fields[ii][0]; ++ii)
319         string_get(&feat->values, fields[ii]);
320 }
321
322 static void finish_features(void)
323 {
324     struct remapped_feature *rmf;
325     struct string_list *sl;
326     struct feature *feat;
327     size_t ii;
328
329     fputs("Features {\n", stdout);
330     fputs("\t\"OPLEVELS\" = \"FALSE\";\n", stdout);
331     fputs("\t\"ZANNELS\" = \"FALSE\";\n", stdout);
332
333     for (feat = features; feat; feat = feat->next) {
334         /* Display the original feature line we are talking about. */
335         for (sl = feat->origins; sl; sl = sl->next)
336             fprintf(stdout, "# %s\n", sl->value);
337
338         /* See if the feature was remapped to an oper privilege. */
339         for (rmf = remapped_features; rmf->name; rmf++)
340             if (0 == strcmp(feat->name, rmf->name))
341                 break;
342         if (rmf->name) {
343             rmf->feature = feat;
344             fprintf(stdout, "# Above feature mapped to an oper privilege.\n");
345             continue;
346         }
347
348         /* Was it removed? */
349         for (ii = 0; removed_features[ii]; ++ii)
350             if (0 == strcmp(feat->name, removed_features[ii]))
351                 break;
352         if (removed_features[ii]) {
353             fprintf(stdout, "# Above feature no longer exists.\n");
354             continue;
355         }
356
357         /* Wasn't remapped, wasn't removed: print it out. */
358         fprintf(stdout, "\t\"%s\" =", feat->name);
359         for (sl = feat->values; sl; sl = sl->next)
360             fprintf(stdout, " \"%s\"", sl->value);
361         fprintf(stdout, ";\n");
362     }
363     fputs("};\n\n", stdout);
364
365 }
366
367 /*** OPERATOR BLOCKS ***/
368
369 struct operator {
370     char *name;
371     char *host;
372     char *password;
373     char *class;
374     char *origin;
375     int is_local;
376     struct operator *next;
377 } *operators;
378
379 static void do_operator(int is_local)
380 {
381     struct operator *oper;
382
383     oper = calloc(1, sizeof(*oper));
384     dupstring(oper->host, fields[0]);
385     dupstring(oper->password, fields[1]);
386     dupstring(oper->name, fields[2]);
387     dupstring(oper->class, fields[4]);
388     dupstring(oper->origin, orig_line);
389     oper->is_local = is_local;
390     oper->next = operators;
391     operators = oper;
392 }
393
394 static void finish_operators(void)
395 {
396     struct remapped_feature *remap;
397     struct operator *oper;
398     struct feature *feat;
399     char *pw_salt = "";
400     int global_kill = 0, mask = 0;
401     size_t ii;
402
403     if ((feat = remapped_features[0].feature) && feat->values
404         && 0 == strcmp(feat->values->value, "FALSE"))
405         pw_salt = "$PLAIN$";
406
407     if ((feat = remapped_features[1].feature) && feat->values
408         && 0 == strcmp(feat->values->value, "FALSE"))
409         global_kill = 1;
410     else if ((feat = remapped_features[2].feature) && feat->values
411         && 0 == strcmp(feat->values->value, "FALSE"))
412         global_kill = 2;
413
414     for (oper = operators; oper; oper = oper->next) {
415         fprintf(stdout, "# %s\nOperator {\n\tname = \"%s\";\n"
416                 "\thost = \"%s\";\n\tpassword = \"%s%s\";\n"
417                 "\tclass = \"%s\";\n",
418                 oper->origin, oper->name, oper->host, pw_salt,
419                 oper->password, oper->class);
420         if (oper->is_local) {
421             fputs("\tlocal = yes;\n", stdout);
422             mask = 1;
423         } else {
424             fputs("\tlocal = no;\n", stdout);
425             if (global_kill == 1)
426                 fputs("\tkill = no;\n\tlocal_kill = no;\n", stdout);
427             else if (global_kill == 2)
428                 fputs("\tkill = no;\n\tlocal_kill = yes;\n", stdout);
429             mask = 2;
430         }
431         for (ii = 0; (remap = &remapped_features[ii++])->name; ) {
432             if (!remap->feature || !remap->privilege
433                 || !remap->feature->values || !(remap->flags & mask))
434                 continue;
435             fprintf(stdout, "\t%s = %s;\n", remap->privilege,
436                     strcmp(remap->feature->values->value, "TRUE") ? "no" : "yes");
437         }
438         fputs("};\n\n", stdout);
439     }
440 }
441
442 /*** OTHER CONFIG TRANSFORMS ***/
443
444 static void do_kill(void)
445 {
446     const char *host = fields[0], *reason = fields[1], *user = fields[2];
447
448     if (!memcmp(host, "$R", 3)) {
449         fprintf(stderr, "Empty realname K: line at line %u.\n", lineno);
450         return;
451     }
452
453     /* Print the current line and start the new block. */
454     fprintf(stdout, "# %s\nKill {\n", orig_line);
455
456     /* Translate the user-matching portions. */
457     if (host[0] == '$' && host[1] == 'R') {
458         /* Realname kill, possibly with a username */
459         fprintf(stdout, "\trealname = \"%s\";\n", host + 2);
460         if (user[0] != '\0' && (user[0] != '*' || user[1] != '\0'))
461             fprintf(stdout, "\thost = \"%s@*\";\n", user);
462     } else {
463         /* Normal host or IP-based kill */
464         if (user[0] != '\0' && (user[0] != '*' || user[1] != '\0'))
465             fprintf(stdout, "\thost = \"%s@%s\";\n", user, host);
466         else
467             fprintf(stdout, "\thost = \"%s\";\n", host);
468     }
469
470     /* Translate the reason section. */
471     if (reason[0] == '!')
472         fprintf(stdout, "\tfile = \"%s\";\n", reason + 1);
473     else
474         fprintf(stdout, "\treason = \"%s\";\n", reason);
475
476     /* Close the block. */
477     fprintf(stdout, "};\n");
478 }
479
480 static void do_port(void)
481 {
482     const char *ipmask = fields[0], *iface = fields[1], *flags = fields[2], *port = fields[3];
483
484     /* Print the current line and start the new block. */
485     fprintf(stdout, "# %s\nPort {\n", orig_line);
486
487     /* Print the easy fields. */
488     fprintf(stdout, "\tport = %s;\n", port);
489     if (iface && iface[0] != '\0')
490         fprintf(stdout, "\tvhost = \"%s\";\n", iface);
491     if (ipmask && ipmask[0] != '\0')
492         fprintf(stdout, "\tmask = \"%s\";\n", ipmask);
493
494     /* Translate flag field. */
495     while (*flags) switch (*flags++) {
496     case 'C': case 'c': /* client port is default state */; break;
497     case 'S': case 's': fprintf(stdout, "\tserver = yes;\n"); break;
498     case 'H': case 'h': fprintf(stdout, "\thidden = yes;\n"); break;
499     }
500
501     /* Close the block. */
502     fprintf(stdout, "};\n");
503 }
504
505 struct string_list *quarantines;
506
507 static void do_quarantine(void)
508 {
509     struct string_list *q;
510     q = string_get(&quarantines, fields[0]);
511     dupstring(q->origin, orig_line);
512     dupstring(q->extra, fields[1]);
513 }
514
515 static void finish_quarantines(void)
516 {
517     struct string_list *sl;
518
519     if (quarantines)
520     {
521         fputs("Quarantine {\n", stdout);
522         for (sl = quarantines; sl; sl = sl->next)
523             fprintf(stdout, "# %s\n\t\"%s\" = \"%s\";\n", sl->origin, sl->value, sl->extra);
524         fputs("};\n\n", stdout);
525     }
526 }
527
528 static void do_uworld(void)
529 {
530     fprintf(stdout, "# %s\n", orig_line);
531     if (fields[0] && fields[0][0])
532         fprintf(stdout, "Uworld { name = \"%s\"; };\n", fields[0]);
533     if (fields[1] && fields[1][0])
534         fprintf(stdout, "Jupe { nick = \"%s\"; };\n", fields[1]);
535 }
536
537 static void emit_client(const char *mask, const char *passwd, const char *class, long maxlinks, int is_ip)
538 {
539     char *delim;
540     size_t len;
541
542     delim = strchr(mask, '@');
543     if (delim) {
544         *delim++ = '\0';
545         if (is_ip) {
546             len = strspn(delim, "0123456789.*");
547             if (delim[len]) {
548                 fprintf(stderr, "Invalid IP mask on line %u.\n", lineno);
549                 return;
550             }
551             fprintf(stdout, "Client {\n\tusername = \"%s\";\n\tip = \"%s\";\n", mask, delim);
552         } else {
553             fprintf(stdout, "Client {\n\tusername =\"%s\";\n\thost = \"%s\";\n", mask, delim);
554         }
555     } else if (is_ip) {
556         len = strspn(mask, "0123456789.*");
557         if (mask[len])
558             return;
559         fprintf(stdout, "Client {\n\tip = \"%s\";\n", mask);
560     } else {
561         if (!strchr(mask, '.') && !strchr(mask, '*'))
562             return;
563         fprintf(stdout, "Client {\n\thost = \"%s\";\n", mask);
564     }
565
566     if (passwd)
567         fprintf(stdout, "\tpassword = \"%s\";\n", passwd);
568
569     if (maxlinks >= 0)
570         fprintf(stdout, "\tmaxlinks = %ld;\n", maxlinks);
571
572     fprintf(stdout, "\tclass = \"%s\";\n};\n", class);
573 }
574
575 static void do_client(void)
576 {
577     char *passwd = NULL, *delim;
578     long maxlinks;
579
580     /* Print the current line. */
581     fprintf(stdout, "# %s\n", orig_line);
582
583     /* See if the password is really a maxlinks count. */
584     maxlinks = strtol(fields[1], &delim, 10);
585     if (fields[1][0] == '\0')
586         maxlinks = -1;
587     else if (maxlinks < 0 || maxlinks > 99 || *delim != '\0')
588         passwd = fields[1];
589
590     /* Translate the IP and host mask fields into blocks. */
591     emit_client(fields[0], passwd, fields[4], maxlinks, 1);
592     emit_client(fields[2], passwd, fields[4], maxlinks, 0);
593 }
594
595 int main(int argc, char *argv[])
596 {
597     FILE *ifile;
598
599     if (argc < 2)
600         ifile = stdin;
601     else if (!(ifile = fopen(argv[1], "rt"))) {
602         fprintf(stderr, "Unable to open file %s for input.\n", argv[1]);
603         return 1;
604     }
605
606     for (lineno = 1; fgets(line, sizeof(line), ifile); ++lineno) {
607         /* Read line and pass comments through. */
608         size_t len = strlen(line);
609         if (line[0] == '#') {
610             fputs(line, stdout);
611             continue;
612         }
613         /* Strip EOL character(s) and pass blank lines through. */
614         while (len > 0 && (line[len-1] == '\n' || line[len-1] == '\r'))
615             line[--len] = '\0';
616         if (len == 0) {
617             fputc('\n', stdout);
618             continue;
619         }
620         /* Skip but report invalid lines. */
621         if (line[1] != ':') {
622             fprintf(stdout, "# %s\n", line);
623             fprintf(stderr, "Invalid input line %d.\n", lineno);
624             continue;
625         }
626         /* Copy the original line into a reusable variable. */
627         strcpy(orig_line, line);
628         /* Split line into fields. */
629         nfields = split_line(line + 2, fields);
630
631         /* Process the input line. */
632         switch (line[0]) {
633         case 'A': case 'a': simple_line("Admin", admin_names, NULL); break;
634         case 'C': case 'c': do_connect(); break;
635         case 'D':           simple_line("CRule", crule_names, "all = yes;"); break;
636                   case 'd': simple_line("CRule", crule_names, NULL); break;
637         case 'F': case 'f': do_feature(); break;
638         case 'H': case 'h': do_hub(); break;
639         case 'I': case 'i': do_client(); break;
640         case 'K': case 'k': do_kill(); break;
641         case 'L': case 'l': do_leaf(); break;
642         case 'M': case 'm': simple_line("General", general_names, NULL); break;
643         case 'O':           do_operator(0); break;
644                   case 'o': do_operator(1); break;
645         case 'P': case 'p': do_port(); break;
646         case 'Q': case 'q': do_quarantine(); break;
647         case 'T': case 't': simple_line("Motd", motd_names, NULL); break;
648         case 'U': case 'u': do_uworld(); break;
649         case 'Y': case 'y': simple_line("Class", class_names, NULL); break;
650         default:
651             fprintf(stderr, "Unknown line %u with leading character '%c'.\n", lineno, line[0]);
652             break;
653         }
654     }
655
656     fclose(ifile);
657
658     fputs("\n# The following lines were intentionally moved and rearranged."
659           "\n# Our apologies for any inconvenience this may cause."
660           "\n\n", stdout);
661     finish_connects();
662     finish_quarantines();
663     finish_features();
664     finish_operators();
665
666     return 0;
667 }