3f0e4d8f15e2a55d5806c081e4805d8a00c6419a
[citadel.git] / textclient / src / routines.c
1 /*
2  * Client-side support functions.
3  *
4  * Copyright (c) 1987-2016 by the citadel.org team
5  *
6  *  This program is open source software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License version 3.
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
15 #include "sysdep.h"
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <fcntl.h>
19 #include <stdio.h>
20 #include <ctype.h>
21 #include <string.h>
22 #include <sys/types.h>
23 #include <sys/ioctl.h>
24 #include <pwd.h>
25 #include <signal.h>
26 #include <dirent.h>
27 #include <errno.h>
28
29 #if TIME_WITH_SYS_TIME
30 # include <sys/time.h>
31 # include <time.h>
32 #else
33 # if HAVE_SYS_TIME_H
34 #  include <sys/time.h>
35 # else
36 #  include <time.h>
37 # endif
38 #endif
39
40 #ifdef HAVE_LIMITS_H
41 #include <limits.h>
42 #endif
43
44 #include <libcitadel.h>
45 #include "citadel_ipc.h"
46 #include "screen.h"
47
48 #define ROUTINES_C
49
50 #include "routines.h"
51 #include "commands.h"
52 #include "citadel_decls.h"
53 #include "routines2.h"
54 #include "tuiconfig.h"
55
56 #define IFAIDE if(axlevel>=AxAideU)
57 #define IFNAIDE if (axlevel<AxAideU)
58
59 extern unsigned userflags;
60 extern char sigcaught;
61 extern char rc_floor_mode;
62 extern int rc_ansi_color;
63 extern int rc_prompt_control;
64
65 /* Destructive backspace */
66 void back(int spaces) {
67         int a;
68         for (a=0; a<spaces; ++a) {
69                 scr_putc(8);
70                 scr_putc(32);
71                 scr_putc(8);
72         }
73 }
74
75 /*
76  * Edit or delete a user (cmd=25 to edit/create, 96 to delete)
77  */
78 void edituser(CtdlIPC *ipc, int cmd)
79 {
80         char buf[SIZ];
81         char who[USERNAME_SIZE];
82         char newname[USERNAME_SIZE];
83         struct ctdluser *user = NULL;
84         int newnow = 0;
85         int r;                          /* IPC response code */
86         int change_name = 0;
87
88         strcpy(newname, "");
89
90         newprompt("User name: ", who, 29);
91         while ((r = CtdlIPCAideGetUserParameters(ipc, who, &user, buf)) / 100 != 2) {
92                 scr_printf("%s\n", buf);
93                 if (cmd == 25) {
94                         scr_printf("Do you want to create this user? ");
95                         if (yesno()) {
96                                 r = CtdlIPCCreateUser(ipc, who, 0, buf);
97                                 if (r / 100 == 2) {
98                                         newnow = 1;
99                                         continue;
100                                 }
101                                 scr_printf("%s\n", buf);
102                         }
103                 }
104                 free(user);
105                 return;
106         }
107
108         if (cmd == 25) {
109                 val_user(ipc, user->fullname, 0); /* Display registration */
110
111                 if (!newnow) {
112                         change_name = 1;
113                         while (change_name == 1) {
114                                 if (boolprompt("Change name", 0)) {
115                                         strprompt("New name", newname, USERNAME_SIZE-1);
116                                         r = CtdlIPCRenameUser(ipc, user->fullname, newname, buf);
117                                         if (r / 100 != 2) {
118                                                 scr_printf("%s\n", buf);
119                                         }
120                                         else {
121                                                 strcpy(user->fullname, newname);
122                                                 change_name = 0;
123                                         }
124                                 }
125                                 else {
126                                         change_name = 0;
127                                 }
128                         }
129                 }
130
131                 if (newnow || boolprompt("Change password", 0)) {
132                         strprompt("Password", user->password, -19);
133                 }
134         
135                 user->axlevel = intprompt("Access level", user->axlevel, 0, 6);
136                 if (boolprompt("Permission to send Internet mail", (user->flags & US_INTERNET)))
137                         user->flags |= US_INTERNET;
138                 else
139                         user->flags &= ~US_INTERNET;
140                 if (boolprompt("Ask user to register again", !(user->flags & US_REGIS)))
141                         user->flags &= ~US_REGIS;
142                 else
143                         user->flags |= US_REGIS;
144                 user->timescalled = intprompt("Times called",
145                                 user->timescalled, 0, INT_MAX);
146                 user->posted = intprompt("Messages posted",
147                                         user->posted, 0, INT_MAX);
148                 user->lastcall = boolprompt("Set last call to now", 0) ?
149                                         time(NULL) : user->lastcall;
150                 user->USuserpurge = intprompt("Purge time (in days, 0 for system default",
151                                 user->USuserpurge, 0, INT_MAX);
152         }
153
154         if (cmd == 96) {
155                 scr_printf("Do you want to delete this user? ");
156                 if (!yesno()) {
157                         free(user);
158                         return;
159                 }
160                 user->axlevel = AxDeleted;
161         }
162
163         r = CtdlIPCAideSetUserParameters(ipc, user, buf);
164         if (r / 100 != 2) {
165                 scr_printf("%s\n", buf);
166         }
167         free(user);
168 }
169
170
171 /* Display a prompt and flip a bit based on whether the user answers
172  * yes or no.  Yes=1 and No=0, unless 'backwards' is set to a nonzero value
173  * in which case No=1 and Yes=0.
174  */
175 int set_attr(CtdlIPC *ipc, unsigned int sval, char *prompt, unsigned int sbit, int backwards)
176 {
177         int a;
178         int temp;
179
180         temp = sval;
181         color(DIM_WHITE);
182         scr_printf("%50s ", prompt);
183         color(DIM_MAGENTA);
184         scr_printf("[");
185         color(BRIGHT_MAGENTA);
186
187         if (backwards) {
188                 scr_printf("%3s", ((temp&sbit) ? "No":"Yes"));
189         }
190         else {
191                 scr_printf("%3s", ((temp&sbit) ? "Yes":"No"));
192         }
193
194         color(DIM_MAGENTA);
195         scr_printf("]? ");
196         color(BRIGHT_CYAN);
197         a = (temp & sbit);
198         if (a != 0) a = 1;
199         if (backwards) a = 1 - a;
200         a = yesno_d(a);
201         if (backwards) a = 1 - a;
202         color(DIM_WHITE);
203         temp = (temp|sbit);
204         if (!a) temp = (temp^sbit);
205         return(temp);
206 }
207
208 /*
209  * modes are:  0 - .EC command, 1 - .EC for new user,
210  *             2 - toggle Xpert mode  3 - toggle floor mode
211  */
212 void enter_config(CtdlIPC *ipc, int mode)
213 {
214         char buf[SIZ];
215         struct ctdluser *user = NULL;
216         int r;                          /* IPC response code */
217
218         r = CtdlIPCGetConfig(ipc, &user, buf);
219         if (r / 100 != 2) {
220                 scr_printf("%s\n", buf);
221                 free(user);
222                 return;
223         }
224
225         if (mode == 0 || mode == 1) {
226
227                 user->flags = set_attr(ipc, user->flags,
228                                        "Are you an experienced Citadel user",
229                                        US_EXPERT, 0);
230                 if ((user->flags & US_EXPERT) == 0 && mode == 1) {
231                         free(user);
232                         return;
233                 }
234
235                 user->flags = set_attr(
236                         ipc,
237                         user->flags,
238                         "Print last old message on New message request",
239                         US_LASTOLD,
240                         0
241                 );
242
243                 user->flags = set_attr(
244                         ipc,
245                         user->flags,
246                         "Prompt after each message",
247                         US_NOPROMPT,
248                         1
249                 );
250
251                 if ((user->flags & US_NOPROMPT) == 0) {
252                         user->flags = set_attr(
253                                 ipc,
254                                 user->flags,
255                                 "Use 'disappearing' prompts",
256                                 US_DISAPPEAR,
257                                 0
258                         );
259                 }
260
261                 user->flags = set_attr(
262                         ipc,
263                         user->flags,
264                         "Pause after each screenful of text",
265                         US_PAGINATOR,
266                         0
267                 );
268
269                 if (rc_prompt_control == 3 && (user->flags & US_PAGINATOR)) {
270                         user->flags = set_attr(
271                                 ipc,
272                                 user->flags,
273                                 "<N>ext and <S>top work at paginator prompt",
274                                 US_PROMPTCTL,
275                                 0
276                         );
277                 }
278
279                 if (rc_floor_mode == RC_DEFAULT) {
280                         user->flags = set_attr(
281                                 ipc,
282                                 user->flags,
283                                 "View rooms by floor",
284                                 US_FLOORS,
285                                 0
286                         );
287                 }
288
289                 if (rc_ansi_color == 3) {
290                         user->flags = set_attr(
291                                 ipc,
292                                 user->flags,
293                                 "Enable color support",
294                                 US_COLOR,
295                                 0
296                         );
297                 }
298
299                 if ((user->flags & US_EXPERT) == 0) {
300                         formout(ipc, "unlisted");
301                 }
302
303                 user->flags = set_attr(
304                         ipc,
305                         user->flags,
306                         "Be unlisted in userlog",
307                         US_UNLISTED,
308                         0
309                 );
310
311                 if (!IsEmptyStr(editor_path)) {
312                         user->flags = set_attr(
313                                 ipc,
314                                 user->flags,
315                                 "Always enter messages with the full-screen editor",
316                                 US_EXTEDIT,
317                                 0
318                         );
319                 }
320
321         }
322
323         if (mode == 2) {
324                 if (user->flags & US_EXPERT) {
325                         user->flags ^= US_EXPERT;
326                         scr_printf("Expert mode now OFF\n");
327                 } else {
328                         user->flags |= US_EXPERT;
329                         scr_printf("Expert mode now ON\n");
330                 }
331         }
332
333         if (mode == 3) {
334                 if (user->flags & US_FLOORS) {
335                         user->flags ^= US_FLOORS;
336                         scr_printf("Floor mode now OFF\n");
337                 } else {
338                         user->flags |= US_FLOORS;
339                         scr_printf("Floor mode now ON\n");
340                 }
341         }
342
343         r = CtdlIPCSetConfig(ipc, user, buf);
344         if (r / 100 != 2) scr_printf("%s\n", buf);
345         userflags = user->flags;
346         free(user);
347 }
348
349 /*
350  * getstring()  -  get a line of text from a file
351  *                 ignores lines beginning with "#"
352  */
353 int getstring(FILE *fp, char *string)
354 {
355         int a,c;
356         do {
357                 strcpy(string,"");
358                 a=0;
359                 do {
360                         c=getc(fp);
361                         if (c<0) {
362                                 string[a]=0;
363                                 return(-1);
364                         }
365                         string[a++]=c;
366                 } while(c!=10);
367                         string[a-1]=0;
368         } while(string[0]=='#');
369         return(strlen(string));
370 }
371
372
373 /* Searches for patn in search string */
374 int pattern(char *search, char *patn) {
375         int a,b,len;
376         
377         len = strlen(patn);
378         for (a=0; !IsEmptyStr(&search[a]); ++a) {
379                 b=strncasecmp(&search[a],patn,len);
380                 if (b==0) return(b);
381         }
382         return(-1);
383 }
384
385
386 void strproc(char *string)
387 {
388         int a;
389
390         if (IsEmptyStr(string)) return;
391
392         /* Convert non-printable characters to blanks */
393         for (a=0; !IsEmptyStr(&string[a]); ++a) {
394                 if (string[a]<32) string[a]=32;
395                 if (string[a]>126) string[a]=32;
396         }
397
398         /* Remove leading and trailing blanks */
399         while(string[0]<33) strcpy(string,&string[1]);
400         while(string[strlen(string)-1]<33) string[strlen(string)-1]=0;
401
402         /* Remove double blanks */
403         for (a=0; a<strlen(string); ++a) {
404                 if ((string[a]==32)&&(string[a+1]==32)) {
405                         strcpy(&string[a],&string[a+1]);
406                         a=0;
407                 }
408         }
409
410         /* remove characters which would interfere with the network */
411         for (a=0; a<strlen(string); ++a) {
412                 if (string[a]=='!') strcpy(&string[a],&string[a+1]);
413                 if (string[a]=='@') strcpy(&string[a],&string[a+1]);
414                 if (string[a]=='_') strcpy(&string[a],&string[a+1]);
415                 if (string[a]==',') strcpy(&string[a],&string[a+1]);
416                 if (string[a]=='%') strcpy(&string[a],&string[a+1]);
417                 if (string[a]=='|') strcpy(&string[a],&string[a+1]);
418         }
419
420 }
421
422
423 void progress(CtdlIPC* ipc, unsigned long curr, unsigned long cmax)
424 {
425         static char dots[] =
426                 "**************************************************";
427         char dots_printed[51];
428         char fmt[42];
429         unsigned long a;
430
431         if (curr >= cmax) {
432                 scr_printf("\r%79s\r","");
433         } else {
434                 /* a will be range 0-50 rather than 0-100 */
435                 a=(curr * 50) / cmax;
436                 sprintf(fmt, "[%%s%%%lds] %%3ld%%%% %%10ld/%%10ld\r", 50 - a);
437                 strncpy(dots_printed, dots, a);
438                 dots_printed[a] = 0;
439                 scr_printf(fmt, dots_printed, "",
440                                 curr * 100 / cmax, curr, cmax);
441                 scr_flush();
442         }
443 }
444
445
446 /*
447  * NOT the same locate_host() in locate_host.c.  This one just does a
448  * 'who am i' to try to discover where the user is...
449  */
450 void locate_host(CtdlIPC* ipc, char *hbuf)
451 {
452         FILE *who = (FILE *)popen("who am i","r");
453         if (who==NULL) {
454                 strcpy(hbuf, ipc->ServInfo.fqdn);
455                 return; 
456         }
457         fgets(hbuf, SIZ, who);
458         pclose(who);
459         stripallbut(hbuf, '(' , ')' );
460 }
461
462 /*
463  * miscellaneous server commands (testing, etc.)
464  */
465 void misc_server_cmd(CtdlIPC *ipc, char *cmd) {
466         char buf[SIZ];
467
468         CtdlIPC_chat_send(ipc, cmd);
469         CtdlIPC_chat_recv(ipc, buf);
470         scr_printf("%s\n",buf);
471         if (buf[0]=='1') {
472                 set_keepalives(KA_HALF);
473                 while (CtdlIPC_chat_recv(ipc, buf), strcmp(buf,"000")) {
474                         scr_printf("%s\n",buf);
475                 }
476                 set_keepalives(KA_YES);
477                 return;
478         }
479         if (buf[0]=='4') {
480                 do {
481                         newprompt("> ",buf,255);
482                         CtdlIPC_chat_send(ipc, buf);
483                 } while(strcmp(buf,"000"));
484                 return;
485         }
486 }
487
488
489 /*
490  * compute the checksum of a file
491  */
492 int file_checksum(char *filename)
493 {
494         int cksum = 0;
495         int ch;
496         FILE *fp;
497
498         fp = fopen(filename,"r");
499         if (fp == NULL) return(0);
500
501         /* yes, this algorithm may allow cksum to overflow, but that's ok
502          * as long as it overflows consistently, which it will.
503          */
504         while (ch=getc(fp), ch>=0) {
505                 cksum = (cksum + ch);
506         }
507
508         fclose(fp);
509         return(cksum);
510 }
511
512 /*
513  * nuke a directory and its contents
514  */
515 int nukedir(char *dirname)
516 {
517         DIR *dp;
518         struct dirent *d;
519         char filename[SIZ];
520
521         dp = opendir(dirname);
522         if (dp == NULL) {
523                 return(errno);
524         }
525
526         while (d = readdir(dp), d != NULL) {
527                 snprintf(filename, sizeof filename, "%s/%s",
528                         dirname, d->d_name);
529                 unlink(filename);
530         }
531
532         closedir(dp);
533         return(rmdir(dirname));
534 }