17f1f1f1b16de058148c8aa6728f7585cbe69613
[lcr.git] / admin_client.c
1 /*****************************************************************************\
2 **                                                                           **
3 ** PBX4Linux                                                                 **
4 **                                                                           **
5 **---------------------------------------------------------------------------**
6 ** Copyright: Andreas Eversberg                                              **
7 **                                                                           **
8 ** Administration tool                                                       **
9 **                                                                           **
10 \*****************************************************************************/
11
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <sys/types.h>
16 #include <time.h>
17 #include <unistd.h>
18 #include <fcntl.h>
19 #include <sys/ioctl.h>
20 #include <sys/socket.h>
21 #include <sys/un.h>
22 #include <curses.h>
23 #include "save.h"
24 #include "call.h"
25 #include "callpbx.h"
26 #include "admin.h"
27 #include "cause.h"
28
29 #define LTEE {addch(ACS_LTEE);addch(ACS_HLINE);addch(ACS_HLINE);}
30 #define LLCORNER {addch(ACS_LLCORNER);addch(ACS_HLINE);addch(ACS_HLINE);}
31 #define VLINE {addch(ACS_VLINE);addstr("  ");}
32 #define EMPTY {addstr("   ");}
33 //char rotator[] = {'-', '\\', '|', '/'};
34 int     lastlines, lastcols;
35 int     show_interfaces = 2,
36         show_calls = 1,
37         show_log = 1;
38
39 enum {
40         MODE_STATE,
41         MODE_INTERFACE,
42         MODE_ROUTE,
43         MODE_DIAL,
44         MODE_RELEASE,
45         MODE_TESTCALL,
46         MODE_TRACE,
47 };
48
49 char *text_interfaces[] = {
50         "off",
51         "brief",
52         "active channels",
53         "all channels",
54 };
55
56 char *text_calls[] = {
57         "off",
58         "brief",
59         "structured",
60 };
61
62 char    red = 1,
63         green = 2,
64         yellow = 3,
65         blue = 4,
66         mangenta = 5,
67         cyan = 6,
68         white = 7;
69
70 #define LOGLINES 128
71 char logline[LOGLINES][256];
72 unsigned long logcur = 0;
73 int logfh = -1;
74 char logfile[128];
75
76 /*
77  * curses
78  */
79 void init_curses(void)
80 {
81         /* init curses */
82         initscr(); cbreak(); noecho();
83         start_color();
84         nodelay(stdscr, TRUE);
85         if (COLOR_PAIRS>=8 && COLORS>=8)
86         {
87                 init_pair(1,1,0);
88                 init_pair(2,2,0);
89                 init_pair(3,3,0);
90                 init_pair(4,4,0);
91                 init_pair(5,5,0);
92                 init_pair(6,6,0);
93                 init_pair(7,7,0);
94         }
95         lastlines = LINES;
96         lastcols = COLS;
97 }
98
99 void cleanup_curses(void)
100 {
101         endwin();
102 }
103
104 void color(int color)
105 {
106         if (COLOR_PAIRS>=8 && COLORS>=8)
107                 attrset(COLOR_PAIR(color));
108 }
109
110 /*
111  * permanently show current state using ncurses
112  */
113 int debug_port(struct admin_message *msg, struct admin_message *m, int line, int i, int vline)
114 {
115         char buffer[256];
116
117         color(white);
118         addstr("PORT:");
119         color(yellow);
120         SPRINT(buffer,"%s(%d)", m[i].u.p.name,m[i].u.p.serial);
121         addstr(buffer);
122         color(cyan);
123         addstr(" state=");
124         switch (m[i].u.p.state)
125         {
126                 case ADMIN_STATE_IDLE:
127                 color(red);
128                 addstr("'idle'");
129                 break;
130                 case ADMIN_STATE_IN_SETUP:
131                 color(red);
132                 addstr("'in << setup'");
133                 break;
134                 case ADMIN_STATE_OUT_SETUP:
135                 color(red);
136                 addstr("'out >> setup'");
137                 break;
138                 case ADMIN_STATE_IN_OVERLAP:
139                 color(yellow);
140                 addstr("'in << overlap'");
141                 break;
142                 case ADMIN_STATE_OUT_OVERLAP:
143                 color(yellow);
144                 addstr("'out >> overlap'");
145                 break;
146                 case ADMIN_STATE_IN_PROCEEDING:
147                 color(mangenta);
148                 addstr("'in << proc'");
149                 break;
150                 case ADMIN_STATE_OUT_PROCEEDING:
151                 color(mangenta);
152                 addstr("'out >> proc'");
153                 break;
154                 case ADMIN_STATE_IN_ALERTING:
155                 color(cyan);
156                 addstr("'in << alert'");
157                 break;
158                 case ADMIN_STATE_OUT_ALERTING:
159                 color(cyan);
160                 addstr("'out >> alert'");
161                 break;
162                 case ADMIN_STATE_CONNECT:
163                 color(white);
164                 addstr("'connect'");
165                 break;
166                 case ADMIN_STATE_IN_DISCONNECT:
167                 color(blue);
168                 addstr("'in  << disc'");
169                 break;
170                 case ADMIN_STATE_OUT_DISCONNECT:
171                 color(blue);
172                 addstr("'out >> disc'");
173                 break;
174                 default:
175                 color(blue);
176                 addstr("'--NONE--'");
177         }
178
179         if (m[i].u.p.isdn)
180         {       
181                 color(cyan);
182                 addstr(" bchannel=");
183                 color(white);
184                 SPRINT(buffer,"%d", m[i].u.p.isdn_chan);
185                 addstr(buffer);
186                 if (m[i].u.p.isdn_ces >= 0)
187                 {
188                         color(cyan);
189                         addstr(" ces=");
190                         color(yellow);
191                         SPRINT(buffer, "%d", m[i].u.p.isdn_ces);
192                         addstr(buffer);
193                 }
194                 if (m[i].u.p.isdn_hold)
195                 {
196                         color(red);
197                         addstr(" hold");
198                 }
199         }
200
201         return(line);
202 }
203 int debug_epoint(struct admin_message *msg, struct admin_message *m, int line, int i, int vline)
204 {
205         unsigned long epoint = m[i].u.e.serial;
206         char buffer[256];
207         unsigned char c;
208         int j, jj;
209         int ltee;
210
211         color(white);
212         SPRINT(buffer,"EPOINT(%d)", epoint);
213         addstr(buffer);
214         color(cyan);
215         addstr(" state=");
216         switch (m[i].u.e.state)
217         {
218                 case ADMIN_STATE_IDLE:
219                 color(red);
220                 addstr("'idle'");
221                 break;
222                 case ADMIN_STATE_IN_SETUP:
223                 color(red);
224                 addstr("'in << setup'");
225                 break;
226                 case ADMIN_STATE_OUT_SETUP:
227                 color(red);
228                 addstr("'out >> setup'");
229                 break;
230                 case ADMIN_STATE_IN_OVERLAP:
231                 color(yellow);
232                 addstr("'in << overlap'");
233                 break;
234                 case ADMIN_STATE_OUT_OVERLAP:
235                 color(yellow);
236                 addstr("'out >> overlap'");
237                 break;
238                 case ADMIN_STATE_IN_PROCEEDING:
239                 color(mangenta);
240                 addstr("'in << proc'");
241                 break;
242                 case ADMIN_STATE_OUT_PROCEEDING:
243                 color(mangenta);
244                 addstr("'out >> proc'");
245                 break;
246                 case ADMIN_STATE_IN_ALERTING:
247                 color(cyan);
248                 addstr("'in << alert'");
249                 break;
250                 case ADMIN_STATE_OUT_ALERTING:
251                 color(cyan);
252                 addstr("'out >> alert'");
253                 break;
254                 case ADMIN_STATE_CONNECT:
255                 color(white);
256                 addstr("'connect'");
257                 break;
258                 case ADMIN_STATE_IN_DISCONNECT:
259                 color(blue);
260                 addstr("'in  << disc'");
261                 break;
262                 case ADMIN_STATE_OUT_DISCONNECT:
263                 color(blue);
264                 addstr("'out >> disc'");
265                 break;
266                 default:
267                 color(blue);
268                 addstr("'--NONE--'");
269         }
270         if (m[i].u.e.terminal[0])
271         {
272                 color(cyan);
273                 addstr(" terminal=");
274                 color(green);
275                 addstr(m[i].u.e.terminal);
276         }
277         color(white);
278         SPRINT(buffer, " %s", m[i].u.e.callerid);
279         addstr(buffer);
280         color(cyan);
281         addstr("->");
282         color(white);
283         addstr(m[i].u.e.dialing);
284         if (m[i].u.e.action[0])
285         {
286                 color(cyan);
287                 addstr(" action=");
288                 color(yellow);
289                 addstr(m[i].u.e.action);
290         }
291         if (m[i].u.e.park)
292         {
293                 color(cyan);
294                 addstr(" park="); /* 9 digits */
295                 color(green);
296                 UCPY(buffer, "\""); /* 9 digits */
297                 j = 0;
298                 jj = m[i].u.e.park_len;
299                 while(j < jj)
300                 {
301                         c = m[i].u.e.park_callid[j];
302                         if (c >= 32 && c < 127 && c != '[')
303                         {
304                                 SCCAT(buffer, c);
305                         } else
306                                 UPRINT(buffer+strlen(buffer), "[%02x]", c);
307                         j++;
308                 }
309                 SCAT(buffer, "\"");
310                 addstr(buffer);
311         } else
312         {
313                 color(red);
314                 switch(m[i].u.e.rx_state)
315                 {
316                         case NOTIFY_STATE_SUSPEND:
317                         addstr(" in=suspend");
318                         break;
319                         case NOTIFY_STATE_HOLD:
320                         addstr(" in=hold");
321                         break;
322                         case NOTIFY_STATE_CONFERENCE:
323                         addstr(" in=conference");
324                         break;
325                 }
326                 switch(m[i].u.e.tx_state)
327                 {
328                         case NOTIFY_STATE_SUSPEND:
329                         addstr(" out=suspend");
330                         break;
331                         case NOTIFY_STATE_HOLD:
332                         addstr(" out=hold");
333                         break;
334                         case NOTIFY_STATE_CONFERENCE:
335                         addstr(" out=conference");
336                         break;
337                 }
338         }
339         if (m[i].u.e.crypt)
340         {
341                 color(cyan);
342                 addstr(" crypt=");
343                 if (m[i].u.e.crypt) /* crypt on */
344                 {
345                         color(green);
346                         addstr("active");
347                 } else
348                 {
349                         color(yellow);
350                         addstr("pending");
351                 }
352         }
353         /* loop all related ports */
354         ltee = 0;
355         j = msg->u.s.interfaces+msg->u.s.calls+msg->u.s.epoints;
356         jj = j + msg->u.s.ports;
357         while(j < jj)
358         {
359                 if (m[j].u.p.epoint == epoint)
360                 {
361                         color(cyan);
362                         move(++line>1?line:1, 1);
363                         if (vline)
364                                 VLINE
365                         else
366                                 EMPTY
367                         move(line>1?line:1, 5);
368                         LTEE
369                         ltee = line;
370                         move(line>1?line:1, 8);
371                         if (line+2 >= LINES) break;
372                         line = debug_port(msg, m, line, j, vline);
373                         if (line+2 >= LINES) break;
374                 }
375                 j++;
376         }
377         if (ltee)
378         {
379                 color(cyan);
380                 move(ltee>1?line:1, 5);
381                 LLCORNER
382         }
383
384         return(line);
385 }
386 int debug_call(struct admin_message *msg, struct admin_message *m, int line, int i)
387 {
388         unsigned long   call = m[i].u.c.serial;
389         char            buffer[256];
390         int             j, jj;
391
392         color(white);
393         SPRINT(buffer,"CALL(%d)", call);
394         addstr(buffer);
395         if (m[i].u.c.partyline)
396         {
397                 color(cyan);
398                 addstr(" partyline=");
399                 color(white);
400                 SPRINT(buffer, "%d\n", m[i].u.c.partyline);
401                 addstr(buffer);
402         }
403         /* find number of epoints */
404         j = msg->u.s.interfaces+msg->u.s.calls;
405         jj = j + msg->u.s.epoints;
406         i = 0;
407         while(j < jj)
408         {
409                 if (m[j].u.e.call == call)
410                         i++;
411                 j++;
412         }
413         /* loop all related endpoints */
414         j = msg->u.s.interfaces+msg->u.s.calls;
415         jj = j + msg->u.s.epoints;
416         while(j < jj)
417         {
418                 if (m[j].u.e.call == call)
419                 {
420                         i--;
421                         move(++line>1?line:1, 1);
422                         color(cyan);
423                         if (i)
424                                 LTEE
425                         else
426                                 LLCORNER
427                         move(line>1?line:1, 4);
428                         if (line+2 >= LINES) break;
429                         line = debug_epoint(msg, m, line, j, i?1:0);
430                         if (line+2 >= LINES) break;
431                 }
432                 j++;
433         }
434
435         return(line);
436 }
437 char *admin_state(int sock)
438 {
439         struct admin_message    msg,
440                                 *m;
441         char                    buffer[256],
442                                 *p;
443         int                     line, offset = 0;
444         int                     i, ii, j, jj, k;
445         unsigned long           l, ll;
446         int                     num;
447         int                     len;
448         int                     off;
449         int                     ltee;
450         int                     anything;
451
452         /* flush logfile name */
453         logfile[0] = '\0';
454
455         /* init curses */
456         init_curses();
457
458         again:
459         /* send reload command */
460         memset(&msg, 0, sizeof(msg));
461         msg.message = ADMIN_REQUEST_STATE;
462 //      printf("sizeof=%d\n",sizeof(msg));
463         if (write(sock, &msg, sizeof(msg)) != sizeof(msg))
464         {
465                 cleanup_curses();
466                 return("Broken pipe while sending command.");
467         }
468
469         /* receive response */
470         if (read(sock, &msg, sizeof(msg)) != sizeof(msg))
471         {
472                 cleanup_curses();
473                 return("Broken pipe while receiving response.");
474         }
475         if (msg.message != ADMIN_RESPONSE_STATE)
476         {
477                 cleanup_curses();
478                 return("Response not valid. Expecting state response.");
479         }
480         num = msg.u.s.interfaces + msg.u.s.calls + msg.u.s.epoints + msg.u.s.ports;
481         if (!(m = (struct admin_message *)malloc(num*sizeof(struct admin_message))))
482         {
483                 cleanup_curses();
484                 return("Not enough memory for messages.");
485         }
486         off=0;
487 readagain:
488         if ((len = read(sock, ((unsigned char *)(m))+off,
489                                         num*sizeof(struct admin_message)-off)) != num*sizeof(struct admin_message)-off)
490         {
491                 if (len <= 0) {
492                         free(m);
493 //                      fprintf(stderr, "got=%d expected=%d\n", i, num*sizeof(struct admin_message));
494                         cleanup_curses();
495                         return("Broken pipe while receiving state infos.");
496                 }
497                 if (len < num*sizeof(struct admin_message))
498                 {
499                         off+=len;
500                         goto readagain;
501                 }
502         }
503         j = 0;
504         i = 0;
505 //      fprintf("getting =%d interfaces\n", msg.u.s.interfaces);
506         while(i < msg.u.s.interfaces)
507         {
508 //              fprintf(stderr, "j=%d message=%d\n", j, m[j].message);
509                 if (m[j].message != ADMIN_RESPONSE_S_INTERFACE)
510                 {
511                         free(m);
512                         cleanup_curses();
513                         return("Response not valid. Expecting interface information.");
514                 }
515                 i++;
516                 j++;
517         }
518         i = 0;
519         while(i < msg.u.s.calls)
520         {
521                 if (m[j].message != ADMIN_RESPONSE_S_CALL)
522                 {
523                         free(m);
524                         cleanup_curses();
525                         return("Response not valid. Expecting call information.");
526                 }
527                 i++;
528                 j++;
529         }
530         i = 0;
531         while(i < msg.u.s.epoints)
532         {
533                 if (m[j].message != ADMIN_RESPONSE_S_EPOINT)
534                 {
535                         free(m);
536                         cleanup_curses();
537                         return("Response not valid. Expecting endpoint information.");
538                 }
539                 i++;
540                 j++;
541         }
542         i = 0;
543         while(i < msg.u.s.ports)
544         {
545                 if (m[j].message != ADMIN_RESPONSE_S_PORT)
546                 {
547                         free(m);
548                         cleanup_curses();
549                         return("Response not valid. Expecting port information.");
550                 }
551                 i++;
552                 j++;
553         }
554         // now j is the number of message blocks
555
556         /* display start */
557         erase();
558
559         line = 1-offset; 
560
561         /* change log */
562         if (!!strcmp(logfile, msg.u.s.logfile))
563         {
564                 SCPY(logfile, msg.u.s.logfile);
565                 if (logfh >= 0)
566                         close(logfh);
567                 i = 0;
568                 ii = LOGLINES;
569                 while(i < ii)
570                 {
571                         logline[i][0] = '~';
572                         logline[i][1] = '\0';
573                         i++;
574                 }
575                 logcur = 0;
576                 logfh = open(logfile, O_RDONLY|O_NONBLOCK);
577                 if (logfh >= 0)
578                 {
579                         /* seek at the end -8000 chars */
580                         lseek(logfh, -8000, SEEK_END);
581                         /* if not at the beginning, read until endofline */
582                         logline[logcur % LOGLINES][0] = '\0';
583                         l = read(logfh, logline[logcur % LOGLINES], sizeof(logline[logcur % LOGLINES])-1);
584                         if (l > 0)
585                         {
586                                 /* read first line and skip junk */
587                                 logline[logcur % LOGLINES][l] = '\0';
588                                 if ((p = strchr(logline[logcur % LOGLINES],'\n')))
589                                 {
590                                         logcur++;
591                                         SCPY(logline[logcur % LOGLINES], p+1);
592                                         SCPY(logline[(logcur-1) % LOGLINES], "...");
593                                 }
594                                 goto finish_line;
595                         }
596                 }
597         }
598
599         /* read log */
600         if (logfh >= 0)
601         {
602                 while(42)
603                 {
604                         ll = strlen(logline[logcur % LOGLINES]);
605                         l = read(logfh, logline[logcur % LOGLINES]+ll, sizeof(logline[logcur % LOGLINES])-ll-1);
606                         if (l<=0)
607                                 break;
608                         logline[logcur % LOGLINES][ll+l] = '\0';
609                         finish_line:
610                         /* put data to lines */
611                         while ((p = strchr(logline[logcur % LOGLINES],'\n')))
612                         {
613                                 *p = '\0';
614                                 logcur++;
615                                 SCPY(logline[logcur % LOGLINES], p+1);
616                         }
617                         /* if line is full without return, go next line */
618                         if (strlen(logline[logcur % LOGLINES]) == sizeof(logline[logcur % LOGLINES])-1)
619                         {
620                                 logcur++;
621                                 logline[logcur % LOGLINES][0] = '\0';
622                         }
623                 }
624         }
625
626         /* display interfaces */
627         if (show_interfaces > 0)
628         {
629                 anything = 0;
630                 i = 0;
631                 ii = i + msg.u.s.interfaces;
632                 while(i < ii)
633                 {
634                         /* show interface summary */
635                         move(++line>1?line:1, 0);
636                         color(white);
637
638                         SPRINT(buffer, "%s(%d) '%s' %s use:%d ", (m[i].u.i.ntmode)?"NT":"TE", m[i].u.i.portnum, m[i].u.i.interface_name, (m[i].u.i.ptp)?"ptp ":"ptmp", m[i].u.i.use);
639                         addstr(buffer);
640                         if (m[i].u.i.ptp || !m[i].u.i.ntmode)
641                         {
642                                 color((m[i].u.i.l2link)?green:red);
643                                 addstr((m[i].u.i.l2link)?"  L2 UP":"  L2 down");
644                         }
645                         color((m[i].u.i.l1link)?green:blue);
646                         addstr((m[i].u.i.l1link)?"  L1 ACTIVE":"  L1 inactive");
647                         if (line+2 >= LINES) goto end;
648                         /* show channels */
649                         if (show_interfaces > 1)
650                         {
651                                 ltee = 0;
652                                 j = k =0;
653                                 jj = m[i].u.i.channels;
654                                 while(j < jj)
655                                 {
656                                         /* show all channels */
657                                         if (show_interfaces>2 || m[i].u.i.busy[j]>0)
658                                         {
659                                                 color(cyan);
660                                                 /* show left side / right side */
661                                                 if ((k & 1) && (COLS > 70))
662                                                 {
663                                                         move(line>1?line:1,4+((COLS-4)/2));
664                                                 } else
665                                                 {
666                                                         move(++line>1?line:1, 1);
667                                                         LTEE
668                                                         ltee = 1;
669                                                 }
670                                                 k++;
671                                                 color(white);
672                                                 if (m[i].u.i.pri)
673                                                         SPRINT(buffer,"S%2d: ", j+1+(j>=15));
674                                                 else
675                                                         SPRINT(buffer,"B%2d: ", j+1);
676                                                 addstr(buffer);
677                                                 if (!m[i].u.i.ptp)
678                                                         goto ptmp;
679                                                 if (m[i].u.i.l2link)
680                                                 {
681                                                         ptmp:
682                                                         color((m[i].u.i.busy[j])?yellow:blue);
683                                                         addstr((m[i].u.i.busy[j])?"busy":"idle");
684                                                 } else
685                                                 {
686                                                         color(red);
687                                                         addstr("blk ");
688                                                 }
689                                                 if (m[i].u.i.port[j])
690                                                 {
691                                                         /* search for port */
692                                                         l = msg.u.s.interfaces+msg.u.s.calls+msg.u.s.epoints;
693                                                         ll = l+msg.u.s.ports;
694                                                         while(l < ll)
695                                                         {
696                                                                 if (m[l].u.p.serial == m[i].u.i.port[j])
697                                                                 {
698                                                                         SPRINT(buffer, " %s(%ld)", m[l].u.p.name, m[l].u.p.serial);
699                                                                         addstr(buffer);
700                                                                 }
701                                                                 l++;
702                                                         }
703                                                 }
704                                                 if (line+2 >= LINES)
705                                                 {
706                                                         if (ltee)
707                                                         {
708                                                                 color(cyan);
709                                                                 move(line>1?line:1, 1);
710                                                                 LLCORNER
711                                                         }
712                                                         goto end;
713                                                 }
714                                         }
715                                         j++;
716                                 }
717                                 if (ltee)
718                                 {
719                                         color(cyan);
720                                         move(line>1?line:1, 1);
721                                         LLCORNER
722                                 }
723                                 if (line+2 >= LINES) goto end;
724                                 /* show summary if no channels were shown */
725                                 if (show_interfaces<2 && ltee==0)
726                                 {
727                                         color(cyan);
728                                         move(++line>1?line:1, 1);
729                                         LLCORNER
730                                                 
731                                         if (m[i].u.i.l2link)
732                                         {
733                                                 color(green);
734                                                 SPRINT(buffer,"all %d channels free", m[i].u.i.channels);
735                                         } else
736                                         {
737                                                 color(red);
738                                                 SPRINT(buffer,"all %d channels blocked", m[i].u.i.channels);
739                                         }
740                                         addstr(buffer);
741                                 }
742                                 if (line+2 >= LINES) goto end;
743                         }
744                         i++;
745                         anything = 1;
746                 }
747                 if (anything)
748                         line++;
749                 if (line+2 >= LINES) goto end;
750         }               
751         /* display calls (brief) */
752         if (show_calls == 1)
753         {
754                 anything = 0;
755                 i = msg.u.s.interfaces+msg.u.s.calls;
756                 ii = i+msg.u.s.epoints;
757                 while(i < ii)
758                 {
759                         /* for each endpoint... */
760                         if (!m[i].u.e.call)
761                         {
762                                 move(++line>1?line:1, 0);
763                                 color(white);
764                                 SPRINT(buffer, "(%d): ", m[i].u.e.serial);
765                                 addstr(buffer);
766                                 color(cyan);
767                                 if (m[i].u.e.terminal[0])
768                                 {
769                                         addstr("intern=");
770                                         color(green);
771                                         addstr(m[i].u.e.terminal);
772                                 } else
773                                         addstr("extern");
774                                 color(white);
775                                 SPRINT(buffer, " %s", m[i].u.e.callerid);
776                                 addstr(buffer);
777                                 color(cyan);
778                                 addstr("->");
779                                 color(white);
780                                 SPRINT(buffer, "%s", m[i].u.e.dialing);
781                                 addstr(buffer);
782                                 if (m[i].u.e.action[0])
783                                 {
784                                         color(cyan);
785                                         addstr(" action=");
786                                         color(yellow);
787                                         addstr(m[i].u.e.action);
788                                 }
789                                 if (line+2 >= LINES) goto end;
790                         }
791                         i++;
792                         anything = 1;
793                 }
794                 j = msg.u.s.interfaces;
795                 jj = j+msg.u.s.calls;
796                 while(j < jj)
797                 {
798                         /* for each call... */
799                         move(++line>1?line:1, 0);
800                         color(white);
801                         SPRINT(buffer, "(%d):", m[j].u.c.serial);
802                         addstr(buffer);
803                         i = msg.u.s.interfaces+msg.u.s.calls;
804                         ii = i+msg.u.s.epoints;
805                         while(i < ii)
806                         {
807                                 /* for each endpoint... */
808                                 if (m[i].u.e.call == m[j].u.c.serial)
809                                 {
810                                         color(white);
811                                         SPRINT(buffer, " (%d)", m[i].u.e.serial);
812                                         addstr(buffer);
813                                         color(cyan);
814                                         if (m[i].u.e.terminal[0])
815                                         {
816                                                 addstr("int=");
817                                                 color(green);
818                                                 addstr(m[i].u.e.terminal);
819                                         } else
820                                                 addstr("ext");
821                                         color(white);
822                                         SPRINT(buffer, "-%s", m[i].u.e.callerid);
823                                         addstr(buffer);
824                                         color(cyan);
825                                         addstr(">");
826                                         color(white);
827                                         SPRINT(buffer, "%s", m[i].u.e.dialing);
828                                         addstr(buffer);
829                                 }
830                                 i++;
831                                 anything = 1;
832                         }
833                         if (line+2 >= LINES) goto end;
834                         j++;
835                 }
836                 if (anything)
837                         line++;
838                 if (line+2 >= LINES) goto end;
839         }
840         /* display calls (structurd) */
841         if (show_calls == 2)
842         {
843                 /* show all ports with no epoint */
844                 anything = 0;
845                 i = msg.u.s.interfaces+msg.u.s.calls+msg.u.s.epoints;
846                 ii = i+msg.u.s.ports;
847                 while(i < ii)
848                 {
849                         if (!m[i].u.p.epoint)
850                         {
851                                 move(++line>1?line:1, 8);
852                                 if (line+2 >= LINES) goto end;
853                                 line = debug_port(&msg, m, line, i, 0);
854                                 if (line+2 >= LINES) goto end;
855                                 anything = 1;
856                         }
857                         i++;
858                 }
859                 if (anything)
860                         line++;
861                 if (line+2 >= LINES) goto end;
862
863                 /* show all epoints with no call */
864                 anything = 0;
865                 i = msg.u.s.interfaces+msg.u.s.calls;
866                 ii = i+msg.u.s.epoints;
867                 while(i < ii)
868                 {
869                         if (!m[i].u.e.call)
870                         {
871                                 move(++line>1?line:1, 4);
872                                 if (line+2 >= LINES) goto end;
873                                 line = debug_epoint(&msg, m, line, i, 0);
874                                 if (line+2 >= LINES) goto end;
875                                 anything = 1;
876                         }
877                         i++;
878                 }
879                 if (anything)
880                         line++;
881                 if (line+2 >= LINES) goto end;
882
883                 /* show all calls */
884                 anything = 0;
885                 i = msg.u.s.interfaces;
886                 ii = i+msg.u.s.calls;
887                 while(i < ii)
888                 {
889                         move(++line>1?line:1, 0);
890                         if (line+2 >= LINES) goto end;
891                         line = debug_call(&msg, m, line, i);
892                         if (line+2 >= LINES) goto end;
893                         i++;
894                         anything = 1;
895                 }
896                 if (anything)
897                         line++;
898                 if (line+2 >= LINES) goto end;
899
900         }
901
902         /* show log */
903         if (show_log)
904         {
905                 if (line+2 < LINES)
906                 {
907                         move(line++>1?line-1:1, 0);
908                         color(blue);
909                         hline(ACS_HLINE, COLS);
910                         color(white);
911                         
912                         l = logcur-(LINES-line-2);
913                         ll = logcur;
914                         if (ll-l >= LOGLINES)
915                                 l = ll-LOGLINES+1;
916                         while(l!=ll)
917                         {
918                                 move(line++>1?line-1:1, 0);
919                                 SCPY(buffer, logline[l % LOGLINES]);
920                                 if (COLS < (int)sizeof(buffer))
921                                         buffer[COLS] = '\0';
922                                 addstr(buffer);
923                                 l++;
924                         }
925                 }
926         }
927
928         end:
929         /* free memory */
930         free(m);
931         /* display name/time */
932 //      move(0, 0);
933 //      hline(' ', COLS);
934         move(0, 0);
935         color(white);
936         msg.u.s.version_string[sizeof(msg.u.s.version_string)-1] = '\0';
937         SPRINT(buffer, "PBX4Linux %s", msg.u.s.version_string);
938         addstr(buffer);
939         if (COLS>50)
940         {
941                 move(0, COLS-19);
942                 SPRINT(buffer, "%04d-%02d-%02d %02d:%02d:%02d",
943                         msg.u.s.tm.tm_year+1900, msg.u.s.tm.tm_mon+1, msg.u.s.tm.tm_mday,
944                         msg.u.s.tm.tm_hour, msg.u.s.tm.tm_min, msg.u.s.tm.tm_sec);
945                 addstr(buffer);
946         }
947         /* displeay head line */
948         move(1, 0);
949         color(blue);
950         hline(ACS_HLINE, COLS);
951         if (offset)
952         {
953                 move(1, 1);
954                 SPRINT(buffer, "Offset +%d", offset);
955                 color(red);
956                 addstr(buffer);
957         }
958         /* display end */
959         move(LINES-2, 0);
960         color(white);
961         hline(ACS_HLINE, COLS);
962         move(LINES-1, 0);
963         color(white);
964         SPRINT(buffer, "i = interfaces '%s'  c = calls '%s'  l = log  q = quit  +/- = scroll", text_interfaces[show_interfaces], text_calls[show_calls]);
965         addstr(buffer);
966         refresh();
967
968         /* resize */
969         if (lastlines!=LINES || lastcols!=COLS)
970         {
971                 cleanup_curses();
972                 init_curses();
973                 goto again;
974         }
975
976         /* user input */
977         switch(getch())
978         {
979                 case 12: /* refresh */
980                 cleanup_curses();
981                 init_curses();
982                 goto again;
983                 break;
984
985                 case 3: /* abort */
986                 case 'q':
987                 case 'Q':
988                 break;
989
990                 case 'i': /* toggle interface */
991                 show_interfaces++;
992                 if (show_interfaces > 3) show_interfaces = 0;
993                 goto again;
994
995                 case 'c': /* toggle calls */
996                 show_calls++;
997                 if (show_calls > 2) show_calls = 0;
998                 goto again;
999
1000                 case 'l': /* toggle log */
1001                 show_log++;
1002                 if (show_log > 1) show_log = 0;
1003                 goto again;
1004
1005                 case '+': /* scroll down */
1006                 offset++;
1007                 goto again;
1008                 
1009                 case '-': /* scroll up */
1010                 if (offset)
1011                         offset--;
1012                 goto again;
1013
1014                 default:
1015                 usleep(250000);
1016                 goto again;
1017         }
1018
1019         /* check for logfh */
1020         if (logfh >= 0)
1021                 close(logfh);
1022         logfh = -1;
1023
1024         /* cleanup curses and exit */
1025         cleanup_curses();
1026
1027         return(NULL);
1028 }
1029
1030
1031 /*
1032  * Send command and show error message.
1033  */
1034 char *admin_cmd(int sock, int mode, char *extension, char *number)
1035 {
1036         static struct admin_message msg;
1037
1038         /* send reload command */
1039         memset(&msg, 0, sizeof(msg));
1040         switch(mode)
1041         {
1042                 case MODE_INTERFACE:
1043                 msg.message = ADMIN_REQUEST_CMD_INTERFACE;
1044                 break;
1045                 case MODE_ROUTE:
1046                 msg.message = ADMIN_REQUEST_CMD_ROUTE;
1047                 break;
1048                 case MODE_DIAL:
1049                 msg.message = ADMIN_REQUEST_CMD_DIAL;
1050                 SPRINT(msg.u.x.message, "%s:%s", extension?:"", number?:"");
1051                 break;
1052                 case MODE_RELEASE:
1053                 msg.message = ADMIN_REQUEST_CMD_RELEASE;
1054                 SCPY(msg.u.x.message, number);
1055                 break;
1056         }
1057
1058         if (write(sock, &msg, sizeof(msg)) != sizeof(msg))
1059                 return("Broken pipe while sending command.");
1060
1061         /* receive response */
1062         if (read(sock, &msg, sizeof(msg)) != sizeof(msg))
1063                 return("Broken pipe while receiving response.");
1064         switch(mode)
1065         {
1066                 case MODE_INTERFACE:
1067                 if (msg.message != ADMIN_RESPONSE_CMD_INTERFACE)
1068                         return("Response not valid.");
1069                 break;
1070                 case MODE_ROUTE:
1071                 if (msg.message != ADMIN_RESPONSE_CMD_ROUTE)
1072                         return("Response not valid.");
1073                 break;
1074                 case MODE_DIAL:
1075                 if (msg.message != ADMIN_RESPONSE_CMD_DIAL)
1076                         return("Response not valid.");
1077                 break;
1078                 case MODE_RELEASE:
1079                 if (msg.message != ADMIN_RESPONSE_CMD_RELEASE)
1080                         return("Response not valid.");
1081                 break;
1082         }
1083
1084         /* process response */
1085         if (msg.u.x.error)
1086         {
1087                 return(msg.u.x.message);
1088         }
1089         printf("Command successfull.\n");
1090         return(NULL);
1091 }
1092
1093
1094 /*
1095  * makes a testcall
1096  */
1097 char *admin_testcall(int sock, int argc, char *argv[])
1098 {
1099         static struct admin_message msg;
1100
1101         printf("pid=%d\n", getpid()); fflush(stdout);
1102
1103         /* send reload command */
1104         memset(&msg, 0, sizeof(msg));
1105         msg.message = ADMIN_CALL_SETUP;
1106         if (argc > 2)
1107         {
1108                 SCPY(msg.u.call.interface, argv[2]);
1109         }
1110         if (argc > 3)
1111         {
1112                 SCPY(msg.u.call.callerid, argv[3]);
1113         }
1114         if (argc > 4)
1115         {
1116                 SCPY(msg.u.call.dialing, argv[4]);
1117         }
1118         if (argc > 5)
1119         {
1120                 if (argv[5][0] == 'p')
1121                         msg.u.call.present = 1;
1122         }
1123         msg.u.call.bc_capa = 0x00; /*INFO_BC_SPEECH*/
1124         msg.u.call.bc_mode = 0x00; /*INFO_BMODE_CIRCUIT*/
1125         msg.u.call.bc_info1 = 0;
1126         msg.u.call.hlc = 0;
1127         msg.u.call.exthlc = 0;
1128         if (argc > 6)
1129                 msg.u.call.bc_capa = strtol(argv[6],NULL,0);
1130         else
1131                 msg.u.call.bc_info1 = 3 | 0x80; /* alaw, if no capability is given at all */
1132         if (argc > 7) {
1133                 msg.u.call.bc_mode = strtol(argv[7],NULL,0);
1134                 if (msg.u.call.bc_mode) msg.u.call.bc_mode = 2;
1135         }
1136         if (argc > 8) {
1137                 msg.u.call.bc_info1 = strtol(argv[8],NULL,0);
1138                 if (msg.u.call.bc_info1 < 0)
1139                         msg.u.call.bc_info1 = 0;
1140                 else
1141                         msg.u.call.bc_info1 |= 0x80;
1142         }
1143         if (argc > 9) {
1144                 msg.u.call.hlc = strtol(argv[9],NULL,0);
1145                 if (msg.u.call.hlc < 0)
1146                         msg.u.call.hlc = 0;
1147                 else
1148                         msg.u.call.hlc |= 0x80;
1149         }
1150 //              printf("hlc=%d\n",  msg.u.call.hlc);
1151         if (argc > 10) {
1152                 msg.u.call.exthlc = strtol(argv[10],NULL,0);
1153                 if (msg.u.call.exthlc < 0)
1154                         msg.u.call.exthlc = 0;
1155                 else
1156                         msg.u.call.exthlc |= 0x80;
1157         }
1158
1159         if (write(sock, &msg, sizeof(msg)) != sizeof(msg))
1160                 return("Broken pipe while sending command.");
1161
1162         /* receive response */
1163 next:
1164         if (read(sock, &msg, sizeof(msg)) != sizeof(msg))
1165                 return("Broken pipe while receiving response.");
1166         switch(msg.message)
1167         {
1168                 case ADMIN_CALL_SETUP_ACK:
1169                 printf("SETUP ACKNOWLEDGE\n"); fflush(stdout);
1170                 goto next;
1171
1172                 case ADMIN_CALL_PROCEEDING:
1173                 printf("PROCEEDING\n"); fflush(stdout);
1174                 goto next;
1175
1176                 case ADMIN_CALL_ALERTING:
1177                 printf("ALERTING\n"); fflush(stdout);
1178                 goto next;
1179
1180                 case ADMIN_CALL_CONNECT:
1181                 printf("CONNECT\n number=%s\n", msg.u.call.callerid); fflush(stdout);
1182                 goto next;
1183
1184                 case ADMIN_CALL_NOTIFY:
1185                 printf("NOTIFY\n notify=%d\n number=%s\n", msg.u.call.notify, msg.u.call.callerid); fflush(stdout);
1186                 goto next;
1187
1188                 case ADMIN_CALL_DISCONNECT:
1189                 printf("DISCONNECT\n cause=%d %s\n location=%d %s\n", msg.u.call.cause, (msg.u.call.cause>0 && msg.u.call.cause<128)?isdn_cause[msg.u.call.cause].german:"", msg.u.call.location, (msg.u.call.location>=0 && msg.u.call.location<128)?isdn_location[msg.u.call.location].german:""); fflush(stdout);
1190                 goto next;
1191
1192                 case ADMIN_CALL_RELEASE:
1193                 printf("RELEASE\n cause=%d %s\n location=%d %s\n", msg.u.call.cause, (msg.u.call.cause>0 && msg.u.call.cause<128)?isdn_cause[msg.u.call.cause].german:"", msg.u.call.location, (msg.u.call.location>=0 && msg.u.call.location<128)?isdn_location[msg.u.call.location].german:""); fflush(stdout);
1194                 break;
1195
1196                 default:
1197                 return("Response not valid.");
1198         }
1199         
1200         printf("Command successfull.\n");
1201         return(NULL);
1202 }
1203
1204
1205 /*
1206  * makes a trace
1207  */
1208 char *admin_trace(int sock, int argc, char *argv[])
1209 {
1210         static struct admin_message msg;
1211
1212         /* show help */
1213         if (!strcasecmp(argv[2], "help"))
1214         {
1215                 printf("Trace Help\n----------\n");
1216                 printf("%s trace [brief|short] [<filter>=<value> [...]]\n\n", argv[0]);
1217                 printf("By default a complete trace is shown in detailed format.\n");
1218                 printf("To show a more compact format, use 'brief' or 'short' keyword.\n");
1219                 printf("Use filter values to select specific trace messages.\n");
1220                 printf("All given filter values must match. If no filter is given, anything matches.\n\n");
1221                 printf("Filters:\n");
1222                 printf(" category=<mask bits>\n");
1223                 printf("  0x01 = L1: layer 1 trace (application view)\n");
1224                 printf("  0x02 = L2: layer 2 trace (application view)\n");
1225                 printf("  0x04 = L3: layer 3 trace (application view)\n");
1226                 printf("  0x08 = CH: channel selection trace\n");
1227                 printf("  0x10 = EP: endpoint trace\n");
1228                 printf("  0x20 = AP: application trace\n");
1229                 printf("  0x40 = RO: routing trace\n");
1230         }
1231         
1232         
1233
1234
1235         tbd
1236         /* send reload command */
1237         memset(&msg, 0, sizeof(msg));
1238         msg.message = ADMIN_CALL_SETUP;
1239         if (argc > 2)
1240         {
1241                 SCPY(msg.u.call.interface, argv[2]);
1242         }
1243         if (argc > 3)
1244         {
1245                 SCPY(msg.u.call.callerid, argv[3]);
1246         }
1247         if (argc > 4)
1248         {
1249                 SCPY(msg.u.call.dialing, argv[4]);
1250         }
1251         if (argc > 5)
1252         {
1253                 if (argv[5][0] == 'p')
1254                         msg.u.call.present = 1;
1255         }
1256         msg.u.call.bc_capa = 0x00; /*INFO_BC_SPEECH*/
1257         msg.u.call.bc_mode = 0x00; /*INFO_BMODE_CIRCUIT*/
1258         msg.u.call.bc_info1 = 0;
1259         msg.u.call.hlc = 0;
1260         msg.u.call.exthlc = 0;
1261         if (argc > 6)
1262                 msg.u.call.bc_capa = strtol(argv[6],NULL,0);
1263         else
1264                 msg.u.call.bc_info1 = 3 | 0x80; /* alaw, if no capability is given at all */
1265         if (argc > 7) {
1266                 msg.u.call.bc_mode = strtol(argv[7],NULL,0);
1267                 if (msg.u.call.bc_mode) msg.u.call.bc_mode = 2;
1268         }
1269         if (argc > 8) {
1270                 msg.u.call.bc_info1 = strtol(argv[8],NULL,0);
1271                 if (msg.u.call.bc_info1 < 0)
1272                         msg.u.call.bc_info1 = 0;
1273                 else
1274                         msg.u.call.bc_info1 |= 0x80;
1275         }
1276         if (argc > 9) {
1277                 msg.u.call.hlc = strtol(argv[9],NULL,0);
1278                 if (msg.u.call.hlc < 0)
1279                         msg.u.call.hlc = 0;
1280                 else
1281                         msg.u.call.hlc |= 0x80;
1282         }
1283 //              printf("hlc=%d\n",  msg.u.call.hlc);
1284         if (argc > 10) {
1285                 msg.u.call.exthlc = strtol(argv[10],NULL,0);
1286                 if (msg.u.call.exthlc < 0)
1287                         msg.u.call.exthlc = 0;
1288                 else
1289                         msg.u.call.exthlc |= 0x80;
1290         }
1291
1292         if (write(sock, &msg, sizeof(msg)) != sizeof(msg))
1293                 return("Broken pipe while sending command.");
1294
1295         /* receive response */
1296 next:
1297         if (read(sock, &msg, sizeof(msg)) != sizeof(msg))
1298                 return("Broken pipe while receiving response.");
1299         switch(msg.message)
1300         {
1301                 case ADMIN_CALL_SETUP_ACK:
1302                 printf("SETUP ACKNOWLEDGE\n"); fflush(stdout);
1303                 goto next;
1304
1305                 case ADMIN_CALL_PROCEEDING:
1306                 printf("PROCEEDING\n"); fflush(stdout);
1307                 goto next;
1308
1309                 case ADMIN_CALL_ALERTING:
1310                 printf("ALERTING\n"); fflush(stdout);
1311                 goto next;
1312
1313                 case ADMIN_CALL_CONNECT:
1314                 printf("CONNECT\n number=%s\n", msg.u.call.callerid); fflush(stdout);
1315                 goto next;
1316
1317                 case ADMIN_CALL_NOTIFY:
1318                 printf("NOTIFY\n notify=%d\n number=%s\n", msg.u.call.notify, msg.u.call.callerid); fflush(stdout);
1319                 goto next;
1320
1321                 case ADMIN_CALL_DISCONNECT:
1322                 printf("DISCONNECT\n cause=%d %s\n location=%d %s\n", msg.u.call.cause, (msg.u.call.cause>0 && msg.u.call.cause<128)?isdn_cause[msg.u.call.cause].german:"", msg.u.call.location, (msg.u.call.location>=0 && msg.u.call.location<128)?isdn_location[msg.u.call.location].german:""); fflush(stdout);
1323                 goto next;
1324
1325                 case ADMIN_CALL_RELEASE:
1326                 printf("RELEASE\n cause=%d %s\n location=%d %s\n", msg.u.call.cause, (msg.u.call.cause>0 && msg.u.call.cause<128)?isdn_cause[msg.u.call.cause].german:"", msg.u.call.location, (msg.u.call.location>=0 && msg.u.call.location<128)?isdn_location[msg.u.call.location].german:""); fflush(stdout);
1327                 break;
1328
1329                 default:
1330                 return("Response not valid.");
1331         }
1332         
1333         printf("Command successfull.\n");
1334         return(NULL);
1335 }
1336
1337
1338 /*
1339  * main function
1340  */
1341 int main(int argc, char *argv[])
1342 {
1343         int mode;
1344         char *socket_name = SOCKET_NAME;
1345         int sock, conn;
1346         struct sockaddr_un sock_address;
1347         char *ret;
1348
1349
1350         /* show options */
1351         if (argc <= 1)
1352         {
1353                 usage:
1354                 printf("\n");
1355                 printf("Usage: %s state | interface | route | dial ...\n", argv[0]);
1356                 printf("state - View current states using graphical console output.\n");
1357                 printf("interface - Tell PBX to reload \"interface.conf\".\n");
1358                 printf("route - Tell PBX to reload \"route.conf\".\n");
1359                 printf("dial <extension> <number> - Tell PBX the next number to dial for extension.\n");
1360                 printf("release <number> - Tell PBX to release endpoint with given number.\n");
1361                 printf("testcall <interface> <callerid> <number> [present|restrict [<capability>]] - Testcall\n");
1362                 printf(" -> capability = <bc> <mode> <codec> <hlc> <exthlc> (Values must be numbers, -1 to omit.)\n");
1363                 printf("trace [brief|short] [<filter> [...]] - Shows call trace. Use filter to reduce output.\n");
1364                 printf(" -> Use 'trace help' to see filter description.\n");
1365                 printf("\n");
1366                 return(0);
1367         }
1368
1369         /* check mode */
1370         if (!(strcasecmp(argv[1],"state")))
1371         {
1372                 mode = MODE_STATE;
1373         } else
1374         if (!(strcasecmp(argv[1],"interface")))
1375         {
1376                 mode = MODE_INTERFACE;
1377         } else
1378         if (!(strcasecmp(argv[1],"route")))
1379         {
1380                 mode = MODE_ROUTE;
1381         } else
1382         if (!(strcasecmp(argv[1],"dial")))
1383         {
1384                 if (argc <= 3)
1385                         goto usage;
1386                 mode = MODE_DIAL;
1387         } else
1388         if (!(strcasecmp(argv[1],"release")))
1389         {
1390                 if (argc <= 2)
1391                         goto usage;
1392                 mode = MODE_RELEASE;
1393         } else
1394         if (!(strcasecmp(argv[1],"testcall")))
1395         {
1396                 if (argc <= 4)
1397                         goto usage;
1398                 mode = MODE_TESTCALL;
1399         } else
1400         if (!(strcasecmp(argv[1],"trace")))
1401         {
1402                 if (argc <= 2)
1403                         goto usage;
1404                 mode = MODE_TRACE;
1405         } else
1406         {
1407                 goto usage;
1408         }
1409
1410 //pipeagain:
1411         /* open socket */
1412         if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
1413         {
1414                 fprintf(stderr, "Failed to create socket.\n");
1415                 exit(EXIT_FAILURE);
1416         }
1417         memset(&sock_address, 0, sizeof(sock_address));
1418         sock_address.sun_family = PF_UNIX;
1419         UCPY(sock_address.sun_path, socket_name);
1420         if ((conn = connect(sock, (struct sockaddr *)&sock_address, SUN_LEN(&sock_address))) < 0)
1421         {
1422                 close(sock);
1423                 fprintf(stderr, "Failed to connect to socket \"%s\".\nIs PBX4Linux running?\n", sock_address.sun_path);
1424                 exit(EXIT_FAILURE);
1425         }
1426
1427         /* process mode */
1428         switch(mode)
1429         {
1430                 case MODE_STATE:
1431                 ret = admin_state(sock);
1432                 break;
1433         
1434                 case MODE_INTERFACE:
1435                 case MODE_ROUTE:
1436                 ret = admin_cmd(sock, mode, NULL, NULL);
1437                 break;
1438
1439                 case MODE_DIAL:
1440                 ret = admin_cmd(sock, mode, argv[2], argv[3]);
1441                 break;
1442
1443                 case MODE_RELEASE:
1444                 ret = admin_cmd(sock, mode, NULL, argv[2]);
1445                 break;
1446
1447                 case MODE_TESTCALL:
1448                 ret = admin_testcall(sock, argc, argv);
1449
1450                 case MODE_TRACE:
1451                 ret = admin_trace(sock, argc, argv);
1452         }
1453
1454         close(sock);
1455         /* now we say good bye */
1456         if (ret)
1457         {
1458 //              if (!strncasecmp(ret, "Broken Pipe", 11))
1459 //                      goto pipeagain;
1460                 printf("%s\n", ret);
1461                 exit(EXIT_FAILURE);
1462         }
1463 }
1464
1465
1466
1467
1468