d5899c247fc5271c85fdf0486c2d9a7e5d925ea5
[citadel.git] / textclient / citadel_ipc.c
1 // Copyright (c) 1987-2022 by the citadel.org team
2 //
3 // This program is open source software.  Use, duplication, and/or
4 // disclosure is subject to the GNU General Purpose License version 3.
5 //
6 // This program is distributed in the hope that it will be useful,
7 // but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9 // GNU General Public License for more details.
10
11 #include "textclient.h"
12
13 #ifdef HAVE_OPENSSL
14 static SSL_CTX *ssl_ctx;
15 char arg_encrypt;
16 char rc_encrypt;
17 #endif                          /* HAVE_OPENSSL */
18
19 #ifndef INADDR_NONE
20 #define INADDR_NONE 0xffffffff
21 #endif
22
23 static void (*status_hook)(char *s) = NULL;
24 char ctdl_autoetc_dir[PATH_MAX] = "";
25 char file_citadel_rc[PATH_MAX] = "";
26 char ctdl_run_dir[PATH_MAX] = "";
27 char ctdl_etc_dir[PATH_MAX] = "";
28 char ctdl_home_directory[PATH_MAX] = "";
29 char file_citadel_socket[PATH_MAX] = "";
30
31 char *viewdefs[] = {
32         "Messages",
33         "Summary",
34         "Address book",
35         "Calendar",
36         "Tasks"
37 };
38
39 char *axdefs[] = {
40         "Deleted",
41         "New User",
42         "Problem User",
43         "Local User",
44         "Normal User",
45         "Preferred User",
46         "Admin",
47         "Admin"
48 };
49
50
51 void CtdlIPC_lock(CtdlIPC * ipc) {
52         if (ipc->network_status_cb)
53                 ipc->network_status_cb(1);
54 }
55
56
57 void CtdlIPC_unlock(CtdlIPC * ipc) {
58         if (ipc->network_status_cb)
59                 ipc->network_status_cb(0);
60 }
61
62
63 char *libcitadelclient_version_string(void) {
64         return "libcitadelclient(unnumbered)";
65 }
66
67
68 #define COMPUTE_DIRECTORY(SUBDIR) memcpy(dirbuffer,SUBDIR, sizeof dirbuffer);\
69         snprintf(SUBDIR,sizeof SUBDIR,  "%s%s%s%s%s%s%s", \
70                          (home&!relh)?ctdl_home_directory:basedir, \
71              ((basedir!=ctdldir)&(home&!relh))?basedir:"/", \
72              ((basedir!=ctdldir)&(home&!relh))?"/":"", \
73                          relhome, \
74              (relhome[0]!='\0')?"/":"",\
75                          dirbuffer,\
76                          (dirbuffer[0]!='\0')?"/":"");
77
78 #define DBG_PRINT(A) if (dbg==1) fprintf (stderr,"%s : %s \n", #A, A)
79
80
81 void calc_dirs_n_files(int relh, int home, const char *relhome, char *ctdldir, int dbg) {
82         const char *basedir = "";
83         char dirbuffer[PATH_MAX] = "";
84
85         StripSlashes(ctdldir, 1);
86
87 #ifndef HAVE_RUN_DIR
88         basedir = ctdldir;
89 #else
90         basedir = RUN_DIR;
91 #endif
92         COMPUTE_DIRECTORY(ctdl_run_dir);
93         StripSlashes(ctdl_run_dir, 1);
94
95
96 #ifndef HAVE_AUTO_ETC_DIR
97         basedir = ctdldir;
98 #else
99         basedir = AUTO_ETC_DIR;
100 #endif
101         COMPUTE_DIRECTORY(ctdl_autoetc_dir);
102         StripSlashes(ctdl_autoetc_dir, 1);
103
104
105 #ifndef HAVE_ETC_DIR
106         basedir = ctdldir;
107 #else
108         basedir = ETC_DIR;
109 #endif
110         COMPUTE_DIRECTORY(ctdl_etc_dir);
111         StripSlashes(ctdl_etc_dir, 1);
112
113
114
115         snprintf(file_citadel_rc, sizeof file_citadel_rc, "%scitadel.rc", ctdl_etc_dir);
116         StripSlashes(file_citadel_rc, 0);
117
118         snprintf(file_citadel_socket, sizeof file_citadel_socket, "%scitadel.socket", ctdl_run_dir);
119         StripSlashes(file_citadel_socket, 0);
120
121         DBG_PRINT(ctdl_run_dir);
122         DBG_PRINT(file_citadel_socket);
123         DBG_PRINT(ctdl_etc_dir);
124         DBG_PRINT(file_citadel_rc);
125 }
126
127 void setCryptoStatusHook(void (*hook)(char *s)) {
128         status_hook = hook;
129 }
130
131 void CtdlIPC_SetNetworkStatusCallback(CtdlIPC * ipc, void (*hook)(int state)) {
132         ipc->network_status_cb = hook;
133 }
134
135
136 char instant_msgs = 0;
137
138
139 static void serv_read(CtdlIPC * ipc, char *buf, unsigned int bytes);
140 static void serv_write(CtdlIPC * ipc, const char *buf, unsigned int nbytes);
141 #ifdef HAVE_OPENSSL
142 static void serv_read_ssl(CtdlIPC * ipc, char *buf, unsigned int bytes);
143 static void serv_write_ssl(CtdlIPC * ipc, const char *buf, unsigned int nbytes);
144 static void endtls(SSL * ssl);
145 #endif                          /* HAVE_OPENSSL */
146 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf);
147 static void CtdlIPC_putline(CtdlIPC * ipc, const char *buf);
148
149
150
151 const char *svn_revision(void);
152
153 /*
154  * Does nothing.  The server should always return 200.
155  */
156 int CtdlIPCNoop(CtdlIPC * ipc) {
157         char aaa[128];
158
159         return CtdlIPCGenericCommand(ipc, "NOOP", NULL, 0, NULL, NULL, aaa);
160 }
161
162
163 /*
164  * Does nothing interesting.  The server should always return 200
165  * along with your string.
166  */
167 int CtdlIPCEcho(CtdlIPC * ipc, const char *arg, char *cret) {
168         int ret;
169         char *aaa;
170
171         if (!arg)
172                 return -2;
173         if (!cret)
174                 return -2;
175
176         aaa = (char *) malloc((size_t) (strlen(arg) + 6));
177         if (!aaa)
178                 return -1;
179
180         sprintf(aaa, "ECHO %s", arg);
181         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
182         free(aaa);
183         return ret;
184 }
185
186
187 /*
188  * Asks the server to close the connecction.
189  * Should always return 200.
190  */
191 int CtdlIPCQuit(CtdlIPC * ipc) {
192         int ret = 221;          /* Default to successful quit */
193         char aaa[SIZ];
194
195         CtdlIPC_lock(ipc);
196         if (ipc->sock > -1) {
197                 CtdlIPC_putline(ipc, "QUIT");
198                 CtdlIPC_getline(ipc, aaa);
199                 ret = atoi(aaa);
200         }
201 #ifdef HAVE_OPENSSL
202         if (ipc->ssl)
203                 SSL_shutdown(ipc->ssl);
204         ipc->ssl = NULL;
205 #endif
206         if (ipc->sock)
207                 shutdown(ipc->sock, 2); /* Close connection; we're dead */
208         ipc->sock = -1;
209         CtdlIPC_unlock(ipc);
210         return ret;
211 }
212
213
214 /*
215  * Asks the server to log out.  Should always return 200, even if no user
216  * was logged in.  The user will not be logged in after this!
217  */
218 int CtdlIPCLogout(CtdlIPC * ipc) {
219         int ret;
220         char aaa[SIZ];
221
222         CtdlIPC_lock(ipc);
223         CtdlIPC_putline(ipc, "LOUT");
224         CtdlIPC_getline(ipc, aaa);
225         ret = atoi(aaa);
226         CtdlIPC_unlock(ipc);
227         return ret;
228 }
229
230
231 /*
232  * First stage of authentication - pass the username.  Returns 300 if the
233  * username is able to log in, with the username correctly spelled in cret.
234  * Returns various 500 error codes if the user doesn't exist, etc.
235  */
236 int CtdlIPCTryLogin(CtdlIPC * ipc, const char *username, char *cret) {
237         int ret;
238         char *aaa;
239
240         if (!username)
241                 return -2;
242         if (!cret)
243                 return -2;
244
245         aaa = (char *) malloc((size_t) (strlen(username) + 6));
246         if (!aaa)
247                 return -1;
248
249         sprintf(aaa, "USER %s", username);
250         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
251         free(aaa);
252         return ret;
253 }
254
255
256 /*
257  * Second stage of authentication - provide password.  The server returns
258  * 200 and several arguments in cret relating to the user's account.
259  */
260 int CtdlIPCTryPassword(CtdlIPC * ipc, const char *passwd, char *cret) {
261         int ret;
262         char *aaa;
263
264         if (!passwd)
265                 return -2;
266         if (!cret)
267                 return -2;
268
269         aaa = (char *) malloc((size_t) (strlen(passwd) + 6));
270         if (!aaa)
271                 return -1;
272
273         sprintf(aaa, "PASS %s", passwd);
274         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
275         free(aaa);
276         return ret;
277 }
278
279
280 /*
281  * Create a new user.  This returns 200 plus the same arguments as TryPassword
282  * if selfservice is nonzero, unless there was a problem creating the account.
283  * If selfservice is zero, creates a new user but does not log out the existing
284  * user - intended for use by system administrators to create accounts on
285  * behalf of other users.
286  */
287 int CtdlIPCCreateUser(CtdlIPC * ipc, const char *username, int selfservice, char *cret) {
288         int ret;
289         char *aaa;
290
291         if (!username)
292                 return -2;
293         if (!cret)
294                 return -2;
295
296         aaa = (char *) malloc((size_t) (strlen(username) + 6));
297         if (!aaa)
298                 return -1;
299
300         sprintf(aaa, "%s %s", selfservice ? "NEWU" : "CREU", username);
301         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
302         free(aaa);
303         return ret;
304 }
305
306
307 /*
308  * Changes the user's password.  Returns 200 if changed, errors otherwise.
309  */
310 int CtdlIPCChangePassword(CtdlIPC * ipc, const char *passwd, char *cret) {
311         int ret;
312         char *aaa;
313
314         if (!passwd)
315                 return -2;
316         if (!cret)
317                 return -2;
318
319         aaa = (char *) malloc((size_t) (strlen(passwd) + 6));
320         if (!aaa)
321                 return -1;
322
323         sprintf(aaa, "SETP %s", passwd);
324         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
325         free(aaa);
326         return ret;
327 }
328
329
330 /* LKRN */
331
332 /* Caller must free the march list */
333
334 /* Room types are defined in enum RoomList; keep these in sync! */
335
336 /* floor is -1 for all, or floornum */
337 int CtdlIPCKnownRooms(CtdlIPC * ipc, enum RoomList which, int floor, struct march **listing, char *cret) {
338         int ret;
339         struct march *march = NULL;
340         static char *proto[] = { "LKRA", "LKRN", "LKRO", "LZRM", "LRMS", "LPRM" };
341         char aaa[SIZ];
342         char *bbb = NULL;
343         size_t bbb_len;
344
345         if (!listing)
346                 return -2;
347         if (*listing)
348                 return -2;      /* Free the listing first */
349         if (!cret)
350                 return -2;
351         /* if (which < 0 || which > 4) return -2; */
352         if (floor < -1)
353                 return -2;      /* Can't validate upper bound, sorry */
354
355         sprintf(aaa, "%s %d", proto[which], floor);
356         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
357         if (ret / 100 == 1) {
358                 struct march *mptr;
359
360                 while (bbb && strlen(bbb)) {
361                         int a;
362
363                         extract_token(aaa, bbb, 0, '\n', sizeof aaa);
364                         a = strlen(aaa);
365                         memmove(bbb, bbb + a + 1, strlen(bbb) - a);
366                         mptr = (struct march *) malloc(sizeof(struct march));
367                         if (mptr) {
368                                 mptr->next = NULL;
369                                 extract_token(mptr->march_name, aaa, 0, '|', sizeof mptr->march_name);
370                                 mptr->march_flags = (unsigned int) extract_int(aaa, 1);
371                                 mptr->march_floor = (char) extract_int(aaa, 2);
372                                 mptr->march_order = (char) extract_int(aaa, 3);
373                                 mptr->march_flags2 = (unsigned int) extract_int(aaa, 4);
374                                 mptr->march_access = (char) extract_int(aaa, 5);
375                                 if (march == NULL)
376                                         march = mptr;
377                                 else {
378                                         struct march *mptr2;
379
380                                         mptr2 = march;
381                                         while (mptr2->next != NULL)
382                                                 mptr2 = mptr2->next;
383                                         mptr2->next = mptr;
384                                 }
385                         }
386                 }
387         }
388         *listing = march;
389         if (bbb)
390                 free(bbb);
391         return ret;
392 }
393
394
395 /* GETU */
396
397 /* Caller must free the struct ctdluser; caller may pass an existing one */
398 int CtdlIPCGetConfig(CtdlIPC * ipc, struct ctdluser **uret, char *cret) {
399         int ret;
400
401         if (!cret)
402                 return -2;
403         if (!uret)
404                 return -2;
405         if (!*uret)
406                 *uret = (struct ctdluser *) calloc(1, sizeof(struct ctdluser));
407         if (!*uret)
408                 return -1;
409
410         ret = CtdlIPCGenericCommand(ipc, "GETU", NULL, 0, NULL, NULL, cret);
411         if (ret / 100 == 2) {
412                 uret[0]->flags = extract_int(cret, 2);
413         }
414         return ret;
415 }
416
417
418 /* SETU */
419 int CtdlIPCSetConfig(CtdlIPC * ipc, struct ctdluser *uret, char *cret) {
420         char aaa[48];
421
422         if (!uret)
423                 return -2;
424         if (!cret)
425                 return -2;
426
427         sprintf(aaa, "SETU 80|24|%d", uret->flags);
428         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
429 }
430
431
432 /* RENU */
433 int CtdlIPCRenameUser(CtdlIPC * ipc, char *oldname, char *newname, char *cret) {
434         int ret;
435         char cmd[256];
436
437         if (!oldname)
438                 return -2;
439         if (!newname)
440                 return -2;
441         if (!cret)
442                 return -2;
443
444         snprintf(cmd, sizeof cmd, "RENU %s|%s", oldname, newname);
445         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
446         return ret;
447 }
448
449
450 /* GOTO */
451 int CtdlIPCGotoRoom(CtdlIPC * ipc, const char *room, const char *passwd, struct ctdlipcroom **rret, char *cret) {
452         int ret;
453         char *aaa;
454
455         if (!cret)
456                 return -2;
457         if (!rret)
458                 return -2;
459         if (!*rret)
460                 *rret = (struct ctdlipcroom *) calloc(1, sizeof(struct ctdlipcroom));
461         if (!*rret)
462                 return -1;
463
464         if (passwd) {
465                 aaa = (char *) malloc(strlen(room) + strlen(passwd) + 7);
466                 if (!aaa) {
467                         free(*rret);
468                         return -1;
469                 }
470                 sprintf(aaa, "GOTO %s|%s", room, passwd);
471         }
472         else {
473                 aaa = (char *) malloc(strlen(room) + 6);
474                 if (!aaa) {
475                         free(*rret);
476                         return -1;
477                 }
478                 sprintf(aaa, "GOTO %s", room);
479         }
480         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
481         if (ret / 100 == 2) {
482                 extract_token(rret[0]->RRname, cret, 0, '|', sizeof rret[0]->RRname);
483                 rret[0]->RRunread = extract_long(cret, 1);
484                 rret[0]->RRtotal = extract_long(cret, 2);
485                 rret[0]->RRinfoupdated = extract_int(cret, 3);
486                 rret[0]->RRflags = extract_int(cret, 4);
487                 rret[0]->RRhighest = extract_long(cret, 5);
488                 rret[0]->RRlastread = extract_long(cret, 6);
489                 rret[0]->RRismailbox = extract_int(cret, 7);
490                 rret[0]->RRaide = extract_int(cret, 8);
491                 // position 9 is no longer used
492                 rret[0]->RRfloor = extract_int(cret, 10);
493                 rret[0]->RRcurrentview = extract_int(cret, 11);
494                 rret[0]->RRdefaultview = extract_int(cret, 12);
495                 // position 13 is a trash folder flag ... irrelevant in this client
496                 rret[0]->RRflags2 = extract_int(cret, 14);
497         }
498         else {
499                 free(*rret);
500                 *rret = NULL;
501         }
502         free(aaa);
503         return ret;
504 }
505
506
507 /* MSGS */
508
509 /* which is 0 = all, 1 = old, 2 = new, 3 = last, 4 = first, 5 = gt, 6 = lt */
510
511 /* whicharg is number of messages, applies to last, first, gt, lt */
512 int CtdlIPCGetMessages(CtdlIPC * ipc, enum MessageList which, int whicharg, const char *mtemplate, unsigned long **mret, char *cret) {
513         int ret;
514         unsigned long count = 0;
515         static char *proto[] = { "ALL", "OLD", "NEW", "LAST", "FIRST", "GT", "LT" };
516         char aaa[33];
517         char *bbb = NULL;
518         size_t bbb_len;
519
520         if (!cret)
521                 return -2;
522         if (!mret)
523                 return -2;
524         if (*mret)
525                 return -2;
526         if (which < 0 || which > 6)
527                 return -2;
528
529         if (which <= 2)
530                 sprintf(aaa, "MSGS %s||%d", proto[which], (mtemplate) ? 1 : 0);
531         else
532                 sprintf(aaa, "MSGS %s|%d|%d", proto[which], whicharg, (mtemplate) ? 1 : 0);
533         if (mtemplate)
534                 count = strlen(mtemplate);
535         ret = CtdlIPCGenericCommand(ipc, aaa, mtemplate, count, &bbb, &bbb_len, cret);
536         if (ret / 100 != 1)
537                 return ret;
538         count = 0;
539         *mret = (unsigned long *) calloc(1, sizeof(unsigned long));
540         if (!*mret)
541                 return -1;
542         while (bbb && strlen(bbb)) {
543                 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
544                 remove_token(bbb, 0, '\n');
545                 *mret = (unsigned long *) realloc(*mret, (size_t) ((count + 2) * sizeof(unsigned long)));
546                 if (*mret) {
547                         (*mret)[count++] = atol(aaa);
548                         (*mret)[count] = 0L;
549                 }
550                 else {
551                         break;
552                 }
553         }
554         if (bbb)
555                 free(bbb);
556         return ret;
557 }
558
559
560 /* MSG0, MSG2 */
561 int CtdlIPCGetSingleMessage(CtdlIPC * ipc, long msgnum, int headers, int as_mime, struct ctdlipcmessage **mret, char *cret) {
562         int ret;
563         char aaa[SIZ];
564         char *bbb = NULL;
565         size_t bbb_len;
566         int multipart_hunting = 0;
567         char multipart_prefix[128];
568         char encoding[256];
569
570         if (!cret)
571                 return -1;
572         if (!mret)
573                 return -1;
574         if (!*mret)
575                 *mret = (struct ctdlipcmessage *) calloc(1, sizeof(struct ctdlipcmessage));
576         if (!*mret)
577                 return -1;
578         if (!msgnum)
579                 return -1;
580
581         strcpy(encoding, "");
582         strcpy(mret[0]->content_type, "");
583         mret[0]->is_local = 0;
584         sprintf(aaa, "MSG%d %ld|%d", as_mime, msgnum, headers);
585         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
586         if (ret / 100 == 1) {
587                 if (as_mime != 2) {
588                         strcpy(mret[0]->mime_chosen, "1");      /* Default chosen-part is "1" */
589                         while (strlen(bbb) > 4 && bbb[4] == '=') {
590                                 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
591                                 remove_token(bbb, 0, '\n');
592
593                                 if (!strncasecmp(aaa, "nhdr=yes", 8))
594                                         mret[0]->nhdr = 1;
595                                 else if (!strncasecmp(aaa, "from=", 5))
596                                         strncpy(mret[0]->author, &aaa[5], SIZ);
597                                 else if (!strncasecmp(aaa, "type=", 5))
598                                         mret[0]->type = atoi(&aaa[5]);
599                                 else if (!strncasecmp(aaa, "msgn=", 5))
600                                         strncpy(mret[0]->msgid, &aaa[5], SIZ);
601                                 else if (!strncasecmp(aaa, "subj=", 5))
602                                         strncpy(mret[0]->subject, &aaa[5], SIZ);
603                                 else if (!strncasecmp(aaa, "rfca=", 5))
604                                         strncpy(mret[0]->email, &aaa[5], SIZ);
605                                 else if (!strncasecmp(aaa, "room=", 5))
606                                         strncpy(mret[0]->room, &aaa[5], SIZ);
607                                 else if (!strncasecmp(aaa, "rcpt=", 5))
608                                         strncpy(mret[0]->recipient, &aaa[5], SIZ);
609                                 else if (!strncasecmp(aaa, "wefw=", 5))
610                                         strncpy(mret[0]->references, &aaa[5], SIZ);
611                                 else if (!strncasecmp(aaa, "time=", 5))
612                                         mret[0]->time = atol(&aaa[5]);
613                                 else if (!strncasecmp(aaa, "locl", 4))
614                                         mret[0]->is_local = 1;
615
616                                 /* Multipart/alternative prefix & suffix strings help
617                                  * us to determine which part we want to download.
618                                  */
619                                 else if (!strncasecmp(aaa, "pref=", 5)) {
620                                         extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
621                                         if (!strcasecmp(multipart_prefix, "multipart/alternative")) {
622                                                 ++multipart_hunting;
623                                         }
624                                 }
625                                 else if (!strncasecmp(aaa, "suff=", 5)) {
626                                         extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
627                                         if (!strcasecmp(multipart_prefix, "multipart/alternative")) {
628                                                 ++multipart_hunting;
629                                         }
630                                 }
631
632                                 else if (!strncasecmp(aaa, "part=", 5)) {
633                                         struct parts *ptr, *chain;
634
635                                         ptr = (struct parts *) calloc(1, sizeof(struct parts));
636                                         if (ptr) {
637
638                                                 /* Fill the buffers for the caller */
639                                                 extract_token(ptr->name, &aaa[5], 0, '|', sizeof ptr->name);
640                                                 extract_token(ptr->filename, &aaa[5], 1, '|', sizeof ptr->filename);
641                                                 extract_token(ptr->number, &aaa[5], 2, '|', sizeof ptr->number);
642                                                 extract_token(ptr->disposition, &aaa[5], 3, '|', sizeof ptr->disposition);
643                                                 extract_token(ptr->mimetype, &aaa[5], 4, '|', sizeof ptr->mimetype);
644                                                 ptr->length = extract_long(&aaa[5], 5);
645                                                 if (!mret[0]->attachments)
646                                                         mret[0]->attachments = ptr;
647                                                 else {
648                                                         chain = mret[0]->attachments;
649                                                         while (chain->next)
650                                                                 chain = chain->next;
651                                                         chain->next = ptr;
652                                                 }
653
654                                                 /* Now handle multipart/alternative */
655                                                 if (multipart_hunting > 0) {
656                                                         if ((!strcasecmp(ptr->mimetype, "text/plain"))
657                                                             || (!strcasecmp(ptr->mimetype, "text/html"))) {
658                                                                 strcpy(mret[0]->mime_chosen, ptr->number);
659                                                         }
660                                                 }
661
662                                         }
663                                 }
664                         }
665                         /* Eliminate "text\n" */
666                         remove_token(bbb, 0, '\n');
667
668                         /* If doing a MIME thing, pull out the extra headers */
669                         if (as_mime == 4) {
670                                 do {
671                                         if (!strncasecmp(bbb, "Content-type:", 13)) {
672                                                 extract_token(mret[0]->content_type, bbb, 0, '\n', sizeof mret[0]->content_type);
673                                                 strcpy(mret[0]->content_type, &mret[0]->content_type[13]);
674                                                 string_trim(mret[0]->content_type);
675
676                                                 /* strip out ";charset=" portion.  FIXME do something with
677                                                  * the charset (like... convert it) instead of just throwing
678                                                  * it away
679                                                  */
680                                                 if (strstr(mret[0]->content_type, ";") != NULL) {
681                                                         strcpy(strstr(mret[0]->content_type, ";"), "");
682                                                 }
683
684                                         }
685                                         if (!strncasecmp(bbb, "X-Citadel-MSG4-Partnum:", 23)) {
686                                                 extract_token(mret[0]->mime_chosen, bbb, 0, '\n', sizeof mret[0]->mime_chosen);
687                                                 strcpy(mret[0]->mime_chosen, &mret[0]->mime_chosen[23]);
688                                                 string_trim(mret[0]->mime_chosen);
689                                         }
690                                         if (!strncasecmp(bbb, "Content-transfer-encoding:", 26)) {
691                                                 extract_token(encoding, bbb, 0, '\n', sizeof encoding);
692                                                 strcpy(encoding, &encoding[26]);
693                                                 string_trim(encoding);
694                                         }
695                                         remove_token(bbb, 0, '\n');
696                                 } while ((bbb[0] != 0) && (bbb[0] != '\n'));
697                                 remove_token(bbb, 0, '\n');
698                         }
699
700
701                 }
702                 if (strlen(bbb)) {
703
704                         if ((!strcasecmp(encoding, "base64")) || (!strcasecmp(encoding, "quoted-printable"))) {
705                                 char *ccc = NULL;
706                                 int bytes_decoded = 0;
707                                 ccc = malloc(strlen(bbb) + 32768);
708                                 if (!strcasecmp(encoding, "base64")) {
709                                         bytes_decoded = CtdlDecodeBase64(ccc, bbb, strlen(bbb));
710                                 }
711                                 else if (!strcasecmp(encoding, "quoted-printable")) {
712                                         bytes_decoded = CtdlDecodeQuotedPrintable(ccc, bbb, strlen(bbb));
713                                 }
714                                 ccc[bytes_decoded] = 0;
715                                 free(bbb);
716                                 bbb = ccc;
717                         }
718
719                         /* FIXME: Strip trailing whitespace */
720                         bbb = (char *) realloc(bbb, (size_t) (strlen(bbb) + 1));
721
722                 }
723                 else {
724                         bbb = (char *) realloc(bbb, 1);
725                         *bbb = '\0';
726                 }
727                 mret[0]->text = bbb;
728         }
729         return ret;
730 }
731
732
733 /* WHOK */
734 int CtdlIPCWhoKnowsRoom(CtdlIPC * ipc, char **listing, char *cret) {
735         int ret;
736         size_t bytes;
737
738         if (!cret)
739                 return -2;
740         if (!listing)
741                 return -2;
742         if (*listing)
743                 return -2;
744
745         ret = CtdlIPCGenericCommand(ipc, "WHOK", NULL, 0, listing, &bytes, cret);
746         return ret;
747 }
748
749
750 /* INFO */
751 int CtdlIPCServerInfo(CtdlIPC * ipc, char *cret) {
752         int ret;
753         size_t bytes;
754         char *listing = NULL;
755         char buf[SIZ];
756
757         if (!cret)
758                 return -2;
759
760         ret = CtdlIPCGenericCommand(ipc, "INFO", NULL, 0, &listing, &bytes, cret);
761         if (ret / 100 == 1) {
762                 int line = 0;
763
764                 while (*listing && strlen(listing)) {
765                         extract_token(buf, listing, 0, '\n', sizeof buf);
766                         remove_token(listing, 0, '\n');
767                         switch (line++) {
768                         case 0:
769                                 ipc->ServInfo.pid = atoi(buf);
770                                 break;
771                         case 1:
772                                 strcpy(ipc->ServInfo.nodename, buf);
773                                 break;
774                         case 2:
775                                 strcpy(ipc->ServInfo.humannode, buf);
776                                 break;
777                         case 3:
778                                 strcpy(ipc->ServInfo.fqdn, buf);
779                                 break;
780                         case 4:
781                                 strcpy(ipc->ServInfo.software, buf);
782                                 break;
783                         case 5:
784                                 ipc->ServInfo.rev_level = atoi(buf);
785                                 break;
786                         case 6:
787                                 strcpy(ipc->ServInfo.site_location, buf);
788                                 break;
789                         case 7:
790                                 strcpy(ipc->ServInfo.sysadm, buf);
791                                 break;
792                         case 9:
793                                 strcpy(ipc->ServInfo.moreprompt, buf);
794                                 break;
795                         case 10:
796                                 ipc->ServInfo.ok_floors = atoi(buf);
797                                 break;
798                         case 14:
799                                 ipc->ServInfo.supports_ldap = atoi(buf);
800                                 break;
801                         case 15:
802                                 ipc->ServInfo.newuser_disabled = atoi(buf);
803                                 break;
804                         case 16:
805                                 strcpy(ipc->ServInfo.default_cal_zone, buf);
806                                 break;
807                         case 17:
808                                 ipc->ServInfo.load_avg = atof(buf);
809                                 break;
810                         case 18:
811                                 ipc->ServInfo.worker_avg = atof(buf);
812                                 break;
813                         case 19:
814                                 ipc->ServInfo.thread_count = atoi(buf);
815                                 break;
816                         case 20:
817                                 ipc->ServInfo.has_sieve = atoi(buf);
818                                 break;
819                         case 21:
820                                 ipc->ServInfo.fulltext_enabled = atoi(buf);
821                                 break;
822                         case 22:
823                                 strcpy(ipc->ServInfo.svn_revision, buf);
824                                 break;
825                         case 24:
826                                 ipc->ServInfo.guest_logins = atoi(buf);
827                                 break;
828                         }
829                 }
830
831         }
832         if (listing)
833                 free(listing);
834         return ret;
835 }
836
837
838 /* RDIR */
839 int CtdlIPCReadDirectory(CtdlIPC * ipc, char **listing, char *cret) {
840         int ret;
841         size_t bytes;
842
843         if (!cret)
844                 return -2;
845         if (!listing)
846                 return -2;
847         if (*listing)
848                 return -2;
849
850         ret = CtdlIPCGenericCommand(ipc, "RDIR", NULL, 0, listing, &bytes, cret);
851         return ret;
852 }
853
854
855 /*
856  * Set last-read pointer in this room to msgnum, or 0 for HIGHEST.
857  */
858 int CtdlIPCSetLastRead(CtdlIPC * ipc, long msgnum, char *cret) {
859         int ret;
860         char aaa[64];
861
862         if (!cret)
863                 return -2;
864
865         if (msgnum) {
866                 sprintf(aaa, "SLRP %ld", msgnum);
867         }
868         else {
869                 sprintf(aaa, "SLRP HIGHEST");
870         }
871         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
872         return ret;
873 }
874
875
876 /* INVT */
877 int CtdlIPCInviteUserToRoom(CtdlIPC * ipc, const char *username, char *cret) {
878         int ret;
879         char *aaa;
880
881         if (!cret)
882                 return -2;
883         if (!username)
884                 return -2;
885
886         aaa = (char *) malloc(strlen(username) + 6);
887         if (!aaa)
888                 return -1;
889
890         sprintf(aaa, "INVT %s", username);
891         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
892         free(aaa);
893         return ret;
894 }
895
896
897 /* KICK */
898 int CtdlIPCKickoutUserFromRoom(CtdlIPC * ipc, const char *username, char *cret) {
899         int ret;
900         char *aaa;
901
902         if (!cret)
903                 return -1;
904         if (!username)
905                 return -1;
906
907         aaa = (char *) malloc(strlen(username) + 6);
908
909         sprintf(aaa, "KICK %s", username);
910         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
911         free(aaa);
912         return ret;
913 }
914
915
916 /* GETR */
917 int CtdlIPCGetRoomAttributes(CtdlIPC * ipc, struct ctdlroom **qret, char *cret) {
918         int ret;
919
920         if (!cret)
921                 return -2;
922         if (!qret)
923                 return -2;
924         if (!*qret)
925                 *qret = (struct ctdlroom *) calloc(1, sizeof(struct ctdlroom));
926         if (!*qret)
927                 return -1;
928
929         ret = CtdlIPCGenericCommand(ipc, "GETR", NULL, 0, NULL, NULL, cret);
930         if (ret / 100 == 2) {
931                 extract_token(qret[0]->QRname, cret, 0, '|', sizeof qret[0]->QRname);
932                 extract_token(qret[0]->QRpasswd, cret, 1, '|', sizeof qret[0]->QRpasswd);
933                 extract_token(qret[0]->QRdirname, cret, 2, '|', sizeof qret[0]->QRdirname);
934                 qret[0]->QRflags = extract_int(cret, 3);
935                 qret[0]->QRfloor = extract_int(cret, 4);
936                 qret[0]->QRorder = extract_int(cret, 5);
937                 qret[0]->QRdefaultview = extract_int(cret, 6);
938                 qret[0]->QRflags2 = extract_int(cret, 7);
939         }
940         return ret;
941 }
942
943
944 /* SETR */
945 int CtdlIPCSetRoomAttributes(CtdlIPC * ipc, int forget, // if nonzero, kick all users out of the room
946                              struct ctdlroom *qret, char *cret) {
947         int ret;
948         char *cmd;
949
950         if (!cret)
951                 return -2;
952         if (!qret)
953                 return -2;
954
955         cmd = (char *) malloc(strlen(qret->QRname) + strlen(qret->QRpasswd) + strlen(qret->QRdirname) + 64);
956         if (!cmd)
957                 return -1;
958
959         sprintf(cmd, "SETR %s|%s|%s|%d|%d|%d|%d|%d|%d",
960                 qret->QRname, qret->QRpasswd, qret->QRdirname,
961                 qret->QRflags, forget, qret->QRfloor, qret->QRorder, qret->QRdefaultview, qret->QRflags2);
962         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
963         free(cmd);
964         return ret;
965 }
966
967
968 /* GETA */
969 int CtdlIPCGetRoomAide(CtdlIPC * ipc, char *cret) {
970         if (!cret)
971                 return -1;
972
973         return CtdlIPCGenericCommand(ipc, "GETA", NULL, 0, NULL, NULL, cret);
974 }
975
976
977 /* SETA */
978 int CtdlIPCSetRoomAide(CtdlIPC * ipc, const char *username, char *cret) {
979         int ret;
980         char *aaa;
981
982         if (!cret)
983                 return -2;
984         if (!username)
985                 return -2;
986
987         aaa = (char *) malloc(strlen(username) + 6);
988         if (!aaa)
989                 return -1;
990
991         sprintf(aaa, "SETA %s", username);
992         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
993         free(aaa);
994         return ret;
995 }
996
997
998 /* ENT0 */
999 int CtdlIPCPostMessage(CtdlIPC * ipc, int flag, int *subject_required, struct ctdlipcmessage *mr, char *cret) {
1000         int ret;
1001         char cmd[SIZ];
1002         char *ptr;
1003
1004         if (!cret)
1005                 return -2;
1006         if (!mr)
1007                 return -2;
1008
1009         if (mr->references) {
1010                 for (ptr = mr->references; *ptr != 0; ++ptr) {
1011                         if (*ptr == '|')
1012                                 *ptr = '!';
1013                 }
1014         }
1015
1016         snprintf(cmd, sizeof cmd,
1017                  "ENT0 %d|%s|%d|%d|%s|%s||||||%s|", flag, mr->recipient,
1018                  mr->anonymous, mr->type, mr->subject, mr->author, mr->references);
1019         ret = CtdlIPCGenericCommand(ipc, cmd, mr->text, strlen(mr->text), NULL, NULL, cret);
1020         if ((flag == 0) && (subject_required != NULL)) {
1021                 /* Is the server strongly recommending that the user enter a message subject? */
1022                 if ((cret[3] != '\0') && (cret[4] != '\0')) {
1023                         *subject_required = extract_int(&cret[4], 1);
1024                 }
1025
1026
1027         }
1028         return ret;
1029 }
1030
1031
1032 /* RINF */
1033 int CtdlIPCRoomInfo(CtdlIPC * ipc, char **iret, char *cret) {
1034         size_t bytes;
1035
1036         if (!cret)
1037                 return -2;
1038         if (!iret)
1039                 return -2;
1040         if (*iret)
1041                 return -2;
1042
1043         return CtdlIPCGenericCommand(ipc, "RINF", NULL, 0, iret, &bytes, cret);
1044 }
1045
1046
1047 /* DELE */
1048 int CtdlIPCDeleteMessage(CtdlIPC * ipc, long msgnum, char *cret) {
1049         char aaa[64];
1050
1051         if (!cret)
1052                 return -2;
1053         if (!msgnum)
1054                 return -2;
1055
1056         sprintf(aaa, "DELE %ld", msgnum);
1057         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1058 }
1059
1060
1061 /* MOVE */
1062 int CtdlIPCMoveMessage(CtdlIPC * ipc, int copy, long msgnum, const char *destroom, char *cret) {
1063         int ret;
1064         char *aaa;
1065
1066         if (!cret)
1067                 return -2;
1068         if (!destroom)
1069                 return -2;
1070         if (!msgnum)
1071                 return -2;
1072
1073         aaa = (char *) malloc(strlen(destroom) + 28);
1074         if (!aaa)
1075                 return -1;
1076
1077         sprintf(aaa, "MOVE %ld|%s|%d", msgnum, destroom, copy);
1078         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1079         free(aaa);
1080         return ret;
1081 }
1082
1083
1084 /* KILL */
1085 int CtdlIPCDeleteRoom(CtdlIPC * ipc, int for_real, char *cret) {
1086         char aaa[64];
1087
1088         if (!cret)
1089                 return -2;
1090
1091         sprintf(aaa, "KILL %d", for_real);
1092         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1093 }
1094
1095
1096 /* CRE8 */
1097 int CtdlIPCCreateRoom(CtdlIPC * ipc, int for_real, const char *roomname, int type, const char *password, int floor, char *cret) {
1098         int ret;
1099         char *aaa;
1100
1101         if (!cret)
1102                 return -2;
1103         if (!roomname)
1104                 return -2;
1105
1106         if (password) {
1107                 aaa = (char *) malloc(strlen(roomname) + strlen(password) + 40);
1108                 if (!aaa)
1109                         return -1;
1110                 sprintf(aaa, "CRE8 %d|%s|%d|%s|%d", for_real, roomname, type, password, floor);
1111         }
1112         else {
1113                 aaa = (char *) malloc(strlen(roomname) + 40);
1114                 if (!aaa)
1115                         return -1;
1116                 sprintf(aaa, "CRE8 %d|%s|%d||%d", for_real, roomname, type, floor);
1117         }
1118         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1119         free(aaa);
1120         return ret;
1121 }
1122
1123
1124 /* FORG */
1125 int CtdlIPCForgetRoom(CtdlIPC * ipc, char *cret) {
1126         if (!cret)
1127                 return -2;
1128
1129         return CtdlIPCGenericCommand(ipc, "FORG", NULL, 0, NULL, NULL, cret);
1130 }
1131
1132
1133 /* MESG */
1134 int CtdlIPCSystemMessage(CtdlIPC * ipc, const char *message, char **mret, char *cret) {
1135         int ret;
1136         char *aaa;
1137         size_t bytes;
1138
1139         if (!cret)
1140                 return -2;
1141         if (!mret)
1142                 return -2;
1143         if (*mret)
1144                 return -2;
1145         if (!message)
1146                 return -2;
1147
1148         aaa = (char *) malloc(strlen(message) + 6);
1149         if (!aaa)
1150                 return -1;
1151
1152         sprintf(aaa, "MESG %s", message);
1153         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, mret, &bytes, cret);
1154         free(aaa);
1155         return ret;
1156 }
1157
1158
1159 /* GNUR */
1160 int CtdlIPCNextUnvalidatedUser(CtdlIPC * ipc, char *cret) {
1161         if (!cret)
1162                 return -2;
1163
1164         return CtdlIPCGenericCommand(ipc, "GNUR", NULL, 0, NULL, NULL, cret);
1165 }
1166
1167
1168 /* GREG */
1169 int CtdlIPCGetUserRegistration(CtdlIPC * ipc, const char *username, char **rret, char *cret) {
1170         int ret;
1171         char *aaa;
1172         size_t bytes;
1173
1174         if (!cret)
1175                 return -2;
1176         if (!rret)
1177                 return -2;
1178         if (*rret)
1179                 return -2;
1180
1181         if (username)
1182                 aaa = (char *) malloc(strlen(username) + 6);
1183         else
1184                 aaa = (char *) malloc(12);
1185         if (!aaa)
1186                 return -1;
1187
1188         if (username)
1189                 sprintf(aaa, "GREG %s", username);
1190         else
1191                 sprintf(aaa, "GREG _SELF_");
1192         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, rret, &bytes, cret);
1193         free(aaa);
1194         return ret;
1195 }
1196
1197
1198 /* VALI */
1199 int CtdlIPCValidateUser(CtdlIPC * ipc, const char *username, int axlevel, char *cret) {
1200         int ret;
1201         char *aaa;
1202
1203         if (!cret)
1204                 return -2;
1205         if (!username)
1206                 return -2;
1207         if (axlevel < AxDeleted || axlevel > AxAideU)
1208                 return -2;
1209
1210         aaa = (char *) malloc(strlen(username) + 17);
1211         if (!aaa)
1212                 return -1;
1213
1214         sprintf(aaa, "VALI %s|%d", username, axlevel);
1215         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1216         free(aaa);
1217         return ret;
1218 }
1219
1220
1221 /* EINF */
1222 int CtdlIPCSetRoomInfo(CtdlIPC * ipc, int for_real, const char *info, char *cret) {
1223         char aaa[64];
1224
1225         if (!cret)
1226                 return -1;
1227         if (!info)
1228                 return -1;
1229
1230         sprintf(aaa, "EINF %d", for_real);
1231         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1232 }
1233
1234
1235 /* LIST */
1236 int CtdlIPCUserListing(CtdlIPC * ipc, char *searchstring, char **listing, char *cret) {
1237         size_t bytes;
1238         char *cmd;
1239         int ret;
1240
1241         if (!cret)
1242                 return -1;
1243         if (!listing)
1244                 return -1;
1245         if (*listing)
1246                 return -1;
1247         if (!searchstring)
1248                 return -1;
1249
1250         cmd = malloc(strlen(searchstring) + 10);
1251         sprintf(cmd, "LIST %s", searchstring);
1252
1253         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, listing, &bytes, cret);
1254         free(cmd);
1255         return (ret);
1256 }
1257
1258
1259 /* REGI */
1260 int CtdlIPCSetRegistration(CtdlIPC * ipc, const char *info, char *cret) {
1261         if (!cret)
1262                 return -1;
1263         if (!info)
1264                 return -1;
1265
1266         return CtdlIPCGenericCommand(ipc, "REGI", info, strlen(info), NULL, NULL, cret);
1267 }
1268
1269
1270 /* CHEK */
1271 int CtdlIPCMiscCheck(CtdlIPC * ipc, struct ctdlipcmisc *chek, char *cret) {
1272         int ret;
1273
1274         if (!cret)
1275                 return -1;
1276         if (!chek)
1277                 return -1;
1278
1279         ret = CtdlIPCGenericCommand(ipc, "CHEK", NULL, 0, NULL, NULL, cret);
1280         if (ret / 100 == 2) {
1281                 chek->newmail = extract_long(cret, 0);
1282                 chek->needregis = extract_int(cret, 1);
1283                 chek->needvalid = extract_int(cret, 2);
1284         }
1285         return ret;
1286 }
1287
1288
1289 /* DELF */
1290 int CtdlIPCDeleteFile(CtdlIPC * ipc, const char *filename, char *cret) {
1291         int ret;
1292         char *aaa;
1293
1294         if (!cret)
1295                 return -2;
1296         if (!filename)
1297                 return -2;
1298
1299         aaa = (char *) malloc(strlen(filename) + 6);
1300         if (!aaa)
1301                 return -1;
1302
1303         sprintf(aaa, "DELF %s", filename);
1304         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1305         free(aaa);
1306         return ret;
1307 }
1308
1309
1310 /* MOVF */
1311 int CtdlIPCMoveFile(CtdlIPC * ipc, const char *filename, const char *destroom, char *cret) {
1312         int ret;
1313         char *aaa;
1314
1315         if (!cret)
1316                 return -2;
1317         if (!filename)
1318                 return -2;
1319         if (!destroom)
1320                 return -2;
1321
1322         aaa = (char *) malloc(strlen(filename) + strlen(destroom) + 7);
1323         if (!aaa)
1324                 return -1;
1325
1326         sprintf(aaa, "MOVF %s|%s", filename, destroom);
1327         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1328         free(aaa);
1329         return ret;
1330 }
1331
1332
1333 /* RWHO */
1334 int CtdlIPCOnlineUsers(CtdlIPC * ipc, char **listing, time_t * stamp, char *cret) {
1335         int ret;
1336         size_t bytes;
1337
1338         if (!cret)
1339                 return -1;
1340         if (!listing)
1341                 return -1;
1342         if (*listing)
1343                 return -1;
1344
1345         *stamp = CtdlIPCServerTime(ipc, cret);
1346         if (!*stamp)
1347                 *stamp = time(NULL);
1348         ret = CtdlIPCGenericCommand(ipc, "RWHO", NULL, 0, listing, &bytes, cret);
1349         return ret;
1350 }
1351
1352
1353 /* OPEN */
1354 int CtdlIPCFileDownload(CtdlIPC * ipc, const char *filename, void **buf, size_t resume, void (*progress_gauge_callback)
1355                          (CtdlIPC *, unsigned long, unsigned long), char *cret) {
1356         int ret;
1357         size_t bytes;
1358         time_t last_mod;
1359         char mimetype[SIZ];
1360         char *aaa;
1361
1362         if (!cret)
1363                 return -2;
1364         if (!filename)
1365                 return -2;
1366         if (!buf)
1367                 return -2;
1368         if (*buf)
1369                 return -2;
1370         if (ipc->downloading)
1371                 return -2;
1372
1373         aaa = (char *) malloc(strlen(filename) + 6);
1374         if (!aaa)
1375                 return -1;
1376
1377         sprintf(aaa, "OPEN %s", filename);
1378         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1379         free(aaa);
1380         if (ret / 100 == 2) {
1381                 ipc->downloading = 1;
1382                 bytes = extract_long(cret, 0);
1383                 last_mod = extract_int(cret, 1);
1384                 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1385                 ret = CtdlIPCReadDownload(ipc, buf, bytes, resume, progress_gauge_callback, cret);
1386                 ret = CtdlIPCEndDownload(ipc, cret);
1387                 if (ret / 100 == 2)
1388                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1389         }
1390         return ret;
1391 }
1392
1393
1394 /* OPNA */
1395 int CtdlIPCAttachmentDownload(CtdlIPC * ipc, long msgnum, const char *part, void **buf, void (*progress_gauge_callback)
1396                                (CtdlIPC *, unsigned long, unsigned long), char *cret) {
1397         int ret;
1398         size_t bytes;
1399         time_t last_mod;
1400         char filename[SIZ];
1401         char mimetype[SIZ];
1402         char aaa[SIZ];
1403
1404         if (!cret)
1405                 return -2;
1406         if (!buf)
1407                 return -2;
1408         if (*buf)
1409                 return -2;
1410         if (!part)
1411                 return -2;
1412         if (!msgnum)
1413                 return -2;
1414         if (ipc->downloading)
1415                 return -2;
1416
1417         sprintf(aaa, "OPNA %ld|%s", msgnum, part);
1418         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1419         if (ret / 100 == 2) {
1420                 ipc->downloading = 1;
1421                 bytes = extract_long(cret, 0);
1422                 last_mod = extract_int(cret, 1);
1423                 extract_token(filename, cret, 2, '|', sizeof filename);
1424                 extract_token(mimetype, cret, 3, '|', sizeof mimetype);
1425                 /* ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1426                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1427                 ret = CtdlIPCEndDownload(ipc, cret);
1428                 if (ret / 100 == 2)
1429                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1430         }
1431         return ret;
1432 }
1433
1434
1435 /* OIMG */
1436 int CtdlIPCImageDownload(CtdlIPC * ipc, const char *filename, void **buf, void (*progress_gauge_callback)
1437                           (CtdlIPC *, unsigned long, unsigned long), char *cret) {
1438         int ret;
1439         size_t bytes;
1440         time_t last_mod;
1441         char mimetype[SIZ];
1442         char *aaa;
1443
1444         if (!cret)
1445                 return -1;
1446         if (!buf)
1447                 return -1;
1448         if (*buf)
1449                 return -1;
1450         if (!filename)
1451                 return -1;
1452         if (ipc->downloading)
1453                 return -1;
1454
1455         aaa = (char *) malloc(strlen(filename) + 6);
1456         if (!aaa)
1457                 return -1;
1458
1459         sprintf(aaa, "OIMG %s", filename);
1460         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1461         free(aaa);
1462         if (ret / 100 == 2) {
1463                 ipc->downloading = 1;
1464                 bytes = extract_long(cret, 0);
1465                 last_mod = extract_int(cret, 1);
1466                 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1467
1468 /*              ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1469                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1470                 ret = CtdlIPCEndDownload(ipc, cret);
1471                 if (ret / 100 == 2)
1472                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1473         }
1474         return ret;
1475 }
1476
1477
1478 /* UOPN */
1479 int CtdlIPCFileUpload(CtdlIPC * ipc, const char *save_as, const char *comment, const char *path, void (*progress_gauge_callback)
1480                        (CtdlIPC *, unsigned long, unsigned long), char *cret) {
1481         int ret;
1482         char *aaa;
1483         FILE *uploadFP;
1484         char MimeTestBuf[64];
1485         const char *MimeType;
1486         long len;
1487
1488         if (!cret)
1489                 return -1;
1490         if (!save_as)
1491                 return -1;
1492         if (!comment)
1493                 return -1;
1494         if (!path)
1495                 return -1;
1496         if (!*path)
1497                 return -1;
1498         if (ipc->uploading)
1499                 return -1;
1500
1501         uploadFP = fopen(path, "r");
1502         if (!uploadFP)
1503                 return -2;
1504
1505         len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1506         rewind(uploadFP);
1507         if (len < 0)
1508                 return -3;
1509
1510         MimeType = GuessMimeType(&MimeTestBuf[0], len);
1511         aaa = (char *) malloc(strlen(save_as) + strlen(MimeType) + strlen(comment) + 7);
1512         if (!aaa)
1513                 return -1;
1514
1515         sprintf(aaa, "UOPN %s|%s|%s", save_as, MimeType, comment);
1516         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1517         free(aaa);
1518         if (ret / 100 == 2) {
1519                 ipc->uploading = 1;
1520                 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1521                 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1522                 ipc->uploading = 0;
1523         }
1524         return ret;
1525 }
1526
1527
1528 /* UIMG */
1529 int CtdlIPCImageUpload(CtdlIPC * ipc, int for_real, const char *path, const char *save_as, void (*progress_gauge_callback)
1530                         (CtdlIPC *, unsigned long, unsigned long), char *cret) {
1531         int ret;
1532         FILE *uploadFP;
1533         char *aaa;
1534         char MimeTestBuf[64];
1535         const char *MimeType;
1536         long len;
1537
1538         if (!cret)
1539                 return -1;
1540         if (!save_as)
1541                 return -1;
1542         if (!path && for_real)
1543                 return -1;
1544         if (!*path && for_real)
1545                 return -1;
1546         if (ipc->uploading)
1547                 return -1;
1548
1549         aaa = (char *) malloc(strlen(save_as) + 17);
1550         if (!aaa)
1551                 return -1;
1552
1553         uploadFP = fopen(path, "r");
1554         if (!uploadFP)
1555                 return -2;
1556
1557         len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1558         rewind(uploadFP);
1559         if (len < 0)
1560                 return -3;
1561         MimeType = GuessMimeType(&MimeTestBuf[0], 64);
1562
1563         sprintf(aaa, "UIMG %d|%s|%s", for_real, MimeType, save_as);
1564         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1565         free(aaa);
1566         if (ret / 100 == 2 && for_real) {
1567                 ipc->uploading = 1;
1568                 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1569                 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1570                 ipc->uploading = 0;
1571         }
1572         return ret;
1573 }
1574
1575
1576 /* QUSR */
1577 int CtdlIPCQueryUsername(CtdlIPC * ipc, const char *username, char *cret) {
1578         int ret;
1579         char *aaa;
1580
1581         if (!cret)
1582                 return -2;
1583         if (!username)
1584                 return -2;
1585
1586         aaa = (char *) malloc(strlen(username) + 6);
1587         if (!aaa)
1588                 return -1;
1589
1590         sprintf(aaa, "QUSR %s", username);
1591         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1592         free(aaa);
1593         return ret;
1594 }
1595
1596
1597 /* LFLR */
1598 int CtdlIPCFloorListing(CtdlIPC * ipc, char **listing, char *cret) {
1599         size_t bytes;
1600
1601         if (!cret)
1602                 return -2;
1603         if (!listing)
1604                 return -2;
1605         if (*listing)
1606                 return -2;
1607
1608         return CtdlIPCGenericCommand(ipc, "LFLR", NULL, 0, listing, &bytes, cret);
1609 }
1610
1611
1612 /* CFLR */
1613 int CtdlIPCCreateFloor(CtdlIPC * ipc, int for_real, const char *name, char *cret) {
1614         int ret;
1615         char aaa[SIZ];
1616
1617         if (!cret)
1618                 return -2;
1619         if (!name)
1620                 return -2;
1621
1622         sprintf(aaa, "CFLR %s|%d", name, for_real);
1623         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1624         return ret;
1625 }
1626
1627
1628 /* KFLR */
1629 int CtdlIPCDeleteFloor(CtdlIPC * ipc, int for_real, int floornum, char *cret) {
1630         char aaa[SIZ];
1631
1632         if (!cret)
1633                 return -1;
1634         if (floornum < 0)
1635                 return -1;
1636
1637         sprintf(aaa, "KFLR %d|%d", floornum, for_real);
1638         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1639 }
1640
1641
1642 /* EFLR */
1643 int CtdlIPCEditFloor(CtdlIPC * ipc, int floornum, const char *floorname, char *cret) {
1644         int ret;
1645         char aaa[SIZ];
1646
1647         if (!cret)
1648                 return -2;
1649         if (!floorname)
1650                 return -2;
1651         if (floornum < 0)
1652                 return -2;
1653
1654         sprintf(aaa, "EFLR %d|%s", floornum, floorname);
1655         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1656         return ret;
1657 }
1658
1659
1660 /*
1661  * IDEN 
1662  *
1663  * You only need to fill out hostname, the defaults will be used if any of the
1664  * other fields are not set properly.
1665  */
1666 int CtdlIPCIdentifySoftware(CtdlIPC * ipc, int developerid, int clientid,
1667                             int revision, const char *software_name, const char *hostname, char *cret) {
1668         int ret;
1669         char *aaa;
1670
1671         if (developerid < 0 || clientid < 0 || revision < 0 || !software_name) {
1672                 developerid = 8;
1673                 clientid = 0;
1674                 revision = CLIENT_VERSION - 600;
1675                 software_name = "Citadel text client";
1676         }
1677         if (!hostname)
1678                 return -2;
1679
1680         aaa = (char *) malloc(strlen(software_name) + strlen(hostname) + 29);
1681         if (!aaa)
1682                 return -1;
1683
1684         sprintf(aaa, "IDEN %d|%d|%d|%s|%s", developerid, clientid, revision, software_name, hostname);
1685         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1686         free(aaa);
1687         return ret;
1688 }
1689
1690
1691 /* SEXP */
1692 int CtdlIPCSendInstantMessage(CtdlIPC * ipc, const char *username, const char *text, char *cret) {
1693         int ret;
1694         char *aaa;
1695
1696         if (!cret)
1697                 return -2;
1698         if (!username)
1699                 return -2;
1700
1701         aaa = (char *) malloc(strlen(username) + 8);
1702         if (!aaa)
1703                 return -1;
1704
1705         if (text) {
1706                 sprintf(aaa, "SEXP %s|-", username);
1707                 ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1708         }
1709         else {
1710                 sprintf(aaa, "SEXP %s||", username);
1711                 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1712         }
1713         free(aaa);
1714         return ret;
1715 }
1716
1717
1718 /* GEXP */
1719 int CtdlIPCGetInstantMessage(CtdlIPC * ipc, char **listing, char *cret) {
1720         size_t bytes;
1721
1722         if (!cret)
1723                 return -2;
1724         if (!listing)
1725                 return -2;
1726         if (*listing)
1727                 return -2;
1728
1729         return CtdlIPCGenericCommand(ipc, "GEXP", NULL, 0, listing, &bytes, cret);
1730 }
1731
1732
1733 /* DEXP */
1734
1735 /* mode is 0 = enable, 1 = disable, 2 = status */
1736 int CtdlIPCEnableInstantMessageReceipt(CtdlIPC * ipc, int mode, char *cret) {
1737         char aaa[64];
1738
1739         if (!cret)
1740                 return -2;
1741
1742         sprintf(aaa, "DEXP %d", mode);
1743         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1744 }
1745
1746
1747 /* EBIO */
1748 int CtdlIPCSetBio(CtdlIPC * ipc, char *bio, char *cret) {
1749         if (!cret)
1750                 return -2;
1751         if (!bio)
1752                 return -2;
1753
1754         return CtdlIPCGenericCommand(ipc, "EBIO", bio, strlen(bio), NULL, NULL, cret);
1755 }
1756
1757
1758 /* RBIO */
1759 int CtdlIPCGetBio(CtdlIPC * ipc, const char *username, char **listing, char *cret) {
1760         int ret;
1761         size_t bytes;
1762         char *aaa;
1763
1764         if (!cret)
1765                 return -2;
1766         if (!username)
1767                 return -2;
1768         if (!listing)
1769                 return -2;
1770         if (*listing)
1771                 return -2;
1772
1773         aaa = (char *) malloc(strlen(username) + 6);
1774         if (!aaa)
1775                 return -1;
1776
1777         sprintf(aaa, "RBIO %s", username);
1778         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
1779         free(aaa);
1780         return ret;
1781 }
1782
1783
1784 /* LBIO */
1785 int CtdlIPCListUsersWithBios(CtdlIPC * ipc, char **listing, char *cret) {
1786         size_t bytes;
1787
1788         if (!cret)
1789                 return -2;
1790         if (!listing)
1791                 return -2;
1792         if (*listing)
1793                 return -2;
1794
1795         return CtdlIPCGenericCommand(ipc, "LBIO", NULL, 0, listing, &bytes, cret);
1796 }
1797
1798
1799 /* STEL */
1800 int CtdlIPCStealthMode(CtdlIPC * ipc, int mode, char *cret) {
1801         char aaa[64];
1802
1803         if (!cret)
1804                 return -1;
1805
1806         sprintf(aaa, "STEL %d", mode ? 1 : 0);
1807         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1808 }
1809
1810
1811 /* TERM */
1812 int CtdlIPCTerminateSession(CtdlIPC * ipc, int sid, char *cret) {
1813         char aaa[64];
1814
1815         if (!cret)
1816                 return -1;
1817
1818         sprintf(aaa, "TERM %d", sid);
1819         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1820 }
1821
1822
1823 /* DOWN */
1824 int CtdlIPCTerminateServerNow(CtdlIPC * ipc, char *cret) {
1825         if (!cret)
1826                 return -1;
1827
1828         return CtdlIPCGenericCommand(ipc, "DOWN", NULL, 0, NULL, NULL, cret);
1829 }
1830
1831
1832 /* SCDN */
1833 int CtdlIPCTerminateServerScheduled(CtdlIPC * ipc, int mode, char *cret) {
1834         char aaa[16];
1835
1836         if (!cret)
1837                 return -1;
1838
1839         sprintf(aaa, "SCDN %d", mode ? 1 : 0);
1840         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1841 }
1842
1843
1844 /* EMSG */
1845 int CtdlIPCEnterSystemMessage(CtdlIPC * ipc, const char *filename, const char *text, char *cret) {
1846         int ret;
1847         char *aaa;
1848
1849         if (!cret)
1850                 return -2;
1851         if (!text)
1852                 return -2;
1853         if (!filename)
1854                 return -2;
1855
1856         aaa = (char *) malloc(strlen(filename) + 6);
1857         if (!aaa)
1858                 return -1;
1859
1860         sprintf(aaa, "EMSG %s", filename);
1861         ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1862         free(aaa);
1863         return ret;
1864 }
1865
1866
1867 /* TIME */
1868
1869 /* This function returns the actual server time reported, or 0 if error */
1870 time_t CtdlIPCServerTime(CtdlIPC * ipc, char *cret) {
1871         time_t tret;
1872         int ret;
1873
1874         ret = CtdlIPCGenericCommand(ipc, "TIME", NULL, 0, NULL, NULL, cret);
1875         if (ret / 100 == 2) {
1876                 tret = extract_long(cret, 0);
1877         }
1878         else {
1879                 tret = 0L;
1880         }
1881         return tret;
1882 }
1883
1884
1885 /* AGUP */
1886 int CtdlIPCAideGetUserParameters(CtdlIPC * ipc, const char *who, struct ctdluser **uret, char *cret) {
1887         int ret;
1888         char aaa[SIZ];
1889
1890         if (!cret)
1891                 return -2;
1892         if (!uret)
1893                 return -2;
1894         if (!*uret)
1895                 *uret = (struct ctdluser *) calloc(1, sizeof(struct ctdluser));
1896         if (!*uret)
1897                 return -1;
1898
1899         sprintf(aaa, "AGUP %s", who);
1900         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1901
1902         if (ret / 100 == 2) {
1903                 extract_token(uret[0]->fullname, cret, 0, '|', sizeof uret[0]->fullname);
1904                 extract_token(uret[0]->password, cret, 1, '|', sizeof uret[0]->password);
1905                 uret[0]->flags = extract_int(cret, 2);
1906                 uret[0]->axlevel = extract_int(cret, 5);
1907                 uret[0]->usernum = extract_long(cret, 6);
1908                 uret[0]->lastcall = extract_long(cret, 7);
1909                 uret[0]->USuserpurge = extract_int(cret, 8);
1910         }
1911         return ret;
1912 }
1913
1914
1915 /* ASUP */
1916 int CtdlIPCAideSetUserParameters(CtdlIPC * ipc, const struct ctdluser *uret, char *cret) {
1917         int ret;
1918         char *aaa;
1919
1920         if (!cret)
1921                 return -2;
1922         if (!uret)
1923                 return -2;
1924
1925         aaa = (char *) malloc(strlen(uret->fullname) + strlen(uret->password) + 84);
1926         if (!aaa)
1927                 return -1;
1928
1929         sprintf(aaa, "ASUP %s|%s|%d|0|0|%d|%ld|%ld|%d",
1930                 uret->fullname, uret->password, uret->flags, uret->axlevel, uret->usernum, uret->lastcall, uret->USuserpurge);
1931         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1932         free(aaa);
1933         return ret;
1934 }
1935
1936
1937 /* AGEA */
1938 int CtdlIPCAideGetEmailAddresses(CtdlIPC * ipc, const char *who, char *target_buf, char *cret) {
1939         int ret;
1940         char aaa[SIZ];
1941         char *emailaddrs = NULL;
1942         size_t emailaddrs_len = 0;
1943
1944         sprintf(aaa, "AGEA %s", who);
1945         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &emailaddrs, &emailaddrs_len, cret);
1946
1947         if (ret / 100 == 1) {
1948                 strcpy(target_buf, emailaddrs);
1949         }
1950
1951         if (emailaddrs != NULL) {
1952                 free(emailaddrs);
1953         }
1954
1955         return ret;
1956 }
1957
1958
1959 /* ASEA */
1960 int CtdlIPCAideSetEmailAddresses(CtdlIPC * ipc, const char *who, char *emailaddrs, char *cret) {
1961         char aaa[SIZ];
1962         int ret;
1963
1964         if (!who)
1965                 return -2;
1966         if (!emailaddrs)
1967                 return -2;
1968         if (!cret)
1969                 return -2;
1970
1971         sprintf(aaa, "ASEA %s", who);
1972         ret = CtdlIPCGenericCommand(ipc, aaa, emailaddrs, 0, NULL, NULL, cret);
1973         return ret;
1974 }
1975
1976
1977 /* GPEX */
1978
1979 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
1980
1981 /* caller must free the struct ExpirePolicy */
1982 int CtdlIPCGetMessageExpirationPolicy(CtdlIPC * ipc, GPEXWhichPolicy which, struct ExpirePolicy **policy, char *cret) {
1983         static char *proto[] = {
1984                 strof(roompolicy),
1985                 strof(floorpolicy),
1986                 strof(sitepolicy),
1987                 strof(mailboxespolicy)
1988         };
1989         char cmd[256];
1990         int ret;
1991
1992         if (!cret)
1993                 return -2;
1994         if (!policy)
1995                 return -2;
1996         if (!*policy)
1997                 *policy = (struct ExpirePolicy *) calloc(1, sizeof(struct ExpirePolicy));
1998         if (!*policy)
1999                 return -1;
2000         if (which < 0 || which > 3)
2001                 return -2;
2002
2003         sprintf(cmd, "GPEX %s", proto[which]);
2004         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2005         if (ret / 100 == 2) {
2006                 policy[0]->expire_mode = extract_int(cret, 0);
2007                 policy[0]->expire_value = extract_int(cret, 1);
2008         }
2009         return ret;
2010 }
2011
2012
2013 /* SPEX */
2014
2015 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2016
2017 /* policy is 0 = inherit, 1 = no purge, 2 = by count, 3 = by age (days) */
2018 int CtdlIPCSetMessageExpirationPolicy(CtdlIPC * ipc, int which, struct ExpirePolicy *policy, char *cret) {
2019         char aaa[38];
2020         char *whichvals[] = { "room", "floor", "site", "mailboxes" };
2021
2022         if (!cret)
2023                 return -2;
2024         if (which < 0 || which > 3)
2025                 return -2;
2026         if (!policy)
2027                 return -2;
2028         if (policy->expire_mode < 0 || policy->expire_mode > 3)
2029                 return -2;
2030         if (policy->expire_mode >= 2 && policy->expire_value < 1)
2031                 return -2;
2032
2033         sprintf(aaa, "SPEX %s|%d|%d", whichvals[which], policy->expire_mode, policy->expire_value);
2034         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2035 }
2036
2037
2038 /* CONF GET */
2039 int CtdlIPCGetSystemConfig(CtdlIPC * ipc, char **listing, char *cret) {
2040         size_t bytes;
2041
2042         if (!cret)
2043                 return -2;
2044         if (!listing)
2045                 return -2;
2046         if (*listing)
2047                 return -2;
2048
2049         return CtdlIPCGenericCommand(ipc, "CONF GET", NULL, 0, listing, &bytes, cret);
2050 }
2051
2052
2053 /* CONF SET */
2054 int CtdlIPCSetSystemConfig(CtdlIPC * ipc, const char *listing, char *cret) {
2055         if (!cret)
2056                 return -2;
2057         if (!listing)
2058                 return -2;
2059
2060         return CtdlIPCGenericCommand(ipc, "CONF SET", listing, strlen(listing), NULL, NULL, cret);
2061 }
2062
2063
2064 /* CONF GETSYS */
2065 int CtdlIPCGetSystemConfigByType(CtdlIPC * ipc, const char *mimetype, char **listing, char *cret) {
2066         int ret;
2067         char *aaa;
2068         size_t bytes;
2069
2070         if (!cret)
2071                 return -2;
2072         if (!mimetype)
2073                 return -2;
2074         if (!listing)
2075                 return -2;
2076         if (*listing)
2077                 return -2;
2078
2079         aaa = malloc(strlen(mimetype) + 13);
2080         if (!aaa)
2081                 return -1;
2082         sprintf(aaa, "CONF GETSYS|%s", mimetype);
2083         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
2084         free(aaa);
2085         return ret;
2086 }
2087
2088
2089 /* CONF PUTSYS */
2090 int CtdlIPCSetSystemConfigByType(CtdlIPC * ipc, const char *mimetype, const char *listing, char *cret) {
2091         int ret;
2092         char *aaa;
2093
2094         if (!cret)
2095                 return -2;
2096         if (!mimetype)
2097                 return -2;
2098         if (!listing)
2099                 return -2;
2100
2101         aaa = malloc(strlen(mimetype) + 13);
2102         if (!aaa)
2103                 return -1;
2104         sprintf(aaa, "CONF PUTSYS|%s", mimetype);
2105         ret = CtdlIPCGenericCommand(ipc, aaa, listing, strlen(listing), NULL, NULL, cret);
2106         free(aaa);
2107         return ret;
2108 }
2109
2110
2111 /* GNET */
2112 int CtdlIPCGetRoomNetworkConfig(CtdlIPC * ipc, char **listing, char *cret) {
2113         size_t bytes;
2114
2115         if (!cret)
2116                 return -2;
2117         if (!listing)
2118                 return -2;
2119         if (*listing)
2120                 return -2;
2121
2122         return CtdlIPCGenericCommand(ipc, "GNET", NULL, 0, listing, &bytes, cret);
2123 }
2124
2125
2126 /* SNET */
2127 int CtdlIPCSetRoomNetworkConfig(CtdlIPC * ipc, const char *listing, char *cret) {
2128         if (!cret)
2129                 return -2;
2130         if (!listing)
2131                 return -2;
2132
2133         return CtdlIPCGenericCommand(ipc, "SNET", listing, strlen(listing), NULL, NULL, cret);
2134 }
2135
2136
2137 /* REQT */
2138 int CtdlIPCRequestClientLogout(CtdlIPC * ipc, int session, char *cret) {
2139         char aaa[64];
2140
2141         if (!cret)
2142                 return -2;
2143         if (session < 0)
2144                 return -2;
2145
2146         sprintf(aaa, "REQT %d", session);
2147         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2148 }
2149
2150
2151 /* SEEN */
2152 int CtdlIPCSetMessageSeen(CtdlIPC * ipc, long msgnum, int seen, char *cret) {
2153         char aaa[27];
2154
2155         if (!cret)
2156                 return -2;
2157         if (msgnum < 0)
2158                 return -2;
2159
2160         sprintf(aaa, "SEEN %ld|%d", msgnum, seen ? 1 : 0);
2161         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2162 }
2163
2164
2165 /* STLS */
2166 int CtdlIPCStartEncryption(CtdlIPC * ipc, char *cret) {
2167         int a;
2168         int r;
2169         char buf[SIZ];
2170
2171 #ifdef HAVE_OPENSSL
2172         SSL *temp_ssl;
2173
2174         /* New SSL object */
2175         temp_ssl = SSL_new(ssl_ctx);
2176         if (!temp_ssl) {
2177                 error_printf("SSL_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2178                 return -2;
2179         }
2180         /* Pointless flag waving */
2181 #if SSLEAY_VERSION_NUMBER >= 0x0922
2182         SSL_set_session_id_context(temp_ssl, (const unsigned char *) "Citadel SID", 14);
2183 #endif
2184
2185         /* Associate network connection with SSL object */
2186         if (SSL_set_fd(temp_ssl, ipc->sock) < 1) {
2187                 error_printf("SSL_set_fd failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2188                 return -2;
2189         }
2190
2191         if (status_hook != NULL) {
2192                 status_hook("Requesting encryption...\r");
2193         }
2194
2195         /* Ready to start SSL/TLS */
2196         r = CtdlIPCGenericCommand(ipc, "STLS", NULL, 0, NULL, NULL, cret);
2197         if (r / 100 != 2) {
2198                 error_printf("Server can't start TLS: %s\n", buf);
2199                 endtls(temp_ssl);
2200                 return r;
2201         }
2202
2203         /* Do SSL/TLS handshake */
2204         if ((a = SSL_connect(temp_ssl)) < 1) {
2205                 error_printf("SSL_connect failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2206                 endtls(temp_ssl);
2207                 return -2;
2208         }
2209         ipc->ssl = temp_ssl;
2210
2211         error_printf("Encrypting with %s cipher %s\n",
2212                      SSL_CIPHER_get_version(SSL_get_current_cipher(ipc->ssl)), SSL_CIPHER_get_name(SSL_get_current_cipher(ipc->ssl))
2213             );
2214         return r;
2215 #else
2216         return 0;
2217 #endif                          /* HAVE_OPENSSL */
2218 }
2219
2220
2221 #ifdef HAVE_OPENSSL
2222 static void endtls(SSL * ssl) {
2223         if (ssl) {
2224                 SSL_shutdown(ssl);
2225                 SSL_free(ssl);
2226         }
2227 }
2228 #endif
2229
2230
2231 /* QDIR */
2232 int CtdlIPCDirectoryLookup(CtdlIPC * ipc, const char *address, char *cret) {
2233         int ret;
2234         char *aaa;
2235
2236         if (!address)
2237                 return -2;
2238         if (!cret)
2239                 return -2;
2240
2241         aaa = (char *) malloc(strlen(address) + 6);
2242         if (!aaa)
2243                 return -1;
2244
2245         sprintf(aaa, "QDIR %s", address);
2246         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2247         free(aaa);
2248         return ret;
2249 }
2250
2251
2252 /* IPGM */
2253 int CtdlIPCInternalProgram(CtdlIPC * ipc, int secret, char *cret) {
2254         char aaa[30];
2255
2256         if (!cret)
2257                 return -2;
2258         sprintf(aaa, "IPGM %d", secret);
2259         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2260 }
2261
2262
2263
2264
2265 /* ************************************************************************** */
2266
2267 /*           Stuff below this line is not for public consumption            */
2268
2269 /* ************************************************************************** */
2270
2271
2272 /* Read a listing from the server up to 000.  Append to dest if it exists */
2273 char *CtdlIPCReadListing(CtdlIPC * ipc, char *dest) {
2274         size_t length = 0;
2275         size_t linelength;
2276         char *ret = NULL;
2277         char aaa[SIZ];
2278
2279         ret = dest;
2280         if (ret != NULL) {
2281                 length = strlen(ret);
2282         }
2283         else {
2284                 length = 0;
2285         }
2286
2287         while (CtdlIPC_getline(ipc, aaa), strcmp(aaa, "000")) {
2288                 linelength = strlen(aaa);
2289                 ret = (char *) realloc(ret, (size_t) (length + linelength + 2));
2290                 if (ret) {
2291                         strcpy(&ret[length], aaa);
2292                         length += linelength;
2293                         strcpy(&ret[length++], "\n");
2294                 }
2295         }
2296
2297         return (ret);
2298 }
2299
2300
2301 /* Send a listing to the server; generate the ending 000. */
2302 int CtdlIPCSendListing(CtdlIPC * ipc, const char *listing) {
2303         char *text;
2304
2305         text = (char *) malloc(strlen(listing) + 6);
2306         if (text) {
2307                 strcpy(text, listing);
2308                 while (text[strlen(text) - 1] == '\n')
2309                         text[strlen(text) - 1] = '\0';
2310                 strcat(text, "\n000");
2311                 CtdlIPC_putline(ipc, text);
2312                 free(text);
2313                 text = NULL;
2314         }
2315         else {
2316                 /* Malloc failed but we are committed to send */
2317                 /* This may result in extra blanks at the bottom */
2318                 CtdlIPC_putline(ipc, text);
2319                 CtdlIPC_putline(ipc, "000");
2320         }
2321         return 0;
2322 }
2323
2324
2325 /* Partial read of file from server */
2326 size_t CtdlIPCPartialRead(CtdlIPC * ipc, void **buf, size_t offset, size_t bytes, char *cret) {
2327         size_t len = 0;
2328         char aaa[SIZ];
2329
2330         if (!buf)
2331                 return 0;
2332         if (!cret)
2333                 return 0;
2334         if (bytes < 1)
2335                 return 0;
2336
2337         CtdlIPC_lock(ipc);
2338         sprintf(aaa, "READ %d|%d", (int) offset, (int) bytes);
2339         CtdlIPC_putline(ipc, aaa);
2340         CtdlIPC_getline(ipc, aaa);
2341         if (aaa[0] != '6')
2342                 strcpy(cret, &aaa[4]);
2343         else {
2344                 len = extract_long(&aaa[4], 0);
2345                 *buf = (void *) realloc(*buf, (size_t) (offset + len));
2346                 if (*buf) {
2347                         /* I know what I'm doing */
2348                         serv_read(ipc, ((char *) (*buf) + offset), len);
2349                 }
2350                 else {
2351                         /* We have to read regardless */
2352                         serv_read(ipc, aaa, len);
2353                         len = 0;
2354                 }
2355         }
2356         CtdlIPC_unlock(ipc);
2357         return len;
2358 }
2359
2360
2361 /* CLOS */
2362 int CtdlIPCEndDownload(CtdlIPC * ipc, char *cret) {
2363         int ret;
2364
2365         if (!cret)
2366                 return -2;
2367         if (!ipc->downloading)
2368                 return -2;
2369
2370         ret = CtdlIPCGenericCommand(ipc, "CLOS", NULL, 0, NULL, NULL, cret);
2371         if (ret / 100 == 2)
2372                 ipc->downloading = 0;
2373         return ret;
2374 }
2375
2376
2377 /* MSGP */
2378 int CtdlIPCSpecifyPreferredFormats(CtdlIPC * ipc, char *cret, char *formats) {
2379         int ret;
2380         char cmd[SIZ];
2381
2382         snprintf(cmd, sizeof cmd, "MSGP %s", formats);
2383         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2384         return ret;
2385 }
2386
2387
2388
2389 /* READ */
2390 int CtdlIPCReadDownload(CtdlIPC * ipc, void **buf, size_t bytes, size_t resume, void (*progress_gauge_callback)
2391                          (CtdlIPC *, unsigned long, unsigned long), char *cret) {
2392         size_t len;
2393
2394         if (!cret)
2395                 return -1;
2396         if (!buf)
2397                 return -1;
2398         if (*buf)
2399                 return -1;
2400         if (!ipc->downloading)
2401                 return -1;
2402
2403         len = resume;
2404         if (progress_gauge_callback)
2405                 progress_gauge_callback(ipc, len, bytes);
2406         while (len < bytes) {
2407                 size_t block;
2408
2409                 block = CtdlIPCPartialRead(ipc, buf, len, 4096, cret);
2410                 if (block == 0) {
2411                         free(*buf);
2412                         return 0;
2413                 }
2414                 len += block;
2415                 if (progress_gauge_callback)
2416                         progress_gauge_callback(ipc, len, bytes);
2417         }
2418         return len;
2419 }
2420
2421 /* READ - pipelined */
2422 int CtdlIPCHighSpeedReadDownload(CtdlIPC * ipc, void **buf, size_t bytes, size_t resume, void (*progress_gauge_callback)
2423                                   (CtdlIPC *, unsigned long, unsigned long), char *cret) {
2424         size_t len;
2425         int calls;              /* How many calls in the pipeline */
2426         int i;                  /* iterator */
2427         char aaa[4096];
2428
2429         if (!cret)
2430                 return -1;
2431         if (!buf)
2432                 return -1;
2433         if (*buf)
2434                 return -1;
2435         if (!ipc->downloading)
2436                 return -1;
2437
2438         *buf = (void *) realloc(*buf, bytes - resume);
2439         if (!*buf)
2440                 return -1;
2441
2442         len = 0;
2443         CtdlIPC_lock(ipc);
2444         if (progress_gauge_callback)
2445                 progress_gauge_callback(ipc, len, bytes);
2446
2447         /* How many calls will be in the pipeline? */
2448         calls = (bytes - resume) / 4096;
2449         if ((bytes - resume) % 4096)
2450                 calls++;
2451
2452         /* Send all requests at once */
2453         for (i = 0; i < calls; i++) {
2454                 sprintf(aaa, "READ %d|4096", (int) (i * 4096 + resume));
2455                 CtdlIPC_putline(ipc, aaa);
2456         }
2457
2458         /* Receive all responses at once */
2459         for (i = 0; i < calls; i++) {
2460                 CtdlIPC_getline(ipc, aaa);
2461                 if (aaa[0] != '6')
2462                         strcpy(cret, &aaa[4]);
2463                 else {
2464                         len = extract_long(&aaa[4], 0);
2465                         /* I know what I'm doing */
2466                         serv_read(ipc, ((char *) (*buf) + (i * 4096)), len);
2467                 }
2468                 if (progress_gauge_callback)
2469                         progress_gauge_callback(ipc, i * 4096 + len, bytes);
2470         }
2471         CtdlIPC_unlock(ipc);
2472         return len;
2473 }
2474
2475
2476 /* UCLS */
2477 int CtdlIPCEndUpload(CtdlIPC * ipc, int discard, char *cret) {
2478         int ret;
2479         char cmd[8];
2480
2481         if (!cret)
2482                 return -1;
2483         if (!ipc->uploading)
2484                 return -1;
2485
2486         sprintf(cmd, "UCLS %d", discard ? 0 : 1);
2487         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2488         ipc->uploading = 0;
2489         return ret;
2490 }
2491
2492
2493 /* WRIT */
2494 int CtdlIPCWriteUpload(CtdlIPC * ipc, FILE * uploadFP, void (*progress_gauge_callback)
2495                         (CtdlIPC *, unsigned long, unsigned long), char *cret) {
2496         int ret = -1;
2497         size_t offset = 0;
2498         size_t bytes;
2499         char aaa[SIZ];
2500         char buf[4096];
2501         FILE *fd = uploadFP;
2502         int ferr;
2503
2504         if (!cret)
2505                 return -1;
2506
2507         fseek(fd, 0L, SEEK_END);
2508         bytes = ftell(fd);
2509         rewind(fd);
2510
2511         if (progress_gauge_callback)
2512                 progress_gauge_callback(ipc, 0, bytes);
2513
2514         while (offset < bytes) {
2515                 size_t to_write;
2516
2517                 /* Read some data in */
2518                 to_write = fread(buf, 1, 4096, fd);
2519                 if (!to_write) {
2520                         if (feof(fd) || ferror(fd))
2521                                 break;
2522                 }
2523                 sprintf(aaa, "WRIT %d", (int) to_write);
2524                 CtdlIPC_putline(ipc, aaa);
2525                 CtdlIPC_getline(ipc, aaa);
2526                 strcpy(cret, &aaa[4]);
2527                 ret = atoi(aaa);
2528                 if (aaa[0] == '7') {
2529                         to_write = extract_long(&aaa[4], 0);
2530
2531                         serv_write(ipc, buf, to_write);
2532                         offset += to_write;
2533                         if (progress_gauge_callback)
2534                                 progress_gauge_callback(ipc, offset, bytes);
2535                         /* Detect short reads and back up if needed */
2536                         /* offset will never be negative anyway */
2537                         fseek(fd, (signed) offset, SEEK_SET);
2538                 }
2539                 else {
2540                         break;
2541                 }
2542         }
2543         if (progress_gauge_callback)
2544                 progress_gauge_callback(ipc, 1, 1);
2545         ferr = ferror(fd);
2546         fclose(fd);
2547         return (!ferr ? ret : -2);
2548 }
2549
2550
2551 /*
2552  * Generic command method.  This method should handle any server command
2553  * except for CHAT.  It takes the following arguments:
2554  *
2555  * ipc                  The server to speak with
2556  * command              Preformatted command to send to server
2557  * to_send              A text or binary file to send to server
2558  *                      (only sent if server requests it)
2559  * bytes_to_send        The number of bytes in to_send (required if
2560  *                      sending binary, optional if sending listing)
2561  * to_receive           Pointer to a NULL pointer, if the server
2562  *                      sends text or binary we will allocate memory
2563  *                      for the file and stuff it here
2564  * bytes_to_receive     If a file is received, we will store its
2565  *                      byte count here
2566  * proto_response       The protocol response.  Caller must provide
2567  *                      this buffer and ensure that it is at least
2568  *                      128 bytes in length.
2569  *
2570  * This function returns a number equal to the protocol response number,
2571  * -1 if an internal error occurred, -2 if caller provided bad values,
2572  * or 0 - the protocol response number if bad values were found during
2573  * the protocol exchange.
2574  * 
2575  * It stores the protocol response string (minus the number) in 
2576  * protocol_response as described above.  Some commands send additional
2577  * data in this string.
2578  */
2579 int CtdlIPCGenericCommand(CtdlIPC * ipc,
2580                           const char *command,
2581                           const char *to_send,
2582                           size_t bytes_to_send, char **to_receive, size_t *bytes_to_receive, char *proto_response) {
2583         char buf[SIZ];
2584         int ret;
2585
2586         if (!command) {
2587                 return -2;
2588         }
2589         if (!proto_response) {
2590                 return -2;
2591         }
2592
2593         CtdlIPC_lock(ipc);
2594         CtdlIPC_putline(ipc, command);
2595         while (1) {
2596                 CtdlIPC_getline(ipc, proto_response);
2597                 if (proto_response[3] == '*')
2598                         instant_msgs = 1;
2599                 ret = atoi(proto_response);
2600                 strcpy(proto_response, &proto_response[4]);
2601                 switch (ret / 100) {
2602                 default:        /* Unknown, punt */
2603                 case 2: /* OK */
2604                 case 3: /* MORE_DATA */
2605                 case 5: /* ERROR */
2606                         /* Don't need to do anything */
2607                         break;
2608                 case 1: /* LISTING_FOLLOWS */
2609                         if (to_receive && !*to_receive && bytes_to_receive) {
2610                                 *to_receive = CtdlIPCReadListing(ipc, NULL);
2611                         }
2612                         else {  /* Drain */
2613                                 while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000"));
2614                                 ret = -ret;
2615                         }
2616                         break;
2617                 case 4: /* SEND_LISTING */
2618                         if (to_send) {
2619                                 CtdlIPCSendListing(ipc, to_send);
2620                         }
2621                         else {
2622                                 /* No listing given, fake it */
2623                                 CtdlIPC_putline(ipc, "000");
2624                                 ret = -ret;
2625                         }
2626                         break;
2627                 case 6: /* BINARY_FOLLOWS */
2628                         if (to_receive && !*to_receive && bytes_to_receive) {
2629                                 *bytes_to_receive = extract_long(proto_response, 0);
2630                                 *to_receive = (char *)
2631                                     malloc((size_t) *bytes_to_receive);
2632                                 if (!*to_receive) {
2633                                         ret = -1;
2634                                 }
2635                                 else {
2636                                         serv_read(ipc, *to_receive, *bytes_to_receive);
2637                                 }
2638                         }
2639                         else {
2640                                 /* Drain */
2641                                 size_t drain;
2642
2643                                 drain = extract_long(proto_response, 0);
2644                                 while (drain > SIZ) {
2645                                         serv_read(ipc, buf, SIZ);
2646                                         drain -= SIZ;
2647                                 }
2648                                 serv_read(ipc, buf, drain);
2649                                 ret = -ret;
2650                         }
2651                         break;
2652                 case 7: /* SEND_BINARY */
2653                         if (to_send && bytes_to_send) {
2654                                 serv_write(ipc, to_send, bytes_to_send);
2655                         }
2656                         else if (bytes_to_send) {
2657                                 /* Fake it, send nulls */
2658                                 size_t fake;
2659
2660                                 fake = bytes_to_send;
2661                                 memset(buf, '\0', SIZ);
2662                                 while (fake > SIZ) {
2663                                         serv_write(ipc, buf, SIZ);
2664                                         fake -= SIZ;
2665                                 }
2666                                 serv_write(ipc, buf, fake);
2667                                 ret = -ret;
2668                         }       /* else who knows?  DANGER WILL ROBINSON */
2669                         break;
2670                 case 8: /* START_CHAT_MODE */
2671                         if (!strncasecmp(command, "CHAT", 4)) {
2672                                 /* Don't call chatmode with generic! */
2673                                 CtdlIPC_putline(ipc, "/quit");
2674                                 ret = -ret;
2675                         }
2676                         else {
2677                                 /* In this mode we send then receive listing */
2678                                 if (to_send) {
2679                                         CtdlIPCSendListing(ipc, to_send);
2680                                 }
2681                                 else {
2682                                         /* No listing given, fake it */
2683                                         CtdlIPC_putline(ipc, "000");
2684                                         ret = -ret;
2685                                 }
2686                                 if (to_receive && !*to_receive && bytes_to_receive) {
2687                                         *to_receive = CtdlIPCReadListing(ipc, NULL);
2688                                 }
2689                                 else {  /* Drain */
2690                                         while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000"));
2691                                         ret = -ret;
2692                                 }
2693                         }
2694                         break;
2695                 case 9: /* ASYNC_MSG */
2696                         /* CtdlIPCDoAsync(ret, proto_response); */
2697                         free(CtdlIPCReadListing(ipc, NULL));    /* STUB FIXME */
2698                         break;
2699                 }
2700                 if (ret / 100 != 9)
2701                         break;
2702         }
2703         CtdlIPC_unlock(ipc);
2704         return ret;
2705 }
2706
2707
2708 /*
2709  * Connect to a Citadel on a remote host using a TCP/IP socket
2710  */
2711 static int tcp_connectsock(char *host, char *service) {
2712         struct in6_addr serveraddr;
2713         struct addrinfo hints;
2714         struct addrinfo *res = NULL;
2715         struct addrinfo *ai = NULL;
2716         int rc = (-1);
2717         int sock = (-1);
2718
2719         if ((host == NULL) || IsEmptyStr(host)) {
2720                 service = DEFAULT_HOST;
2721         }
2722         if ((service == NULL) || IsEmptyStr(service)) {
2723                 service = DEFAULT_PORT;
2724         }
2725
2726         memset(&hints, 0x00, sizeof(hints));
2727         hints.ai_flags = AI_NUMERICSERV;
2728         hints.ai_family = AF_UNSPEC;
2729         hints.ai_socktype = SOCK_STREAM;
2730
2731         /*
2732          * Handle numeric IPv4 and IPv6 addresses
2733          */
2734         rc = inet_pton(AF_INET, host, &serveraddr);
2735         if (rc == 1) {          /* dotted quad */
2736                 hints.ai_family = AF_INET;
2737                 hints.ai_flags |= AI_NUMERICHOST;
2738         }
2739         else {
2740                 rc = inet_pton(AF_INET6, host, &serveraddr);
2741                 if (rc == 1) {  /* IPv6 address */
2742                         hints.ai_family = AF_INET6;
2743                         hints.ai_flags |= AI_NUMERICHOST;
2744                 }
2745         }
2746
2747         /* Begin the connection process */
2748
2749         rc = getaddrinfo(host, service, &hints, &res);
2750         if (rc != 0) {
2751                 return (-1);
2752         }
2753
2754         /*
2755          * Try all available addresses until we connect to one or until we run out.
2756          */
2757         for (ai = res; ai != NULL; ai = ai->ai_next) {
2758                 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2759                 if (sock < 0)
2760                         return (-1);
2761
2762                 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
2763                 if (rc >= 0) {
2764                         return (sock);  /* Connected! */
2765                 }
2766                 else {
2767                         close(sock);    /* Failed.  Close the socket to avoid fd leak! */
2768                 }
2769         }
2770         return (-1);
2771 }
2772
2773
2774 /*
2775  * Connect to a Citadel on the local host using a unix domain socket
2776  */
2777 static int uds_connectsock(int *isLocal, char *sockpath) {
2778         struct sockaddr_un addr;
2779         int s;
2780
2781         memset(&addr, 0, sizeof(addr));
2782         addr.sun_family = AF_UNIX;
2783         strncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
2784
2785         s = socket(AF_UNIX, SOCK_STREAM, 0);
2786         if (s < 0) {
2787                 return -1;
2788         }
2789
2790         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
2791                 close(s);
2792                 return -1;
2793         }
2794
2795         *isLocal = 1;
2796         return s;
2797 }
2798
2799
2800 /*
2801  * input binary data from socket
2802  */
2803 static void serv_read(CtdlIPC * ipc, char *buf, unsigned int bytes) {
2804         unsigned int len, rlen;
2805
2806 #if defined(HAVE_OPENSSL)
2807         if (ipc->ssl) {
2808                 serv_read_ssl(ipc, buf, bytes);
2809                 return;
2810         }
2811 #endif
2812         len = 0;
2813         while (len < bytes) {
2814                 rlen = read(ipc->sock, &buf[len], bytes - len);
2815                 if (rlen < 1) {
2816                         connection_died(ipc, 0);
2817                         return;
2818                 }
2819                 len += rlen;
2820         }
2821 }
2822
2823
2824 /*
2825  * send binary to server
2826  */
2827 void serv_write(CtdlIPC * ipc, const char *buf, unsigned int nbytes) {
2828         unsigned int bytes_written = 0;
2829         int retval;
2830
2831 #if defined(HAVE_OPENSSL)
2832         if (ipc->ssl) {
2833                 serv_write_ssl(ipc, buf, nbytes);
2834                 return;
2835         }
2836 #endif
2837         while (bytes_written < nbytes) {
2838                 retval = write(ipc->sock, &buf[bytes_written], nbytes - bytes_written);
2839                 if (retval < 1) {
2840                         connection_died(ipc, 0);
2841                         return;
2842                 }
2843                 bytes_written += retval;
2844         }
2845 }
2846
2847
2848 #ifdef HAVE_OPENSSL
2849
2850 /*
2851  * input binary data from encrypted connection
2852  */
2853 static void serv_read_ssl(CtdlIPC * ipc, char *buf, unsigned int bytes) {
2854         int len, rlen;
2855         char junk[1];
2856
2857         len = 0;
2858         while (len < bytes) {
2859                 if (SSL_want_read(ipc->ssl)) {
2860                         if ((SSL_write(ipc->ssl, junk, 0)) < 1) {
2861                                 error_printf("SSL_write in serv_read:\n");
2862                                 ERR_print_errors_fp(stderr);
2863                         }
2864                 }
2865                 rlen = SSL_read(ipc->ssl, &buf[len], bytes - len);
2866                 if (rlen < 1) {
2867                         long errval;
2868
2869                         errval = SSL_get_error(ipc->ssl, rlen);
2870                         if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
2871                                 sleep(1);
2872                                 continue;
2873                         }
2874                         error_printf("SSL_read in serv_read: %s\n", ERR_reason_error_string(ERR_peek_error()));
2875                         connection_died(ipc, 1);
2876                         return;
2877                 }
2878                 len += rlen;
2879         }
2880 }
2881
2882
2883 /*
2884  * send binary to server encrypted
2885  */
2886 static void serv_write_ssl(CtdlIPC * ipc, const char *buf, unsigned int nbytes) {
2887         unsigned int bytes_written = 0;
2888         int retval;
2889         char junk[1];
2890
2891         while (bytes_written < nbytes) {
2892                 if (SSL_want_write(ipc->ssl)) {
2893                         if ((SSL_read(ipc->ssl, junk, 0)) < 1) {
2894                                 error_printf("SSL_read in serv_write:\n");
2895                                 ERR_print_errors_fp(stderr);
2896                         }
2897                 }
2898                 retval = SSL_write(ipc->ssl, &buf[bytes_written], nbytes - bytes_written);
2899                 if (retval < 1) {
2900                         long errval;
2901
2902                         errval = SSL_get_error(ipc->ssl, retval);
2903                         if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
2904                                 sleep(1);
2905                                 continue;
2906                         }
2907                         if (errval == SSL_ERROR_ZERO_RETURN || errval == SSL_ERROR_SSL) {
2908                                 serv_write(ipc, &buf[bytes_written], nbytes - bytes_written);
2909                                 return;
2910                         }
2911                         error_printf("SSL_write in serv_write: %s\n", ERR_reason_error_string(ERR_peek_error()));
2912                         connection_died(ipc, 1);
2913                         return;
2914                 }
2915                 bytes_written += retval;
2916         }
2917 }
2918
2919
2920 static void CtdlIPC_init_OpenSSL(void) {
2921         int a;
2922         const SSL_METHOD *ssl_method;
2923         DH *dh;
2924
2925         /* already done init */
2926         if (ssl_ctx) {
2927                 return;
2928         }
2929
2930         /* Get started */
2931         a = 0;
2932         ssl_ctx = NULL;
2933         dh = NULL;
2934         SSL_load_error_strings();
2935         SSLeay_add_ssl_algorithms();
2936
2937         /* Set up the SSL context in which we will oeprate */
2938         ssl_method = SSLv23_client_method();
2939         ssl_ctx = SSL_CTX_new(ssl_method);
2940         if (!ssl_ctx) {
2941                 error_printf("SSL_CTX_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2942                 return;
2943         }
2944         /* Any reasonable cipher we can get */
2945         if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
2946                 error_printf("No ciphers available for encryption\n");
2947                 return;
2948         }
2949         SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
2950
2951         /* Load DH parameters into the context */
2952         dh = DH_new();
2953         if (!dh) {
2954                 error_printf("Can't allocate a DH object: %s\n", ERR_reason_error_string(ERR_get_error()));
2955                 return;
2956         }
2957
2958         if (!(DH_generate_parameters_ex(dh, 128, DH_GENERATOR_2, 0))) {
2959                 error_printf("Can't generate DH parameters: %s\n", ERR_reason_error_string(ERR_get_error()));
2960                 DH_free(dh);
2961                 return;
2962         }
2963
2964         SSL_CTX_set_tmp_dh(ssl_ctx, dh);
2965         DH_free(dh);
2966 }
2967
2968 #endif                          /* HAVE_OPENSSL */
2969
2970
2971 int ReadNetworkChunk(CtdlIPC * ipc) {
2972         fd_set read_fd;
2973         int ret = 0;
2974         int err = 0;
2975         struct timeval tv;
2976         size_t n;
2977
2978         tv.tv_sec = 1;
2979         tv.tv_usec = 1000;
2980         /*tries = 0; */
2981         n = 0;
2982         while (1) {
2983                 errno = 0;
2984                 FD_ZERO(&read_fd);
2985                 FD_SET(ipc->sock, &read_fd);
2986                 ret = select(ipc->sock + 1, &read_fd, NULL, NULL, &tv);
2987
2988                 if (ret > 0) {
2989
2990                         *(ipc->BufPtr) = '\0';
2991                         n = recv(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1, 0);
2992                         if (n > 0) {
2993                                 ipc->BufPtr[n] = '\0';
2994                                 ipc->BufUsed += n;
2995                                 return n;
2996                         }
2997                         else {
2998                                 return n;
2999                         }
3000                 }
3001                 else if (ret < 0) {
3002                         if (!(errno == EINTR || errno == EAGAIN))
3003                                 error_printf("\nselect failed: %d %s\n", err, strerror(err));
3004                         return -1;
3005                 }
3006         }
3007 }
3008
3009
3010 /*
3011  * input string from socket - implemented in terms of serv_read()
3012  */
3013 #ifdef CHUNKED_READ
3014
3015 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf) {
3016         int i, ntries;
3017         char *aptr, *bptr, *aeptr, *beptr;
3018
3019
3020         beptr = buf + SIZ;
3021 #if defined(HAVE_OPENSSL)
3022         if (ipc->ssl) {
3023
3024                 /* Read one character at a time. */
3025                 for (i = 0;; i++) {
3026                         serv_read(ipc, &buf[i], 1);
3027                         if (buf[i] == '\n' || i == (SIZ - 1))
3028                                 break;
3029                 }
3030
3031                 /* If we got a long line, discard characters until the newline. */
3032                 if (i == (SIZ - 1))
3033                         while (buf[i] != '\n')
3034                                 serv_read(ipc, &buf[i], 1);
3035
3036                 /* Strip the trailing newline (and carriage return, if present) */
3037                 if (i >= 0 && buf[i] == 10)
3038                         buf[i--] = 0;
3039                 if (i >= 0 && buf[i] == 13)
3040                         buf[i--] = 0;
3041         }
3042         else
3043 #endif
3044         {
3045                 if (ipc->Buf == NULL) {
3046                         ipc->BufSize = SIZ;
3047                         ipc->Buf = (char *) malloc(ipc->BufSize + 10);
3048                         *(ipc->Buf) = '\0';
3049                         ipc->BufPtr = ipc->Buf;
3050                 }
3051
3052                 ntries = 0;
3053                 if (ipc->BufUsed == 0)
3054                         ReadNetworkChunk(ipc);
3055
3056                 bptr = buf;
3057                 while (1) {
3058                         aptr = ipc->BufPtr;
3059                         aeptr = ipc->Buf + ipc->BufSize;
3060                         while ((aptr < aeptr) && (bptr < beptr) && (*aptr != '\0') && (*aptr != '\n'))
3061                                 *(bptr++) = *(aptr++);
3062                         if ((*aptr == '\n') && (aptr < aeptr)) {
3063                                 /* Terminate it right, remove the line breaks */
3064                                 while ((aptr < aeptr) && ((*aptr == '\n') || (*aptr == '\r')))
3065                                         aptr++;
3066                                 while ((aptr < aeptr) && (*(aptr + 1) == '\0'))
3067                                         aptr++;
3068                                 *(bptr++) = '\0';
3069                                 if ((bptr > buf + 1) && (*(bptr - 1) == '\r'))
3070                                         *(--bptr) = '\0';
3071
3072                                 /* is there more in the buffer we need to read later? */
3073                                 if (ipc->Buf + ipc->BufUsed > aptr) {
3074                                         ipc->BufPtr = aptr;
3075                                 }
3076                                 else {
3077                                         ipc->BufUsed = 0;
3078                                         ipc->BufPtr = ipc->Buf;
3079                                 }
3080                                 return;
3081
3082                         }
3083                         // should we move our read stuf to the bufferstart so we have more space at the end?
3084                         else if ((ipc->BufPtr != ipc->Buf) && (ipc->BufUsed > (ipc->BufSize - (ipc->BufSize / 4)))) {
3085                                 size_t NewBufSize = ipc->BufSize * 2;
3086                                 int delta = (ipc->BufPtr - ipc->Buf);
3087                                 char *NewBuf;
3088
3089                                 /* if the line would end after our buffer, we should use a bigger buffer. */
3090                                 NewBuf = (char *) malloc(NewBufSize + 10);
3091                                 memcpy(NewBuf, ipc->BufPtr, ipc->BufUsed - delta);
3092                                 free(ipc->Buf);
3093                                 ipc->Buf = ipc->BufPtr = NewBuf;
3094                                 ipc->BufUsed -= delta;
3095                                 ipc->BufSize = NewBufSize;
3096                         }
3097                         if (ReadNetworkChunk(ipc) < 0) {
3098                                 return;
3099                         }
3100                 }
3101         }
3102 }
3103
3104 #else                           /* CHUNKED_READ */
3105
3106 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf) {
3107         int i;
3108
3109         /* Read one character at a time. */
3110         for (i = 0;; i++) {
3111                 serv_read(ipc, &buf[i], 1);
3112                 if (buf[i] == '\n' || i == (SIZ - 1))
3113                         break;
3114         }
3115
3116         /* If we got a long line, discard characters until the newline. */
3117         if (i == (SIZ - 1))
3118                 while (buf[i] != '\n')
3119                         serv_read(ipc, &buf[i], 1);
3120
3121         /* Strip the trailing newline (and carriage return, if present) */
3122         if (i >= 0 && buf[i] == 10)
3123                 buf[i--] = 0;
3124         if (i >= 0 && buf[i] == 13)
3125                 buf[i--] = 0;
3126 }
3127
3128
3129 #endif                          /* CHUNKED_READ */
3130
3131
3132 void CtdlIPC_chat_recv(CtdlIPC * ipc, char *buf) {
3133         CtdlIPC_getline(ipc, buf);
3134 }
3135
3136 /*
3137  * send line to server - implemented in terms of serv_write()
3138  */
3139 static void CtdlIPC_putline(CtdlIPC * ipc, const char *buf) {
3140         char *cmd = NULL;
3141         int len;
3142
3143         len = strlen(buf);
3144         cmd = malloc(len + 2);
3145         if (!cmd) {
3146                 /* This requires no extra memory */
3147                 serv_write(ipc, buf, len);
3148                 serv_write(ipc, "\n", 1);
3149         }
3150         else {
3151                 /* This is network-optimized */
3152                 strncpy(cmd, buf, len);
3153                 strcpy(cmd + len, "\n");
3154                 serv_write(ipc, cmd, len + 1);
3155                 free(cmd);
3156         }
3157
3158         ipc->last_command_sent = time(NULL);
3159 }
3160
3161
3162 void CtdlIPC_chat_send(CtdlIPC * ipc, const char *buf) {
3163         CtdlIPC_putline(ipc, buf);
3164 }
3165
3166
3167 /*
3168  * attach to server
3169  */
3170 CtdlIPC *CtdlIPC_new(int argc, char **argv, char *hostbuf, char *portbuf) {
3171         int a;
3172         char cithost[SIZ];
3173         char citport[SIZ];
3174         char sockpath[SIZ];
3175         CtdlIPC *ipc;
3176
3177         ipc = malloc(sizeof(struct _CtdlIPC));
3178         if (!ipc) {
3179                 return 0;
3180         }
3181 #if defined(HAVE_OPENSSL)
3182         ipc->ssl = NULL;
3183         CtdlIPC_init_OpenSSL();
3184 #endif
3185         ipc->sock = -1;         /* Not connected */
3186         ipc->isLocal = 0;       /* Not local, of course! */
3187         ipc->downloading = 0;
3188         ipc->uploading = 0;
3189         ipc->last_command_sent = 0L;
3190         ipc->network_status_cb = NULL;
3191         ipc->Buf = NULL;
3192         ipc->BufUsed = 0;
3193         ipc->BufPtr = NULL;
3194
3195         strcpy(cithost, DEFAULT_HOST);  /* default host */
3196         strcpy(citport, DEFAULT_PORT);  /* default port */
3197
3198         /* Allow caller to supply our values */
3199         if (hostbuf && strlen(hostbuf) > 0) {
3200                 strcpy(cithost, hostbuf);
3201         }
3202         if (portbuf && strlen(portbuf) > 0) {
3203                 strcpy(citport, portbuf);
3204         }
3205
3206         /* Read host/port from command line if present */
3207         for (a = 0; a < argc; ++a) {
3208                 if (a == 0) {
3209                         /* do nothing */
3210                 }
3211                 else if (a == 1) {
3212                         strcpy(cithost, argv[a]);
3213                 }
3214                 else if (a == 2) {
3215                         strcpy(citport, argv[a]);
3216                 }
3217                 else {
3218                         error_printf("%s: usage: ", argv[0]);
3219                         error_printf("%s [host] [port] ", argv[0]);
3220                         free(ipc);
3221                         errno = EINVAL;
3222                         return 0;
3223                 }
3224         }
3225
3226         if ((!strcmp(cithost, "localhost")) || (!strcmp(cithost, "127.0.0.1"))) {
3227                 ipc->isLocal = 1;
3228         }
3229
3230         /* If we're using a unix domain socket we can do a bunch of stuff */
3231         if (!strcmp(cithost, UDS)) {
3232                 if (!strcasecmp(citport, DEFAULT_PORT)) {
3233                         snprintf(sockpath, sizeof sockpath, "%s", file_citadel_socket);
3234                 }
3235                 else {
3236                         snprintf(sockpath, sizeof sockpath, "%s/%s", citport, "citadel.socket");
3237                 }
3238                 printf("[%s]\n", sockpath);
3239                 ipc->sock = uds_connectsock(&(ipc->isLocal), sockpath);
3240                 if (ipc->sock == -1) {
3241                         free(ipc);
3242                         return 0;
3243                 }
3244                 if (hostbuf != NULL)
3245                         strcpy(hostbuf, cithost);
3246                 if (portbuf != NULL)
3247                         strcpy(portbuf, sockpath);
3248                 strcpy(ipc->ip_hostname, "");
3249                 strcpy(ipc->ip_address, "");
3250                 return ipc;
3251         }
3252
3253         printf("[%s:%s]\n", cithost, citport);
3254         ipc->sock = tcp_connectsock(cithost, citport);
3255         if (ipc->sock == -1) {
3256                 free(ipc);
3257                 return 0;
3258         }
3259
3260
3261         /* Learn the actual network identity of the host to which we are connected */
3262
3263         struct sockaddr_in6 clientaddr;
3264         unsigned int addrlen = sizeof(clientaddr);
3265
3266         ipc->ip_hostname[0] = 0;
3267         ipc->ip_address[0] = 0;
3268
3269         getpeername(ipc->sock, (struct sockaddr *) &clientaddr, &addrlen);
3270         getnameinfo((struct sockaddr *) &clientaddr, addrlen, ipc->ip_hostname, sizeof ipc->ip_hostname, NULL, 0, 0);
3271         getnameinfo((struct sockaddr *) &clientaddr, addrlen, ipc->ip_address, sizeof ipc->ip_address, NULL, 0, NI_NUMERICHOST);
3272
3273         /* stuff other things elsewhere */
3274
3275         if (hostbuf != NULL)
3276                 strcpy(hostbuf, cithost);
3277         if (portbuf != NULL)
3278                 strcpy(portbuf, citport);
3279         return ipc;
3280 }
3281
3282
3283 /*
3284  * Disconnect and delete the IPC class (destructor)
3285  */
3286 void CtdlIPC_delete(CtdlIPC * ipc) {
3287 #ifdef HAVE_OPENSSL
3288         if (ipc->ssl) {
3289                 SSL_shutdown(ipc->ssl);
3290                 SSL_free(ipc->ssl);
3291                 ipc->ssl = NULL;
3292         }
3293 #endif
3294         if (ipc->sock > -1) {
3295                 shutdown(ipc->sock, 2); /* Close it up */
3296                 ipc->sock = -1;
3297         }
3298         if (ipc->Buf != NULL)
3299                 free(ipc->Buf);
3300         ipc->Buf = NULL;
3301         ipc->BufPtr = NULL;
3302         free(ipc);
3303 }
3304
3305
3306 /*
3307  * Disconnect and delete the IPC class (destructor)
3308  * Also NULLs out the pointer
3309  */
3310 void CtdlIPC_delete_ptr(CtdlIPC ** pipc) {
3311         CtdlIPC_delete(*pipc);
3312         *pipc = NULL;
3313 }