a2f583c6e909b77348eaf2af1de18ec8e7e2b880
[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 are 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                 rret[0]->RRnewmail = extract_long(cret, 9);
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                                                 striplt(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                                                 striplt(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                                                 striplt(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]->timescalled = extract_long(cret, 3);
1907                 uret[0]->posted = extract_long(cret, 4);
1908                 uret[0]->axlevel = extract_int(cret, 5);
1909                 uret[0]->usernum = extract_long(cret, 6);
1910                 uret[0]->lastcall = extract_long(cret, 7);
1911                 uret[0]->USuserpurge = extract_int(cret, 8);
1912         }
1913         return ret;
1914 }
1915
1916
1917 /* ASUP */
1918 int CtdlIPCAideSetUserParameters(CtdlIPC * ipc, const struct ctdluser *uret, char *cret) {
1919         int ret;
1920         char *aaa;
1921
1922         if (!cret)
1923                 return -2;
1924         if (!uret)
1925                 return -2;
1926
1927         aaa = (char *) malloc(strlen(uret->fullname) + strlen(uret->password) + 84);
1928         if (!aaa)
1929                 return -1;
1930
1931         sprintf(aaa, "ASUP %s|%s|%d|%ld|%ld|%d|%ld|%ld|%d",
1932                 uret->fullname, uret->password, uret->flags, uret->timescalled,
1933                 uret->posted, uret->axlevel, uret->usernum, uret->lastcall, uret->USuserpurge);
1934         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1935         free(aaa);
1936         return ret;
1937 }
1938
1939
1940 /* AGEA */
1941 int CtdlIPCAideGetEmailAddresses(CtdlIPC * ipc, const char *who, char *target_buf, char *cret) {
1942         int ret;
1943         char aaa[SIZ];
1944         char *emailaddrs = NULL;
1945         size_t emailaddrs_len = 0;
1946
1947         sprintf(aaa, "AGEA %s", who);
1948         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &emailaddrs, &emailaddrs_len, cret);
1949
1950         if (ret / 100 == 1) {
1951                 strcpy(target_buf, emailaddrs);
1952         }
1953
1954         if (emailaddrs != NULL) {
1955                 free(emailaddrs);
1956         }
1957
1958         return ret;
1959 }
1960
1961
1962 /* ASEA */
1963 int CtdlIPCAideSetEmailAddresses(CtdlIPC * ipc, const char *who, char *emailaddrs, char *cret) {
1964         char aaa[SIZ];
1965         int ret;
1966
1967         if (!who)
1968                 return -2;
1969         if (!emailaddrs)
1970                 return -2;
1971         if (!cret)
1972                 return -2;
1973
1974         sprintf(aaa, "ASEA %s", who);
1975         ret = CtdlIPCGenericCommand(ipc, aaa, emailaddrs, 0, NULL, NULL, cret);
1976         return ret;
1977 }
1978
1979
1980 /* GPEX */
1981
1982 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
1983
1984 /* caller must free the struct ExpirePolicy */
1985 int CtdlIPCGetMessageExpirationPolicy(CtdlIPC * ipc, GPEXWhichPolicy which, struct ExpirePolicy **policy, char *cret) {
1986         static char *proto[] = {
1987                 strof(roompolicy),
1988                 strof(floorpolicy),
1989                 strof(sitepolicy),
1990                 strof(mailboxespolicy)
1991         };
1992         char cmd[256];
1993         int ret;
1994
1995         if (!cret)
1996                 return -2;
1997         if (!policy)
1998                 return -2;
1999         if (!*policy)
2000                 *policy = (struct ExpirePolicy *) calloc(1, sizeof(struct ExpirePolicy));
2001         if (!*policy)
2002                 return -1;
2003         if (which < 0 || which > 3)
2004                 return -2;
2005
2006         sprintf(cmd, "GPEX %s", proto[which]);
2007         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2008         if (ret / 100 == 2) {
2009                 policy[0]->expire_mode = extract_int(cret, 0);
2010                 policy[0]->expire_value = extract_int(cret, 1);
2011         }
2012         return ret;
2013 }
2014
2015
2016 /* SPEX */
2017
2018 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2019
2020 /* policy is 0 = inherit, 1 = no purge, 2 = by count, 3 = by age (days) */
2021 int CtdlIPCSetMessageExpirationPolicy(CtdlIPC * ipc, int which, struct ExpirePolicy *policy, char *cret) {
2022         char aaa[38];
2023         char *whichvals[] = { "room", "floor", "site", "mailboxes" };
2024
2025         if (!cret)
2026                 return -2;
2027         if (which < 0 || which > 3)
2028                 return -2;
2029         if (!policy)
2030                 return -2;
2031         if (policy->expire_mode < 0 || policy->expire_mode > 3)
2032                 return -2;
2033         if (policy->expire_mode >= 2 && policy->expire_value < 1)
2034                 return -2;
2035
2036         sprintf(aaa, "SPEX %s|%d|%d", whichvals[which], policy->expire_mode, policy->expire_value);
2037         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2038 }
2039
2040
2041 /* CONF GET */
2042 int CtdlIPCGetSystemConfig(CtdlIPC * ipc, char **listing, char *cret) {
2043         size_t bytes;
2044
2045         if (!cret)
2046                 return -2;
2047         if (!listing)
2048                 return -2;
2049         if (*listing)
2050                 return -2;
2051
2052         return CtdlIPCGenericCommand(ipc, "CONF GET", NULL, 0, listing, &bytes, cret);
2053 }
2054
2055
2056 /* CONF SET */
2057 int CtdlIPCSetSystemConfig(CtdlIPC * ipc, const char *listing, char *cret) {
2058         if (!cret)
2059                 return -2;
2060         if (!listing)
2061                 return -2;
2062
2063         return CtdlIPCGenericCommand(ipc, "CONF SET", listing, strlen(listing), NULL, NULL, cret);
2064 }
2065
2066
2067 /* CONF GETSYS */
2068 int CtdlIPCGetSystemConfigByType(CtdlIPC * ipc, const char *mimetype, char **listing, char *cret) {
2069         int ret;
2070         char *aaa;
2071         size_t bytes;
2072
2073         if (!cret)
2074                 return -2;
2075         if (!mimetype)
2076                 return -2;
2077         if (!listing)
2078                 return -2;
2079         if (*listing)
2080                 return -2;
2081
2082         aaa = malloc(strlen(mimetype) + 13);
2083         if (!aaa)
2084                 return -1;
2085         sprintf(aaa, "CONF GETSYS|%s", mimetype);
2086         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
2087         free(aaa);
2088         return ret;
2089 }
2090
2091
2092 /* CONF PUTSYS */
2093 int CtdlIPCSetSystemConfigByType(CtdlIPC * ipc, const char *mimetype, const char *listing, char *cret) {
2094         int ret;
2095         char *aaa;
2096
2097         if (!cret)
2098                 return -2;
2099         if (!mimetype)
2100                 return -2;
2101         if (!listing)
2102                 return -2;
2103
2104         aaa = malloc(strlen(mimetype) + 13);
2105         if (!aaa)
2106                 return -1;
2107         sprintf(aaa, "CONF PUTSYS|%s", mimetype);
2108         ret = CtdlIPCGenericCommand(ipc, aaa, listing, strlen(listing), NULL, NULL, cret);
2109         free(aaa);
2110         return ret;
2111 }
2112
2113
2114 /* GNET */
2115 int CtdlIPCGetRoomNetworkConfig(CtdlIPC * ipc, char **listing, char *cret) {
2116         size_t bytes;
2117
2118         if (!cret)
2119                 return -2;
2120         if (!listing)
2121                 return -2;
2122         if (*listing)
2123                 return -2;
2124
2125         return CtdlIPCGenericCommand(ipc, "GNET", NULL, 0, listing, &bytes, cret);
2126 }
2127
2128
2129 /* SNET */
2130 int CtdlIPCSetRoomNetworkConfig(CtdlIPC * ipc, const char *listing, char *cret) {
2131         if (!cret)
2132                 return -2;
2133         if (!listing)
2134                 return -2;
2135
2136         return CtdlIPCGenericCommand(ipc, "SNET", listing, strlen(listing), NULL, NULL, cret);
2137 }
2138
2139
2140 /* REQT */
2141 int CtdlIPCRequestClientLogout(CtdlIPC * ipc, int session, char *cret) {
2142         char aaa[64];
2143
2144         if (!cret)
2145                 return -2;
2146         if (session < 0)
2147                 return -2;
2148
2149         sprintf(aaa, "REQT %d", session);
2150         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2151 }
2152
2153
2154 /* SEEN */
2155 int CtdlIPCSetMessageSeen(CtdlIPC * ipc, long msgnum, int seen, char *cret) {
2156         char aaa[27];
2157
2158         if (!cret)
2159                 return -2;
2160         if (msgnum < 0)
2161                 return -2;
2162
2163         sprintf(aaa, "SEEN %ld|%d", msgnum, seen ? 1 : 0);
2164         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2165 }
2166
2167
2168 /* STLS */
2169 int CtdlIPCStartEncryption(CtdlIPC * ipc, char *cret) {
2170         int a;
2171         int r;
2172         char buf[SIZ];
2173
2174 #ifdef HAVE_OPENSSL
2175         SSL *temp_ssl;
2176
2177         /* New SSL object */
2178         temp_ssl = SSL_new(ssl_ctx);
2179         if (!temp_ssl) {
2180                 error_printf("SSL_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2181                 return -2;
2182         }
2183         /* Pointless flag waving */
2184 #if SSLEAY_VERSION_NUMBER >= 0x0922
2185         SSL_set_session_id_context(temp_ssl, (const unsigned char *) "Citadel SID", 14);
2186 #endif
2187
2188         /* Associate network connection with SSL object */
2189         if (SSL_set_fd(temp_ssl, ipc->sock) < 1) {
2190                 error_printf("SSL_set_fd failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2191                 return -2;
2192         }
2193
2194         if (status_hook != NULL) {
2195                 status_hook("Requesting encryption...\r");
2196         }
2197
2198         /* Ready to start SSL/TLS */
2199         r = CtdlIPCGenericCommand(ipc, "STLS", NULL, 0, NULL, NULL, cret);
2200         if (r / 100 != 2) {
2201                 error_printf("Server can't start TLS: %s\n", buf);
2202                 endtls(temp_ssl);
2203                 return r;
2204         }
2205
2206         /* Do SSL/TLS handshake */
2207         if ((a = SSL_connect(temp_ssl)) < 1) {
2208                 error_printf("SSL_connect failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2209                 endtls(temp_ssl);
2210                 return -2;
2211         }
2212         ipc->ssl = temp_ssl;
2213
2214         error_printf("Encrypting with %s cipher %s\n",
2215                      SSL_CIPHER_get_version(SSL_get_current_cipher(ipc->ssl)), SSL_CIPHER_get_name(SSL_get_current_cipher(ipc->ssl))
2216             );
2217         return r;
2218 #else
2219         return 0;
2220 #endif                          /* HAVE_OPENSSL */
2221 }
2222
2223
2224 #ifdef HAVE_OPENSSL
2225 static void endtls(SSL * ssl) {
2226         if (ssl) {
2227                 SSL_shutdown(ssl);
2228                 SSL_free(ssl);
2229         }
2230 }
2231 #endif
2232
2233
2234 /* QDIR */
2235 int CtdlIPCDirectoryLookup(CtdlIPC * ipc, const char *address, char *cret) {
2236         int ret;
2237         char *aaa;
2238
2239         if (!address)
2240                 return -2;
2241         if (!cret)
2242                 return -2;
2243
2244         aaa = (char *) malloc(strlen(address) + 6);
2245         if (!aaa)
2246                 return -1;
2247
2248         sprintf(aaa, "QDIR %s", address);
2249         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2250         free(aaa);
2251         return ret;
2252 }
2253
2254
2255 /* IPGM */
2256 int CtdlIPCInternalProgram(CtdlIPC * ipc, int secret, char *cret) {
2257         char aaa[30];
2258
2259         if (!cret)
2260                 return -2;
2261         sprintf(aaa, "IPGM %d", secret);
2262         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2263 }
2264
2265
2266
2267
2268 /* ************************************************************************** */
2269
2270 /*           Stuff below this line is not for public consumption            */
2271
2272 /* ************************************************************************** */
2273
2274
2275 /* Read a listing from the server up to 000.  Append to dest if it exists */
2276 char *CtdlIPCReadListing(CtdlIPC * ipc, char *dest) {
2277         size_t length = 0;
2278         size_t linelength;
2279         char *ret = NULL;
2280         char aaa[SIZ];
2281
2282         ret = dest;
2283         if (ret != NULL) {
2284                 length = strlen(ret);
2285         }
2286         else {
2287                 length = 0;
2288         }
2289
2290         while (CtdlIPC_getline(ipc, aaa), strcmp(aaa, "000")) {
2291                 linelength = strlen(aaa);
2292                 ret = (char *) realloc(ret, (size_t) (length + linelength + 2));
2293                 if (ret) {
2294                         strcpy(&ret[length], aaa);
2295                         length += linelength;
2296                         strcpy(&ret[length++], "\n");
2297                 }
2298         }
2299
2300         return (ret);
2301 }
2302
2303
2304 /* Send a listing to the server; generate the ending 000. */
2305 int CtdlIPCSendListing(CtdlIPC * ipc, const char *listing) {
2306         char *text;
2307
2308         text = (char *) malloc(strlen(listing) + 6);
2309         if (text) {
2310                 strcpy(text, listing);
2311                 while (text[strlen(text) - 1] == '\n')
2312                         text[strlen(text) - 1] = '\0';
2313                 strcat(text, "\n000");
2314                 CtdlIPC_putline(ipc, text);
2315                 free(text);
2316                 text = NULL;
2317         }
2318         else {
2319                 /* Malloc failed but we are committed to send */
2320                 /* This may result in extra blanks at the bottom */
2321                 CtdlIPC_putline(ipc, text);
2322                 CtdlIPC_putline(ipc, "000");
2323         }
2324         return 0;
2325 }
2326
2327
2328 /* Partial read of file from server */
2329 size_t CtdlIPCPartialRead(CtdlIPC * ipc, void **buf, size_t offset, size_t bytes, char *cret) {
2330         size_t len = 0;
2331         char aaa[SIZ];
2332
2333         if (!buf)
2334                 return 0;
2335         if (!cret)
2336                 return 0;
2337         if (bytes < 1)
2338                 return 0;
2339
2340         CtdlIPC_lock(ipc);
2341         sprintf(aaa, "READ %d|%d", (int) offset, (int) bytes);
2342         CtdlIPC_putline(ipc, aaa);
2343         CtdlIPC_getline(ipc, aaa);
2344         if (aaa[0] != '6')
2345                 strcpy(cret, &aaa[4]);
2346         else {
2347                 len = extract_long(&aaa[4], 0);
2348                 *buf = (void *) realloc(*buf, (size_t) (offset + len));
2349                 if (*buf) {
2350                         /* I know what I'm doing */
2351                         serv_read(ipc, ((char *) (*buf) + offset), len);
2352                 }
2353                 else {
2354                         /* We have to read regardless */
2355                         serv_read(ipc, aaa, len);
2356                         len = 0;
2357                 }
2358         }
2359         CtdlIPC_unlock(ipc);
2360         return len;
2361 }
2362
2363
2364 /* CLOS */
2365 int CtdlIPCEndDownload(CtdlIPC * ipc, char *cret) {
2366         int ret;
2367
2368         if (!cret)
2369                 return -2;
2370         if (!ipc->downloading)
2371                 return -2;
2372
2373         ret = CtdlIPCGenericCommand(ipc, "CLOS", NULL, 0, NULL, NULL, cret);
2374         if (ret / 100 == 2)
2375                 ipc->downloading = 0;
2376         return ret;
2377 }
2378
2379
2380 /* MSGP */
2381 int CtdlIPCSpecifyPreferredFormats(CtdlIPC * ipc, char *cret, char *formats) {
2382         int ret;
2383         char cmd[SIZ];
2384
2385         snprintf(cmd, sizeof cmd, "MSGP %s", formats);
2386         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2387         return ret;
2388 }
2389
2390
2391
2392 /* READ */
2393 int CtdlIPCReadDownload(CtdlIPC * ipc, void **buf, size_t bytes, size_t resume, void (*progress_gauge_callback)
2394                          (CtdlIPC *, unsigned long, unsigned long), char *cret) {
2395         size_t len;
2396
2397         if (!cret)
2398                 return -1;
2399         if (!buf)
2400                 return -1;
2401         if (*buf)
2402                 return -1;
2403         if (!ipc->downloading)
2404                 return -1;
2405
2406         len = resume;
2407         if (progress_gauge_callback)
2408                 progress_gauge_callback(ipc, len, bytes);
2409         while (len < bytes) {
2410                 size_t block;
2411
2412                 block = CtdlIPCPartialRead(ipc, buf, len, 4096, cret);
2413                 if (block == 0) {
2414                         free(*buf);
2415                         return 0;
2416                 }
2417                 len += block;
2418                 if (progress_gauge_callback)
2419                         progress_gauge_callback(ipc, len, bytes);
2420         }
2421         return len;
2422 }
2423
2424 /* READ - pipelined */
2425 int CtdlIPCHighSpeedReadDownload(CtdlIPC * ipc, void **buf, size_t bytes, size_t resume, void (*progress_gauge_callback)
2426                                   (CtdlIPC *, unsigned long, unsigned long), char *cret) {
2427         size_t len;
2428         int calls;              /* How many calls in the pipeline */
2429         int i;                  /* iterator */
2430         char aaa[4096];
2431
2432         if (!cret)
2433                 return -1;
2434         if (!buf)
2435                 return -1;
2436         if (*buf)
2437                 return -1;
2438         if (!ipc->downloading)
2439                 return -1;
2440
2441         *buf = (void *) realloc(*buf, bytes - resume);
2442         if (!*buf)
2443                 return -1;
2444
2445         len = 0;
2446         CtdlIPC_lock(ipc);
2447         if (progress_gauge_callback)
2448                 progress_gauge_callback(ipc, len, bytes);
2449
2450         /* How many calls will be in the pipeline? */
2451         calls = (bytes - resume) / 4096;
2452         if ((bytes - resume) % 4096)
2453                 calls++;
2454
2455         /* Send all requests at once */
2456         for (i = 0; i < calls; i++) {
2457                 sprintf(aaa, "READ %d|4096", (int) (i * 4096 + resume));
2458                 CtdlIPC_putline(ipc, aaa);
2459         }
2460
2461         /* Receive all responses at once */
2462         for (i = 0; i < calls; i++) {
2463                 CtdlIPC_getline(ipc, aaa);
2464                 if (aaa[0] != '6')
2465                         strcpy(cret, &aaa[4]);
2466                 else {
2467                         len = extract_long(&aaa[4], 0);
2468                         /* I know what I'm doing */
2469                         serv_read(ipc, ((char *) (*buf) + (i * 4096)), len);
2470                 }
2471                 if (progress_gauge_callback)
2472                         progress_gauge_callback(ipc, i * 4096 + len, bytes);
2473         }
2474         CtdlIPC_unlock(ipc);
2475         return len;
2476 }
2477
2478
2479 /* UCLS */
2480 int CtdlIPCEndUpload(CtdlIPC * ipc, int discard, char *cret) {
2481         int ret;
2482         char cmd[8];
2483
2484         if (!cret)
2485                 return -1;
2486         if (!ipc->uploading)
2487                 return -1;
2488
2489         sprintf(cmd, "UCLS %d", discard ? 0 : 1);
2490         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2491         ipc->uploading = 0;
2492         return ret;
2493 }
2494
2495
2496 /* WRIT */
2497 int CtdlIPCWriteUpload(CtdlIPC * ipc, FILE * uploadFP, void (*progress_gauge_callback)
2498                         (CtdlIPC *, unsigned long, unsigned long), char *cret) {
2499         int ret = -1;
2500         size_t offset = 0;
2501         size_t bytes;
2502         char aaa[SIZ];
2503         char buf[4096];
2504         FILE *fd = uploadFP;
2505         int ferr;
2506
2507         if (!cret)
2508                 return -1;
2509
2510         fseek(fd, 0L, SEEK_END);
2511         bytes = ftell(fd);
2512         rewind(fd);
2513
2514         if (progress_gauge_callback)
2515                 progress_gauge_callback(ipc, 0, bytes);
2516
2517         while (offset < bytes) {
2518                 size_t to_write;
2519
2520                 /* Read some data in */
2521                 to_write = fread(buf, 1, 4096, fd);
2522                 if (!to_write) {
2523                         if (feof(fd) || ferror(fd))
2524                                 break;
2525                 }
2526                 sprintf(aaa, "WRIT %d", (int) to_write);
2527                 CtdlIPC_putline(ipc, aaa);
2528                 CtdlIPC_getline(ipc, aaa);
2529                 strcpy(cret, &aaa[4]);
2530                 ret = atoi(aaa);
2531                 if (aaa[0] == '7') {
2532                         to_write = extract_long(&aaa[4], 0);
2533
2534                         serv_write(ipc, buf, to_write);
2535                         offset += to_write;
2536                         if (progress_gauge_callback)
2537                                 progress_gauge_callback(ipc, offset, bytes);
2538                         /* Detect short reads and back up if needed */
2539                         /* offset will never be negative anyway */
2540                         fseek(fd, (signed) offset, SEEK_SET);
2541                 }
2542                 else {
2543                         break;
2544                 }
2545         }
2546         if (progress_gauge_callback)
2547                 progress_gauge_callback(ipc, 1, 1);
2548         ferr = ferror(fd);
2549         fclose(fd);
2550         return (!ferr ? ret : -2);
2551 }
2552
2553
2554 /*
2555  * Generic command method.  This method should handle any server command
2556  * except for CHAT.  It takes the following arguments:
2557  *
2558  * ipc                  The server to speak with
2559  * command              Preformatted command to send to server
2560  * to_send              A text or binary file to send to server
2561  *                      (only sent if server requests it)
2562  * bytes_to_send        The number of bytes in to_send (required if
2563  *                      sending binary, optional if sending listing)
2564  * to_receive           Pointer to a NULL pointer, if the server
2565  *                      sends text or binary we will allocate memory
2566  *                      for the file and stuff it here
2567  * bytes_to_receive     If a file is received, we will store its
2568  *                      byte count here
2569  * proto_response       The protocol response.  Caller must provide
2570  *                      this buffer and ensure that it is at least
2571  *                      128 bytes in length.
2572  *
2573  * This function returns a number equal to the protocol response number,
2574  * -1 if an internal error occurred, -2 if caller provided bad values,
2575  * or 0 - the protocol response number if bad values were found during
2576  * the protocol exchange.
2577  * 
2578  * It stores the protocol response string (minus the number) in 
2579  * protocol_response as described above.  Some commands send additional
2580  * data in this string.
2581  */
2582 int CtdlIPCGenericCommand(CtdlIPC * ipc,
2583                           const char *command,
2584                           const char *to_send,
2585                           size_t bytes_to_send, char **to_receive, size_t *bytes_to_receive, char *proto_response) {
2586         char buf[SIZ];
2587         int ret;
2588
2589         if (!command) {
2590                 return -2;
2591         }
2592         if (!proto_response) {
2593                 return -2;
2594         }
2595
2596         CtdlIPC_lock(ipc);
2597         CtdlIPC_putline(ipc, command);
2598         while (1) {
2599                 CtdlIPC_getline(ipc, proto_response);
2600                 if (proto_response[3] == '*')
2601                         instant_msgs = 1;
2602                 ret = atoi(proto_response);
2603                 strcpy(proto_response, &proto_response[4]);
2604                 switch (ret / 100) {
2605                 default:        /* Unknown, punt */
2606                 case 2: /* OK */
2607                 case 3: /* MORE_DATA */
2608                 case 5: /* ERROR */
2609                         /* Don't need to do anything */
2610                         break;
2611                 case 1: /* LISTING_FOLLOWS */
2612                         if (to_receive && !*to_receive && bytes_to_receive) {
2613                                 *to_receive = CtdlIPCReadListing(ipc, NULL);
2614                         }
2615                         else {  /* Drain */
2616                                 while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000"));
2617                                 ret = -ret;
2618                         }
2619                         break;
2620                 case 4: /* SEND_LISTING */
2621                         if (to_send) {
2622                                 CtdlIPCSendListing(ipc, to_send);
2623                         }
2624                         else {
2625                                 /* No listing given, fake it */
2626                                 CtdlIPC_putline(ipc, "000");
2627                                 ret = -ret;
2628                         }
2629                         break;
2630                 case 6: /* BINARY_FOLLOWS */
2631                         if (to_receive && !*to_receive && bytes_to_receive) {
2632                                 *bytes_to_receive = extract_long(proto_response, 0);
2633                                 *to_receive = (char *)
2634                                     malloc((size_t) *bytes_to_receive);
2635                                 if (!*to_receive) {
2636                                         ret = -1;
2637                                 }
2638                                 else {
2639                                         serv_read(ipc, *to_receive, *bytes_to_receive);
2640                                 }
2641                         }
2642                         else {
2643                                 /* Drain */
2644                                 size_t drain;
2645
2646                                 drain = extract_long(proto_response, 0);
2647                                 while (drain > SIZ) {
2648                                         serv_read(ipc, buf, SIZ);
2649                                         drain -= SIZ;
2650                                 }
2651                                 serv_read(ipc, buf, drain);
2652                                 ret = -ret;
2653                         }
2654                         break;
2655                 case 7: /* SEND_BINARY */
2656                         if (to_send && bytes_to_send) {
2657                                 serv_write(ipc, to_send, bytes_to_send);
2658                         }
2659                         else if (bytes_to_send) {
2660                                 /* Fake it, send nulls */
2661                                 size_t fake;
2662
2663                                 fake = bytes_to_send;
2664                                 memset(buf, '\0', SIZ);
2665                                 while (fake > SIZ) {
2666                                         serv_write(ipc, buf, SIZ);
2667                                         fake -= SIZ;
2668                                 }
2669                                 serv_write(ipc, buf, fake);
2670                                 ret = -ret;
2671                         }       /* else who knows?  DANGER WILL ROBINSON */
2672                         break;
2673                 case 8: /* START_CHAT_MODE */
2674                         if (!strncasecmp(command, "CHAT", 4)) {
2675                                 /* Don't call chatmode with generic! */
2676                                 CtdlIPC_putline(ipc, "/quit");
2677                                 ret = -ret;
2678                         }
2679                         else {
2680                                 /* In this mode we send then receive listing */
2681                                 if (to_send) {
2682                                         CtdlIPCSendListing(ipc, to_send);
2683                                 }
2684                                 else {
2685                                         /* No listing given, fake it */
2686                                         CtdlIPC_putline(ipc, "000");
2687                                         ret = -ret;
2688                                 }
2689                                 if (to_receive && !*to_receive && bytes_to_receive) {
2690                                         *to_receive = CtdlIPCReadListing(ipc, NULL);
2691                                 }
2692                                 else {  /* Drain */
2693                                         while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000"));
2694                                         ret = -ret;
2695                                 }
2696                         }
2697                         break;
2698                 case 9: /* ASYNC_MSG */
2699                         /* CtdlIPCDoAsync(ret, proto_response); */
2700                         free(CtdlIPCReadListing(ipc, NULL));    /* STUB FIXME */
2701                         break;
2702                 }
2703                 if (ret / 100 != 9)
2704                         break;
2705         }
2706         CtdlIPC_unlock(ipc);
2707         return ret;
2708 }
2709
2710
2711 /*
2712  * Connect to a Citadel on a remote host using a TCP/IP socket
2713  */
2714 static int tcp_connectsock(char *host, char *service) {
2715         struct in6_addr serveraddr;
2716         struct addrinfo hints;
2717         struct addrinfo *res = NULL;
2718         struct addrinfo *ai = NULL;
2719         int rc = (-1);
2720         int sock = (-1);
2721
2722         if ((host == NULL) || IsEmptyStr(host)) {
2723                 service = DEFAULT_HOST;
2724         }
2725         if ((service == NULL) || IsEmptyStr(service)) {
2726                 service = DEFAULT_PORT;
2727         }
2728
2729         memset(&hints, 0x00, sizeof(hints));
2730         hints.ai_flags = AI_NUMERICSERV;
2731         hints.ai_family = AF_UNSPEC;
2732         hints.ai_socktype = SOCK_STREAM;
2733
2734         /*
2735          * Handle numeric IPv4 and IPv6 addresses
2736          */
2737         rc = inet_pton(AF_INET, host, &serveraddr);
2738         if (rc == 1) {          /* dotted quad */
2739                 hints.ai_family = AF_INET;
2740                 hints.ai_flags |= AI_NUMERICHOST;
2741         }
2742         else {
2743                 rc = inet_pton(AF_INET6, host, &serveraddr);
2744                 if (rc == 1) {  /* IPv6 address */
2745                         hints.ai_family = AF_INET6;
2746                         hints.ai_flags |= AI_NUMERICHOST;
2747                 }
2748         }
2749
2750         /* Begin the connection process */
2751
2752         rc = getaddrinfo(host, service, &hints, &res);
2753         if (rc != 0) {
2754                 return (-1);
2755         }
2756
2757         /*
2758          * Try all available addresses until we connect to one or until we run out.
2759          */
2760         for (ai = res; ai != NULL; ai = ai->ai_next) {
2761                 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2762                 if (sock < 0)
2763                         return (-1);
2764
2765                 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
2766                 if (rc >= 0) {
2767                         return (sock);  /* Connected! */
2768                 }
2769                 else {
2770                         close(sock);    /* Failed.  Close the socket to avoid fd leak! */
2771                 }
2772         }
2773         return (-1);
2774 }
2775
2776
2777 /*
2778  * Connect to a Citadel on the local host using a unix domain socket
2779  */
2780 static int uds_connectsock(int *isLocal, char *sockpath) {
2781         struct sockaddr_un addr;
2782         int s;
2783
2784         memset(&addr, 0, sizeof(addr));
2785         addr.sun_family = AF_UNIX;
2786         strncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
2787
2788         s = socket(AF_UNIX, SOCK_STREAM, 0);
2789         if (s < 0) {
2790                 return -1;
2791         }
2792
2793         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
2794                 close(s);
2795                 return -1;
2796         }
2797
2798         *isLocal = 1;
2799         return s;
2800 }
2801
2802
2803 /*
2804  * input binary data from socket
2805  */
2806 static void serv_read(CtdlIPC * ipc, char *buf, unsigned int bytes) {
2807         unsigned int len, rlen;
2808
2809 #if defined(HAVE_OPENSSL)
2810         if (ipc->ssl) {
2811                 serv_read_ssl(ipc, buf, bytes);
2812                 return;
2813         }
2814 #endif
2815         len = 0;
2816         while (len < bytes) {
2817                 rlen = read(ipc->sock, &buf[len], bytes - len);
2818                 if (rlen < 1) {
2819                         connection_died(ipc, 0);
2820                         return;
2821                 }
2822                 len += rlen;
2823         }
2824 }
2825
2826
2827 /*
2828  * send binary to server
2829  */
2830 void serv_write(CtdlIPC * ipc, const char *buf, unsigned int nbytes) {
2831         unsigned int bytes_written = 0;
2832         int retval;
2833
2834 #if defined(HAVE_OPENSSL)
2835         if (ipc->ssl) {
2836                 serv_write_ssl(ipc, buf, nbytes);
2837                 return;
2838         }
2839 #endif
2840         while (bytes_written < nbytes) {
2841                 retval = write(ipc->sock, &buf[bytes_written], nbytes - bytes_written);
2842                 if (retval < 1) {
2843                         connection_died(ipc, 0);
2844                         return;
2845                 }
2846                 bytes_written += retval;
2847         }
2848 }
2849
2850
2851 #ifdef HAVE_OPENSSL
2852
2853 /*
2854  * input binary data from encrypted connection
2855  */
2856 static void serv_read_ssl(CtdlIPC * ipc, char *buf, unsigned int bytes) {
2857         int len, rlen;
2858         char junk[1];
2859
2860         len = 0;
2861         while (len < bytes) {
2862                 if (SSL_want_read(ipc->ssl)) {
2863                         if ((SSL_write(ipc->ssl, junk, 0)) < 1) {
2864                                 error_printf("SSL_write in serv_read:\n");
2865                                 ERR_print_errors_fp(stderr);
2866                         }
2867                 }
2868                 rlen = SSL_read(ipc->ssl, &buf[len], bytes - len);
2869                 if (rlen < 1) {
2870                         long errval;
2871
2872                         errval = SSL_get_error(ipc->ssl, rlen);
2873                         if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
2874                                 sleep(1);
2875                                 continue;
2876                         }
2877                         error_printf("SSL_read in serv_read: %s\n", ERR_reason_error_string(ERR_peek_error()));
2878                         connection_died(ipc, 1);
2879                         return;
2880                 }
2881                 len += rlen;
2882         }
2883 }
2884
2885
2886 /*
2887  * send binary to server encrypted
2888  */
2889 static void serv_write_ssl(CtdlIPC * ipc, const char *buf, unsigned int nbytes) {
2890         unsigned int bytes_written = 0;
2891         int retval;
2892         char junk[1];
2893
2894         while (bytes_written < nbytes) {
2895                 if (SSL_want_write(ipc->ssl)) {
2896                         if ((SSL_read(ipc->ssl, junk, 0)) < 1) {
2897                                 error_printf("SSL_read in serv_write:\n");
2898                                 ERR_print_errors_fp(stderr);
2899                         }
2900                 }
2901                 retval = SSL_write(ipc->ssl, &buf[bytes_written], nbytes - bytes_written);
2902                 if (retval < 1) {
2903                         long errval;
2904
2905                         errval = SSL_get_error(ipc->ssl, retval);
2906                         if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
2907                                 sleep(1);
2908                                 continue;
2909                         }
2910                         if (errval == SSL_ERROR_ZERO_RETURN || errval == SSL_ERROR_SSL) {
2911                                 serv_write(ipc, &buf[bytes_written], nbytes - bytes_written);
2912                                 return;
2913                         }
2914                         error_printf("SSL_write in serv_write: %s\n", ERR_reason_error_string(ERR_peek_error()));
2915                         connection_died(ipc, 1);
2916                         return;
2917                 }
2918                 bytes_written += retval;
2919         }
2920 }
2921
2922
2923 static void CtdlIPC_init_OpenSSL(void) {
2924         int a;
2925         const SSL_METHOD *ssl_method;
2926         DH *dh;
2927
2928         /* already done init */
2929         if (ssl_ctx) {
2930                 return;
2931         }
2932
2933         /* Get started */
2934         a = 0;
2935         ssl_ctx = NULL;
2936         dh = NULL;
2937         SSL_load_error_strings();
2938         SSLeay_add_ssl_algorithms();
2939
2940         /* Set up the SSL context in which we will oeprate */
2941         ssl_method = SSLv23_client_method();
2942         ssl_ctx = SSL_CTX_new(ssl_method);
2943         if (!ssl_ctx) {
2944                 error_printf("SSL_CTX_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2945                 return;
2946         }
2947         /* Any reasonable cipher we can get */
2948         if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
2949                 error_printf("No ciphers available for encryption\n");
2950                 return;
2951         }
2952         SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
2953
2954         /* Load DH parameters into the context */
2955         dh = DH_new();
2956         if (!dh) {
2957                 error_printf("Can't allocate a DH object: %s\n", ERR_reason_error_string(ERR_get_error()));
2958                 return;
2959         }
2960
2961         if (!(DH_generate_parameters_ex(dh, 128, DH_GENERATOR_2, 0))) {
2962                 error_printf("Can't generate DH parameters: %s\n", ERR_reason_error_string(ERR_get_error()));
2963                 DH_free(dh);
2964                 return;
2965         }
2966
2967         SSL_CTX_set_tmp_dh(ssl_ctx, dh);
2968         DH_free(dh);
2969 }
2970
2971 #endif                          /* HAVE_OPENSSL */
2972
2973
2974 int ReadNetworkChunk(CtdlIPC * ipc) {
2975         fd_set read_fd;
2976         int ret = 0;
2977         int err = 0;
2978         struct timeval tv;
2979         size_t n;
2980
2981         tv.tv_sec = 1;
2982         tv.tv_usec = 1000;
2983         /*tries = 0; */
2984         n = 0;
2985         while (1) {
2986                 errno = 0;
2987                 FD_ZERO(&read_fd);
2988                 FD_SET(ipc->sock, &read_fd);
2989                 ret = select(ipc->sock + 1, &read_fd, NULL, NULL, &tv);
2990
2991                 if (ret > 0) {
2992
2993                         *(ipc->BufPtr) = '\0';
2994                         n = recv(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1, 0);
2995                         if (n > 0) {
2996                                 ipc->BufPtr[n] = '\0';
2997                                 ipc->BufUsed += n;
2998                                 return n;
2999                         }
3000                         else {
3001                                 return n;
3002                         }
3003                 }
3004                 else if (ret < 0) {
3005                         if (!(errno == EINTR || errno == EAGAIN))
3006                                 error_printf("\nselect failed: %d %s\n", err, strerror(err));
3007                         return -1;
3008                 }
3009         }
3010 }
3011
3012
3013 /*
3014  * input string from socket - implemented in terms of serv_read()
3015  */
3016 #ifdef CHUNKED_READ
3017
3018 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf) {
3019         int i, ntries;
3020         char *aptr, *bptr, *aeptr, *beptr;
3021
3022
3023         beptr = buf + SIZ;
3024 #if defined(HAVE_OPENSSL)
3025         if (ipc->ssl) {
3026
3027                 /* Read one character at a time. */
3028                 for (i = 0;; i++) {
3029                         serv_read(ipc, &buf[i], 1);
3030                         if (buf[i] == '\n' || i == (SIZ - 1))
3031                                 break;
3032                 }
3033
3034                 /* If we got a long line, discard characters until the newline. */
3035                 if (i == (SIZ - 1))
3036                         while (buf[i] != '\n')
3037                                 serv_read(ipc, &buf[i], 1);
3038
3039                 /* Strip the trailing newline (and carriage return, if present) */
3040                 if (i >= 0 && buf[i] == 10)
3041                         buf[i--] = 0;
3042                 if (i >= 0 && buf[i] == 13)
3043                         buf[i--] = 0;
3044         }
3045         else
3046 #endif
3047         {
3048                 if (ipc->Buf == NULL) {
3049                         ipc->BufSize = SIZ;
3050                         ipc->Buf = (char *) malloc(ipc->BufSize + 10);
3051                         *(ipc->Buf) = '\0';
3052                         ipc->BufPtr = ipc->Buf;
3053                 }
3054
3055                 ntries = 0;
3056                 if (ipc->BufUsed == 0)
3057                         ReadNetworkChunk(ipc);
3058
3059                 bptr = buf;
3060                 while (1) {
3061                         aptr = ipc->BufPtr;
3062                         aeptr = ipc->Buf + ipc->BufSize;
3063                         while ((aptr < aeptr) && (bptr < beptr) && (*aptr != '\0') && (*aptr != '\n'))
3064                                 *(bptr++) = *(aptr++);
3065                         if ((*aptr == '\n') && (aptr < aeptr)) {
3066                                 /* Terminate it right, remove the line breaks */
3067                                 while ((aptr < aeptr) && ((*aptr == '\n') || (*aptr == '\r')))
3068                                         aptr++;
3069                                 while ((aptr < aeptr) && (*(aptr + 1) == '\0'))
3070                                         aptr++;
3071                                 *(bptr++) = '\0';
3072                                 if ((bptr > buf + 1) && (*(bptr - 1) == '\r'))
3073                                         *(--bptr) = '\0';
3074
3075                                 /* is there more in the buffer we need to read later? */
3076                                 if (ipc->Buf + ipc->BufUsed > aptr) {
3077                                         ipc->BufPtr = aptr;
3078                                 }
3079                                 else {
3080                                         ipc->BufUsed = 0;
3081                                         ipc->BufPtr = ipc->Buf;
3082                                 }
3083                                 return;
3084
3085                         }
3086                         // should we move our read stuf to the bufferstart so we have more space at the end?
3087                         else if ((ipc->BufPtr != ipc->Buf) && (ipc->BufUsed > (ipc->BufSize - (ipc->BufSize / 4)))) {
3088                                 size_t NewBufSize = ipc->BufSize * 2;
3089                                 int delta = (ipc->BufPtr - ipc->Buf);
3090                                 char *NewBuf;
3091
3092                                 /* if the line would end after our buffer, we should use a bigger buffer. */
3093                                 NewBuf = (char *) malloc(NewBufSize + 10);
3094                                 memcpy(NewBuf, ipc->BufPtr, ipc->BufUsed - delta);
3095                                 free(ipc->Buf);
3096                                 ipc->Buf = ipc->BufPtr = NewBuf;
3097                                 ipc->BufUsed -= delta;
3098                                 ipc->BufSize = NewBufSize;
3099                         }
3100                         if (ReadNetworkChunk(ipc) < 0) {
3101                                 return;
3102                         }
3103                 }
3104         }
3105 }
3106
3107 #else                           /* CHUNKED_READ */
3108
3109 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf) {
3110         int i;
3111
3112         /* Read one character at a time. */
3113         for (i = 0;; i++) {
3114                 serv_read(ipc, &buf[i], 1);
3115                 if (buf[i] == '\n' || i == (SIZ - 1))
3116                         break;
3117         }
3118
3119         /* If we got a long line, discard characters until the newline. */
3120         if (i == (SIZ - 1))
3121                 while (buf[i] != '\n')
3122                         serv_read(ipc, &buf[i], 1);
3123
3124         /* Strip the trailing newline (and carriage return, if present) */
3125         if (i >= 0 && buf[i] == 10)
3126                 buf[i--] = 0;
3127         if (i >= 0 && buf[i] == 13)
3128                 buf[i--] = 0;
3129 }
3130
3131
3132 #endif                          /* CHUNKED_READ */
3133
3134
3135 void CtdlIPC_chat_recv(CtdlIPC * ipc, char *buf) {
3136         CtdlIPC_getline(ipc, buf);
3137 }
3138
3139 /*
3140  * send line to server - implemented in terms of serv_write()
3141  */
3142 static void CtdlIPC_putline(CtdlIPC * ipc, const char *buf) {
3143         char *cmd = NULL;
3144         int len;
3145
3146         len = strlen(buf);
3147         cmd = malloc(len + 2);
3148         if (!cmd) {
3149                 /* This requires no extra memory */
3150                 serv_write(ipc, buf, len);
3151                 serv_write(ipc, "\n", 1);
3152         }
3153         else {
3154                 /* This is network-optimized */
3155                 strncpy(cmd, buf, len);
3156                 strcpy(cmd + len, "\n");
3157                 serv_write(ipc, cmd, len + 1);
3158                 free(cmd);
3159         }
3160
3161         ipc->last_command_sent = time(NULL);
3162 }
3163
3164
3165 void CtdlIPC_chat_send(CtdlIPC * ipc, const char *buf) {
3166         CtdlIPC_putline(ipc, buf);
3167 }
3168
3169
3170 /*
3171  * attach to server
3172  */
3173 CtdlIPC *CtdlIPC_new(int argc, char **argv, char *hostbuf, char *portbuf) {
3174         int a;
3175         char cithost[SIZ];
3176         char citport[SIZ];
3177         char sockpath[SIZ];
3178         CtdlIPC *ipc;
3179
3180         ipc = malloc(sizeof(struct _CtdlIPC));
3181         if (!ipc) {
3182                 return 0;
3183         }
3184 #if defined(HAVE_OPENSSL)
3185         ipc->ssl = NULL;
3186         CtdlIPC_init_OpenSSL();
3187 #endif
3188         ipc->sock = -1;         /* Not connected */
3189         ipc->isLocal = 0;       /* Not local, of course! */
3190         ipc->downloading = 0;
3191         ipc->uploading = 0;
3192         ipc->last_command_sent = 0L;
3193         ipc->network_status_cb = NULL;
3194         ipc->Buf = NULL;
3195         ipc->BufUsed = 0;
3196         ipc->BufPtr = NULL;
3197
3198         strcpy(cithost, DEFAULT_HOST);  /* default host */
3199         strcpy(citport, DEFAULT_PORT);  /* default port */
3200
3201         /* Allow caller to supply our values */
3202         if (hostbuf && strlen(hostbuf) > 0) {
3203                 strcpy(cithost, hostbuf);
3204         }
3205         if (portbuf && strlen(portbuf) > 0) {
3206                 strcpy(citport, portbuf);
3207         }
3208
3209         /* Read host/port from command line if present */
3210         for (a = 0; a < argc; ++a) {
3211                 if (a == 0) {
3212                         /* do nothing */
3213                 }
3214                 else if (a == 1) {
3215                         strcpy(cithost, argv[a]);
3216                 }
3217                 else if (a == 2) {
3218                         strcpy(citport, argv[a]);
3219                 }
3220                 else {
3221                         error_printf("%s: usage: ", argv[0]);
3222                         error_printf("%s [host] [port] ", argv[0]);
3223                         free(ipc);
3224                         errno = EINVAL;
3225                         return 0;
3226                 }
3227         }
3228
3229         if ((!strcmp(cithost, "localhost")) || (!strcmp(cithost, "127.0.0.1"))) {
3230                 ipc->isLocal = 1;
3231         }
3232
3233         /* If we're using a unix domain socket we can do a bunch of stuff */
3234         if (!strcmp(cithost, UDS)) {
3235                 if (!strcasecmp(citport, DEFAULT_PORT)) {
3236                         snprintf(sockpath, sizeof sockpath, "%s", file_citadel_socket);
3237                 }
3238                 else {
3239                         snprintf(sockpath, sizeof sockpath, "%s/%s", citport, "citadel.socket");
3240                 }
3241                 printf("[%s]\n", sockpath);
3242                 ipc->sock = uds_connectsock(&(ipc->isLocal), sockpath);
3243                 if (ipc->sock == -1) {
3244                         free(ipc);
3245                         return 0;
3246                 }
3247                 if (hostbuf != NULL)
3248                         strcpy(hostbuf, cithost);
3249                 if (portbuf != NULL)
3250                         strcpy(portbuf, sockpath);
3251                 strcpy(ipc->ip_hostname, "");
3252                 strcpy(ipc->ip_address, "");
3253                 return ipc;
3254         }
3255
3256         printf("[%s:%s]\n", cithost, citport);
3257         ipc->sock = tcp_connectsock(cithost, citport);
3258         if (ipc->sock == -1) {
3259                 free(ipc);
3260                 return 0;
3261         }
3262
3263
3264         /* Learn the actual network identity of the host to which we are connected */
3265
3266         struct sockaddr_in6 clientaddr;
3267         unsigned int addrlen = sizeof(clientaddr);
3268
3269         ipc->ip_hostname[0] = 0;
3270         ipc->ip_address[0] = 0;
3271
3272         getpeername(ipc->sock, (struct sockaddr *) &clientaddr, &addrlen);
3273         getnameinfo((struct sockaddr *) &clientaddr, addrlen, ipc->ip_hostname, sizeof ipc->ip_hostname, NULL, 0, 0);
3274         getnameinfo((struct sockaddr *) &clientaddr, addrlen, ipc->ip_address, sizeof ipc->ip_address, NULL, 0, NI_NUMERICHOST);
3275
3276         /* stuff other things elsewhere */
3277
3278         if (hostbuf != NULL)
3279                 strcpy(hostbuf, cithost);
3280         if (portbuf != NULL)
3281                 strcpy(portbuf, citport);
3282         return ipc;
3283 }
3284
3285
3286 /*
3287  * Disconnect and delete the IPC class (destructor)
3288  */
3289 void CtdlIPC_delete(CtdlIPC * ipc) {
3290 #ifdef HAVE_OPENSSL
3291         if (ipc->ssl) {
3292                 SSL_shutdown(ipc->ssl);
3293                 SSL_free(ipc->ssl);
3294                 ipc->ssl = NULL;
3295         }
3296 #endif
3297         if (ipc->sock > -1) {
3298                 shutdown(ipc->sock, 2); /* Close it up */
3299                 ipc->sock = -1;
3300         }
3301         if (ipc->Buf != NULL)
3302                 free(ipc->Buf);
3303         ipc->Buf = NULL;
3304         ipc->BufPtr = NULL;
3305         free(ipc);
3306 }
3307
3308
3309 /*
3310  * Disconnect and delete the IPC class (destructor)
3311  * Also NULLs out the pointer
3312  */
3313 void CtdlIPC_delete_ptr(CtdlIPC ** pipc) {
3314         CtdlIPC_delete(*pipc);
3315         *pipc = NULL;
3316 }